--- a/crates/jsonnet-evaluator/build.rs +++ b/crates/jsonnet-evaluator/build.rs @@ -1,13 +1,18 @@ use bincode::serialize; use jsonnet_parser::{parse, ParserSettings}; use jsonnet_stdlib::STDLIB_STR; -use std::{env, fs::File, io::Write, path::Path}; +use std::{ + env, + fs::File, + io::Write, + path::{Path, PathBuf}, +}; fn main() { let parsed = parse( STDLIB_STR, &ParserSettings { - file_name: "std.jsonnet".to_owned(), + file_name: PathBuf::from("std.jsonnet"), loc_data: true, }, ) --- a/crates/jsonnet-evaluator/src/error.rs +++ b/crates/jsonnet-evaluator/src/error.rs @@ -8,6 +8,7 @@ NoSuchField(String), RuntimeError(String), + StackOverflow, } #[derive(Clone, Debug)] --- a/crates/jsonnet-evaluator/src/evaluate.rs +++ b/crates/jsonnet-evaluator/src/evaluate.rs @@ -31,12 +31,14 @@ ( b.name.clone(), lazy_binding!(move |this, super_obj| { - Ok(lazy_val!( - closure!(clone context_creator, clone b, || evaluate( - context_creator.0(this.clone(), super_obj.clone())?, - &b.value - )) - )) + Ok(lazy_val!(closure!(clone context_creator, clone b, || + push(b.value.clone(), "thunk".to_owned(), ||{ + evaluate( + context_creator.0(this.clone(), super_obj.clone())?, + &b.value + ) + }) + ))) }), ) } @@ -326,7 +328,9 @@ Num(v) => Val::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) => Val::Lazy(context.binding(&name)).unwrap_if_lazy()?, + Var(name) => push(locexpr, "var".to_owned(), || { + Val::Lazy(context.binding(&name)).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 @@ -412,6 +416,7 @@ match evaluate(context, expr)? { Val::Str(n) => Val::Num(n.chars().count() as f64), Val::Arr(i) => Val::Num(i.len() as f64), + Val::Obj(o) => Val::Num(o.fields().len() as f64), v => panic!("can't get length of {:?}", v), } } --- a/crates/jsonnet-evaluator/src/lib.rs +++ b/crates/jsonnet-evaluator/src/lib.rs @@ -16,7 +16,7 @@ pub use evaluate::*; use jsonnet_parser::*; pub use obj::*; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, path::PathBuf, rc::Rc}; pub use val::*; rc_fn_helper!( @@ -43,7 +43,7 @@ stack: RefCell>, /// Contains file source codes and evaluated results for imports and pretty /// printing stacktraces - files: RefCell>, + files: RefCell>, globals: RefCell>, } @@ -56,7 +56,7 @@ pub(crate) fn create_error(err: Error) -> Result { with_state(|s| s.error(err)) } -pub(crate) fn push(e: LocExpr, comment: String, f: impl FnOnce() -> T) -> T { +pub(crate) fn push(e: LocExpr, comment: String, f: impl FnOnce() -> Result) -> Result { with_state(|s| s.push(e, comment, f)) } @@ -65,7 +65,7 @@ impl EvaluationState { pub fn add_file( &self, - name: String, + name: PathBuf, code: String, ) -> std::result::Result<(), Box> { self.0.files.borrow_mut().insert( @@ -87,7 +87,7 @@ } pub fn add_parsed_file( &self, - name: String, + name: PathBuf, code: String, parsed: LocExpr, ) -> std::result::Result<(), Box> { @@ -98,14 +98,13 @@ Ok(()) } - pub fn get_source(&self, name: &str) -> String { + pub fn get_source(&self, name: &PathBuf) -> Option { let ro_map = self.0.files.borrow(); - let value = ro_map + ro_map .get(name) - .unwrap_or_else(|| panic!("file not added: {:?}", name)); - value.0.clone() + .map(|value|value.0.clone()) } - pub fn evaluate_file(&self, name: &str) -> Result { + pub fn evaluate_file(&self, name: &PathBuf) -> Result { self.begin_state(); let expr: LocExpr = { let ro_map = self.0.files.borrow(); @@ -135,7 +134,7 @@ let parsed = parse( &code, &ParserSettings { - file_name: "raw.jsonnet".to_owned(), + file_name: PathBuf::from("raw.jsonnet"), loc_data: true, }, ); @@ -154,17 +153,17 @@ use jsonnet_stdlib::STDLIB_STR; if cfg!(feature = "serialized-stdlib") { self.add_parsed_file( - "std.jsonnet".to_owned(), + PathBuf::from("std.jsonnet"), STDLIB_STR.to_owned(), bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode"))) .expect("deserialize stdlib"), ) .unwrap(); } else { - self.add_file("std.jsonnet".to_owned(), STDLIB_STR.to_owned()) + self.add_file(PathBuf::from("std.jsonnet"), STDLIB_STR.to_owned()) .unwrap(); } - let val = self.evaluate_file("std.jsonnet").unwrap(); + let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap(); self.add_global("std".to_owned(), val); self.end_state(); } @@ -183,11 +182,16 @@ Context::new().extend(new_bindings, None, None, None) } - pub fn push(&self, e: LocExpr, comment: String, f: impl FnOnce() -> T) -> T { - self.0 - .stack - .borrow_mut() - .push(StackTraceElement(e, comment)); + pub fn push(&self, e: LocExpr, comment: String, f: impl FnOnce() -> Result) -> Result { + { + let mut stack = self.0.stack.borrow_mut(); + if stack.len() > 5000 { + drop(stack); + return self.error(Error::StackOverflow); + } else { + stack.push(StackTraceElement(e, comment)); + } + } let result = f(); self.0.stack.borrow_mut().pop(); result @@ -217,21 +221,36 @@ use super::Val; use crate::EvaluationState; use jsonnet_parser::*; + use std::path::PathBuf; #[test] fn eval_state_stacktrace() { let state = EvaluationState::default(); - state.push( - loc_expr!(Expr::Num(0.0), true, ("test1.jsonnet".to_owned(), 10, 20)), - "outer".to_owned(), - || { - state.push( - loc_expr!(Expr::Num(0.0), true, ("test2.jsonnet".to_owned(), 30, 40)), - "inner".to_owned(), - || state.print_stack_trace(), - ); - }, - ); + state + .push( + loc_expr!( + Expr::Num(0.0), + true, + (PathBuf::from("test1.jsonnet"), 10, 20) + ), + "outer".to_owned(), + || { + state.push( + loc_expr!( + Expr::Num(0.0), + true, + (PathBuf::from("test2.jsonnet"), 30, 40) + ), + "inner".to_owned(), + || { + state.print_stack_trace(); + Ok(()) + }, + )?; + Ok(()) + }, + ) + .unwrap(); } #[test] @@ -240,7 +259,7 @@ state.add_stdlib(); assert_eq!( state - .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==w")"#) + .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#) .unwrap(), Val::Bool(true) );