--- a/crates/jsonnet-evaluator/src/error.rs +++ b/crates/jsonnet-evaluator/src/error.rs @@ -9,6 +9,8 @@ RuntimeError(String), StackOverflow, + FractionalIndex, + DivisionByZero, } #[derive(Clone, Debug)] --- a/crates/jsonnet-evaluator/src/evaluate.rs +++ b/crates/jsonnet-evaluator/src/evaluate.rs @@ -135,7 +135,12 @@ // Num X Num (Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2), - (Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => Val::Num(v1 / v2), + (Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => { + if *v2 <= f64::EPSILON { + create_error(crate::Error::DivisionByZero)? + } + Val::Num(v1 / v2) + } (Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2), @@ -305,6 +310,7 @@ }) } +#[inline(always)] pub fn evaluate(context: Context, expr: &LocExpr) -> Result { use Expr::*; let locexpr = expr.clone(); @@ -356,11 +362,15 @@ create_error(crate::Error::NoSuchField(s))? } } - (Val::Arr(v), Val::Num(n)) => v - .get(n as usize) - .unwrap_or_else(|| panic!("out of bounds")) - .clone() - .unwrap_if_lazy()?, + (Val::Arr(v), Val::Num(n)) => { + if n.fract() > f64::EPSILON { + create_error(crate::Error::FractionalIndex)? + } + v.get(n as usize) + .unwrap_or_else(|| panic!("out of bounds")) + .clone() + .unwrap_if_lazy()? + } (Val::Str(s), Val::Num(n)) => { Val::Str(s.chars().skip(n as usize).take(1).collect()) } @@ -511,29 +521,52 @@ panic!("bad trace call"); } } + ("std", "pow") => { + assert_eq!(args.len(), 2); + if let (Val::Num(a), Val::Num(b)) = ( + evaluate(context.clone(), &args[0].1)?, + evaluate(context, &args[1].1)?, + ) { + Val::Num(a.powf(b)) + } else { + panic!("bad pow call"); + } + } (ns, name) => panic!("Intristic not found: {}.{}", ns, name), }, - Val::Func(f) => push(locexpr, "function call".to_owned(), || { - f.evaluate( - args.clone() - .into_iter() - .map(move |a| { - ( - a.clone().0, - if *tailstrict { - Val::Lazy(LazyVal::new_resolved( - evaluate(context.clone(), &a.1).unwrap(), + Val::Func(f) => { + let body = #[inline(always)] + || { + f.evaluate( + args.clone() + .into_iter() + .map( + #[inline(always)] + move |a| { + Ok(( + a.clone().0, + if *tailstrict { + Val::Lazy(LazyVal::new_resolved(evaluate( + context.clone(), + &a.1, + )?)) + } else { + Val::Lazy(lazy_val!( + closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1)) + )) + }, )) - } else { - Val::Lazy(lazy_val!( - closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1)) - )) }, ) - }) - .collect(), - ) - })?, + .collect::>>()?, + ) + }; + if *tailstrict { + body()? + } else { + push(locexpr, "function call".to_owned(), body)? + } + } _ => panic!("{:?} is not a function", value), } } @@ -578,9 +611,7 @@ )? } else { match cond_else { - Some(v) => push(v.clone(), "if condition 'else' branch".to_owned(), || { - evaluate(context, v) - })?, + Some(v) => evaluate(context, v)?, None => Val::Null, } } --- a/crates/jsonnet-evaluator/src/lib.rs +++ b/crates/jsonnet-evaluator/src/lib.rs @@ -2,6 +2,7 @@ #![feature(type_alias_impl_trait)] #![feature(debug_non_exhaustive)] #![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)] +#![feature(stmt_expr_attributes)] mod ctx; mod dynamic; mod error; @@ -51,6 +52,19 @@ } } +pub struct EvaluationSettings { + max_stack_frames: usize, + max_stack_trace_size: usize, +} +impl Default for EvaluationSettings { + fn default() -> Self { + EvaluationSettings { + max_stack_frames: 500, + max_stack_trace_size: 20, + } + } +} + pub struct FileData(String, LocExpr, Option); #[derive(Default)] pub struct EvaluationStateInternals { @@ -60,21 +74,31 @@ /// printing stacktraces files: RefCell>, globals: RefCell>, + + settings: EvaluationSettings, } thread_local! { - pub static EVAL_STATE: RefCell> = RefCell::new(None) + /// Contains state for currently executing file + /// Global state is fine there + pub(crate) static EVAL_STATE: RefCell> = RefCell::new(None) } +#[inline(always)] pub(crate) fn with_state(f: impl FnOnce(&EvaluationState) -> T) -> T { - EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap())) + EVAL_STATE.with( + #[inline(always)] + |s| f(s.borrow().as_ref().unwrap()), + ) } pub(crate) fn create_error(err: Error) -> Result { with_state(|s| s.error(err)) } +#[inline(always)] pub(crate) fn push(e: LocExpr, comment: String, f: impl FnOnce() -> Result) -> Result { with_state(|s| s.push(e, comment, f)) } +/// Maintains stack trace and import resolution #[derive(Default, Clone)] pub struct EvaluationState(Rc); impl EvaluationState { @@ -189,10 +213,11 @@ Context::new().extend(new_bindings, None, None, None) } + #[inline(always)] pub fn push(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result) -> Result { { let mut stack = self.0.stack.borrow_mut(); - if stack.len() > 500 { + if stack.len() > self.0.settings.max_stack_frames { drop(stack); return self.error(Error::StackOverflow); } else { @@ -209,7 +234,16 @@ } } pub fn stack_trace(&self) -> StackTrace { - StackTrace(self.0.stack.borrow().iter().rev().cloned().collect()) + StackTrace( + self.0 + .stack + .borrow() + .iter() + .rev() + .take(self.0.settings.max_stack_trace_size) + .cloned() + .collect(), + ) } pub fn error(&self, err: Error) -> Result { Err(LocError(err, self.stack_trace())) --- a/crates/jsonnet-evaluator/src/val.rs +++ b/crates/jsonnet-evaluator/src/val.rs @@ -78,6 +78,8 @@ } impl FuncDesc { // TODO: Check for unset variables + /// This function is always inlined to make tailstrict work + #[inline(always)] pub fn evaluate(&self, args: Vec<(Option, Val)>) -> Result { let mut new_bindings: HashMap = HashMap::new(); let future_ctx = Context::new_future();