NPE navigation: better support compiler-generated null-checks

GitOrigin-RevId: 6f611a250f2fe0a2e1e0532b0abdca5045e5ec53
This commit is contained in:
Tagir Valeev
2020-05-09 07:19:48 +00:00
committed by intellij-monorepo-bot
parent 506a2a8ccf
commit 3b5bd1761b
4 changed files with 70 additions and 27 deletions
@@ -49,6 +49,9 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi
}
private static @Nullable Analysis getIntermediateRowAnalysis(@NotNull PsiElement anchor) {
if (anchor instanceof PsiExpression) {
return new Analysis(DfTypes.NULL, (PsiExpression)anchor);
}
if (!(anchor instanceof PsiIdentifier)) return null;
PsiReferenceExpression ref = tryCast(anchor.getParent(), PsiReferenceExpression.class);
if (ref == null) return null;
@@ -24,8 +24,10 @@ import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.PsiClass;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,6 +37,10 @@ import java.util.List;
* @author gregsh
*/
public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
@Override
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
return JAVA_8;
}
public void testParsing() {
myFixture.addClass("package com.sample;\n" +
@@ -620,6 +626,22 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
checkColumnFinder(classText, traceAndPositions);
}
public void testNpeRequireNonNullOnSwitch() {
@Language("JAVA") String classText =
"/** @noinspection ALL*/\n" +
"public class MainTest {\n" +
" public static void main(String[] args) {\n" +
" String test = null;\n" +
" switch(test) {}\n" +
" }\n" +
"}";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.NullPointerException\n", null, null),
Trinity.create("\tat java.base/java.util.Objects.requireNonNull(Objects.java:222)\n", null, null),
Trinity.create("\tat MainTest.main(MainTest.java:5)\n", 5, 16));
checkColumnFinder(classText, traceAndPositions);
}
public void testNpePoorMan() {
@Language("JAVA") String classText =
"/** @noinspection ALL*/\n" +
@@ -344,6 +344,13 @@ public class ExceptionWorker {
@Override
public PsiElement matchElement(@NotNull PsiElement element) {
if (myMethodName.equals("requireNonNull") && myClassName.equals(CommonClassNames.JAVA_UTIL_OBJECTS)) {
// Since Java 9 Objects.requireNonNull(x) is used by javac instead of x.getClass() for generated null-check (JDK-8074306)
PsiExpression expression = NullPointerExceptionInfo.matchCompilerGeneratedNullCheck(element);
if (expression != null) {
return expression;
}
}
if (!(element instanceof PsiIdentifier)) return null;
if (myMethodName.equals("<init>")) {
if (myHasDollarInName || element.textMatches(StringUtil.getShortName(myClassName))) {
@@ -54,9 +54,7 @@ public class NullPointerExceptionInfo extends ExceptionInfo {
if (result != null) return result;
result = fromSynchronized(e);
if (result != null) return result;
result = fromQualifiedNew(e);
if (result != null) return result;
result = fromMethodReference(e);
result = matchCompilerGeneratedNullCheck(e);
if (result != null) return result;
result = fromUnboxing(e, null);
if (result != null) return result;
@@ -127,26 +125,6 @@ public class NullPointerExceptionInfo extends ExceptionInfo {
}
return null;
}
private static PsiExpression fromMethodReference(PsiElement e) {
if (e instanceof PsiJavaToken && ((PsiJavaToken)e).getTokenType().equals(JavaTokenType.DOUBLE_COLON)) {
PsiMethodReferenceExpression methodRef = tryCast(e.getParent(), PsiMethodReferenceExpression.class);
if (methodRef == null) return null;
PsiExpression qualifier = methodRef.getQualifierExpression();
if (mayBeNull(qualifier)) return qualifier;
}
return null;
}
private static PsiExpression fromQualifiedNew(PsiElement e) {
if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.NEW)) {
PsiNewExpression newExpression = tryCast(e.getParent(), PsiNewExpression.class);
if (newExpression == null) return null;
PsiExpression qualifier = newExpression.getQualifier();
if (mayBeNull(qualifier)) return qualifier;
}
return null;
}
private static UnaryOperator<PsiElement> getJep358Extractor(String message) {
if (!message.startsWith("Cannot ")) return null;
@@ -191,10 +169,10 @@ public class NullPointerExceptionInfo extends ExceptionInfo {
String methodName = method.substring(dotPos + 1);
PsiPrimitiveType type = UNBOXING_METHODS.get(methodName);
return e -> {
if (methodName.equals("getClass")) {
PsiExpression result = fromMethodReference(e);
if (result != null) return result;
result = fromQualifiedNew(e);
if (methodName.equals("getClass") || methodName.equals("ordinal")) {
// x.getClass() was generated as a null-check by javac until Java 9
// it's still possible that such a code is executed under Java 14+
PsiElement result = matchCompilerGeneratedNullCheck(e);
if (result != null) return result;
}
if (type != null) {
@@ -214,6 +192,39 @@ public class NullPointerExceptionInfo extends ExceptionInfo {
return null;
}
@Nullable
static PsiExpression matchCompilerGeneratedNullCheck(PsiElement e) {
PsiExpression dereferenced = null;
if (e instanceof PsiJavaToken && ((PsiJavaToken)e).getTokenType().equals(JavaTokenType.DOUBLE_COLON)) {
// method reference qualifier
PsiMethodReferenceExpression methodRef = tryCast(e.getParent(), PsiMethodReferenceExpression.class);
if (methodRef != null) {
dereferenced = methodRef.getQualifierExpression();
}
}
else if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.SWITCH)) {
// switch on string or enum
PsiSwitchBlock switchBlock = tryCast(e.getParent(), PsiSwitchBlock.class);
if (switchBlock != null) {
PsiExpression selector = switchBlock.getExpression();
if (selector != null) {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(selector.getType());
if (psiClass != null && (psiClass.isEnum() || CommonClassNames.JAVA_LANG_STRING.equals(psiClass.getQualifiedName()))) {
dereferenced = selector;
}
}
}
}
else if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.NEW)) {
// qualified new
PsiNewExpression newExpression = tryCast(e.getParent(), PsiNewExpression.class);
if (newExpression != null) {
dereferenced = newExpression.getQualifier();
}
}
return mayBeNull(dereferenced) ? dereferenced : null;
}
private static boolean mayBeNull(PsiExpression qualifier) {
return qualifier != null && !(qualifier instanceof PsiNewExpression) &&
!(qualifier instanceof PsiLiteralExpression) && !(qualifier instanceof PsiPolyadicExpression);