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:
@@ -276,7 +276,7 @@ public class ReplInterpreter {
|
|||||||
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
|
scriptInstance = scriptInstanceConstructor.newInstance(constructorArgs);
|
||||||
}
|
}
|
||||||
catch (Throwable e) {
|
catch (Throwable e) {
|
||||||
return LineResult.error(Throwables.getStackTraceAsString(e.getCause()));
|
return LineResult.error(renderStackTrace(e.getCause()));
|
||||||
}
|
}
|
||||||
Field rvField = scriptClass.getDeclaredField("rv");
|
Field rvField = scriptClass.getDeclaredField("rv");
|
||||||
rvField.setAccessible(true);
|
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
|
@Nullable
|
||||||
private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
|
private ScriptDescriptor doAnalyze(@NotNull JetFile psiFile, @NotNull MessageCollector messageCollector) {
|
||||||
WritableScope scope = new WritableScopeImpl(
|
WritableScope scope = new WritableScopeImpl(
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
>>> throw Exception("hi there")
|
>>> throw Exception("hi there")
|
||||||
substring: Exception: hi there
|
java.lang.Exception: hi there
|
||||||
|
at Line1.<init>(Unknown Source)
|
||||||
>>> fun foo() = 2
|
>>> fun foo() = 2
|
||||||
>>> foo()
|
>>> foo()
|
||||||
2
|
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 START_PATTERN = Pattern.compile(">>>( *)(.*)$")
|
||||||
private val INCOMPLETE_PATTERN = Pattern.compile("\\.\\.\\.( *)(.*)$")
|
private val INCOMPLETE_PATTERN = Pattern.compile("\\.\\.\\.( *)(.*)$")
|
||||||
private val SUBSTRING_PATTERN = Pattern.compile("substring: (.*)")
|
|
||||||
|
|
||||||
private val INCOMPLETE_LINE_MESSAGE = "incomplete line"
|
private val INCOMPLETE_LINE_MESSAGE = "incomplete line"
|
||||||
|
|
||||||
@@ -39,12 +38,7 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
|
|||||||
System.setProperty("java.awt.headless", "true")
|
System.setProperty("java.awt.headless", "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class MatchType {
|
private data class OneLine(val code: String, val expected: String)
|
||||||
EQUALS
|
|
||||||
SUBSTRING
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class OneLine(val code: String, val expected: String, val matchType: MatchType)
|
|
||||||
|
|
||||||
private fun loadLines(file: File): List<OneLine> {
|
private fun loadLines(file: File): List<OneLine> {
|
||||||
val lines = ArrayDeque(file.readLines())
|
val lines = ArrayDeque(file.readLines())
|
||||||
@@ -63,16 +57,9 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
|
|||||||
if (lines.isNotEmpty()) {
|
if (lines.isNotEmpty()) {
|
||||||
val nextLine = lines.peek()!!
|
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)
|
val incompleteMatcher = INCOMPLETE_PATTERN.matcher(nextLine)
|
||||||
if (incompleteMatcher.matches()) {
|
if (incompleteMatcher.matches()) {
|
||||||
result.add(OneLine(code, INCOMPLETE_LINE_MESSAGE, MatchType.EQUALS))
|
result.add(OneLine(code, INCOMPLETE_LINE_MESSAGE))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +69,7 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
|
|||||||
value.appendln(lines.poll()!!)
|
value.appendln(lines.poll()!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.add(OneLine(code, value.toString(), MatchType.EQUALS))
|
result.add(OneLine(code, value.toString()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -92,26 +79,19 @@ public abstract class AbstractReplInterpreterTest : UsefulTestCase() {
|
|||||||
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.FULL_JDK)
|
val configuration = JetTestUtils.compilerConfigurationForTests(ConfigurationKind.ALL, TestJdkKind.FULL_JDK)
|
||||||
val repl = ReplInterpreter(getTestRootDisposable()!!, configuration)
|
val repl = ReplInterpreter(getTestRootDisposable()!!, configuration)
|
||||||
|
|
||||||
for ((code, expected, matchType) in loadLines(File(path))) {
|
for ((code, expected) in loadLines(File(path))) {
|
||||||
val expectedString = StringUtil.convertLineSeparators(expected).replaceFirst("\n$", "")
|
|
||||||
|
|
||||||
val lineResult = repl.eval(code)
|
val lineResult = repl.eval(code)
|
||||||
val actual = when (lineResult.getType()) {
|
val actual = when (lineResult.getType()) {
|
||||||
ReplInterpreter.LineResultType.SUCCESS -> lineResult.getValue()?.toString() ?: ""
|
ReplInterpreter.LineResultType.SUCCESS -> lineResult.getValue()?.toString() ?: ""
|
||||||
ReplInterpreter.LineResultType.ERROR -> lineResult.getErrorText()
|
ReplInterpreter.LineResultType.ERROR -> lineResult.getErrorText()
|
||||||
ReplInterpreter.LineResultType.INCOMPLETE -> INCOMPLETE_LINE_MESSAGE
|
ReplInterpreter.LineResultType.INCOMPLETE -> INCOMPLETE_LINE_MESSAGE
|
||||||
}
|
}
|
||||||
val actualString = StringUtil.convertLineSeparators(actual).replaceFirst("\n$", "")
|
|
||||||
|
|
||||||
when (matchType) {
|
Assert.assertEquals(
|
||||||
MatchType.EQUALS -> {
|
"After evaluation of: $code",
|
||||||
Assert.assertEquals("After evaluation of: $code", expectedString, actualString)
|
StringUtil.convertLineSeparators(expected).replaceFirst("\n$", ""),
|
||||||
}
|
StringUtil.convertLineSeparators(actual).replaceFirst("\n$", "")
|
||||||
MatchType.SUBSTRING -> {
|
)
|
||||||
Assert.assertTrue("Evaluated result must contain substring: $expectedString, actual: $actualString, line: $code",
|
|
||||||
expectedString in actualString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user