
수명 및 기타 정제 유형 검사기
C검사기
L생명주기 및 기타
R정제 타입
Zig를 위한
동영상: https://www.youtube.com/watch?v=mf0WzTOe-40 후원: https://buymeacoffee.com/dnautics
HN에서 토론: https://news.ycombinator.com/item?id=42923829
Lobsters에서 토론: https://lobste.rs/s/9sitsj/clr_checker_for_lifetimes_other
라이브 데모 동영상: https://www.youtube.com/watch?v=ZY_Z-aGbYm8
이 프로젝트는 Zig 컴파일러용 Zig 트랜스파일러를 생성합니다. 이 트랜스파일러는 AIR(Abstract Intermediate Representation)를 컴파일 타임에 정적 분석을 수행하는 Zig 소스 코드로 변환합니다. 생성된 분석기는 메모리 안전 문제(사용 전 할당, 사용 후 해제, 스택 포인터 탈출)와 Zig 특유의 UB(널이 아님 단정, 태그된 공용체 위반, fieldParentPtr 오용)를 잡아냅니다.
목표는 언어 자체를 변경하지 않고 AIR의 정적 분석을 통해 Rust 수준의 메모리 안전성을 Zig에 도입하는 것입니다.
CLR은 zig/에 서브모듈로 포함된 포크된 버전의 Zig 컴파일러에 의존하며, 이는 AIR를 외부 플러그인으로 라우팅하는 기능을 추가합니다. -ofmt=air -fair-out=<plugin.so>로 호출하면 컴파일러는 지정된 공유 라이브러리를 로드하고 생성된 AIR를 처리하도록 전달합니다.
CLR은 기술적으로 유효한 모든 Zig 프로그램을 인식하는 것이 아니라, 리소스 상태를 타입과 제어 흐름 구조에서 명시적으로 드러내는 생명주기 패턴으로 프로그램을 유도하려는 의도입니다. 두 가지 표현이 가능할 때 CLR은 제어 흐름 구조에서 리소스 상태를 명시적으로 드러내는 쪽을 선호합니다.
예를 들어, 조건부로 비-옵셔널 파일 디스크립터를 닫는 것을 피하십시오:
const file = try std.fs.cwd().openFile(path, .{});
if (should_close) {
file.close(); // 나쁨: 이 분기 이후 파일이 열려 있는지 모호합니다.
}
조건부 소유권을 옵셔널로 표현하는 것을 선호하십시오:
var file: ?std.fs.File = null;
if (should_open) {
file = try std.fs.cwd().openFile(path, .{});
}
if (file) |open_file| {
open_file.close();
}
비-옵셔널 디스크립터를 조건부로 닫으면 분기 이후 생명주기가 모호해집니다. CLR의 의도된 정책은 영구적인 "아마도 닫힘" 상태를 유지하는 대신 해당 패턴을 거부하는 것입니다.
동일한 원칙이 할당된 포인터에도 적용됩니다. 파생 포인터를 통해 해제하지 마십시오:
const allocation = try allocator.alloc(u8, size);
const payload = allocation[header_size..];
allocator.free(payload); // 나쁨: payload는 할당 기준이 아닙니다.
할당 기준 포인터를 해제에 사용할 수 있도록 유지하고, 파생 포인터는 접근에만 사용하십시오:
const allocation = try allocator.alloc(u8, size);
defer allocator.free(allocation);
const payload = allocation[header_size..];
use(payload);
필드 포인터, 하위 슬라이스 또는 산술 연산으로 생성된 포인터를 해제하는 것은 문서화된 내부 규칙이 할당 기준 출처를 다시 설정하지 않는 한 거부됩니다.
이러한 정책은 기본적으로 엄격한데, 더 단순하고 검토하기 쉬운 리소스 생명주기를 가진 코드를 생성하기 때문입니다. 추후 unsafe 어노테이션 메커니즘을 통해 선택된 GID 또는 연산이 개별 분석에서 제외될 수 있습니다. 이는 성능을 위해 더 약한 검사를 의도적으로 수용하는 코드를 지원하면서 프로그램의 나머지 부분에 대한 기본 모델을 약화시키지 않습니다.
이것은 원래 Elixir 기반 프로토타입을 Zig로 능동적으로 다시 작성한 것입니다. Zig 구현은 컴파일러 플러그인으로 로드되어 AIR를 직접 분석합니다.
현재 구현됨:
std.mem.Allocator 인터페이스 커버리지:
create/destroy - 단일 항목 할당alloc/free - 슬라이스 할당 (alignedAlloc, allocSentinel 등 포함)realloc/remap - 슬라이스 재할당 (이전 슬라이스 해제 추적)dupe/dupeZ - 슬라이스 복제init/deinit/ - 전체 아레나 생명주기계획 중 (자세한 내용은 LIMITATIONS.md 참조):
sudo apt install bats벤더링된 Zig 컴파일러와 libclr 플러그인은 일치하는 최적화 수준으로 빌드되어야 합니다. 최적화 수준이 일치하지 않으면 세그폴트가 발생합니다.
# 사용자 정의 Zig 컴파일러를 ReleaseFast로 빌드 (처음 한 번만, 또는 서브모듈 변경 후)
cd zig && zig build --zig-lib-dir lib -Doptimize=ReleaseFast && cd ..
# CLR 플러그인을 일치하는 최적화로 빌드
zig build -Doptimize=ReleaseFast
개발/디버깅을 위해 둘 다 ReleaseSafe 또는 Debug를 사용하십시오:
# ReleaseSafe (안전 검사 포함, 약간 느림)
cd zig && zig build --zig-lib-dir lib -Doptimize=ReleaseSafe && cd ..
zig build -Doptimize=ReleaseSafe
# Debug (전체 디버그 정보, 가장 느림)
cd zig && zig build --zig-lib-dir lib && cd ..
zig build
# AIR 백엔드를 사용하여 Zig 파일 컴파일
zig/zig-out/bin/zig build-exe -fair-out=zig-out/lib/libclr.so -ofmt=air -femit-bin=output.air.zig your_file.zig
# 생성된 분석기 실행
zig run --dep clr -Mroot=output.air.zig -Mclr=lib/lib.zig
출력은 stderr로 전송됩니다.
# 단위 테스트 (코드젠/DLL)
zig build test
# 단위 테스트 (런타임 라이브러리)
zig test lib/lib.zig
# 집중 통합 테스트 파일
bats test/integration/fd.bats
# 통합 테스트 (BATS 필요)
# 기본값은 ReleaseFast입니다. OPTIMIZE=ReleaseSafe 또는 OPTIMIZE=Debug로 재정의하십시오.
./run_integration.sh
# 단일 파일 수동 테스트
./run_one.sh test/cases/undefined/use_before_assign.zig
참고: 통합 테스트는 libclr를 지정된 최적화 수준(기본값: ReleaseFast)으로 다시 빌드합니다. 벤더링된 Zig 컴파일러가 일치하는 최적화 수준으로 빌드되었는지 확인하십시오.
clr/
├── src/ # DLL/플러그인 코드 (.air.zig 생성)
│ ├── clr.zig # CLR 플러그인 진입점
│ ├── codegen.zig # AIR 명령어에서 .air.zig 소스 생성
│ └── allocator.zig # DLL 안전 할당자 래퍼
├── lib/ # 런타임 분석 라이브러리
│ ├── lib.zig # 라이브러리 진입점
│ ├── tag.zig # AnyTag 공용체, Type, 태그 핸들러, splat 디스패치
│ ├── Inst.zig # 명령어 결과 및 절차 간 분석
│ ├── Refinements.zig # 정제 타입 (포인터, 구조체, 옵셔널 등)
│ ├── Analyte.zig # 분석 상태 컨테이너
│ ├── Context.zig # 실행 컨텍스트 (메타데이터, 오류 보고)
│ └── analysis/ # 분석 모듈
│ ├── undefined_safety.zig # 사용 전 할당 추적
│ ├── memory_safety.zig # 할당/해제 추적
│ ├── null_safety.zig # 옵셔널 언래핑 검사
│ ├── variant_safety.zig # 태그된 공용체 필드 접근
│ └── fd_safety.zig # 파일 디스크립터 추적
├── test/
│ ├── integration/ # BATS 통합 테스트
│ │ ├── test_helper.bash
│ │ └── *.bats
│ └── cases/ # 테스트 입력 파일 (.zig)
├── zig/ # Zig 컴파일러 서브모듈 (계측된 포크)
├── build.zig # 빌드 설정
└── build.zig.zon # 패키지 종속성

Zig는 악명 높은 "불안전한" 언어입니다. 메모리 관리를 수동으로 수행하므로 구현 오류 가능성이 열려 있습니다. Zig는 안전 확인 코드에서 배열 범위 초과 접근과 널 포인터 역참조를 제거하여 C에 비해 보안 문제를 줄이지만, 정적 분석을 통해 사용 후 해제, 이중 해제, 데이터 경합을 제거하는 Rust보다는 여전히 덜 안전합니다.
Rust의 MIRI 프로젝트에서 영감을 받아, CLR은 Zig의 AIR 중간 표현에 대해 정적 분석을 수행하여 Zig가 기본적으로 제공하는 것보다 더 높은 수준의 안전성을 달성합니다. MIRI가 Rust의 MIR을 샌드박스된 의사 런타임에서 해석하는 반면, CLR은 AIR를 분석을 정적으로 실행하는 Zig 소스 코드로 트랜스파일합니다. CLR의 AIR 출력 zig 코드는 원칙적으로 컴파일 타임에 실행될 수 있지만, zig 중간 단계를 거치면 이해하기 쉽고 디버그하기 쉬운 논리 흐름을 생성합니다. 야심 찬 사람은 이 일반적인 접근 방식을 사용하여 증명 보조 언어와 같은 다른 출력 대상을 내보내거나, 완전히 zig 컴파일러 내에서 실행되도록 리팩터링할 수 있습니다!
핵심 통찰: 보안에 민감한 Rust 프로젝트에 MIRI가 필요한데, 더 간단한 언어를 선택하고 MIRI 스타일 분석을 수행하여 대여 검사 및 기타 정제 타입 분석을 얻는 것은 어떨까? 이 프로젝트는 그러한 미래가 Zig에서 실제로 가능함을 보여줍니다.
Zig 컴파일 파이프라인은 다음과 같습니다:
AIR는 분석에 이상적인 수준입니다. 타입이 있고, "최소 실행 가능" 일반화된 프로그래밍 명령어 목록으로 해석할 수 있으며, 정제 메타데이터로 타입을 확장할 수 있기 때문입니다.
Zig의 AIR가 어떻게 작동하는지 자세히 알아보려면 Mitchell Hashimoto의 블로그 게시물을 참조하십시오: https://mitchellh.com/zig/sema
MIT 라이선스 - 자세한 내용은 LICENSE를 참조하십시오.
allocatorstd.process.args, std.mem.asBytes, std.HashMap, 할당자/파일 API와 같은 일반적인 패턴에 대한 Stdlib 경계 축소std.HashMap 개선: put, get, getPtr, 값 반복 전반에 걸친 표준 메타데이터/키/값 저장소 정체성posix.open/close/dup/dup2/socket/accept/epoll_create/pipe 추적