From edb0c667af7f1193f03bf2a0029db1b5119b0202 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 31 Jan 2019 10:31:14 +0700 Subject: [PATCH] ExceptionFilter: support ArithmeticException, AssertionError, NegativeArraySizeException; fixed support of exceptions without message --- .../execution/filters/ExceptionFilter.java | 61 ++++++++++++++-- .../filters/ExceptionWorkerTest.java | 70 ++++++++++++++++++- 2 files changed, 122 insertions(+), 9 deletions(-) diff --git a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java index 8946159c02c6..353df232a7e1 100644 --- a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java +++ b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java @@ -19,7 +19,9 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiElementFilter; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,7 +47,8 @@ public class ExceptionFilter implements Filter, DumbAware { private static PsiElementFilter getRefinerFromException(@NotNull String line) { String exceptionName = getExceptionFromMessage(line); if (exceptionName == null) return null; - PsiElementFilter throwFilter = e -> { + PsiElementFilter exceptionCreationFilter = e -> { + // We look for new Exception() expression rather than throw statement, because stack-trace is filled in exception constructor if (!(e instanceof PsiKeyword) || !(e.textMatches(PsiKeyword.NEW))) return false; PsiNewExpression newExpression = ObjectUtils.tryCast(e.getParent(), PsiNewExpression.class); if (newExpression == null) return false; @@ -53,8 +56,8 @@ public class ExceptionFilter implements Filter, DumbAware { return type != null && type.equalsToText(exceptionName); }; PsiElementFilter specificFilter = getExceptionSpecificFilter(exceptionName); - if (specificFilter == null) return throwFilter; - return element -> throwFilter.isAccepted(element) || specificFilter.isAccepted(element); + if (specificFilter == null) return exceptionCreationFilter; + return element -> exceptionCreationFilter.isAccepted(element) || specificFilter.isAccepted(element); } @Nullable @@ -72,6 +75,44 @@ public class ExceptionFilter implements Filter, DumbAware { } return false; }; + 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; } @@ -81,18 +122,18 @@ public class ExceptionFilter implements Filter, DumbAware { private static String getExceptionFromMessage(String line) { int firstSpace = line.indexOf(' '); if (firstSpace == -1) { - return getExceptionFromMessage(line, 0, line.length()); + return getExceptionFromMessage(line, 0, getLength(line)); } if (firstSpace == "Caused".length() && line.startsWith(CAUSED_BY)) { int colonPos = line.indexOf(':', CAUSED_BY.length()); - return getExceptionFromMessage(line, CAUSED_BY.length(), colonPos == -1 ? line.length() : colonPos); + return getExceptionFromMessage(line, CAUSED_BY.length(), colonPos == -1 ? getLength(line) : colonPos); } 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(); int colonPos = line.indexOf(':', start); - return getExceptionFromMessage(line, start, colonPos == -1 ? line.length() : colonPos); + return getExceptionFromMessage(line, start, colonPos == -1 ? getLength(line) : colonPos); } if (firstSpace > 2 && line.charAt(firstSpace - 1) == ':') { return getExceptionFromMessage(line, 0, firstSpace - 1); @@ -100,6 +141,14 @@ public class ExceptionFilter implements Filter, DumbAware { return null; } + private static int getLength(String line) { + int length = line.length(); + while (length > 2 && Character.isWhitespace(line.charAt(length - 1))) { + length--; + } + return length; + } + /** * Returns a substring of {@code line} from {@code from} to {@code to} position after heuristically checking that * given substring could be an exception class name. Currently all names which are not very long, consist of diff --git a/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java b/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java index 395761e072aa..0e296591cb06 100644 --- a/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/execution/filters/ExceptionWorkerTest.java @@ -166,9 +166,6 @@ public class ExceptionWorkerTest extends LightCodeInsightFixtureTestCase { " r.run();\n" + " }\n" + "}"; - myFixture.configureByText("SomeClass.java", classText); - Editor editor = myFixture.getEditor(); - assertEquals(classText, editor.getDocument().getText()); List> traceAndPositions = Arrays.asList( Trinity.create("Exception in thread \"main\" java.lang.RuntimeException: java.lang.ArrayIndexOutOfBoundsException: Index 1 out of bounds for length 0\n", null, null), Trinity.create("\tat SomeClass$Inner.run(SomeClass.java:12)\n", 12, 15), @@ -182,6 +179,73 @@ public class ExceptionWorkerTest extends LightCodeInsightFixtureTestCase { Trinity.create("\tat SomeClass.test(SomeClass.java:18)\n", 18, 9), Trinity.create("\tat SomeClass.access$000(SomeClass.java:2)\n", 2, 1), Trinity.create("\tat SomeClass$Inner.run(SomeClass.java:10)\n", 10, 54)); + checkColumnFinder(classText, traceAndPositions); + } + + public void testColumnFinderAssert() { + @Language("JAVA") String classText = + "/** @noinspection ALL*/\n" + + "public class SomeClass {\n" + + " public static void main(String[] args) {\n" + + " assert false;\n" + + " }\n" + + "}"; + List> traceAndPositions = Arrays.asList( + Trinity.create("Exception in thread \"main\" java.lang.AssertionError\n", null, null), + Trinity.create("\tat SomeClass.main(SomeClass.java:4)\n", 4, 5)); + checkColumnFinder(classText, traceAndPositions); + } + + public void testColumnFinderArrayStore() { + @Language("JAVA") String classText = + "/** @noinspection ALL*/\n" + + "public class SomeClass {\n" + + " public static void main(String[] args) {\n" + + " Object[] arr = new String[1];\n" + + " arr[0] = 1;\n" + + " }\n" + + "}"; + List> traceAndPositions = Arrays.asList( + Trinity.create("Exception in thread \"main\" java.lang.ArrayStoreException: java.lang.Integer\n", null, null), + Trinity.create("\tat SomeClass.main(SomeClass.java:5)\n", 5, 12)); + checkColumnFinder(classText, traceAndPositions); + } + + public void testColumnFinderNegativeArraySize() { + @Language("JAVA") String classText = + "/** @noinspection ALL*/\n" + + "public class SomeClass {\n" + + " public static void main(String[] args) {\n" + + " int a = -1;\n" + + " Object[] arr = new String[1][a], arr2 = new String[] {\"foo\"}, arr3 = new String[(2)][1];\n" + + " }\n" + + "}"; + List> traceAndPositions = Arrays.asList( + Trinity.create("Exception in thread \"main\" java.lang.NegativeArraySizeException\n", null, null), + Trinity.create("\tat SomeClass.main(SomeClass.java:5)\n", 5, 20)); + checkColumnFinder(classText, traceAndPositions); + } + + public void testColumnFinderDivisionByZero() { + @Language("JAVA") String classText = + "/** @noinspection ALL*/\n" + + "public class SomeClass {\n" + + " public static void main(String[] args) {\n" + + " int a = 0;\n" + + " double b = 1.1;\n" + + " double res = 1 / a / -2 + a / 2 + b / 0;\n" + + " }\n" + + "}"; + List> traceAndPositions = Arrays.asList( + Trinity.create("Exception in thread \"main\" java.lang.ArithmeticException: / by zero\n", null, null), + Trinity.create("\tat SomeClass.main(SomeClass.java:6)\n", 6, 20)); + checkColumnFinder(classText, traceAndPositions); + } + + private void checkColumnFinder(String classText, List> traceAndPositions) { + myFixture.configureByText("SomeClass.java", classText); + Editor editor = myFixture.getEditor(); + assertEquals(classText, editor.getDocument().getText()); ExceptionFilter filter = new ExceptionFilter(myFixture.getFile().getResolveScope()); for (Trinity line : traceAndPositions) { String stackLine = line.getFirst();