1#![warn(clippy::all, clippy::nursery, clippy::pedantic)]2#![allow(3 macro_expanded_macro_exports_accessed_by_absolute_paths,4 clippy::ptr_arg,5 6 clippy::must_use_candidate,7 8 clippy::missing_errors_doc,9 10 clippy::needless_pass_by_value,11 12 clippy::wildcard_imports,13 clippy::enum_glob_use,14 clippy::module_name_repetitions,15 16 clippy::cast_precision_loss,17 clippy::cast_possible_wrap,18 clippy::cast_possible_truncation,19 clippy::cast_sign_loss,20)]212223extern crate self as jrsonnet_evaluator;2425mod ctx;26mod dynamic;27pub mod error;28mod evaluate;29pub mod function;30pub mod gc;31mod import;32mod integrations;33mod map;34mod obj;35mod stdlib;36pub mod trace;37pub mod typed;38pub mod val;3940use std::{41 cell::{Ref, RefCell, RefMut},42 collections::HashMap,43 fmt::{self, Debug},44 path::{Path, PathBuf},45 rc::Rc,46};4748pub use ctx::*;49pub use dynamic::*;50use error::{Error::*, LocError, Result, StackTraceElement};51pub use evaluate::*;52use function::{builtin::Builtin, CallLocation, TlaArg};53use gc::{GcHashMap, TraceBox};54use gcmodule::{Cc, Trace, Weak};55pub use import::*;56pub use jrsonnet_interner::IStr;57pub use jrsonnet_parser as parser;58use jrsonnet_parser::*;59pub use obj::*;60use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat};61pub use val::{LazyVal, ManifestFormat, Val};6263pub trait Bindable: Trace + 'static {64 fn bind(65 &self,66 s: State,67 this: Option<ObjValue>,68 super_obj: Option<ObjValue>,69 ) -> Result<LazyVal>;70}7172#[derive(Clone, Trace)]73pub enum LazyBinding {74 Bindable(Cc<TraceBox<dyn Bindable>>),75 Bound(LazyVal),76}7778impl Debug for LazyBinding {79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {80 write!(f, "LazyBinding")81 }82}83impl LazyBinding {84 pub fn evaluate(85 &self,86 s: State,87 this: Option<ObjValue>,88 super_obj: Option<ObjValue>,89 ) -> Result<LazyVal> {90 match self {91 Self::Bindable(v) => v.bind(s, this, super_obj),92 Self::Bound(v) => Ok(v.clone()),93 }94 }95}9697pub struct EvaluationSettings {98 99 pub max_stack: usize,100 101 pub max_trace: usize,102 103 pub ext_vars: HashMap<IStr, Val>,104 105 pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,106 107 pub tla_vars: HashMap<IStr, TlaArg>,108 109 pub globals: HashMap<IStr, Val>,110 111 pub import_resolver: Box<dyn ImportResolver>,112 113 pub manifest_format: ManifestFormat,114 115 pub trace_format: Box<dyn TraceFormat>,116}117impl Default for EvaluationSettings {118 fn default() -> Self {119 Self {120 max_stack: 200,121 max_trace: 20,122 globals: HashMap::default(),123 ext_vars: HashMap::default(),124 ext_natives: HashMap::default(),125 tla_vars: HashMap::default(),126 import_resolver: Box::new(DummyImportResolver),127 manifest_format: ManifestFormat::Json {128 padding: 4,129 #[cfg(feature = "exp-preserve-order")]130 preserve_order: false,131 },132 trace_format: Box::new(CompactFormat {133 padding: 4,134 resolver: trace::PathResolver::Absolute,135 }),136 }137 }138}139140#[derive(Default)]141struct EvaluationData {142 143 stack_depth: usize,144 145 stack_generation: usize,146147 breakpoints: Breakpoints,148 149 files: GcHashMap<Rc<Path>, FileData>,150 str_files: GcHashMap<Rc<Path>, IStr>,151 bin_files: GcHashMap<Rc<Path>, Rc<[u8]>>,152}153154pub struct FileData {155 source_code: IStr,156 parsed: LocExpr,157 evaluated: Option<Val>,158}159160#[allow(clippy::type_complexity)]161pub struct Breakpoint {162 loc: ExprLocation,163 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,164}165#[derive(Default)]166struct Breakpoints(Vec<Rc<Breakpoint>>);167impl Breakpoints {168 fn insert(169 &self,170 stack_depth: usize,171 stack_generation: usize,172 loc: &ExprLocation,173 result: Result<Val>,174 ) -> Result<Val> {175 if self.0.is_empty() {176 return result;177 }178 for item in &self.0 {179 if item.loc.belongs_to(loc) {180 let mut collected = item.collected.borrow_mut();181 let (depth, vals) = collected.entry(stack_generation).or_default();182 if stack_depth > *depth {183 vals.clear();184 }185 vals.push(result.clone());186 }187 }188 result189 }190}191192#[derive(Default)]193pub struct EvaluationStateInternals {194 195 data: RefCell<EvaluationData>,196 197 settings: RefCell<EvaluationSettings>,198}199200201#[derive(Default, Clone)]202pub struct State(Rc<EvaluationStateInternals>);203204impl State {205 206 pub fn add_file(&self, path: Rc<Path>, source_code: IStr) -> Result<LocExpr> {207 let parsed = parse(208 &source_code,209 &ParserSettings {210 file_name: path.clone(),211 },212 )213 .map_err(|error| ImportSyntaxError {214 error: Box::new(error),215 path: path.clone(),216 source_code: source_code.clone(),217 })?;218 self.add_parsed_file(path, source_code, parsed.clone())?;219220 Ok(parsed)221 }222223 pub fn reset_evaluation_state(&self, name: &Path) {224 self.data_mut()225 .files226 .get_mut(name)227 .expect("file not found")228 .evaluated229 .take();230 }231232 233 pub fn add_parsed_file(234 &self,235 name: Rc<Path>,236 source_code: IStr,237 parsed: LocExpr,238 ) -> Result<()> {239 self.data_mut().files.insert(240 name,241 FileData {242 source_code,243 parsed,244 evaluated: None,245 },246 );247248 Ok(())249 }250 pub fn get_source(&self, name: &Path) -> Option<IStr> {251 let ro_map = &self.data().files;252 ro_map.get(name).map(|value| value.source_code.clone())253 }254 pub fn map_source_locations(&self, file: &Path, locs: &[usize]) -> Vec<CodeLocation> {255 offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs)256 }257 pub fn map_from_source_location(258 &self,259 file: &Path,260 line: usize,261 column: usize,262 ) -> Option<usize> {263 location_to_offset(264 &self.get_source(file).expect("file not found"),265 line,266 column,267 )268 }269 pub fn import_file(&self, from: &Path, path: &Path) -> Result<Val> {270 let file_path = self.resolve_file(from, path)?;271 {272 let data = self.data();273 let files = &data.files;274 if files.contains_key(&file_path as &Path) {275 drop(data);276 return self.evaluate_loaded_file_raw(&file_path);277 }278 }279 let contents = self.load_file_str(&file_path)?;280 self.add_file(file_path.clone(), contents)?;281 self.evaluate_loaded_file_raw(&file_path)282 }283 pub(crate) fn import_file_str(&self, from: &Path, path: &Path) -> Result<IStr> {284 let path = self.resolve_file(from, path)?;285 if !self.data().str_files.contains_key(&path) {286 let file_str = self.load_file_str(&path)?;287 self.data_mut().str_files.insert(path.clone(), file_str);288 }289 Ok(self.data().str_files.get(&path).cloned().unwrap())290 }291 pub(crate) fn import_file_bin(&self, from: &Path, path: &Path) -> Result<Rc<[u8]>> {292 let path = self.resolve_file(from, path)?;293 if !self.data().bin_files.contains_key(&path) {294 let file_bin = self.load_file_bin(&path)?;295 self.data_mut().bin_files.insert(path.clone(), file_bin);296 }297 Ok(self.data().bin_files.get(&path).cloned().unwrap())298 }299300 fn evaluate_loaded_file_raw(&self, name: &Path) -> Result<Val> {301 let expr: LocExpr = {302 let ro_map = &self.data().files;303 let value = ro_map304 .get(name)305 .unwrap_or_else(|| panic!("file not added: {:?}", name));306 if let Some(ref evaluated) = value.evaluated {307 return Ok(evaluated.clone());308 }309 value.parsed.clone()310 };311 let value = evaluate(self.clone(), self.create_default_context(), &expr)?;312 {313 self.data_mut()314 .files315 .get_mut(name)316 .unwrap()317 .evaluated318 .replace(value.clone());319 }320 Ok(value)321 }322323 324 pub fn with_stdlib(&self) -> &Self {325 use jrsonnet_stdlib::STDLIB_STR;326 let std_path: Rc<Path> = PathBuf::from("std.jsonnet").into();327328 self.add_parsed_file(329 std_path.clone(),330 STDLIB_STR.to_owned().into(),331 stdlib::get_parsed_stdlib(),332 )333 .expect("stdlib is correct");334 let val = self335 .evaluate_loaded_file_raw(&std_path)336 .expect("stdlib is correct");337 self.settings_mut().globals.insert("std".into(), val);338 self339 }340341 342 pub fn create_default_context(&self) -> Context {343 let globals = &self.settings().globals;344 let mut new_bindings = GcHashMap::with_capacity(globals.len());345 for (name, value) in globals.iter() {346 new_bindings.insert(name.clone(), LazyVal::new_resolved(value.clone()));347 }348 Context::new().extend_bound(new_bindings)349 }350351 352 pub fn push<T>(353 &self,354 e: CallLocation,355 frame_desc: impl FnOnce() -> String,356 f: impl FnOnce() -> Result<T>,357 ) -> Result<T> {358 {359 let mut data = self.data_mut();360 let stack_depth = &mut data.stack_depth;361 if *stack_depth > self.max_stack() {362 363 drop(data);364 throw!(StackOverflow);365 }366 *stack_depth += 1;367 }368 let result = f();369 {370 let mut data = self.data_mut();371 data.stack_depth -= 1;372 data.stack_generation += 1;373 }374 if let Err(mut err) = result {375 err.trace_mut().0.push(StackTraceElement {376 location: e.0.cloned(),377 desc: frame_desc(),378 });379 return Err(err);380 }381 result382 }383384 385 pub fn push_val(386 &self,387 e: &ExprLocation,388 frame_desc: impl FnOnce() -> String,389 f: impl FnOnce() -> Result<Val>,390 ) -> Result<Val> {391 {392 let mut data = self.data_mut();393 let stack_depth = &mut data.stack_depth;394 if *stack_depth > self.max_stack() {395 396 drop(data);397 throw!(StackOverflow);398 }399 *stack_depth += 1;400 }401 let mut result = f();402 {403 let mut data = self.data_mut();404 data.stack_depth -= 1;405 data.stack_generation += 1;406 result = data407 .breakpoints408 .insert(data.stack_depth, data.stack_generation, e, result);409 }410 if let Err(mut err) = result {411 err.trace_mut().0.push(StackTraceElement {412 location: Some(e.clone()),413 desc: frame_desc(),414 });415 return Err(err);416 }417 result418 }419 420 pub fn push_description<T>(421 &self,422 frame_desc: impl FnOnce() -> String,423 f: impl FnOnce() -> Result<T>,424 ) -> Result<T> {425 {426 let mut data = self.data_mut();427 let stack_depth = &mut data.stack_depth;428 if *stack_depth > self.max_stack() {429 430 drop(data);431 throw!(StackOverflow);432 }433 *stack_depth += 1;434 }435 let result = f();436 {437 let mut data = self.data_mut();438 data.stack_depth -= 1;439 data.stack_generation += 1;440 }441 if let Err(mut err) = result {442 err.trace_mut().0.push(StackTraceElement {443 location: None,444 desc: frame_desc(),445 });446 return Err(err);447 }448 result449 }450451 452 453 pub fn stringify_err(&self, e: &LocError) -> String {454 let mut out = String::new();455 self.settings()456 .trace_format457 .write_trace(&mut out, self, e)458 .unwrap();459 out460 }461462 pub fn manifest(&self, val: Val) -> Result<IStr> {463 self.push_description(464 || "manifestification".to_string(),465 || val.manifest(self.clone(), &self.manifest_format()),466 )467 }468 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {469 val.manifest_multi(self.clone(), &self.manifest_format())470 }471 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {472 val.manifest_stream(self.clone(), &self.manifest_format())473 }474475 476 pub fn with_tla(&self, val: Val) -> Result<Val> {477 Ok(match val {478 Val::Func(func) => self.push_description(479 || "during TLA call".to_owned(),480 || {481 func.evaluate(482 self.clone(),483 self.create_default_context(),484 CallLocation::native(),485 &self.settings().tla_vars,486 true,487 )488 },489 )?,490 v => v,491 })492 }493}494495496impl State {497 fn data(&self) -> Ref<EvaluationData> {498 self.0.data.borrow()499 }500 fn data_mut(&self) -> RefMut<EvaluationData> {501 self.0.data.borrow_mut()502 }503 pub fn settings(&self) -> Ref<EvaluationSettings> {504 self.0.settings.borrow()505 }506 pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {507 self.0.settings.borrow_mut()508 }509}510511512impl State {513 pub fn evaluate_file_raw(&self, name: &Path) -> Result<Val> {514 self.import_file(&std::env::current_dir().expect("cwd"), name)515 }516 pub fn evaluate_file_raw_nocwd(&self, name: &Path) -> Result<Val> {517 self.import_file(&PathBuf::from("."), name)518 }519 520 pub fn evaluate_snippet_raw(&self, source: Rc<Path>, code: IStr) -> Result<Val> {521 let parsed = parse(522 &code,523 &ParserSettings {524 file_name: source.clone(),525 },526 )527 .map_err(|e| ImportSyntaxError {528 path: source.clone(),529 source_code: code.clone(),530 error: Box::new(e),531 })?;532 self.add_parsed_file(source, code, parsed.clone())?;533 self.evaluate_expr_raw(parsed)534 }535 536 pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {537 evaluate(self.clone(), self.create_default_context(), &code)538 }539}540541542impl State {543 pub fn add_ext_var(&self, name: IStr, value: Val) {544 self.settings_mut().ext_vars.insert(name, value);545 }546 pub fn add_ext_str(&self, name: IStr, value: IStr) {547 self.add_ext_var(name, Val::Str(value));548 }549 pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> {550 let value =551 self.evaluate_snippet_raw(PathBuf::from(format!("ext_code {}", name)).into(), code)?;552 self.add_ext_var(name, value);553 Ok(())554 }555556 pub fn add_tla(&self, name: IStr, value: Val) {557 self.settings_mut()558 .tla_vars559 .insert(name, TlaArg::Val(value));560 }561 pub fn add_tla_str(&self, name: IStr, value: IStr) {562 self.settings_mut()563 .tla_vars564 .insert(name, TlaArg::String(value));565 }566 pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> {567 let parsed = self.add_file(PathBuf::from(format!("tla_code {}", name)).into(), code)?;568 self.settings_mut()569 .tla_vars570 .insert(name, TlaArg::Code(parsed));571 Ok(())572 }573574 pub fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {575 self.settings().import_resolver.resolve_file(from, path)576 }577 pub fn load_file_str(&self, path: &Path) -> Result<IStr> {578 self.settings().import_resolver.load_file_str(path)579 }580 pub fn load_file_bin(&self, path: &Path) -> Result<Rc<[u8]>> {581 self.settings().import_resolver.load_file_bin(path)582 }583584 pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {585 Ref::map(self.settings(), |s| &*s.import_resolver)586 }587 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {588 self.settings_mut().import_resolver = resolver;589 }590591 pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {592 self.settings_mut().ext_natives.insert(name, cb);593 }594595 pub fn manifest_format(&self) -> ManifestFormat {596 self.settings().manifest_format.clone()597 }598 pub fn set_manifest_format(&self, format: ManifestFormat) {599 self.settings_mut().manifest_format = format;600 }601602 pub fn trace_format(&self) -> Ref<dyn TraceFormat> {603 Ref::map(self.settings(), |s| &*s.trace_format)604 }605 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {606 self.settings_mut().trace_format = format;607 }608609 pub fn max_trace(&self) -> usize {610 self.settings().max_trace611 }612 pub fn set_max_trace(&self, trace: usize) {613 self.settings_mut().max_trace = trace;614 }615616 pub fn max_stack(&self) -> usize {617 self.settings().max_stack618 }619 pub fn set_max_stack(&self, trace: usize) {620 self.settings_mut().max_stack = trace;621 }622}623624pub fn cc_ptr_eq<T>(a: &Cc<T>, b: &Cc<T>) -> bool {625 let a = a as &T;626 let b = b as &T;627 std::ptr::eq(a, b)628}629630fn weak_raw<T>(a: Weak<T>) -> *const () {631 unsafe { std::mem::transmute(a) }632}633fn weak_ptr_eq<T>(a: Weak<T>, b: Weak<T>) -> bool {634 std::ptr::eq(weak_raw(a), weak_raw(b))635}636637#[test]638fn weak_unsafe() {639 let a = Cc::new(1);640 let b = Cc::new(2);641642 let aw1 = a.clone().downgrade();643 let aw2 = a.clone().downgrade();644 let aw3 = a.clone().downgrade();645646 let bw = b.clone().downgrade();647648 assert!(weak_ptr_eq(aw1, aw2));649 assert!(!weak_ptr_eq(aw3, bw));650}