1use std::future::Future;
4use std::pin::Pin;
5
6use wasmtime::{
7 AnyRef, ArrayRef, ArrayRefPre, ArrayType, AsContext, AsContextMut, Caller, Engine, FieldType,
8 FuncType, Global, HeapType, Linker, Mutability, RefType, Rooted, StorageType, Store, StructRef,
9 StructRefPre, StructType, Val, ValType,
10};
11
12use crate::runtime::fuel::{self, charge_host_fuel};
13use crate::runtime::intrinsic_types::{build_intrinsic_types, intrinsic_types};
14pub(crate) use crate::runtime::intrinsic_types::{
15 intrinsic_array_type, intrinsic_bigint_type, intrinsic_string_type, intrinsic_uint8_array_type,
16};
17use crate::runtime::number::{parse_float_js, parse_int_with_radix, string_to_number_js};
18use crate::runtime::{StoreData, read_submilli_string};
19use crate::stdlib::Stdlib;
20use crate::{PackageDeclaration, Param, Span, Type, ValueKind, ValueSymbol};
21
22pub const NUMBER_MODULE_NAME: &str = "submilli:number";
23
24pub const INTERNAL_MODULE_NAME: &str = "__submilli_internal";
26
27pub async fn install_async(
32 linker: &mut Linker<StoreData>,
33 store: &mut Store<StoreData>,
34) -> wasmtime::Result<()> {
35 install_async_for(linker, store, Stdlib::core()).await
36}
37
38pub async fn install_async_for(
40 linker: &mut Linker<StoreData>,
41 store: &mut Store<StoreData>,
42 stdlib: Stdlib,
43) -> wasmtime::Result<()> {
44 install_host_functions_for(linker, stdlib)?;
45 install_store_bound(linker, store)
46}
47
48pub fn install_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
49 install_host_functions_for(linker, Stdlib::core())
50}
51
52pub fn install_host_functions_for(
54 linker: &mut Linker<StoreData>,
55 stdlib: Stdlib,
56) -> wasmtime::Result<()> {
57 install_core_host_functions(linker)?;
58 stdlib.install_host_functions(linker)?;
59 super::mcp::install_mcp_async(linker)?;
60 super::tools::install_tools_async(linker)?;
61 Ok(())
62}
63
64pub fn install_store_bound(
67 linker: &mut Linker<StoreData>,
68 store: &mut Store<StoreData>,
69) -> wasmtime::Result<()> {
70 install_prelude(linker, store)?;
71 crate::stdlib::install_store_bound(linker, store)
72}
73
74fn install_prelude(
78 linker: &mut Linker<StoreData>,
79 store: &mut Store<StoreData>,
80) -> wasmtime::Result<()> {
81 let (vtables, error_host) = super::prelude::install_vtables(linker, store)?;
82 let intr = build_intrinsic_types(store.engine())?;
83 let map_tombstone =
84 super::prelude::map::build_tombstone(store, intr.object.clone(), vtables.object)?;
85 let collection_null =
86 super::prelude::map::build_tombstone(store, intr.object.clone(), vtables.object)?;
87 let temporal = super::prelude::temporal::install_abi(linker, store, &intr)?;
88 let error_subclass_type =
89 super::prelude::error::build_error_subclass_types(store.engine(), &intr)?.1;
90 let map_backing_type = super::prelude::map::map_backing_struct(store.engine(), &intr)?;
91 let set_backing_type = super::prelude::set::set_backing_struct(store.engine(), &intr)?;
92 let member_functions = super::prelude::member::functions(linker, store);
93 store.data_mut().host_abi = Some(HostAbi {
94 member_functions,
95 string_type: intr.string,
96 uint8_type: intr.uint8_array,
97 array_type: intr.array,
98 raw_array_type: intr.raw_array,
99 boxed_number_type: intr.boxed_number,
100 boxed_boolean_type: intr.boxed_boolean,
101 field_names_type: intr.field_names,
102 object_shape_type: intr.object_shape,
103 object_fields_type: intr.object_fields,
104 map_backing_type,
105 set_backing_type,
106 string_vtable: vtables.string,
107 array_vtable: vtables.array,
108 object_vtable: vtables.object,
109 boxed_number_vtable: vtables.boxed_number,
110 boxed_boolean_vtable: vtables.boxed_boolean,
111 uint8_array_vtable: vtables.uint8_array,
112 bigint_vtable: vtables.bigint,
113 closure_vtable: vtables.closure,
114 regex_vtable: vtables.regex,
115 opaque_vtable: vtables.opaque,
116 temporal,
117 map_tombstone,
118 collection_null,
119 error_type: intr.error,
120 error_subclass_type,
121 error_vtable: error_host.vtable,
122 range_error_vtable: error_host.range_vtable,
123 quota_exceeded_vtable: error_host.quota_exceeded_vtable,
124 type_error_vtable: error_host.type_vtable,
125 syntax_error_vtable: error_host.syntax_vtable,
126 uri_error_vtable: error_host.uri_vtable,
127 reference_error_vtable: error_host.reference_vtable,
128 permission_denied_vtable: error_host.permission_denied_vtable,
129 timeout_error_vtable: error_host.timeout_vtable,
130 error_field_names: error_host.field_names,
131 permission_denied_field_names: error_host.permission_denied_field_names,
132 error_tag: error_host.tag,
133 });
134 Ok(())
135}
136
137fn install_core_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
138 let engine = linker.engine().clone();
139
140 let string_type = string_array_type(&engine);
149
150 install_number_module(linker)?;
151 super::prelude::install(linker)?;
152 super::prelude::bigint::ops::install(linker, &string_type)?;
153 install_internal_module(linker, &string_type)?;
154 super::json::install_json_module(linker, &string_type)?;
155 Ok(())
156}
157
158fn install_internal_module(
159 linker: &mut Linker<StoreData>,
160 string_type: &ArrayType,
161) -> wasmtime::Result<()> {
162 use base64::Engine as _;
163 use base64::alphabet;
164 use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig, NO_PAD, PAD};
165
166 let engine = linker.engine().clone();
167
168 let raw_uint8_array_type = uint8_array_array_type(&engine);
169 let raw_uint8_array_param = ValType::Ref(RefType::new(
170 false,
171 HeapType::from(raw_uint8_array_type.clone()),
172 ));
173 let raw_uint8_array_result = raw_uint8_array_param.clone();
174 let raw_string_param = ValType::Ref(RefType::new(false, HeapType::from(string_type.clone())));
175 let raw_string_result = raw_string_param.clone();
176
177 let ty = FuncType::new(
178 &engine,
179 [raw_uint8_array_param.clone(), ValType::I32, ValType::I32],
180 [raw_string_result.clone()],
181 );
182 register_host_fn(
183 linker,
184 INTERNAL_MODULE_NAME,
185 crate::mangle::host(INTERNAL_MODULE_NAME, "uint8array_to_base64"),
186 ty,
187 |caller, params, results| -> wasmtime::Result<()> {
188 let bytes =
189 read_uint8_array_arg(&mut *caller, abi_arg(params, 0)?, "uint8array_to_base64")?;
190 let alphabet_flag = (*abi_arg(params, 1)?)
191 .i32()
192 .ok_or_else(|| invariant_trap("host ABI: expected i32"))?;
193 let omit_padding = (*abi_arg(params, 2)?)
194 .i32()
195 .ok_or_else(|| invariant_trap("host ABI: expected i32"))?
196 != 0;
197 let alphabet = if alphabet_flag == 1 {
198 &alphabet::URL_SAFE
199 } else {
200 &alphabet::STANDARD
201 };
202 let config: GeneralPurposeConfig = if omit_padding { NO_PAD } else { PAD };
203 let engine = GeneralPurpose::new(alphabet, config);
204 fuel::charge(&mut *caller, fuel::SCAN, bytes.len() as u64)?;
205 let encoded = engine.encode(&bytes);
206 let arr = write_submilli_string(&mut *caller, &encoded)?;
207 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
208 Ok(())
209 },
210 )?;
211
212 let ty = FuncType::new(
213 &engine,
214 [raw_string_param.clone(), ValType::I32],
215 [raw_uint8_array_result.clone()],
216 );
217 let raw_uint8_array_type_for_decode = raw_uint8_array_type.clone();
218 register_host_fn(
219 linker,
220 INTERNAL_MODULE_NAME,
221 crate::mangle::host(INTERNAL_MODULE_NAME, "uint8array_from_base64"),
222 ty,
223 move |caller, params, results| -> wasmtime::Result<()> {
224 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "uint8array_from_base64")?;
225 fuel::charge(&mut *caller, fuel::SCAN, s.len() as u64)?;
226 let alphabet_flag = (*abi_arg(params, 1)?)
227 .i32()
228 .ok_or_else(|| invariant_trap("host ABI: expected i32"))?;
229 let alphabet = if alphabet_flag == 1 {
230 &alphabet::URL_SAFE
231 } else {
232 &alphabet::STANDARD
233 };
234 let config = if s.as_bytes().last() == Some(&b'=') {
236 PAD
237 } else {
238 NO_PAD
239 };
240 let decoded = GeneralPurpose::new(alphabet, config).decode(s.as_bytes());
241 let bytes =
242 decoded.map_err(|e| wasmtime::Error::msg(format!("Uint8Array.fromBase64: {e}")))?;
243 let arr = write_uint8_array(
244 &mut *caller,
245 raw_uint8_array_type_for_decode.clone(),
246 &bytes,
247 )?;
248 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
249 Ok(())
250 },
251 )?;
252
253 let ty = FuncType::new(
254 &engine,
255 [raw_string_param.clone()],
256 [raw_uint8_array_result.clone()],
257 );
258 let raw_uint8_array_type_for_encode = raw_uint8_array_type.clone();
259 register_host_fn(
260 linker,
261 INTERNAL_MODULE_NAME,
262 crate::mangle::host(INTERNAL_MODULE_NAME, "textencoder_encode"),
263 ty,
264 move |caller, params, results| -> wasmtime::Result<()> {
265 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "textencoder_encode")?;
266 let bytes = s.into_bytes();
267 let arr = write_uint8_array(
268 &mut *caller,
269 raw_uint8_array_type_for_encode.clone(),
270 &bytes,
271 )?;
272 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
273 Ok(())
274 },
275 )?;
276
277 let ty = FuncType::new(
278 &engine,
279 [raw_uint8_array_param],
280 [raw_string_result.clone()],
281 );
282 register_host_fn(
283 linker,
284 INTERNAL_MODULE_NAME,
285 crate::mangle::host(INTERNAL_MODULE_NAME, "textdecoder_decode"),
286 ty,
287 |caller, params, results| -> wasmtime::Result<()> {
288 let bytes =
289 read_uint8_array_arg(&mut *caller, abi_arg(params, 0)?, "textdecoder_decode")?;
290 let s = std::str::from_utf8(&bytes).map_err(|e| {
291 type_error(format!(
292 "TextDecoder.decode: invalid UTF-8 at byte {}: {e}",
293 e.valid_up_to(),
294 ))
295 })?;
296 let arr = write_submilli_string(&mut *caller, s)?;
297 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
298 Ok(())
299 },
300 )?;
301
302 Ok(())
303}
304
305pub(crate) fn write_uint8_array(
308 mut ctx: impl AsContextMut<Data = StoreData>,
309 array_ty: ArrayType,
310 bytes: &[u8],
311) -> wasmtime::Result<Rooted<ArrayRef>> {
312 fuel::charge(&mut ctx, fuel::COPY, bytes.len() as u64)?;
313 write_uint8_array_precharged(ctx, array_ty, bytes)
314}
315
316pub(crate) fn write_uint8_array_precharged(
318 mut ctx: impl AsContextMut<Data = StoreData>,
319 array_ty: ArrayType,
320 bytes: &[u8],
321) -> wasmtime::Result<Rooted<ArrayRef>> {
322 let pre = ArrayRefPre::new(&mut ctx, array_ty);
323 ArrayRef::new_from_i8_slice(&mut ctx, &pre, bytes)
324}
325
326pub(crate) fn uint8_array_array_type(engine: &Engine) -> ArrayType {
327 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I8))
328}
329
330pub(crate) fn read_uint8_array_arg(
331 caller: &mut Caller<'_, StoreData>,
332 val: &Val,
333 name: &str,
334) -> wasmtime::Result<Vec<u8>> {
335 let arr = uint8_array_backing(caller, val, name)?;
336 let len = usize::try_from(arr.len(&mut *caller)?).map_err(fatal_host_error)?;
337 read_uint8_array_range(caller, arr, 0, len, name)
338}
339
340pub(crate) fn read_uint8_array_range(
344 caller: &mut Caller<'_, StoreData>,
345 arr: Rooted<ArrayRef>,
346 offset: usize,
347 len: usize,
348 name: &str,
349) -> wasmtime::Result<Vec<u8>> {
350 fuel::charge(&mut *caller, fuel::COPY, len as u64)?;
351 let offset = u32::try_from(offset).map_err(fatal_host_error)?;
352 let mut out = Vec::new();
353 out.try_reserve_exact(len).map_err(fatal_host_error)?;
354 out.resize(len, 0);
355 arr.read_i8(&mut *caller, offset, &mut out)
356 .map_err(|error| type_error(format!("{name}: {error}")))?;
357 Ok(out)
358}
359
360pub(crate) fn read_uint8(
362 caller: &mut Caller<'_, StoreData>,
363 arr: Rooted<ArrayRef>,
364 index: usize,
365) -> wasmtime::Result<u8> {
366 let index = u32::try_from(index).map_err(fatal_host_error)?;
367 match arr.get(&mut *caller, index).map_err(fatal_host_error)? {
368 Val::I32(byte) => Ok(byte as u8),
369 other => Err(fatal_host_error(format!("byte {index} is {other:?}"))),
370 }
371}
372
373pub(crate) fn uint8_array_backing(
375 caller: &mut Caller<'_, StoreData>,
376 val: &Val,
377 name: &str,
378) -> wasmtime::Result<Rooted<ArrayRef>> {
379 let any = match val {
380 Val::AnyRef(Some(any)) => *any,
381 Val::AnyRef(None) => {
382 return Err(type_error(format!("{name} arg is null")));
383 }
384 other => {
385 return Err(type_error(format!(
386 "{name} expects a Uint8Array, got {other:?}"
387 )));
388 }
389 };
390 let arr = if let Some(st) = any.as_struct(&mut *caller)? {
393 let uint8_ty = intrinsic_types(&mut *caller)?.uint8_array.clone();
397 if !st.matches_ty(&*caller, &uint8_ty)? {
398 return Err(type_error(format!("{name} expects a Uint8Array")));
399 }
400 match st.field(&mut *caller, 1)? {
401 Val::AnyRef(Some(inner)) => inner.unwrap_array(&mut *caller)?,
402 other => {
403 return Err(wasmtime::Error::msg(format!(
404 "{name}: malformed $Uint8Array payload {other:?}"
405 )));
406 }
407 }
408 } else {
409 any.unwrap_array(&mut *caller)?
410 };
411 Ok(arr)
412}
413
414fn install_number_module(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
415 type FormatOp = fn(f64, f64) -> Result<String, String>;
416 let engine = linker.engine().clone();
417 let string_struct = intrinsic_string_type(&engine)?;
418 let string_struct_result =
419 ValType::Ref(RefType::new(false, HeapType::ConcreteStruct(string_struct)));
420 let string_struct_param = string_struct_result.clone();
421
422 let to_string_ty = FuncType::new(&engine, [ValType::F64], [string_struct_result.clone()]);
423 register_host_fn(
424 linker,
425 NUMBER_MODULE_NAME,
426 crate::mangle::host(NUMBER_MODULE_NAME, "toString"),
427 to_string_ty,
428 |caller, params, results| -> wasmtime::Result<()> {
429 let n = match *abi_arg(params, 0)? {
430 Val::F64(bits) => f64::from_bits(bits),
431 ref other => {
432 return Err(type_error(format!(
433 "number.toString expects f64, got {other:?}"
434 )));
435 }
436 };
437 let s = format_number(n);
438 let st = write_submilli_string_struct(caller, &s)?;
439 *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
440 Ok(())
441 },
442 )?;
443
444 let to_number_ty = FuncType::new(&engine, [string_struct_param.clone()], [ValType::F64]);
445 register_host_fn(
446 linker,
447 NUMBER_MODULE_NAME,
448 crate::mangle::host(NUMBER_MODULE_NAME, "toNumber"),
449 to_number_ty,
450 |caller, params, results| -> wasmtime::Result<()> {
451 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "number.toNumber")?;
452 let value = string_to_number_js(&s);
453 *abi_result(results, 0)? = Val::F64(value.to_bits());
454 Ok(())
455 },
456 )?;
457
458 let parse_int_ty = FuncType::new(
459 &engine,
460 [string_struct_param.clone(), ValType::F64],
461 [ValType::F64],
462 );
463 register_host_fn(
464 linker,
465 NUMBER_MODULE_NAME,
466 crate::mangle::host(NUMBER_MODULE_NAME, "parseInt"),
467 parse_int_ty,
468 |caller, params, results| -> wasmtime::Result<()> {
469 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "number.parseInt")?;
470 let radix = match *abi_arg(params, 1)? {
471 Val::F64(bits) => f64::from_bits(bits),
472 ref other => {
473 return Err(type_error(format!(
474 "number.parseInt expects f64 radix, got {other:?}"
475 )));
476 }
477 };
478 let value = parse_int_with_radix(&s, radix);
479 *abi_result(results, 0)? = Val::F64(value.to_bits());
480 Ok(())
481 },
482 )?;
483
484 let parse_float_ty = FuncType::new(&engine, [string_struct_param], [ValType::F64]);
485 register_host_fn(
486 linker,
487 NUMBER_MODULE_NAME,
488 crate::mangle::host(NUMBER_MODULE_NAME, "parseFloat"),
489 parse_float_ty,
490 |caller, params, results| -> wasmtime::Result<()> {
491 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "number.parseFloat")?;
492 let value = parse_float_js(&s);
493 *abi_result(results, 0)? = Val::F64(value.to_bits());
494 Ok(())
495 },
496 )?;
497
498 let format_ty = FuncType::new(
501 &engine,
502 [ValType::F64, ValType::F64],
503 [string_struct_result],
504 );
505 for (name, op) in [
506 ("toFixed", crate::runtime::number::to_fixed_js as FormatOp),
507 ("toPrecision", crate::runtime::number::to_precision_js),
508 ("toExponential", crate::runtime::number::to_exponential_js),
509 ("toStringRadix", crate::runtime::number::to_string_radix_js),
510 ] {
511 register_number_formatter(linker, name, format_ty.clone(), op)?;
512 }
513
514 Ok(())
515}
516
517fn register_number_formatter(
518 linker: &mut Linker<StoreData>,
519 name: &'static str,
520 ty: FuncType,
521 op: fn(f64, f64) -> Result<String, String>,
522) -> wasmtime::Result<()> {
523 register_host_fn(
524 linker,
525 NUMBER_MODULE_NAME,
526 crate::mangle::host(NUMBER_MODULE_NAME, name),
527 ty,
528 move |caller, params, results| -> wasmtime::Result<()> {
529 let (Val::F64(x_bits), Val::F64(arg_bits)) = (abi_arg(params, 0)?, abi_arg(params, 1)?)
530 else {
531 wasmtime::bail!("number.{name} expects (f64, f64)");
532 };
533 let formatted = op(f64::from_bits(*x_bits), f64::from_bits(*arg_bits))
534 .map_err(wasmtime::Error::msg)?;
535 let st = write_submilli_string_struct(caller, &formatted)?;
536 *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
537 Ok(())
538 },
539 )
540}
541
542fn format_number(n: f64) -> String {
544 if n.is_nan() {
545 "NaN".to_string()
546 } else if n.is_infinite() {
547 if n > 0.0 {
548 "Infinity".to_string()
549 } else {
550 "-Infinity".to_string()
551 }
552 } else if n == 0.0 {
553 "0".to_string()
556 } else {
557 n.to_string()
558 }
559}
560
561fn read_string_from_anyref(
564 caller: &mut Caller<'_, StoreData>,
565 any: Rooted<AnyRef>,
566 name: &str,
567) -> wasmtime::Result<String> {
568 let arr = if let Some(st) = any.as_struct(&mut *caller)? {
569 match st.field(&mut *caller, 1)? {
570 Val::AnyRef(Some(inner)) => inner.unwrap_array(&mut *caller)?,
571 other => {
572 return Err(wasmtime::Error::msg(format!(
573 "{name}: malformed $string payload {other:?}"
574 )));
575 }
576 }
577 } else {
578 any.unwrap_array(&mut *caller)?
579 };
580 read_submilli_string(caller, arr)
581}
582
583pub fn read_string_arg(
584 caller: &mut Caller<'_, StoreData>,
585 val: &Val,
586 name: &str,
587) -> wasmtime::Result<String> {
588 match val {
589 Val::AnyRef(Some(any)) => read_string_from_anyref(caller, *any, name),
590 Val::AnyRef(None) => Err(type_error(format!("{name} arg is null"))),
591 other => Err(type_error(format!(
592 "{name} expects a string, got {other:?}"
593 ))),
594 }
595}
596
597pub(crate) fn read_string_arg_units(
599 caller: &mut Caller<'_, StoreData>,
600 val: &Val,
601 name: &str,
602) -> wasmtime::Result<Vec<u16>> {
603 let any = match val {
604 Val::AnyRef(Some(any)) => *any,
605 _ => return Err(type_error(format!("{name} expects a string"))),
606 };
607 let string = any.unwrap_struct(&mut *caller)?;
608 let raw = match string.field(&mut *caller, 1)? {
609 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
610 _ => {
611 return Err(fatal_host_error(format!(
612 "{name}: malformed string payload"
613 )));
614 }
615 };
616 read_code_units(caller, raw, name)
617}
618
619pub fn read_string_array_arg(
621 caller: &mut Caller<'_, StoreData>,
622 val: &Val,
623 name: &str,
624) -> wasmtime::Result<Vec<String>> {
625 let any = match val {
626 Val::AnyRef(Some(any)) => *any,
627 Val::AnyRef(None) => return Err(type_error(format!("{name} arg is null"))),
628 other => {
629 return Err(type_error(format!(
630 "{name} expects a string[], got {other:?}"
631 )));
632 }
633 };
634 let object = any
635 .as_struct(&mut *caller)?
636 .ok_or_else(|| type_error(format!("{name}: expected $Array struct")))?;
637 let storage = super::array_storage::ArrayStorage::from_struct(caller, object)?;
638 let raw = storage.backing;
639 let len = storage.len;
640 let mut out = Vec::new();
641 out.try_reserve_exact(len as usize)
642 .map_err(fatal_host_error)?;
643 for i in 0..len {
644 match raw.get(&mut *caller, i)? {
645 Val::AnyRef(Some(elem)) => out.push(read_string_from_anyref(caller, elem, name)?),
646 Val::AnyRef(None) => out.push(String::new()),
647 other => {
648 return Err(type_error(format!(
649 "{name}: slot {i} expects a $string, got {other:?}"
650 )));
651 }
652 }
653 }
654 Ok(out)
655}
656
657pub fn write_submilli_string(
660 mut ctx: impl AsContextMut<Data = StoreData>,
661 s: &str,
662) -> wasmtime::Result<Rooted<ArrayRef>> {
663 let units = encode_utf16(&mut ctx, s)?;
664 write_code_units(ctx, &units)
665}
666
667pub(crate) fn encode_utf16(
669 ctx: impl AsContextMut<Data = StoreData>,
670 s: &str,
671) -> wasmtime::Result<Vec<u16>> {
672 fuel::charge(ctx, fuel::SCAN, s.len() as u64)?;
673 let mut units = Vec::new();
676 units.try_reserve_exact(s.len()).map_err(fatal_host_error)?;
677 units.extend(s.encode_utf16());
678 Ok(units)
679}
680
681pub(crate) fn write_code_units(
685 mut ctx: impl AsContextMut<Data = StoreData>,
686 units: &[u16],
687) -> wasmtime::Result<Rooted<ArrayRef>> {
688 fuel::charge(&mut ctx, fuel::COPY, units.len() as u64)?;
689 let array_ty = string_array_type(ctx.as_context().engine());
690 let pre = ArrayRefPre::new(&mut ctx, array_ty);
691 ArrayRef::new_from_i16_slice(&mut ctx, &pre, units)
692}
693
694pub(crate) fn read_code_units(
702 mut ctx: impl AsContextMut<Data = StoreData>,
703 raw: Rooted<ArrayRef>,
704 name: &str,
705) -> wasmtime::Result<Vec<u16>> {
706 let len = raw.len(&mut ctx)?;
707 fuel::charge(&mut ctx, fuel::COPY, u64::from(len))?;
708 let len = usize::try_from(len).map_err(fatal_host_error)?;
709 let mut units = Vec::new();
710 units.try_reserve_exact(len).map_err(fatal_host_error)?;
711 units.resize(len, 0);
712 raw.copy_to_i16_slice(&mut ctx, &mut units)
713 .map_err(|error| wasmtime::Error::msg(format!("{name}: {error}")))?;
714 Ok(units)
715}
716
717pub(crate) fn read_code_units_range(
722 mut ctx: impl AsContextMut<Data = StoreData>,
723 raw: Rooted<ArrayRef>,
724 offset: usize,
725 len: usize,
726) -> wasmtime::Result<Vec<u16>> {
727 fuel::charge(&mut ctx, fuel::COPY, len as u64)?;
728 let offset = u32::try_from(offset).map_err(fatal_host_error)?;
729 let mut units = Vec::new();
730 units.try_reserve_exact(len).map_err(fatal_host_error)?;
731 units.resize(len, 0);
732 raw.read_i16(&mut ctx, offset, &mut units)
733 .map_err(fatal_host_error)?;
734 Ok(units)
735}
736
737pub(crate) fn read_code_unit(
739 mut ctx: impl AsContextMut<Data = StoreData>,
740 raw: Rooted<ArrayRef>,
741 index: usize,
742) -> wasmtime::Result<u16> {
743 let index = u32::try_from(index).map_err(fatal_host_error)?;
744 match raw.get(&mut ctx, index).map_err(fatal_host_error)? {
745 Val::I32(unit) => Ok(unit as u16),
746 other => Err(fatal_host_error(format!("code unit {index} is {other:?}"))),
747 }
748}
749
750pub struct HostAbi {
757 pub(crate) member_functions: std::collections::BTreeMap<String, wasmtime::Func>,
758 pub(crate) string_type: StructType,
759 pub(crate) uint8_type: StructType,
760 pub(crate) array_type: StructType,
761 pub(crate) raw_array_type: ArrayType,
762 pub(crate) boxed_number_type: StructType,
763 pub(crate) boxed_boolean_type: StructType,
764 pub(crate) field_names_type: ArrayType,
765 pub(crate) object_shape_type: StructType,
766 pub(crate) object_fields_type: ArrayType,
767 pub(crate) map_backing_type: StructType,
773 pub(crate) set_backing_type: StructType,
774 pub(crate) string_vtable: Global,
777 pub(crate) array_vtable: Global,
778 pub(crate) object_vtable: Global,
779 pub(crate) boxed_number_vtable: Global,
780 pub(crate) boxed_boolean_vtable: Global,
781 pub(crate) uint8_array_vtable: Global,
782 pub(crate) bigint_vtable: Global,
783 pub(crate) closure_vtable: Global,
784 pub(crate) regex_vtable: Global,
785 pub(crate) opaque_vtable: Global,
788 pub(crate) temporal: super::prelude::temporal::TemporalAbi,
789 pub(crate) map_tombstone: Global,
792 pub(crate) collection_null: Global,
793 pub(crate) error_type: StructType,
794 pub(crate) error_subclass_type: StructType,
798 pub(crate) error_vtable: Global,
799 pub(crate) range_error_vtable: Global,
800 pub(crate) quota_exceeded_vtable: Global,
801 pub(crate) type_error_vtable: Global,
802 pub(crate) syntax_error_vtable: Global,
803 pub(crate) uri_error_vtable: Global,
804 pub(crate) reference_error_vtable: Global,
805 pub(crate) permission_denied_vtable: Global,
806 pub(crate) timeout_error_vtable: Global,
807 pub(crate) error_field_names: Global,
808 pub(crate) permission_denied_field_names: Global,
809 pub(crate) error_tag: wasmtime::Tag,
810}
811
812pub(crate) struct HostAbiHandles {
814 pub error_type: StructType,
815 pub error_subclass_type: StructType,
816 pub object_fields_type: ArrayType,
817 pub error_vtable: Global,
818 pub range_error_vtable: Global,
819 pub quota_exceeded_vtable: Global,
820 pub type_error_vtable: Global,
821 pub syntax_error_vtable: Global,
822 pub uri_error_vtable: Global,
823 pub reference_error_vtable: Global,
824 pub permission_denied_vtable: Global,
825 pub timeout_vtable: Global,
826 pub error_field_names: Global,
827 pub permission_denied_field_names: Global,
828}
829
830pub(crate) fn error_abi(caller: &Caller<'_, StoreData>) -> wasmtime::Result<HostAbiHandles> {
831 let abi = caller
832 .data()
833 .host_abi
834 .as_ref()
835 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
836 Ok(HostAbiHandles {
837 error_type: abi.error_type.clone(),
838 error_subclass_type: abi.error_subclass_type.clone(),
839 object_fields_type: abi.object_fields_type.clone(),
840 error_vtable: abi.error_vtable,
841 range_error_vtable: abi.range_error_vtable,
842 quota_exceeded_vtable: abi.quota_exceeded_vtable,
843 type_error_vtable: abi.type_error_vtable,
844 syntax_error_vtable: abi.syntax_error_vtable,
845 uri_error_vtable: abi.uri_error_vtable,
846 reference_error_vtable: abi.reference_error_vtable,
847 permission_denied_vtable: abi.permission_denied_vtable,
848 timeout_vtable: abi.timeout_error_vtable,
849 error_field_names: abi.error_field_names,
850 permission_denied_field_names: abi.permission_denied_field_names,
851 })
852}
853
854fn host_vtable(
857 ctx: &mut impl AsContextMut<Data = StoreData>,
858 select: impl FnOnce(&HostAbi) -> Global,
859) -> wasmtime::Result<Val> {
860 let global =
861 {
862 let abi =
863 ctx.as_context().data().host_abi.as_ref().ok_or_else(|| {
864 wasmtime::Error::msg("host_abi unset (prelude not instantiated)")
865 })?;
866 select(abi)
867 };
868 Ok(global.get(&mut *ctx))
869}
870
871pub(crate) fn host_string_vtable(
872 ctx: &mut impl AsContextMut<Data = StoreData>,
873) -> wasmtime::Result<Val> {
874 host_vtable(ctx, |abi| abi.string_vtable)
875}
876
877pub(crate) fn host_array_vtable(
878 ctx: &mut impl AsContextMut<Data = StoreData>,
879) -> wasmtime::Result<Val> {
880 host_vtable(ctx, |abi| abi.array_vtable)
881}
882
883pub(crate) fn host_object_vtable(
884 ctx: &mut impl AsContextMut<Data = StoreData>,
885) -> wasmtime::Result<Val> {
886 host_vtable(ctx, |abi| abi.object_vtable)
887}
888
889pub(crate) fn host_boxed_number_vtable(
890 ctx: &mut impl AsContextMut<Data = StoreData>,
891) -> wasmtime::Result<Val> {
892 host_vtable(ctx, |abi| abi.boxed_number_vtable)
893}
894
895pub(crate) fn host_boxed_boolean_vtable(
896 ctx: &mut impl AsContextMut<Data = StoreData>,
897) -> wasmtime::Result<Val> {
898 host_vtable(ctx, |abi| abi.boxed_boolean_vtable)
899}
900
901pub(crate) fn host_uint8_array_vtable(
902 ctx: &mut impl AsContextMut<Data = StoreData>,
903) -> wasmtime::Result<Val> {
904 host_vtable(ctx, |abi| abi.uint8_array_vtable)
905}
906
907pub(crate) fn host_bigint_vtable(
908 ctx: &mut impl AsContextMut<Data = StoreData>,
909) -> wasmtime::Result<Val> {
910 host_vtable(ctx, |abi| abi.bigint_vtable)
911}
912
913pub(crate) fn host_closure_vtable(
914 ctx: &mut impl AsContextMut<Data = StoreData>,
915) -> wasmtime::Result<Val> {
916 host_vtable(ctx, |abi| abi.closure_vtable)
917}
918
919pub(crate) fn host_regex_vtable(
920 ctx: &mut impl AsContextMut<Data = StoreData>,
921) -> wasmtime::Result<Val> {
922 host_vtable(ctx, |abi| abi.regex_vtable)
923}
924
925pub(crate) fn host_opaque_vtable(
926 ctx: &mut impl AsContextMut<Data = StoreData>,
927) -> wasmtime::Result<Val> {
928 host_vtable(ctx, |abi| abi.opaque_vtable)
929}
930
931pub(crate) fn host_map_tombstone(
933 ctx: &mut impl AsContextMut<Data = StoreData>,
934) -> wasmtime::Result<Val> {
935 host_vtable(ctx, |abi| abi.map_tombstone)
936}
937pub(crate) fn host_collection_null(
939 ctx: &mut impl AsContextMut<Data = StoreData>,
940) -> wasmtime::Result<Val> {
941 host_vtable(ctx, |abi| abi.collection_null)
942}
943
944pub fn write_submilli_string_struct(
947 caller: &mut Caller<'_, StoreData>,
948 s: &str,
949) -> wasmtime::Result<Rooted<StructRef>> {
950 let units = encode_utf16(&mut *caller, s)?;
951 write_submilli_string_struct_units(caller, &units)
952}
953
954pub fn write_submilli_string_struct_units(
959 caller: &mut Caller<'_, StoreData>,
960 units: &[u16],
961) -> wasmtime::Result<Rooted<StructRef>> {
962 let raw = write_code_units(&mut *caller, units)?;
964 let string_type = {
965 let abi = caller
966 .data()
967 .host_abi
968 .as_ref()
969 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
970 abi.string_type.clone()
971 };
972 let vtable = host_string_vtable(caller)?;
973 let pre = StructRefPre::new(&mut *caller, string_type);
974 StructRef::new(
975 &mut *caller,
976 &pre,
977 &[vtable, Val::AnyRef(Some(raw.to_anyref())), Val::I64(0)],
978 )
979}
980
981pub fn write_submilli_uint8array_struct(
984 caller: &mut Caller<'_, StoreData>,
985 bytes: &[u8],
986) -> wasmtime::Result<Rooted<StructRef>> {
987 let array_ty = uint8_array_array_type(caller.engine());
988 let raw = write_uint8_array(&mut *caller, array_ty, bytes)?;
989 let uint8_type = {
990 let abi = caller
991 .data()
992 .host_abi
993 .as_ref()
994 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
995 abi.uint8_type.clone()
996 };
997 let vtable = host_uint8_array_vtable(caller)?;
998 let pre = StructRefPre::new(&mut *caller, uint8_type);
999 StructRef::new(
1000 &mut *caller,
1001 &pre,
1002 &[vtable, Val::AnyRef(Some(raw.to_anyref()))],
1003 )
1004}
1005
1006pub(crate) fn read_boxed_number(
1008 caller: &mut Caller<'_, StoreData>,
1009 val: &Val,
1010 context: &str,
1011) -> wasmtime::Result<f64> {
1012 let Val::AnyRef(Some(any)) = val else {
1013 return Err(type_error(format!("{context}: expected a boxed number")));
1014 };
1015 let st = any
1016 .as_struct(&mut *caller)?
1017 .ok_or_else(|| type_error(format!("{context}: expected a boxed number")))?;
1018 match st.field(&mut *caller, 1)? {
1019 Val::F64(bits) => Ok(f64::from_bits(bits)),
1020 other => Err(wasmtime::Error::msg(format!(
1021 "{context}: boxed-number payload is {other:?}"
1022 ))),
1023 }
1024}
1025
1026pub(crate) fn write_boxed_boolean_struct(
1029 caller: &mut Caller<'_, StoreData>,
1030 value: bool,
1031) -> wasmtime::Result<Rooted<StructRef>> {
1032 let boxed_type = intrinsic_types(&mut *caller)?.boxed_boolean.clone();
1033 let vtable = host_boxed_boolean_vtable(caller)?;
1034 let pre = StructRefPre::new(&mut *caller, boxed_type);
1035 StructRef::new(&mut *caller, &pre, &[vtable, Val::I32(i32::from(value))])
1036}
1037
1038pub(crate) fn write_boxed_number_struct(
1041 caller: &mut Caller<'_, StoreData>,
1042 n: f64,
1043) -> wasmtime::Result<Rooted<StructRef>> {
1044 let boxed_type = {
1045 let abi = caller
1046 .data()
1047 .host_abi
1048 .as_ref()
1049 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1050 abi.boxed_number_type.clone()
1051 };
1052 let vtable = host_boxed_number_vtable(caller)?;
1053 let pre = StructRefPre::new(&mut *caller, boxed_type);
1054 StructRef::new(&mut *caller, &pre, &[vtable, Val::F64(n.to_bits())])
1055}
1056
1057pub fn write_submilli_array_struct(
1061 caller: &mut Caller<'_, StoreData>,
1062 elements: &[Val],
1063) -> wasmtime::Result<Rooted<StructRef>> {
1064 let len = super::array_storage::checked_length(elements.len())?;
1065 fuel::charge(&mut *caller, fuel::ELEM, u64::from(len))?;
1066 write_submilli_array_struct_precharged(caller, elements)
1067}
1068
1069pub(crate) fn write_submilli_array_struct_precharged(
1071 caller: &mut Caller<'_, StoreData>,
1072 elements: &[Val],
1073) -> wasmtime::Result<Rooted<StructRef>> {
1074 let len = super::array_storage::checked_length(elements.len())?;
1075 let (array_type, raw_array_type) = {
1076 let abi = caller
1077 .data()
1078 .host_abi
1079 .as_ref()
1080 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1081 (abi.array_type.clone(), abi.raw_array_type.clone())
1082 };
1083 let raw_pre = ArrayRefPre::new(&mut *caller, raw_array_type);
1084 let raw = ArrayRef::new_fixed(&mut *caller, &raw_pre, elements)?;
1085 let vtable = host_array_vtable(caller)?;
1086 let pre = StructRefPre::new(&mut *caller, array_type);
1087 StructRef::new(
1088 &mut *caller,
1089 &pre,
1090 &[
1091 vtable,
1092 Val::AnyRef(Some(raw.to_anyref())),
1093 Val::I32(len as i32),
1094 ],
1095 )
1096}
1097
1098#[derive(Debug)]
1104pub struct RangeError(pub String);
1105
1106impl std::fmt::Display for RangeError {
1107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1108 f.write_str(&self.0)
1109 }
1110}
1111
1112impl std::error::Error for RangeError {}
1113
1114pub fn range_error(message: impl Into<String>) -> wasmtime::Error {
1116 wasmtime::Error::new(RangeError(message.into()))
1117}
1118
1119#[derive(Debug)]
1125pub struct QuotaExceededError(pub String);
1126
1127impl std::fmt::Display for QuotaExceededError {
1128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1129 f.write_str(&self.0)
1130 }
1131}
1132
1133impl std::error::Error for QuotaExceededError {}
1134
1135pub fn quota_exceeded_error(message: impl Into<String>) -> wasmtime::Error {
1137 wasmtime::Error::new(QuotaExceededError(message.into()))
1138}
1139
1140#[derive(Debug)]
1144pub struct TimeoutError(pub String);
1145
1146impl std::fmt::Display for TimeoutError {
1147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1148 f.write_str(&self.0)
1149 }
1150}
1151
1152impl std::error::Error for TimeoutError {}
1153
1154pub fn timeout_error(message: impl Into<String>) -> wasmtime::Error {
1156 wasmtime::Error::new(TimeoutError(message.into()))
1157}
1158
1159#[derive(Debug)]
1162pub struct SyntaxError(pub String);
1163
1164impl std::fmt::Display for SyntaxError {
1165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1166 f.write_str(&self.0)
1167 }
1168}
1169
1170impl std::error::Error for SyntaxError {}
1171
1172pub fn syntax_error(message: impl Into<String>) -> wasmtime::Error {
1174 wasmtime::Error::new(SyntaxError(message.into()))
1175}
1176
1177#[derive(Debug)]
1180pub struct UriError(pub String);
1181
1182impl std::fmt::Display for UriError {
1183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1184 f.write_str(&self.0)
1185 }
1186}
1187
1188impl std::error::Error for UriError {}
1189
1190pub fn uri_error(message: impl Into<String>) -> wasmtime::Error {
1192 wasmtime::Error::new(UriError(message.into()))
1193}
1194
1195#[derive(Debug)]
1201pub struct TypeError(pub String);
1202
1203impl std::fmt::Display for TypeError {
1204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1205 f.write_str(&self.0)
1206 }
1207}
1208
1209impl std::error::Error for TypeError {}
1210
1211pub fn type_error(message: impl Into<String>) -> wasmtime::Error {
1213 wasmtime::Error::new(TypeError(message.into()))
1214}
1215
1216#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1221#[non_exhaustive]
1222pub struct PermissionDenied {
1223 pub caller: String,
1224 pub capability: String,
1225 pub reason: String,
1226 source: DenialSource,
1227}
1228
1229#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1234pub enum DenialSource {
1235 Policy,
1237 Invariant,
1240 ReadOnly,
1243 Harness,
1245 Human,
1248 Admin,
1251}
1252
1253#[derive(Debug, Clone, PartialEq, Eq)]
1255pub struct Denial {
1256 pub caller: String,
1257 pub capability: String,
1258 pub source: DenialSource,
1259}
1260
1261impl DenialSource {
1262 pub fn as_str(self) -> &'static str {
1263 match self {
1264 Self::Policy => "policy",
1265 Self::Invariant => "invariant",
1266 Self::ReadOnly => "read_only",
1267 Self::Harness => "harness",
1268 Self::Human => "human",
1269 Self::Admin => "admin",
1270 }
1271 }
1272}
1273
1274#[derive(Default)]
1278pub(crate) struct ThrownDenials {
1279 entries: std::collections::VecDeque<(Rooted<StructRef>, Denial)>,
1280}
1281
1282const MAX_THROWN_DENIALS: usize = 64;
1285
1286impl ThrownDenials {
1287 fn record(&mut self, store: impl AsContext, handle: Rooted<StructRef>, denial: Denial) {
1289 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1290 if self.entries.len() >= MAX_THROWN_DENIALS {
1291 self.entries.pop_front();
1292 }
1293 self.entries.push_back((handle, denial));
1294 }
1295
1296 pub(crate) fn find(
1300 &mut self,
1301 store: impl AsContext,
1302 thrown: &Rooted<AnyRef>,
1303 ) -> Option<Denial> {
1304 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1305 self.entries
1306 .iter()
1307 .find(|(entry, _)| Rooted::ref_eq(&store, entry, thrown).unwrap_or(false))
1308 .map(|(_, denial)| denial.clone())
1309 }
1310}
1311
1312pub(crate) fn with_thrown_denials<T: AsContextMut<Data = StoreData>, R>(
1318 store: &mut T,
1319 f: impl FnOnce(&mut ThrownDenials, &T) -> R,
1320) -> R {
1321 let mut table = std::mem::take(&mut store.as_context_mut().data_mut().thrown_denials);
1322 let result = f(&mut table, store);
1323 store.as_context_mut().data_mut().thrown_denials = table;
1324 result
1325}
1326
1327impl PermissionDenied {
1328 fn denial(&self) -> Denial {
1330 Denial {
1331 caller: self.caller.clone(),
1332 capability: self.capability.clone(),
1333 source: self.source,
1334 }
1335 }
1336
1337 pub fn is_policy(&self) -> bool {
1343 matches!(self.source, DenialSource::Policy)
1344 }
1345}
1346
1347impl std::fmt::Display for PermissionDenied {
1348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1354 let Self {
1355 caller,
1356 capability,
1357 reason,
1358 source,
1359 } = self;
1360 write!(
1361 f,
1362 "permission denied: caller={caller} capability={capability}: {reason}."
1363 )?;
1364 f.write_str(match source {
1365 DenialSource::Policy => {
1366 " This request is forbidden by the operator's policy — do not work \
1367 around the denial (another package, raw HTTP, or other arguments to \
1368 reach the same thing); report it and stop."
1369 }
1370 DenialSource::Invariant => {
1371 " Do not work around this denial: not through another package, not by \
1372 asking a package to fetch the value and hand it back, not through raw \
1373 HTTP. Report it and stop."
1374 }
1375 DenialSource::ReadOnly => {
1376 " This volume cannot be written from this blueprint; write under a \
1377 writable path instead (fs.info() lists each mount and its access)."
1378 }
1379 DenialSource::Harness => {
1380 " The application refused this call — do not work around the refusal \
1381 (another tool, raw HTTP, or other arguments to reach the same thing); \
1382 report it and stop."
1383 }
1384 DenialSource::Human => {
1385 " This call needed the person's approval and did not get it — do not \
1386 retry it or work around it (another package, raw HTTP, or other \
1387 arguments to reach the same thing); report it and stop."
1388 }
1389 DenialSource::Admin => {
1390 " This call needed an administrator's approval and did not get it — do \
1391 not retry it or work around it (another package, raw HTTP, or other \
1392 arguments to reach the same thing); report it and stop."
1393 }
1394 })
1395 }
1396}
1397
1398impl std::error::Error for PermissionDenied {}
1399
1400pub fn permission_denied(
1403 caller: impl Into<String>,
1404 capability: impl Into<String>,
1405 reason: impl Into<String>,
1406) -> wasmtime::Error {
1407 denied(caller, capability, reason, DenialSource::Policy)
1408}
1409
1410pub fn permission_denied_invariant(
1413 caller: impl Into<String>,
1414 capability: impl Into<String>,
1415 reason: impl Into<String>,
1416) -> wasmtime::Error {
1417 denied(caller, capability, reason, DenialSource::Invariant)
1418}
1419
1420pub fn permission_denied_read_only(
1422 caller: impl Into<String>,
1423 capability: impl Into<String>,
1424 reason: impl Into<String>,
1425) -> wasmtime::Error {
1426 denied(caller, capability, reason, DenialSource::ReadOnly)
1427}
1428
1429pub fn permission_denied_by_harness(
1431 caller: impl Into<String>,
1432 capability: impl Into<String>,
1433 reason: impl Into<String>,
1434) -> wasmtime::Error {
1435 denied(caller, capability, reason, DenialSource::Harness)
1436}
1437
1438pub fn permission_denied_by(
1440 source: DenialSource,
1441 caller: impl Into<String>,
1442 capability: impl Into<String>,
1443 reason: impl Into<String>,
1444) -> wasmtime::Error {
1445 denied(caller, capability, reason, source)
1446}
1447
1448fn denied(
1449 caller: impl Into<String>,
1450 capability: impl Into<String>,
1451 reason: impl Into<String>,
1452 source: DenialSource,
1453) -> wasmtime::Error {
1454 wasmtime::Error::new(PermissionDenied {
1455 caller: caller.into(),
1456 capability: capability.into(),
1457 reason: reason.into(),
1458 source,
1459 })
1460}
1461
1462fn builtin_class_of(err: &wasmtime::Error) -> super::prelude::error::BuiltinErrorClass {
1463 if err.downcast_ref::<RangeError>().is_some() {
1464 super::prelude::error::BuiltinErrorClass::Range
1465 } else if err.downcast_ref::<QuotaExceededError>().is_some() {
1466 super::prelude::error::BuiltinErrorClass::QuotaExceeded
1467 } else if err.downcast_ref::<TimeoutError>().is_some() {
1468 super::prelude::error::BuiltinErrorClass::Timeout
1469 } else if err.downcast_ref::<TypeError>().is_some() {
1470 super::prelude::error::BuiltinErrorClass::Type
1471 } else if err.downcast_ref::<SyntaxError>().is_some() {
1472 super::prelude::error::BuiltinErrorClass::Syntax
1473 } else if err.downcast_ref::<UriError>().is_some() {
1474 super::prelude::error::BuiltinErrorClass::Uri
1475 } else if err.downcast_ref::<PermissionDenied>().is_some() {
1476 super::prelude::error::BuiltinErrorClass::PermissionDenied
1477 } else {
1478 super::prelude::error::BuiltinErrorClass::Error
1479 }
1480}
1481
1482fn own_field_texts(err: &wasmtime::Error) -> Vec<&str> {
1485 match err.downcast_ref::<PermissionDenied>() {
1486 Some(pd) => vec![&pd.caller, &pd.capability, &pd.reason],
1487 None => Vec::new(),
1488 }
1489}
1490
1491pub fn throw_error(caller: &mut Caller<'_, StoreData>, message: &str) -> wasmtime::Error {
1505 throw_error_as(
1506 caller,
1507 super::prelude::error::BuiltinErrorClass::Error,
1508 message,
1509 &[],
1510 )
1511}
1512
1513#[derive(Debug)]
1518pub struct FatalHostError(String);
1519
1520impl std::fmt::Display for FatalHostError {
1521 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1522 write!(out, "internal host error: {}", self.0)
1523 }
1524}
1525
1526impl std::error::Error for FatalHostError {}
1527
1528pub(crate) fn check_host_abi(
1532 ty: &FuncType,
1533 params: &[Val],
1534 results: &[Val],
1535) -> wasmtime::Result<()> {
1536 if params.len() != ty.params().len() || results.len() != ty.results().len() {
1537 return Err(invariant_trap("host ABI: incorrect buffer length"));
1538 }
1539 for (value, expected) in params.iter().zip(ty.params()) {
1540 let valid = match (value, expected) {
1541 (Val::I32(_), ValType::I32)
1542 | (Val::I64(_), ValType::I64)
1543 | (Val::F32(_), ValType::F32)
1544 | (Val::F64(_), ValType::F64)
1545 | (Val::V128(_), ValType::V128) => true,
1546 (value, ValType::Ref(reference)) => {
1547 let nullable = reference.is_nullable();
1548 match (value, reference.heap_type()) {
1549 (
1550 Val::FuncRef(value),
1551 HeapType::Func | HeapType::NoFunc | HeapType::ConcreteFunc(_),
1552 ) => nullable || value.is_some(),
1553 (Val::ExternRef(value), HeapType::Extern | HeapType::NoExtern) => {
1554 nullable || value.is_some()
1555 }
1556 (Val::ExnRef(value), HeapType::Exn | HeapType::NoExn) => {
1557 nullable || value.is_some()
1558 }
1559 (
1560 Val::AnyRef(value),
1561 HeapType::Any
1562 | HeapType::Eq
1563 | HeapType::I31
1564 | HeapType::Struct
1565 | HeapType::ConcreteStruct(_)
1566 | HeapType::Array
1567 | HeapType::ConcreteArray(_)
1568 | HeapType::None,
1569 ) => nullable || value.is_some(),
1570 _ => false,
1571 }
1572 }
1573 _ => false,
1574 };
1575 if !valid {
1576 return Err(invariant_trap(
1577 "host ABI: incorrect argument representation",
1578 ));
1579 }
1580 }
1581 Ok(())
1582}
1583
1584pub(crate) fn abi_arg(params: &[Val], index: usize) -> wasmtime::Result<&Val> {
1585 params
1586 .get(index)
1587 .ok_or_else(|| invariant_trap("host ABI: missing argument"))
1588}
1589
1590pub(crate) fn abi_result(results: &mut [Val], index: usize) -> wasmtime::Result<&mut Val> {
1591 results
1592 .get_mut(index)
1593 .ok_or_else(|| invariant_trap("host ABI: missing result slot"))
1594}
1595
1596pub(crate) fn invariant_trap(message: &'static str) -> wasmtime::Error {
1598 wasmtime::Error::new(wasmtime::Trap::UnreachableCodeReached).context(message)
1599}
1600
1601pub fn fatal_host_error(message: impl std::fmt::Display) -> wasmtime::Error {
1602 wasmtime::Error::new(FatalHostError(message.to_string()))
1603}
1604
1605pub(crate) fn throw_host_error(
1612 caller: &mut Caller<'_, StoreData>,
1613 err: wasmtime::Error,
1614) -> wasmtime::Error {
1615 if ends_the_run(&err) || err.is::<wasmtime::ThrownException>() {
1616 return err;
1617 }
1618 let class = builtin_class_of(&err);
1619 let own = own_field_texts(&err);
1620 let denial = err
1621 .downcast_ref::<PermissionDenied>()
1622 .map(PermissionDenied::denial);
1623 throw_error_with(caller, class, &err.to_string(), &own, denial)
1624}
1625
1626pub(crate) struct ThrownErrorParts {
1628 pub error: Rooted<StructRef>,
1629 pub name: String,
1630 pub message: String,
1631}
1632
1633pub(crate) fn thrown_error_parts(
1637 mut ctx: impl AsContextMut<Data = StoreData>,
1638 exn: Rooted<wasmtime::ExnRef>,
1639) -> Option<ThrownErrorParts> {
1640 let Val::AnyRef(Some(error_ref)) = exn.field(&mut ctx, 0).ok()? else {
1642 return None;
1643 };
1644 let error = error_ref.unwrap_struct(&mut ctx).ok()?;
1645 let Val::AnyRef(Some(payload_ref)) = error.field(&mut ctx, 2).ok()? else {
1647 return None;
1648 };
1649 let payload = payload_ref.unwrap_array(&mut ctx).ok()?;
1650 let message = payload
1651 .get(&mut ctx, super::prelude::error::MESSAGE_SLOT)
1652 .ok()?;
1653 let name = payload
1654 .get(&mut ctx, super::prelude::error::NAME_SLOT)
1655 .ok()?;
1656 let name = read_string_value(&mut ctx, name)?;
1657 let message = read_string_value(&mut ctx, message)?;
1658 Some(ThrownErrorParts {
1659 error,
1660 name,
1661 message,
1662 })
1663}
1664
1665pub(crate) fn read_string_value(
1667 mut ctx: impl AsContextMut<Data = StoreData>,
1668 value: Val,
1669) -> Option<String> {
1670 let Val::AnyRef(Some(s_ref)) = value else {
1671 return None;
1672 };
1673 let s_struct = s_ref.unwrap_struct(&mut ctx).ok()?;
1674 let Val::AnyRef(Some(raw_ref)) = s_struct.field(&mut ctx, 1).ok()? else {
1676 return None;
1677 };
1678 let arr = raw_ref.unwrap_array(&mut ctx).ok()?;
1679 read_submilli_string(&mut ctx, arr).ok()
1680}
1681
1682pub(crate) fn ends_the_run(err: &wasmtime::Error) -> bool {
1688 err.is::<FatalHostError>()
1689 || err.is::<wasmtime::Trap>()
1690 || super::limits::is_memory_exhausted(err)
1691}
1692
1693fn throw_error_as(
1694 caller: &mut Caller<'_, StoreData>,
1695 class: super::prelude::error::BuiltinErrorClass,
1696 message: &str,
1697 own_fields: &[&str],
1698) -> wasmtime::Error {
1699 throw_error_with(caller, class, message, own_fields, None)
1700}
1701
1702fn throw_error_with(
1704 caller: &mut Caller<'_, StoreData>,
1705 class: super::prelude::error::BuiltinErrorClass,
1706 message: &str,
1707 own_fields: &[&str],
1708 denial: Option<Denial>,
1709) -> wasmtime::Error {
1710 match throw_error_inner(caller, class, message, own_fields, denial) {
1711 Ok(err) => err,
1712 Err(cause) if ends_the_run(&cause) => cause,
1715 Err(cause) => fatal_host_error(format!("{message}; error construction failed: {cause:#}")),
1716 }
1717}
1718
1719fn throw_error_inner(
1720 caller: &mut Caller<'_, StoreData>,
1721 class: super::prelude::error::BuiltinErrorClass,
1722 message: &str,
1723 own_fields: &[&str],
1724 denial: Option<Denial>,
1725) -> wasmtime::Result<wasmtime::Error> {
1726 let tag = caller
1727 .data()
1728 .host_abi
1729 .as_ref()
1730 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?
1731 .error_tag;
1732 let error_struct =
1733 super::prelude::error::construct_from_message(caller, class, message, own_fields)?;
1734 let error = Val::AnyRef(Some(error_struct.to_anyref()));
1735 let exn_ty = wasmtime::ExnType::from_tag_type(&tag.ty(&*caller))?;
1736 let pre = wasmtime::ExnRefPre::new(&mut *caller, exn_ty);
1737 let exn = wasmtime::ExnRef::new(&mut *caller, &pre, &tag, &[error])?;
1738 match caller.as_context_mut().throw::<()>(exn) {
1739 Err(thrown) => {
1740 if let Some(denial) = denial {
1741 with_thrown_denials(caller, |table, store| {
1742 table.record(store, error_struct, denial);
1743 });
1744 }
1745 Ok(wasmtime::Error::new(thrown))
1746 }
1747 Ok(()) => Err(fatal_host_error(
1749 "engine did not return its pending-exception error",
1750 )),
1751 }
1752}
1753
1754pub(crate) fn string_array_type(engine: &Engine) -> ArrayType {
1755 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I16))
1756}
1757
1758pub fn register_host_fn(
1775 linker: &mut Linker<StoreData>,
1776 module: &str,
1777 mangled_name: crate::MangledName,
1778 ty: FuncType,
1779 body: impl Fn(&mut Caller<'_, StoreData>, &[Val], &mut [Val]) -> wasmtime::Result<()>
1780 + Send
1781 + Sync
1782 + 'static,
1783) -> wasmtime::Result<()> {
1784 let call_fuel = call_fuel_of(module);
1785 let ends_its_calls = begins_calls(module);
1786 let abi = ty.clone();
1787 linker.func_new(
1788 module,
1789 mangled_name.as_str(),
1790 ty,
1791 move |mut hc, params, results| {
1792 check_host_abi(&abi, params, results)?;
1793 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1794 let outcome =
1795 charge_host_fuel(&mut hc, call_fuel).and_then(|()| body(&mut hc, params, results));
1796 if let Some(marker) = marker {
1797 exit_host_call(&hc, marker, outcome.is_ok());
1798 }
1799 match outcome {
1800 Ok(()) => Ok(()),
1801 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1803 Err(err) => Err(throw_host_error(&mut hc, err)),
1804 }
1805 },
1806 )?;
1807 Ok(())
1808}
1809
1810pub(crate) const CALL_MODULES: &[&str] = &[
1814 crate::stdlib::fs::MODULE_NAME,
1815 crate::stdlib::http::MODULE_NAME,
1816 crate::stdlib::llm::MODULE_NAME,
1817 crate::stdlib::embedding::MODULE_NAME,
1818 crate::stdlib::session::MODULE_NAME,
1819 crate::stdlib::secrets::MODULE_NAME,
1820 crate::stdlib::variables::MODULE_NAME,
1821 crate::stdlib::git::MODULE_NAME,
1822 crate::stdlib::code::MODULE_NAME,
1823 crate::stdlib::security::MODULE_NAME,
1824 crate::stdlib::agents::MODULE_NAME,
1825 crate::stdlib::skills::MODULE_NAME,
1826 super::mcp::MCP_MODULE_NAME,
1827 super::tools::TOOLS_MODULE_NAME,
1828];
1829
1830pub(crate) fn begins_calls(module: &str) -> bool {
1831 CALL_MODULES.contains(&module)
1832}
1833
1834fn enter_host_call(store: &impl wasmtime::AsContext<Data = StoreData>) -> Option<u64> {
1835 store
1836 .as_context()
1837 .data()
1838 .security_check
1839 .recorder()
1840 .map(super::decision::DecisionRecorder::enter_host_call)
1841}
1842
1843fn exit_host_call(store: &impl wasmtime::AsContext<Data = StoreData>, marker: u64, returned: bool) {
1844 if let Some(recorder) = store.as_context().data().security_check.recorder() {
1845 recorder.exit_host_call(marker, returned);
1846 }
1847}
1848
1849fn call_fuel_of(module: &str) -> u64 {
1853 if module == crate::stdlib::test::MODULE_NAME {
1854 0
1855 } else {
1856 super::fuel::CALL
1857 }
1858}
1859
1860pub fn register_host_fn_async<F>(
1864 linker: &mut Linker<StoreData>,
1865 module: &str,
1866 mangled_name: crate::MangledName,
1867 ty: FuncType,
1868 body: F,
1869) -> wasmtime::Result<()>
1870where
1871 F: for<'a> Fn(
1872 &'a mut Caller<'_, StoreData>,
1873 &'a [Val],
1874 &'a mut [Val],
1875 ) -> Pin<Box<dyn Future<Output = wasmtime::Result<()>> + Send + 'a>>
1876 + Send
1877 + Sync
1878 + 'static,
1879{
1880 let body = std::sync::Arc::new(body);
1883 let call_fuel = call_fuel_of(module);
1884 let ends_its_calls = begins_calls(module);
1885 let abi = ty.clone();
1886 linker.func_new_async(
1887 module,
1888 mangled_name.as_str(),
1889 ty,
1890 move |mut hc, params, results| {
1891 let body = std::sync::Arc::clone(&body);
1892 let shape = check_host_abi(&abi, params, results);
1893 Box::new(async move {
1894 shape?;
1895 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1896 let outcome = match charge_host_fuel(&mut hc, call_fuel) {
1897 Ok(()) => body(&mut hc, params, results).await,
1898 Err(err) => Err(err),
1899 };
1900 if let Some(marker) = marker {
1901 exit_host_call(&hc, marker, outcome.is_ok());
1902 }
1903 match outcome {
1904 Ok(()) => Ok(()),
1905 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1906 Err(err) => Err(throw_host_error(&mut hc, err)),
1907 }
1908 })
1909 },
1910 )?;
1911 Ok(())
1912}
1913
1914pub fn register_host_value_async<F>(
1918 linker: &mut Linker<StoreData>,
1919 module: &str,
1920 mangled_name: crate::MangledName,
1921 ty: FuncType,
1922 body: F,
1923) -> wasmtime::Result<()>
1924where
1925 F: for<'a> Fn(
1926 &'a mut Caller<'_, StoreData>,
1927 &'a [Val],
1928 ) -> Pin<Box<dyn Future<Output = wasmtime::Result<Val>> + Send + 'a>>
1929 + Send
1930 + Sync
1931 + 'static,
1932{
1933 if ty.results().len() != 1 {
1934 return Err(fatal_host_error(
1935 "value-returning host function requires one ABI result",
1936 ));
1937 }
1938 register_host_fn_async(
1939 linker,
1940 module,
1941 mangled_name,
1942 ty,
1943 move |caller, params, results| {
1944 let value = body(caller, params);
1945 Box::pin(async move {
1946 *abi_result(results, 0)? = value.await?;
1947 Ok(())
1948 })
1949 },
1950 )
1951}
1952
1953pub(crate) fn host_value_async<F>(
1955 store: impl AsContextMut<Data = StoreData>,
1956 ty: FuncType,
1957 body: F,
1958) -> wasmtime::Func
1959where
1960 F: for<'a> Fn(
1961 &'a mut Caller<'_, StoreData>,
1962 &'a [Val],
1963 ) -> Box<dyn Future<Output = wasmtime::Result<Val>> + Send + 'a>
1964 + Send
1965 + Sync
1966 + 'static,
1967{
1968 let body = std::sync::Arc::new(body);
1969 fuel::host_func_async(store, ty, move |mut caller, params, results| {
1970 let body = std::sync::Arc::clone(&body);
1971 Box::new(async move {
1972 let [result] = results else {
1973 return Err(fatal_host_error(
1974 "value-returning callback requires one ABI result",
1975 ));
1976 };
1977 *result = Box::into_pin(body(&mut caller, params))
1978 .await
1979 .map_err(|error| throw_host_error(&mut caller, error))?;
1980 Ok(())
1981 })
1982 })
1983}
1984
1985pub fn host_package_declarations() -> Vec<PackageDeclaration> {
1989 vec![
1990 number_module_definitions(),
1991 super::prelude::temporal::shared::temporal_module_package_declaration(),
1992 ]
1993}
1994
1995pub fn internal_host_package_declarations() -> Vec<PackageDeclaration> {
1997 vec![
1998 super::json::json_module_definitions(),
1999 super::mcp::mcp_call_package_declaration(),
2000 super::tools::tools_call_package_declaration(),
2001 ]
2002}
2003
2004pub fn stdlib_package_declarations() -> Vec<PackageDeclaration> {
2005 crate::stdlib::stdlib_package_declarations()
2006}
2007
2008fn number_module_definitions() -> PackageDeclaration {
2009 let mut defs = PackageDeclaration::with_package(NUMBER_MODULE_NAME);
2010 let insert = |defs: &mut PackageDeclaration,
2011 name: &str,
2012 params: Vec<Param>,
2013 ret: Type,
2014 doc: Option<crate::DocComment>| {
2015 defs.values.insert(
2016 name.to_string(),
2017 ValueSymbol {
2018 name: name.to_string(),
2019 mangled_name: crate::mangle::host(NUMBER_MODULE_NAME, name),
2020 declaration_span: Span::at(crate::FileId::NUMBER),
2021 kind: ValueKind::Function {
2022 generics: Vec::new(),
2023 params,
2024 ret,
2025 type_predicate: None,
2026 doc,
2027 },
2028 },
2029 );
2030 };
2031 insert(
2032 &mut defs,
2033 "toNumber",
2034 vec![Param::new("string", Type::String)],
2035 Type::Number,
2036 crate::doc(
2037 crate::FileId::NUMBER,
2038 "/**\n * Strict whole-string decimal parse.\n * Returns `NaN` when `string` is not a valid number.\n * @param string The text to parse.\n */",
2039 ),
2040 );
2041 insert(
2042 &mut defs,
2043 "parseInt",
2044 vec![Param::new("string", Type::String), parse_int_radix_param()],
2045 Type::Number,
2046 crate::doc(
2047 crate::FileId::NUMBER,
2048 "/**\n * Parses `string` as an integer in the given `radix`. Stops at the first non-digit character. Returns `NaN` if no digits are found.\n * @param string The text to parse.\n * @param radix The base (2-36). Omitted or `undefined`, a `0x` prefix reads as base 16 and anything else as base 10.\n */",
2049 ),
2050 );
2051 insert(
2052 &mut defs,
2053 "parseFloat",
2054 vec![Param::new("string", Type::String)],
2055 Type::Number,
2056 crate::doc(
2057 crate::FileId::NUMBER,
2058 "/**\n * Parses `string` as a floating-point number. Stops at the first non-numeric character. Returns `NaN` if no digits are found.\n * @param string The text to parse.\n */",
2059 ),
2060 );
2061 defs
2062}
2063
2064pub(crate) fn parse_int_radix_param() -> Param {
2069 Param::with_default("radix", Type::Number, crate::DefaultValue::Number(0.0))
2070}
2071
2072#[cfg(test)]
2073mod tests {
2074 use super::*;
2075
2076 #[test]
2077 fn host_abi_rejects_bad_shapes_without_numeric_defaults() {
2078 let engine = Engine::default();
2079 let ty = FuncType::new(&engine, [ValType::F64, ValType::I32], [ValType::I64]);
2080 for (params, results) in [
2081 (vec![], vec![Val::I64(0)]),
2082 (vec![Val::F64(0), Val::I32(0)], vec![]),
2083 (vec![Val::I32(0), Val::I32(0)], vec![Val::I64(0)]),
2084 (vec![Val::F64(0), Val::F64(0)], vec![Val::I64(0)]),
2085 ] {
2086 let error = check_host_abi(&ty, ¶ms, &results).unwrap_err();
2087 assert!(error.is::<wasmtime::Trap>());
2088 }
2089 check_host_abi(
2090 &ty,
2091 &[Val::F64(f64::NAN.to_bits()), Val::I32(1)],
2092 &[Val::I64(0)],
2093 )
2094 .unwrap();
2095 }
2096
2097 fn denial(capability: &str) -> Denial {
2098 Denial {
2099 caller: "main".into(),
2100 capability: capability.into(),
2101 source: DenialSource::Policy,
2102 }
2103 }
2104
2105 #[test]
2109 fn a_collected_denial_never_matches_a_later_object() {
2110 let (engine, mut store, _) = async_store();
2111 let ty = StructType::new(
2112 &engine,
2113 [FieldType::new(Mutability::Const, StorageType::I8)],
2114 )
2115 .expect("struct type");
2116 let pre = StructRefPre::new(&mut store, ty);
2117 let mut denials = ThrownDenials::default();
2118
2119 let thrown = {
2120 let mut scope = wasmtime::RootScope::new(&mut store);
2121 let thrown = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(1)]).expect("alloc");
2122 denials.record(&scope, thrown, denial("fs.read"));
2123 let found = denials.find(&scope, &thrown.to_anyref());
2124 assert_eq!(found.map(|d| d.capability), Some("fs.read".to_string()));
2125 thrown.to_anyref()
2126 };
2127 store.gc();
2128
2129 let mut scope = wasmtime::RootScope::new(&mut store);
2130 let forged = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(2)]).expect("alloc");
2131 assert!(
2132 denials.find(&scope, &forged.to_anyref()).is_none(),
2133 "a program-built error is not a runtime denial"
2134 );
2135 assert!(
2136 denials.find(&scope, &thrown).is_none(),
2137 "the collected denial's entry is dropped"
2138 );
2139 assert!(denials.entries.is_empty());
2140 }
2141
2142 #[test]
2143 fn the_denial_table_drops_its_oldest_entries_past_the_cap() {
2144 let (engine, mut store, _) = async_store();
2145 let ty = StructType::new(
2146 &engine,
2147 [FieldType::new(Mutability::Const, StorageType::I8)],
2148 )
2149 .expect("struct type");
2150 let pre = StructRefPre::new(&mut store, ty);
2151 let mut denials = ThrownDenials::default();
2152 let mut scope = wasmtime::RootScope::new(&mut store);
2153 let mut thrown = Vec::new();
2154 for i in 0..=MAX_THROWN_DENIALS {
2155 let object =
2156 wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(i as i32)]).expect("alloc");
2157 denials.record(&scope, object, denial(&format!("cap.{i}")));
2158 thrown.push(object.to_anyref());
2159 }
2160 assert_eq!(denials.entries.len(), MAX_THROWN_DENIALS);
2161 assert!(denials.find(&scope, &thrown[0]).is_none(), "oldest dropped");
2162 let newest = denials.find(&scope, &thrown[MAX_THROWN_DENIALS]);
2163 assert_eq!(
2164 newest.map(|d| d.capability),
2165 Some(format!("cap.{MAX_THROWN_DENIALS}"))
2166 );
2167 }
2168
2169 #[tokio::test]
2170 async fn run_ending_errors_bypass_guest_catch_in_both_wrappers() {
2171 #[derive(Clone, Copy, PartialEq, Debug)]
2172 enum Failure {
2173 Ordinary,
2174 Fatal,
2175 MissingArgument,
2176 MissingResult,
2177 NestedTrap(wasmtime::Trap),
2179 }
2180 let failures = [
2181 Failure::Ordinary,
2182 Failure::Fatal,
2183 Failure::MissingArgument,
2184 Failure::MissingResult,
2185 Failure::NestedTrap(wasmtime::Trap::StackOverflow),
2186 Failure::NestedTrap(wasmtime::Trap::OutOfFuel),
2187 Failure::NestedTrap(wasmtime::Trap::Interrupt),
2188 Failure::NestedTrap(wasmtime::Trap::UnreachableCodeReached),
2189 Failure::NestedTrap(wasmtime::Trap::CastFailure),
2190 Failure::NestedTrap(wasmtime::Trap::NullReference),
2191 Failure::NestedTrap(wasmtime::Trap::ArrayOutOfBounds),
2192 ];
2193 for asynchronous in [false, true] {
2194 for kind in failures {
2195 let (engine, mut store, mut linker) = async_store();
2196 crate::runtime::install_runtime_async(&mut linker, &mut store)
2197 .await
2198 .expect("runtime");
2199 let name = crate::mangle::host(TEST_MODULE, "failure");
2200 let failure = move || match kind {
2201 Failure::Fatal => fatal_host_error("test operation: invalid state")
2202 .context("outer host context"),
2203 Failure::NestedTrap(trap) => {
2204 wasmtime::Error::new(trap).context("outer host context")
2205 }
2206 Failure::MissingArgument => abi_arg(&[], usize::MAX).unwrap_err(),
2207 Failure::MissingResult => abi_result(&mut [], usize::MAX).unwrap_err(),
2208 Failure::Ordinary => wasmtime::Error::msg("ordinary operation failure"),
2209 };
2210 let ty = FuncType::new(&engine, [], []);
2211 if asynchronous {
2212 register_host_fn_async(
2213 &mut linker,
2214 TEST_MODULE,
2215 name.clone(),
2216 ty,
2217 move |_, _, _| Box::pin(async move { Err(failure()) }),
2218 )
2219 .expect("async registration");
2220 } else {
2221 register_host_fn(
2222 &mut linker,
2223 TEST_MODULE,
2224 name.clone(),
2225 ty,
2226 move |_, _, _| Err(failure()),
2227 )
2228 .expect("sync registration");
2229 }
2230 let source = format!(
2231 r#"(module
2232 (import "{TEST_MODULE}" "{name}" (func $failure))
2233 (func (export "attempt") (result i32)
2234 (block $caught
2235 (try_table (catch_all $caught) (call $failure))
2236 (return (i32.const 0)))
2237 (i32.const 1))
2238 (func (export "healthy") (result i32) (i32.const 42)))"#
2239 );
2240 let buffer = wast::parser::ParseBuffer::new(&source).expect("WAT tokens");
2241 let mut wat = wast::parser::parse::<wast::Wat>(&buffer).expect("WAT");
2242 let module =
2243 wasmtime::Module::new(&engine, wat.encode().expect("encode")).expect("module");
2244 let instance = linker
2245 .instantiate_async(&mut store, &module)
2246 .await
2247 .expect("instance");
2248 let attempt = instance
2249 .get_typed_func::<(), i32>(&mut store, "attempt")
2250 .expect("attempt");
2251 let result = attempt.call_async(&mut store, ()).await;
2252 match kind {
2253 Failure::Fatal => {
2254 let err =
2255 result.expect_err("fatal failure cannot enter the guest catch handler");
2256 assert!(err.is::<FatalHostError>(), "typed cause lost: {err:#}");
2257 assert!(format!("{err:#}").contains("invalid state"));
2258 }
2259 Failure::MissingArgument | Failure::MissingResult => {
2260 let err = result.expect_err("ABI invariants bypass guest catch");
2261 assert_eq!(
2262 err.downcast_ref::<wasmtime::Trap>(),
2263 Some(&wasmtime::Trap::UnreachableCodeReached)
2264 );
2265 }
2266 Failure::NestedTrap(trap) => {
2267 let err = result.expect_err("a trap cannot enter the guest catch handler");
2268 assert_eq!(
2269 err.downcast_ref::<wasmtime::Trap>(),
2270 Some(&trap),
2271 "trap kind lost: {err:#}"
2272 );
2273 }
2274 Failure::Ordinary => {
2275 assert_eq!(result.expect("guest catches ordinary failure"), 1);
2276 }
2277 }
2278 let healthy = instance
2279 .get_typed_func::<(), i32>(&mut store, "healthy")
2280 .expect("healthy");
2281 assert_eq!(
2282 healthy
2283 .call_async(&mut store, ())
2284 .await
2285 .expect("subsequent call"),
2286 42
2287 );
2288 }
2289 }
2290 }
2291
2292 const TEST_MODULE: &str = "test:sync";
2293
2294 fn async_store() -> (Engine, Store<StoreData>, Linker<StoreData>) {
2297 let cfg = crate::runtime::RuntimeConfig::default();
2298 let engine = cfg.engine().expect("engine");
2299 let data = StoreData::with_vfs(
2300 "test-run".into(),
2301 crate::runtime::Vfs::tempdir().expect("tempdir"),
2302 );
2303 let store = cfg.store_async(&engine, data).expect("store");
2304 let linker = Linker::<StoreData>::new(&engine);
2305 (engine, store, linker)
2306 }
2307
2308 fn resolve(
2309 linker: &Linker<StoreData>,
2310 store: &mut Store<StoreData>,
2311 name: &crate::MangledName,
2312 ) -> wasmtime::Func {
2313 match linker
2314 .get(&mut *store, TEST_MODULE, name.as_str())
2315 .expect("registered function resolves")
2316 {
2317 wasmtime::Extern::Func(func) => func,
2318 other => panic!("expected a func, got {other:?}"),
2319 }
2320 }
2321
2322 #[test]
2326 fn sync_bodies_are_callable_synchronously_on_an_async_store() {
2327 let (engine, mut store, mut linker) = async_store();
2328 let name = crate::mangle::host(TEST_MODULE, "answer");
2329 register_host_fn(
2330 &mut linker,
2331 TEST_MODULE,
2332 name.clone(),
2333 FuncType::new(&engine, [], [ValType::I32]),
2334 |_caller, _params, results| {
2335 results[0] = Val::I32(42);
2336 Ok(())
2337 },
2338 )
2339 .expect("register");
2340
2341 let func = resolve(&linker, &mut store, &name);
2342 let mut results = [Val::I32(0)];
2343 func.call(&mut store, &[], &mut results)
2344 .expect("synchronous call reaches the body");
2345 assert_eq!(results[0].i32(), Some(42));
2346 }
2347
2348 #[test]
2355 fn an_ordinary_error_keeps_its_message() {
2356 let (engine, mut store, mut linker) = async_store();
2357 let name = crate::mangle::host(TEST_MODULE, "boom");
2358 register_host_fn(
2359 &mut linker,
2360 TEST_MODULE,
2361 name.clone(),
2362 FuncType::new(&engine, [], []),
2363 |_caller, _params, _results| Err(wasmtime::Error::msg("boom")),
2364 )
2365 .expect("register");
2366
2367 let func = resolve(&linker, &mut store, &name);
2368 let err = func
2369 .call(&mut store, &[], &mut [])
2370 .expect_err("the body fails");
2371 assert!(format!("{err}").contains("boom"), "got: {err}");
2372 }
2373
2374 #[test]
2375 fn fatal_host_errors_preserve_their_marker() {
2376 let (engine, mut store, mut linker) = async_store();
2377 let name = crate::mangle::host(TEST_MODULE, "fatal");
2378 register_host_fn(
2379 &mut linker,
2380 TEST_MODULE,
2381 name.clone(),
2382 FuncType::new(&engine, [], []),
2383 |_, _, _| Err(super::fatal_host_error("broken dynamic object ABI")),
2384 )
2385 .expect("register");
2386 let func = resolve(&linker, &mut store, &name);
2387 let error = func
2388 .call(&mut store, &[], &mut [])
2389 .expect_err("fatal host error");
2390 assert!(
2391 error.is::<super::FatalHostError>(),
2392 "fatal errors must bypass guest conversion: {error}"
2393 );
2394 }
2395
2396 #[tokio::test]
2397 async fn async_fatal_host_errors_preserve_their_marker() {
2398 let (engine, mut store, mut linker) = async_store();
2399 let name = crate::mangle::host(TEST_MODULE, "fatal_async");
2400 super::register_host_fn_async(
2401 &mut linker,
2402 TEST_MODULE,
2403 name.clone(),
2404 FuncType::new(&engine, [], []),
2405 |_, _, _| Box::pin(async { Err(super::fatal_host_error("broken dynamic object ABI")) }),
2406 )
2407 .expect("register");
2408 let func = resolve(&linker, &mut store, &name);
2409 let error = func
2410 .call_async(&mut store, &[], &mut [])
2411 .await
2412 .expect_err("fatal host error");
2413 assert!(
2414 error.is::<super::FatalHostError>(),
2415 "fatal errors must bypass guest conversion: {error}"
2416 );
2417 }
2418
2419 #[test]
2422 fn a_thrown_exception_passes_through_unconverted() {
2423 let (engine, mut store, mut linker) = async_store();
2424 let name = crate::mangle::host(TEST_MODULE, "rethrow");
2425 register_host_fn(
2426 &mut linker,
2427 TEST_MODULE,
2428 name.clone(),
2429 FuncType::new(&engine, [], []),
2430 |_caller, _params, _results| Err(wasmtime::Error::new(wasmtime::ThrownException)),
2431 )
2432 .expect("register");
2433
2434 let func = resolve(&linker, &mut store, &name);
2435 let err = func
2436 .call(&mut store, &[], &mut [])
2437 .expect_err("the body throws");
2438 assert!(
2439 err.is::<wasmtime::ThrownException>(),
2440 "the pending exception must survive; got: {err}"
2441 );
2442 }
2443}