Add FENCE word (SDK v1.9.0 scoping); fix severe pre-existing FORGET use-after-free
FENCE ( -- ) exposes the dict_fence_latest/dict_fence_here state FORGET already honored internally, letting callers (e.g. a future SDK capsule) raise the boundary after loading their own content -- no new VM fields, no policy logic beyond exposing existing state. Writing a direct test for it surfaced a real, severe, pre-existing bug in FORGET's relink logic, unrelated to FENCE itself and reproducible with the original boot-time fence alone: - Forgetting the single newest word incorrectly destroyed every other word back to the fence too, not just the target. - Forgetting an older word (correctly cascading to remove newer words too, per FORTH-79 semantics) crashed with SIGSEGV. Root cause: the relink code's target_prev pointer was, by construction, always inside the range the preceding loop had just freed whenever target wasn't vm->latest -- so writing through it was a use-after-free every time that branch executed. Fixed by removing the target_prev tracking and both branches entirely; vm->latest unconditionally becomes target_next (target's own captured, still-valid link) after the free loop, correct in every case. Added a FENCE test suite to dictionary_manipulation_words_test.c (Module 14) including the exact regression case (forgetting the newest word must not disturb an older one). Verified zero warnings and identical POST/dict_hash results across all three kernel architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6d8b48f100
commit
4e7dcdf889
@@ -70,6 +70,34 @@ static WordTestSuite dict_manip_word_suites[] = {
|
||||
3, {0}
|
||||
},
|
||||
|
||||
{
|
||||
/* FENCE ( -- ): raises the FORGET boundary to the current dictionary
|
||||
* top. Added alongside a real bug fix in FORGET itself, caught while
|
||||
* building this word -- forgetting the single newest word used to
|
||||
* incorrectly destroy every other word back to the fence too (a
|
||||
* use-after-free in the relink logic, not just an off-by-one), a
|
||||
* SIGSEGV in the worst case. "forget_latest_keeps_predecessor" below
|
||||
* is exactly the regression case that fix addresses. */
|
||||
"FENCE", {
|
||||
{
|
||||
"basic", "CREATE fnc1 FENCE CREATE fnc2 FORGET fnc2",
|
||||
"Should forget a word defined after FENCE", TEST_NORMAL, 0, 1, {0}
|
||||
},
|
||||
{
|
||||
"protects_older", "CREATE fnc3 CREATE fnc4 FENCE FORGET fnc3",
|
||||
"Should refuse to forget a word defined before FENCE", TEST_ERROR_CASE, 1, 1, {0}
|
||||
},
|
||||
{
|
||||
"forget_latest_keeps_predecessor",
|
||||
"CREATE fnc5 FENCE CREATE fnc6 CREATE fnc7 FORGET fnc7 fnc6 DROP",
|
||||
"Forgetting the newest word must not disturb an older one still above FENCE",
|
||||
TEST_NORMAL, 0, 1, {0}
|
||||
},
|
||||
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
|
||||
},
|
||||
3, {0}
|
||||
},
|
||||
|
||||
{
|
||||
"IMMEDIATE", {
|
||||
{"basic", ": test3 42 ; IMMEDIATE test3 . CR", "Should execute immediately", TEST_NORMAL, 0, 1, {0}},
|
||||
|
||||
Reference in New Issue
Block a user