ExceptionFilter: support ArithmeticException, AssertionError, NegativeArraySizeException; fixed support of exceptions without message

This commit is contained in:
Tagir Valeev
2019-01-31 10:33:17 +07:00
parent 5f7603dfe3
commit edb0c667af
2 changed files with 122 additions and 9 deletions
@@ -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
@@ -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<Trinity<String, Integer, Integer>> 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<Trinity<String, Integer, Integer>> 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<Trinity<String, Integer, Integer>> 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<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.NegativeArraySizeException\n", null, null),
Trinity.create("\tat SomeClass.main(SomeClass.java:5)\n", 5, 20));
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<Trinity<String, Integer, Integer>> traceAndPositions = Arrays.asList(
Trinity.create("Exception in thread \"main\" java.lang.ArithmeticException: / by zero\n", null, null),
Trinity.create("\tat SomeClass.main(SomeClass.java:6)\n", 6, 20));
checkColumnFinder(classText, traceAndPositions);
}
private void checkColumnFinder(String classText, List<Trinity<String, Integer, Integer>> traceAndPositions) {
myFixture.configureByText("SomeClass.java", classText);
Editor editor = myFixture.getEditor();
assertEquals(classText, editor.getDocument().getText());
ExceptionFilter filter = new ExceptionFilter(myFixture.getFile().getResolveScope());
for (Trinity<String, Integer, Integer> line : traceAndPositions) {
String stackLine = line.getFirst();