Skip to content

Commit d9b631d

Browse files
Revert "[PART 1] Refactor stack traces to use call-stack model with proper frame names (#633) (#636)
Really bad performance regression. This needs more thought. This reverts commit f46ecfe.
1 parent b94669b commit d9b631d

361 files changed

Lines changed: 4779 additions & 5210 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

sjsonnet/src/sjsonnet/Error.scala

Lines changed: 91 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,122 @@
11
package sjsonnet
22

33
import java.io.{PrintWriter, StringWriter}
4+
import scala.util.control.NonFatal
45

56
/**
6-
* Resolved stack frame captured at error-throw time. Positions are pre-resolved to file:line:col so
7-
* that rendering does not need access to the evaluator.
7+
* An exception that can keep track of the Sjsonnet call-stack while it is propagating upwards. This
8+
* helps provide good error messages with line numbers pointing towards user code.
89
*/
9-
final class StackTrace(val name: String, val file: String, val line: Int, val col: Int)
10-
11-
/**
12-
* An exception carrying a Jsonnet-level stack trace. The trace is captured from the evaluator's
13-
* live call stack when the error is thrown (via [[Error.fail]]).
14-
*/
15-
class Error(
16-
msg: String,
17-
private[sjsonnet] val trace: Array[StackTrace] = Array.empty,
18-
underlying: Option[Throwable] = None)
10+
class Error(msg: String, stack: List[Error.Frame] = Nil, underlying: Option[Throwable] = None)
1911
extends Exception(msg, underlying.orNull) {
2012

13+
setStackTrace(stack.reverseIterator.map(_.ste).toArray)
14+
2115
override def fillInStackTrace: Throwable = this
16+
17+
def addFrame(pos: Position, expr: Expr = null)(implicit ev: EvalErrorScope): Error = {
18+
if (stack.isEmpty || alwaysAddPos(expr)) {
19+
val exprErrorString = if (expr == null) null else expr.exprErrorString
20+
addFrameString(pos, exprErrorString)
21+
} else this
22+
}
23+
24+
def addFrameString(pos: Position, exprErrorString: String)(implicit ev: EvalErrorScope): Error = {
25+
val newFrame = new Error.Frame(pos, exprErrorString)
26+
stack match {
27+
case s :: ss if s.pos == pos =>
28+
if (s.exprErrorString == null && exprErrorString != null) copy(stack = newFrame :: ss)
29+
else this
30+
case _ => copy(stack = newFrame :: stack)
31+
}
32+
}
33+
34+
def asSeenFrom(ev: EvalErrorScope): Error =
35+
copy(stack = stack.map(_.asSeenFrom(ev)))
36+
37+
protected def copy(
38+
msg: String = msg,
39+
stack: List[Error.Frame] = stack,
40+
underlying: Option[Throwable] = underlying) =
41+
new Error(msg, stack, underlying)
42+
43+
private def alwaysAddPos(expr: Expr): Boolean = expr match {
44+
case _: Expr.LocalExpr | _: Expr.Arr | _: Expr.ObjExtend | _: Expr.ObjBody | _: Expr.IfElse =>
45+
false
46+
case _ => true
47+
}
2248
}
2349

2450
object Error {
25-
def fail(msg: String, pos: Position)(implicit ev: EvalErrorScope): Nothing =
26-
throw new Error(msg, ev.captureTrace(pos))
51+
final class Frame(val pos: Position, val exprErrorString: String)(implicit ev: EvalErrorScope) {
52+
val ste: StackTraceElement = {
53+
val cl = if (exprErrorString == null) "" else s"[$exprErrorString]"
54+
val (frameFile, frameLine) = ev.prettyIndex(pos) match {
55+
case None => (pos.currentFile.relativeToString(ev.wd) + " offset", pos.offset)
56+
case Some((line, col)) => (pos.currentFile.relativeToString(ev.wd) + ":" + line, col)
57+
}
58+
new StackTraceElement(cl, "", frameFile, frameLine)
59+
}
60+
61+
def asSeenFrom(ev: EvalErrorScope): Frame =
62+
if (ev eq this.ev) this else new Frame(pos, exprErrorString)(ev)
63+
}
64+
65+
def withStackFrame[T](expr: Expr)(implicit
66+
evaluator: EvalErrorScope): PartialFunction[Throwable, Nothing] = {
67+
case e: Error => throw e.addFrame(expr.pos, expr)
68+
case NonFatal(e) =>
69+
throw new Error("Internal Error", Nil, Some(e)).addFrame(expr.pos, expr)
70+
}
2771

2872
def fail(msg: String, expr: Expr)(implicit ev: EvalErrorScope): Nothing =
29-
fail(msg, expr.pos)
73+
fail(msg, expr.pos, expr.exprErrorString)
74+
75+
def fail(msg: String, pos: Position, cl: String = null)(implicit ev: EvalErrorScope): Nothing =
76+
throw new Error(msg, new Frame(pos, cl) :: Nil, None)
3077

3178
def fail(msg: String): Nothing =
3279
throw new Error(msg)
3380

34-
private def errorPrefix(err: Error): String = err match {
35-
case _: ParseError => "sjsonnet.ParseError: "
36-
case _: StaticError => "sjsonnet.StaticError: "
37-
case _ => "sjsonnet.Error: "
38-
}
39-
40-
def formatError(e: Throwable): String = e match {
41-
case err: Error if err.trace.nonEmpty =>
42-
val sb = new StringBuilder
43-
sb.append(errorPrefix(err)).append(err.getMessage)
44-
for (frame <- err.trace) {
45-
sb.append("\n at [").append(frame.name).append(']')
46-
if (frame.file != null) {
47-
sb.append(".(")
48-
.append(frame.file)
49-
.append(':')
50-
.append(frame.line)
51-
.append(':')
52-
.append(frame.col)
53-
.append(')')
54-
}
55-
}
56-
sb.append('\n')
57-
sb.toString
58-
case err: Error =>
59-
errorPrefix(err) + err.getMessage + '\n'
60-
case _ =>
61-
val s = new StringWriter()
62-
val p = new PrintWriter(s)
63-
try { e.printStackTrace(p); s.toString.replace("\t", " ") }
64-
finally p.close()
81+
def formatError(e: Throwable): String = {
82+
val s = new StringWriter()
83+
val p = new PrintWriter(s)
84+
try {
85+
e.printStackTrace(p)
86+
s.toString.replace("\t", " ")
87+
} finally {
88+
p.close()
89+
}
6590
}
6691
}
6792

6893
class ParseError(
6994
msg: String,
70-
_trace: Array[StackTrace] = Array.empty,
95+
stack: List[Error.Frame] = Nil,
7196
underlying: Option[Throwable] = None,
7297
val offset: Int = -1)
73-
extends Error(msg, _trace, underlying)
98+
extends Error(msg, stack, underlying) {
7499

75-
class StaticError(
76-
msg: String,
77-
_trace: Array[StackTrace] = Array.empty,
78-
underlying: Option[Throwable] = None)
79-
extends Error(msg, _trace, underlying)
100+
override protected def copy(
101+
msg: String = msg,
102+
stack: List[Error.Frame] = stack,
103+
underlying: Option[Throwable] = underlying): sjsonnet.ParseError =
104+
new ParseError(msg, stack, underlying, offset)
105+
}
106+
107+
class StaticError(msg: String, stack: List[Error.Frame] = Nil, underlying: Option[Throwable] = None)
108+
extends Error(msg, stack, underlying) {
109+
110+
override protected def copy(
111+
msg: String = msg,
112+
stack: List[Error.Frame] = stack,
113+
underlying: Option[Throwable] = underlying): sjsonnet.StaticError =
114+
new StaticError(msg, stack, underlying)
115+
}
80116

81117
object StaticError {
82-
def fail(msg: String, expr: Expr)(implicit ev: EvalErrorScope): Nothing = {
83-
var trace = ev.captureTrace(expr.pos)
84-
if (trace.isEmpty && expr.pos != null) {
85-
ev.prettyIndex(expr.pos) match {
86-
case Some((line, col)) =>
87-
trace = Array(
88-
new StackTrace(
89-
Util.wrapInLessThanGreaterThan("root"),
90-
expr.pos.currentFile.relativeToString(ev.wd),
91-
line,
92-
col
93-
)
94-
)
95-
case None =>
96-
}
97-
}
98-
throw new StaticError(msg, trace)
99-
}
118+
def fail(msg: String, expr: Expr)(implicit ev: EvalErrorScope): Nothing =
119+
throw new StaticError(msg, new Error.Frame(expr.pos, expr.exprErrorString) :: Nil, None)
100120
}
101121

102122
trait EvalErrorScope {
@@ -114,6 +134,4 @@ trait EvalErrorScope {
114134
(splitted(0).toInt, splitted(1).toInt)
115135
}
116136
}
117-
118-
def captureTrace(throwPos: Position): Array[StackTrace] = Array.empty
119137
}

0 commit comments

Comments
 (0)