Refactor ExceptionInfo: separate subclasses for exceptions

GitOrigin-RevId: dfd9013385125b55b68ddaa5e8bc11dc2a109241
This commit is contained in:
Tagir Valeev
2020-05-08 08:53:14 +00:00
committed by intellij-monorepo-bot
parent f752499188
commit de4db0fbc2
12 changed files with 568 additions and 447 deletions
@@ -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;
@@ -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<caret>[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((<caret>(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((<caret>(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.<caret>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<caret>[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) {<caret>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) {<caret>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) {<caret>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) {<caret>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) {<caret>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) {<caret>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) {<caret>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) {<caret>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) {<caret>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) {<caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 <caret>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 = <caret>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<caret>/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 <caret>% 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.<caret>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) {<caret>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) {<caret>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) {<caret>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.<caret>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) {
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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<PsiClass> 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;
}
}
@@ -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
@@ -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 \"(?<invoke>.+)\\(\\)\"|" +
"assign field \"(?<putfield>.+)\"|" +
"read field \"(?<getfield>.+)\"|" +
"store to (?<xastore>[a-z]+) array|" +
"load from (?<xaload>[a-z]+) array|" +
"read the array (?<arraylength>length)|" +
"enter (?<monitor>synchronized) block|" +
"throw (?<athrow>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<String> 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<PsiElement> 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<PsiElement> 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<PsiElement> 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<PsiElement> 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<PsiClass> 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);
}
}
@@ -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());
@@ -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;
}
}
@@ -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 \"(?<invoke>.+)\\(\\)\"|" +
"assign field \"(?<putfield>.+)\"|" +
"read field \"(?<getfield>.+)\"|" +
"store to (?<xastore>[a-z]+) array|" +
"load from (?<xaload>[a-z]+) array|" +
"read the array (?<arraylength>length)|" +
"enter (?<monitor>synchronized) block|" +
"throw (?<athrow>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<String> IGNORED_METHODS = ContainerUtil.immutableSet("intValue", "longValue", "doubleValue", "floatValue",
"shortValue", "byteValue", "booleanValue", "charValue",
"getClass");
final Predicate<PsiElement> myPredicate;
NullPointerExceptionInfo(int offset, String message) {
super(offset, CommonClassNames.JAVA_LANG_NULL_POINTER_EXCEPTION, message);
myPredicate = getPredicate(message);
}
private static Predicate<PsiElement> 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);
}
}