News: LimeReport new version 1.5 has been released
Вышла новая версия LimeReport 1.5

Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.


Messages - maestro

Pages: [1]
1
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). Программа завершается с сообщением:

Quote
corrupted size vs. prev_size while consolidating

Краш происходит в деструкторах при завершении программы — в момент уничтожения синглтона
LimeReport::ScriptEngineManager, который владеет собственным QJSEngine (и, соответственно, QV4::ExecutionEngine).

3. Шаги для воспроизведения
3.1. Скрипт сборки LimeReport

Code: [Select]
#! /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 тестового проекта

Code: [Select]
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

Code: [Select]
#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:

Quote
********* 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

Стек вызовов на момент краша:

Quote
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, что вызывает повреждение кучи.

2
Я разместил в отчёте изображение, присвоил ему objectName. Изображение генерируется программно. Как можно программно извлечь размеры элемента отчёта (ItemGeometry), чтобы произвести рендер в нужной пропорции? Как извлечь handle самого элемента?

Code: [Select]
LimeReport::ReportEngine report;
qDebug() << report.loadFromFile("report-pic.lrxml");
auto pic = report.findChild<QObject*>("Picture");  // возвращает nullptr

Pages: [1]