IDEA-204608 Integrate production stacktraces into static nullability analysis improvement

- Support JetBrains NotNull instrumented parameters
- Better caret positioning

GitOrigin-RevId: f867eb5599faa2bc496fa30eae9402597043c268
This commit is contained in:
Tagir Valeev
2020-05-08 08:53:14 +00:00
committed by intellij-monorepo-bot
parent de4db0fbc2
commit 37de5ef597
15 changed files with 410 additions and 197 deletions
@@ -35,6 +35,10 @@ public class ExceptionFilter implements Filter, DumbAware {
ExceptionWorker worker = new ExceptionWorker(myCache);
Result result = worker.execute(line, textEndOffset, myNextLineRefiner);
if (result == null) {
if (myNextLineRefiner != null) {
myNextLineRefiner = myNextLineRefiner.consumeNextLine(line);
if (myNextLineRefiner != null) return null;
}
ExceptionInfo exceptionInfo = ExceptionInfo.parseMessage(line, textEndOffset);
myNextLineRefiner = exceptionInfo == null ? null : exceptionInfo.getPositionRefiner();
return null;
@@ -106,7 +106,10 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi
return fromAssertionError(anchor);
}
else if (info instanceof ArrayIndexOutOfBoundsExceptionInfo) {
return fromArrayIndexOutOfBoundsException(anchor, ((ArrayIndexOutOfBoundsExceptionInfo)info).getIndex());
Integer index = ((ArrayIndexOutOfBoundsExceptionInfo)info).getIndex();
if (index != null && anchor instanceof PsiExpression) {
return Analysis.create(DfTypes.intValue(index), (PsiExpression)anchor);
}
}
else if (info instanceof ClassCastExceptionInfo) {
return fromClassCastException(anchor, ((ClassCastExceptionInfo)info).getActualClass());
@@ -115,29 +118,31 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi
return Analysis.create(DfTypes.NULL, findDereferencedExpression(anchor));
}
else if (info instanceof NegativeArraySizeExceptionInfo) {
return fromNegativeArraySizeException(anchor, ((NegativeArraySizeExceptionInfo)info).getSuppliedSize());
Integer size = ((NegativeArraySizeExceptionInfo)info).getSuppliedSize();
if (size != null && size < 0 && anchor instanceof PsiExpression) {
return Analysis.create(DfTypes.intValue(size), (PsiExpression)anchor);
}
}
else if (info instanceof ArithmeticExceptionInfo) {
return fromArithmeticException(anchor);
}
else if (info instanceof JetBrainsNotNullInstrumentationExceptionInfo) {
if (anchor instanceof PsiExpression) {
return Analysis.create(DfTypes.NULL, (PsiExpression)anchor);
}
}
return null;
}
private static Analysis fromArithmeticException(PsiElement anchor) {
if (anchor instanceof PsiJavaToken && (((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.DIV) ||
((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.PERC))) {
PsiPolyadicExpression division = tryCast(anchor.getParent(), PsiPolyadicExpression.class);
if (division != null) {
PsiExpression divisor = PsiTreeUtil.getNextSiblingOfType(anchor, PsiExpression.class);
if (divisor != null) {
PsiType type = divisor.getType();
if (PsiType.LONG.equals(type)) {
return Analysis.create(DfTypes.longValue(0), divisor);
}
else if (TypeConversionUtil.isIntegralNumberType(type)) {
return Analysis.create(DfTypes.intValue(0), divisor);
}
}
if (anchor instanceof PsiExpression) {
PsiExpression divisor = (PsiExpression)anchor;
PsiType type = divisor.getType();
if (PsiType.LONG.equals(type)) {
return Analysis.create(DfTypes.longValue(0), divisor);
}
else if (TypeConversionUtil.isIntegralNumberType(type)) {
return Analysis.create(DfTypes.intValue(0), divisor);
}
}
return null;
@@ -264,9 +269,7 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi
}
private @Nullable Analysis fromClassCastException(@NotNull PsiElement anchor, @Nullable String actualClass) {
if (!(anchor instanceof PsiJavaToken) || !((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.LPARENTH)) {
return null;
}
if (!(anchor instanceof PsiTypeElement)) return null;
PsiTypeCastExpression castExpression = tryCast(anchor.getParent(), PsiTypeCastExpression.class);
if (castExpression == null) return null;
PsiExpression ref = extractAnchor(castExpression.getOperand());
@@ -286,37 +289,10 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi
return null;
}
@Nullable
private static Analysis fromArrayIndexOutOfBoundsException(@NotNull PsiElement anchor, @Nullable Integer index) {
if (index != null && anchor instanceof PsiJavaToken && ((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.LBRACKET)) {
PsiArrayAccessExpression access = tryCast(anchor.getParent(), PsiArrayAccessExpression.class);
if (access != null) {
return Analysis.create(DfTypes.intValue(index), access.getIndexExpression());
}
}
return null;
}
@Nullable
private static Analysis fromNegativeArraySizeException(@NotNull PsiElement anchor, @Nullable Integer size) {
if (size == null || size >= 0) return null;
if (anchor instanceof PsiKeyword && anchor.textMatches(PsiKeyword.NEW) && anchor.getParent() instanceof PsiNewExpression) {
PsiExpression[] dimensions = ((PsiNewExpression)anchor.getParent()).getArrayDimensions();
if (dimensions.length == 1) {
return Analysis.create(DfTypes.intValue(size), dimensions[0]);
}
}
return null;
}
@Nullable
private static Analysis fromAssertionError(@NotNull PsiElement anchor) {
if (anchor instanceof PsiKeyword &&
anchor.textMatches(PsiKeyword.ASSERT)) {
PsiAssertStatement assertStatement = tryCast(anchor.getParent(), PsiAssertStatement.class);
if (assertStatement != null) {
return tryNegate(fromCondition(assertStatement.getAssertCondition()));
}
if (anchor instanceof PsiAssertStatement) {
return tryNegate(fromCondition(((PsiAssertStatement)anchor).getAssertCondition()));
}
return null;
}
@@ -174,7 +174,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
Trinity.create("\tat SomeClass.lambda$main$0(SomeClass.java:32)\n", 32, 32),
Trinity.create("\tat SomeClass.main(SomeClass.java:33)\n", 33, 7),
Trinity.create("Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 0\n", null, null),
Trinity.create("\tat SomeClass.<init>(SomeClass.java:4)\n", 4, 36),
Trinity.create("\tat SomeClass.<init>(SomeClass.java:4)\n", 4, 37),
Trinity.create("\tat SomeClass$1.<init>(SomeClass.java:18)\n", 18, 9),
Trinity.create("\tat SomeClass.test(SomeClass.java:18)\n", 18, 9),
Trinity.create("\tat SomeClass.access$000(SomeClass.java:2)\n", 2, 1),
@@ -222,7 +222,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}\n";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ArrayIndexOutOfBoundsException: 0\n", null, null),
Trinity.create("\tat Test.main(Test.java:3)\n", 3, 28));
Trinity.create("\tat Test.main(Test.java:3)\n", 3, 29));
checkColumnFinder(classText, traceAndPositions);
}
@@ -284,7 +284,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}\n";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.RandomAccess\n", null, null),
Trinity.create("\tat Test.main(Test.java:7)\n", 7, 58));
Trinity.create("\tat Test.main(Test.java:7)\n", 7, 59));
checkColumnFinder(classText, traceAndPositions);
}
@@ -323,7 +323,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}\n";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long\n", null, null),
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 40));
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 41));
checkColumnFinder(classText, traceAndPositions);
}
@@ -338,7 +338,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}\n";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ClassCastException: class [I cannot be cast to class [C\n", null, null),
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 42));
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 43));
checkColumnFinder(classText, traceAndPositions);
}
@@ -353,7 +353,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}\n";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ClassCastException: class [[I cannot be cast to class [[C\n", null, null),
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 65));
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 66));
checkColumnFinder(classText, traceAndPositions);
}
@@ -368,7 +368,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.NegativeArraySizeException\n", null, null),
Trinity.create("\tat SomeClass.main(SomeClass.java:5)\n", 5, 20));
Trinity.create("\tat SomeClass.main(SomeClass.java:5)\n", 5, 34));
checkColumnFinder(classText, traceAndPositions);
}
@@ -384,7 +384,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
"}";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ArithmeticException: / by zero\n", null, null),
Trinity.create("\tat SomeClass.main(SomeClass.java:6)\n", 6, 20));
Trinity.create("\tat SomeClass.main(SomeClass.java:6)\n", 6, 22));
checkColumnFinder(classText, traceAndPositions);
}
@@ -402,6 +402,24 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
Trinity.create("\tat Test.main(Test.java:5)\n", 5, 27));
checkColumnFinder(classText, traceAndPositions);
}
public void testNpeJetBrains() {
@Language("JAVA") String classText =
"package foo.bar;\n" +
"class Test {\n" +
" void caller(String a, String b, String c) {\n" +
" callee(a, b, c);\n" +
" }\n" +
"\n" +
" void callee(String x, String y, String z) {}\n" +
"}";
List<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("java.lang.IllegalArgumentException: Argument for @NotNull parameter 'y' of foo/bar/Test.callee must not be null\n", null, null),
Trinity.create("\tat foo.bar.Test.$$$reportNull$$$0(Test.java)\n", null, null),
Trinity.create("\tat foo.bar.Test.callee(Test.java)\n", null, null),
Trinity.create("\tat foo.bar.Test.caller(Test.java:4)\n", 4, 15));
checkColumnFinder(classText, traceAndPositions);
}
public void testNpeSynchronized() {
@Language("JAVA") String classText =
@@ -530,6 +548,7 @@ public class ExceptionWorkerTest extends LightJavaCodeInsightFixtureTestCase {
Integer row = line.getSecond();
Integer column = line.getThird();
if (row != null) {
assertNotNull(result);
HyperlinkInfo info = result.getFirstHyperlinkInfo();
assertNotNull(info);
info.navigate(getProject());
@@ -3,167 +3,170 @@ package com.intellij.java.slicer;
import com.intellij.execution.filters.ExceptionAnalysisProvider;
import com.intellij.execution.filters.ExceptionInfo;
import com.intellij.execution.filters.ExceptionLineRefiner;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.LightJavaCodeInsightTestCase;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightTestCase {
public void testArrayIndex() {
doTest("java.lang.ArrayIndexOutOfBoundsException: 10",
"Find why 'idx' could be 10", "class X {static int test(int[] x, int idx) {return x<caret>[idx];}}");
"Find why 'idx' could be 10", "class X {static int test(int[] x, int idx) {return x[idx];}}");
}
public void testClassCast() {
doTest("java.lang.ClassCastException: class X cannot be cast to class java.lang.Number",
"Find why 'obj' could be instanceof X (not-null)",
"class X {static void test(Object obj) {System.out.println((<caret>(Number) obj).intValue());}}");
"Find why 'obj' could be instanceof X (not-null)",
"class X {static void test(Object obj) {System.out.println(((Number) obj).intValue());}}");
}
public void testClassCastUnknownClass() {
doTest("java.lang.ClassCastException: class XYZ cannot be cast to class java.lang.Number",
"Find why 'obj' could be not instanceof java.lang.Number (not-null)",
"class X {static void test(Object obj) {System.out.println((<caret>(Number) obj).intValue());}}");
"Find why 'obj' could be not instanceof java.lang.Number (not-null)",
"class X {static void test(Object obj) {System.out.println(((Number) obj).intValue());}}");
}
public void testNpe() {
doTest("java.lang.NullPointerException: Cannot invoke \"Object.hashCode()\" because \"obj\" is null",
"Find why 'obj' could be null",
"class X {static void test(Object obj) {System.out.println(obj.<caret>hashCode());}}");
"class X {static void test(Object obj) {System.out.println(obj.hashCode());}}");
}
public void testNpeArray() {
doTest("java.lang.NullPointerException: Cannot load from object array because \"obj\" is null",
"Find why 'obj' could be null",
"class X {static void test(Object[] obj) {System.out.println(obj<caret>[0]);}}");
"class X {static void test(Object[] obj) {System.out.println(obj[0]);}}");
}
public void testNpeSynchronized() {
doTest("java.lang.NullPointerException: Cannot enter synchronized block because \"obj\" is null",
"Find why 'obj' could be null",
"class X {static void test(Object obj) {<caret>synchronized(obj){}}}");
"class X {Object lock;void test() {synchronized(obj) {System.out.println(obj);}}}");
}
public void testNpeThrow() {
doTest("java.lang.NullPointerException: Cannot throw exception because \"obj\" is null",
"Find why 'obj' could be null",
"class X {static void test(RuntimeException obj) {<caret>throw obj;}}");
"Find why 'obj' could be null",
"class X {static void test(RuntimeException obj) {throw obj;}}");
}
public void testAssertChar() {
doTest("java.lang.AssertionError",
"Find why 'c' could be 97",
"class X {static void test(char c) {<caret>assert c != 'a';}}");
"class X {static void test(char c) {assert c != 'a';}}");
}
public void testAssertDivisibility() {
doTest("java.lang.AssertionError",
"Find why 'i' could be odd",
"class X {static void test(int i) {<caret>assert i % 2 == 0;}}");
"class X {static void test(int i) {assert i % 2 == 0;}}");
}
public void testAssertAnd() {
doTest("java.lang.AssertionError",
"Find why 'idx' could be <= 0 or >= 4",
"class X {static void test(int idx) {<caret>assert idx > 0 && idx < 4;}}");
"class X {static void test(int idx) {assert idx > 0 && idx < 4;}}");
}
public void testAssertOr() {
doTest("java.lang.AssertionError",
"Find why 'idx' could be in {0..4}",
"class X {static void test(int idx) {<caret>assert idx < 0 || idx > 4;}}");
"class X {static void test(int idx) {assert idx < 0 || idx > 4;}}");
}
public void testAssertByte() {
doTest("java.lang.AssertionError",
"Find why 'idx' could be <= 0",
"class X {static void test(byte idx) {<caret>assert idx > 0;}}");
"class X {static void test(byte idx) {assert idx > 0;}}");
}
public void testAssertShort() {
doTest("java.lang.AssertionError",
"Find why 'idx' could be <= 0",
"class X {static void test(short idx) {<caret>assert idx > 0;}}");
"class X {static void test(short idx) {assert idx > 0;}}");
}
public void testAssertLong() {
doTest("java.lang.AssertionError",
"Find why 'idx' could be <= 0",
"class X {static void test(long idx) {<caret>assert idx > 0;}}");
"class X {static void test(long idx) {assert idx > 0;}}");
}
public void testAssertBoxed() {
// boxed types are unsupported
doTest("java.lang.AssertionError",
null,
"class X {static void test(Long idx) {<caret>assert idx > 0;}}");
"class X {static void test(Long idx) {assert idx > 0;}}");
}
public void testInstanceOf() {
doTest("java.lang.IllegalArgumentException",
"Find why 'obj' could be null or not instanceof java.lang.String",
"class X {static void test(Object obj) {if (!(obj instanceof String)) throw <caret>new IllegalArgumentException();}}");
"class X {static void test(Object obj) {if (!(obj instanceof String)) throw new IllegalArgumentException();}}");
}
public void testStringInEquality() {
doTest("java.lang.IllegalArgumentException",
"Find why 's' could be != \"hello\" (not-null)",
"class X {static void test(String s) {if (!s.equals(\"hello\")) throw <caret>new IllegalArgumentException();}}");
"class X {static void test(String s) {if (!s.equals(\"hello\")) throw new IllegalArgumentException();}}");
}
public void testStringEqEq() {
doTest("java.lang.IllegalArgumentException",
null,
"class X {static void test(String s) {if (s == \"hello\")) throw <caret>new IllegalArgumentException();}}");
"class X {static void test(String s) {if (s == \"hello\") throw new IllegalArgumentException();}}");
}
public void testClassEquality() {
doTest("java.lang.IllegalArgumentException",
"Find why 'cls' could be String",
"class X {static void test(Class<?> cls) {if (cls.equals(String.class)) throw <caret>new IllegalArgumentException();}}");
"class X {static void test(Class<?> cls) {if (cls.equals(String.class)) throw new IllegalArgumentException();}}");
}
public void testClassInEquality() {
doTest("java.lang.IllegalArgumentException",
"Find why 'cls' could be != String (not-null)",
"class X {static void test(Class<?> cls) {if (!cls.equals(String.class)) throw <caret>new IllegalArgumentException();}}");
"class X {static void test(Class<?> cls) {if (!cls.equals(String.class)) throw new IllegalArgumentException();}}");
}
public void testClassInEqualityInverted() {
doTest("java.lang.IllegalArgumentException",
"Find why 'cls' could be null or != String",
"class X {static void test(Class<?> cls) {if (!String.class.equals(cls)) {throw <caret>new IllegalArgumentException();}}}");
"class X {static void test(Class<?> cls) {if (!String.class.equals(cls)) {throw new IllegalArgumentException();}}}");
}
public void testBooleanTrue() {
doTest("java.lang.IllegalArgumentException",
"Find why 'b' could be true",
"class X {static void test(boolean b) {if (b) {throw <caret>new IllegalArgumentException();}}}");
"class X {static void test(boolean b) {if (b) {throw new IllegalArgumentException();}}}");
}
public void testBooleanFalse() {
doTest("java.lang.IllegalArgumentException",
"Find why 'b' could be false",
"class X {static void test(boolean b) {if (!b) {throw <caret>new IllegalArgumentException();}}}");
"class X {static void test(boolean b) {if (!b) {throw new IllegalArgumentException();}}}");
}
public void testIsNull() {
doTest("java.lang.IllegalArgumentException",
"Find why 'obj' could be null",
"class X {static void test(Object obj) {if (obj == null) {throw <caret>new IllegalArgumentException();}}}");
"class X {static void test(Object obj) {if (obj == null) {throw new IllegalArgumentException();}}}");
}
public void testIsNotNull() {
doTest("java.lang.IllegalArgumentException",
"Find why 'obj' could be not-null",
"class X {static void test(Object obj) {if (null != obj) {throw <caret>new IllegalArgumentException();}}}");
"class X {static void test(Object obj) {if (null != obj) {throw new IllegalArgumentException();}}}");
}
public void testEnumEquality() {
doTest("java.lang.IllegalArgumentException",
"Find why 'x' could be X.A",
"enum X {A,B,C;static void test(X x) {if (x == X.A) throw <caret>new IllegalArgumentException();}}");
"enum X {A,B,C;static void test(X x) {if (x == X.A) throw new IllegalArgumentException();}}");
}
public void testInSwitch() {
@@ -176,32 +179,32 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT
" System.out.println(\"oops\");\n" +
" break;\n" +
" case 5:\n" +
" throw <caret>new IllegalArgumentException();\n" +
" throw new IllegalArgumentException();\n" +
" }\n" +
" }" +
"}");
}
public void testInSwitchRule() {
doTest("java.lang.IllegalArgumentException",
doTest("java.lang.RuntimeException",
"Find why 'x' could be in {2, 3, 5}",
"class X {static void test(int x) { switch (x) { case 2,3,5 -> throw <caret>new RuntimeException();default -> {} } }}");
"class X {static void test(int x) { switch (x) { case 2,3,5 -> throw new RuntimeException();default -> {} } }}");
}
public void testInSwitchDefault() {
doTest("java.lang.IllegalArgumentException",
"Find why 'x' could be <= 0 or >= 5",
"class X {static void test(int x) { switch (x) { " +
"case 1: break; case 2: break; case 3, 4: break; " +
"case 5: default: throw <caret>new IllegalArgumentException(); } }");
"case 1: break; case 2: return; case 3, 4: System.out.println(\"\");break; " +
"case 5: default: throw new IllegalArgumentException(); } } }");
}
public void testInSwitchDefaultString() {
doTest("java.lang.IllegalArgumentException",
"Find why 's' could be != \"BAR\", \"FOO\" (not-null)",
"class X {static void test(String s) { switch (s) { " +
"case \"FOO\": break; case \"BAR\": break;" +
"default: case \"BAZ\": throw <caret>new IllegalArgumentException(); } }");
"case \"FOO\": break; case \"BAR\": return;" +
"default: case \"BAZ\": throw new IllegalArgumentException(); } } }");
}
public void testIfExits() {
@@ -213,7 +216,7 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT
" System.out.println(\"ok\");\n" +
" return;\n" +
" }\n" +
" throw <caret>new IllegalArgumentException();\n" +
" throw new IllegalArgumentException();\n" +
" }" +
"}");
}
@@ -221,75 +224,119 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT
public void testNoInfo() {
doTest("java.lang.IllegalArgumentException",
null,
"class X {static void test(X x) {throw <caret>new IllegalArgumentException();}}");
"class X {static void test(X x) {throw new IllegalArgumentException();}}");
}
public void testNegativeArraySizeException() {
doTest("java.lang.NegativeArraySizeException: -2",
"Find why 'x' could be -2",
"class X {static void test(int x) {int[] data = <caret>new int[x];}}");
"class X {static void test(int x) {int[] data = new int[x];}}");
}
public void testDivisionByZero() {
doTest("java.lang.ArithmeticException: / by zero",
"Find why 'y' could be 0",
"class X {static void test(int x, int y) {int[] data = new int[x<caret>/y];}}");
"class X {static void test(int x, int y) {int[] data = new int[x/y];}}");
}
public void testModByZero() {
doTest("java.lang.ArithmeticException: / by zero",
"Find why 'y' could be 0",
"class X {static void test(int x, long y) {long res = x <caret>% y;}}");
"class X {static void test(int x, long y) {long res = x % y;}}");
}
public void testRequireNonNull() {
doTest(null, "Find why 'str' could be null",
"class X {static void test(String str, String msg) {java.util.Objects.<caret>requireNonNull(str, msg);}}");
doTestIntermediate("Find why 'str' could be null",
"class X {static void test(String str, String msg) {java.util.Objects.<caret>requireNonNull(str, msg);}}");
}
public void testAssertNull() {
doTest(null, "Find why 'str' could be not-null",
"class X {static void test(String str) {<caret>assertNull(str);}" +
"static void assertNull(Object obj) {if(obj != null) throw new AssertionError();}}");
doTestIntermediate("Find why 'str' could be not-null",
"class X {static void test(String str) {<caret>assertNull(str);}" +
"static void assertNull(Object obj) {if(obj != null) throw new AssertionError();}}");
}
public void testAssertTrue() {
doTest(null, "Find why 'x' could be <= 0",
"class X {static void test(int x) {<caret>assertTrue(x > 0);}" +
"static void assertTrue(boolean flag) {if(!flag) throw new AssertionError();}}");
doTestIntermediate("Find why 'x' could be <= 0",
"class X {static void test(int x) {<caret>assertTrue(x > 0);}" +
"static void assertTrue(boolean flag) {if(!flag) throw new AssertionError();}}");
}
public void testAssertFalse() {
doTest(null, "Find why 'x' could be >= 1",
"class X {static void test(int x) {<caret>assertFalse(x > 0);}" +
"static void assertFalse(boolean flag) {if(flag) throw new AssertionError();}}");
doTestIntermediate("Find why 'x' could be >= 1",
"class X {static void test(int x) {<caret>assertFalse(x > 0);}" +
"static void assertFalse(boolean flag) {if(flag) throw new AssertionError();}}");
}
public void testOptionalGet() {
// Not supported
doTest(null, null,
"class X {static void test(java.util.Optional x) {x.<caret>get();}}");
doTestIntermediate(null,
"class X {static void test(java.util.Optional x) {x.<caret>get();}}");
}
private void doTest(@Nullable("If intermediate line action should be tested") String exceptionLine,
public void testNpeJetBrains() {
doTest("java.lang.IllegalArgumentException: Argument for @NotNull parameter 'y' of foo/bar/Test.callee must not be null\n" +
"\tat foo.bar.Test.$$$reportNull$$$0(Test.java)\n" +
"\tat foo.bar.Test.callee(Test.java)",
"Find why 'b' could be null",
"package foo.bar;\n" +
"class Test {\n" +
" void caller(String a, String b, String c) {\n" +
" callee(a, b, c);\n" +
" }\n" +
"\n" +
" void callee(String x, String y, String z) {}\n" +
"}");
}
private void doTest(@NotNull String exceptionLine,
@Nullable("If no action is expected") String expectedActionTitle,
@NotNull String source) {
@NotNull @Language("JAVA") String source) {
configureFromFileText("Test.java", source);
ExceptionAnalysisProvider analysisProvider = getProject().getService(ExceptionAnalysisProvider.class);
String[] lines = exceptionLine.split("\n");
ExceptionInfo info = ExceptionInfo.parseMessage(lines[0], 0);
assertNotNull(info);
for (int i = 1; i < lines.length; i++) {
info = info.consumeStackLine(lines[i]);
assertNotNull(lines[i], info);
}
ExceptionLineRefiner refiner = info.getPositionRefiner();
PsiElement leaf = getFile().findElementAt(0);
PsiElement anchor = null;
while (leaf != null) {
PsiElement candidate = refiner.matchElement(leaf);
if (candidate != null) {
if (anchor == null) {
anchor = candidate;
}
else {
fail("Two candidates found: " + anchor.getText());
}
}
leaf = PsiTreeUtil.nextLeaf(leaf);
}
assertNotNull("No anchors found", anchor);
AnAction action = analysisProvider.getAnalysisAction(anchor, info);
checkAction(action, expectedActionTitle);
}
private void doTestIntermediate(@Nullable("If no action is expected") String expectedActionTitle,
@NotNull String source) {
configureFromFileText("Test.java", source);
ExceptionAnalysisProvider analysisProvider = getProject().getService(ExceptionAnalysisProvider.class);
int offset = getEditor().getCaretModel().getOffset();
assertTrue("Offset is not set", offset > 0);
PsiElement leaf = getFile().findElementAt(offset);
AnAction action;
ExceptionAnalysisProvider analysisProvider = getProject().getService(ExceptionAnalysisProvider.class);
if (exceptionLine != null) {
ExceptionInfo info = ExceptionInfo.parseMessage(exceptionLine, 0);
assertNotNull(info);
action = analysisProvider.getAnalysisAction(leaf, info);
} else {
action = analysisProvider.getIntermediateRowAnalysisAction(leaf);
}
AnAction action = analysisProvider.getIntermediateRowAnalysisAction(leaf);
checkAction(action, expectedActionTitle);
}
private static void checkAction(AnAction action, @Nullable("If no action is expected") String expectedActionTitle) {
if (expectedActionTitle == null) {
assertNull(action);
} else {
}
else {
assertNotNull(action);
String text = action.getTemplatePresentation().getDescription();
assertEquals(expectedActionTitle, text);
@@ -5,6 +5,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import org.jetbrains.annotations.NotNull;
public class ArithmeticExceptionInfo extends ExceptionInfo {
ArithmeticExceptionInfo(int offset, String message) {
@@ -12,7 +13,7 @@ public class ArithmeticExceptionInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
if (e instanceof PsiJavaToken && (e.textMatches("%") || e.textMatches("/")) &&
e.getParent() instanceof PsiPolyadicExpression) {
PsiExpression prevOperand = PsiTreeUtil.getPrevSiblingOfType(e, PsiExpression.class);
@@ -25,11 +26,11 @@ public class ArithmeticExceptionInfo extends ExceptionInfo {
}
if (nextOperand instanceof PsiLiteral) {
Object value = ((PsiLiteral)nextOperand).getValue();
if (value instanceof Number && ((Number)value).longValue() != 0) return false;
if (value instanceof Number && ((Number)value).longValue() != 0) return null;
}
return true;
return nextOperand;
}
}
return false;
return null;
}
}
@@ -1,12 +1,9 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.filters;
import com.intellij.psi.PsiArrayAccessExpression;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaToken;
import com.intellij.psi.PsiLiteralExpression;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.psi.*;
import com.intellij.psi.util.ConstantExpressionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Matcher;
@@ -32,14 +29,16 @@ public class ArrayIndexOutOfBoundsExceptionInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
if (!(e instanceof PsiJavaToken && e.textMatches("[") && e.getParent() instanceof PsiArrayAccessExpression)) {
return false;
return null;
}
PsiExpression indexExpression = ((PsiArrayAccessExpression)e.getParent()).getIndexExpression();
if (indexExpression == null) return null;
if (myIndex != null) {
PsiLiteralExpression next = ObjectUtils.tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(e), PsiLiteralExpression.class);
return next == null || myIndex.equals(next.getValue());
Object value = ConstantExpressionUtil.computeCastTo(indexExpression, PsiType.INT);
if (value != null && !value.equals(myIndex)) return null;
}
return true;
return indexExpression;
}
}
@@ -3,6 +3,7 @@ package com.intellij.execution.filters;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
public class ArrayStoreExceptionInfo extends ExceptionInfo {
ArrayStoreExceptionInfo(int offset, String message) {
@@ -10,11 +11,13 @@ public class ArrayStoreExceptionInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
if (e instanceof PsiJavaToken && e.textMatches("=") && e.getParent() instanceof PsiAssignmentExpression) {
PsiExpression lExpression = ((PsiAssignmentExpression)e.getParent()).getLExpression();
return PsiUtil.skipParenthesizedExprDown(lExpression) instanceof PsiArrayAccessExpression;
if (PsiUtil.skipParenthesizedExprDown(lExpression) instanceof PsiArrayAccessExpression) {
return e;
}
}
return false;
return null;
}
}
@@ -1,8 +1,11 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.filters;
import com.intellij.psi.PsiAssertStatement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiKeyword;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
public class AssertionErrorInfo extends ExceptionInfo {
AssertionErrorInfo(int offset, String message) {
@@ -10,7 +13,10 @@ public class AssertionErrorInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
return e instanceof PsiKeyword && e.textMatches(PsiKeyword.ASSERT);
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.ASSERT)) {
return ObjectUtils.tryCast(e.getParent(), PsiAssertStatement.class);
}
return null;
}
}
@@ -34,12 +34,14 @@ public class ClassCastExceptionInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
if (myTargetClass == null) return false;
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
if (myTargetClass == null) return null;
if (e instanceof PsiJavaToken && e.textMatches("(") && e.getParent() instanceof PsiTypeCastExpression) {
PsiTypeElement typeElement = ((PsiTypeCastExpression)e.getParent()).getCastType();
if (typeElement == null) return true;
return castClassMatches(typeElement.getType(), myTargetClass);
if (typeElement == null) return null;
if (castClassMatches(typeElement.getType(), myTargetClass)) {
return typeElement;
}
}
if (e instanceof PsiIdentifier && e.getParent() instanceof PsiReferenceExpression) {
PsiReferenceExpression ref = (PsiReferenceExpression)e.getParent();
@@ -52,14 +54,16 @@ public class ClassCastExceptionInfo extends ExceptionInfo {
type = ((PsiVariable)target).getType();
}
else {
return false;
return null;
}
PsiClass psiClass = PsiUtil.resolveClassInType(type);
if (!(psiClass instanceof PsiTypeParameter)) return false;
if (!(psiClass instanceof PsiTypeParameter)) return null;
// Implicit cast added by compiler
return castClassMatches(ref.getType(), myTargetClass);
if (castClassMatches(ref.getType(), myTargetClass)) {
return e;
}
}
return false;
return null;
}
private static boolean castClassMatches(PsiType type, String className) {
@@ -8,9 +8,11 @@ import com.intellij.psi.PsiKeyword;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiType;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ApiStatus.Experimental
public class ExceptionInfo {
private static final String EXCEPTION_IN_THREAD = "Exception in thread \"";
private static final String CAUSED_BY = "Caused by: ";
@@ -24,33 +26,16 @@ public class ExceptionInfo {
myExceptionMessage = exceptionMessage;
}
boolean isSpecificExceptionElement(PsiElement element) {
return false;
@Nullable PsiElement matchSpecificExceptionElement(@NotNull PsiElement element) {
return null;
}
/**
* @return a predicate that matches an element within the source line that is likely an exception source
*/
@NotNull ExceptionLineRefiner getPositionRefiner() {
return new ExceptionLineRefiner() {
@Override
public boolean test(PsiElement element) {
// We look for new Exception() expression rather than throw statement, because stack-trace is filled in exception constructor
if (element instanceof PsiKeyword && element.textMatches(PsiKeyword.NEW)) {
PsiNewExpression newExpression = ObjectUtils.tryCast(element.getParent(), PsiNewExpression.class);
if (newExpression != null) {
PsiType type = newExpression.getType();
if (type != null && type.equalsToText(getExceptionClassName())) return true;
}
}
return isSpecificExceptionElement(element);
}
@Override
public ExceptionInfo getExceptionInfo() {
return ExceptionInfo.this;
}
};
@NotNull
public ExceptionLineRefiner getPositionRefiner() {
return new AfterExceptionRefiner(this);
}
/**
@@ -127,7 +112,9 @@ public class ExceptionInfo {
case "java.lang.NegativeArraySizeException":
return new NegativeArraySizeExceptionInfo(startOffset, message);
default:
return new ExceptionInfo(startOffset, exceptionName, message);
ExceptionInfo info =
JetBrainsNotNullInstrumentationExceptionInfo.tryCreate(startOffset, exceptionName, message);
return info != null ? info : new ExceptionInfo(startOffset, exceptionName, message);
}
}
@@ -162,4 +149,41 @@ public class ExceptionInfo {
if (!hasDot) return null;
return new TextRange(from, to);
}
private static class AfterExceptionRefiner implements ExceptionLineRefiner {
private final ExceptionInfo myInfo;
public AfterExceptionRefiner(ExceptionInfo info) {this.myInfo = info;}
@Override
public PsiElement matchElement(@NotNull PsiElement element) {
// We look for new Exception() expression rather than throw statement, because stack-trace is filled in exception constructor
if (element instanceof PsiKeyword && element.textMatches(PsiKeyword.NEW)) {
PsiNewExpression newExpression = ObjectUtils.tryCast(element.getParent(), PsiNewExpression.class);
if (newExpression != null) {
PsiType type = newExpression.getType();
if (type != null && type.equalsToText(myInfo.getExceptionClassName())) return element;
}
}
return myInfo.matchSpecificExceptionElement(element);
}
@Override
public ExceptionLineRefiner consumeNextLine(String line) {
ExceptionInfo info = myInfo.consumeStackLine(line);
if (info != null) {
return new AfterExceptionRefiner(info);
}
return null;
}
@Override
public ExceptionInfo getExceptionInfo() {
return myInfo;
}
}
public ExceptionInfo consumeStackLine(String line) {
return null;
}
}
@@ -2,15 +2,35 @@
package com.intellij.execution.filters;
import com.intellij.psi.PsiElement;
import java.util.function.Predicate;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Allows to refine the stacktrace navigation position based on the previous line
*/
@FunctionalInterface
interface ExceptionLineRefiner extends Predicate<PsiElement> {
default ExceptionInfo getExceptionInfo() {
@ApiStatus.Experimental
public interface ExceptionLineRefiner {
/**
* @param element element to check
* @return element to position caret to if element matches; null if it doesn't match
*/
@Nullable PsiElement matchElement(@NotNull PsiElement element);
/**
* @return ExceptionInfo object associated with this refiner if it matches some exception
*/
default @Nullable ExceptionInfo getExceptionInfo() {
return null;
}
/**
* Provides a way to merge several lines into single refiner.
*
* @param line next line
* @return ExceptionLineRefiner if next line is successfully consumed; null otherwise
*/
default @Nullable ExceptionLineRefiner consumeNextLine(String line) {
return null;
}
}
@@ -343,8 +343,8 @@ public class ExceptionWorker {
}
@Override
public boolean test(@NotNull PsiElement element) {
if (!(element instanceof PsiIdentifier)) return false;
public PsiElement matchElement(@NotNull PsiElement element) {
if (!(element instanceof PsiIdentifier)) return null;
if (myMethodName.equals("<init>")) {
if (myHasDollarInName || element.textMatches(StringUtil.getShortName(myClassName))) {
PsiElement parent = element.getParent();
@@ -352,11 +352,11 @@ public class ExceptionWorker {
parent = parent.getParent();
}
if (parent instanceof PsiAnonymousClass) {
return isTargetClass(parent) || isTargetClass(((PsiAnonymousClass)parent).getSuperClass());
return isTargetClass(parent) || isTargetClass(((PsiAnonymousClass)parent).getSuperClass()) ? element : null;
}
if (parent instanceof PsiNewExpression) {
PsiJavaCodeReferenceElement ref = ((PsiNewExpression)parent).getClassOrAnonymousClassReference();
return ref != null && isTargetClass(ref.resolve());
return ref != null && isTargetClass(ref.resolve()) ? element : null;
}
}
}
@@ -364,10 +364,10 @@ public class ExceptionWorker {
PsiElement parent = element.getParent();
if (parent instanceof PsiReferenceExpression) {
PsiElement target = ((PsiReferenceExpression)parent).resolve();
return target instanceof PsiMethod && isTargetClass(((PsiMethod)target).getContainingClass());
return target instanceof PsiMethod && isTargetClass(((PsiMethod)target).getContainingClass()) ? element : null;
}
}
return false;
return null;
}
private boolean isTargetClass(PsiElement maybeClass) {
@@ -403,8 +403,9 @@ public class ExceptionWorker {
PsiElement element = file.findElementAt(startOffset);
List<PsiElement> candidates = new ArrayList<>();
while (element != null && element.getTextRange().getStartOffset() < endOffset) {
if (myElementMatcher.test(element)) {
candidates.add(element);
PsiElement matched = myElementMatcher.matchElement(element);
if (matched != null) {
candidates.add(matched);
if (candidates.size() > 1) return;
}
element = PsiTreeUtil.nextLeaf(element);
@@ -453,15 +454,16 @@ public class ExceptionWorker {
private static class FunctionCallMatcher implements ExceptionLineRefiner {
@Override
public boolean test(@NotNull PsiElement element) {
if (!(element instanceof PsiIdentifier)) return false;
public PsiElement matchElement(@NotNull PsiElement element) {
if (!(element instanceof PsiIdentifier)) return null;
PsiElement parent = element.getParent();
if (!(parent instanceof PsiReferenceExpression)) return false;
if (!(parent instanceof PsiReferenceExpression)) return null;
PsiMethodCallExpression call = ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class);
if (call == null) return false;
if (call == null) return null;
PsiMethod target = call.resolveMethod();
if (target == null) return false;
return LambdaUtil.getFunctionalInterfaceMethod(target.getContainingClass()) == target;
if (target == null) return null;
if (LambdaUtil.getFunctionalInterfaceMethod(target.getContainingClass()) != target) return null;
return element;
}
}
}
@@ -0,0 +1,101 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.execution.filters;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class JetBrainsNotNullInstrumentationExceptionInfo extends ExceptionInfo {
/**
* @see com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter.NotNullState#getNullParamMessage(String)
*/
private static final Pattern INSTRUMENTATION_MESSAGE_PATTERN = Pattern.compile(
"Argument for @(?:\\w+) parameter '(\\w+)' of (\\S+)\\.(\\w+) must not be null");
private final String myParameterName;
private final String myClassName;
private final String myMethodName;
private final int myWantLines;
private JetBrainsNotNullInstrumentationExceptionInfo(int offset,
@NotNull String exceptionClassName,
@NotNull String exceptionMessage,
@NotNull String parameterName,
@NotNull String className,
@NotNull String methodName,
int wantLines) {
super(offset, exceptionClassName, exceptionMessage);
myParameterName = parameterName;
myClassName = StringUtil.getShortName(className, '/');
myMethodName = methodName;
myWantLines = wantLines;
}
@Override
PsiElement matchSpecificExceptionElement(@NotNull PsiElement element) {
if (myWantLines != 0) return null;
return getArgument(element);
}
private PsiExpression getArgument(PsiElement element) {
if (!(element instanceof PsiIdentifier)) return null;
if (!element.getText().equals(myMethodName)) return null;
PsiReferenceExpression ref = ObjectUtils.tryCast(element.getParent(), PsiReferenceExpression.class);
if (ref == null) return null;
PsiCallExpression call = ObjectUtils.tryCast(ref.getParent(), PsiMethodCallExpression.class);
if (call == null) return null;
PsiExpressionList argumentList = call.getArgumentList();
if (argumentList == null) return null;
PsiMethod method = call.resolveMethod();
if (method == null) return null;
PsiClass psiClass = method.getContainingClass();
if (psiClass == null) return null;
if (!myClassName.equals(psiClass.getName())) return null;
PsiParameter[] parameters = method.getParameterList().getParameters();
for (int i = 0; i < parameters.length; i++) {
if (parameters[i].getName().equals(myParameterName)) {
PsiExpression[] expressions = argumentList.getExpressions();
if (expressions.length > i) {
return expressions[i];
}
return null;
}
}
return null;
}
@Override
public ExceptionInfo consumeStackLine(String line) {
switch (myWantLines) {
case 2:
if (line.contains(myClassName+".$$$reportNull$$$0")) {
return new JetBrainsNotNullInstrumentationExceptionInfo(getClassNameOffset(), getExceptionClassName(), getExceptionMessage(),
myParameterName, myClassName, myMethodName, 1);
}
break;
case 1:
if (line.contains(myClassName+"."+myMethodName)) {
return new JetBrainsNotNullInstrumentationExceptionInfo(getClassNameOffset(), getExceptionClassName(), getExceptionMessage(),
myParameterName, myClassName, myMethodName, 0);
}
}
return null;
}
static JetBrainsNotNullInstrumentationExceptionInfo tryCreate(int offset,
@NotNull String exceptionClassName,
@NotNull String exceptionMessage) {
if (!exceptionClassName.equals("java.lang.IllegalArgumentException")) return null;
if (!exceptionMessage.startsWith("Argument ")) return null;
Matcher matcher = INSTRUMENTATION_MESSAGE_PATTERN.matcher(exceptionMessage);
if (!matcher.matches()) return null;
String parameterName = matcher.group(1);
String className = matcher.group(2);
String methodName = matcher.group(3);
return new JetBrainsNotNullInstrumentationExceptionInfo(offset, exceptionClassName, exceptionMessage, parameterName, className,
methodName, 2);
}
}
@@ -4,6 +4,7 @@ package com.intellij.execution.filters;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class NegativeArraySizeExceptionInfo extends ExceptionInfo {
@@ -21,7 +22,8 @@ public class NegativeArraySizeExceptionInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
PsiExpression candidate = null;
if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.NEW) && e.getParent() instanceof PsiNewExpression) {
PsiExpression[] dimensions = ((PsiNewExpression)e.getParent()).getArrayDimensions();
for (PsiExpression dimension : dimensions) {
@@ -30,9 +32,13 @@ public class NegativeArraySizeExceptionInfo extends ExceptionInfo {
// Explicit negative number like -1 cannot be literal, it's unary expression
if (literal != null && literal.getValue() instanceof Integer) continue;
}
return true;
if (candidate == null) {
candidate = dimension;
} else {
return null;
}
}
}
return false;
return candidate;
}
}
@@ -4,6 +4,7 @@ package com.intellij.execution.filters;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
@@ -118,7 +119,7 @@ public class NullPointerExceptionInfo extends ExceptionInfo {
}
@Override
boolean isSpecificExceptionElement(PsiElement e) {
return myPredicate != null && myPredicate.test(e);
PsiElement matchSpecificExceptionElement(@NotNull PsiElement e) {
return (myPredicate != null && myPredicate.test(e)) ? e : null;
}
}