1. infer란?
페이스북에서 개발한 정적 분석기로, Java, ObjC/C/C++ 등의 언어 분석을 지원한다. 정적 분석기인만큼 프로그램을 실행하지 않고 소스 코드를 분석하여 오류를 탐지한다.
infer는 구조적으로 앞단과 뒷단으로 구성된다. 앞단에서는 분석 대상 프로그램을 infer가 이해할 수 있는 중간 언어인 SIL 또는 HIL로 변환하고, 뒷단에서는 변환된 코드를 기반으로 다양한 체커들이 오류를 탐지한다.
이때 체커는 infer 내부에서 특정 오류를 탐지하기 위해 설계된 독립적인 분석 모듈로, 각각 고유한 분석 기법을 사용하여 변환된 프로그램을 분석하는 역할을 한다.
자바 코드에서 널 포인터 접근을 방지하는 Nullsafe, 멀티스레드 환경에서 발생할 수 있는 레이스 컨디션을 분석하는 RacerD, 코드 변경에 따른 실행 시간 복잡도 증가를 탐지하는 Cost Checker 등의 체커가 있으며, 이 외에도 다양한 체커들이 존재한다. 사용자는 분석 목적에 따라 기존 체커를 확장하거나 새로운 체커를 직접 추가하여 활용할 수 있다.
2. infer 설치 방법, Mac M1(arm)
자세한 내용은 1.2.0 버전 릴리즈를 확인하면 된다.
아래 코드는 25.05.27 기준으로 작성.
2.1. 패키지 다운로드
VERSION=1.2.0
curl -sSL "https://github.com/facebook/infer/releases/download/v$VERSION/infer-osx-arm64-v$VERSION.tar.xz" \
| sudo tar -C /opt -xJ
위 명령어를 통해 1.2.0 버전의 macOS arm용 infer 패키지를 다운로드한 뒤, 압축을 풀어 설치한다.
2.2. 심볼릭 링크 생성
sudo ln -s "/opt/infer-osx-arm64-v$VERSION/bin/infer" /usr/local/bin/infer
터미널 어디서든 편하게 infer를 실행하기 위해 심볼릭 링크를 생성한다.
2.3. infer 버전 확인
infer --version
#Infer version v1.2.0
#Copyright 2009 - present Facebook. All Rights Reserved.
주석과 같은 출력이 나타나면 infer가 정상적으로 설치된 것이다.
3. 실행 과정
infer의 정적 분석은 캡처와 분석, 두 단계로 이루어진다.
우선 캡처 단계에서는 사용자가 입력한 컴파일 명령을 가로채, 분석할 파일을 infer의 내부 중간 언어로 변환한다. 이 과정에서 실제로 컴파일이 진행되어야만 infer가 필요한 정보를 수집할 수 있다. 또한 infer를 실행하면 명령이 실행된 디렉토리에 infer-out 폴더가 생성되며, 이 안에 분석에 사용될 중간 언어(중간 표현)가 저장된다.
분석 단계에서는 infer가 infer-out 폴더에 저장된 중간 언어들을 분석한다. 각 함수와 메서드를 개별적으로 분석하며, 하나의 함수 또는 메서드를 분석하는 도중 오류가 발생하면 해당 부분의 분석은 중단되지만 다른 함수나 메서드의 분석은 계속 진행된다.
분석을 마치면 결과는 터미널에 출력되며, infer-out 폴더 내의 report.txt 파일로 저장된다. infer는 실제로 문제가 있을 가능성이 높은 오류들 위주로 필터링하여 보여준다.
3.1. 실행 명령어
# 캡처 + 분석
infer run -- <컴파일 명령어 및 옵션>
# 캡처만
infer capture -- <컴파일 명령어 및 옵션>
# 분석만
infer analyze
#ex) infer run -- gcc -c hello.c
#ex) infer run -- make
정적 분석을 실행하려면 infer run -- 뒤에 컴파일러 명령어와 옵션을 입력하면 된다. 사용하는 컴파일러에 따라 gcc, clang, javac 등을 지정할 수 있으며, Makefile이 있는 경우 make 명령어를 그대로 사용할 수도 있다.
run은 기본적으로 캡처와 분석을 동시에 진행하며, 이 두 단계를 별도로 실행하려면 capture, analyze 명령어를 각각 사용하면 된다.
4. 예시 코드 실행
4.1. 예시 코드 (NULL 포인터 역참조)
#include <stdio.h>
char* may_return_null() {
return NULL;
}
int main() {
char *msg = may_return_null();
char c = msg[0];
return 0;
}
NULL 포인터 역참조 오류를 포함한 예시 코드를 작성한 뒤, infer를 사용해 정적 분석을 수행했다. 분석이 완료되면 결과는 터미널에 출력되며, 동시에 infer-out 폴더 내부에 report.txt와 report.json 파일 형태로 저장된다.
4.2. 결과 출력 및 report.txt 저장
변수에 값을 저장해놓고 사용하지 않는 경우(Dead Store)와 NULL값에 역참조하는 경우(Null Dereference)의 두 가지 오류가 탐지된 것을 확인할 수 있다. 결과를 infer-out/report.txt에 저장한 것도 확인할 수 있다.
4.3. infer-out/report.json
{
"bug_type": "NULLPTR_DEREFERENCE",
"qualifier": "`msg` could be null (from the call to `may_return_null()` on line 8) and is dereferenced.",
"severity": "ERROR",
"category": "Null pointer dereference",
"line": 9,
"column": 14,
"procedure": "main",
"procedure_start_line": 7,
"file": "infer_C.c",
"bug_trace": [
{
"level": 1,
"filename": "infer_C.c",
"line_number": 8,
"column_number": 17,
"description": "in call to `may_return_null`"
},
{
"level": 2,
"filename": "infer_C.c",
"line_number": 4,
"column_number": 5,
"description": "is assigned to the null pointer"
},
{ "level": 2, "filename": "infer_C.c", "line_number": 4, "column_number": 5, "description": "returned" },
{
"level": 1,
"filename": "infer_C.c",
"line_number": 8,
"column_number": 17,
"description": "return from call to `may_return_null`"
},
{ "level": 1, "filename": "infer_C.c", "line_number": 8, "column_number": 5, "description": "assigned" },
{
"level": 1,
"filename": "infer_C.c",
"line_number": 9,
"column_number": 14,
"description": "invalid access occurs here"
}
],
"bug_trace_length": 6,
"bug_trace_max_depth": 2,
"key": "infer_C.c|main|NULLPTR_DEREFERENCE",
"hash": "330ca703a1d1d7481787923c1ff78ec8",
"bug_type_hum": "Null Dereference",
"extras": {}
}
report.json은 정적 분석 결과를 JSON 형식으로 구조화해 제공하는 파일로, 버그에 대한 다양한 정보를 포함하고 있다. 위는 예시 코드를 정적 분석한 후 얻은 결과이며, 각 항목의 의미는 다음과 같다.
기본 정보
"bug_type" | 버그 유형의 코드 (ex: NULLPTR_DEREFERENCE) |
"bug_type_hum" | 사람이 읽기 쉬운 버그 이름 (ex: Null Dereference) |
"qualifier" | 버그 원인 설명 |
"severity" | WARING,ERROR 등 중요도 표시 |
"category" | 버그가 속한 분류 |
"key" | 버그 고유 식별자 (형식: 파일명|함수명|버그유형) |
"hash" | 버그 고유 해시값 |
"file" | 버그 발생한 소스코드 파일 이름 |
"procedure" | 버그 발생 함수 이름 |
"procedure_start_line" |
해당 함수 시작 라인 번호 |
버그 위치 정보
"line" | 버그가 발생한 실제 소스코드 라인 |
"column" | 버그가 발생한 소스코드 열 |
버그 발생 경로(bug_trace)
"level" | 추적 깊이 (1이 최상위로 숫자가 커질수록 더 깊은 호출 관계) |
"filename" | 발생한 파일 이름 |
"line_number" | 해당 이벤트가 발생한 줄 |
"column_number" | 해당 줄에서의 열 |
"description" | 해당 위치에서 발생한 동작 설명 |
report.json 파일에 담긴 다양한 정보를 통해 infer가 발견한 버그의 정확한 위치와 특성을 파악할 수 있으며, bug trace를 통해 버그가 발생한 경로와 원인까지 추적할 수 있다.
이러한 정보를 활용하면 '버그 발생 함수 자동 추출'과 같은 다양한 후처리 작업을 자동화할 수 있다.
참고 자료
1. facebook/infer repository
https://github.com/facebook/infer
2. infer 공식문서
https://fbinfer.com/docs/about-Infer/
3. 정적 분석기 “인퍼” 소개 – 조성근, 정보과학회지
https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE10554205
'개발 지식' 카테고리의 다른 글
형태소 분석기 ‘바른’ REST 방식으로 사용하기 (1) | 2024.12.11 |
---|---|
[리액트] i18next 통해 다국어를 지원해 보자 (4) | 2024.08.29 |
Chakra(차크라) UI를 사용해 보자(with React) (0) | 2024.07.18 |
[TS] interface와 type alias의 차이점은 무엇인가? (0) | 2024.07.06 |