fix: release check-mode expression tokens

This commit is contained in:
Hermes Agent
2026-10-04 00:30:22 +00:00
parent da51e33411
commit d844d8cb85
3 changed files with 53 additions and 0 deletions
+2
View File
@@ -51,6 +51,8 @@ The `DELETE FILE ...` and `DELETE FOLDER ...` commands now release their evaluat
`typecheck` now frees its temporary type environment on return, including the owned variable names and type-string values; replacing an inferred type releases the previous value first. `python3 tools/validate_typecheck_token_cleanup.py` reproduced 479 bytes of leaked type-environment allocations before the change, then passed under ASan/UBSan/LSan with the fix. The validator is named for the original local work file; it specifically checks type-environment cleanup, not tokenizer allocations. This is a narrow cleanup slice and does not establish that all typecheck metadata allocations are leak-free.
`claro check` now releases expression-token arrays and strings after variable-name analysis, and frees its temporary variable environment at the end of each check. The untracked focused regression validator `tools/validate_check_token_cleanup.py` first reproduced 310,447 leaked bytes across 2,000 repeated-string expressions; after the cleanup, `python3 tools/validate_check_token_cleanup.py` passes under ASan/UBSan/LSan. Fresh verification: `make -s all`, `./claro test` (0 failures), `./claro doctor`, `./claro validate`, and `git diff --check` pass. This is a narrow, runtime-verified cleanup for check mode; it does not establish that all allocations are leak-free.
## Feature matrix
### Beginner scripting core
+3
View File
@@ -480,6 +480,7 @@ static char *check_var_after_as(const char *line){ const char *as=find_word_ci(l
static char *check_between_words(const char *line,const char *a,const char *b){ const char *pa=find_word_ci(line,a); const char *pb; if(!pa) return NULL; pa+=strlen(a); pb=find_word_ci(pa,b); if(!pb) return NULL; return substr(pa,pb); }
typedef struct CheckBlock { const char *expect; const char *open; int line; } CheckBlock;
static void check_vars_free(Var **vars){ Var *v,*next; if(!vars) return; for(v=*vars;v;v=next){ next=v->next; free(v->name); value_free(v->val); free(v); } *vars=NULL; }
static int check_file(const char *path){
FILE *f=fopen(path,"rb"); char line[4096]; int line_no=0,errs=0,sp=0; CheckBlock stack[256]; Var *vars=NULL;
check_add_var(&vars,"RESULT"); check_add_var(&vars,"LASTERROR"); check_add_var(&vars,"LASTERRORFILE"); check_add_var(&vars,"LASTERRORLINE"); check_add_var(&vars,"LASTEXIT"); check_add_var(&vars,"LASTHTTP");
@@ -570,11 +571,13 @@ static int check_file(const char *path){
errs++;
}
}
tokens_free(&ts);
}
}
fclose(f);
while(sp>0){ CheckBlock b=stack[--sp]; printf("%s:%d: %s block opened here needs %s\n",path,b.line,b.open,b.expect); errs++; }
if(errs==0) printf("OK\n");
check_vars_free(&vars);
return errs?1:0;
}
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Check that check-mode releases tokens allocated for each expression."""
from pathlib import Path
import os
import subprocess
import sys
import tempfile
ROOT = Path(__file__).resolve().parent.parent
def main() -> int:
if os.name == "nt":
print("SKIP: LeakSanitizer check is POSIX-only")
return 0
with tempfile.TemporaryDirectory(prefix="check-token-cleanup-") as tmp:
tmp_path = Path(tmp)
binary = tmp_path / "claro-lsan"
script = tmp_path / "tokens.claro"
script.write_text('SAY "a repeated string literal"\n' * 2000, encoding="utf-8")
build = subprocess.run(
["gcc", "-std=c99", "-O0", "-g", "-fsanitize=address,undefined",
"src/claro.c", "-o", str(binary), "-lm"],
cwd=ROOT, text=True, capture_output=True,
)
if build.returncode:
print(build.stdout, end="")
print(build.stderr, end="", file=sys.stderr)
return build.returncode
run = subprocess.run(
[str(binary), "check", str(script)], cwd=ROOT, text=True,
capture_output=True, env={**os.environ, "ASAN_OPTIONS": "detect_leaks=1"},
)
if run.returncode:
print("FAIL: check-mode token cleanup probe failed")
print(run.stdout, end="")
print(run.stderr, end="", file=sys.stderr)
return 1
if "LeakSanitizer" in run.stderr or "SUMMARY:" in run.stderr:
print("FAIL: check-mode token allocations still leak")
print(run.stderr, end="", file=sys.stderr)
return 1
print("PASS: check-mode expression tokens are released")
return 0
if __name__ == "__main__":
raise SystemExit(main())