fix: release check-mode expression tokens
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user