GOAL P4: --report-unsafe 와 --report-instances, 그리고 예산을 CI 에

'ffec 에서 unsafe 가 몇 군데인가' 는 좋은 지표인데 세는 방법이 없으면 지표가
아니다. 이제 센다.

  unit                   unsafe       *T  unchecked
  std.io                      6        1          0
  std.sys                    10       12          0
  total                      16       13          0
  outside std                 0        0          0

Ferro 렉서와 파서, interner, 아레나, 맵을 쓰는 프로그램 전부가 std 바깥에서
0 이다. 목표치를 이미 지키고 있었던 셈인데, 그것을 아무도 확인할 수 없었다.

run.py 가 그 숫자를 검사한다. std.mem 과 std.sys 밖의 unsafe 와 *T 는 검사기가
약속한 것에 뚫린 구멍이므로 0 을 유지해야 하고, 늘어나면 알아채는 것이 아니라
빌드가 실패해야 한다. unsafe 블록 하나를 넣어 실패하는 것까지 확인했다.

--report-instances 는 제네릭이 무엇이 됐는지 센다. interns.fe 는 20 인스턴스,
타입 4 개에 저장소 80 바이트, 메서드 16 개다.

245/245, 38/38.
This commit is contained in:
2026-08-17 16:31:57 +09:00
parent b0cc737c6b
commit f06a12f341
4 changed files with 177 additions and 1 deletions
+34 -1
View File
@@ -34,7 +34,7 @@ FIXTURES = ROOT / "fec" / "tests"
WATCOM = ROOT / ".dosboxx" / "watcom"
SOURCES = ("arena", "diag", "lexer", "ast", "parser", "types", "m7", "own",
"check", "checkexp", "checkstm", "checkgen", "checkcal", "checkpro",
"resolve", "ir", "lower", "lowerprn", "lowerexp", "lowerstm", "x86", "driver")
"resolve", "ir", "lower", "lowerprn", "lowerexp", "lowerstm", "x86", "report", "driver")
MARKER = re.compile(r"^//\s*ERROR:(?:(\d+):)?(.*)$")
@@ -132,8 +132,41 @@ def main() -> int:
marked = sum(1 for p in cases if expectation(p).line is not None)
print(f"\n{len(cases) - len(failed)}/{len(cases)} passed "
f"({marked} pin a line and message)")
if not args.select:
leak = unsafe_budget(fec)
if leak:
print(leak)
return 1
return 1 if failed else 0
# The programs the budget is measured on: the Ferro front end, and the ones
# that lean hardest on the standard library.
BUDGETED = ("exec/lexer/tree.fe", "exec/interns.fe", "exec/arenat.fe",
"exec/maps.fe", "exec/wordfreq.fe")
def unsafe_budget(fec: Path) -> str:
"""`unsafe` and `*T` belong to std.mem and std.sys. Anywhere else they are
a hole in what the checker promises, so the count outside std has to stay
at zero and a regression has to fail the build rather than be noticed."""
for rel in BUDGETED:
path = FIXTURES / rel
if not path.is_file():
return f"budget: {rel} is gone"
done = subprocess.run([str(fec), "--report-unsafe", str(path),
f"--std={ROOT / 'fec'}"],
capture_output=True, text=True)
line = [l for l in done.stdout.splitlines()
if l.startswith("outside std")]
if not line:
return f"budget: no report for {rel}\n{done.stdout}{done.stderr}"
counts = line[0].split()[2:]
if any(c != "0" for c in counts):
return (f"budget: {rel} has unsafe/raw pointers outside std: "
f"{line[0]}")
return ""
if __name__ == "__main__":
raise SystemExit(main())