From 09a4bd7b68d6444933e3d34a64498ecd021423d6 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 23 Nov 2021 18:45:57 +0000 Subject: [PATCH] feat: breakpoints --- --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -1,5 +1,6 @@ use crate::error::Error::*; use crate::error::Result; +use crate::push_frame; use crate::{throw, Val}; #[derive(PartialEq, Clone, Copy)] @@ -102,12 +103,13 @@ buf.push_str(cur_padding); escape_string_json_buf(&field, buf); buf.push_str(": "); - crate::push( + push_frame( None, || format!("field <{}> manifestification", field.clone()), || { let value = obj.get(field.clone())?.unwrap(); - manifest_json_ex_buf(&value, buf, cur_padding, options) + manifest_json_ex_buf(&value, buf, cur_padding, options)?; + Ok(Val::Null) }, )?; } --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -3,8 +3,8 @@ equals, error::{Error::*, Result}, operator::evaluate_mod_op, - parse_args, primitive_equals, push, throw, with_state, ArrValue, Context, EvaluationState, - FuncVal, IndexableVal, LazyVal, Val, + parse_args, primitive_equals, push_frame, throw, with_state, ArrValue, Context, + EvaluationState, FuncVal, IndexableVal, LazyVal, Val, }; use format::{format_arr, format_obj}; use jrsonnet_gc::Gc; @@ -23,7 +23,7 @@ pub mod sort; pub fn std_format(str: IStr, vals: Val) -> Result { - push( + push_frame( Some(&ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), || format!("std.format of {}", str), || { --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -1,7 +1,4 @@ -use crate::{ - builtin::{format::FormatError, sort::SortError}, - typed::TypeLocError, -}; +use crate::{Val, builtin::{format::FormatError, sort::SortError}, typed::TypeLocError}; use jrsonnet_gc::Trace; use jrsonnet_interner::IStr; use jrsonnet_parser::{BinaryOpType, ExprLocation, UnaryOpType}; --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -2,7 +2,7 @@ builtin::std_slice, error::Error::*, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, - push, throw, with_state, ArrValue, Bindable, Context, ContextCreator, FuncDesc, FuncVal, + push_frame, throw, with_state, ArrValue, Bindable, Context, ContextCreator, FuncDesc, FuncVal, FutureWrapper, LazyBinding, LazyVal, LazyValValue, ObjValue, ObjValueBuilder, ObjectAssertion, Result, Val, }; @@ -464,7 +464,7 @@ if tailstrict { body()? } else { - push(loc, || format!("function <{}> call", f.name()), body)? + push_frame(loc, || format!("function <{}> call", f.name()), body)? } } v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())), @@ -474,7 +474,7 @@ pub fn evaluate_assert(context: Context, assertion: &AssertStmt) -> Result<()> { let value = &assertion.0; let msg = &assertion.1; - let assertion_result = push( + let assertion_result = push_frame( value.1.as_ref(), || "assertion condition".to_owned(), || { @@ -483,7 +483,7 @@ }, )?; if !assertion_result { - push( + push_frame( value.1.as_ref(), || "assertion failure".to_owned(), || { @@ -510,6 +510,7 @@ pub fn evaluate(context: Context, expr: &LocExpr) -> Result { use Expr::*; let LocExpr(expr, loc) = expr; + // let bp = with_state(|s| s.0.stop_at.borrow().clone()); Ok(match &**expr { Literal(LiteralType::This) => { Val::Obj(context.this().clone().ok_or(CantUseSelfOutsideOfObject)?) @@ -532,7 +533,7 @@ 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( + Var(name) => push_frame( loc.as_ref(), || format!("variable <{}>", name), || context.binding(name.clone())?.evaluate(), @@ -541,7 +542,7 @@ match (evaluate(context.clone(), value)?, evaluate(context, index)?) { (Val::Obj(v), Val::Str(s)) => { let sn = s.clone(); - push( + push_frame( loc.as_ref(), || format!("field <{}> access", sn), || { @@ -652,7 +653,7 @@ evaluate_assert(context.clone(), assert)?; evaluate(context, returned)? } - ErrorStmt(e) => push( + ErrorStmt(e) => push_frame( loc.as_ref(), || "error statement".to_owned(), || { @@ -666,7 +667,7 @@ cond_then, cond_else, } => { - if push( + if push_frame( loc.as_ref(), || "if condition".to_owned(), || evaluate(context.clone(), &cond.0)?.try_cast_bool("in if condition"), @@ -708,7 +709,7 @@ .0; let mut import_location = tmp.to_path_buf(); import_location.pop(); - push( + push_frame( loc.as_ref(), || format!("import {:?}", path), || with_state(|s| s.import_file(&import_location, path)), --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -243,7 +243,7 @@ ($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [ $($id: expr, $name: ident: $ty: expr $(=>$match: path)?);+ $(;)? ], $handler:block) => {{ - use $crate::{error::Error::*, throw, evaluate, push_stack_frame, typed::CheckType}; + use $crate::{error::Error::*, throw, evaluate, push_frame, typed::CheckType}; let args = $args; if args.unnamed.len() + args.named.len() > $total_args { @@ -263,7 +263,7 @@ } else { &$args.unnamed[$id] }; - let $name = push_stack_frame(None, || format!("evaluating argument"), || { + let $name = push_frame(None, || format!("evaluating argument"), || { let value = evaluate($ctx.clone(), &$name)?; $ty.check(&value)?; Ok(value) --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -40,7 +40,7 @@ path::{Path, PathBuf}, rc::Rc, }; -use trace::{offset_to_location, CodeLocation, CompactFormat, TraceFormat}; +use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat}; pub use val::*; pub trait Bindable: Trace { @@ -109,6 +109,10 @@ struct EvaluationData { /// Used for stack overflow detection, stacktrace is populated on unwind stack_depth: usize, + /// Updated every time stack entry is popt + stack_generation: usize, + + breakpoints: Breakpoints, /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces files: HashMap, FileData>, str_files: HashMap, IStr>, @@ -119,6 +123,38 @@ parsed: LocExpr, evaluated: Option, } + +pub struct Breakpoint { + loc: ExprLocation, + collected: RefCell>)>>, +} +#[derive(Default)] +struct Breakpoints(Vec>); +impl Breakpoints { + fn insert( + &self, + stack_depth: usize, + stack_generation: usize, + loc: &ExprLocation, + result: Result, + ) -> Result { + if self.0.is_empty() { + return result; + } + for item in self.0.iter() { + if item.loc.belongs_to(loc) { + let mut collected = item.collected.borrow_mut(); + let (depth, vals) = collected.entry(stack_generation).or_default(); + if stack_depth > *depth { + vals.clear(); + } + vals.push(result.clone()); + } + } + result + } +} + #[derive(Default)] pub struct EvaluationStateInternals { /// Internal state @@ -135,7 +171,7 @@ pub(crate) fn with_state(f: impl FnOnce(&EvaluationState) -> T) -> T { EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap())) } -pub(crate) fn push( +pub(crate) fn push_frame( e: Option<&ExprLocation>, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, @@ -143,12 +179,12 @@ with_state(|s| s.push(e, frame_desc, f)) } -pub fn push_stack_frame( +pub(crate) fn push_val_frame( e: Option<&ExprLocation>, frame_desc: impl FnOnce() -> String, - f: impl FnOnce() -> Result, -) -> Result { - push(e, frame_desc, f) + f: impl FnOnce() -> Result, +) -> Result { + with_state(|s| s.push(e, frame_desc, f)) } /// Maintains stack trace and import resolution @@ -178,6 +214,15 @@ Ok(()) } + pub fn reset_evaluation_state(&self, name: &Path) { + self.data_mut() + .files + .get_mut(name) + .unwrap() + .evaluated + .take(); + } + /// Adds file by source code and parsed expr pub fn add_parsed_file( &self, @@ -203,8 +248,15 @@ pub fn map_source_locations(&self, file: &Path, locs: &[usize]) -> Vec { offset_to_location(&self.get_source(file).unwrap(), locs) } - - pub(crate) fn import_file(&self, from: &Path, path: &Path) -> Result { + pub fn map_from_source_location( + &self, + file: &Path, + line: usize, + column: usize, + ) -> Option { + location_to_offset(&self.get_source(file).unwrap(), line, column) + } + pub fn import_file(&self, from: &Path, path: &Path) -> Result { let file_path = self.resolve_file(from, path)?; { let data = self.data(); @@ -297,7 +349,54 @@ } } let result = f(); - self.data_mut().stack_depth -= 1; + { + let mut data = self.data_mut(); + data.stack_depth -= 1; + data.stack_generation += 1; + // if let Some(e) = e { + // result = + // data.breakpoints + // .insert(data.stack_depth, data.stack_generation, &e, result) + // } + } + if let Err(mut err) = result { + err.trace_mut().0.push(StackTraceElement { + location: e.cloned(), + desc: frame_desc(), + }); + return Err(err); + } + result + } + /// Executes code creating a new stack frame + pub fn push_val( + &self, + e: Option<&ExprLocation>, + frame_desc: impl FnOnce() -> String, + f: impl FnOnce() -> Result, + ) -> Result { + { + let mut data = self.data_mut(); + let stack_depth = &mut data.stack_depth; + if *stack_depth > self.max_stack() { + // Error creation uses data, so i drop guard here + drop(data); + throw!(StackOverflow); + } else { + *stack_depth += 1; + } + } + let mut result = f(); + { + let mut data = self.data_mut(); + data.stack_depth -= 1; + data.stack_generation += 1; + if let Some(e) = e { + result = + data.breakpoints + .insert(data.stack_depth, data.stack_generation, &e, result) + } + } if let Err(mut err) = result { err.trace_mut().0.push(StackTraceElement { location: e.cloned(), @@ -322,7 +421,26 @@ result }) } + pub fn run_in_state_with_breakpoint( + &self, + bp: Rc, + f: impl FnOnce() -> Result<()>, + ) -> Result<()> { + { + let mut data = self.data_mut(); + data.breakpoints.0.push(bp); + } + + let result = self.run_in_state(f); + { + let mut data = self.data_mut(); + data.breakpoints.0.pop(); + } + + result + } + pub fn stringify_err(&self, e: &LocError) -> String { let mut out = String::new(); self.settings() @@ -346,7 +464,7 @@ pub fn with_tla(&self, val: Val) -> Result { self.run_in_state(|| { Ok(match val { - Val::Func(func) => push( + Val::Func(func) => push_frame( None, || "during TLA call".to_owned(), || { @@ -514,7 +632,7 @@ || "inner".to_owned(), || Err(RuntimeError("".into()).into()), )?; - Ok(()) + Ok(Val::Null) }, ) .unwrap(); --- a/crates/jrsonnet-evaluator/src/trace/location.rs +++ b/crates/jrsonnet-evaluator/src/trace/location.rs @@ -9,6 +9,18 @@ pub line_end_offset: usize, } +pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option { + let mut offset = 0; + while line > 1 { + let pos = file.find('\n')?; + offset += pos + 1; + file = &file[pos + 1..]; + line -= 1; + } + offset += column - 1; + Some(offset) +} + pub fn offset_to_location(file: &str, offsets: &[usize]) -> Vec { if offsets.is_empty() { return vec![]; --- a/crates/jrsonnet-evaluator/src/typed.rs +++ b/crates/jrsonnet-evaluator/src/typed.rs @@ -2,7 +2,7 @@ use crate::{ error::{Error, LocError, Result}, - push, Val, + push_frame, Val, }; use jrsonnet_gc::Trace; use jrsonnet_parser::ExprLocation; @@ -103,7 +103,7 @@ path: impl Fn() -> ValuePathItem, item: impl Fn() -> Result<()>, ) -> Result<()> { - push(location, error_reason, || match item() { + push_frame(location, error_reason, || match item() { Ok(_) => Ok(()), Err(mut e) => { if let Error::TypeError(e) = &mut e.error_mut() { --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -356,6 +356,11 @@ #[derive(Clone, PartialEq, Trace)] #[trivially_drop] pub struct ExprLocation(pub Rc, pub usize, pub usize); +impl ExprLocation { + pub fn belongs_to(&self, other: &ExprLocation) -> bool { + other.0 == self.0 && other.1 <= self.1 && other.2 >= self.2 + } +} impl Debug for ExprLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -- gitstuff