From 44b2129d5ae07886f1ae73a82eea39136c4414b3 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Sat, 10 Oct 2026 19:58:01 +1300 Subject: [PATCH] Fix a crash opening a project whose folio report arrow has no terminal A folio report arrow's text looks for a wire to show: on the arrow itself, else on the arrow it is linked to, through that arrow's first terminal. Some report symbols have no terminal -- ref_voyant_2_h.elmt in the collection, and older collections' arrows -- so a project linking an arrow with no wire to one of them crashed QElectroTech as it opened. The other arrow's terminals are now checked first, as the arrow's own already were. Found on a project attached to the forum (saved by 0.70), which crashed on opening. Test: tst_resaveunchanged::reportArrowWithoutTerminalOpens builds the case from Projet_vierge.qet's own symbols; it crashes without the fix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_019Wga9B92BNurDEtxxtCDxf --- .../dynamicelementtextitem.cpp | 4 +- tests/qttest/tst_resaveunchanged.cpp | 55 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/sources/qetgraphicsitem/dynamicelementtextitem.cpp b/sources/qetgraphicsitem/dynamicelementtextitem.cpp index cb0ec0ab3..c99eb4a37 100644 --- a/sources/qetgraphicsitem/dynamicelementtextitem.cpp +++ b/sources/qetgraphicsitem/dynamicelementtextitem.cpp @@ -1469,7 +1469,9 @@ void DynamicElementTextItem::setPotentialConductor() m_watched_conductor = c_list.first(); connect(m_watched_conductor.data(), &Conductor::propertiesChange, this, &DynamicElementTextItem::conductorPropertiesChanged); } - else if(m_watched_conductor.isNull() && m_other_report) + else if(m_watched_conductor.isNull() && m_other_report + //A report symbol can have no terminal (some older collections') + && !m_other_report.data()->terminals().isEmpty()) { if (!m_other_report.data()->terminals().first()->conductors().isEmpty()) { diff --git a/tests/qttest/tst_resaveunchanged.cpp b/tests/qttest/tst_resaveunchanged.cpp index e80b4cb50..9198bf850 100644 --- a/tests/qttest/tst_resaveunchanged.cpp +++ b/tests/qttest/tst_resaveunchanged.cpp @@ -160,6 +160,61 @@ private slots: QVERIFY2(sb < sa, "the coil's contacts were saved in another order than the file's"); } + // A folio report arrow linked to one that has no terminal -- such as + // ref_voyant_2_h.elmt from the collection -- crashed QElectroTech on + // opening: the arrow's text looked for a wire on the other arrow's + // first terminal. Projet_vierge.qet with a previous-folio arrow (one + // terminal, a label text) on folio 1 linked to a ref_voyant_2_h on + // folio 2. + void reportArrowWithoutTerminalOpens() + { + QByteArray xml = read(QStringLiteral(QET_EXAMPLES_DIR "/Projet_vierge.qet")); + const QByteArray a = "{0a000000-0000-4000-8000-00000000000a}", + b = "{0b000000-0000-4000-8000-00000000000b}"; + const QByteArray arrow = + "" + "" + "" + "xlabel" + ""; + const QByteArray no_terminal = + "" + "" + ""; + QVERIFY(xml.contains("ref_voyant_2_h.elmt") && xml.contains("01previous_folio.elmt")); + // put @p element first in the elements of folio @p n + auto put = [&xml](int n, const QByteArray &element) { + int at = -1; + for (int i = 0 ; i <= n ; ++i) { + at = xml.indexOf("", at), open = xml.indexOf("", at), + end = xml.indexOf("", at); + if (empty >= 0 && empty < end && (open < 0 || empty < open)) + xml.replace(empty, int(qstrlen("")), "" + element + ""); + else if (open >= 0 && open < end) + xml.insert(open + int(qstrlen("")), element); + else + return false; + return true; + }; + QVERIFY(put(0, arrow)); + QVERIFY(put(1, no_terminal)); + const QString in = m_dir.filePath(QStringLiteral("report_without_terminal.qet")); + QFile f(in); + QVERIFY(f.open(QIODevice::WriteOnly)); + f.write(xml); + f.close(); + const QString saved = resave(in); + QVERIFY2(!saved.isEmpty(), "--resave failed: QElectroTech crashed opening the project"); + QVERIFY2(read(saved).contains(b), "the arrow without a terminal was not kept"); + } + // A title-block value that is a single space is kept through two saves // (#973), and a value with accents comes back as it went in. void singleSpaceValueKept()