git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

fix guard against deep recursion in toml

sonxoxzuYaroslav Bolyukin2026-04-05parent: #d5225b8.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-stdlib/src/manifest/toml.rsdiffbeforeafterboth
before · crates/jrsonnet-stdlib/src/manifest/toml.rs
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	IStr, ObjValue, Result, ResultExt, Val, bail, in_description_frame,5	manifest::{ManifestFormat, escape_string_json_buf},6	val::ArrValue,7};89pub struct TomlFormat<'s> {10	/// Padding before fields, i.e11	/// ```toml12	/// [a]13	///   b = 114	/// ## <- this15	/// ```16	padding: Cow<'s, str>,17	/// Do not emit sections for objects, consisting only from sections:18	/// ```toml19	/// # false20	/// [a]21	/// [a.b]22	///23	/// # true24	/// [a.b]25	/// ```26	skip_empty_sections: bool,27	/// If true - then order of fields is preserved as written,28	/// instead of sorting alphabetically29	#[cfg(feature = "exp-preserve-order")]30	preserve_order: bool,31}32impl TomlFormat<'_> {33	pub fn cli(34		padding: usize,35		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,36	) -> Self {37		let padding = " ".repeat(padding);38		Self {39			padding: Cow::Owned(padding),40			skip_empty_sections: true,41			#[cfg(feature = "exp-preserve-order")]42			preserve_order,43		}44	}45	pub fn std_to_toml(46		padding: String,47		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,48	) -> Self {49		Self {50			padding: Cow::Owned(padding),51			skip_empty_sections: false,52			#[cfg(feature = "exp-preserve-order")]53			preserve_order,54		}55	}56}5758fn bare_allowed(s: &str) -> bool {59	s.bytes()60		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))61}6263fn escape_key_toml_buf(key: &str, buf: &mut String) {64	if bare_allowed(key) {65		buf.push_str(key);66	} else {67		escape_string_json_buf(key, buf);68	}69}7071fn is_section(val: &Val) -> Result<bool> {72	Ok(match val {73		Val::Arr(a) => {74			if a.is_empty() {75				return Ok(false);76			}77			for e in a.iter() {78				let e = e?;79				if !matches!(e, Val::Obj(_)) {80					return Ok(false);81				}82			}83			true84		}85		Val::Obj(_) => true,86		_ => false,87	})88}8990fn manifest_value(91	val: &Val,92	inline: bool,93	buf: &mut String,94	cur_padding: &str,95	options: &TomlFormat<'_>,96) -> Result<()> {97	use std::fmt::Write;98	match val {99		Val::Bool(true) => buf.push_str("true"),100		Val::Bool(false) => buf.push_str("false"),101		Val::Str(s) => {102			escape_string_json_buf(&s.clone().into_flat(), buf);103		}104		Val::Num(n) => write!(buf, "{n}").unwrap(),105		#[cfg(feature = "exp-bigint")]106		Val::BigInt(n) => write!(buf, "{n}").unwrap(),107		Val::Arr(a) => {108			buf.push('[');109110			let mut had_items = false;111			for (i, e) in a.iter().enumerate() {112				had_items = true;113				let e = e.with_description(|| format!("elem <{i}> evaluation"))?;114115				if i != 0 {116					buf.push(',');117				}118				if inline {119					buf.push(' ');120				} else {121					buf.push('\n');122					buf.push_str(cur_padding);123					buf.push_str(&options.padding);124				}125126				in_description_frame(127					|| format!("elem <{i}> manifestification"),128					|| manifest_value(&e, true, buf, "", options),129				)?;130			}131132			if !had_items {133			} else if inline {134				buf.push(' ');135			} else {136				buf.push('\n');137				buf.push_str(cur_padding);138			}139			buf.push(']');140		}141		Val::Obj(o) => {142			o.run_assertions()?;143			buf.push('{');144145			let mut had_fields = false;146			for (i, (k, v)) in o147				.iter(148					#[cfg(feature = "exp-preserve-order")]149					options.preserve_order,150				)151				.enumerate()152			{153				had_fields = true;154				let v = v.with_description(|| format!("field <{k}> evaluation"))?;155156				if i != 0 {157					buf.push(',');158				}159				buf.push(' ');160161				escape_key_toml_buf(&k, buf);162				buf.push_str(" = ");163				in_description_frame(164					|| format!("field <{k}> manifestification"),165					|| manifest_value(&v, true, buf, "", options),166				)?;167			}168169			if had_fields {170				buf.push(' ');171			}172173			buf.push('}');174		}175		Val::Null => {176			bail!("tried to manifest null")177		}178		Val::Func(_) => {179			bail!("tried to manifest function")180		}181	}182	Ok(())183}184185fn manifest_table_internal(186	obj: &ObjValue,187	path: &mut Vec<IStr>,188	buf: &mut String,189	cur_padding: &mut String,190	options: &TomlFormat<'_>,191) -> Result<()> {192	let mut sections = Vec::new();193	let mut first = true;194	for (key, value) in obj.iter(195		#[cfg(feature = "exp-preserve-order")]196		options.preserve_order,197	) {198		let value = value.with_description(|| format!("field <{key}> evaluation"))?;199		if is_section(&value)? {200			sections.push((key, value));201		} else {202			if !first {203				buf.push('\n');204			}205			first = false;206			buf.push_str(cur_padding);207			escape_key_toml_buf(&key, buf);208			buf.push_str(" = ");209			in_description_frame(210				|| format!("table <{key}> manifestification"),211				|| manifest_value(&value, false, buf, cur_padding, options),212			)?;213		}214	}215	for (k, v) in sections {216		if !first {217			buf.push_str("\n\n");218		}219		first = false;220		path.push(k.clone());221		in_description_frame(222			|| format!("section <{k}> manifestification"),223			|| match v {224				Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),225				Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),226				_ => unreachable!("iterating over sections"),227			},228		)?;229		path.pop();230	}231	Ok(())232}233234fn manifest_table(235	obj: &ObjValue,236	path: &mut Vec<IStr>,237	buf: &mut String,238	cur_padding: &mut String,239	options: &TomlFormat<'_>,240) -> Result<()> {241	if options.skip_empty_sections242		&& !obj.is_empty()243		&& obj244			.iter(245				#[cfg(feature = "exp-preserve-order")]246				false,247			)248			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?249	{250		manifest_table_internal(obj, path, buf, cur_padding, options)?;251		return Ok(());252	}253	buf.push_str(cur_padding);254	buf.push('[');255	for (i, k) in path.iter().enumerate() {256		if i != 0 {257			buf.push('.');258		}259		escape_key_toml_buf(k, buf);260	}261	buf.push(']');262	if obj.is_empty() {263		return Ok(());264	}265	buf.push('\n');266	let prev_len = cur_padding.len();267	cur_padding.push_str(&options.padding);268	manifest_table_internal(obj, path, buf, cur_padding, options)?;269	cur_padding.truncate(prev_len);270	Ok(())271}272fn manifest_table_array(273	arr: &ArrValue,274	path: &mut Vec<IStr>,275	buf: &mut String,276	cur_padding: &mut String,277	options: &TomlFormat<'_>,278) -> Result<()> {279	let mut formatted_path = String::new();280	{281		formatted_path.push_str(cur_padding);282		formatted_path.push_str("[[");283		for (i, k) in path.iter().enumerate() {284			if i != 0 {285				formatted_path.push('.');286			}287			escape_key_toml_buf(k, &mut formatted_path);288		}289		formatted_path.push_str("]]");290	}291	let prev_len = cur_padding.len();292	cur_padding.push_str(&options.padding);293	for (i, e) in arr.iter().enumerate() {294		let obj = e.expect("already tested").as_obj().expect("already tested");295		if i != 0 {296			buf.push_str("\n\n");297		}298		buf.push_str(&formatted_path);299		if obj.is_empty() {300			continue;301		}302		buf.push('\n');303		manifest_table_internal(&obj, path, buf, cur_padding, options)?;304	}305	cur_padding.truncate(prev_len);306	Ok(())307}308309impl ManifestFormat for TomlFormat<'_> {310	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {311		match val {312			Val::Obj(obj) => {313				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)314			}315			_ => bail!("toml body should be object"),316		}317	}318}
after · crates/jrsonnet-stdlib/src/manifest/toml.rs
1use std::borrow::Cow;23use jrsonnet_evaluator::{4	Error, IStr, ObjValue, Result, ResultExt, Val, bail, ensure_sufficient_stack, in_description_frame, manifest::{ManifestFormat, escape_string_json_buf}, val::ArrValue5};67pub struct TomlFormat<'s> {8	/// Padding before fields, i.e9	/// ```toml10	/// [a]11	///   b = 112	/// ## <- this13	/// ```14	padding: Cow<'s, str>,15	/// Do not emit sections for objects, consisting only from sections:16	/// ```toml17	/// # false18	/// [a]19	/// [a.b]20	///21	/// # true22	/// [a.b]23	/// ```24	skip_empty_sections: bool,25	/// If true - then order of fields is preserved as written,26	/// instead of sorting alphabetically27	#[cfg(feature = "exp-preserve-order")]28	preserve_order: bool,29}30impl TomlFormat<'_> {31	pub fn cli(32		padding: usize,33		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,34	) -> Self {35		let padding = " ".repeat(padding);36		Self {37			padding: Cow::Owned(padding),38			skip_empty_sections: true,39			#[cfg(feature = "exp-preserve-order")]40			preserve_order,41		}42	}43	pub fn std_to_toml(44		padding: String,45		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,46	) -> Self {47		Self {48			padding: Cow::Owned(padding),49			skip_empty_sections: false,50			#[cfg(feature = "exp-preserve-order")]51			preserve_order,52		}53	}54}5556fn bare_allowed(s: &str) -> bool {57	s.bytes()58		.all(|c| matches!(c, b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'_' | b'-'))59}6061fn escape_key_toml_buf(key: &str, buf: &mut String) {62	if bare_allowed(key) {63		buf.push_str(key);64	} else {65		escape_string_json_buf(key, buf);66	}67}6869fn is_section(val: &Val) -> Result<bool> {70	Ok(match val {71		Val::Arr(a) => {72			if a.is_empty() {73				return Ok(false);74			}75			for e in a.iter() {76				let e = e?;77				if !matches!(e, Val::Obj(_)) {78					return Ok(false);79				}80			}81			true82		}83		Val::Obj(_) => true,84		_ => false,85	})86}8788fn manifest_value(89	val: &Val,90	inline: bool,91	buf: &mut String,92	cur_padding: &str,93	options: &TomlFormat<'_>,94) -> Result<()> {95	use std::fmt::Write;96	match val {97		Val::Bool(true) => buf.push_str("true"),98		Val::Bool(false) => buf.push_str("false"),99		Val::Str(s) => {100			escape_string_json_buf(&s.clone().into_flat(), buf);101		}102		Val::Num(n) => write!(buf, "{n}").unwrap(),103		#[cfg(feature = "exp-bigint")]104		Val::BigInt(n) => write!(buf, "{n}").unwrap(),105		Val::Arr(a) => ensure_sufficient_stack(|| {106			buf.push('[');107108			let mut had_items = false;109			for (i, e) in a.iter().enumerate() {110				had_items = true;111				let e = e.with_description(|| format!("elem <{i}> evaluation"))?;112113				if i != 0 {114					buf.push(',');115				}116				if inline {117					buf.push(' ');118				} else {119					buf.push('\n');120					buf.push_str(cur_padding);121					buf.push_str(&options.padding);122				}123124				in_description_frame(125					|| format!("elem <{i}> manifestification"),126					|| manifest_value(&e, true, buf, "", options),127				)?;128			}129130			if !had_items {131			} else if inline {132				buf.push(' ');133			} else {134				buf.push('\n');135				buf.push_str(cur_padding);136			}137			buf.push(']');138			Ok::<_, Error>(())139		})?,140		Val::Obj(o) => ensure_sufficient_stack(|| {141			o.run_assertions()?;142			buf.push('{');143144			let mut had_fields = false;145			for (i, (k, v)) in o146				.iter(147					#[cfg(feature = "exp-preserve-order")]148					options.preserve_order,149				)150				.enumerate()151			{152				had_fields = true;153				let v = v.with_description(|| format!("field <{k}> evaluation"))?;154155				if i != 0 {156					buf.push(',');157				}158				buf.push(' ');159160				escape_key_toml_buf(&k, buf);161				buf.push_str(" = ");162				in_description_frame(163					|| format!("field <{k}> manifestification"),164					|| manifest_value(&v, true, buf, "", options),165				)?;166			}167168			if had_fields {169				buf.push(' ');170			}171172			buf.push('}');173			Ok::<_, Error>(())174		})?,175		Val::Null => {176			bail!("tried to manifest null")177		}178		Val::Func(_) => {179			bail!("tried to manifest function")180		}181	}182	Ok(())183}184185fn manifest_table_internal(186	obj: &ObjValue,187	path: &mut Vec<IStr>,188	buf: &mut String,189	cur_padding: &mut String,190	options: &TomlFormat<'_>,191) -> Result<()> {192	let mut sections = Vec::new();193	let mut first = true;194	for (key, value) in obj.iter(195		#[cfg(feature = "exp-preserve-order")]196		options.preserve_order,197	) {198		let value = value.with_description(|| format!("field <{key}> evaluation"))?;199		if is_section(&value)? {200			sections.push((key, value));201		} else {202			if !first {203				buf.push('\n');204			}205			first = false;206			buf.push_str(cur_padding);207			escape_key_toml_buf(&key, buf);208			buf.push_str(" = ");209			in_description_frame(210				|| format!("table <{key}> manifestification"),211				|| manifest_value(&value, false, buf, cur_padding, options),212			)?;213		}214	}215	for (k, v) in sections {216		if !first {217			buf.push_str("\n\n");218		}219		first = false;220		path.push(k.clone());221		ensure_sufficient_stack(|| in_description_frame(222			|| format!("section <{k}> manifestification"),223			|| match v {224				Val::Obj(obj) => manifest_table(&obj, path, buf, cur_padding, options),225				Val::Arr(arr) => manifest_table_array(&arr, path, buf, cur_padding, options),226				_ => unreachable!("iterating over sections"),227			},228		))?;229		path.pop();230	}231	Ok(())232}233234fn manifest_table(235	obj: &ObjValue,236	path: &mut Vec<IStr>,237	buf: &mut String,238	cur_padding: &mut String,239	options: &TomlFormat<'_>,240) -> Result<()> {241	if options.skip_empty_sections242		&& !obj.is_empty()243		&& obj244			.iter(245				#[cfg(feature = "exp-preserve-order")]246				false,247			)248			.try_fold(true, |c, (_, v)| Ok(c && is_section(&v?)?) as Result<bool>)?249	{250		manifest_table_internal(obj, path, buf, cur_padding, options)?;251		return Ok(());252	}253	buf.push_str(cur_padding);254	buf.push('[');255	for (i, k) in path.iter().enumerate() {256		if i != 0 {257			buf.push('.');258		}259		escape_key_toml_buf(k, buf);260	}261	buf.push(']');262	if obj.is_empty() {263		return Ok(());264	}265	buf.push('\n');266	let prev_len = cur_padding.len();267	cur_padding.push_str(&options.padding);268	manifest_table_internal(obj, path, buf, cur_padding, options)?;269	cur_padding.truncate(prev_len);270	Ok(())271}272fn manifest_table_array(273	arr: &ArrValue,274	path: &mut Vec<IStr>,275	buf: &mut String,276	cur_padding: &mut String,277	options: &TomlFormat<'_>,278) -> Result<()> {279	let mut formatted_path = String::new();280	{281		formatted_path.push_str(cur_padding);282		formatted_path.push_str("[[");283		for (i, k) in path.iter().enumerate() {284			if i != 0 {285				formatted_path.push('.');286			}287			escape_key_toml_buf(k, &mut formatted_path);288		}289		formatted_path.push_str("]]");290	}291	let prev_len = cur_padding.len();292	cur_padding.push_str(&options.padding);293	for (i, e) in arr.iter().enumerate() {294		let obj = e.expect("already tested").as_obj().expect("already tested");295		if i != 0 {296			buf.push_str("\n\n");297		}298		buf.push_str(&formatted_path);299		if obj.is_empty() {300			continue;301		}302		buf.push('\n');303		manifest_table_internal(&obj, path, buf, cur_padding, options)?;304	}305	cur_padding.truncate(prev_len);306	Ok(())307}308309impl ManifestFormat for TomlFormat<'_> {310	fn manifest_buf(&self, val: Val, buf: &mut String) -> jrsonnet_evaluator::Result<()> {311		match val {312			Val::Obj(obj) => {313				manifest_table_internal(&obj, &mut Vec::new(), buf, &mut String::new(), self)314			}315			_ => bail!("toml body should be object"),316		}317	}318}