difftreelog
fix ensure stack for deep manifest recursion
in: master
1 file changed
crates/jrsonnet-evaluator/src/manifest.rsdiffbeforeafterboth1use std::{borrow::Cow, fmt::Write, hint::black_box, ptr};23use crate::{4 bail, evaluate::ensure_sufficient_stack, in_description_frame, Error,5 Result, ResultExt, Val,6};78pub trait ManifestFormat {9 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()>;10 fn manifest(&self, val: Val) -> Result<String> {11 let mut out = String::new();12 self.manifest_buf(val, &mut out)?;13 Ok(out)14 }15 /// When outputing to file, is it safe to append a trailing newline (I.e newline won't change16 /// the meaning).17 ///18 /// Default implementation returns `true`19 fn file_trailing_newline(&self) -> bool {20 true21 }22}23impl<T> ManifestFormat for Box<T>24where25 T: ManifestFormat + ?Sized,26{27 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {28 let inner = &**self;29 inner.manifest_buf(val, buf)30 }31 fn file_trailing_newline(&self) -> bool {32 let inner = &**self;33 inner.file_trailing_newline()34 }35}36impl<T> ManifestFormat for &'_ T37where38 T: ManifestFormat + ?Sized,39{40 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {41 let inner = &**self;42 inner.manifest_buf(val, buf)43 }44 fn file_trailing_newline(&self) -> bool {45 let inner = &**self;46 inner.file_trailing_newline()47 }48}4950pub struct BlackBoxFormat;51impl ManifestFormat for BlackBoxFormat {52 #[allow(clippy::only_used_in_recursion)]53 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {54 match val {55 Val::Bool(v) => {56 black_box(v);57 }58 val @ Val::Null => {59 black_box(val);60 }61 Val::Str(str_value) => {62 black_box(format!("{str_value}"));63 }64 Val::Num(num_value) => {65 black_box(num_value);66 }67 Val::Arr(arr_value) => {68 for ele in arr_value.iter() {69 let ele = ele?;70 self.manifest_buf(ele, buf)?;71 }72 }73 Val::Obj(obj_value) => {74 for (name, value) in obj_value.iter(75 #[cfg(feature = "exp-preserve-order")]76 true,77 ) {78 black_box(name);79 let value = value?;80 self.manifest_buf(value, buf)?;81 }82 }83 Val::Func(func_val) => {84 black_box(func_val);85 bail!("tried to manifest function")86 }87 #[cfg(feature = "exp-bigint")]88 Val::BigInt(n) => {89 black_box(n);90 }91 }92 Ok(())93 }94}9596#[derive(PartialEq, Eq, Clone, Copy)]97enum JsonFormatting {98 // Applied in manifestification99 Manifest,100 /// Used for std.manifestJson101 /// Empty array/objects extends to "[\n\n]" instead of "[ ]" as in manifest102 Std,103 /// No line breaks, used in `obj+''`104 ToString,105 /// Minified json106 Minify,107}108109pub struct JsonFormat<'s> {110 padding: Cow<'s, str>,111 mtype: JsonFormatting,112 newline: &'s str,113 key_val_sep: &'s str,114 #[cfg(feature = "exp-preserve-order")]115 preserve_order: bool,116 #[cfg(feature = "exp-bigint")]117 preserve_bigints: bool,118}119120impl<'s> JsonFormat<'s> {121 // Minifying format122 pub fn minify(#[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Self {123 Self {124 padding: Cow::Borrowed(""),125 mtype: JsonFormatting::Minify,126 newline: "\n",127 key_val_sep: ":",128 #[cfg(feature = "exp-preserve-order")]129 preserve_order,130 #[cfg(feature = "exp-bigint")]131 preserve_bigints: false,132 }133 }134 /// Same format as std.toString, except does not keeps top-level string as-is135 /// To avoid confusion, the format is private in jrsonnet, use [`ToStringFormat`] instead136 const fn std_to_string_helper() -> Self {137 Self {138 padding: Cow::Borrowed(""),139 mtype: JsonFormatting::ToString,140 newline: "\n",141 key_val_sep: ": ",142 #[cfg(feature = "exp-preserve-order")]143 preserve_order: false,144 #[cfg(feature = "exp-bigint")]145 preserve_bigints: false,146 }147 }148 pub fn std_to_json(149 padding: String,150 newline: &'s str,151 key_val_sep: &'s str,152 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,153 ) -> Self {154 Self {155 padding: Cow::Owned(padding),156 mtype: JsonFormatting::Std,157 newline,158 key_val_sep,159 #[cfg(feature = "exp-preserve-order")]160 preserve_order,161 #[cfg(feature = "exp-bigint")]162 preserve_bigints: false,163 }164 }165 // Same format as CLI manifestification166 pub fn cli(167 padding: usize,168 #[cfg(feature = "exp-preserve-order")] preserve_order: bool,169 ) -> Self {170 if padding == 0 {171 return Self::minify(172 #[cfg(feature = "exp-preserve-order")]173 preserve_order,174 );175 }176 Self {177 padding: Cow::Owned(" ".repeat(padding)),178 mtype: JsonFormatting::Manifest,179 newline: "\n",180 key_val_sep: ": ",181 #[cfg(feature = "exp-preserve-order")]182 preserve_order,183 #[cfg(feature = "exp-bigint")]184 preserve_bigints: false,185 }186 }187 // Same format as CLI manifestification188 pub fn debug() -> Self {189 Self {190 padding: Cow::Borrowed(" "),191 mtype: JsonFormatting::Manifest,192 newline: "\n",193 key_val_sep: ": ",194 #[cfg(feature = "exp-preserve-order")]195 preserve_order: true,196 #[cfg(feature = "exp-bigint")]197 preserve_bigints: true,198 }199 }200}201impl Default for JsonFormat<'static> {202 fn default() -> Self {203 Self {204 padding: Cow::Borrowed(" "),205 mtype: JsonFormatting::Manifest,206 newline: "\n",207 key_val_sep: ": ",208 #[cfg(feature = "exp-preserve-order")]209 preserve_order: false,210 #[cfg(feature = "exp-bigint")]211 preserve_bigints: false,212 }213 }214}215216pub fn manifest_json_ex(val: &Val, options: &JsonFormat<'_>) -> Result<String> {217 let mut out = String::new();218 manifest_json_ex_buf(val, &mut out, &mut String::new(), options)?;219 Ok(out)220}221222#[allow(clippy::too_many_lines)]223fn manifest_json_ex_buf(224 val: &Val,225 buf: &mut String,226 cur_padding: &mut String,227 options: &JsonFormat<'_>,228) -> Result<()> {229 use JsonFormatting::*;230231 let mtype = options.mtype;232 match val {233 Val::Bool(v) => {234 if *v {235 buf.push_str("true");236 } else {237 buf.push_str("false");238 }239 }240 Val::Null => buf.push_str("null"),241 Val::Str(s) => {242 buf.reserve(2 + s.len());243 buf.push('"');244 s.chunks(&mut |c| {245 escape_string_json_buf_raw(c, buf);246 });247 buf.push('"');248 }249 Val::Num(n) => write!(buf, "{n}").unwrap(),250 #[cfg(feature = "exp-bigint")]251 Val::BigInt(n) => {252 if options.preserve_bigints {253 write!(buf, "{n}").unwrap();254 } else {255 write!(buf, "{:?}", n.to_string()).unwrap();256 }257 }258 Val::Arr(items) => ensure_sufficient_stack(|| {259 buf.push('[');260261 let old_len = cur_padding.len();262 cur_padding.push_str(&options.padding);263264 let mut had_items = false;265 for (i, item) in items.iter().enumerate() {266 had_items = true;267 let item = item.with_description(|| format!("elem <{i}> evaluation"))?;268269 if i != 0 {270 buf.push(',');271 }272 match mtype {273 Manifest | Std => {274 buf.push_str(options.newline);275 buf.push_str(cur_padding);276 }277 ToString if i != 0 => buf.push(' '),278 Minify | ToString => {}279 }280281 in_description_frame(282 || format!("elem <{i}> manifestification"),283 || manifest_json_ex_buf(&item, buf, cur_padding, options),284 )?;285 }286287 cur_padding.truncate(old_len);288289 match mtype {290 Manifest | ToString if !had_items => {291 // Empty array as "[ ]"292 buf.push(' ');293 }294 Manifest => {295 buf.push_str(options.newline);296 buf.push_str(cur_padding);297 }298 Std => {299 if !had_items {300 // Stdlib formats empty array as "[\n\n]"301 buf.push_str(options.newline);302 }303 buf.push_str(options.newline);304 buf.push_str(cur_padding);305 }306 Minify | ToString => {}307 }308309 buf.push(']');310 Ok::<_, Error>(())311 })?,312 Val::Obj(obj) => ensure_sufficient_stack(|| {313 obj.run_assertions()?;314 buf.push('{');315316 let old_len = cur_padding.len();317 cur_padding.push_str(&options.padding);318319 let mut had_fields = false;320 for (i, (key, value)) in obj321 .iter(322 #[cfg(feature = "exp-preserve-order")]323 options.preserve_order,324 )325 .enumerate()326 {327 had_fields = true;328 let value = value.with_description(|| format!("field <{key}> evaluation"))?;329330 if i != 0 {331 buf.push(',');332 }333 match mtype {334 Manifest | Std => {335 buf.push_str(options.newline);336 buf.push_str(cur_padding);337 }338 ToString if i != 0 => buf.push(' '),339 Minify | ToString => {}340 }341342 escape_string_json_buf(&key, buf);343 buf.push_str(options.key_val_sep);344 in_description_frame(345 || format!("field <{key}> manifestification"),346 || manifest_json_ex_buf(&value, buf, cur_padding, options),347 )?;348 }349350 cur_padding.truncate(old_len);351352 match mtype {353 Manifest | ToString if !had_fields => {354 // Empty object as "{ }"355 buf.push(' ');356 }357 Manifest => {358 buf.push_str(options.newline);359 buf.push_str(cur_padding);360 }361 Std => {362 if !had_fields {363 // Stdlib formats empty object as "{\n\n}"364 buf.push_str(options.newline);365 }366 buf.push_str(options.newline);367 buf.push_str(cur_padding);368 }369 Minify | ToString => {}370 }371372 buf.push('}');373 Ok::<_, Error>(())374 })?,375 Val::Func(_) => bail!("tried to manifest function"),376 }377 Ok(())378}379380impl ManifestFormat for JsonFormat<'_> {381 fn manifest_buf(&self, val: Val, buf: &mut String) -> Result<()> {382 manifest_json_ex_buf(&val, buf, &mut String::new(), self)383 }384}385386/// Same as [`JsonFormat`] with pre-set options, but top-level string is serialized as-is,387/// without quoting.388pub struct ToStringFormat;389impl ManifestFormat for ToStringFormat {390 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {391 const JSON_TO_STRING: JsonFormat = JsonFormat::std_to_string_helper();392 if let Some(str) = val.as_str() {393 out.push_str(&str);394 return Ok(());395 }396 #[cfg(feature = "exp-bigint")]397 if let Some(int) = val.as_bigint() {398 out.push_str(&int.to_str_radix(10));399 return Ok(());400 }401 JSON_TO_STRING.manifest_buf(val, out)402 }403 fn file_trailing_newline(&self) -> bool {404 false405 }406}407pub struct StringFormat;408impl ManifestFormat for StringFormat {409 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {410 let Val::Str(s) = val else {411 bail!(412 "output should be string for string manifest format, got {}",413 val.value_type()414 )415 };416 write!(out, "{s}").unwrap();417 Ok(())418 }419 fn file_trailing_newline(&self) -> bool {420 false421 }422}423424pub struct YamlStreamFormat<I> {425 inner: I,426 c_document_end: bool,427 end_newline: bool,428}429impl<I> YamlStreamFormat<I> {430 pub fn std_yaml_stream(inner: I, c_document_end: bool) -> Self {431 Self {432 inner,433 c_document_end,434 // Stdlib format always inserts useless newline at the end435 end_newline: true,436 }437 }438 pub fn cli(inner: I) -> Self {439 Self {440 inner,441 c_document_end: true,442 end_newline: false,443 }444 }445}446impl<I: ManifestFormat> ManifestFormat for YamlStreamFormat<I> {447 fn manifest_buf(&self, val: Val, out: &mut String) -> Result<()> {448 let Val::Arr(arr) = val else {449 bail!(450 "output should be array for yaml stream format, got {}",451 val.value_type()452 )453 };454 for (i, v) in arr.iter().enumerate() {455 if i != 0 {456 out.push('\n');457 }458 let v = v.with_description(|| format!("elem <{i}> evaluation"))?;459 out.push_str("---\n");460 in_description_frame(461 || format!("elem <{i}> manifestification"),462 || self.inner.manifest_buf(v, out),463 )?;464 }465 if self.c_document_end {466 out.push('\n');467 out.push_str("...");468 }469 if self.end_newline {470 out.push('\n');471 }472 Ok(())473 }474}475476pub fn escape_string_json(s: &str) -> String {477 let mut buf = String::new();478 escape_string_json_buf(s, &mut buf);479 buf480}481482// Json string encoding was borrowed from https://github.com/serde-rs/json483484const BB: u8 = b'b'; // \x08485const TT: u8 = b't'; // \x09486const NN: u8 = b'n'; // \x0A487const FF: u8 = b'f'; // \x0C488const RR: u8 = b'r'; // \x0D489const QU: u8 = b'"'; // \x22490const BS: u8 = b'\\'; // \x5C491const UU: u8 = b'u'; // \x00...\x1F except the ones above492const __: u8 = 0;493494// Lookup table of escape sequences. A value of b'x' at index i means that byte495// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.496static ESCAPE: [u8; 256] = [497 // 1 2 3 4 5 6 7 8 9 A B C D E F498 UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0499 UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1500 __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2501 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3502 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4503 __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5504 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6505 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7506 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8507 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9508 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A509 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B510 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C511 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D512 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E513 __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F514];515516pub fn escape_string_json_buf(value: &str, buf: &mut String) {517 buf.reserve_exact(value.len() + 2);518 buf.push('"');519 escape_string_json_buf_raw(value, buf);520 buf.push('"');521}522523fn escape_string_json_buf_raw(value: &str, buf: &mut String) {524 // Safety: we only write correct utf-8 in this function525 let buf: &mut Vec<u8> = unsafe { &mut *ptr::from_mut(buf).cast::<Vec<u8>>() };526 let bytes = value.as_bytes();527528 let mut start = 0;529530 for (i, &byte) in bytes.iter().enumerate() {531 let escape = ESCAPE[byte as usize];532 if escape == __ {533 continue;534 }535536 if start < i {537 buf.extend_from_slice(&bytes[start..i]);538 }539 start = i + 1;540541 match escape {542 self::BB | self::TT | self::NN | self::FF | self::RR | self::QU | self::BS => {543 buf.extend_from_slice(&[b'\\', escape]);544 }545 self::UU => {546 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";547 let bytes = &[548 b'\\',549 b'u',550 b'0',551 b'0',552 HEX_DIGITS[(byte >> 4) as usize],553 HEX_DIGITS[(byte & 0xF) as usize],554 ];555 buf.extend_from_slice(bytes);556 }557 _ => unreachable!(),558 }559 }560561 if start == bytes.len() {562 return;563 }564565 buf.extend_from_slice(&bytes[start..]);566}