From de4db0fbc29286caf2af75db16135e08042ff3b2 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 8 May 2020 11:01:04 +0700 Subject: [PATCH] Refactor ExceptionInfo: separate subclasses for exceptions GitOrigin-RevId: dfd9013385125b55b68ddaa5e8bc11dc2a109241 --- .../DataflowExceptionAnalysisProvider.java | 128 +++--- ...DataflowExceptionAnalysisProviderTest.java | 99 +++-- .../filters/ArithmeticExceptionInfo.java | 35 ++ .../ArrayIndexOutOfBoundsExceptionInfo.java | 45 +++ .../filters/ArrayStoreExceptionInfo.java | 20 + .../execution/filters/AssertionErrorInfo.java | 16 + .../filters/ClassCastExceptionInfo.java | 138 +++++++ .../filters/ExceptionAnalysisProvider.java | 7 +- .../execution/filters/ExceptionInfo.java | 363 +++--------------- .../execution/filters/ExceptionWorker.java | 2 +- .../NegativeArraySizeExceptionInfo.java | 38 ++ .../filters/NullPointerExceptionInfo.java | 124 ++++++ 12 files changed, 568 insertions(+), 447 deletions(-) create mode 100644 java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java create mode 100644 java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java create mode 100644 java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java create mode 100644 java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java create mode 100644 java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java create mode 100644 java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java create mode 100644 java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java diff --git a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java index aff8b7eacc46..925484fee916 100644 --- a/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java +++ b/java/java-impl/src/com/intellij/slicer/DataflowExceptionAnalysisProvider.java @@ -7,8 +7,7 @@ import com.intellij.codeInspection.dataFlow.*; import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet; import com.intellij.codeInspection.dataFlow.types.*; import com.intellij.codeInspection.dataFlow.value.RelationType; -import com.intellij.execution.filters.ExceptionAnalysisProvider; -import com.intellij.execution.filters.ExceptionInfo; +import com.intellij.execution.filters.*; import com.intellij.java.JavaBundle; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -37,8 +36,9 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi } @Override - public @Nullable AnAction getAnalysisAction(@NotNull PsiElement anchor, @NotNull String exceptionName, @NotNull String exceptionMessage) { - Analysis analysis = getAnalysis(anchor, exceptionName, exceptionMessage); + public @Nullable AnAction getAnalysisAction(@NotNull PsiElement anchor, + @NotNull ExceptionInfo info) { + Analysis analysis = getAnalysis(anchor, info); return createAction(analysis); } @@ -92,8 +92,7 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi } private @Nullable Analysis getAnalysis(@NotNull PsiElement anchor, - @NotNull String exceptionName, - @NotNull String exceptionMessage) { + @NotNull ExceptionInfo info) { if (anchor instanceof PsiKeyword && anchor.textMatches(PsiKeyword.NEW)) { PsiNewExpression exceptionConstructor = tryCast(anchor.getParent(), PsiNewExpression.class); if (exceptionConstructor != null && !exceptionConstructor.isArrayCreation()) { @@ -103,22 +102,25 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi return fromThrowStatement(throwStatement); } } - switch (exceptionName) { - case CommonClassNames.JAVA_LANG_ASSERTION_ERROR: - return fromAssertionError(anchor); - case "java.lang.ArrayIndexOutOfBoundsException": - return fromArrayIndexOutOfBoundsException(anchor, exceptionMessage); - case "java.lang.ClassCastException": - return fromClassCastException(anchor, exceptionMessage); - case CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION: - return Analysis.create(DfTypes.NULL, findDereferencedExpression(anchor)); - case "java.lang.NegativeArraySizeException": - return fromNegativeArraySizeException(anchor, exceptionMessage); - case "java.lang.ArithmeticException": - return fromArithmeticException(anchor); - default: - return null; + if (info instanceof AssertionErrorInfo) { + return fromAssertionError(anchor); } + else if (info instanceof ArrayIndexOutOfBoundsExceptionInfo) { + return fromArrayIndexOutOfBoundsException(anchor, ((ArrayIndexOutOfBoundsExceptionInfo)info).getIndex()); + } + else if (info instanceof ClassCastExceptionInfo) { + return fromClassCastException(anchor, ((ClassCastExceptionInfo)info).getActualClass()); + } + else if (info instanceof NullPointerExceptionInfo) { + return Analysis.create(DfTypes.NULL, findDereferencedExpression(anchor)); + } + else if (info instanceof NegativeArraySizeExceptionInfo) { + return fromNegativeArraySizeException(anchor, ((NegativeArraySizeExceptionInfo)info).getSuppliedSize()); + } + else if (info instanceof ArithmeticExceptionInfo) { + return fromArithmeticException(anchor); + } + return null; } private static Analysis fromArithmeticException(PsiElement anchor) { @@ -261,63 +263,47 @@ public class DataflowExceptionAnalysisProvider implements ExceptionAnalysisProvi return null; } - private @Nullable Analysis fromClassCastException(@NotNull PsiElement anchor, @NotNull String exceptionMessage) { - if (anchor instanceof PsiJavaToken && ((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.LPARENTH)) { - String actualClass = ExceptionInfo.getCastActualClassFromMessage(exceptionMessage); - if (actualClass != null) { - PsiTypeCastExpression castExpression = tryCast(anchor.getParent(), PsiTypeCastExpression.class); - if (castExpression != null) { - PsiExpression ref = extractAnchor(castExpression.getOperand()); - if (ref != null) { - // TODO: support arrays, primitive arrays, inner classes - PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(actualClass, GlobalSearchScope.allScope(myProject)); - if (classes.length == 1) { - return new Analysis( - DfTypes.typedObject(JavaPsiFacade.getElementFactory(myProject).createType(classes[0]), Nullability.NOT_NULL), ref); - } - else { - PsiType castType = castExpression.getType(); - if (castType != null) { - return tryNegate(new Analysis(DfTypes.typedObject(castType, Nullability.NULLABLE), ref)); - } - } - } - } + private @Nullable Analysis fromClassCastException(@NotNull PsiElement anchor, @Nullable String actualClass) { + if (!(anchor instanceof PsiJavaToken) || !((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.LPARENTH)) { + return null; + } + PsiTypeCastExpression castExpression = tryCast(anchor.getParent(), PsiTypeCastExpression.class); + if (castExpression == null) return null; + PsiExpression ref = extractAnchor(castExpression.getOperand()); + if (ref == null) return null; + if (actualClass != null) { + // TODO: support arrays, primitive arrays, inner classes + PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(actualClass, GlobalSearchScope.allScope(myProject)); + if (classes.length == 1) { + return new Analysis( + DfTypes.typedObject(JavaPsiFacade.getElementFactory(myProject).createType(classes[0]), Nullability.NOT_NULL), ref); + } + } + PsiType castType = castExpression.getType(); + if (castType != null) { + return tryNegate(new Analysis(DfTypes.typedObject(castType, Nullability.NULLABLE), ref)); + } + 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 fromArrayIndexOutOfBoundsException(@NotNull PsiElement anchor, @NotNull String exceptionMessage) { - if (anchor instanceof PsiJavaToken && - ((PsiJavaToken)anchor).getTokenType().equals(JavaTokenType.LBRACKET)) { - Integer index = ExceptionInfo.getArrayIndexFromMessage(exceptionMessage); - if (index != null) { - 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, @NotNull String exceptionMessage) { + 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) { - int size; - try { - size = Integer.parseInt(exceptionMessage); - } - catch (NumberFormatException e) { - return null; - } - if (size < 0) { - PsiExpression[] dimensions = ((PsiNewExpression)anchor.getParent()).getArrayDimensions(); - if (dimensions.length == 1) { - return Analysis.create(DfTypes.intValue(size), dimensions[0]); - } + PsiExpression[] dimensions = ((PsiNewExpression)anchor.getParent()).getArrayDimensions(); + if (dimensions.length == 1) { + return Analysis.create(DfTypes.intValue(size), dimensions[0]); } } return null; 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 5652af091db6..e464b46ff7da 100644 --- a/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java +++ b/java/java-tests/testSrc/com/intellij/java/slicer/DataflowExceptionAnalysisProviderTest.java @@ -2,8 +2,8 @@ package com.intellij.java.slicer; import com.intellij.execution.filters.ExceptionAnalysisProvider; +import com.intellij.execution.filters.ExceptionInfo; import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiElement; import com.intellij.testFramework.LightJavaCodeInsightTestCase; import org.jetbrains.annotations.NotNull; @@ -11,163 +11,163 @@ import org.jetbrains.annotations.Nullable; public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightTestCase { public void testArrayIndex() { - doTest("java.lang.ArrayIndexOutOfBoundsException", "10", + doTest("java.lang.ArrayIndexOutOfBoundsException: 10", "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", + 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());}}"); } public void testClassCastUnknownClass() { - doTest("java.lang.ClassCastException", "class XYZ cannot be cast to class java.lang.Number", + 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());}}"); } public void testNpe() { - doTest(CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION, "Cannot invoke \"Object.hashCode()\" because \"obj\" is null", - "Find why 'obj' could be null", + 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());}}"); } public void testNpeArray() { - doTest(CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION, "Cannot load from object array because \"obj\" is null", - "Find why 'obj' could be null", + 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]);}}"); } public void testNpeSynchronized() { - doTest(CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION, "Cannot enter synchronized block because \"obj\" is null", - "Find why 'obj' could be null", + 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){}}}"); } public void testNpeThrow() { - doTest(CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION, "Cannot throw exception because \"obj\" is null", + 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;}}"); } public void testAssertChar() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'c' could be 97", "class X {static void test(char c) {assert c != 'a';}}"); } public void testAssertDivisibility() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'i' could be odd", "class X {static void test(int i) {assert i % 2 == 0;}}"); } public void testAssertAnd() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'idx' could be <= 0 or >= 4", "class X {static void test(int idx) {assert idx > 0 && idx < 4;}}"); } public void testAssertOr() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'idx' could be in {0..4}", "class X {static void test(int idx) {assert idx < 0 || idx > 4;}}"); } public void testAssertByte() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'idx' could be <= 0", "class X {static void test(byte idx) {assert idx > 0;}}"); } public void testAssertShort() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'idx' could be <= 0", "class X {static void test(short idx) {assert idx > 0;}}"); } public void testAssertLong() { - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", "Find why 'idx' could be <= 0", "class X {static void test(long idx) {assert idx > 0;}}"); } public void testAssertBoxed() { // boxed types are unsupported - doTest("java.lang.AssertionError", "", + doTest("java.lang.AssertionError", null, "class X {static void test(Long idx) {assert idx > 0;}}"); } public void testInstanceOf() { - doTest("java.lang.IllegalArgumentException", "", + 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();}}"); } public void testStringInEquality() { - doTest("java.lang.IllegalArgumentException", "", + 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();}}"); } public void testStringEqEq() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", null, "class X {static void test(String s) {if (s == \"hello\")) throw new IllegalArgumentException();}}"); } public void testClassEquality() { - doTest("java.lang.IllegalArgumentException", "", + 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();}}"); } public void testClassInEquality() { - doTest("java.lang.IllegalArgumentException", "", + 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();}}"); } public void testClassInEqualityInverted() { - doTest("java.lang.IllegalArgumentException", "", + 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();}}}"); } public void testBooleanTrue() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "Find why 'b' could be true", "class X {static void test(boolean b) {if (b) {throw new IllegalArgumentException();}}}"); } public void testBooleanFalse() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "Find why 'b' could be false", "class X {static void test(boolean b) {if (!b) {throw new IllegalArgumentException();}}}"); } public void testIsNull() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "Find why 'obj' could be null", "class X {static void test(Object obj) {if (obj == null) {throw new IllegalArgumentException();}}}"); } public void testIsNotNull() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "Find why 'obj' could be not-null", "class X {static void test(Object obj) {if (null != obj) {throw new IllegalArgumentException();}}}"); } public void testEnumEquality() { - doTest("java.lang.IllegalArgumentException", "", + 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();}}"); } public void testInSwitch() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "Find why 'x' could be 5", "class X {" + " static void test(int x) {\n" + @@ -183,13 +183,13 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT } public void testInSwitchRule() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "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 -> {} } }}"); } public void testInSwitchDefault() { - doTest("java.lang.IllegalArgumentException", "", + 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; " + @@ -197,7 +197,7 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT } public void testInSwitchDefaultString() { - doTest("java.lang.IllegalArgumentException", "", + 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;" + @@ -205,7 +205,7 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT } public void testIfExits() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", "Find why 'x' could be >= 0", "class X {" + " static void test(int x) {\n" + @@ -219,60 +219,59 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT } public void testNoInfo() { - doTest("java.lang.IllegalArgumentException", "", + doTest("java.lang.IllegalArgumentException", null, "class X {static void test(X x) {throw new IllegalArgumentException();}}"); } public void testNegativeArraySizeException() { - doTest("java.lang.NegativeArraySizeException", "-2", + doTest("java.lang.NegativeArraySizeException: -2", "Find why 'x' could be -2", "class X {static void test(int x) {int[] data = new int[x];}}"); } public void testDivisionByZero() { - doTest("java.lang.ArithmeticException", "/ by zero", + 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];}}"); } public void testModByZero() { - doTest("java.lang.ArithmeticException", "/ by zero", + 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;}}"); } public void testRequireNonNull() { - doTest(null, null, "Find why 'str' could be null", + doTest(null, "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, null, "Find why 'str' could be not-null", + 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();}}"); } public void testAssertTrue() { - doTest(null, null, "Find why 'x' could be <= 0", + 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();}}"); } public void testAssertFalse() { - doTest(null, null, "Find why 'x' could be >= 1", + 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();}}"); } public void testOptionalGet() { // Not supported - doTest(null, null, null, + doTest(null, null, "class X {static void test(java.util.Optional x) {x.get();}}"); } - private void doTest(@Nullable("If intermediate line action should be tested") String exceptionName, - @Nullable("If exceptionName is null") String exceptionMessage, + private void doTest(@Nullable("If intermediate line action should be tested") String exceptionLine, @Nullable("If no action is expected") String expectedActionTitle, @NotNull String source) { configureFromFileText("Test.java", source); @@ -281,11 +280,11 @@ public class DataflowExceptionAnalysisProviderTest extends LightJavaCodeInsightT PsiElement leaf = getFile().findElementAt(offset); AnAction action; ExceptionAnalysisProvider analysisProvider = getProject().getService(ExceptionAnalysisProvider.class); - if (exceptionName != null) { - assertNotNull(exceptionMessage); - action = analysisProvider.getAnalysisAction(leaf, exceptionName, exceptionMessage); + if (exceptionLine != null) { + ExceptionInfo info = ExceptionInfo.parseMessage(exceptionLine, 0); + assertNotNull(info); + action = analysisProvider.getAnalysisAction(leaf, info); } else { - assertNull(exceptionMessage); action = analysisProvider.getIntermediateRowAnalysisAction(leaf); } if (expectedActionTitle == null) { diff --git a/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java new file mode 100644 index 000000000000..726c013aa2be --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/ArithmeticExceptionInfo.java @@ -0,0 +1,35 @@ +// 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.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; + +public class ArithmeticExceptionInfo extends ExceptionInfo { + ArithmeticExceptionInfo(int offset, String message) { + super(offset, "java.lang.ArithmeticException", message); + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + if (e instanceof PsiJavaToken && (e.textMatches("%") || e.textMatches("/")) && + e.getParent() instanceof PsiPolyadicExpression) { + PsiExpression prevOperand = PsiTreeUtil.getPrevSiblingOfType(e, PsiExpression.class); + PsiExpression nextOperand = PsiUtil.skipParenthesizedExprDown(PsiTreeUtil.getNextSiblingOfType(e, PsiExpression.class)); + if (prevOperand != null && TypeConversionUtil.isIntegralNumberType(prevOperand.getType()) && + nextOperand != null && TypeConversionUtil.isIntegralNumberType(nextOperand.getType())) { + while (nextOperand instanceof PsiUnaryExpression && ((PsiUnaryExpression)nextOperand).getOperationTokenType().equals( + JavaTokenType.MINUS)) { + nextOperand = PsiUtil.skipParenthesizedExprDown(((PsiUnaryExpression)nextOperand).getOperand()); + } + if (nextOperand instanceof PsiLiteral) { + Object value = ((PsiLiteral)nextOperand).getValue(); + if (value instanceof Number && ((Number)value).longValue() != 0) return false; + } + return true; + } + } + return false; + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java new file mode 100644 index 000000000000..604925f9a054 --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/ArrayIndexOutOfBoundsExceptionInfo.java @@ -0,0 +1,45 @@ +// 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 org.jetbrains.annotations.Nullable; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ArrayIndexOutOfBoundsExceptionInfo extends ExceptionInfo { + private static final Pattern AIOOBE_MESSAGE = Pattern.compile("(?:Index )?(-?\\d{1,9})(?: out of bounds for length \\d+)?"); + + private final @Nullable Integer myIndex; + + ArrayIndexOutOfBoundsExceptionInfo(int offset, String message) { + super(offset, "java.lang.ArrayIndexOutOfBoundsException", message); + Matcher matcher = AIOOBE_MESSAGE.matcher(message); + if (matcher.matches()) { + myIndex = Integer.valueOf(matcher.group(1) == null ? matcher.group(2) : matcher.group(1)); + } else { + myIndex = null; + } + } + + public @Nullable Integer getIndex() { + return myIndex; + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + if (!(e instanceof PsiJavaToken && e.textMatches("[") && e.getParent() instanceof PsiArrayAccessExpression)) { + return false; + } + if (myIndex != null) { + PsiLiteralExpression next = ObjectUtils.tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(e), PsiLiteralExpression.class); + return next == null || myIndex.equals(next.getValue()); + } + return true; + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java new file mode 100644 index 000000000000..7cacd2aefe1b --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/ArrayStoreExceptionInfo.java @@ -0,0 +1,20 @@ +// 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.*; +import com.intellij.psi.util.PsiUtil; + +public class ArrayStoreExceptionInfo extends ExceptionInfo { + ArrayStoreExceptionInfo(int offset, String message) { + super(offset, "java.lang.ArrayStoreException", message); + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + if (e instanceof PsiJavaToken && e.textMatches("=") && e.getParent() instanceof PsiAssignmentExpression) { + PsiExpression lExpression = ((PsiAssignmentExpression)e.getParent()).getLExpression(); + return PsiUtil.skipParenthesizedExprDown(lExpression) instanceof PsiArrayAccessExpression; + } + return false; + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java b/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java new file mode 100644 index 000000000000..cb5c3f72de3a --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/AssertionErrorInfo.java @@ -0,0 +1,16 @@ +// 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.PsiElement; +import com.intellij.psi.PsiKeyword; + +public class AssertionErrorInfo extends ExceptionInfo { + AssertionErrorInfo(int offset, String message) { + super(offset, "java.lang.ArrayStoreException", message); + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + return e instanceof PsiKeyword && e.textMatches(PsiKeyword.ASSERT); + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java new file mode 100644 index 000000000000..d10139637403 --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/ClassCastExceptionInfo.java @@ -0,0 +1,138 @@ +// 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.psi.util.PsiUtil; +import gnu.trove.THashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ClassCastExceptionInfo extends ExceptionInfo { + private static final Pattern CCE_MESSAGE = Pattern.compile("(?:class )?(\\S+) cannot be cast to (?:class )?(\\S+)(?: \\(.+\\))?"); + private final @Nullable String myTargetClass; + private final @Nullable String myActualClass; + + ClassCastExceptionInfo(int offset, @NotNull String exceptionMessage) { + super(offset, "java.lang.ClassCastException", exceptionMessage); + Matcher matcher = CCE_MESSAGE.matcher(exceptionMessage); + if (matcher.matches()) { + myTargetClass = matcher.group(2); + myActualClass = matcher.group(1); + } else { + myTargetClass = null; + myActualClass = null; + } + } + + public @Nullable String getActualClass() { + return myActualClass; + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + if (myTargetClass == null) return false; + 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 (e instanceof PsiIdentifier && e.getParent() instanceof PsiReferenceExpression) { + PsiReferenceExpression ref = (PsiReferenceExpression)e.getParent(); + PsiElement target = ref.resolve(); + PsiType type; + if (target instanceof PsiMethod) { + type = ((PsiMethod)target).getReturnType(); + } + else if (target instanceof PsiVariable) { + type = ((PsiVariable)target).getType(); + } + else { + return false; + } + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (!(psiClass instanceof PsiTypeParameter)) return false; + // Implicit cast added by compiler + return castClassMatches(ref.getType(), myTargetClass); + } + return false; + } + + private static boolean castClassMatches(PsiType type, String className) { + if (type instanceof PsiPrimitiveType) { + return className.equals(((PsiPrimitiveType)type).getBoxedTypeName()); + } + if (type instanceof PsiIntersectionType) { + for (PsiType conjunct : ((PsiIntersectionType)type).getConjuncts()) { + if (castClassMatches(conjunct, className)) return true; + } + return false; + } + if (type instanceof PsiArrayType) { + if (className.startsWith("[") && className.length() > 1) { + PsiType componentType = ((PsiArrayType)type).getComponentType(); + switch (className.charAt(1)) { + case '[': + return castClassMatches(componentType, className.substring(1)); + case 'B': + return componentType.equals(PsiType.BYTE); + case 'C': + return componentType.equals(PsiType.CHAR); + case 'D': + return componentType.equals(PsiType.DOUBLE); + case 'F': + return componentType.equals(PsiType.FLOAT); + case 'Z': + return componentType.equals(PsiType.BOOLEAN); + case 'I': + return componentType.equals(PsiType.INT); + case 'J': + return componentType.equals(PsiType.LONG); + case 'S': + return componentType.equals(PsiType.SHORT); + case 'L': + if (className.charAt(className.length() - 1) == ';') { + return castClassMatches(componentType, className.substring(2, className.length() - 1)); + } + return false; + default: + return false; + } + } + } + if (type instanceof PsiClassType) { + return classTypeMatches(className, (PsiClassType)type, new THashSet<>()); + } + return true; + } + + private static boolean classTypeMatches(String className, PsiClassType classType, Set visited) { + PsiClass psiClass = PsiUtil.resolveClassInType(classType); + if (!visited.add(psiClass)) { + return true; + } + if (psiClass instanceof PsiTypeParameter) { + for (PsiClassType bound : ((PsiTypeParameter)psiClass).getExtendsList().getReferencedTypes()) { + if (classTypeMatches(className, bound, visited)) return true; + } + return false; + } + String name = classType.getClassName(); + if (name == null) return true; + if (!name.equals(StringUtil.substringAfterLast(className, ".")) && + !name.equals(StringUtil.substringAfterLast(className, "$"))) { + return false; + } + if (psiClass != null) { + if (className.equals(psiClass.getQualifiedName())) return true; + String packageName = StringUtil.getPackageName(className); + PsiFile psiFile = psiClass.getContainingFile(); + return psiFile instanceof PsiClassOwner && packageName.equals(((PsiClassOwner)psiFile).getPackageName()); + } + return true; + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionAnalysisProvider.java b/java/openapi/src/com/intellij/execution/filters/ExceptionAnalysisProvider.java index 5c10e63588b5..eea3bfe7dbfb 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionAnalysisProvider.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionAnalysisProvider.java @@ -11,13 +11,10 @@ import org.jetbrains.annotations.Nullable; public interface ExceptionAnalysisProvider { /** * @param anchor a place (leaf element) in the source code where exception occurred - * @param exceptionClassName exception class name (fully-qualified) - * @param exceptionMessage exception message (fully-qualified) + * @param info exception info * @return an action to provide additional analysis for given exception; null if not available. */ - @Nullable AnAction getAnalysisAction(@NotNull PsiElement anchor, - @NotNull String exceptionClassName, - @NotNull String exceptionMessage); + @Nullable AnAction getAnalysisAction(@NotNull PsiElement anchor, @NotNull ExceptionInfo info); /** * @param anchor a place (method name identifier) in the source code where next stack frame row is invoked diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java index a8e9fa14341b..472ad106e12b 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java @@ -1,38 +1,17 @@ // 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.TextRange; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.*; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiKeyword; +import com.intellij.psi.PsiNewExpression; +import com.intellij.psi.PsiType; import com.intellij.util.ObjectUtils; -import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Set; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class ExceptionInfo { - private static final Pattern AIOOBE_MESSAGE = Pattern.compile("(?:Index )?(-?\\d{1,9})(?: out of bounds for length \\d+)?"); - private static final Pattern CCE_MESSAGE = Pattern.compile("(?:class )?(\\S+) cannot be cast to (?:class )?(\\S+)(?: \\(.+\\))?"); - // See JEP 358 Helpful NullPointerExceptions for details - private static final Pattern NPE_MESSAGE = Pattern.compile("Cannot (?:invoke \"(?.+)\\(\\)\"|" + - "assign field \"(?.+)\"|" + - "read field \"(?.+)\"|" + - "store to (?[a-z]+) array|" + - "load from (?[a-z]+) array|" + - "read the array (?length)|" + - "enter (?synchronized) block|" + - "throw (?exception))(?: because .+)?"); - // Methods that could be added by compiler implicitly: either unboxing or getClass (implicit NPE check used for method-refs, etc.) - private static final Set IGNORED_METHODS = ContainerUtil.immutableSet("intValue", "longValue", "doubleValue", "floatValue", - "shortValue", "byteValue", "booleanValue", "charValue", - "getClass"); private static final String EXCEPTION_IN_THREAD = "Exception in thread \""; private static final String CAUSED_BY = "Caused by: "; private final int myClassNameOffset; @@ -45,16 +24,14 @@ public class ExceptionInfo { myExceptionMessage = exceptionMessage; } - @NotNull ExceptionInfo adjust(@Nullable String message, int offset) { - message = StringUtil.notNullize(message); - return new ExceptionInfo(myClassNameOffset + offset, myExceptionClassName, message); + boolean isSpecificExceptionElement(PsiElement element) { + return false; } /** * @return a predicate that matches an element within the source line that is likely an exception source */ @NotNull ExceptionLineRefiner getPositionRefiner() { - Predicate specificFilter = getExceptionSpecificFilter(); return new ExceptionLineRefiner() { @Override public boolean test(PsiElement element) { @@ -66,7 +43,7 @@ public class ExceptionInfo { if (type != null && type.equalsToText(getExceptionClassName())) return true; } } - return specificFilter != null && specificFilter.test(element); + return isSpecificExceptionElement(element); } @Override @@ -76,66 +53,6 @@ public class ExceptionInfo { }; } - @Nullable - private Predicate getExceptionSpecificFilter() { - switch (getExceptionClassName()) { - case "java.lang.ArrayIndexOutOfBoundsException": - return e -> isArrayIndexOutOfBoundsSource(getExceptionMessage(), e); - case "java.lang.ArrayStoreException": - return e -> { - if (e instanceof PsiJavaToken && e.textMatches("=") && e.getParent() instanceof PsiAssignmentExpression) { - PsiExpression lExpression = ((PsiAssignmentExpression)e.getParent()).getLExpression(); - return PsiUtil.skipParenthesizedExprDown(lExpression) instanceof PsiArrayAccessExpression; - } - return false; - }; - case "java.lang.ClassCastException": - return getClassCastPredicate(getExceptionMessage()); - case "java.lang.NullPointerException": - return getNullPointerPredicate(getExceptionMessage()); - case "java.lang.AssertionError": - return e -> e instanceof PsiKeyword && e.textMatches(PsiKeyword.ASSERT); - case "java.lang.ArithmeticException": - return e -> { - if (e instanceof PsiJavaToken && (e.textMatches("%") || e.textMatches("/")) && - e.getParent() instanceof PsiPolyadicExpression) { - PsiExpression prevOperand = PsiTreeUtil.getPrevSiblingOfType(e, PsiExpression.class); - PsiExpression nextOperand = PsiUtil.skipParenthesizedExprDown(PsiTreeUtil.getNextSiblingOfType(e, PsiExpression.class)); - if (prevOperand != null && TypeConversionUtil.isIntegralNumberType(prevOperand.getType()) && - nextOperand != null && TypeConversionUtil.isIntegralNumberType(nextOperand.getType())) { - while (nextOperand instanceof PsiUnaryExpression && ((PsiUnaryExpression)nextOperand).getOperationTokenType().equals( - JavaTokenType.MINUS)) { - nextOperand = PsiUtil.skipParenthesizedExprDown(((PsiUnaryExpression)nextOperand).getOperand()); - } - if (nextOperand instanceof PsiLiteral) { - Object value = ((PsiLiteral)nextOperand).getValue(); - if (value instanceof Number && ((Number)value).longValue() != 0) return false; - } - return true; - } - } - return false; - }; - case "java.lang.NegativeArraySizeException": - return e -> { - if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.NEW) && e.getParent() instanceof PsiNewExpression) { - PsiExpression[] dimensions = ((PsiNewExpression)e.getParent()).getArrayDimensions(); - for (PsiExpression dimension : dimensions) { - if (dimension != null) { - PsiLiteral literal = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(dimension), PsiLiteral.class); - // Explicit negative number like -1 cannot be literal, it's unary expression - if (literal != null && literal.getValue() instanceof Integer) continue; - } - return true; - } - } - return false; - }; - default: - return null; - } - } - /** * @return offset of the class name within the parsed line */ @@ -157,255 +74,61 @@ public class ExceptionInfo { return myExceptionMessage; } - private static Predicate getNullPointerPredicate(String message) { - if (!message.startsWith("Cannot ")) return null; - Matcher matcher = NPE_MESSAGE.matcher(message); - if (!matcher.matches()) return null; - if (matcher.group("athrow") != null) { - return e -> e instanceof PsiKeyword && ((PsiKeyword)e).getTokenType().equals(JavaTokenType.THROW_KEYWORD); - } - if (matcher.group("monitor") != null) { - return e -> e instanceof PsiKeyword && ((PsiKeyword)e).getTokenType().equals(JavaTokenType.SYNCHRONIZED_KEYWORD); - } - if (matcher.group("arraylength") != null) { - return e -> { - PsiExpression qualifier = getFieldReferenceQualifier(e, "length"); - return qualifier != null && qualifier.getType() instanceof PsiArrayType; - }; - } - String getField = matcher.group("getfield"); - String putField = matcher.group("putfield"); - String field = getField == null ? putField : getField; - if (field != null) { - return e -> { - PsiExpression qualifier = getFieldReferenceQualifier(e, field); - return qualifier != null && !(qualifier.getType() instanceof PsiArrayType) && - storeMatches(e.getParent(), getField == null); - }; - } - boolean arrayLoad = matcher.group("xaload") != null; - boolean arrayStore = matcher.group("xastore") != null; - if (arrayLoad || arrayStore) { - return e -> { - if (!(e instanceof PsiJavaToken) || !e.textMatches("[")) return false; - PsiElement parent = e.getParent(); - if (!(parent instanceof PsiArrayAccessExpression)) return false; - return storeMatches(parent, arrayStore); - }; - } - String method = matcher.group("invoke"); - if (method != null) { - int dotPos = method.lastIndexOf('.'); - if (dotPos != -1) { - String methodName = method.substring(dotPos + 1); - if (!IGNORED_METHODS.contains(methodName)) { - return e -> { - if (!(e instanceof PsiIdentifier) || !e.textMatches(methodName)) return false; - PsiElement parent = e.getParent(); - if (!(parent instanceof PsiReferenceExpression)) return false; - if (!(parent.getParent() instanceof PsiMethodCallExpression)) return false; - PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(((PsiReferenceExpression)parent).getQualifierExpression()); - if (qualifier == null || qualifier instanceof PsiNewExpression || - qualifier instanceof PsiLiteralExpression || qualifier instanceof PsiPolyadicExpression) { - return false; - } - return true; - }; - } - } - } - return null; - } - - private static @Nullable PsiExpression getFieldReferenceQualifier(PsiElement e, String fieldName) { - if (!(e instanceof PsiIdentifier) || !e.textMatches(fieldName)) return null; - PsiElement parent = e.getParent(); - if (!(parent instanceof PsiReferenceExpression)) return null; - if (parent.getParent() instanceof PsiMethodCallExpression) return null; - PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(((PsiReferenceExpression)parent).getQualifierExpression()); - if (qualifier == null || qualifier instanceof PsiNewExpression) return null; - return qualifier; - } - - private static boolean storeMatches(PsiElement element, boolean mustBeStore) { - PsiElement parent = element.getParent(); - while (parent instanceof PsiParenthesizedExpression) { - element = parent; - parent = element.getParent(); - } - if (parent instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent; - boolean isStore = assignment.getOperationTokenType().equals(JavaTokenType.EQ) && - assignment.getLExpression() == element; - return isStore == mustBeStore; - } - return !mustBeStore; - } - - private static Predicate getClassCastPredicate(@NotNull String message) { - Matcher matcher = CCE_MESSAGE.matcher(message); - if (!matcher.matches()) return null; - String targetClass = matcher.group(2); - return e -> { - 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(), targetClass); - } - if (e instanceof PsiIdentifier && e.getParent() instanceof PsiReferenceExpression) { - PsiReferenceExpression ref = (PsiReferenceExpression)e.getParent(); - PsiElement target = ref.resolve(); - PsiType type; - if (target instanceof PsiMethod) { - type = ((PsiMethod)target).getReturnType(); - } - else if (target instanceof PsiVariable) { - type = ((PsiVariable)target).getType(); - } - else { - return false; - } - PsiClass psiClass = PsiUtil.resolveClassInType(type); - if (!(psiClass instanceof PsiTypeParameter)) return false; - // Implicit cast added by compiler - return castClassMatches(ref.getType(), targetClass); - } - return false; - }; - } - - private static boolean castClassMatches(PsiType type, String className) { - if (type instanceof PsiPrimitiveType) { - return className.equals(((PsiPrimitiveType)type).getBoxedTypeName()); - } - if (type instanceof PsiIntersectionType) { - for (PsiType conjunct : ((PsiIntersectionType)type).getConjuncts()) { - if (castClassMatches(conjunct, className)) return true; - } - return false; - } - if (type instanceof PsiArrayType) { - if (className.startsWith("[") && className.length() > 1) { - PsiType componentType = ((PsiArrayType)type).getComponentType(); - switch (className.charAt(1)) { - case '[': - return castClassMatches(componentType, className.substring(1)); - case 'B': - return componentType.equals(PsiType.BYTE); - case 'C': - return componentType.equals(PsiType.CHAR); - case 'D': - return componentType.equals(PsiType.DOUBLE); - case 'F': - return componentType.equals(PsiType.FLOAT); - case 'Z': - return componentType.equals(PsiType.BOOLEAN); - case 'I': - return componentType.equals(PsiType.INT); - case 'J': - return componentType.equals(PsiType.LONG); - case 'S': - return componentType.equals(PsiType.SHORT); - case 'L': - if (className.charAt(className.length() - 1) == ';') { - return castClassMatches(componentType, className.substring(2, className.length() - 1)); - } - return false; - default: - return false; - } - } - } - if (type instanceof PsiClassType) { - return classTypeMatches(className, (PsiClassType)type, new THashSet<>()); - } - return true; - } - - private static boolean classTypeMatches(String className, PsiClassType classType, Set visited) { - PsiClass psiClass = PsiUtil.resolveClassInType(classType); - if (!visited.add(psiClass)) { - return true; - } - if (psiClass instanceof PsiTypeParameter) { - for (PsiClassType bound : ((PsiTypeParameter)psiClass).getExtendsList().getReferencedTypes()) { - if (classTypeMatches(className, bound, visited)) return true; - } - return false; - } - String name = classType.getClassName(); - if (name == null) return true; - if (!name.equals(StringUtil.substringAfterLast(className, ".")) && - !name.equals(StringUtil.substringAfterLast(className, "$"))) { - return false; - } - if (psiClass != null) { - if (className.equals(psiClass.getQualifiedName())) return true; - String packageName = StringUtil.getPackageName(className); - PsiFile psiFile = psiClass.getContainingFile(); - return psiFile instanceof PsiClassOwner && packageName.equals(((PsiClassOwner)psiFile).getPackageName()); - } - return true; - } - - private static boolean isArrayIndexOutOfBoundsSource(@NotNull String message, PsiElement e) { - if (!(e instanceof PsiJavaToken && e.textMatches("[") && e.getParent() instanceof PsiArrayAccessExpression)) { - return false; - } - Integer index = getArrayIndexFromMessage(message); - if (index != null) { - PsiLiteralExpression next = ObjectUtils.tryCast(PsiTreeUtil.skipWhitespacesAndCommentsForward(e), PsiLiteralExpression.class); - return next == null || index.equals(next.getValue()); - } - return true; - } - - public static @Nullable Integer getArrayIndexFromMessage(@NotNull String message) { - Matcher matcher = AIOOBE_MESSAGE.matcher(message); - if (matcher.matches()) { - return Integer.valueOf(matcher.group(1) == null ? matcher.group(2) : matcher.group(1)); - } - return null; - } - - public static @Nullable String getCastActualClassFromMessage(@NotNull String message) { - Matcher matcher = CCE_MESSAGE.matcher(message); - if (matcher.matches()) { - return matcher.group(1); - } - return null; - } - @Nullable public static ExceptionInfo parseMessage(String line, int textEndOffset) { int firstSpace = line.indexOf(' '); int colonPos = -1; - ExceptionInfo info = null; + TextRange classRange = null; if (firstSpace == -1) { - info = getExceptionClassFromMessage(line, 0, getLength(line)); + classRange = getExceptionClassFromMessage(line, 0, getLength(line)); } else if (firstSpace == "Caused".length() && line.startsWith(CAUSED_BY)) { colonPos = line.indexOf(':', CAUSED_BY.length()); - info = getExceptionClassFromMessage(line, CAUSED_BY.length(), colonPos == -1 ? getLength(line) : colonPos); + classRange = getExceptionClassFromMessage(line, CAUSED_BY.length(), colonPos == -1 ? getLength(line) : colonPos); } else if (firstSpace == "Exception".length() && line.startsWith(EXCEPTION_IN_THREAD)) { int nextQuotePos = line.indexOf("\" ", EXCEPTION_IN_THREAD.length()); if (nextQuotePos == -1) return null; int start = nextQuotePos + "\" ".length(); colonPos = line.indexOf(':', start); - info = getExceptionClassFromMessage(line, start, colonPos == -1 ? getLength(line) : colonPos); + classRange = getExceptionClassFromMessage(line, start, colonPos == -1 ? getLength(line) : colonPos); } else if (firstSpace > 2 && line.charAt(firstSpace - 1) == ':') { colonPos = firstSpace - 1; - info = getExceptionClassFromMessage(line, 0, firstSpace - 1); + classRange = getExceptionClassFromMessage(line, 0, firstSpace - 1); } else if (firstSpace > 3 && line.charAt(0) == '[' && line.charAt(firstSpace - 1) == ':' && line.charAt(firstSpace - 2) == ']') { colonPos = line.indexOf(':', firstSpace); - info = getExceptionClassFromMessage(line, firstSpace + 1, colonPos == -1 ? getLength(line) : colonPos); + classRange = getExceptionClassFromMessage(line, firstSpace + 1, colonPos == -1 ? getLength(line) : colonPos); } - if (info == null) return null; + if (classRange == null) return null; String message = colonPos == -1 ? null : line.substring(colonPos + 1).trim(); - return info.adjust(message, textEndOffset - line.length()); + message = StringUtil.notNullize(message); + int absoluteOffset = textEndOffset - line.length(); + String exceptionName = line.substring(classRange.getStartOffset(), classRange.getEndOffset()); + int startOffset = classRange.getStartOffset() + absoluteOffset; + return createExceptionInfo(message, exceptionName, startOffset); + } + + private static @NotNull ExceptionInfo createExceptionInfo(String message, String exceptionName, int startOffset) { + switch (exceptionName) { + case "java.lang.ArrayIndexOutOfBoundsException": + return new ArrayIndexOutOfBoundsExceptionInfo(startOffset, message); + case "java.lang.ArrayStoreException": + return new ArrayStoreExceptionInfo(startOffset, message); + case "java.lang.ClassCastException": + return new ClassCastExceptionInfo(startOffset, message); + case "java.lang.NullPointerException": + return new NullPointerExceptionInfo(startOffset, message); + case "java.lang.AssertionError": + return new AssertionErrorInfo(startOffset, message); + case "java.lang.ArithmeticException": + return new ArithmeticExceptionInfo(startOffset, message); + case "java.lang.NegativeArraySizeException": + return new NegativeArraySizeExceptionInfo(startOffset, message); + default: + return new ExceptionInfo(startOffset, exceptionName, message); + } } private static int getLength(String line) { @@ -426,7 +149,7 @@ public class ExceptionInfo { * @param to end index (exclusive) * @return a substring between from and to or null if it doesn't look like an exception name. */ - private static ExceptionInfo getExceptionClassFromMessage(String line, int from, int to) { + private static TextRange getExceptionClassFromMessage(String line, int from, int to) { if (to - from > 200) return null; boolean hasDot = false; for (int i = from; i < to; i++) { @@ -437,6 +160,6 @@ public class ExceptionInfo { hasDot |= c == '.'; } if (!hasDot) return null; - return new ExceptionInfo(from, line.substring(from, to), ""); + return new TextRange(from, to); } } diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java index 0d5b5b4069f8..169418332db5 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java @@ -424,7 +424,7 @@ public class ExceptionWorker { if (info == null) { action = exceptionAnalysisProvider.getIntermediateRowAnalysisAction(element); } else { - action = exceptionAnalysisProvider.getAnalysisAction(element, info.getExceptionClassName(), info.getExceptionMessage()); + action = exceptionAnalysisProvider.getAnalysisAction(element, info); } if (action == null) return; String actionName = Objects.requireNonNull(action.getTemplatePresentation().getDescription()); diff --git a/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java new file mode 100644 index 000000000000..b5732202b693 --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/NegativeArraySizeExceptionInfo.java @@ -0,0 +1,38 @@ +// 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.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.ObjectUtils; +import org.jetbrains.annotations.Nullable; + +public class NegativeArraySizeExceptionInfo extends ExceptionInfo { + NegativeArraySizeExceptionInfo(int offset, String message) { + super(offset, "java.lang.NegativeArraySizeException", message); + } + + public @Nullable Integer getSuppliedSize() { + try { + return Integer.valueOf(getExceptionMessage()); + } + catch (NumberFormatException e) { + return null; + } + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + if (e instanceof PsiKeyword && e.textMatches(PsiKeyword.NEW) && e.getParent() instanceof PsiNewExpression) { + PsiExpression[] dimensions = ((PsiNewExpression)e.getParent()).getArrayDimensions(); + for (PsiExpression dimension : dimensions) { + if (dimension != null) { + PsiLiteral literal = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(dimension), PsiLiteral.class); + // Explicit negative number like -1 cannot be literal, it's unary expression + if (literal != null && literal.getValue() instanceof Integer) continue; + } + return true; + } + } + return false; + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java new file mode 100644 index 000000000000..aa7f95081244 --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/NullPointerExceptionInfo.java @@ -0,0 +1,124 @@ +// 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.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NullPointerExceptionInfo extends ExceptionInfo { + // See JEP 358 Helpful NullPointerExceptions for details + private static final Pattern NPE_MESSAGE = Pattern.compile("Cannot (?:invoke \"(?.+)\\(\\)\"|" + + "assign field \"(?.+)\"|" + + "read field \"(?.+)\"|" + + "store to (?[a-z]+) array|" + + "load from (?[a-z]+) array|" + + "read the array (?length)|" + + "enter (?synchronized) block|" + + "throw (?exception))(?: because .+)?"); + // Methods that could be added by compiler implicitly: either unboxing or getClass (implicit NPE check used for method-refs, etc.) + private static final Set IGNORED_METHODS = ContainerUtil.immutableSet("intValue", "longValue", "doubleValue", "floatValue", + "shortValue", "byteValue", "booleanValue", "charValue", + "getClass"); + final Predicate myPredicate; + + NullPointerExceptionInfo(int offset, String message) { + super(offset, CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION, message); + myPredicate = getPredicate(message); + } + + private static Predicate getPredicate(String message) { + if (!message.startsWith("Cannot ")) return null; + Matcher matcher = NPE_MESSAGE.matcher(message); + if (!matcher.matches()) return null; + if (matcher.group("athrow") != null) { + return e -> e instanceof PsiKeyword && ((PsiKeyword)e).getTokenType().equals(JavaTokenType.THROW_KEYWORD); + } + if (matcher.group("monitor") != null) { + return e -> e instanceof PsiKeyword && ((PsiKeyword)e).getTokenType().equals(JavaTokenType.SYNCHRONIZED_KEYWORD); + } + if (matcher.group("arraylength") != null) { + return e -> { + PsiExpression qualifier = getFieldReferenceQualifier(e, "length"); + return qualifier != null && qualifier.getType() instanceof PsiArrayType; + }; + } + String getField = matcher.group("getfield"); + String putField = matcher.group("putfield"); + String field = getField == null ? putField : getField; + if (field != null) { + return e -> { + PsiExpression qualifier = getFieldReferenceQualifier(e, field); + return qualifier != null && !(qualifier.getType() instanceof PsiArrayType) && + storeMatches(e.getParent(), getField == null); + }; + } + boolean arrayLoad = matcher.group("xaload") != null; + boolean arrayStore = matcher.group("xastore") != null; + if (arrayLoad || arrayStore) { + return e -> { + if (!(e instanceof PsiJavaToken) || !e.textMatches("[")) return false; + PsiElement parent = e.getParent(); + if (!(parent instanceof PsiArrayAccessExpression)) return false; + return storeMatches(parent, arrayStore); + }; + } + String method = matcher.group("invoke"); + if (method != null) { + int dotPos = method.lastIndexOf('.'); + if (dotPos != -1) { + String methodName = method.substring(dotPos + 1); + if (!IGNORED_METHODS.contains(methodName)) { + return e -> { + if (!(e instanceof PsiIdentifier) || !e.textMatches(methodName)) return false; + PsiElement parent = e.getParent(); + if (!(parent instanceof PsiReferenceExpression)) return false; + if (!(parent.getParent() instanceof PsiMethodCallExpression)) return false; + PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(((PsiReferenceExpression)parent).getQualifierExpression()); + if (qualifier == null || qualifier instanceof PsiNewExpression || + qualifier instanceof PsiLiteralExpression || qualifier instanceof PsiPolyadicExpression) { + return false; + } + return true; + }; + } + } + } + return null; + } + + private static @Nullable PsiExpression getFieldReferenceQualifier(PsiElement e, String fieldName) { + if (!(e instanceof PsiIdentifier) || !e.textMatches(fieldName)) return null; + PsiElement parent = e.getParent(); + if (!(parent instanceof PsiReferenceExpression)) return null; + if (parent.getParent() instanceof PsiMethodCallExpression) return null; + PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(((PsiReferenceExpression)parent).getQualifierExpression()); + if (qualifier == null || qualifier instanceof PsiNewExpression) return null; + return qualifier; + } + + private static boolean storeMatches(PsiElement element, boolean mustBeStore) { + PsiElement parent = element.getParent(); + while (parent instanceof PsiParenthesizedExpression) { + element = parent; + parent = element.getParent(); + } + if (parent instanceof PsiAssignmentExpression) { + PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent; + boolean isStore = assignment.getOperationTokenType().equals(JavaTokenType.EQ) && + assignment.getLExpression() == element; + return isStore == mustBeStore; + } + return !mustBeStore; + } + + @Override + boolean isSpecificExceptionElement(PsiElement e) { + return myPredicate != null && myPredicate.test(e); + } +}