LimeReport Forum
General Category | Основное => Discussion | Обсуждение => Topic started by: maestro on April 18, 2026, 04:00:03 am
-
1. Окружение
ОС: Debian 13 (Trixie), amd64
Qt: 6.10.1 (x86_64, shared, GCC 14.2.0)
LimeReport: последняя версия из репозитория, собрана с опцией -DQT_VERSION_MAJOR=6 -DLIMEREPORT_DEMO=ON
Компилятор: GCC 14.2.0
Система сборки: CMake + Ninja
2. Описание проблемы
При одновременном использовании в одном процессе QQmlEngine (с загруженным QML-компонентом) и LimeReport ReportEngine происходит повреждение кучи (heap corruption). Программа завершается с сообщением:
corrupted size vs. prev_size while consolidating
Краш происходит в деструкторах при завершении программы — в момент уничтожения синглтона
LimeReport::ScriptEngineManager, который владеет собственным QJSEngine (и, соответственно, QV4::ExecutionEngine).
3. Шаги для воспроизведения
3.1. Скрипт сборки LimeReport
#! /bin/bash
cmake -G "Ninja" \
-B build \
-S src \
-DQT_VERSION_MAJOR=6 \
-DLIMEREPORT_DEMO=ON \
-DCMAKE_INSTALL_PREFIX=install \
-DCMAKE_TOOLCHAIN_FILE=/root/toolchain.cmake
cmake --build build --config Release --parallel --target install
3.2. CMakeLists.txt тестового проекта
cmake_minimum_required(VERSION 3.14)
project(limereport-tests)
enable_testing()
find_package(Qt6 REQUIRED COMPONENTS Core Test Widgets PrintSupport Qml)
find_library(limereport NAMES limereport limereport-qt6)
find_path(LIME_REPORT_INCLUDE_DIRS
NAMES "LimeReport/LimeReport" "limereport/LimeReport"
PATH_SUFFIXES "include" HINTS ENV CMAKE_PREFIX_PATH)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOMOC ON)
qt_add_executable(tst_limereport_heap tst_limereport_heap.cpp)
add_test(NAME tst_limereport_heap COMMAND tst_limereport_heap)
target_link_libraries(tst_limereport_heap PRIVATE
Qt6::Test Qt6::Core Qt6::Widgets Qt6::PrintSupport Qt6::Qml
${limereport}
)
target_include_directories(tst_limereport_heap PRIVATE ${LIME_REPORT_INCLUDE_DIRS})
# Файл отчёта содержит один элемент — ReportHeader
qt_add_resources(tst_limereport_heap "resources"
PREFIX "/" FILES ReportWithHeadBand.lrxml)
set_target_properties(tst_limereport_heap PROPERTIES
CXX_STANDARD 23 CXX_STANDARD_REQUIRED ON)
3.3. Тестовый файл tst_limereport_heap.cpp
#include <QObject>
#include <QtTest>
#include <QGraphicsScene>
#include <QQmlApplicationEngine>
#include <QQmlComponent>
#include <QVariant>
#include "limereport/LimeReport"
using namespace LimeReport;
// Файл отчёта .lrxml встроен в ресурсы приложения.
// Отчёт содержит единственный элемент — ReportHeader.
static const char ResourceFileName[] = ":/ReportWithHeadBand.lrxml";
class TestLimereportHeap: public QObject
{
Q_OBJECT
private slots:
// Контрольный тест: только QQmlEngine без загрузки компонента.
// Ошибка НЕ воспроизводится — одного создания движка недостаточно.
void testQV4Init()
{
auto qmlEngine = std::make_unique<QQmlEngine>();
std::unique_ptr<ReportEngine> report = std::make_unique<ReportEngine>();
report->loadFromFile(ResourceFileName);
std::shared_ptr<QGraphicsScene> scene(report->createPreviewScene());
// Деструкторы вызываются в порядке: scene, report, qmlEngine
}
// Минимальный тест, воспроизводящий ошибку.
// Ключевое отличие от testQV4Init: загружается QML-компонент.
// Это инициализирует QV4::ExecutableCompilationUnit и связанные
// глобальные структуры QV4, что приводит к конфликту с внутренним
// QJSEngine библиотеки LimeReport при завершении программы.
void testQV4Compile()
{
auto qmlEngine = std::make_unique<QQmlEngine>();
// Загрузка минимального QML-компонента из строки.
// Именно этот шаг является триггером ошибки.
QQmlComponent component(qmlEngine.get());
component.setData("import QtQuick\nItem {}", QUrl());
qDebug() << component.status() << component.errorString();
// ReportEngine создаёт ScriptEngineManager (синглтон),
// который внутри владеет собственным QJSEngine.
std::unique_ptr<ReportEngine> report = std::make_unique<ReportEngine>();
qDebug() << report->loadFromFile(ResourceFileName);
std::shared_ptr<QGraphicsScene> scene(report->createPreviewScene());
// При выходе из функции уничтожаются scene, report и qmlEngine.
// Синглтон ScriptEngineManager уничтожается позже — через atexit,
// после того как QV4-инфраструктура qmlEngine уже освобождена.
// Это вызывает heap corruption.
}
};
QTEST_MAIN(TestLimereportHeap)
#include "tst_limereport_heap.moc"
4. Фактический результат
Тест testQV4Compile проходит (все assertions успешны), однако после завершения тестового фреймворка процесс падает с сообщением в stderr:
********* Start testing of TestLimereportHeap *********
Config: Using QtTest library 6.10.1, Qt 6.10.1 (x86_64-little_endian-lp64
shared (dynamic) release build; by GCC 14.2.0), debian 13
PASS : TestLimereportHeap::initTestCase()
QDEBUG : TestLimereportHeap::testQV4Compile() true ""
PASS : TestLimereportHeap::testQV4Compile()
PASS : TestLimereportHeap::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped, 0 blacklisted, 260ms
********* Finished testing of TestLimereportHeap *********
corrupted size vs. prev_size while consolidating
Стек вызовов на момент краша:
libc!malloc_printerr
libc!_int_free_merge_chunk
libc!__GI___libc_free
libQt6Qml!QV4::ExecutableCompilationUnit::~ExecutableCompilationUnit
libQt6Qml!QV4::ExecutionEngine::~ExecutionEngine
libQt6Qml!QJSEngine::~QJSEngine
liblimereport-qt6!LimeReport::ScriptEngineManager::~ScriptEngineManager
liblimereport-qt6!LimeReport::Singleton<ScriptEngineManager>::destroy
libc!__run_exit_handlers <-- вызов через atexit
5. Ожидаемый результат
Процесс завершается без ошибок. LimeReport корректно сосуществует с QQmlEngine в одном процессе.
6. Анализ причины
Проблема воспроизводится при выполнении всех трёх условий одновременно:
- В процессе создаётся и уничтожается QQmlEngine с загруженным QML-компонентом (QQmlComponent::setData / load). Простое создание QQmlEngine или QJSEngine без загрузки компонента ошибки не вызывает.
- Создаётся LimeReport::ReportEngine, который лениво инициализирует синглтон ScriptEngineManager, содержащий собственный QJSEngine.
- Синглтон ScriptEngineManager регистрируется через механизм atexit и уничтожается после выхода из main — то есть позже, чем QQmlEngine, управляемый умным указателем внутри теста.
При загрузке QML-компонента Qt компилирует его в байткод и создаёт QV4::ExecutableCompilationUnit, который регистрирует данные в глобальном состоянии QV4 (таблицы типов, строковые пулы и т.д.). При уничтожении QQmlEngine это глобальное состояние частично освобождается. Впоследствии, когда atexit вызывает деструктор синглтона ScriptEngineManager, его внутренний QJSEngine обращается к уже освобождённым структурам QV4, что вызывает повреждение кучи.