REPL: sanitize stack traces

Don't include our compiler's and preloader's internal code, reflection, native
methods etc. Also delete "substring" matching logic from tests
This commit is contained in:
Alexander Udalov
2014-08-13 15:33:18 +04:00
parent 5beefac5bc
commit 844845c6e3
3 changed files with 38 additions and 31 deletions
@@ -276,7 +276,7 @@ public class ReplInterpreter {
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
}
catch (Throwable e) {
return LineResult.error(Throwables.getStackTraceAsString(e.getCause()));
return LineResult.error(renderStackTrace(e.getCause()));
}
Field rvField = scriptClass.getDeclaredField("rv");
rvField.setAccessible(true);
@@ -295,6 +295,27 @@ public class ReplInterpreter {
}
}
@NotNull
private static String renderStackTrace(@NotNull Throwable cause) {
StackTraceElement[] oldTrace = cause.getStackTrace();
List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>();
boolean skip = true;
for (int i = oldTrace.length - 1; i >= 0; i--) {
StackTraceElement element = oldTrace[i];
// All our code happens in the script constructor, and no reflection/native code happens in constructors.
// So we ignore everything in the stack trace until the first constructor
if (element.getMethodName().equals("<init>")) {
skip = false;
}
if (!skip) {
newTrace.add(element);
}
}
Collections.reverse(newTrace);
cause.setStackTrace(newTrace.toArray(new StackTraceElement[newTrace.size()]));
return Throwables.getStackTraceAsString(cause);
}
@Nullable
private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
WritableScope scope = new WritableScopeImpl(
+7 -1
View File
@@ -1,5 +1,11 @@
>>> throw Exception("hi there")
substring: Exception: hi there
java.lang.Exception: hi there
at Line1.<init>(Unknown Source)
>>> fun foo() = 2
>>> foo()
2
>>> fun bar() = throw AssertionError()
>>> bar()
java.lang.AssertionError
at Line4.bar(Unknown Source)
at Line5.<init>(Unknown Source)
@@ -30,7 +30,6 @@ import org.junit.Assert
private val START_PATTERN = Pattern.compile(">>>( *)(.*)$")
private val INCOMPLETE_PATTERN = Pattern.compile("\\.\\.\\.( *)(.*)$")
private val SUBSTRING_PATTERN = Pattern.compile("substring: (.*)")
private val INCOMPLETE_LINE_MESSAGE = "incomplete line"
@@ -39,12 +38,7 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
System.setProperty("java.awt.headless", "true")
}
private enum class MatchType {
EQUALS
SUBSTRING
}
private data class OneLine(val code: String, val expected: String, val matchType: MatchType)
private data class OneLine(val code: String, val expected: String)
private fun loadLines(file: File): List<OneLine> {
val lines = ArrayDeque(file.readLines())
@@ -63,16 +57,9 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
if (lines.isNotEmpty()) {
val nextLine = lines.peek()!!
val substringMatcher = SUBSTRING_PATTERN.matcher(nextLine)
if (substringMatcher.matches()) {
result.add(OneLine(code, substringMatcher.group(1)!!, MatchType.SUBSTRING))
lines.poll()
continue
}
val incompleteMatcher = INCOMPLETE_PATTERN.matcher(nextLine)
if (incompleteMatcher.matches()) {
result.add(OneLine(code, INCOMPLETE_LINE_MESSAGE, MatchType.EQUALS))
result.add(OneLine(code, INCOMPLETE_LINE_MESSAGE))
continue
}
}
@@ -82,7 +69,7 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
value.appendln(lines.poll()!!)
}
result.add(OneLine(code, value.toString(), MatchType.EQUALS))
result.add(OneLine(code, value.toString()))
}
return result
@@ -92,26 +79,19 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.FULL_JDK)
val repl = ReplInterpreter(getTestRootDisposable()!!, configuration)
for ((code, expected, matchType) in loadLines(File(path))) {
val expectedString = StringUtil.convertLineSeparators(expected).replaceFirst("\n$", "")
for ((code, expected) in loadLines(File(path))) {
val lineResult = repl.eval(code)
val actual = when (lineResult.getType()) {
ReplInterpreter.LineResultType.SUCCESS -> lineResult.getValue()?.toString() ?: ""
ReplInterpreter.LineResultType.ERROR -> lineResult.getErrorText()
ReplInterpreter.LineResultType.INCOMPLETE -> INCOMPLETE_LINE_MESSAGE
}
val actualString = StringUtil.convertLineSeparators(actual).replaceFirst("\n$", "")
when (matchType) {
MatchType.EQUALS -> {
Assert.assertEquals("After evaluation of: $code", expectedString, actualString)
}
MatchType.SUBSTRING -> {
Assert.assertTrue("Evaluated result must contain substring: $expectedString, actual: $actualString, line: $code",
expectedString in actualString)
}
}
Assert.assertEquals(
"After evaluation of: $code",
StringUtil.convertLineSeparators(expected).replaceFirst("\n$", ""),
StringUtil.convertLineSeparators(actual).replaceFirst("\n$", "")
)
}
}
}