From 8dd0fb45b040322cd66a1638c6f145f5e948e422 Mon Sep 17 00:00:00 2001 From: ppizarror Date: Sun, 7 Jun 2026 20:55:26 -0400 Subject: [PATCH] Fix heap-after-free due to document rescan --- src/latexdocument.cpp | 18 ++++++++++++++++++ src/tests/latexdocument_t.cpp | 36 ++++++++++++++++++++++++++++++++++- src/tests/latexdocument_t.h | 1 + 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/latexdocument.cpp b/src/latexdocument.cpp index 11d580e67b..002855c486 100644 --- a/src/latexdocument.cpp +++ b/src/latexdocument.cpp @@ -63,6 +63,10 @@ LatexDocument::~LatexDocument() mLineSnapshot.clear(); qDeleteAll(docStructure); + + // make sure this (about to be freed) document can never linger in the + // shared project cache as a dangling pointer (see getListOfDocs) + if (lp) lp->projectDocuments.clear(); } void LatexDocument::setFileName(const QString &fileName) @@ -1943,11 +1947,13 @@ void LatexDocument::setMasterDocument(LatexDocument *doc, bool recheck) void LatexDocument::addChild(LatexDocument *doc) { childDocs.insert(doc); + lp->projectDocuments.clear(); // graph changed, invalidate cache } void LatexDocument::removeChild(LatexDocument *doc) { childDocs.remove(doc); + lp->projectDocuments.clear(); // graph changed, invalidate cache } bool LatexDocument::containsChild(LatexDocument *doc) const @@ -2168,6 +2174,9 @@ void LatexDocuments::addDocument(LatexDocument *document, bool hidden) } connect(document, SIGNAL(updateBibTeXFiles()), SLOT(bibTeXFilesNeedUpdate())); document->parent = this; + // document set changed, invalidate cached project document lists (see getListOfDocs) + if (document->lp) document->lp->projectDocuments.clear(); + if (masterDocument && masterDocument->lp) masterDocument->lp->projectDocuments.clear(); if (masterDocument) { // repaint all docs foreach (const LatexDocument *doc, documents) { @@ -2179,6 +2188,10 @@ void LatexDocuments::addDocument(LatexDocument *document, bool hidden) void LatexDocuments::deleteDocument(LatexDocument *document, bool hidden, bool purge) { + // document set / master-child graph is about to change, invalidate cached + // project document lists so they can't keep dangling pointers (see getListOfDocs) + if (document->lp) document->lp->projectDocuments.clear(); + if (masterDocument && masterDocument->lp) masterDocument->lp->projectDocuments.clear(); // save caching information document->saveCachingData(m_cachingFolder); if (!hidden) @@ -2474,6 +2487,8 @@ LatexDocument *LatexDocuments::findDocumentFromName(const QString &fileName) con void LatexDocuments::reorder(const QList &order) { if (order.size() != documents.size()) qDebug() << "Warning: Size of list of documents for reordering differs from current documents"; + // order of the cached project document list may change (see getListOfDocs) + if (masterDocument && masterDocument->lp) masterDocument->lp->projectDocuments.clear(); foreach (LatexDocument *doc, order) { int n = documents.removeAll(doc); if (n > 1) qDebug() << "Warning: document listed multiple times in LatexDocuments"; @@ -2627,6 +2642,9 @@ void LatexDocuments::updateBibFiles(bool updateFiles) void LatexDocuments::removeDocs(QStringList removeIncludes) { + // documents may be removed/deleted below, invalidate cached project + // document lists so they can't keep dangling pointers (see getListOfDocs) + if (masterDocument && masterDocument->lp) masterDocument->lp->projectDocuments.clear(); QSet lstRecheckLabels; foreach (QString fname, removeIncludes) { LatexDocument *dc = findDocumentFromName(fname); diff --git a/src/tests/latexdocument_t.cpp b/src/tests/latexdocument_t.cpp index ff0c8d8d1c..c9a75311c0 100644 --- a/src/tests/latexdocument_t.cpp +++ b/src/tests/latexdocument_t.cpp @@ -9,5 +9,39 @@ LatexDocumentTest::LatexDocumentTest(LatexEditorView* view): m_edView(view){ m_doc=m_edView->getDocument(); } -#endif +/*! + * Regression test for the use-after-free crash in LatexDocument::getListOfDocs() + * (SIGABRT in getListOfDocs while rescanning a multi-file project). + */ +void LatexDocumentTest::getListOfDocsCacheInvalidation(){ + LatexDocuments docs; + LatexDocument *master = new LatexDocument(); + LatexDocument *child = new LatexDocument(); + master->setFileName("/tmp/texstudio_test_master.tex"); + child->setFileName("/tmp/texstudio_test_child.tex"); + docs.addDocument(master, false); // visible master + docs.addDocument(child, true); // hidden included document + + // register child as an included document of master + master->addChild(child); + + // first call builds and caches the project document list + QList before = master->getListOfDocs(); + QVERIFY2(before.contains(master), "master document missing from list"); + QVERIFY2(before.contains(child), "child document missing from list"); + + // break the relationship: the cache must be invalidated so the stale + // (potentially freed) child pointer is not returned again + master->removeChild(child); + QList after = master->getListOfDocs(); + QVERIFY2(after.contains(master), "master document missing after removeChild"); + QVERIFY2(!after.contains(child), "stale child returned from cached project document list (cache not invalidated)"); + // docs has no destructor that deletes its documents, so free them here. + // master->childDocs no longer references child and neither document holds + // a masterDocument pointer, so deletion order is irrelevant. + delete master; + delete child; +} + +#endif diff --git a/src/tests/latexdocument_t.h b/src/tests/latexdocument_t.h index 5b29800b0b..65052ffb4d 100644 --- a/src/tests/latexdocument_t.h +++ b/src/tests/latexdocument_t.h @@ -14,6 +14,7 @@ class LatexDocumentTest: public QObject{ LatexEditorView *m_edView; LatexDocument *m_doc; private slots: + void getListOfDocsCacheInvalidation(); }; #endif