mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
intellij-monorepo-bot
parent
de4db0fbc2
commit
37de5ef597
@@ -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;
|
||||
}
|
||||
|
||||
+27
-8
@@ -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());
|
||||
|
||||
+113
-66
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-11
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+101
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user