Files
doslang-mirror/audits/2026-08-17-frontend-gaps.md
T
coolguy b4f947b643 audit: 프런트엔드 빈틈 열하나와 진법 리터럴
전부 구현 쪽이었다. SPEC 은 §6.2(체이닝·단항 뒤 as), §5 R9(asm), §6.1·§7.3
(extern, 빈 enum/error)을 이미 옳게 적고 있었고 구현만 따라가지 않았다.

  const A: i32 = r();        전역 초기값에 호출. lowering 이 조용히 버리고 0
  static A: i32 = r();       같은 것을 저장소 0 으로
  true == false == true      비교 체이닝
  -x as u32                  괄호 없이 단항 뒤 as
  asm { }                    unsafe 밖에서
  extern fn f();             ABI 문자열 없이
  extern "stdcall" fn f();   c 아닌 ABI
  extern "c" fn f() { }      extern 에 본문
  fn f() -> i32;             extern 아닌데 본문 없음
  enum E { }  error E { }    빈 선언

FRONT-01/02 는 SPEC 에도 규칙이 없었다. §7.1 에 넣었다 -- 전역의 바이트는
이미지에 들어가므로 초기값이 실행될 순간이 없다. 리터럴과 다른 const, 배리언트,
error.Name, 그리고 그것들에 대한 연산까지가 허용된다.

-x as T 와 비교 체이닝을 잡으려면 괄호가 트리에 남아야 해서 FE_NODE_PAREN 을
두었다. 파싱 뒤에는 -x as T 와 -(x as T) 가 같은 트리다.

그리고 0b1010 이 0, 0o17 이 0 이었다. 10진으로 읽다가 b 에서 멈춰 0 을 내는데
0 도 숫자라 아무도 눈치채지 못한다. 값 계산 두 군데를 고쳤다.

262/262, 40/40.
2026-08-17 17:00:29 +09:00

3.6 KiB

Frontend gap audit

  • 날짜: 2026-08-17
  • 기준 커밋: 6dc298d828872409fdf6b7d2e85830f18a118d9f
  • 범위: parser, checker, 전역 lowering의 경계
  • 해결: 11 건 전부와 0b/0o 리터럴까지. 모두 구현 쪽이었다. fixture 는 아래 표에 적었다. 본문은 조사 시점 그대로다.

재현된 문제

아래 최소 입력은 발견 시점의 fec --check를 모두 통과했다.

ID 문제 필요한 fixture
FRONT-01 runtime 호출을 const 초기값으로 허용하고 lowering에서 초기값을 방출하지 않음 types/badcini.fe
FRONT-02 runtime 호출을 static 초기값으로 허용하고 저장소를 0으로 초기화 types/badsini.fe
FRONT-03 bool 비교 체이닝 허용: true == false == true types/badchain.fe
FRONT-04 괄호 없는 단항식 뒤 cast 허용: -x as u32 types/badunas.fe
FRONT-05 unsafe 밖에서 asm 허용 types/badasm.fe
FRONT-06 ABI 문자열 없는 extern fn f(); 허용 types/badexns.fe
FRONT-07 extern "c" 이외 ABI 문자열 허용 types/badexab.fe
FRONT-08 extern 함수 본문 허용 types/badexbd.fe
FRONT-09 extern이 아닌 본문 없는 fn f();를 외부 심볼로 처리 types/badfnsm.fe
FRONT-10 빈 enum 선언 허용 parse/bademen.fe
FRONT-11 빈 error 선언 허용 parse/bademer.fe

중복 struct field와 중복 enum variant 선언도 통과했지만, 중복 선언 규칙을 SPEC에서 먼저 확정해야 하므로 위 목록에는 넣지 않았다.

해결

SPEC 은 FRONT-03·04(§6.2), 05(§5 R9), 06~09(§6.1·§7.3), 10·11(§6.1)을 이미 옳게 적고 있었다. 구현만 따라가지 않았다. FRONT-01·02 는 SPEC 에도 규칙이 없어서 §7.1 에 문장을 넣었다 -- 전역 초기값은 컴파일 시점에 알 수 있어야 한다.

ID fixture
FRONT-01 types/badcini.fe
FRONT-02 types/badsini.fe
FRONT-03 types/badchain.fe
FRONT-04 types/badunas.fe
FRONT-05 types/badasm.fe
FRONT-06 types/badexns.fe
FRONT-07 types/badexab.fe
FRONT-08 types/badexbd.fe
FRONT-09 types/badfnsm.fe
FRONT-10 parse/bademen.fe
FRONT-11 parse/bademer.fe
허용되는 짝 types/okglobin.fe
0b/0o exec/radix.fe

-x as T 와 비교 체이닝을 구별하려면 괄호가 트리에 남아야 해서 노드에 FE_NODE_PAREN 을 두었다. 파싱 뒤에는 -x as T-(x as T) 가 같은 트리다.

남은 것: parse/ fixture 가 트리 내용을 비교하지 않는다는 지적은 그대로 유효하다. 우선순위는 지금 exec/bitnot.fe 처럼 실행 결과로 구별한다.

이미 알려진 실행 문제

0b0o 리터럴은 lexer가 받지만 값 계산이 진법을 반영하지 않는다. 실행 결과를 고정하는 exec/radix.fe가 필요하다.

테스트 기반의 빈틈

parse/ fixture는 --dump-ast의 성공 여부만 검사하고 트리 내용은 비교하지 않는다. 따라서 연산자 우선순위나 postfix 결합 방향은 parse fixture만으로 고정되지 않는다. 이런 항목은 실행 결과로 구별하거나 선택적인 AST 기대값 검사를 추가해야 한다.

검증

  • uv run python tests/run.py: 245/245 통과
  • 각 문제를 독립적인 최소 입력으로 만들어 --check 결과를 확인함
  • 조사용 임시 입력은 제거함

이 문서는 발견 시점의 상태를 기록한다. 작성 중인 미커밋 수정으로 일부 항목의 상태가 바뀔 수 있으므로 해결 여부는 fixture와 두 테스트 suite로 확인한다.