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