Control: tags -1 + patch
Dear Maintainer,
Further analysis showed that upstream commit
294293edbaf9ba03f169c25e88005dca5f9b79e5 alone
is not sufficient when the full test suite runs sequentially.
Instances of File were also leaked in readWrite() (src/tests/tests.cpp) and in
src/tests/attributes.cpp. These leaked instances remain active in the Qt event
loop,
and when their terminal is destroyed, subsequent event loop iterations in later
tests
trigger a use-after-free SIGSEGV in termpaint_text_measurement_feed_codepoint.
Upstream subsequently resolved these leaks as well:
- 9657fd3ad95536c19c8b3f68987614d81b04845c ("tests: readWrite: Fix memory
leak.")
- bbfd527dd697b4ffe57894c8f66da5cd18c50e91 ("tests: cleanAttributesFileName:
Fix memory leak.")
- fbeb86f82fe942464944afacbdd314a11faf5bfe ("tests: attributes: Fix memory
leak.")
The updated patch combines all four upstream test fixes. With this patch,
all 17 test cases pass (2482 assertions, 0 failures).
Ubuntu bug: https://bugs.launchpad.net/ubuntu/+source/chr/+bug/2170633
Thanks,
Viktor Pashaiev
Description: Fix memory leaks in test suite
Leaking File instances keeps them active in the Qt event loop. When
their terminal is destroyed, incoming events (such as
cursorPositionChanged) trigger a use-after-free SIGSEGV during later
event loop iterations (in particular in SECTION("search")).
Avoid File leaks by using std::unique_ptr in filetests.cpp and stack
allocation in tests.cpp and attributes.cpp.
Author: Martin Hostettler <[email protected]>
Origin: upstream, https://github.com/chr-editor/chr/commit/294293edbaf9ba03f169c25e88005dca5f9b79e5, https://github.com/chr-editor/chr/commit/9657fd3ad95536c19c8b3f68987614d81b04845c
Bug-Debian: https://bugs.debian.org/1150796
Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/chr/+bug/2170633
Last-Update: 2026-10-13
---
diff --git a/src/tests/attributes.cpp b/src/tests/attributes.cpp
index be98813..e45ba0c 100644
--- a/src/tests/attributes.cpp
+++ b/src/tests/attributes.cpp
@@ -13,14 +13,14 @@
const QString attributesFileName = "attributes.file";
void cleanAttributesFileName() {
- QFile *file = new QFile(attributesFileName);
- file->remove();
+ QFile file(attributesFileName);
+ file.remove();
}
TEST_CASE("attributes") {
SECTION("empty") {
- Attributes *a = new Attributes("");
- CHECK(a->attributesFile() == "");
+ Attributes a("");
+ CHECK(a.attributesFile() == "");
}
SECTION("attributes-in-out") {
@@ -50,20 +50,20 @@ TEST_CASE("attributes") {
datei.close();
}
- Attributes *a = new Attributes(attributesFileName);
- a->writeAttributes(file,
+ Attributes a(attributesFileName);
+ a.writeAttributes(file,
{testCase.cursorPositionX, testCase.cursorPositionY},
testCase.scrollPositionColumn,
testCase.scrollPositionLine,
testCase.scrollPositionFineLine,
list);
- CHECK(a->getAttributesCursorPosition(file).codeUnit == testCase.cursorPositionX);
- CHECK(a->getAttributesCursorPosition(file).line == testCase.cursorPositionY);
- CHECK(a->getAttributesScrollCol(file) == testCase.scrollPositionColumn);
- CHECK(a->getAttributesScrollLine(file) == testCase.scrollPositionLine);
- CHECK(a->getAttributesScrollFine(file) == testCase.scrollPositionFineLine);
- CHECK(a->getAttributesLineMarker(file) == list);
+ CHECK(a.getAttributesCursorPosition(file).codeUnit == testCase.cursorPositionX);
+ CHECK(a.getAttributesCursorPosition(file).line == testCase.cursorPositionY);
+ CHECK(a.getAttributesScrollCol(file) == testCase.scrollPositionColumn);
+ CHECK(a.getAttributesScrollLine(file) == testCase.scrollPositionLine);
+ CHECK(a.getAttributesScrollFine(file) == testCase.scrollPositionFineLine);
+ CHECK(a.getAttributesLineMarker(file) == list);
cleanAttributesFileName();
}
diff --git a/src/tests/filetests.cpp b/src/tests/filetests.cpp
index 425c623..bf72092 100644
--- a/src/tests/filetests.cpp
+++ b/src/tests/filetests.cpp
@@ -41,12 +41,12 @@ TEST_CASE("file") {
terminal.setMainWidget(&root);
w->setGeometry({0, 0, 80, 24});
- File *f = new File(terminal.textMetrics(), w);
+ auto f = std::make_unique<File>(terminal.textMetrics(), w);
f->setFocus();
f->setGeometry({0, 0, 80, 24});
DocumentTestHelper t;
- Tui::ZDocument &doc = t.getDoc(f);
+ Tui::ZDocument &doc = t.getDoc(f.get());
Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) {
(void)wrappingAllowed;
Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line));
@@ -169,11 +169,11 @@ TEST_CASE("file-getseter") {
terminal.setMainWidget(&root);
w->setGeometry({0, 0, 80, 24});
- File *f = new File(terminal.textMetrics(), w);
+ auto f = std::make_unique<File>(terminal.textMetrics(), w);
DocumentTestHelper t;
- Tui::ZDocument &doc = t.getDoc(f);
+ Tui::ZDocument &doc = t.getDoc(f.get());
Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) {
(void)wrappingAllowed;
Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line));
@@ -313,11 +313,11 @@ TEST_CASE("actions") {
terminal.setMainWidget(&root);
w->setGeometry({0, 0, 80, 24});
- File *f = new File(terminal.textMetrics(), w);
+ auto f = std::make_unique<File>(terminal.textMetrics(), w);
DocumentTestHelper t;
- Tui::ZDocument &doc = t.getDoc(f);
+ Tui::ZDocument &doc = t.getDoc(f.get());
Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) {
(void)wrappingAllowed;
Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line));
@@ -785,31 +785,31 @@ TEST_CASE("actions") {
SECTION("search") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1});
f->setSearchText("t");
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{5,0});
recorder.clearEvents();
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,0});
recorder.clearEvents();
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{5,0});
recorder.clearEvents();
}
SECTION("search-t") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -829,7 +829,7 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0});
recorder.clearEvents();
@@ -845,7 +845,7 @@ TEST_CASE("actions") {
}
SECTION("search-t-t") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -865,19 +865,19 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0});
recorder.clearEvents();
@@ -886,7 +886,7 @@ TEST_CASE("actions") {
SECTION("search-space-t-t") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool reg = GENERATE(true, false);
CAPTURE(reg);
@@ -902,19 +902,19 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
@@ -923,7 +923,7 @@ TEST_CASE("actions") {
SECTION("searchBackword-space-t-t") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool reg = GENERATE(true, false);
CAPTURE(reg);
@@ -939,19 +939,19 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(true, &terminal, f);
+ t.f3(true, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(true, &terminal, f);
+ t.f3(true, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(true, &terminal, f);
+ t.f3(true, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0});
recorder.clearEvents();
@@ -960,7 +960,7 @@ TEST_CASE("actions") {
SECTION("search-t-newline-t") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -979,19 +979,19 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,1});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{1,0});
recorder.clearEvents();
@@ -1000,7 +1000,7 @@ TEST_CASE("actions") {
SECTION("search-t-newline-space-t") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -1019,19 +1019,19 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
@@ -1040,7 +1040,7 @@ TEST_CASE("actions") {
SECTION("search-aa") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -1060,13 +1060,13 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1});
recorder.clearEvents();
@@ -1075,7 +1075,7 @@ TEST_CASE("actions") {
SECTION("search-aa-aa") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -1104,7 +1104,7 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
for (Tui::ZDocumentCursor::Position point : positions) {
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == point);
recorder.clearEvents();
@@ -1114,7 +1114,7 @@ TEST_CASE("actions") {
SECTION("search-asd") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool reg = GENERATE(true, false);
CAPTURE(reg);
@@ -1131,7 +1131,7 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(true, &terminal, f);
+ t.f3(true, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{3,0});
recorder.clearEvents();
@@ -1141,7 +1141,7 @@ TEST_CASE("actions") {
SECTION("search-forward-backward") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool reg = GENERATE(true, false);
CAPTURE(reg);
@@ -1162,13 +1162,13 @@ TEST_CASE("actions") {
SECTION("toggel-wraparound") {
for(int i = 0; i < 3; i++) {
- t.f3(false, &terminal, f);
+ t.f3(false, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(true, &terminal, f);
+ t.f3(true, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,2});
recorder.clearEvents();
@@ -1182,7 +1182,7 @@ TEST_CASE("actions") {
positions << Tui::ZDocumentCursor::Position{2,1} << Tui::ZDocumentCursor::Position{2,0} << Tui::ZDocumentCursor::Position{2,1} << Tui::ZDocumentCursor::Position{2,2} << Tui::ZDocumentCursor::Position{2,0} << Tui::ZDocumentCursor::Position{2,2} << Tui::ZDocumentCursor::Position{2,1};
bool backward = true;
for (Tui::ZDocumentCursor::Position point : positions) {
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == point);
recorder.clearEvents();
@@ -1196,7 +1196,7 @@ TEST_CASE("actions") {
SECTION("search-smiley") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -1215,13 +1215,13 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{4,0});
recorder.clearEvents();
@@ -1230,7 +1230,7 @@ TEST_CASE("actions") {
SECTION("search-smiley-multiline") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
bool backward = GENERATE(true, false);
CAPTURE(backward);
@@ -1249,13 +1249,13 @@ TEST_CASE("actions") {
recorder.waitForEvent(cursorSignal);
recorder.clearEvents();
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,0});
recorder.clearEvents();
CHECK(f->hasSelection() == true);
- t.f3(backward, &terminal, f);
+ t.f3(backward, &terminal, f.get());
recorder.waitForEvent(cursorSignal);
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{2,1});
recorder.clearEvents();
@@ -1264,7 +1264,7 @@ TEST_CASE("actions") {
SECTION("replace") {
EventRecorder recorder;
- auto cursorSignal = recorder.watchSignal(f, RECORDER_SIGNAL(&File::cursorPositionChanged));
+ auto cursorSignal = recorder.watchSignal(f.get(), RECORDER_SIGNAL(&File::cursorPositionChanged));
CHECK(f->cursorPosition() == Tui::ZDocumentCursor::Position{8,1});
f->replaceAll("1","2");
@@ -1295,12 +1295,12 @@ TEST_CASE("multiline") {
terminal.setMainWidget(&root);
w->setGeometry({0, 0, 80, 24});
- File *f = new File(terminal.textMetrics(), w);
+ auto f = std::make_unique<File>(terminal.textMetrics(), w);
f->setFocus();
f->setGeometry({0, 0, 80, 24});
DocumentTestHelper t;
- Tui::ZDocument &doc = t.getDoc(f);
+ Tui::ZDocument &doc = t.getDoc(f.get());
Tui::ZDocumentCursor cursor{&doc, [&terminal,&doc](int line, bool wrappingAllowed) {
(void)wrappingAllowed;
Tui::ZTextLayout lay(terminal.textMetrics(), doc.line(line));
diff --git a/src/tests/tests.cpp b/src/tests/tests.cpp
index 2b170c8..efb2107 100644
--- a/src/tests/tests.cpp
+++ b/src/tests/tests.cpp
@@ -39,11 +39,11 @@ void readWrite(QString in) {
Tui::ZTerminal terminal(of);
QString out = in + "_dub";
- File *f = new File(terminal.textMetrics(), nullptr);
- //f->setFilename(in);
- CHECK(f->openText(in));
- f->setFilename(out);
- CHECK(f->saveText());
+ File f(terminal.textMetrics(), nullptr);
+ //f.setFilename(in);
+ CHECK(f.openText(in));
+ f.setFilename(out);
+ CHECK(f.saveText());
CHECK(fileChecksum(in) == fileChecksum(out));