Delete the text items ElementPictureFactory::build() creates and drops

parseText() creates a QGraphicsSimpleTextItem for every <text> of an
element (the DXF export reads them later) and appends it to the
primitives it is given. build() draws every primitive twice, once for
the normal and once for the low-zoom picture, and gave the second pass
a fresh `primitives fake_prim` per primitive that went out of scope
with its text items still allocated. dropCache() also removed a
location's primitives without deleting their text items.

LeakSanitizer on the Linux CI sanitizer build (separate PR) stops
every --resave or export of a project whose elements have texts:

  ERROR: LeakSanitizer: detected memory leaks
  Indirect leak of 37200 byte(s) in 75 object(s) allocated from:
    #1 QGraphicsSimpleTextItem::QGraphicsSimpleTextItem(QGraphicsItem*)
    #2 ElementPictureFactory::parseText(...) elementpicturefactory.cpp:658
    #4 ElementPictureFactory::build(...) elementpicturefactory.cpp:324

Give the low-zoom pass one set of primitives for the whole element and
delete its text items when both pictures are drawn; in dropCache(),
take the primitives out of the hash and delete their text items. The
items kept in m_primitives_H for the DXF export are unchanged, and the
destructor deletes those as before.

No file-format change, nothing drawn differently. Tests: the
sanitizer job on Linux fails on master with the report above and
passes with this change; ctest on macOS 64/64. The leak is invisible
to a plain test and to ASan on macOS, which has no LeakSanitizer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Beat Hangartner <beat@hangartners.ch>
This commit is contained in:
Beat Hangartner
2026-10-06 17:42:19 +02:00
parent 0708338bce
commit ad14eb1724
+7 -3
View File
@@ -114,7 +114,8 @@ void ElementPictureFactory::dropCache(const ElementsLocation &location)
m_pictures_H.remove(uuid);
m_low_pictures_H.remove(uuid);
m_pixmap_H.remove(uuid);
m_primitives_H.remove(uuid);
//The text items belong to the cache; nothing else holds them.
qDeleteAll(m_primitives_H.take(uuid).m_texts);
}
/**
@@ -249,6 +250,9 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
QPainter painter;
QPicture pic;
primitives primitives_;
//The low-zoom pass fills a second set of primitives that is only
//drawn, never kept: its text items are deleted at the end.
primitives low_primitives;
if (picture) {
painter.begin(picture);
}
@@ -320,8 +324,7 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
// complex font/text operations.
} else {
parseElement(qde, painter, primitives_);
primitives fake_prim;
parseElement(qde, low_painter, fake_prim);
parseElement(qde, low_painter, low_primitives);
}
}
}
@@ -330,6 +333,7 @@ bool ElementPictureFactory::build(const ElementsLocation &location,
//End of the drawing
painter.end();
low_painter.end();
qDeleteAll(low_primitives.m_texts);
const auto uuid_ = cacheKey(location);
if (!picture) {