Java 함정 - 비트 연산자와 부울 연산자
“Java Gotcha” - 의도치 않게 쉽게 구현되는 흔한 오류 패턴.
의외로 빠지기 쉬운 아주 간단한 Java 문제는: 부울 비교 연산자 대신 비트 연산자를 사용하는 것이다.
예를 들어, 실제로 "&&"를 입력하려 할 때 간단한 입력 오류로 인해 "&"가 입력될 수 있습니다.
코드를 검토할 때 우리가 배운 일반적인 휴리스틱 방법은 다음과 같습니다:
조건문에서 "&" 또는 "|"를 사용하는 것은 의도적이지 않을 수 있습니다.
이 블로그 글에서는 휴리스틱 접근법을 살펴보고, 이 코딩 문제를 식별하고 수정하는 방법을 확인해 보겠습니다.
문제는 어디에 있는가? 부울 연산을 사용하면 비트 연산을 정상적으로 수행할 수 있다.
부울 값을 사용하는 비트 연산자는 완전히 유효하므로 Java는 문법 오류를 보고하지 않습니다.
비트별 OR(|) 및 비트별 AND(&) 연산자의 진리값 표를 탐색하기 위해 JUnit 테스트를 구성한다면, 비트 연산자의 출력이 진리값 표와 일치하는 것을 확인할 수 있을 것입니다. 이를 고려할 때, 비트 연산자 사용이 문제가 되지 않는다고 생각할 수 있습니다.
AND 진상표
@Test
void 비트 연산자와 진리표 () {
assertions.assertEquals(true, true, true);
assertions.assertEquals(false, true, false);
assertions.assertEquals(false, false, true);
assertions.assertEquals(false, false, false);
}
테스트를 통과했습니다. 이것은 완전히 유효한 Java입니다.
OR 진상표
@Test
void 비트 연산자 또는 진리값 표 () {
assertions.asserteQuals(true, true | true);
assertions.asserteQuals(true, true | false);
assertions.asserteQuals(true, false | true);
assertions.asserteQuals(false, false | false);
}
이 테스트도 통과했는데, 왜 우리는 "&&"와 "||"를 더 선호할까요?
진상표 도형은 생성된 진실표 도구 에서 web.standfor.edu에서 생성된 것입니다.
문제: 단락 작업
진정한 문제는 비트 연산자(&, |)와 부울 연산자(&&, ||) 사이의 동작 차이입니다.
부울 연산자는 단락 연산자로, 필요한 경우에만 평가됩니다.
예를 들어
if (args!= null & args.length () > 23) {
system.out.println (args);
}
위의 코드에서는 비트 연산자를 사용했기 때문에 두 부울 조건 모두 평가됩니다:
- args!= 0
- args.length() > 23
args가 비어 있을 경우, 이는 내 코드가 NullPointerException 위험에 노출되게 합니다. 왜냐하면 두 개의 부울 조건을 반드시 평가해야 하기 때문에, 매개변수가 비어 있더라도 항상 args.length를 검사하게 되기 때문입니다.
부울 연산자의 단락 평가
&&를 사용할 때, 예를 들어
if (args!= null && args.length () > 23) {
system.out.println (args);
}
arg = null 이 아닐 때만 false로 계산되며, 조건 표현식의 계산이 중단됩니다.
우측을 평가할 필요가 없습니다.
오른쪽 조건의 결과가 어떻든, 부울 표현식의 최종 값은 거짓이 될 것입니다.
그러나 이는 실제 코드에서는 절대 발생하지 않습니다.
이는 매우 흔히 저지르는 실수이며, 정적 분석 도구가 항상 이 실수를 범하는 것은 아닙니다.
다음 Google Dork를 사용하여 이러한 패턴의 공개된 예시를 찾을 수 있는지 확인했습니다:
파일 유형: java if “!=null &”
이번 검색에서 rootwindowContainer 내에서 Android에서 유래한 코드 일부를 찾아냈습니다.
isDocument = Intent != null & intent.isDocument()
이 코드는 코드 검토를 통과할 수 있습니다. 왜냐하면 우리는 종종 할당문에서 비트 연산자를 사용하여 값을 가리기 때문입니다. 하지만 이 경우 결과는 위의 if 문 예제와 동일합니다. intent가 영원히 null이라면 NullPointerException이 발생합니다.
우리는 방어적 코딩을 하고 중복 코드를 작성하는 경우가 많아 종종 이러한 구조에서 벗어나곤 합니다. `check! = null`은 대부분의 사용 사례에서 불필요할 가능성이 높습니다.
이것은 프로그래머가 생산 코드에서 저지르는 실수입니다.
검색 결과가 얼마나 최신인지 모르겠지만, 제가 검색을 할 때 반환되는 코드는 구글, 아마존, 아파치... 그리고 저에게서 비롯됩니다.
최근의 풀 리퀘스트는 제 오픈소스 프로젝트에서 바로 이 오류를 해결하기 위한 것입니다.
if (键入!=null & type.trim () .length () >0) {
AcceptMediatypeDefinitionsList.add (type.trim ());
}
이것을 어떻게 찾을 수 있나요?
여러 정적 분석기에서 내 예제 코드를 살펴봤을 때, 그들 중 어느 것도 이 숨겨진 자폭 코드를 발견하지 못했습니다.
Secure Code Warrior 우리는 이 문제를 해결할 수 있는 상당히 간단한 Sensei 작성하고 검토했습니다.
비트 연산자는 완전히 유효하며 할당에 자주 사용되기 때문에, 문제의 코드를 찾기 위해 if 문 사용 사례와 비트 단위 AND 연산자(Bitwise &) 사용에 중점을 두어 연구했습니다.
搜索:
表情:
AnyOF:
-在:
条件:{}
价值:
区分大小写:假
匹配:“.* &。*”
조건 표현식으로 사용될 때, 정규 표현식을 사용하여 "&"를 일치시킵니다. 예를 들어 if 문에서.
이 문제를 해결하기 위해 우리는 다시 정규 표현식에 의존합니다. 이번에는 QuickFix의 sed 함수를 사용하여 표현식 내의 &를 &&로 전역 대체합니다.
사용 가능한 수정 프로그램:
- 이름: "비트별 AND 연산자를 논리 AND 연산자로 대체"
작업:
- 재작성:
변경 내용: "{{#sed}} s/&/&&&g,{{{.}}} {{/sed}}"
각주
이것은 가장 흔한 비트 연산자 오용 사례, 즉 실제로 부울 연산자를 사용할 때를 포함합니다.
다른 상황에서는 이러한 현상이 발생할 수 있습니다. 예를 들어 작업 예시에서 그렇습니다. 그러나 레시피를 작성할 때는 가짜 양성 식별을 최대한 피해야 합니다. 그렇지 않으면 레시피가 무시되거나 비활성화될 수 있습니다. 우리는 가장 일반적인 상황에 부합하도록 레시피를 생성합니다. Sensei가 발전함에 따라 더 많은 일치 조건을 포괄하기 위해 검색 기능에 추가 특이성을 도입할 가능성이 높습니다.
현재 형태로 볼 때, 이 공식은 많은 실제 사용 사례를 식별할 것이며, 무엇보다도제 프로젝트에서 보고된 그 사례를 포함합니다.
참고: 이 예제와 레시피 검토에 기여한 많은 코드 전사들이 있습니다—찰리 에릭슨, 매튜 칼리, 로빈 클레어호트, 브라이슨 액스, 네이선 데스메트, 도니 로버슈턴. 도움 주셔서 감사합니다.
---
IntelliJ에서 Sensei 설치하려면 "환경설정\ 플러그인"(Mac) 또는 "설정\ 플러그인"(Windows)을 사용한 Sensei "sensei Sensei
Secure Code Warrior "sensei" 저장소에는 이 글을 포함한 여러 블로그 글의 소스 코드와 레시피가 있습니다.
https://github.com/securecodewarrior/sensei-blog-examples
Sensei 대해 Sensei 알아보기