Unify beginner syntax while preserving compatibility, repair ownership and parser guards, add verified distribution tooling and VC6 Windows 98 build inputs, and clarify documentation and release status. Preserve DOS build-input line endings and exclude generated binaries/caches.
79 lines
3.5 KiB
Python
79 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Bounded malformed-expression regressions; sanitizer runs include leak checks."""
|
|
from pathlib import Path
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
DRIVER = r'''#define main claro_cli_main
|
|
#include "claro.c"
|
|
#undef main
|
|
int main(int argc,char **argv)
|
|
{
|
|
Runtime rt;
|
|
Value result;
|
|
int status;
|
|
if(argc==3 && strcmp(argv[1],"check")==0) return check_file(argv[2]);
|
|
if(argc!=2) return 2;
|
|
rt_init(&rt);
|
|
result=eval_expr(&rt,argv[1]);
|
|
value_free(result);
|
|
status=rt.error?1:0;
|
|
if(rt.error) fprintf(stderr,"%s\n",rt.err_msg);
|
|
runtime_free(&rt);
|
|
return status;
|
|
}
|
|
'''
|
|
CASES = [
|
|
('!', 'NOT'),
|
|
('(1 + 2', 'closing'),
|
|
('1 +', 'value'),
|
|
('"unfinished', 'quote'),
|
|
('1 2', 'Unexpected'),
|
|
('(' * 600 + '1' + ')' * 600, 'nesting'),
|
|
('NOT ' * 600 + 'YES', 'nesting'),
|
|
]
|
|
|
|
|
|
def limited():
|
|
import resource
|
|
resource.setrlimit(resource.RLIMIT_AS, (128 * 1024 * 1024, 128 * 1024 * 1024))
|
|
|
|
|
|
def main():
|
|
if os.name == 'nt':
|
|
print('SKIP: current parser sanitizer driver requires POSIX')
|
|
return
|
|
with tempfile.TemporaryDirectory(prefix='claro-parse-', dir=os.environ.get('TMPDIR')) as tmp:
|
|
work = Path(tmp)
|
|
(work / 'claro.c').write_bytes((ROOT / 'src/claro.c').read_bytes())
|
|
driver = work / 'parse.c'
|
|
driver.write_text(DRIVER)
|
|
flags = ['gcc', '-std=c89', '-pedantic-errors', '-Werror=implicit-function-declaration', '-O0', '-g']
|
|
plain, sanitized = work / 'parse', work / 'parse-asan'
|
|
subprocess.run(flags + [str(driver), '-o', str(plain), '-lm'], check=True)
|
|
for expression, expected in CASES:
|
|
run = subprocess.run([str(plain), expression], cwd=work, capture_output=True, text=True, timeout=5, preexec_fn=limited)
|
|
assert run.returncode == 1 and expected in run.stderr, (expression[:50], run.returncode, run.stderr[-1000:])
|
|
subprocess.run(flags + ['-fsanitize=address,undefined', str(driver), '-o', str(sanitized), '-lm'], check=True)
|
|
env = dict(os.environ, ASAN_OPTIONS='detect_leaks=1:halt_on_error=1', UBSAN_OPTIONS='halt_on_error=1')
|
|
for expression, expected in CASES:
|
|
run = subprocess.run([str(sanitized), expression], cwd=work, env=env, capture_output=True, text=True, timeout=10)
|
|
assert run.returncode == 1 and expected in run.stderr, (expression[:50], run.returncode, run.stderr[-1000:])
|
|
assert not any(marker in run.stderr for marker in ['AddressSanitizer', 'LeakSanitizer', 'runtime error:']), run.stderr
|
|
for expression, expected in CASES[:1] + CASES[3:4]:
|
|
script = work / 'bad.claro'
|
|
script.write_text('SAY ' + expression + '\n')
|
|
run = subprocess.run([str(sanitized), 'check', str(script)], cwd=work, env=env, capture_output=True, text=True, timeout=10)
|
|
assert run.returncode == 1 and expected in run.stdout, ('check', expression, run.returncode, run.stdout, run.stderr[-1000:])
|
|
assert not run.stderr, run.stderr
|
|
for expression in ['(1 + 2) * 3', '(' * 64 + '1' + ')' * 64, 'NOT ' * 64 + 'YES', '"a" + "b"', '1 != 2']:
|
|
run = subprocess.run([str(sanitized), expression], cwd=work, env=env, capture_output=True, text=True, timeout=10)
|
|
assert run.returncode == 0 and not run.stderr, (expression[:50], run.returncode, run.stderr[-1000:])
|
|
print('PASS: seven malformed expressions and five valid expressions terminate cleanly under ASan/UBSan/LSan')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|