1use crate::runtime::host::{abi_arg, abi_result};
8use wasmtime::{
9 ArrayRef, ArrayRefPre, ArrayType, AsContextMut, Caller, FuncType, HeapType, Linker, RefType,
10 Rooted, StructRef, StructRefPre, Val, ValType,
11};
12
13mod format;
14
15use crate::runtime::StoreData;
16use crate::runtime::fuel;
17use crate::runtime::host::{
18 host_array_vtable, host_boxed_boolean_vtable, host_boxed_number_vtable, host_object_vtable,
19 host_string_vtable, read_string_arg, register_host_fn, register_host_fn_async,
20};
21use crate::runtime::intrinsic_types::intrinsic_types;
22pub(crate) use crate::runtime::json_text::JsonValue;
23use crate::runtime::prelude::vtable::serialization::Output;
24use crate::{PackageDeclaration, Param, Span, Type, ValueKind, ValueSymbol};
25
26pub const JSON_MODULE_NAME: &str = "submilli:json";
27
28pub(super) fn install_json_module(
29 linker: &mut Linker<StoreData>,
30 string_type: &ArrayType,
31) -> wasmtime::Result<()> {
32 let engine = linker.engine().clone();
33 let raw_string_param = ValType::Ref(RefType::new(false, HeapType::from(string_type.clone())));
34
35 let object_type = crate::runtime::intrinsic_types::build_intrinsic_types(&engine)?.object;
36 let unknown_result = ValType::Ref(RefType::new(true, HeapType::from(object_type)));
37
38 let ty = FuncType::new(
39 &engine,
40 [raw_string_param.clone()],
41 [unknown_result.clone()],
42 );
43 register_host_fn(
44 linker,
45 JSON_MODULE_NAME,
46 crate::mangle::host(JSON_MODULE_NAME, "parse"),
47 ty,
48 true,
49 move |caller, params, results| -> wasmtime::Result<()> {
50 let raw = match *abi_arg(params, 0)? {
51 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
52 _ => {
53 return Err(super::host::fatal_host_error(
54 "json.parse: invalid string payload",
55 ));
56 }
57 };
58 let text = super::host::read_code_units(&mut *caller, raw, "json.parse")?;
59 fuel::charge(&mut *caller, fuel::PARSE, text.len() as u64)?;
60 let value = super::json_text::parse(&text)
65 .map_err(|e| json_parse_error(&format!("JSON.parse: {}", e.message()), &e))?;
66 let allocator = JsonUnknownAllocator::new(&mut *caller)?;
67 *abi_result(results, 0)? = allocator.allocate(&mut *caller, &value)?;
68 Ok(())
69 },
70 )?;
71
72 let escape_ty = FuncType::new(
75 &engine,
76 [raw_string_param.clone()],
77 [raw_string_param.clone()],
78 );
79 register_host_fn(
80 linker,
81 JSON_MODULE_NAME,
82 crate::mangle::host(JSON_MODULE_NAME, "stringify"),
83 escape_ty,
84 true,
85 |caller, params, results| -> wasmtime::Result<()> {
86 let raw = match *abi_arg(params, 0)? {
87 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
88 _ => {
89 return Err(super::host::fatal_host_error(
90 "JSON.stringify: invalid string payload",
91 ));
92 }
93 };
94 let len = raw.len(&mut *caller)?;
95 let _input =
96 super::limits::HostBytes::new(&caller.data().tenant_limits, u64::from(len) * 2)?;
97 let units = super::host::read_code_units(&mut *caller, raw, "JSON.stringify")?;
98 let output = crate::runtime::prelude::vtable::serialization::quoted(caller, &units)?;
99 let raw = super::host::write_code_units(&mut *caller, output.units())?;
100 *abi_result(results, 0)? = Val::AnyRef(Some(raw.to_anyref()));
101 Ok(())
102 },
103 )?;
104
105 let pretty_number_ty = FuncType::new(
106 &engine,
107 [raw_string_param.clone(), ValType::F64],
108 [raw_string_param.clone()],
109 );
110 register_host_fn(
111 linker,
112 JSON_MODULE_NAME,
113 crate::mangle::host(JSON_MODULE_NAME, "stringifyPrettyNumber"),
114 pretty_number_ty,
115 true,
116 |caller, params, results| -> wasmtime::Result<()> {
117 let spaces = match *abi_arg(params, 1)? {
118 Val::F64(bits) => stringify_space_from_number(f64::from_bits(bits)),
119 ref other => {
120 return Err(super::host::fatal_host_error(format!(
121 "json.stringifyPrettyNumber expects f64, got {other:?}"
122 )));
123 }
124 };
125 let raw = format::pretty(
126 caller,
127 abi_arg(params, 0)?,
128 spaces
129 .as_bytes()
130 .iter()
131 .map(|byte| u16::from(*byte))
132 .collect::<Vec<_>>()
133 .as_slice(),
134 )?;
135 *abi_result(results, 0)? = Val::AnyRef(Some(raw.to_anyref()));
136 Ok(())
137 },
138 )?;
139
140 let pretty_string_ty = FuncType::new(
141 &engine,
142 [raw_string_param.clone(), raw_string_param.clone()],
143 [raw_string_param.clone()],
144 );
145 register_host_fn(
146 linker,
147 JSON_MODULE_NAME,
148 crate::mangle::host(JSON_MODULE_NAME, "stringifyPrettyString"),
149 pretty_string_ty,
150 true,
151 |caller, params, results| -> wasmtime::Result<()> {
152 let raw_indent = match *abi_arg(params, 1)? {
153 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
154 _ => {
155 return Err(super::host::fatal_host_error(
156 "JSON.stringify: invalid indent payload",
157 ));
158 }
159 };
160 let len = raw_indent.len(&mut *caller)?.min(10);
161 let indent =
162 super::host::read_code_units_range(&mut *caller, raw_indent, 0, len as usize)?;
163 let raw = format::pretty(caller, abi_arg(params, 0)?, &indent)?;
164 *abi_result(results, 0)? = Val::AnyRef(Some(raw.to_anyref()));
165 Ok(())
166 },
167 )?;
168
169 let object_ref = ValType::Ref(RefType::new(false, HeapType::Struct));
170 let stringify_typed_object_ty = FuncType::new(
171 &engine,
172 [raw_string_param.clone(), ValType::I32, object_ref],
173 [raw_string_param.clone()],
174 );
175 register_host_fn_async(
176 linker,
177 JSON_MODULE_NAME,
178 crate::mangle::host(JSON_MODULE_NAME, "stringifyTypedObject"),
179 stringify_typed_object_ty,
180 true,
181 |mut caller, params, results| {
182 Box::pin(async move {
183 let package =
184 read_string_arg(caller, abi_arg(params, 0)?, "json.stringifyTypedObject")?;
185 let type_id = crate::TypeInfoId(
186 (*abi_arg(params, 1)?)
187 .i32()
188 .ok_or_else(|| wasmtime::Error::msg("json.stringifyTypedObject type id"))?
189 as u32,
190 );
191 let value = match abi_arg(params, 2)? {
192 Val::AnyRef(Some(any)) => any.unwrap_struct(&mut caller)?,
193 Val::AnyRef(None) => {
194 return Err(wasmtime::Error::msg(
195 "json.stringifyTypedObject value is null",
196 ));
197 }
198 other => {
199 return Err(wasmtime::Error::msg(format!(
200 "json.stringifyTypedObject expects object, got {other:?}"
201 )));
202 }
203 };
204 let intr = intrinsic_types(&mut *caller)?;
205 let mut remaining = crate::runtime::MAX_STRUCTURAL_WALK_NODES;
206 let typed = if contains_dynamic_object(
209 caller,
210 abi_arg(params, 2)?,
211 &intr,
212 0,
213 &mut remaining,
214 )? {
215 None
216 } else {
217 stringify_typed_object(caller, &package, type_id, value)?
218 };
219 let json = match typed {
220 Some(json) => json,
221 None => stringify_dynamic_object(caller, abi_arg(params, 2)?).await?,
222 };
223 let raw = super::host::write_code_units(&mut *caller, json.units())?;
224 *abi_result(results, 0)? = Val::AnyRef(Some(raw.to_anyref()));
225 Ok(())
226 })
227 },
228 )?;
229
230 Ok(())
231}
232
233async fn stringify_dynamic_object(
235 caller: &mut Caller<'_, StoreData>,
236 value: &Val,
237) -> wasmtime::Result<Output> {
238 let intr = intrinsic_types(&mut *caller)?;
239 let serialized = crate::runtime::prelude::vtable::object_to_json(
240 caller,
241 value,
242 &intr.raw_string,
243 &intr.string,
244 )
245 .await?;
246 let string = boxed_struct(caller, serialized, "JSON serializer result")?;
247 let raw = match string.field(&mut *caller, 1)? {
248 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
249 _ => {
250 return Err(super::host::fatal_host_error(
251 "JSON serializer returned invalid string payload",
252 ));
253 }
254 };
255 let len = raw.len(&mut *caller)?;
256 let _input = super::limits::HostBytes::new(&caller.data().tenant_limits, u64::from(len) * 2)?;
257 let units = crate::runtime::prelude::vtable::read_string_units(
258 caller,
259 &serialized,
260 "JSON.stringify dynamic object",
261 )?;
262 let mut output = Output::new(caller);
263 output.append(caller, &units)?;
264 Ok(output)
265}
266
267fn contains_dynamic_object(
270 caller: &mut Caller<'_, StoreData>,
271 value: &Val,
272 intr: &crate::runtime::intrinsic_types::IntrinsicTypes,
273 depth: u32,
274 remaining: &mut u32,
275) -> wasmtime::Result<bool> {
276 charge_json_visit(caller, remaining)?;
277 if depth >= crate::runtime::MAX_VTABLE_WALK_DEPTH {
279 return Ok(true);
280 }
281 let Val::AnyRef(Some(value)) = value else {
282 return Ok(false);
283 };
284 let Some(object) = value.as_struct(&mut *caller)? else {
285 return Ok(false);
286 };
287 let values = if object.matches_ty(&*caller, &intr.object_shape)? {
288 let names = match object.field(&mut *caller, 1)? {
289 Val::AnyRef(Some(names)) => names.unwrap_array(&mut *caller)?,
290 _ => {
291 return Err(wasmtime::Error::msg(
292 "JSON.stringify: invalid field-name array",
293 ));
294 }
295 };
296 let name_count = crate::runtime::prelude::object::field_count(
297 caller,
298 &Val::AnyRef(Some(object.to_anyref())),
299 )?;
300 fuel::charge(&mut *caller, fuel::ELEM, u64::from(name_count))?;
301 for index in 0..name_count {
302 let name = names.get(&mut *caller, index)?;
303 if crate::runtime::prelude::object::field_was_inserted(caller, &name)? {
304 return Ok(true);
305 }
306 }
307 let actual = object.field(&mut *caller, 0)?;
308 let dynamic = host_object_vtable(caller)?;
309 if let (Val::AnyRef(Some(actual)), Val::AnyRef(Some(dynamic))) = (actual, dynamic)
310 && Rooted::ref_eq(&*caller, &actual, &dynamic)?
311 {
312 return Ok(true);
313 }
314 object.field(&mut *caller, 2)?
315 } else if object.matches_ty(&*caller, &intr.array)? {
316 object.field(&mut *caller, 1)?
317 } else {
318 return Ok(false);
319 };
320 let Val::AnyRef(Some(values)) = values else {
321 return Ok(false);
322 };
323 let values = values.unwrap_array(&mut *caller)?;
324 let len = if object.matches_ty(&*caller, &intr.array)? {
325 super::array_storage::ArrayStorage::from_struct(caller, object)?.len
326 } else {
327 values.len(&mut *caller)?
328 };
329 for index in 0..len {
330 let value = values.get(&mut *caller, index)?;
331 if contains_dynamic_object(caller, &value, intr, depth + 1, remaining)? {
332 return Ok(true);
333 }
334 }
335 Ok(false)
336}
337
338fn charge_json_visit(
341 caller: &mut Caller<'_, StoreData>,
342 remaining: &mut u32,
343) -> wasmtime::Result<()> {
344 *remaining = remaining.checked_sub(1).ok_or_else(|| {
345 super::host::range_error(format!(
346 "JSON.stringify exceeds {} structural visits; serialize a smaller value or reduce shared nesting",
347 crate::runtime::MAX_STRUCTURAL_WALK_NODES,
348 ))
349 })?;
350 fuel::charge(caller, fuel::ELEM, 1)
351}
352
353#[derive(Clone, Copy, PartialEq, Eq)]
358enum Fit {
359 Matched,
360 Mismatch,
361}
362
363fn stringify_typed_object(
366 caller: &mut Caller<'_, StoreData>,
367 package: &str,
368 type_id: crate::TypeInfoId,
369 value: Rooted<StructRef>,
370) -> wasmtime::Result<Option<Output>> {
371 let (kind, _metadata) = read_type_kind(caller, package, type_id)?;
372 let crate::TypeInfoKind::Object { fields } = kind else {
373 return Err(super::host::fatal_host_error(
374 "JSON.stringifyTypedObject type is not an object",
375 ));
376 };
377 let mut remaining = crate::runtime::MAX_STRUCTURAL_WALK_NODES;
378 charge_json_visit(caller, &mut remaining)?;
379 let mut output = Output::new(caller);
380 let fit = stringify_typed_object_value(
381 caller,
382 package,
383 fields,
384 value,
385 &mut remaining,
386 0,
387 &mut output,
388 )?;
389 Ok((fit == Fit::Matched).then_some(output))
390}
391
392fn stringify_typed_object_value(
393 caller: &mut Caller<'_, StoreData>,
394 package: &str,
395 fields: Vec<crate::FieldInfo>,
396 value: Rooted<StructRef>,
397 remaining: &mut u32,
398 depth: u32,
399 output: &mut Output,
400) -> wasmtime::Result<Fit> {
401 let receiver = Val::AnyRef(Some(value.to_anyref()));
402 let intr = intrinsic_types(&mut *caller)?;
403 if !value.matches_ty(&*caller, &intr.object_shape)? {
405 return Ok(Fit::Mismatch);
406 }
407 output.append(caller, &[123])?;
408 let mut written = 0usize;
409 let mut listed = 0usize;
410 for field in fields {
412 let Some(raw) = crate::runtime::prelude::collection::object_field_present(
413 caller,
414 &receiver,
415 &field.name,
416 )?
417 else {
418 if field.optional {
419 continue;
420 }
421 return Ok(Fit::Mismatch);
422 };
423 listed = listed.saturating_add(1);
424 if super::prelude::undefined::is_undefined(caller, &raw)? {
426 continue;
427 }
428 if written != 0 {
429 output.append(caller, &[44])?;
430 }
431 written = written.saturating_add(1);
432 append_quoted_text(caller, output, &field.name)?;
433 output.append(caller, &[58])?;
434 if matches!(raw, Val::AnyRef(None)) {
435 output.append(caller, &[110, 117, 108, 108])?;
436 continue;
437 }
438 let fit = stringify_type_info_val(
439 caller,
440 package,
441 field.type_id,
442 raw,
443 remaining,
444 depth + 1,
445 output,
446 )?;
447 if fit == Fit::Mismatch {
448 return Ok(Fit::Mismatch);
449 }
450 }
451 if crate::runtime::prelude::object::data_field_count(caller, &receiver)? > listed {
454 return Ok(Fit::Mismatch);
455 }
456 output.append(caller, &[125])?;
457 Ok(Fit::Matched)
458}
459
460fn stringify_type_info_val(
461 caller: &mut Caller<'_, StoreData>,
462 package: &str,
463 type_id: crate::TypeInfoId,
464 val: Val,
465 remaining: &mut u32,
466 depth: u32,
467 output: &mut Output,
468) -> wasmtime::Result<Fit> {
469 if depth >= crate::runtime::MAX_VTABLE_WALK_DEPTH {
470 return Err(super::host::range_error(
471 "JSON.stringify type traversal exceeds 128 levels; serialize a less deeply nested value",
472 ));
473 }
474 charge_json_visit(caller, remaining)?;
475 if super::prelude::undefined::is_undefined(caller, &val)? {
478 output.append(caller, &[110, 117, 108, 108])?;
479 return Ok(Fit::Matched);
480 }
481 let (kind, _metadata) = read_type_kind(caller, package, type_id)?;
482 if !value_fits_kind(caller, &kind, &val)? {
483 return Ok(Fit::Mismatch);
484 }
485 match kind {
486 crate::TypeInfoKind::Null | crate::TypeInfoKind::Undefined => {
487 output.append(caller, &[110, 117, 108, 108])?;
488 Ok(Fit::Matched)
489 }
490 crate::TypeInfoKind::Boolean | crate::TypeInfoKind::BooleanLiteral(_) => {
491 let value = boxed_bool(caller, val)?;
492 output.append(
493 caller,
494 if value {
495 &[116, 114, 117, 101]
496 } else {
497 &[102, 97, 108, 115, 101]
498 },
499 )?;
500 Ok(Fit::Matched)
501 }
502 crate::TypeInfoKind::Number | crate::TypeInfoKind::NumberLiteral(_) => {
503 let n = boxed_number(caller, val)?;
504 if n.is_finite() {
505 append_text(caller, output, &json_number(n)?.to_string())?;
506 } else {
507 output.append(caller, &[110, 117, 108, 108])?;
508 }
509 Ok(Fit::Matched)
510 }
511 crate::TypeInfoKind::String | crate::TypeInfoKind::StringLiteral(_) => {
512 let string = boxed_struct(caller, val, "string")?;
513 let raw = match string.field(&mut *caller, 1)? {
514 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
515 _ => {
516 return Err(super::host::fatal_host_error(
517 "JSON.stringify: malformed boxed string",
518 ));
519 }
520 };
521 let count = raw.len(&mut *caller)?;
522 let _input =
523 super::limits::HostBytes::new(&caller.data().tenant_limits, u64::from(count) * 2)?;
524 let units = super::host::read_code_units(&mut *caller, raw, "JSON.stringify string")?;
525 output.append_escaped(caller, &units)?;
526 Ok(Fit::Matched)
527 }
528 crate::TypeInfoKind::Array { element } => {
529 stringify_array(caller, package, element, val, remaining, depth, output)
530 }
531 crate::TypeInfoKind::Tuple { elements, optional } => stringify_tuple(
532 caller,
533 package,
534 TupleSchema {
535 elements: &elements,
536 optional,
537 },
538 val,
539 remaining,
540 depth,
541 output,
542 ),
543 crate::TypeInfoKind::Object { fields } => {
544 let object = boxed_struct(caller, val, "object")?;
545 stringify_typed_object_value(caller, package, fields, object, remaining, depth, output)
546 }
547 crate::TypeInfoKind::Union { members }
548 if members.iter().any(|id| {
549 matches!(
550 caller
551 .data()
552 .type_info
553 .get(package)
554 .and_then(|table| table.get(*id))
555 .map(|info| &info.kind),
556 Some(crate::TypeInfoKind::Null | crate::TypeInfoKind::Undefined)
557 )
558 }) =>
559 {
560 if matches!(val, Val::AnyRef(None)) {
561 output.append(caller, &[110, 117, 108, 108])?;
562 return Ok(Fit::Matched);
563 }
564 let non_null = members
565 .iter()
566 .find(|id| {
567 !matches!(
568 caller
569 .data()
570 .type_info
571 .get(package)
572 .and_then(|table| table.get(**id))
573 .map(|info| &info.kind),
574 Some(crate::TypeInfoKind::Null | crate::TypeInfoKind::Undefined)
575 )
576 })
577 .ok_or_else(|| wasmtime::Error::msg("JSON.stringify: empty nullable union"))?;
578 stringify_type_info_val(
579 caller,
580 package,
581 *non_null,
582 val,
583 remaining,
584 depth + 1,
585 output,
586 )
587 }
588 other => Err(wasmtime::Error::msg(format!(
589 "JSON.stringify: host TypeInfo stringify for `{other:?}` is not implemented yet"
590 ))),
591 }
592}
593
594fn value_fits_kind(
597 caller: &mut Caller<'_, StoreData>,
598 kind: &crate::TypeInfoKind,
599 val: &Val,
600) -> wasmtime::Result<bool> {
601 let intr = intrinsic_types(&mut *caller)?;
602 let expected = match kind {
603 crate::TypeInfoKind::Null => return Ok(matches!(val, Val::AnyRef(None))),
604 crate::TypeInfoKind::Undefined => {
605 return super::prelude::undefined::is_undefined(caller, val);
606 }
607 crate::TypeInfoKind::Boolean | crate::TypeInfoKind::BooleanLiteral(_) => {
608 &intr.boxed_boolean
609 }
610 crate::TypeInfoKind::Number | crate::TypeInfoKind::NumberLiteral(_) => &intr.boxed_number,
611 crate::TypeInfoKind::String | crate::TypeInfoKind::StringLiteral(_) => &intr.string,
612 crate::TypeInfoKind::Array { .. } | crate::TypeInfoKind::Tuple { .. } => &intr.array,
613 crate::TypeInfoKind::Object { .. } => &intr.object_shape,
614 _ => return Ok(true),
615 };
616 is_struct_of(caller, val, expected)
617}
618
619fn is_struct_of(
620 caller: &mut Caller<'_, StoreData>,
621 val: &Val,
622 ty: &wasmtime::StructType,
623) -> wasmtime::Result<bool> {
624 let Val::AnyRef(Some(any)) = val else {
625 return Ok(false);
626 };
627 match any.as_struct(&mut *caller)? {
628 Some(object) => object.matches_ty(&*caller, ty),
629 None => Ok(false),
630 }
631}
632
633fn stringify_array(
634 caller: &mut Caller<'_, StoreData>,
635 package: &str,
636 element: crate::TypeInfoId,
637 val: Val,
638 remaining: &mut u32,
639 depth: u32,
640 output: &mut Output,
641) -> wasmtime::Result<Fit> {
642 let storage = super::array_storage::ArrayStorage::read(caller, &val)?;
643 let raw = storage.backing;
644 let len = storage.len;
645 output.append(caller, &[91])?;
646 for index in 0..len {
647 let elem = raw.get(&mut *caller, index)?;
648 if index != 0 {
649 output.append(caller, &[44])?;
650 }
651 let fit =
652 stringify_type_info_val(caller, package, element, elem, remaining, depth + 1, output)?;
653 if fit == Fit::Mismatch {
654 return Ok(Fit::Mismatch);
655 }
656 }
657 output.append(caller, &[93])?;
658 Ok(Fit::Matched)
659}
660
661struct TupleSchema<'a> {
662 elements: &'a [crate::TypeInfoId],
663 optional: usize,
664}
665
666fn stringify_tuple(
667 caller: &mut Caller<'_, StoreData>,
668 package: &str,
669 schema: TupleSchema<'_>,
670 val: Val,
671 remaining: &mut u32,
672 depth: u32,
673 output: &mut Output,
674) -> wasmtime::Result<Fit> {
675 let TupleSchema { elements, optional } = schema;
676 let storage = super::array_storage::ArrayStorage::read(caller, &val)?;
677 let raw = storage.backing;
678 let len = storage.len;
679 let minimum = elements.len().checked_sub(optional).ok_or_else(|| {
680 super::host::fatal_host_error("JSON.stringify: invalid tuple optional count")
681 })?;
682 if !(minimum..=elements.len()).contains(&(len as usize)) {
683 return Ok(Fit::Mismatch);
684 }
685 output.append(caller, &[91])?;
686 for index in 0..len {
687 let elem = raw.get(&mut *caller, index)?;
688 let element = elements.get(index as usize).copied().ok_or_else(|| {
689 super::host::fatal_host_error("JSON.stringify: tuple element metadata missing")
690 })?;
691 if index != 0 {
692 output.append(caller, &[44])?;
693 }
694 let fit =
695 stringify_type_info_val(caller, package, element, elem, remaining, depth + 1, output)?;
696 if fit == Fit::Mismatch {
697 return Ok(Fit::Mismatch);
698 }
699 }
700 output.append(caller, &[93])?;
701 Ok(Fit::Matched)
702}
703
704fn read_type_kind(
707 caller: &mut Caller<'_, StoreData>,
708 package: &str,
709 type_id: crate::TypeInfoId,
710) -> wasmtime::Result<(crate::TypeInfoKind, super::limits::HostBytes)> {
711 let info = caller
712 .data()
713 .type_info
714 .get(package)
715 .and_then(|table| table.get(type_id))
716 .ok_or_else(|| {
717 super::host::fatal_host_error(format!(
718 "JSON.stringify: missing TypeInfo for {package}#{}",
719 type_id.as_u32()
720 ))
721 })?;
722 let entries = match &info.kind {
723 crate::TypeInfoKind::Object { fields } => fields.len(),
724 crate::TypeInfoKind::Tuple { elements, .. } => elements.len(),
725 crate::TypeInfoKind::Union { members } => members.len(),
726 _ => 0,
727 };
728 fuel::charge(&mut *caller, fuel::ELEM, (entries as u64).saturating_mul(2))?;
729 let info = caller
730 .data()
731 .type_info
732 .get(package)
733 .and_then(|table| table.get(type_id))
734 .ok_or_else(|| super::host::fatal_host_error("JSON schema disappeared"))?;
735 let (_, bytes) = match &info.kind {
736 crate::TypeInfoKind::Object { fields } => {
737 let bytes = fields
738 .iter()
739 .try_fold(0u64, |total, field| {
740 total
741 .checked_add(std::mem::size_of::<crate::FieldInfo>() as u64)
742 .and_then(|n| n.checked_add(field.name.len() as u64))
743 })
744 .ok_or_else(|| super::host::fatal_host_error("JSON schema size overflow"))?;
745 (fields.len(), bytes)
746 }
747 crate::TypeInfoKind::Tuple { elements, .. } => (
748 elements.len(),
749 (elements.len() as u64) * std::mem::size_of::<crate::TypeInfoId>() as u64,
750 ),
751 crate::TypeInfoKind::Union { members } => (
752 members.len(),
753 (members.len() as u64) * std::mem::size_of::<crate::TypeInfoId>() as u64,
754 ),
755 _ => (0, 0),
756 };
757 let metadata = super::limits::HostBytes::new(&caller.data().tenant_limits, bytes)?;
758 let name_bytes = if matches!(&info.kind, crate::TypeInfoKind::Object { .. }) {
761 let records = (entries as u64)
762 .checked_mul(std::mem::size_of::<crate::FieldInfo>() as u64)
763 .ok_or_else(|| super::host::fatal_host_error("JSON schema record size overflow"))?;
764 bytes
765 .checked_sub(records)
766 .ok_or_else(|| super::host::fatal_host_error("JSON schema name size is invalid"))?
767 } else {
768 0
769 };
770 fuel::charge(&mut *caller, fuel::COPY, name_bytes)?;
771 let info = caller
772 .data()
773 .type_info
774 .get(package)
775 .and_then(|table| table.get(type_id))
776 .ok_or_else(|| super::host::fatal_host_error("JSON schema disappeared"))?;
777 let kind = match &info.kind {
778 crate::TypeInfoKind::Null => crate::TypeInfoKind::Null,
779 crate::TypeInfoKind::Undefined => crate::TypeInfoKind::Undefined,
780 crate::TypeInfoKind::Boolean | crate::TypeInfoKind::BooleanLiteral(_) => {
781 crate::TypeInfoKind::Boolean
782 }
783 crate::TypeInfoKind::Number | crate::TypeInfoKind::NumberLiteral(_) => {
784 crate::TypeInfoKind::Number
785 }
786 crate::TypeInfoKind::String | crate::TypeInfoKind::StringLiteral(_) => {
787 crate::TypeInfoKind::String
788 }
789 crate::TypeInfoKind::Array { element } => crate::TypeInfoKind::Array { element: *element },
790 crate::TypeInfoKind::Object { fields } => {
791 let mut copied = Vec::new();
792 copied
793 .try_reserve_exact(fields.len())
794 .map_err(super::host::fatal_host_error)?;
795 for field in fields {
796 let mut name = String::new();
797 name.try_reserve_exact(field.name.len())
798 .map_err(super::host::fatal_host_error)?;
799 name.push_str(&field.name);
800 copied.push(crate::FieldInfo {
801 name,
802 type_id: field.type_id,
803 optional: field.optional,
804 });
805 }
806 crate::TypeInfoKind::Object { fields: copied }
807 }
808 crate::TypeInfoKind::Tuple { elements, optional } => crate::TypeInfoKind::Tuple {
809 elements: copy_type_ids(elements)?,
810 optional: *optional,
811 },
812 crate::TypeInfoKind::Union { members } => crate::TypeInfoKind::Union {
813 members: copy_type_ids(members)?,
814 },
815 other => {
816 return Err(wasmtime::Error::msg(format!(
817 "JSON.stringify: host TypeInfo stringify for `{other:?}` is not implemented yet"
818 )));
819 }
820 };
821 Ok((kind, metadata))
822}
823
824fn copy_type_ids(ids: &[crate::TypeInfoId]) -> wasmtime::Result<Vec<crate::TypeInfoId>> {
825 let mut copied = Vec::new();
826 copied
827 .try_reserve_exact(ids.len())
828 .map_err(super::host::fatal_host_error)?;
829 copied.extend_from_slice(ids);
830 Ok(copied)
831}
832
833fn append_text(
834 caller: &mut Caller<'_, StoreData>,
835 output: &mut Output,
836 text: &str,
837) -> wasmtime::Result<()> {
838 let _input =
839 super::limits::HostBytes::new(&caller.data().tenant_limits, (text.len() as u64) * 2)?;
840 let units = text_units(caller, text)?;
841 output.append(caller, &units)
842}
843
844fn append_quoted_text(
845 caller: &mut Caller<'_, StoreData>,
846 output: &mut Output,
847 text: &str,
848) -> wasmtime::Result<()> {
849 let _input =
850 super::limits::HostBytes::new(&caller.data().tenant_limits, (text.len() as u64) * 2)?;
851 let units = text_units(caller, text)?;
852 output.append_escaped(caller, &units)
853}
854
855fn text_units(caller: &mut Caller<'_, StoreData>, text: &str) -> wasmtime::Result<Vec<u16>> {
856 fuel::charge(&mut *caller, fuel::SCAN, text.len() as u64)?;
857 let mut units = Vec::new();
858 units
859 .try_reserve_exact(text.len())
860 .map_err(super::host::fatal_host_error)?;
861 units.extend(text.encode_utf16());
862 Ok(units)
863}
864
865fn json_number(n: f64) -> wasmtime::Result<serde_json::Number> {
866 if !n.is_finite() {
867 return Err(wasmtime::Error::msg("JSON.stringify: non-finite number"));
868 }
869 if n.fract() == 0.0 && n >= i64::MIN as f64 && n <= i64::MAX as f64 {
870 return Ok(serde_json::Number::from(n as i64));
871 }
872 serde_json::Number::from_f64(n)
873 .ok_or_else(|| wasmtime::Error::msg("JSON.stringify: non-finite number"))
874}
875
876pub(crate) fn parse_json_as_unknown(
885 caller: &mut wasmtime::Caller<'_, StoreData>,
886 text: &str,
887 context: &str,
888) -> wasmtime::Result<Val> {
889 fuel::charge(&mut *caller, fuel::PARSE, text.len() as u64)?;
890 let units: Vec<u16> = text.encode_utf16().collect();
891 let value = super::json_text::parse(&units)
892 .map_err(|e| json_parse_error(&format!("{context}: {}", e.message()), &e))?;
893 let allocator = JsonUnknownAllocator::new(&mut *caller)?;
894 allocator.allocate(&mut *caller, &value)
895}
896
897fn json_parse_error(message: &str, error: &super::json_text::JsonError) -> wasmtime::Error {
900 match error {
901 super::json_text::JsonError::Range(_) => crate::runtime::host::range_error(message),
902 super::json_text::JsonError::Syntax(_) => crate::runtime::host::syntax_error(message),
903 }
904}
905
906pub(crate) fn boxed_struct(
907 ctx: &mut impl AsContextMut<Data = StoreData>,
908 val: Val,
909 name: &str,
910) -> wasmtime::Result<Rooted<StructRef>> {
911 match val {
912 Val::AnyRef(Some(any)) => any.unwrap_struct(&mut *ctx),
913 Val::AnyRef(None) => Err(wasmtime::Error::msg(format!(
914 "JSON.stringify: {name} is null"
915 ))),
916 other => Err(wasmtime::Error::msg(format!(
917 "JSON.stringify: expected boxed {name}, got {other:?}"
918 ))),
919 }
920}
921
922pub(crate) fn boxed_bool(
923 ctx: &mut impl AsContextMut<Data = StoreData>,
924 val: Val,
925) -> wasmtime::Result<bool> {
926 let s = boxed_struct(ctx, val, "boolean")?;
927 Ok(s.field(&mut *ctx, 1)?
928 .i32()
929 .ok_or_else(|| crate::runtime::host::invariant_trap("JSON ABI: expected boolean field"))?
930 != 0)
931}
932
933pub(crate) fn boxed_number(
934 ctx: &mut impl AsContextMut<Data = StoreData>,
935 val: Val,
936) -> wasmtime::Result<f64> {
937 let s = boxed_struct(ctx, val, "number")?;
938 let bits = s
939 .field(&mut *ctx, 1)?
940 .f64()
941 .ok_or_else(|| wasmtime::Error::msg("JSON.stringify: boxed number field"))?;
942 Ok(bits)
943}
944
945pub(crate) fn boxed_string(
946 ctx: &mut impl AsContextMut<Data = StoreData>,
947 val: Val,
948) -> wasmtime::Result<String> {
949 let s = boxed_struct(ctx, val, "string")?;
950 let raw = s.field(&mut *ctx, 1)?;
951 let arr = match raw {
952 Val::AnyRef(Some(any)) => any.unwrap_array(&mut *ctx)?,
953 other => {
954 return Err(wasmtime::Error::msg(format!(
955 "JSON.stringify: boxed string raw field got {other:?}"
956 )));
957 }
958 };
959 crate::runtime::read_submilli_string(&mut *ctx, arr)
960}
961
962fn stringify_space_from_number(space: f64) -> String {
963 if space.is_nan() || space <= 0.0 {
964 return String::new();
965 }
966 " ".repeat((space.floor() as usize).min(10))
967}
968
969pub(crate) struct JsonUnknownAllocator {
970 string_pre: StructRefPre,
971 boxed_number_pre: StructRefPre,
972 boxed_boolean_pre: StructRefPre,
973 array_pre: StructRefPre,
974 raw_array_pre: ArrayRefPre,
975 object_shape_pre: StructRefPre,
976 field_names_pre: ArrayRefPre,
977 object_fields_pre: ArrayRefPre,
978 string_vtable: Val,
979 boxed_number_vtable: Val,
980 boxed_boolean_vtable: Val,
981 array_vtable: Val,
982 object_vtable: Val,
983}
984
985impl JsonUnknownAllocator {
986 pub(crate) fn new(ctx: &mut impl AsContextMut<Data = StoreData>) -> wasmtime::Result<Self> {
987 let (
988 string_type,
989 boxed_number_type,
990 boxed_boolean_type,
991 array_type,
992 raw_array_type,
993 object_shape_type,
994 field_names_type,
995 object_fields_type,
996 ) = {
997 let abi =
998 ctx.as_context().data().host_abi.as_ref().ok_or_else(|| {
999 wasmtime::Error::msg("host_abi unset (prelude not instantiated)")
1000 })?;
1001 (
1002 abi.string_type.clone(),
1003 abi.boxed_number_type.clone(),
1004 abi.boxed_boolean_type.clone(),
1005 abi.array_type.clone(),
1006 abi.raw_array_type.clone(),
1007 abi.object_shape_type.clone(),
1008 abi.field_names_type.clone(),
1009 abi.object_fields_type.clone(),
1010 )
1011 };
1012 let string_vtable = host_string_vtable(ctx)?;
1013 let boxed_number_vtable = host_boxed_number_vtable(ctx)?;
1014 let boxed_boolean_vtable = host_boxed_boolean_vtable(ctx)?;
1015 let array_vtable = host_array_vtable(ctx)?;
1016 let object_vtable = host_object_vtable(ctx)?;
1017
1018 Ok(Self {
1019 string_pre: StructRefPre::new(&mut *ctx, string_type),
1020 boxed_number_pre: StructRefPre::new(&mut *ctx, boxed_number_type),
1021 boxed_boolean_pre: StructRefPre::new(&mut *ctx, boxed_boolean_type),
1022 array_pre: StructRefPre::new(&mut *ctx, array_type),
1023 raw_array_pre: ArrayRefPre::new(&mut *ctx, raw_array_type),
1024 object_shape_pre: StructRefPre::new(&mut *ctx, object_shape_type),
1025 field_names_pre: ArrayRefPre::new(&mut *ctx, field_names_type),
1026 object_fields_pre: ArrayRefPre::new(&mut *ctx, object_fields_type),
1027 string_vtable,
1028 boxed_number_vtable,
1029 boxed_boolean_vtable,
1030 array_vtable,
1031 object_vtable,
1032 })
1033 }
1034
1035 pub(crate) fn allocate(
1036 &self,
1037 ctx: &mut impl AsContextMut<Data = StoreData>,
1038 value: &JsonValue,
1039 ) -> wasmtime::Result<Val> {
1040 fuel::charge(&mut *ctx, fuel::ELEM, 1)?;
1042 match value {
1043 JsonValue::Null => Ok(Val::AnyRef(None)),
1044 JsonValue::Bool(b) => {
1045 let object = StructRef::new(
1046 &mut *ctx,
1047 &self.boxed_boolean_pre,
1048 &[self.boxed_boolean_vtable, Val::I32(i32::from(*b))],
1049 )?;
1050 Ok(Val::AnyRef(Some(object.to_anyref())))
1051 }
1052 JsonValue::Number(f) => {
1053 let object = StructRef::new(
1054 &mut *ctx,
1055 &self.boxed_number_pre,
1056 &[self.boxed_number_vtable, Val::F64(f.to_bits())],
1057 )?;
1058 Ok(Val::AnyRef(Some(object.to_anyref())))
1059 }
1060 JsonValue::String(units) => {
1061 let raw = super::host::write_code_units(&mut *ctx, units)?;
1062 let object = StructRef::new(
1063 &mut *ctx,
1064 &self.string_pre,
1065 &[
1066 self.string_vtable,
1067 Val::AnyRef(Some(raw.to_anyref())),
1068 Val::I64(0),
1069 ],
1070 )?;
1071 Ok(Val::AnyRef(Some(object.to_anyref())))
1072 }
1073 JsonValue::Array(items) => {
1074 let mut elements = Vec::new();
1075 elements
1076 .try_reserve_exact(items.len())
1077 .map_err(crate::runtime::host::fatal_host_error)?;
1078 for item in items {
1079 elements.push(self.allocate(&mut *ctx, item)?);
1080 }
1081 let raw = ArrayRef::new_fixed(&mut *ctx, &self.raw_array_pre, &elements)?;
1082 let object = StructRef::new(
1083 &mut *ctx,
1084 &self.array_pre,
1085 &[
1086 self.array_vtable,
1087 Val::AnyRef(Some(raw.to_anyref())),
1088 Val::I32(super::array_storage::checked_length(elements.len())? as i32),
1089 ],
1090 )?;
1091 Ok(Val::AnyRef(Some(object.to_anyref())))
1092 }
1093 JsonValue::Object(map) => {
1094 let mut names = Vec::new();
1095 let mut values = Vec::new();
1096 names
1097 .try_reserve_exact(map.len())
1098 .map_err(crate::runtime::host::fatal_host_error)?;
1099 values
1100 .try_reserve_exact(map.len())
1101 .map_err(crate::runtime::host::fatal_host_error)?;
1102 for (name, item) in map {
1103 let raw_name = super::host::write_code_units(&mut *ctx, &name.0)?;
1104 let name_object = StructRef::new(
1105 &mut *ctx,
1106 &self.string_pre,
1107 &[
1108 self.string_vtable,
1109 Val::AnyRef(Some(raw_name.to_anyref())),
1110 Val::I64(0),
1111 ],
1112 )?;
1113 names.push(Val::AnyRef(Some(name_object.to_anyref())));
1114 values.push(self.allocate(&mut *ctx, item)?);
1115 }
1116 let field_names = ArrayRef::new_fixed(&mut *ctx, &self.field_names_pre, &names)?;
1117 let object_fields =
1118 ArrayRef::new_fixed(&mut *ctx, &self.object_fields_pre, &values)?;
1119 let object = StructRef::new(
1120 &mut *ctx,
1121 &self.object_shape_pre,
1122 &[
1123 self.object_vtable,
1124 Val::AnyRef(Some(field_names.to_anyref())),
1125 Val::AnyRef(Some(object_fields.to_anyref())),
1126 Val::AnyRef(None),
1127 ],
1128 )?;
1129 Ok(Val::AnyRef(Some(object.to_anyref())))
1130 }
1131 }
1132 }
1133}
1134
1135pub(super) fn json_module_definitions() -> PackageDeclaration {
1136 let mut defs = PackageDeclaration::with_package(JSON_MODULE_NAME);
1137 insert_json_host_fn(&mut defs, "parse", vec![Param::new("text", Type::String)]);
1138 insert_json_host_fn(
1139 &mut defs,
1140 "stringify",
1141 vec![Param::new("value", Type::String)],
1142 );
1143 insert_json_host_fn(
1144 &mut defs,
1145 "stringifyPrettyNumber",
1146 vec![
1147 Param::new("json", Type::String),
1148 Param::new("space", Type::Number),
1149 ],
1150 );
1151 insert_json_host_fn(
1152 &mut defs,
1153 "stringifyPrettyString",
1154 vec![
1155 Param::new("json", Type::String),
1156 Param::new("space", Type::String),
1157 ],
1158 );
1159 insert_json_host_fn(
1160 &mut defs,
1161 "stringifyTypedObject",
1162 vec![
1163 Param::new("package", Type::String),
1164 Param::new("typeId", Type::Number),
1165 Param::new("value", Type::Unknown),
1166 ],
1167 );
1168 defs
1169}
1170
1171fn insert_json_host_fn(defs: &mut PackageDeclaration, name: &str, params: Vec<Param>) {
1172 defs.values.insert(
1173 name.to_string(),
1174 ValueSymbol {
1175 name: name.to_string(),
1176 mangled_name: crate::mangle::host(JSON_MODULE_NAME, name),
1177 declaration_span: Span::at(crate::FileId::JSON),
1178 kind: ValueKind::Function {
1179 generics: Vec::new(),
1180 params,
1181 ret: Type::Error,
1182 type_predicate: None,
1183 doc: None,
1184 },
1185 },
1186 );
1187}