1#![allow(clippy::box_default)]23pub mod interop;45pub mod import;6pub mod native;7pub mod val_extract;8pub mod val_make;9pub mod val_modify;10pub mod vars_tlas;1112use std::{13 alloc::Layout,14 any::Any,15 borrow::Cow,16 cell::RefCell,17 ffi::{CStr, CString, OsStr},18 os::raw::{c_char, c_double, c_int, c_uint},19 path::{Path, PathBuf},20 rc::Rc,21};2223use jrsonnet_evaluator::{24 apply_tla, bail,25 function::TlaArg,26 gc::WithCapacityExt as _,27 manifest::{JsonFormat, ManifestFormat, ToStringFormat},28 rustc_hash::FxHashMap,29 stack::set_stack_depth_limit,30 trace::{CompactFormat, PathResolver, TraceFormat},31 FileImportResolver, IStr, ImportResolver, Result, State, Val,32};33use jrsonnet_gcmodule::Acyclic;34use jrsonnet_parser::SourcePath;35use jrsonnet_stdlib::ContextInitializer;363738#[cfg(target_arch = "wasm32")]39#[no_mangle]40pub extern "C" fn _start() {}414243444546#[no_mangle]47pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {48 b"v0.20.0\0"49}5051unsafe fn parse_path(input: &CStr) -> Cow<'_, Path> {52 #[cfg(target_family = "unix")]53 {54 use std::os::unix::ffi::OsStrExt;55 let str = OsStr::from_bytes(input.to_bytes());56 Cow::Borrowed(Path::new(str))57 }58 #[cfg(not(target_family = "unix"))]59 {60 let string = input.to_str().expect("bad utf-8");61 Cow::Borrowed(string.as_ref())62 }63}6465unsafe fn unparse_path(input: &Path) -> Cow<'_, CStr> {66 #[cfg(target_family = "unix")]67 {68 use std::os::unix::ffi::OsStrExt;69 let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it");70 Cow::Owned(str)71 }72 #[cfg(not(target_family = "unix"))]73 {74 let str = input.as_os_str().to_str().expect("bad utf-8");75 let cstr = CString::new(str).expect("input has NUL inside");76 Cow::Owned(cstr)77 }78}7980#[derive(Acyclic)]81struct VMImportResolver {82 inner: RefCell<Rc<dyn ImportResolver>>,83}84impl VMImportResolver {85 fn new(value: impl ImportResolver) -> Self {86 Self {87 inner: RefCell::new(Rc::new(value)),88 }89 }90}91impl ImportResolver for VMImportResolver {92 fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {93 self.inner.borrow().load_file_contents(resolved)94 }9596 fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {97 self.inner.borrow().resolve_from(from, path)98 }99100 fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {101 self.inner.borrow().resolve_from_default(path)102 }103104 fn resolve(&self, path: &Path) -> Result<SourcePath> {105 self.inner.borrow().resolve(path)106 }107}108109pub struct VM {110 state: State,111 manifest_format: Box<dyn ManifestFormat>,112 trace_format: Box<dyn TraceFormat>,113 tla_args: FxHashMap<IStr, TlaArg>,114}115impl VM {116 fn replace_import_resolver(&self, resolver: impl ImportResolver) {117 *(self.state.import_resolver() as &dyn Any)118 .downcast_ref::<VMImportResolver>()119 .expect("valid resolver ty")120 .inner121 .borrow_mut() = Rc::new(resolver);122 }123 fn add_jpath(&self, path: PathBuf) {124 let ir = self.state.import_resolver();125 let vmi = (ir as &dyn Any)126 .downcast_ref::<VMImportResolver>()127 .expect("valid resolver ty");128 let vmi = &mut *vmi.inner.borrow_mut();129 (vmi as &mut dyn Any)130 .downcast_mut::<FileImportResolver>()131 .expect("jpaths are not compatible with callback imports!")132 .add_jpath(path);133 }134}135136137#[no_mangle]138#[allow(clippy::box_default)]139pub extern "C" fn jsonnet_make() -> *mut VM {140 let mut state = State::builder();141 state142 .import_resolver(VMImportResolver::new(FileImportResolver::default()))143 .context_initializer(ContextInitializer::new(PathResolver::new_cwd_fallback()));144 let state = state.build();145 Box::into_raw(Box::new(VM {146 state,147 manifest_format: Box::new(JsonFormat::default()),148 trace_format: Box::new(CompactFormat::default()),149 tla_args: FxHashMap::new(),150 }))151}152153154#[no_mangle]155#[allow(clippy::boxed_local)]156pub extern "C" fn jsonnet_destroy(vm: Box<VM>) {157 drop(vm);158}159160161#[no_mangle]162pub extern "C" fn jsonnet_max_stack(_vm: &VM, v: c_uint) {163 set_stack_depth_limit(v as usize);164}165166167168169#[no_mangle]170pub extern "C" fn jsonnet_gc_min_objects(_vm: &VM, _v: c_uint) {}171172173174175#[no_mangle]176pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &VM, _v: c_double) {}177178179#[no_mangle]180pub extern "C" fn jsonnet_string_output(vm: &mut VM, v: c_int) {181 vm.manifest_format = match v {182 0 => Box::new(JsonFormat::default()),183 1 => Box::new(ToStringFormat),184 _ => panic!("incorrect output format"),185 };186}187188189190191192193194195196#[no_mangle]197pub unsafe extern "C" fn jsonnet_realloc(_vm: &VM, buf: *mut u8, sz: usize) -> *mut u8 {198 if buf.is_null() {199 if sz == 0 {200 return std::ptr::null_mut();201 }202 return unsafe {203 std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap())204 };205 }206 207 208 209 210 let old_layout = Layout::from_size_align(16, std::mem::align_of::<u8>()).unwrap();211 if sz == 0 {212 unsafe { std::alloc::dealloc(buf, old_layout) };213 return std::ptr::null_mut();214 }215 unsafe { std::alloc::realloc(buf, old_layout, sz) }216}217218219220221#[no_mangle]222#[allow(clippy::boxed_local)]223pub extern "C" fn jsonnet_json_destroy(_vm: &VM, v: Box<Val>) {224 drop(v);225}226227228#[no_mangle]229pub extern "C" fn jsonnet_max_trace(vm: &mut VM, v: c_uint) {230 if let Some(format) = vm.trace_format.as_any_mut().downcast_mut::<CompactFormat>() {231 format.max_trace = v as usize;232 } else {233 panic!("max_trace is not supported by current tracing format")234 }235}236237238239240241242243244#[no_mangle]245pub unsafe extern "C" fn jsonnet_evaluate_file(246 vm: &VM,247 filename: *const c_char,248 error: &mut c_int,249) -> *const c_char {250 let filename = unsafe { parse_path(CStr::from_ptr(filename)) };251 match vm252 .state253 .import(filename)254 .and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))255 .and_then(|val| val.manifest(&vm.manifest_format))256 {257 Ok(v) => {258 *error = 0;259 CString::new(&*v as &str).unwrap().into_raw()260 }261 Err(e) => {262 *error = 1;263 let mut out = String::new();264 vm.trace_format.write_trace(&mut out, &e).unwrap();265 CString::new(&out as &str).unwrap().into_raw()266 }267 }268}269270271272273274275276277#[no_mangle]278pub unsafe extern "C" fn jsonnet_evaluate_snippet(279 vm: &VM,280 filename: *const c_char,281 snippet: *const c_char,282 error: &mut c_int,283) -> *const c_char {284 let filename = unsafe { CStr::from_ptr(filename) };285 let snippet = unsafe { CStr::from_ptr(snippet) };286 match vm287 .state288 .evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())289 .and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))290 .and_then(|val| val.manifest(&vm.manifest_format))291 {292 Ok(v) => {293 *error = 0;294 CString::new(&*v as &str).unwrap().into_raw()295 }296 Err(e) => {297 *error = 1;298 let mut out = String::new();299 vm.trace_format.write_trace(&mut out, &e).unwrap();300 CString::new(&out as &str).unwrap().into_raw()301 }302 }303}304305fn val_to_multi(val: Val, format: &dyn ManifestFormat) -> Result<Vec<(IStr, IStr)>> {306 let Val::Obj(val) = val else {307 bail!("expected object as multi output")308 };309 let mut out = Vec::new();310 for (k, v) in val.iter(311 #[cfg(feature = "exp-preserve-order")]312 false,313 ) {314 out.push((k, v?.manifest(format)?.into()));315 }316 Ok(out)317}318319fn multi_to_raw(multi: Vec<(IStr, IStr)>) -> *const c_char {320 let mut out = Vec::new();321 for (i, (k, v)) in multi.iter().enumerate() {322 if i != 0 {323 out.push(0);324 }325 out.extend_from_slice(k.as_bytes());326 out.push(0);327 out.extend_from_slice(v.as_bytes());328 }329 out.push(0);330 out.push(0);331 let v = out.as_ptr();332 std::mem::forget(out);333 v.cast::<c_char>()334}335336337#[no_mangle]338pub unsafe extern "C" fn jsonnet_evaluate_file_multi(339 vm: &VM,340 filename: *const c_char,341 error: &mut c_int,342) -> *const c_char {343 let filename = unsafe { parse_path(CStr::from_ptr(filename)) };344 match vm345 .state346 .import(filename)347 .and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))348 .and_then(|val| val_to_multi(val, &vm.manifest_format))349 {350 Ok(v) => {351 *error = 0;352 multi_to_raw(v)353 }354 Err(e) => {355 *error = 1;356 let mut out = String::new();357 vm.trace_format.write_trace(&mut out, &e).unwrap();358 CString::new(&out as &str).unwrap().into_raw()359 }360 }361}362363364#[no_mangle]365pub unsafe extern "C" fn jsonnet_evaluate_snippet_multi(366 vm: &VM,367 filename: *const c_char,368 snippet: *const c_char,369 error: &mut c_int,370) -> *const c_char {371 let filename = unsafe { CStr::from_ptr(filename) };372 let snippet = unsafe { CStr::from_ptr(snippet) };373 match vm374 .state375 .evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())376 .and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))377 .and_then(|val| val_to_multi(val, &vm.manifest_format))378 {379 Ok(v) => {380 *error = 0;381 multi_to_raw(v)382 }383 Err(e) => {384 *error = 1;385 let mut out = String::new();386 vm.trace_format.write_trace(&mut out, &e).unwrap();387 CString::new(&out as &str).unwrap().into_raw()388 }389 }390}391392fn val_to_stream(val: Val, format: &dyn ManifestFormat) -> Result<Vec<IStr>> {393 let Val::Arr(val) = val else {394 bail!("expected array as stream output")395 };396 let mut out = Vec::new();397 for item in val.iter() {398 out.push(item?.manifest(format)?.into());399 }400 Ok(out)401}402403fn stream_to_raw(multi: Vec<IStr>) -> *const c_char {404 let mut out = Vec::new();405 for (i, v) in multi.iter().enumerate() {406 if i != 0 {407 out.push(0);408 }409 out.extend_from_slice(v.as_bytes());410 }411 out.push(0);412 out.push(0);413 let v = out.as_ptr();414 std::mem::forget(out);415 v.cast::<c_char>()416}417418419#[no_mangle]420pub unsafe extern "C" fn jsonnet_evaluate_file_stream(421 vm: &VM,422 filename: *const c_char,423 error: &mut c_int,424) -> *const c_char {425 let filename = unsafe { parse_path(CStr::from_ptr(filename)) };426 match vm427 .state428 .import(filename)429 .and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))430 .and_then(|val| val_to_stream(val, &vm.manifest_format))431 {432 Ok(v) => {433 *error = 0;434 stream_to_raw(v)435 }436 Err(e) => {437 *error = 1;438 let mut out = String::new();439 vm.trace_format.write_trace(&mut out, &e).unwrap();440 CString::new(&out as &str).unwrap().into_raw()441 }442 }443}444445446#[no_mangle]447pub unsafe extern "C" fn jsonnet_evaluate_snippet_stream(448 vm: &VM,449 filename: *const c_char,450 snippet: *const c_char,451 error: &mut c_int,452) -> *const c_char {453 let filename = unsafe { CStr::from_ptr(filename) };454 let snippet = unsafe { CStr::from_ptr(snippet) };455 match vm456 .state457 .evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())458 .and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))459 .and_then(|val| val_to_stream(val, &vm.manifest_format))460 {461 Ok(v) => {462 *error = 0;463 stream_to_raw(v)464 }465 Err(e) => {466 *error = 1;467 let mut out = String::new();468 vm.trace_format.write_trace(&mut out, &e).unwrap();469 CString::new(&out as &str).unwrap().into_raw()470 }471 }472}