--- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -658,9 +658,11 @@ Num(v) => Val::new_checked_num(*v)?, BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, &v1, *o, &v2)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?, - Var(name) => push(loc, "var", || { - Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?) - })?, + Var(name) => push( + loc, + || "var".to_owned(), + || Ok(Val::Lazy(context.binding(name.clone())?).unwrap_if_lazy()?), + )?, Index(LocExpr(v, _), index) if matches!(&**v, Expr::Literal(LiteralType::Super)) => { let name = evaluate(context.clone(), index)?.try_cast_str("object index")?; context @@ -769,10 +771,14 @@ Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?, Function(params, body) => evaluate_method(context, params.clone(), body.clone()), AssertExpr(AssertStmt(value, msg), returned) => { - let assertion_result = push(&value.1, "assertion condition", || { - evaluate(context.clone(), &value)? - .try_cast_bool("assertion condition should be boolean") - })?; + let assertion_result = push( + &value.1, + || "assertion condition".to_owned(), + || { + evaluate(context.clone(), &value)? + .try_cast_bool("assertion condition should be boolean") + }, + )?; if assertion_result { evaluate(context, returned)? } else if let Some(msg) = msg { @@ -781,9 +787,15 @@ create_error_result(crate::Error::AssertionFailed(Val::Null))? } } - Error(e) => create_error_result(crate::Error::RuntimeError( - evaluate(context, e)?.try_cast_str("error text should be string")?, - ))?, + Error(e) => push( + &loc, + || "error statement".to_owned(), + || { + create_error_result(crate::Error::RuntimeError( + evaluate(context, e)?.try_cast_str("error text should be string")?, + ))? + }, + )?, IfElse { cond, cond_then, --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -72,8 +72,8 @@ #[derive(Default)] struct EvaluationData { - /// Used for stack-overflows and stacktraces - stack: Vec, + /// Used for stack overflow detection, stacktrace is now populated on unwind + stack_depth: usize, /// Contains file source codes and evaluated results for imports and pretty /// printing stacktraces files: HashMap, FileData>, @@ -103,11 +103,11 @@ } pub(crate) fn push( e: &Option, - comment: &str, + frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { - if e.is_some() { - with_state(|s| s.push(e.clone().unwrap(), comment.to_owned(), f)) + if let Some(v) = e { + with_state(|s| s.push(&v, frame_desc, f)) } else { f() } @@ -332,42 +332,33 @@ /// Executes code, creating new stack frame pub fn push( &self, - e: ExprLocation, - comment: String, + e: &ExprLocation, + frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { { let mut data = self.data_mut(); - let stack = &mut data.stack; - if stack.len() > self.settings().max_stack_frames { + let stack_depth = &mut data.stack_depth; + if *stack_depth > self.settings().max_stack_frames { // Error creation uses data, so i drop guard here drop(data); return Err(self.error(Error::StackOverflow)); } else { - stack.push(StackTraceElement(e, comment)); + *stack_depth+=1; } } let result = f(); - self.data_mut().stack.pop(); + self.data_mut().stack_depth -= 1; + if let Err(mut err) = result { + (err.1).0.push(StackTraceElement(e.clone(), frame_desc())); + return Err(err); + } result } - /// Returns current stack trace - pub fn stack_trace(&self) -> StackTrace { - StackTrace( - self.data() - .stack - .iter() - .rev() - .take(self.settings().max_stack_trace_size) - .cloned() - .collect(), - ) - } - /// Creates error with stack trace pub fn error(&self, err: Error) -> LocError { - LocError(err, self.stack_trace()) + LocError(err, StackTrace(vec![])) } /// Runs passed function in state (required, if function needs to modify stack trace) @@ -396,22 +387,24 @@ #[test] fn eval_state_stacktrace() { let state = EvaluationState::default(); - state + state.run_in_state(||{ + state .push( - ExprLocation(Rc::new(PathBuf::from("test1.jsonnet")), 10, 20), - "outer".to_owned(), + &ExprLocation(Rc::new(PathBuf::from("test1.jsonnet")), 10, 20), + || "outer".to_owned(), || { state.push( - ExprLocation(Rc::new(PathBuf::from("test2.jsonnet")), 30, 40), - "inner".to_owned(), + &ExprLocation(Rc::new(PathBuf::from("test2.jsonnet")), 30, 40), + || "inner".to_owned(), || { - Ok(()) + Err(create_error(crate::error::Error::RuntimeError("".into()))) }, )?; Ok(()) }, ) .unwrap(); + }); } #[test]