git.delta.rocks / jrsonnet / refs/commits / 284612bfbf1c

difftreelog

refactor! implement stack size limit using thread local

Yaroslav Bolyukin2022-10-25parent: #7b8b823.patch.diff
in: master

18 files changed

modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
8585
86/// Set the maximum stack depth.86/// Set the maximum stack depth.
87#[no_mangle]87#[no_mangle]
88pub extern "C" fn jsonnet_max_stack(vm: &State, v: c_uint) {88pub extern "C" fn jsonnet_max_stack(_vm: &State, _v: c_uint) {
89 vm.settings_mut().max_stack = v as usize;89 todo!()
90}90}
9191
92/// Set the number of objects required before a garbage collection cycle is allowed.92/// Set the number of objects required before a garbage collection cycle is allowed.
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
55
6use clap::{AppSettings, IntoApp, Parser};6use clap::{AppSettings, IntoApp, Parser};
7use clap_complete::Shell;7use clap_complete::Shell;
8use jrsonnet_cli::{ConfigureState, GcOpts, GeneralOpts, ManifestOpts, OutputOpts};8use jrsonnet_cli::{ConfigureState, GeneralOpts, ManifestOpts, OutputOpts};
9use jrsonnet_evaluator::{error::LocError, State};9use jrsonnet_evaluator::{error::LocError, State};
1010
11#[cfg(feature = "mimalloc")]11#[cfg(feature = "mimalloc")]
60 output: OutputOpts,60 output: OutputOpts,
61 #[clap(flatten)]61 #[clap(flatten)]
62 debug: DebugOpts,62 debug: DebugOpts,
63 #[clap(flatten)]
64 gc: GcOpts,
65}63}
6664
67fn main() {65fn main() {
113}111}
114112
115fn main_catch(opts: Opts) -> bool {113fn main_catch(opts: Opts) -> bool {
116 let _printer = opts.gc.stats_printer();
117 let s = State::default();114 let s = State::default();
118 if let Err(e) = main_real(&s, opts) {115 if let Err(e) = main_real(&s, opts) {
119 if let Error::Evaluation(e) = e {116 if let Error::Evaluation(e) = e {
127}124}
128125
129fn main_real(s: &State, opts: Opts) -> Result<(), Error> {126fn main_real(s: &State, opts: Opts) -> Result<(), Error> {
130 opts.general.configure(s)?;127 let _guards = opts.general.configure(s)?;
131 opts.manifest.configure(s)?;128 opts.manifest.configure(s)?;
132129
133 let input = opts.input.input.ok_or(Error::MissingInputArgument)?;130 let input = opts.input.input.ok_or(Error::MissingInputArgument)?;
modifiedcrates/jrsonnet-cli/src/lib.rsdiffbeforeafterboth
3mod tla;3mod tla;
4mod trace;4mod trace;
55
6use std::{env, path::PathBuf};6use std::{env, marker::PhantomData, path::PathBuf};
77
8use clap::Parser;8use clap::Parser;
9use jrsonnet_evaluator::{error::Result, FileImportResolver, State};9use jrsonnet_evaluator::{
10 error::Result, stack::StackDepthLimitOverrideGuard, FileImportResolver, State,
11};
10use jrsonnet_gcmodule::with_thread_object_space;12use jrsonnet_gcmodule::with_thread_object_space;
11pub use manifest::*;13pub use manifest::*;
12pub use stdlib::*;14pub use stdlib::*;
13pub use tla::*;15pub use tla::*;
14pub use trace::*;16pub use trace::*;
1517
16pub trait ConfigureState {18pub trait ConfigureState {
19 type Guards;
20
17 fn configure(&self, s: &State) -> Result<()>;21 fn configure(&self, s: &State) -> Result<Self::Guards>;
18}22}
1923
20#[derive(Parser)]24#[derive(Parser)]
44 jpath: Vec<PathBuf>,48 jpath: Vec<PathBuf>,
45}49}
46impl ConfigureState for MiscOpts {50impl ConfigureState for MiscOpts {
51 type Guards = StackDepthLimitOverrideGuard;
47 fn configure(&self, s: &State) -> Result<()> {52 fn configure(&self, s: &State) -> Result<Self::Guards> {
48 let mut library_paths = self.jpath.clone();53 let mut library_paths = self.jpath.clone();
49 library_paths.reverse();54 library_paths.reverse();
50 if let Some(path) = env::var_os("JSONNET_PATH") {55 if let Some(path) = env::var_os("JSONNET_PATH") {
5358
54 s.set_import_resolver(Box::new(FileImportResolver::new(library_paths)));59 s.set_import_resolver(Box::new(FileImportResolver::new(library_paths)));
5560
56 s.set_max_stack(self.max_stack);61 let _depth_limit = jrsonnet_evaluator::stack::limit_stack_depth(self.max_stack);
57 Ok(())62 Ok(_depth_limit)
58 }63 }
59}64}
6065
73 #[clap(flatten)]78 #[clap(flatten)]
74 trace: TraceOpts,79 trace: TraceOpts,
80
81 #[clap(flatten)]
82 gc: GcOpts,
75}83}
7684
77impl ConfigureState for GeneralOpts {85impl ConfigureState for GeneralOpts {
86 type Guards = (
87 <MiscOpts as ConfigureState>::Guards,
88 <GcOpts as ConfigureState>::Guards,
89 );
78 fn configure(&self, s: &State) -> Result<()> {90 fn configure(&self, s: &State) -> Result<Self::Guards> {
79 // Configure trace first, because tla-code/ext-code can throw91 // Configure trace first, because tla-code/ext-code can throw
80 self.trace.configure(s)?;92 self.trace.configure(s)?;
81 self.misc.configure(s)?;93 let misc_guards = self.misc.configure(s)?;
82 self.tla.configure(s)?;94 self.tla.configure(s)?;
83 self.std.configure(s)?;95 self.std.configure(s)?;
96 let gc_guards = self.gc.configure(s)?;
84 Ok(())97 Ok((misc_guards, gc_guards))
85 }98 }
86}99}
87100
100 #[clap(long)]113 #[clap(long)]
101 gc_collect_before_printing_stats: bool,114 gc_collect_before_printing_stats: bool,
102}115}
103impl GcOpts {116impl ConfigureState for GcOpts {
104 pub fn stats_printer(&self) -> (Option<GcStatsPrinter>, Option<LeakSpace>) {117 type Guards = (Option<GcStatsPrinter>, Option<LeakSpace>);
118
119 fn configure(&self, _s: &State) -> Result<Self::Guards> {
105 // Constructed structs have side-effects in Drop impl120 // Constructed structs have side-effects in Drop impl
106 #[allow(clippy::unnecessary_lazy_evaluations)]121 #[allow(clippy::unnecessary_lazy_evaluations)]
107 (122 Ok((
108 self.gc_print_stats.then(|| GcStatsPrinter {123 self.gc_print_stats.then(|| GcStatsPrinter {
109 collect_before_printing_stats: self.gc_collect_before_printing_stats,124 collect_before_printing_stats: self.gc_collect_before_printing_stats,
110 }),125 }),
111 (!self.gc_collect_on_exit).then(|| LeakSpace {}),126 (!self.gc_collect_on_exit).then(|| LeakSpace(PhantomData)),
112 )127 ))
113 }128 }
114}129}
115130
116pub struct LeakSpace {}131pub struct LeakSpace(PhantomData<()>);
117132
118impl Drop for LeakSpace {133impl Drop for LeakSpace {
119 fn drop(&mut self) {134 fn drop(&mut self) {
modifiedcrates/jrsonnet-cli/src/manifest.rsdiffbeforeafterboth
47 exp_preserve_order: bool,47 exp_preserve_order: bool,
48}48}
49impl ConfigureState for ManifestOpts {49impl ConfigureState for ManifestOpts {
50 type Guards = ();
50 fn configure(&self, s: &State) -> Result<()> {51 fn configure(&self, s: &State) -> Result<()> {
51 if self.string {52 if self.string {
52 s.set_manifest_format(ManifestFormat::String);53 s.set_manifest_format(ManifestFormat::String);
modifiedcrates/jrsonnet-cli/src/stdlib.rsdiffbeforeafterboth
106 ext_code_file: Vec<ExtFile>,106 ext_code_file: Vec<ExtFile>,
107}107}
108impl ConfigureState for StdOpts {108impl ConfigureState for StdOpts {
109 type Guards = ();
109 fn configure(&self, s: &State) -> Result<()> {110 fn configure(&self, s: &State) -> Result<()> {
110 if self.no_stdlib {111 if self.no_stdlib {
111 return Ok(());112 return Ok(());
modifiedcrates/jrsonnet-cli/src/tla.rsdiffbeforeafterboth
47 tla_code_file: Vec<ExtFile>,47 tla_code_file: Vec<ExtFile>,
48}48}
49impl ConfigureState for TLAOpts {49impl ConfigureState for TLAOpts {
50 type Guards = ();
50 fn configure(&self, s: &State) -> Result<()> {51 fn configure(&self, s: &State) -> Result<()> {
51 for tla in self.tla_str.iter() {52 for tla in self.tla_str.iter() {
52 s.add_tla_str((&tla.name as &str).into(), (&tla.value as &str).into());53 s.add_tla_str((&tla.name as &str).into(), (&tla.value as &str).into());
modifiedcrates/jrsonnet-cli/src/trace.rsdiffbeforeafterboth
41 max_trace: usize,41 max_trace: usize,
42}42}
43impl ConfigureState for TraceOpts {43impl ConfigureState for TraceOpts {
44 type Guards = ();
44 fn configure(&self, s: &State) -> Result<()> {45 fn configure(&self, s: &State) -> Result<()> {
45 let resolver = PathResolver::new_cwd_fallback();46 let resolver = PathResolver::new_cwd_fallback();
46 match self47 match self
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
20exp-preserve-order = []20exp-preserve-order = []
21# Implements field destructuring21# Implements field destructuring
22exp-destruct = ["jrsonnet-parser/exp-destruct"]22exp-destruct = ["jrsonnet-parser/exp-destruct"]
23# Provide Typed for conversions to/from serde_json::Value type23# Improves performance, and implements some useful things using nightly-only features
24serde_json = ["dep:serde_json"]24nightly = []
2525
26[dependencies]26[dependencies]
27jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }27jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
22
3use jrsonnet_gcmodule::Trace;3use jrsonnet_gcmodule::Trace;
4use jrsonnet_interner::IStr;4use jrsonnet_interner::IStr;
5use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, SourcePath, UnaryOpType};5use jrsonnet_parser::{BinaryOpType, ExprLocation, LocExpr, Source, SourcePath, UnaryOpType};
6use jrsonnet_types::ValType;6use jrsonnet_types::ValType;
7use thiserror::Error;7use thiserror::Error;
88
9use crate::{stdlib::format::FormatError, typed::TypeLocError};9use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError};
1010
11fn format_found(list: &[IStr], what: &str) -> String {11fn format_found(list: &[IStr], what: &str) -> String {
12 if list.is_empty() {12 if list.is_empty() {
262 }262 }
263}263}
264
265pub trait ErrorSource {
266 fn to_location(self) -> Option<ExprLocation>;
267}
268impl ErrorSource for &LocExpr {
269 fn to_location(self) -> Option<ExprLocation> {
270 Some(self.1.clone())
271 }
272}
273impl ErrorSource for &ExprLocation {
274 fn to_location(self) -> Option<ExprLocation> {
275 Some(self.clone())
276 }
277}
278impl ErrorSource for CallLocation<'_> {
279 fn to_location(self) -> Option<ExprLocation> {
280 self.0.cloned()
281 }
282}
264283
265pub type Result<V, E = LocError> = std::result::Result<V, E>;284pub type Result<V, E = LocError> = std::result::Result<V, E>;
285pub trait ResultExt: Sized {
286 #[must_use]
287 fn with_description<O: Into<String>>(self, msg: impl FnOnce() -> O) -> Self;
288 #[must_use]
289 fn description(self, msg: &str) -> Self {
290 self.with_description(|| msg)
291 }
292
293 #[must_use]
294 fn with_description_src<O: Into<String>>(
295 self,
296 src: impl ErrorSource,
297 msg: impl FnOnce() -> O,
298 ) -> Self;
299 #[must_use]
300 fn description_src(self, src: impl ErrorSource, msg: &str) -> Self {
301 self.with_description_src(src, || msg)
302 }
303}
304impl<T> ResultExt for Result<T, LocError> {
305 fn with_description<O: Into<String>>(mut self, msg: impl FnOnce() -> O) -> Self {
306 if let Err(e) = &mut self {
307 let trace = e.trace_mut();
308 trace.0.push(StackTraceElement {
309 location: None,
310 desc: msg().into(),
311 });
312 }
313 self
314 }
315
316 fn with_description_src<O: Into<String>>(
317 mut self,
318 src: impl ErrorSource,
319 msg: impl FnOnce() -> O,
320 ) -> Self {
321 if let Err(e) = &mut self {
322 let trace = e.trace_mut();
323 trace.0.push(StackTraceElement {
324 location: src.to_location(),
325 desc: msg().into(),
326 });
327 }
328 self
329 }
330}
266331
267#[macro_export]332#[macro_export]
268macro_rules! throw {333macro_rules! throw {
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
34pub fn evaluate_field_name(s: State, ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {34pub fn evaluate_field_name(s: State, ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {
35 Ok(match field_name {35 Ok(match field_name {
36 FieldName::Fixed(n) => Some(n.clone()),36 FieldName::Fixed(n) => Some(n.clone()),
37 FieldName::Dyn(expr) => s.push(37 FieldName::Dyn(expr) => State::push(
38 CallLocation::new(&expr.1),38 CallLocation::new(&expr.1),
39 || "evaluating field name".to_string(),39 || "evaluating field name".to_string(),
40 || {40 || {
185 .with_visibility(*visibility)185 .with_visibility(*visibility)
186 .with_location(value.1.clone())186 .with_location(value.1.clone())
187 .bindable(187 .bindable(tb!(UnboundValue {
188 s.clone(),
189 tb!(UnboundValue {
190 uctx: uctx.clone(),188 uctx: uctx.clone(),
191 value: value.clone(),189 value: value.clone(),
192 name: name.clone()190 name: name.clone()
193 }),191 }))?;
194 )?;
195 }192 }
196 Member::Field(FieldMember {193 Member::Field(FieldMember {
234 .hide()231 .hide()
235 .with_location(value.1.clone())232 .with_location(value.1.clone())
236 .bindable(233 .bindable(tb!(UnboundMethod {
237 s.clone(),
238 tb!(UnboundMethod {
239 uctx: uctx.clone(),234 uctx: uctx.clone(),
240 value: value.clone(),235 value: value.clone(),
241 params: params.clone(),236 params: params.clone(),
242 name: name.clone()237 name: name.clone()
243 }),238 }))?;
244 )?;
245 }239 }
246 Member::BindStmt(_) => {}240 Member::BindStmt(_) => {}
325 .with_location(obj.value.1.clone())319 .with_location(obj.value.1.clone())
326 .with_add(obj.plus)320 .with_add(obj.plus)
327 .bindable(321 .bindable(tb!(UnboundValue {
328 s.clone(),
329 tb!(UnboundValue {
330 uctx,322 uctx,
331 value: obj.value.clone(),323 value: obj.value.clone(),
332 }),324 }))?;
333 )?;
334 }325 }
335 v => throw!(FieldMustBeStringGot(v.value_type())),326 v => throw!(FieldMustBeStringGot(v.value_type())),
364 if tailstrict {355 if tailstrict {
365 body()?356 body()?
366 } else {357 } else {
367 s.push(loc, || format!("function <{}> call", f.name()), body)?358 State::push(loc, || format!("function <{}> call", f.name()), body)?
368 }359 }
369 }360 }
370 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),361 v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),
374pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {365pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {
375 let value = &assertion.0;366 let value = &assertion.0;
376 let msg = &assertion.1;367 let msg = &assertion.1;
377 let assertion_result = s.push(368 let assertion_result = State::push(
378 CallLocation::new(&value.1),369 CallLocation::new(&value.1),
379 || "assertion condition".to_owned(),370 || "assertion condition".to_owned(),
380 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),371 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),
381 )?;372 )?;
382 if !assertion_result {373 if !assertion_result {
383 s.push(374 State::push(
384 CallLocation::new(&value.1),375 CallLocation::new(&value.1),
385 || "assertion failure".to_owned(),376 || "assertion failure".to_owned(),
386 || {377 || {
432 Num(v) => Val::new_checked_num(*v)?,423 Num(v) => Val::new_checked_num(*v)?,
433 BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,424 BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,
434 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,425 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,
435 Var(name) => s.push(426 Var(name) => State::push(
436 CallLocation::new(loc),427 CallLocation::new(loc),
437 || format!("variable <{name}> access"),428 || format!("variable <{name}> access"),
438 || ctx.binding(name.clone())?.evaluate(s.clone()),429 || ctx.binding(name.clone())?.evaluate(s.clone()),
442 evaluate(s.clone(), ctx.clone(), value)?,433 evaluate(s.clone(), ctx.clone(), value)?,
443 evaluate(s.clone(), ctx, index)?,434 evaluate(s.clone(), ctx, index)?,
444 ) {435 ) {
445 (Val::Obj(v), Val::Str(key)) => s.push(436 (Val::Obj(v), Val::Str(key)) => State::push(
446 CallLocation::new(loc),437 CallLocation::new(loc),
447 || format!("field <{key}> access"),438 || format!("field <{key}> access"),
448 || match v.get(s.clone(), key.clone()) {439 || match v.get(s.clone(), key.clone()) {
571 evaluate_assert(s.clone(), ctx.clone(), assert)?;562 evaluate_assert(s.clone(), ctx.clone(), assert)?;
572 evaluate(s, ctx, returned)?563 evaluate(s, ctx, returned)?
573 }564 }
574 ErrorStmt(e) => s.push(565 ErrorStmt(e) => State::push(
575 CallLocation::new(loc),566 CallLocation::new(loc),
576 || "error statement".to_owned(),567 || "error statement".to_owned(),
577 || {568 || {
585 cond_then,576 cond_then,
586 cond_else,577 cond_else,
587 } => {578 } => {
588 if s.push(579 if State::push(
589 CallLocation::new(loc),580 CallLocation::new(loc),
590 || "if condition".to_owned(),581 || "if condition".to_owned(),
591 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),582 || bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),
607 desc: &'static str,598 desc: &'static str,
608 ) -> Result<Option<T>> {599 ) -> Result<Option<T>> {
609 if let Some(value) = expr {600 if let Some(value) = expr {
610 Ok(Some(s.push(601 Ok(Some(State::push(
611 loc,602 loc,
612 || format!("slice {desc}"),603 || format!("slice {desc}"),
613 || T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),604 || T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),
630 let tmp = loc.clone().0;621 let tmp = loc.clone().0;
631 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;622 let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;
632 match i {623 match i {
633 Import(_) => s.push(624 Import(_) => State::push(
634 CallLocation::new(loc),625 CallLocation::new(loc),
635 || format!("import {:?}", path.clone()),626 || format!("import {:?}", path.clone()),
636 || s.import_resolved(resolved_path),627 || s.import_resolved(resolved_path),
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
1//! jsonnet interpreter implementation1//! jsonnet interpreter implementation
22#![cfg_attr(feature = "nightly", feature(thread_local))]
3#![deny(unsafe_op_in_unsafe_fn)]3#![deny(unsafe_op_in_unsafe_fn)]
4#![warn(4#![warn(
5 clippy::all,5 clippy::all,
51mod integrations;51mod integrations;
52mod map;52mod map;
53mod obj;53mod obj;
54pub mod stack;
54pub mod stdlib;55pub mod stdlib;
55pub mod trace;56pub mod trace;
56pub mod typed;57pub mod typed;
6768
68pub use ctx::*;69pub use ctx::*;
69pub use dynamic::*;70pub use dynamic::*;
70use error::{Error::*, LocError, Result, StackTraceElement};71use error::{Error::*, LocError, Result, ResultExt};
71pub use evaluate::*;72pub use evaluate::*;
72use function::{CallLocation, TlaArg};73use function::{CallLocation, TlaArg};
73use gc::{GcHashMap, TraceBox};74use gc::{GcHashMap, TraceBox};
78pub use jrsonnet_parser as parser;79pub use jrsonnet_parser as parser;
79use jrsonnet_parser::*;80use jrsonnet_parser::*;
80pub use obj::*;81pub use obj::*;
82use stack::check_depth;
81use trace::{CompactFormat, TraceFormat};83use trace::{CompactFormat, TraceFormat};
82pub use val::{ManifestFormat, Thunk, Val};84pub use val::{ManifestFormat, Thunk, Val};
8385
143145
144/// Dynamically reconfigurable evaluation settings146/// Dynamically reconfigurable evaluation settings
145pub struct EvaluationSettings {147pub struct EvaluationSettings {
146 /// Limits recursion by limiting the number of stack frames
147 pub max_stack: usize,
148 /// Limits amount of stack trace items preserved148 /// Limits amount of stack trace items preserved
149 pub max_trace: usize,149 pub max_trace: usize,
150 /// TLA vars150 /// TLA vars
162impl Default for EvaluationSettings {162impl Default for EvaluationSettings {
163 fn default() -> Self {163 fn default() -> Self {
164 Self {164 Self {
165 max_stack: 200,
166 max_trace: 20,165 max_trace: 20,
167 context_initializer: Box::new(DummyContextInitializer),166 context_initializer: Box::new(DummyContextInitializer),
168 tla_vars: HashMap::default(),167 tla_vars: HashMap::default(),
180 }179 }
181}180}
182181
183#[derive(Default)]
184struct EvaluationData {
185 /// Used for stack overflow detection, stacktrace is populated on unwind
186 stack_depth: usize,
187 /// Updated every time stack entry is popt
188 stack_generation: usize,
189
190 breakpoints: Breakpoints,
191
192 /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces
193 files: GcHashMap<SourcePath, FileData>,
194}
195struct FileData {182struct FileData {
196 string: Option<IStr>,183 string: Option<IStr>,
197 bytes: Option<IBytes>,184 bytes: Option<IBytes>,
221 }208 }
222}209}
223
224#[allow(clippy::type_complexity)]
225pub struct Breakpoint {
226 loc: ExprLocation,
227 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,
228}
229#[derive(Default)]
230struct Breakpoints(Vec<Rc<Breakpoint>>);
231impl Breakpoints {
232 fn insert(
233 &self,
234 stack_depth: usize,
235 stack_generation: usize,
236 loc: &ExprLocation,
237 result: Result<Val>,
238 ) -> Result<Val> {
239 if self.0.is_empty() {
240 return result;
241 }
242 for item in &self.0 {
243 if item.loc.belongs_to(loc) {
244 let mut collected = item.collected.borrow_mut();
245 let (depth, vals) = collected.entry(stack_generation).or_default();
246 if stack_depth > *depth {
247 vals.clear();
248 }
249 vals.push(result.clone());
250 }
251 }
252 result
253 }
254}
255210
256#[derive(Default)]211#[derive(Default)]
257pub struct EvaluationStateInternals {212pub struct EvaluationStateInternals {
258 /// Internal state213 /// Internal state
259 data: RefCell<EvaluationData>,214 file_cache: RefCell<GcHashMap<SourcePath, FileData>>,
260 /// Settings, safe to change at runtime215 /// Settings, safe to change at runtime
261 settings: RefCell<EvaluationSettings>,216 settings: RefCell<EvaluationSettings>,
262}217}
268impl State {223impl State {
269 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise224 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
270 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {225 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {
271 let mut data = self.data_mut();226 let mut file_cache = self.file_cache();
272 let mut file = data.files.raw_entry_mut().from_key(&path);227 let mut file = file_cache.raw_entry_mut().from_key(&path);
273228
274 let file = match file {229 let file = match file {
275 RawEntryMut::Occupied(ref mut d) => d.get_mut(),230 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
303 }258 }
304 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise259 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
305 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {260 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {
306 let mut data = self.data_mut();261 let mut file_cache = self.file_cache();
307 let mut file = data.files.raw_entry_mut().from_key(&path);262 let mut file = file_cache.raw_entry_mut().from_key(&path);
308263
309 let file = match file {264 let file = match file {
310 RawEntryMut::Occupied(ref mut d) => d.get_mut(),265 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
330 }285 }
331 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise286 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
332 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {287 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {
333 let mut data = self.data_mut();288 let mut file_cache = self.file_cache();
334 let mut file = data.files.raw_entry_mut().from_key(&path);289 let mut file = file_cache.raw_entry_mut().from_key(&path);
335290
336 let file = match file {291 let file = match file {
337 RawEntryMut::Occupied(ref mut d) => d.get_mut(),292 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
383 throw!(InfiniteRecursionDetected)338 throw!(InfiniteRecursionDetected)
384 }339 }
385 file.evaluating = true;340 file.evaluating = true;
386 // Dropping file here, as it borrows data, which may be used in evaluation341 // Dropping file cache guard here, as evaluation may use this map too
387 drop(data);342 drop(file_cache);
388 let res = evaluate(343 let res = evaluate(
389 self.clone(),344 self.clone(),
390 self.create_default_context(file_name),345 self.create_default_context(file_name),
391 &parsed,346 &parsed,
392 );347 );
393348
394 let mut data = self.data_mut();349 let mut file_cache = self.file_cache();
395 let mut file = data.files.raw_entry_mut().from_key(&path);350 let mut file = file_cache.raw_entry_mut().from_key(&path);
396351
397 let file = match file {352 let file = match file {
398 RawEntryMut::Occupied(ref mut d) => d.get_mut(),353 RawEntryMut::Occupied(ref mut d) => d.get_mut(),
426381
427 /// Executes code creating a new stack frame382 /// Executes code creating a new stack frame
428 pub fn push<T>(383 pub fn push<T>(
429 &self,
430 e: CallLocation<'_>,384 e: CallLocation<'_>,
431 frame_desc: impl FnOnce() -> String,385 frame_desc: impl FnOnce() -> String,
432 f: impl FnOnce() -> Result<T>,386 f: impl FnOnce() -> Result<T>,
433 ) -> Result<T> {387 ) -> Result<T> {
434 {
435 let mut data = self.data_mut();388 let _guard = check_depth()?;
436 let stack_depth = &mut data.stack_depth;389
437 if *stack_depth > self.max_stack() {
438 // Error creation uses data, so i drop guard here
439 drop(data);
440 throw!(StackOverflow);
441 }
442 *stack_depth += 1;
443 }
444 let result = f();390 f().with_description_src(e, frame_desc)
445 {
446 let mut data = self.data_mut();
447 data.stack_depth -= 1;
448 data.stack_generation += 1;
449 }
450 if let Err(mut err) = result {
451 err.trace_mut().0.push(StackTraceElement {
452 location: e.0.cloned(),
453 desc: frame_desc(),
454 });
455 return Err(err);
456 }
457 result
458 }391 }
459392
460 /// Executes code creating a new stack frame393 /// Executes code creating a new stack frame
464 frame_desc: impl FnOnce() -> String,397 frame_desc: impl FnOnce() -> String,
465 f: impl FnOnce() -> Result<Val>,398 f: impl FnOnce() -> Result<Val>,
466 ) -> Result<Val> {399 ) -> Result<Val> {
467 {
468 let mut data = self.data_mut();400 let _guard = check_depth()?;
469 let stack_depth = &mut data.stack_depth;401
470 if *stack_depth > self.max_stack() {
471 // Error creation uses data, so i drop guard here
472 drop(data);
473 throw!(StackOverflow);
474 }
475 *stack_depth += 1;
476 }
477 let mut result = f();402 f().with_description_src(e, frame_desc)
478 {
479 let mut data = self.data_mut();
480 data.stack_depth -= 1;
481 data.stack_generation += 1;
482 result = data
483 .breakpoints
484 .insert(data.stack_depth, data.stack_generation, e, result);
485 }
486 if let Err(mut err) = result {
487 err.trace_mut().0.push(StackTraceElement {
488 location: Some(e.clone()),
489 desc: frame_desc(),
490 });
491 return Err(err);
492 }
493 result
494 }403 }
495 /// Executes code creating a new stack frame404 /// Executes code creating a new stack frame
496 pub fn push_description<T>(405 pub fn push_description<T>(
497 &self,
498 frame_desc: impl FnOnce() -> String,406 frame_desc: impl FnOnce() -> String,
499 f: impl FnOnce() -> Result<T>,407 f: impl FnOnce() -> Result<T>,
500 ) -> Result<T> {408 ) -> Result<T> {
501 {
502 let mut data = self.data_mut();409 let _guard = check_depth()?;
503 let stack_depth = &mut data.stack_depth;410
504 if *stack_depth > self.max_stack() {
505 // Error creation uses data, so i drop guard here
506 drop(data);
507 throw!(StackOverflow);
508 }
509 *stack_depth += 1;
510 }
511 let result = f();411 f().with_description(frame_desc)
512 {
513 let mut data = self.data_mut();
514 data.stack_depth -= 1;
515 data.stack_generation += 1;
516 }
517 if let Err(mut err) = result {
518 err.trace_mut().0.push(StackTraceElement {
519 location: None,
520 desc: frame_desc(),
521 });
522 return Err(err);
523 }
524 result
525 }412 }
526413
527 /// # Panics414 /// # Panics
536 }423 }
537424
538 pub fn manifest(&self, val: Val) -> Result<IStr> {425 pub fn manifest(&self, val: Val) -> Result<IStr> {
539 self.push_description(426 Self::push_description(
540 || "manifestification".to_string(),427 || "manifestification".to_string(),
541 || val.manifest(self.clone(), &self.manifest_format()),428 || val.manifest(self.clone(), &self.manifest_format()),
542 )429 )
551 /// If passed value is function then call with set TLA438 /// If passed value is function then call with set TLA
552 pub fn with_tla(&self, val: Val) -> Result<Val> {439 pub fn with_tla(&self, val: Val) -> Result<Val> {
553 Ok(match val {440 Ok(match val {
554 Val::Func(func) => self.push_description(441 Val::Func(func) => State::push_description(
555 || "during TLA call".to_owned(),442 || "during TLA call".to_owned(),
556 || {443 || {
557 func.evaluate(444 func.evaluate(
573460
574/// Internals461/// Internals
575impl State {462impl State {
576 fn data_mut(&self) -> RefMut<'_, EvaluationData> {463 fn file_cache(&self) -> RefMut<'_, GcHashMap<SourcePath, FileData>> {
577 self.0.data.borrow_mut()464 self.0.file_cache.borrow_mut()
578 }465 }
579 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {466 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {
580 self.0.settings.borrow()467 self.0.settings.borrow()
677 self.settings_mut().max_trace = trace;564 self.settings_mut().max_trace = trace;
678 }565 }
679
680 pub fn max_stack(&self) -> usize {
681 self.settings().max_stack
682 }
683 pub fn set_max_stack(&self, trace: usize) {
684 self.settings_mut().max_stack = trace;
685 }
686}566}
687567
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
577577
578pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);578pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);
579impl ObjMemberBuilder<ValueBuilder<'_>> {579impl ObjMemberBuilder<ValueBuilder<'_>> {
580 /// Inserts value, replacing if it is already defined
581 pub fn value_unchecked(self, value: Val) {
582 let (receiver, name, member) =
583 self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value)));
584 let entry = receiver.0.map.entry(name);
585 entry.insert(member);
586 }
587
580 pub fn value(self, s: State, value: Val) -> Result<()> {588 pub fn value(self, value: Val) -> Result<()> {
589 self.thunk(Thunk::evaluated(value))
590 }
591 pub fn thunk(self, value: Thunk<Val>) -> Result<()> {
581 self.binding(s, MaybeUnbound::Bound(Thunk::evaluated(value)))592 self.binding(MaybeUnbound::Bound(value))
582 }593 }
583 pub fn bindable(594 pub fn bindable(self, bindable: TraceBox<dyn Unbound<Bound = Thunk<Val>>>) -> Result<()> {
584 self,
585 s: State,
586 bindable: TraceBox<dyn Unbound<Bound = Thunk<Val>>>,
587 ) -> Result<()> {
588 self.binding(s, MaybeUnbound::Unbound(Cc::new(bindable)))595 self.binding(MaybeUnbound::Unbound(Cc::new(bindable)))
589 }596 }
590 pub fn binding(self, s: State, binding: MaybeUnbound) -> Result<()> {597 pub fn binding(self, binding: MaybeUnbound) -> Result<()> {
591 let (receiver, name, member) = self.build_member(binding);598 let (receiver, name, member) = self.build_member(binding);
592 let location = member.location.clone();599 let location = member.location.clone();
593 let old = receiver.0.map.insert(name.clone(), member);600 let old = receiver.0.map.insert(name.clone(), member);
594 if old.is_some() {601 if old.is_some() {
595 s.push(602 State::push(
596 CallLocation(location.as_ref()),603 CallLocation(location.as_ref()),
597 || format!("field <{}> initializtion", name.clone()),604 || format!("field <{}> initializtion", name.clone()),
598 || throw!(DuplicateFieldName(name.clone())),605 || throw!(DuplicateFieldName(name.clone())),
addedcrates/jrsonnet-evaluator/src/stack.rsdiffbeforeafterboth

no changes

modifiedcrates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth
111 buf.push_str(cur_padding);111 buf.push_str(cur_padding);
112 escape_string_json_buf(&field, buf);112 escape_string_json_buf(&field, buf);
113 buf.push_str(options.key_val_sep);113 buf.push_str(options.key_val_sep);
114 s.push_description(114 State::push_description(
115 || format!("field <{}> manifestification", field.clone()),115 || format!("field <{}> manifestification", field.clone()),
116 || {116 || {
117 let value = obj.get(s.clone(), field.clone())?.unwrap();117 let value = obj.get(s.clone(), field.clone())?.unwrap();
modifiedcrates/jrsonnet-evaluator/src/stdlib/mod.rsdiffbeforeafterboth
10pub mod manifest;10pub mod manifest;
1111
12pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {12pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
13 s.push(13 State::push(
14 CallLocation::native(),14 CallLocation::native(),
15 || format!("std.format of {str}"),15 || format!("std.format of {str}"),
16 || {16 || {
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
85}85}
8686
87fn push_type_description(87fn push_type_description(
88 s: State,
89 error_reason: impl Fn() -> String,88 error_reason: impl Fn() -> String,
90 path: impl Fn() -> ValuePathItem,89 path: impl Fn() -> ValuePathItem,
91 item: impl Fn() -> Result<()>,90 item: impl Fn() -> Result<()>,
92) -> Result<()> {91) -> Result<()> {
93 s.push_description(error_reason, || match item() {92 State::push_description(error_reason, || match item() {
94 Ok(_) => Ok(()),93 Ok(_) => Ok(()),
95 Err(mut e) => {94 Err(mut e) => {
96 if let Error::TypeError(e) = &mut e.error_mut() {95 if let Error::TypeError(e) = &mut e.error_mut() {
170 Val::Arr(a) => {169 Val::Arr(a) => {
171 for (i, item) in a.iter(s.clone()).enumerate() {170 for (i, item) in a.iter(s.clone()).enumerate() {
172 push_type_description(171 push_type_description(
173 s.clone(),
174 || format!("array index {i}"),172 || format!("array index {i}"),
175 || ValuePathItem::Index(i as u64),173 || ValuePathItem::Index(i as u64),
176 || elem_type.check(s.clone(), &item.clone()?),174 || elem_type.check(s.clone(), &item.clone()?),
184 Val::Arr(a) => {182 Val::Arr(a) => {
185 for (i, item) in a.iter(s.clone()).enumerate() {183 for (i, item) in a.iter(s.clone()).enumerate() {
186 push_type_description(184 push_type_description(
187 s.clone(),
188 || format!("array index {i}"),185 || format!("array index {i}"),
189 || ValuePathItem::Index(i as u64),186 || ValuePathItem::Index(i as u64),
190 || elem_type.check(s.clone(), &item.clone()?),187 || elem_type.check(s.clone(), &item.clone()?),
199 for (k, v) in elems.iter() {196 for (k, v) in elems.iter() {
200 if let Some(got_v) = obj.get(s.clone(), (*k).into())? {197 if let Some(got_v) = obj.get(s.clone(), (*k).into())? {
201 push_type_description(198 push_type_description(
202 s.clone(),
203 || format!("property {k}"),199 || format!("property {k}"),
204 || ValuePathItem::Field((*k).into()),200 || ValuePathItem::Field((*k).into()),
205 || v.check(s.clone(), &got_v),201 || v.check(s.clone(), &got_v),
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
299 cfg_attrs,299 cfg_attrs,
300 } => {300 } => {
301 let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");301 let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");
302 let eval = quote! {s.push_description(302 let eval = quote! {jrsonnet_evaluator::State::push_description(
303 || format!("argument <{}> evaluation", #name),303 || format!("argument <{}> evaluation", #name),
304 || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),304 || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
305 )?};305 )?};
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
137 builder137 builder
138 .member(name.into())138 .member(name.into())
139 .hide()139 .hide()
140 .value(s.clone(), Val::Func(FuncVal::StaticBuiltin(builtin)))140 .value(Val::Func(FuncVal::StaticBuiltin(builtin)))
141 .expect("no conflict");141 .expect("no conflict");
142 }142 }
143143
144 builder144 builder
145 .member("extVar".into())145 .member("extVar".into())
146 .hide()146 .hide()
147 .value(147 .value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_ext_var {
148 s.clone(),
149 Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_ext_var {
150 settings: settings.clone()148 settings: settings.clone()
151 })))),149 })))))
152 )
153 .expect("no conflict");150 .expect("no conflict");
154 builder151 builder
155 .member("native".into())152 .member("native".into())
156 .hide()153 .hide()
157 .value(154 .value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_native {
158 s.clone(),
159 Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_native {
160 settings: settings.clone()155 settings: settings.clone()
161 })))),156 })))))
162 )
163 .expect("no conflict");157 .expect("no conflict");
164 builder158 builder
165 .member("trace".into())159 .member("trace".into())
166 .hide()160 .hide()
167 .value(161 .value(Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_trace {
168 s.clone(),
169 Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_trace { settings })))),162 settings
170 )163 })))))
171 .expect("no conflict");164 .expect("no conflict");
172165
173 builder166 builder
174 .member("id".into())167 .member("id".into())
175 .hide()168 .hide()
176 .value(s, Val::Func(FuncVal::Id))169 .value(Val::Func(FuncVal::Id))
177 .expect("no conflict");170 .expect("no conflict");
178171
179 builder.build()172 builder.build()