From 1f63df07c635cb6d8c378021af3065c37869c073 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Sat, 21 Feb 2026 14:34:16 +0900 Subject: [PATCH 1/2] fix(translate): validate control flow stack before popping --- lib/Translation/ForthToMLIR/ForthToMLIR.cpp | 24 +++++++++++++++++++ .../Forth/begin-then-mismatch-error.forth | 4 ++++ .../Forth/else-without-if-error.forth | 4 ++++ .../Forth/if-until-mismatch-error.forth | 4 ++++ .../Forth/repeat-without-while-error.forth | 4 ++++ .../Forth/then-without-if-error.forth | 4 ++++ .../Forth/until-without-begin-error.forth | 4 ++++ .../Forth/while-without-begin-error.forth | 4 ++++ 8 files changed, 52 insertions(+) create mode 100644 test/Translation/Forth/begin-then-mismatch-error.forth create mode 100644 test/Translation/Forth/else-without-if-error.forth create mode 100644 test/Translation/Forth/if-until-mismatch-error.forth create mode 100644 test/Translation/Forth/repeat-without-while-error.forth create mode 100644 test/Translation/Forth/then-without-if-error.forth create mode 100644 test/Translation/Forth/until-without-begin-error.forth create mode 100644 test/Translation/Forth/while-without-begin-error.forth diff --git a/lib/Translation/ForthToMLIR/ForthToMLIR.cpp b/lib/Translation/ForthToMLIR/ForthToMLIR.cpp index aebb7e8..425f6f7 100644 --- a/lib/Translation/ForthToMLIR/ForthToMLIR.cpp +++ b/lib/Translation/ForthToMLIR/ForthToMLIR.cpp @@ -763,7 +763,11 @@ LogicalResult ForthParser::parseBody(Value &stack) { builder.create(loc, mergeBlock, ValueRange{stack}); // Pop the false-path block (from IF) - this becomes else-body start. + if (cfStack.empty()) + return emitError("ELSE without matching IF"); auto [tag, joinBlock] = cfStack.pop_back_val(); + if (tag != CFTag::Orig) + return emitError("ELSE without matching IF"); // Push merge block for THEN to pick up. cfStack.push_back({CFTag::Orig, mergeBlock}); @@ -777,7 +781,11 @@ LogicalResult ForthParser::parseBody(Value &stack) { consume(); // Pop the join/merge block. + if (cfStack.empty()) + return emitError("THEN without matching IF"); auto [tag, joinBlock] = cfStack.pop_back_val(); + if (tag != CFTag::Orig) + return emitError("THEN without matching IF"); // Branch from current block to join. builder.create(loc, joinBlock, ValueRange{stack}); @@ -810,7 +818,11 @@ LogicalResult ForthParser::parseBody(Value &stack) { auto [s1, flag] = emitPopFlag(loc, stack); + if (cfStack.empty()) + return emitError("UNTIL without matching BEGIN"); auto [tag, loopBlock] = cfStack.pop_back_val(); + if (tag != CFTag::Dest) + return emitError("UNTIL without matching BEGIN"); auto *exitBlock = createStackBlock(parentRegion, loc); @@ -829,7 +841,11 @@ LogicalResult ForthParser::parseBody(Value &stack) { auto [s1, flag] = emitPopFlag(loc, stack); + if (cfStack.empty()) + return emitError("WHILE without matching BEGIN"); auto [tag, loopBlock] = cfStack.pop_back_val(); + if (tag != CFTag::Dest) + return emitError("WHILE without matching BEGIN"); auto *bodyBlock = createStackBlock(parentRegion, loc); auto *exitBlock = createStackBlock(parentRegion, loc); @@ -851,13 +867,21 @@ LogicalResult ForthParser::parseBody(Value &stack) { consume(); // Pop loop header (from WHILE's re-push). + if (cfStack.empty()) + return emitError("REPEAT without matching WHILE"); auto [destTag, loopBlock] = cfStack.pop_back_val(); + if (destTag != CFTag::Dest) + return emitError("REPEAT without matching WHILE"); // Branch back to loop header. builder.create(loc, loopBlock, ValueRange{stack}); // Pop exit block (from WHILE). + if (cfStack.empty()) + return emitError("REPEAT without matching WHILE"); auto [origTag, exitBlock] = cfStack.pop_back_val(); + if (origTag != CFTag::Orig) + return emitError("REPEAT without matching WHILE"); // Continue after exit. builder.setInsertionPointToStart(exitBlock); diff --git a/test/Translation/Forth/begin-then-mismatch-error.forth b/test/Translation/Forth/begin-then-mismatch-error.forth new file mode 100644 index 0000000..16a1708 --- /dev/null +++ b/test/Translation/Forth/begin-then-mismatch-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: THEN without matching IF +\! kernel main +BEGIN THEN diff --git a/test/Translation/Forth/else-without-if-error.forth b/test/Translation/Forth/else-without-if-error.forth new file mode 100644 index 0000000..7179e26 --- /dev/null +++ b/test/Translation/Forth/else-without-if-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: ELSE without matching IF +\! kernel main +ELSE diff --git a/test/Translation/Forth/if-until-mismatch-error.forth b/test/Translation/Forth/if-until-mismatch-error.forth new file mode 100644 index 0000000..16f4283 --- /dev/null +++ b/test/Translation/Forth/if-until-mismatch-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: UNTIL without matching BEGIN +\! kernel main +IF UNTIL diff --git a/test/Translation/Forth/repeat-without-while-error.forth b/test/Translation/Forth/repeat-without-while-error.forth new file mode 100644 index 0000000..8728fae --- /dev/null +++ b/test/Translation/Forth/repeat-without-while-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: REPEAT without matching WHILE +\! kernel main +REPEAT diff --git a/test/Translation/Forth/then-without-if-error.forth b/test/Translation/Forth/then-without-if-error.forth new file mode 100644 index 0000000..8c8ada6 --- /dev/null +++ b/test/Translation/Forth/then-without-if-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: THEN without matching IF +\! kernel main +THEN diff --git a/test/Translation/Forth/until-without-begin-error.forth b/test/Translation/Forth/until-without-begin-error.forth new file mode 100644 index 0000000..576d10f --- /dev/null +++ b/test/Translation/Forth/until-without-begin-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: UNTIL without matching BEGIN +\! kernel main +UNTIL diff --git a/test/Translation/Forth/while-without-begin-error.forth b/test/Translation/Forth/while-without-begin-error.forth new file mode 100644 index 0000000..e26bac0 --- /dev/null +++ b/test/Translation/Forth/while-without-begin-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: WHILE without matching BEGIN +\! kernel main +WHILE From f2ec8d69ebccaffd36c9539c0ca08f5a30fc3458 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Sat, 21 Feb 2026 14:59:38 +0900 Subject: [PATCH 2/2] fix(translate): detect unclosed control flow and add REPEAT mismatch tests --- lib/Translation/ForthToMLIR/ForthToMLIR.cpp | 5 +++++ .../Forth/begin-begin-repeat-mismatch-error.forth | 4 ++++ test/Translation/Forth/if-repeat-mismatch-error.forth | 4 ++++ test/Translation/Forth/unclosed-begin-while-error.forth | 4 ++++ test/Translation/Forth/unclosed-if-error.forth | 4 ++++ 5 files changed, 21 insertions(+) create mode 100644 test/Translation/Forth/begin-begin-repeat-mismatch-error.forth create mode 100644 test/Translation/Forth/if-repeat-mismatch-error.forth create mode 100644 test/Translation/Forth/unclosed-begin-while-error.forth create mode 100644 test/Translation/Forth/unclosed-if-error.forth diff --git a/lib/Translation/ForthToMLIR/ForthToMLIR.cpp b/lib/Translation/ForthToMLIR/ForthToMLIR.cpp index 425f6f7..d12d2a8 100644 --- a/lib/Translation/ForthToMLIR/ForthToMLIR.cpp +++ b/lib/Translation/ForthToMLIR/ForthToMLIR.cpp @@ -1031,6 +1031,11 @@ LogicalResult ForthParser::parseBody(Value &stack) { } } + if (!cfStack.empty()) { + cfStack.clear(); + return emitError("unclosed control flow (missing THEN, REPEAT, or UNTIL?)"); + } + return success(); } diff --git a/test/Translation/Forth/begin-begin-repeat-mismatch-error.forth b/test/Translation/Forth/begin-begin-repeat-mismatch-error.forth new file mode 100644 index 0000000..d9d7b7d --- /dev/null +++ b/test/Translation/Forth/begin-begin-repeat-mismatch-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: REPEAT without matching WHILE +\! kernel main +BEGIN BEGIN REPEAT diff --git a/test/Translation/Forth/if-repeat-mismatch-error.forth b/test/Translation/Forth/if-repeat-mismatch-error.forth new file mode 100644 index 0000000..70b62c9 --- /dev/null +++ b/test/Translation/Forth/if-repeat-mismatch-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: REPEAT without matching WHILE +\! kernel main +1 IF REPEAT diff --git a/test/Translation/Forth/unclosed-begin-while-error.forth b/test/Translation/Forth/unclosed-begin-while-error.forth new file mode 100644 index 0000000..1d56b55 --- /dev/null +++ b/test/Translation/Forth/unclosed-begin-while-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: unclosed control flow (missing THEN, REPEAT, or UNTIL?) +\! kernel main +BEGIN 1 WHILE 42 diff --git a/test/Translation/Forth/unclosed-if-error.forth b/test/Translation/Forth/unclosed-if-error.forth new file mode 100644 index 0000000..7a156d4 --- /dev/null +++ b/test/Translation/Forth/unclosed-if-error.forth @@ -0,0 +1,4 @@ +\ RUN: %not %warpforth-translate --forth-to-mlir %s 2>&1 | %FileCheck %s +\ CHECK: unclosed control flow (missing THEN, REPEAT, or UNTIL?) +\! kernel main +1 IF 42