Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

퍼징

이 가이드에서 퍼징은 rustc의 버그를 찾아내기 위해 매우 다양한 프로그램을 컴파일하는 모든 테스트 방법론을 가리킵니다. 퍼징은 주로 내부 컴파일러 오류(ICE)를 찾는 데 사용됩니다. 퍼징은 사용자가 버그를 겪기 전에 먼저 찾아낼 수 있으므로 유익합니다. 또한 작고 독립적인 프로그램을 제공하므로 버그를 추적하기가 더 쉬워집니다. 그러나 흔히 저지르는 몇 가지 실수는 퍼징의 유용성을 떨어뜨리고 결국 기여자들을 더 힘들게 만들 수 있습니다. Rust 프로젝트에 최대한 긍정적인 영향을 주려면 퍼저가 생성한 버그를 보고하기 전에 이 가이드를 읽어 주십시오!

지침

요약

해야 할 일:

  • 최신 nightly rustc에서도 버그가 여전히 발생하는지 확인하십시오
  • 모든 버그 보고서에 적절히 최소화된 독립 실행형 예제를 포함하십시오
  • 버그 보고서 템플릿에서 요청하는 모든 정보를 포함하십시오
  • 같은 메시지와 쿼리 스택을 가진 기존 보고서가 있는지 검색하십시오
  • 테스트 케이스를 rustfmt로 포맷하십시오
  • 퍼징으로 발견한 버그임을 밝히십시오

하지 말아야 할 일:

  • custom_mir, lang_items, no_core, rustc_attrs 등을 포함하되 이에 국한되지 않는 내부 기능을 사용하는 버그를 대량으로 보고하지 마십시오.
  • rustc를 크래시시키는 것으로 알려진 입력을 퍼저의 시드로 사용하지 마십시오(자세한 내용은 아래 참조).

논의

어떤 ICE가 이미 보고된 것과 중복인지 확실하지 않다면 일단 보고하고, 관련이 있다고 생각되는 이슈에 링크를 걸어 주십시오. 일반적으로 같은 줄에서 발생하더라도 쿼리 스택이 다른 ICE는 대개 서로 다른 버그입니다. 예를 들어, #109020과 #109129는 비슷한 오류 메시지를 냈습니다:

error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <[closure@src/main.rs:36:25: 36:28] as std::ops::FnOnce<(Emplacable<()>,)>>::Output, maybe try to call `try_normalize_erasing_regions` instead
error: internal compiler error: compiler/rustc_middle/src/ty/normalize_erasing_regions.rs:195:90: Failed to normalize <() as Project>::Assoc, maybe try to call `try_normalize_erasing_regions` instead

그러나 두 이슈의 쿼리 스택은 서로 다릅니다:

query stack during panic:
#0 [fn_abi_of_instance] computing call ABI of `<[closure@src/main.rs:36:25: 36:28] as core::ops::function::FnOnce<(Emplacable<()>,)>>::call_once - shim(vtable)`
end of query stack
query stack during panic:
#0 [check_mod_attrs] checking attributes in top-level module
#1 [analysis] running analysis passes on this crate
end of query stack

코퍼스 구축하기

코퍼스를 구축할 때는 rustc를 크래시시키는 것으로 이미 알려진 테스트를 수집하지 않도록 주의하십시오. 그런 테스트를 시드로 사용한 퍼저는 근본 원인이 같은 버그를 생성할 가능성이 더 높습니다. 이를 피하는 가장 간단한 방법은 코퍼스의 각 파일을 루프로 돌면서 ICE를 일으키는지 확인하고, 그렇다면 제거하는 것입니다.

코퍼스를 빌드하려면 다음을 사용하는 것이 좋습니다:

  • rustc/rust-analyzer/clippy 테스트 스위트(또는 소스 코드) — 단, 실패를 일으키는 것으로 이미 알려진 테스트는 피하십시오. 이런 테스트는 흔히 //@ failure-status: 101이나 //@ known-bug: #NNN 같은 주석으로 시작합니다.
  • 보관된 Glacier 저장소에 있는 이미 수정된 ICE — 단, ices/에 있는 아직 수정되지 않은 것들은 피하십시오!

추가로 하면 좋은 일

ICE를 보고한 후 Rust 프로젝트에 도움이 되도록 할 수 있는 일은 다음과 같습니다.

  • 버그를 이분 탐색하여 언제 도입되었는지 알아내십시오. 회귀를 일으킨 PR / 커밋을 찾았다면 해당 이슈에 S-has-bisection 레이블을 달 수 있습니다. 찾지 못했다면 대신 E-needs-bisection을 적용하는 것을 고려하십시오.
  • “주의를 흐트러뜨리는 요소“를 수정하십시오. 이는 구문 오류나 빌림 검사 오류처럼 ICE 발생에 기여하지 않는 테스트 케이스의 문제를 말합니다.
  • 테스트 케이스를 최소화하십시오(아래 참조). 성공했다면 해당 이슈에 S-has-mcve 레이블을 달 수 있습니다. 그렇지 않다면 E-needs-mcve를 적용할 수 있습니다.
  • 최소화한 테스트 케이스를 크래시 테스트로 rust-lang/rust 저장소에 추가하십시오. 이 작업을 하는 김에 “추적되지 않은” 다른 크래시도 PR에 포함하는 것을 고려하십시오. PR이 병합되면 관련된 모든 이슈에 S-bug-has-test 레이블을 다는 것을 잊지 마십시오.

레이블 적용 및 제거도 참조하십시오.

최소화

퍼저가 생성한 입력을 신중하게 최소화하면 도움이 됩니다. 최소화할 때는 원래의 오류를 보존하도록 주의하고, 구문 오류, 타입 검사 오류, 빌림 검사 오류처럼 주의를 흐트러뜨리는 문제를 새로 만들지 않도록 하십시오.

최소화에 도움이 되는 도구가 몇 가지 있습니다. 이러한 도구를 사용하면서 구문 오류, 타입 검사 오류, 빌림 검사 오류를 만들지 않는 방법을 잘 모르겠다면 전체 테스트 케이스와 최소화한 테스트 케이스를 모두 올리십시오. 일반적으로 구문을 인식하는 도구가 가장 짧은 시간에 가장 좋은 결과를 냅니다. treereduce-rust와 picireny는 구문을 인식합니다. halfempty는 구문을 인식하지 않지만 대체로 품질이 높은 도구입니다.

효과적인 퍼징

rustc를 퍼징할 때는 기계어 코드 생성을 피하는 것이 좋을 수 있습니다. 이 작업은 대부분 LLVM이 수행하기 때문입니다. 대신 --emit=mir를 사용해 보십시오.

다양한 컴파일러 플래그로 여러 가지 문제를 찾아낼 수 있습니다. -Zmir-opt-level=4는 기본적으로 실행되지 않는 MIR 최적화 패스를 켜므로 흥미로운 버그를 찾아낼 수도 있습니다. -Zvalidate-mir는 이러한 버그를 찾아내는 데 도움이 됩니다.

직접 빌드한 컴파일러를 퍼징한다면 초당 실행 횟수를 조금이라도 더 늘리기 위해 -C target-cpu=native나 PGO/BOLT까지 사용하여 빌드하는 것이 좋을 수 있습니다. 물론 여러 빌드 구성을 시도해 보고 실제로 어떤 구성이 더 높은 처리량을 내는지 확인하는 것이 가장 좋습니다.

추가 버그를 찾기 위해 디버그 단언을 활성화하여 소스에서 rustc를 빌드할 수도 있지만, 이렇게 하면 매 실행마다 추가 작업이 필요하므로 퍼징 속도가 느려질 수 있습니다. 디버그 단언을 활성화하려면 rustc를 컴파일할 때 bootstrap.toml에 다음을 추가하십시오:

rust.debug-assertions = true

재현하는 데 디버그 단언이 필요한 ICE에는 requires-debug-assertions 태그를 붙여야 합니다.

기존 프로젝트

  • fuzz-rustc는 libfuzzer로 rustc를 퍼징하는 방법을 보여 줍니다.
  • icemaker는 ICE를 잡아내기 위해 다양한 플래그로 대량의 소스 파일에 rustc 및 기타 도구를 실행합니다.
  • tree-splicer는 올바른 구문을 유지하면서 기존 소스 파일을 결합하여 새로운 소스 파일을 생성합니다. 이때 각 파일의 구문 트리를 이어 붙입니다