From 37de5ef5977fff5f2a41fe37b612f65ad25033da Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 8 May 2020 15:16:11 +0700 Subject: [PATCH] IDEA-204608 Integrate production stacktraces into static nullability analysis improvement - Support JetBrains NotNull instrumented parameters - Better caret positioning GitOrigin-RevId: f867eb5599faa2bc496fa30eae9402597043c268 --- .../execution/filters/ExceptionFilter.java | 4 + .../DataflowExceptionAnalysisProvider.java | 72 +++---- .../filters/ExceptionWorkerTest.java | 35 +++- ...DataflowExceptionAnalysisProviderTest.java | 179 +++++++++++------- .../filters/ArithmeticExceptionInfo.java | 9 +- .../ArrayIndexOutOfBoundsExceptionInfo.java | 21 +- .../filters/ArrayStoreExceptionInfo.java | 9 +- .../execution/filters/AssertionErrorInfo.java | 10 +- .../filters/ClassCastExceptionInfo.java | 20 +- .../execution/filters/ExceptionInfo.java | 70 ++++--- .../filters/ExceptionLineRefiner.java | 30 ++- .../execution/filters/ExceptionWorker.java | 30 +-- ...nsNotNullInstrumentationExceptionInfo.java | 101 ++++++++++ .../NegativeArraySizeExceptionInfo.java | 12 +- .../filters/NullPointerExceptionInfo.java | 5 +- 15 files changed, 410 insertions(+), 197 deletions(-) create mode 100644 java/openapi/src/com/intellij/execution/filters/JetBrainsNotNullInstrumentationExceptionInfo.java diff --git a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java index e628ed0e4f31..3f4f59f16547 100644 --- a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java +++ b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java @@ -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; diff --git a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java index 925484fee916..6238cd3493f2 100644 --- a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java +++ b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java @@ -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; } diff --git a/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java b/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java index 5b76d1320527..c766db1cb82e 100644 --- a/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java @@ -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.(SomeClass.java:4)\n", 4, 36), + Trinity.create("\tat SomeClass.(SomeClass.java:4)\n", 4, 37), Trinity.create("\tat SomeClass$1.(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> 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> 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> 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> 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> 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> 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> 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> 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()); diff --git a/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java b/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java index e464b46ff7da..d60de9418a20 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java @@ -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[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(((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(((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.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[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) {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) {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) {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) {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) {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) {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) {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) {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) {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) {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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 = 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/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 % 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.requireNonNull(str, msg);}}"); + doTestIntermediate("Find why 'str' could be null", + "class X {static void test(String str, String msg) {java.util.Objects.requireNonNull(str, msg);}}"); } public void testAssertNull() { - doTest(null, "Find why 'str' could be not-null", - "class X {static void test(String str) {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) {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) {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) {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) {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) {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.get();}}"); + doTestIntermediate(null, + "class X {static void test(java.util.Optional x) {x.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); diff --git a/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java index 726c013aa2be..087b084b3aeb 100644 --- a/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java @@ -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; } } diff --git a/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java index 604925f9a054..12cc24be9246 100644 --- a/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java @@ -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; } } diff --git a/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java index 7cacd2aefe1b..891b34290145 100644 --- a/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java @@ -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; } } diff --git a/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java b/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java index cb5c3f72de3a..9aca51ec0d9c 100644 --- a/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java @@ -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; } } diff --git a/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java index d10139637403..56c8b818d2b9 100644 --- a/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java @@ -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) { diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java index 472ad106e12b..88bfa2357369 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java @@ -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; + } } diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java b/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java index a99cda7c1a31..67fa2e679131 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java @@ -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 { - 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; } } diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java index 169418332db5..30733003ad39 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java @@ -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("")) { 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 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; } } } diff --git a/java/openapi/src/com/intellij/execution/filters/JetBrainsNotNullInstrumentationExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/JetBrainsNotNullInstrumentationExceptionInfo.java new file mode 100644 index 000000000000..92ed4b0c0f76 --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/JetBrainsNotNullInstrumentationExceptionInfo.java @@ -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); + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java index b5732202b693..78f2863ecbcf 100644 --- a/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java @@ -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; } } diff --git a/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java index aa7f95081244..4a7dcc57a4a3 100644 --- a/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java @@ -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; } }