Java 데이터 흐름 분석(DFA)와 디버거의 통합
저희는 가능한 예외, 항상 true/항상 false인 조건 등 프로그램에 관한 다양한 사실을 도출해 낼 수 있는 Java 데이터 흐름 분석(DFA)을 지원합니다. 이 기능은 소스 코드에 대한 추상적인 해석을 수행하여, 코드가 실행되기 전에 코드 실행에 대한 정보를 수집합니다. 하지만, 코드에 입력되는 내용에 대해서는 거의 아무 것도 알지 못합니다. 엄밀히 말하면, 메소드 매개 변수가 @NotNull
로 주석 처리된 경우, 분석 시 이 주석을 신뢰하여 null
이 여기에 나타나지 않을 것으로 가정하는데, 이건 단지 약간의 정보에 불과합니다.
다른 한편으로 저희는 디버거를 제공합니다. 중단점에서 중단될 때 이 도구는 각 변수 및 필드의 정확한 값, 각 배열의 내용 등 프로그램에 대한 거의 모든 사항을 이해합니다. 디버거는 현재에 대해 알고, DFA는 미래를 예측할 수 있는 셈입니다. 그렇다면, 현재 데이터를 DFA에 입력하여 발생하는 결과를 확인하면 어떨까요?
저희는 Java 디버거에 이러한 실험적 개선 사항을 추가했습니다. Java 소스를 디버그하고 중단점 위에 있으면 현재 프로그램 상태를 바탕으로 데이터 흐름 분석을 실행하여 다음에 발생할 내용을 확인합니다. 프로세스의 상태에 영향을 주지 않을 경우 디버깅 세션 안에서 아무 것도 실행되지 않습니다. 대략 프로세스의 가상적인 포크쯤 된다고 생각하시면 됩니다.
이 기능의 이점은 무엇일까요? 가장 유용한 점은 조건 평가입니다. 아래를 살펴봐주세요.
“= false” 및 “= true”라는 삽입된 힌트가 보이시나요? 이 힌트는 DFA가 추가한 것입니다. 이제 두 번째는 실행되지만 첫 번째 if
는 실행되지 않는다는 것을 알 수 있습니다. 마치 디버거가 코드 안에 있는 식을 단순히 평가하여 표시하는 것처럼 보입니다. 하지만 꼭 그렇지는 않습니다. if (exception == null) return false;
줄이 보이시나요? 디버거는 exception
이 null이므로 exception == null
은 true임을 이해합니다. 하지만, DFA는 이 조건이 실행될 때까지 exception
변수가 다시 할당될 수 있다는 것도 알고 있습니다. 따라서, 바로 결론을 내리지 않고, 대신 실제로 실행되었을 때 표시되는 값이 가져야하는 조건에 대한 결과만 표시합니다. 종종 변수의 값이 변할 수 있음을 이해하면서도 가끔은 정확히 어떻게 변하는지에 대해서도 이해합니다.
여기서 size
는 계산되지 않았지만, DFA는 미래를 예측하여 cst
가 Long
또는 Double
이 아님을 이해하여 size
는 1로 초기화되고, 다음 줄에서 size == 1
은 true가 됩니다. 다른 예를 볼까요.
steps의 현재 값은 0이므로 steps % 128 == 0
을 평가하면 true
가 됩니다. 하지만, DFA는 이를 false
로 표시합니다. 왜 그럴까요? steps++
가 실행되려고 하기 때문입니다.
또한, DFA는 실제로 발생하기도 전에 일부 알려진 예외에 대해 미리 경고를 보낼 수 있습니다(정적인 DFA와 달리 “잠재적인 NPE”가 아닌 확실한 것만 보고합니다). 예를 들면:
conf
가 null이고 역참조 연산자 전에 분명히 변하지 않으므로, 여기서 NPE는 불가피하며 DFA는 이에 대해 경고를 보냅니다. 10 프레임 위에 있는 어떤 블록에서 최종적으로 멈춰버리는 바람에 문제가 발생한 이유를 이해하지 못하는 것보다 미리 경고를 받는 것이 훨씬 더 편리합니다. 다음은 다른 예입니다.
에디터에는 표시되지 않지만, preConf.insnIndex
는 이 지점에서 음수라는 것을 알 수 있습니다. 따라서, dfsTree.loopEnters[insnIndex]
는 AIOOBE에서 실패할 것이라는 것을 알게 됩니다. 또한, DFA는 현재 ClassCastException
, ArrayStoreException
을 보고하고, @NotNull
로 주석 처리된 매개 변수에 보장된 null을 전달하며 메소드 컨트랙트 위반을 보고합니다.
향후, 저희는 실험적으로 더 많은 값을 표시할 수도 있습니다(현재는 true
및 false
만 표시됩니다). 또한, 실행되지 않을 코드 블록(예: if
브랜치)을 회색으로 처리하자는 제안이 있습니다.
DFA는 실제로 프로그램을 실행하는 것이 아니므로 틀릴 수도 있음에 유의하세요. 더 유용한 분석을 하기 위하여 다음과 같이 합리적이지만, 언제나 옳은 것은 아닌 가정을 합니다.
- final 필드는 절대로 변하지 않는다고 가정합니다.
- 메소드 컨트랙트를 신뢰합니다(예:
@NotNull
반환 값이 쓰인 경우 해당 메소드는 null을 반환하지 않는다고 가정합니다). - 현재 스레드에서 읽은 volatile이 아닌 필드는 최소한 일부 동기화 지점이 발생하기 전까지는(예:
synchronized {}
블록의 시작) 다른 스레드에서 절대로 변하지 않는다고 가정합니다. - pure로 주석 처리된 메소드는 가시적인 필드, 배열 등을 변경하지 않는다고 가정합니다.
- 기타 등등.
그래서, 잘못된 힌트가 표시될 때도 있지만, 이는 매우 드문 경우입니다.
이 기능은 v2020.1 EAP부터 사용할 수 있습니다. 이 기능이 마음에 들지 않으시면 Preferences / Settings | Build, Execution, Deployment | Debugger | Data Views | Java에서 Predict future condition values… 옵션을 끄면 됩니다.
하지만, 왜 마음에 들지 않으시는지 알려 주시면 감사하겠습니다. 그리고, 표시된 힌트를 오른쪽 클릭하여 간편하게 현재 디버그 세션에 대하여 일시적으로 DFA를 끌 수 있습니다.
우리는 여러분의 피드백을 언제나 환영합니다. 여기 또는 이 YouTrack 티켓에 여러분의 의견을 남겨주세요.
Happy Developing!
본문은 Tagir Valeev의 Integrating Java Dataflow Analysis and the Debugger를 번역한 글입니다.