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 Ok(())
61}
62
63pub fn install_store_bound(
66 linker: &mut Linker<StoreData>,
67 store: &mut Store<StoreData>,
68) -> wasmtime::Result<()> {
69 install_prelude(linker, store)?;
70 crate::stdlib::install_store_bound(linker, store)
71}
72
73fn install_prelude(
77 linker: &mut Linker<StoreData>,
78 store: &mut Store<StoreData>,
79) -> wasmtime::Result<()> {
80 let (vtables, error_host) = super::prelude::install_vtables(linker, store)?;
81 let intr = build_intrinsic_types(store.engine())?;
82 let map_tombstone =
83 super::prelude::map::build_tombstone(store, intr.object.clone(), vtables.object)?;
84 let collection_null =
85 super::prelude::map::build_tombstone(store, intr.object.clone(), vtables.object)?;
86 let temporal = super::prelude::temporal::install_abi(linker, store, &intr)?;
87 let error_subclass_type =
88 super::prelude::error::build_error_subclass_types(store.engine(), &intr)?.1;
89 let map_backing_type = super::prelude::map::map_backing_struct(store.engine(), &intr)?;
90 let set_backing_type = super::prelude::set::set_backing_struct(store.engine(), &intr)?;
91 let member_functions = super::prelude::member::functions(linker, store);
92 store.data_mut().host_abi = Some(HostAbi {
93 member_functions,
94 string_type: intr.string,
95 uint8_type: intr.uint8_array,
96 array_type: intr.array,
97 raw_array_type: intr.raw_array,
98 boxed_number_type: intr.boxed_number,
99 boxed_boolean_type: intr.boxed_boolean,
100 field_names_type: intr.field_names,
101 object_shape_type: intr.object_shape,
102 object_fields_type: intr.object_fields,
103 map_backing_type,
104 set_backing_type,
105 string_vtable: vtables.string,
106 array_vtable: vtables.array,
107 object_vtable: vtables.object,
108 boxed_number_vtable: vtables.boxed_number,
109 boxed_boolean_vtable: vtables.boxed_boolean,
110 uint8_array_vtable: vtables.uint8_array,
111 bigint_vtable: vtables.bigint,
112 closure_vtable: vtables.closure,
113 regex_vtable: vtables.regex,
114 regex_match_box_vtable: vtables.regex_match_box,
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 error_field_names: error_host.field_names,
130 permission_denied_field_names: error_host.permission_denied_field_names,
131 error_tag: error_host.tag,
132 });
133 Ok(())
134}
135
136fn install_core_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
137 let engine = linker.engine().clone();
138
139 let string_type = string_array_type(&engine);
148
149 install_number_module(linker)?;
150 super::prelude::install(linker)?;
151 super::prelude::bigint::ops::install(linker, &string_type)?;
152 install_internal_module(linker, &string_type)?;
153 super::json::install_json_module(linker, &string_type)?;
154 Ok(())
155}
156
157fn install_internal_module(
158 linker: &mut Linker<StoreData>,
159 string_type: &ArrayType,
160) -> wasmtime::Result<()> {
161 use base64::Engine as _;
162 use base64::alphabet;
163 use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig, NO_PAD, PAD};
164
165 let engine = linker.engine().clone();
166
167 let raw_uint8_array_type = uint8_array_array_type(&engine);
168 let raw_uint8_array_param = ValType::Ref(RefType::new(
169 false,
170 HeapType::from(raw_uint8_array_type.clone()),
171 ));
172 let raw_uint8_array_result = raw_uint8_array_param.clone();
173 let raw_string_param = ValType::Ref(RefType::new(false, HeapType::from(string_type.clone())));
174 let raw_string_result = raw_string_param.clone();
175
176 let ty = FuncType::new(
177 &engine,
178 [raw_uint8_array_param.clone(), ValType::I32, ValType::I32],
179 [raw_string_result.clone()],
180 );
181 register_host_fn(
182 linker,
183 INTERNAL_MODULE_NAME,
184 crate::mangle::host(INTERNAL_MODULE_NAME, "uint8array_to_base64"),
185 ty,
186 true,
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 true,
224 move |caller, params, results| -> wasmtime::Result<()> {
225 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "uint8array_from_base64")?;
226 fuel::charge(&mut *caller, fuel::SCAN, s.len() as u64)?;
227 let alphabet_flag = (*abi_arg(params, 1)?)
228 .i32()
229 .ok_or_else(|| invariant_trap("host ABI: expected i32"))?;
230 let alphabet = if alphabet_flag == 1 {
231 &alphabet::URL_SAFE
232 } else {
233 &alphabet::STANDARD
234 };
235 let config = if s.as_bytes().last() == Some(&b'=') {
237 PAD
238 } else {
239 NO_PAD
240 };
241 let decoded = GeneralPurpose::new(alphabet, config).decode(s.as_bytes());
242 let bytes =
243 decoded.map_err(|e| wasmtime::Error::msg(format!("Uint8Array.fromBase64: {e}")))?;
244 let arr = write_uint8_array(
245 &mut *caller,
246 raw_uint8_array_type_for_decode.clone(),
247 &bytes,
248 )?;
249 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
250 Ok(())
251 },
252 )?;
253
254 let ty = FuncType::new(
255 &engine,
256 [raw_string_param.clone()],
257 [raw_uint8_array_result.clone()],
258 );
259 let raw_uint8_array_type_for_encode = raw_uint8_array_type.clone();
260 register_host_fn(
261 linker,
262 INTERNAL_MODULE_NAME,
263 crate::mangle::host(INTERNAL_MODULE_NAME, "textencoder_encode"),
264 ty,
265 true,
266 move |caller, params, results| -> wasmtime::Result<()> {
267 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "textencoder_encode")?;
268 let bytes = s.into_bytes();
269 let arr = write_uint8_array(
270 &mut *caller,
271 raw_uint8_array_type_for_encode.clone(),
272 &bytes,
273 )?;
274 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
275 Ok(())
276 },
277 )?;
278
279 let ty = FuncType::new(
280 &engine,
281 [raw_uint8_array_param],
282 [raw_string_result.clone()],
283 );
284 register_host_fn(
285 linker,
286 INTERNAL_MODULE_NAME,
287 crate::mangle::host(INTERNAL_MODULE_NAME, "textdecoder_decode"),
288 ty,
289 true,
290 |caller, params, results| -> wasmtime::Result<()> {
291 let bytes =
292 read_uint8_array_arg(&mut *caller, abi_arg(params, 0)?, "textdecoder_decode")?;
293 let s = std::str::from_utf8(&bytes).map_err(|e| {
294 type_error(format!(
295 "TextDecoder.decode: invalid UTF-8 at byte {}: {e}",
296 e.valid_up_to(),
297 ))
298 })?;
299 let arr = write_submilli_string(&mut *caller, s)?;
300 *abi_result(results, 0)? = Val::AnyRef(Some(arr.to_anyref()));
301 Ok(())
302 },
303 )?;
304
305 Ok(())
306}
307
308pub(crate) fn write_uint8_array(
311 mut ctx: impl AsContextMut<Data = StoreData>,
312 array_ty: ArrayType,
313 bytes: &[u8],
314) -> wasmtime::Result<Rooted<ArrayRef>> {
315 fuel::charge(&mut ctx, fuel::COPY, bytes.len() as u64)?;
316 write_uint8_array_precharged(ctx, array_ty, bytes)
317}
318
319pub(crate) fn write_uint8_array_precharged(
321 mut ctx: impl AsContextMut<Data = StoreData>,
322 array_ty: ArrayType,
323 bytes: &[u8],
324) -> wasmtime::Result<Rooted<ArrayRef>> {
325 let pre = ArrayRefPre::new(&mut ctx, array_ty);
326 ArrayRef::new_from_i8_slice(&mut ctx, &pre, bytes)
327}
328
329pub(crate) fn uint8_array_array_type(engine: &Engine) -> ArrayType {
330 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I8))
331}
332
333pub(crate) fn read_uint8_array_arg(
334 caller: &mut Caller<'_, StoreData>,
335 val: &Val,
336 name: &str,
337) -> wasmtime::Result<Vec<u8>> {
338 let arr = uint8_array_backing(caller, val, name)?;
339 let len = usize::try_from(arr.len(&mut *caller)?).map_err(fatal_host_error)?;
340 read_uint8_array_range(caller, arr, 0, len, name)
341}
342
343pub(crate) fn read_uint8_array_range(
347 caller: &mut Caller<'_, StoreData>,
348 arr: Rooted<ArrayRef>,
349 offset: usize,
350 len: usize,
351 name: &str,
352) -> wasmtime::Result<Vec<u8>> {
353 fuel::charge(&mut *caller, fuel::COPY, len as u64)?;
354 let offset = u32::try_from(offset).map_err(fatal_host_error)?;
355 let mut out = Vec::new();
356 out.try_reserve_exact(len).map_err(fatal_host_error)?;
357 out.resize(len, 0);
358 arr.read_i8(&mut *caller, offset, &mut out)
359 .map_err(|error| type_error(format!("{name}: {error}")))?;
360 Ok(out)
361}
362
363pub(crate) fn read_uint8(
365 caller: &mut Caller<'_, StoreData>,
366 arr: Rooted<ArrayRef>,
367 index: usize,
368) -> wasmtime::Result<u8> {
369 let index = u32::try_from(index).map_err(fatal_host_error)?;
370 match arr.get(&mut *caller, index).map_err(fatal_host_error)? {
371 Val::I32(byte) => Ok(byte as u8),
372 other => Err(fatal_host_error(format!("byte {index} is {other:?}"))),
373 }
374}
375
376pub(crate) fn uint8_array_backing(
378 caller: &mut Caller<'_, StoreData>,
379 val: &Val,
380 name: &str,
381) -> wasmtime::Result<Rooted<ArrayRef>> {
382 let any = match val {
383 Val::AnyRef(Some(any)) => *any,
384 Val::AnyRef(None) => {
385 return Err(type_error(format!("{name} arg is null")));
386 }
387 other => {
388 return Err(type_error(format!(
389 "{name} expects a Uint8Array, got {other:?}"
390 )));
391 }
392 };
393 let arr = if let Some(st) = any.as_struct(&mut *caller)? {
396 let uint8_ty = intrinsic_types(&mut *caller)?.uint8_array.clone();
400 if !st.matches_ty(&*caller, &uint8_ty)? {
401 return Err(type_error(format!("{name} expects a Uint8Array")));
402 }
403 match st.field(&mut *caller, 1)? {
404 Val::AnyRef(Some(inner)) => inner.unwrap_array(&mut *caller)?,
405 other => {
406 return Err(wasmtime::Error::msg(format!(
407 "{name}: malformed $Uint8Array payload {other:?}"
408 )));
409 }
410 }
411 } else {
412 any.unwrap_array(&mut *caller)?
413 };
414 Ok(arr)
415}
416
417fn install_number_module(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
418 type FormatOp = fn(f64, f64) -> Result<String, String>;
419 let engine = linker.engine().clone();
420 let string_struct = intrinsic_string_type(&engine)?;
421 let string_struct_result =
422 ValType::Ref(RefType::new(false, HeapType::ConcreteStruct(string_struct)));
423 let string_struct_param = string_struct_result.clone();
424
425 let to_string_ty = FuncType::new(&engine, [ValType::F64], [string_struct_result.clone()]);
426 register_host_fn(
427 linker,
428 NUMBER_MODULE_NAME,
429 crate::mangle::host(NUMBER_MODULE_NAME, "toString"),
430 to_string_ty,
431 true,
432 |caller, params, results| -> wasmtime::Result<()> {
433 let n = match *abi_arg(params, 0)? {
434 Val::F64(bits) => f64::from_bits(bits),
435 ref other => {
436 return Err(type_error(format!(
437 "number.toString expects f64, got {other:?}"
438 )));
439 }
440 };
441 let s = format_number(n);
442 let st = write_submilli_string_struct(caller, &s)?;
443 *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
444 Ok(())
445 },
446 )?;
447
448 let to_number_ty = FuncType::new(&engine, [string_struct_param.clone()], [ValType::F64]);
449 register_host_fn(
450 linker,
451 NUMBER_MODULE_NAME,
452 crate::mangle::host(NUMBER_MODULE_NAME, "toNumber"),
453 to_number_ty,
454 true,
455 |caller, params, results| -> wasmtime::Result<()> {
456 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "number.toNumber")?;
457 let value = string_to_number_js(&s);
458 *abi_result(results, 0)? = Val::F64(value.to_bits());
459 Ok(())
460 },
461 )?;
462
463 let parse_int_ty = FuncType::new(
464 &engine,
465 [string_struct_param.clone(), ValType::F64],
466 [ValType::F64],
467 );
468 register_host_fn(
469 linker,
470 NUMBER_MODULE_NAME,
471 crate::mangle::host(NUMBER_MODULE_NAME, "parseInt"),
472 parse_int_ty,
473 true,
474 |caller, params, results| -> wasmtime::Result<()> {
475 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "number.parseInt")?;
476 let radix = match *abi_arg(params, 1)? {
477 Val::F64(bits) => f64::from_bits(bits),
478 ref other => {
479 return Err(type_error(format!(
480 "number.parseInt expects f64 radix, got {other:?}"
481 )));
482 }
483 };
484 let value = parse_int_with_radix(&s, radix);
485 *abi_result(results, 0)? = Val::F64(value.to_bits());
486 Ok(())
487 },
488 )?;
489
490 let parse_float_ty = FuncType::new(&engine, [string_struct_param], [ValType::F64]);
491 register_host_fn(
492 linker,
493 NUMBER_MODULE_NAME,
494 crate::mangle::host(NUMBER_MODULE_NAME, "parseFloat"),
495 parse_float_ty,
496 true,
497 |caller, params, results| -> wasmtime::Result<()> {
498 let s = read_string_arg(&mut *caller, abi_arg(params, 0)?, "number.parseFloat")?;
499 let value = parse_float_js(&s);
500 *abi_result(results, 0)? = Val::F64(value.to_bits());
501 Ok(())
502 },
503 )?;
504
505 let format_ty = FuncType::new(
508 &engine,
509 [ValType::F64, ValType::F64],
510 [string_struct_result],
511 );
512 for (name, op) in [
513 ("toFixed", crate::runtime::number::to_fixed_js as FormatOp),
514 ("toPrecision", crate::runtime::number::to_precision_js),
515 ("toExponential", crate::runtime::number::to_exponential_js),
516 ("toStringRadix", crate::runtime::number::to_string_radix_js),
517 ] {
518 register_number_formatter(linker, name, format_ty.clone(), op)?;
519 }
520
521 Ok(())
522}
523
524fn register_number_formatter(
525 linker: &mut Linker<StoreData>,
526 name: &'static str,
527 ty: FuncType,
528 op: fn(f64, f64) -> Result<String, String>,
529) -> wasmtime::Result<()> {
530 register_host_fn(
531 linker,
532 NUMBER_MODULE_NAME,
533 crate::mangle::host(NUMBER_MODULE_NAME, name),
534 ty,
535 true,
536 move |caller, params, results| -> wasmtime::Result<()> {
537 let (Val::F64(x_bits), Val::F64(arg_bits)) = (abi_arg(params, 0)?, abi_arg(params, 1)?)
538 else {
539 wasmtime::bail!("number.{name} expects (f64, f64)");
540 };
541 let formatted = op(f64::from_bits(*x_bits), f64::from_bits(*arg_bits))
542 .map_err(wasmtime::Error::msg)?;
543 let st = write_submilli_string_struct(caller, &formatted)?;
544 *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
545 Ok(())
546 },
547 )
548}
549
550fn format_number(n: f64) -> String {
552 if n.is_nan() {
553 "NaN".to_string()
554 } else if n.is_infinite() {
555 if n > 0.0 {
556 "Infinity".to_string()
557 } else {
558 "-Infinity".to_string()
559 }
560 } else if n == 0.0 {
561 "0".to_string()
564 } else {
565 n.to_string()
566 }
567}
568
569fn read_string_from_anyref(
572 caller: &mut Caller<'_, StoreData>,
573 any: Rooted<AnyRef>,
574 name: &str,
575) -> wasmtime::Result<String> {
576 let arr = if let Some(st) = any.as_struct(&mut *caller)? {
577 match st.field(&mut *caller, 1)? {
578 Val::AnyRef(Some(inner)) => inner.unwrap_array(&mut *caller)?,
579 other => {
580 return Err(wasmtime::Error::msg(format!(
581 "{name}: malformed $string payload {other:?}"
582 )));
583 }
584 }
585 } else {
586 any.unwrap_array(&mut *caller)?
587 };
588 read_submilli_string(caller, arr)
589}
590
591pub fn read_string_arg(
592 caller: &mut Caller<'_, StoreData>,
593 val: &Val,
594 name: &str,
595) -> wasmtime::Result<String> {
596 match val {
597 Val::AnyRef(Some(any)) => read_string_from_anyref(caller, *any, name),
598 Val::AnyRef(None) => Err(type_error(format!("{name} arg is null"))),
599 other => Err(type_error(format!(
600 "{name} expects a string, got {other:?}"
601 ))),
602 }
603}
604
605pub fn read_string_array_arg(
607 caller: &mut Caller<'_, StoreData>,
608 val: &Val,
609 name: &str,
610) -> wasmtime::Result<Vec<String>> {
611 let any = match val {
612 Val::AnyRef(Some(any)) => *any,
613 Val::AnyRef(None) => return Err(type_error(format!("{name} arg is null"))),
614 other => {
615 return Err(type_error(format!(
616 "{name} expects a string[], got {other:?}"
617 )));
618 }
619 };
620 let object = any
621 .as_struct(&mut *caller)?
622 .ok_or_else(|| type_error(format!("{name}: expected $Array struct")))?;
623 let storage = super::array_storage::ArrayStorage::from_struct(caller, object)?;
624 let raw = storage.backing;
625 let len = storage.len;
626 let mut out = Vec::new();
627 out.try_reserve_exact(len as usize)
628 .map_err(fatal_host_error)?;
629 for i in 0..len {
630 match raw.get(&mut *caller, i)? {
631 Val::AnyRef(Some(elem)) => out.push(read_string_from_anyref(caller, elem, name)?),
632 Val::AnyRef(None) => out.push(String::new()),
633 other => {
634 return Err(type_error(format!(
635 "{name}: slot {i} expects a $string, got {other:?}"
636 )));
637 }
638 }
639 }
640 Ok(out)
641}
642
643pub fn write_submilli_string(
646 mut ctx: impl AsContextMut<Data = StoreData>,
647 s: &str,
648) -> wasmtime::Result<Rooted<ArrayRef>> {
649 let units = encode_utf16(&mut ctx, s)?;
650 write_code_units(ctx, &units)
651}
652
653pub(crate) fn encode_utf16(
655 ctx: impl AsContextMut<Data = StoreData>,
656 s: &str,
657) -> wasmtime::Result<Vec<u16>> {
658 fuel::charge(ctx, fuel::SCAN, s.len() as u64)?;
659 Ok(s.encode_utf16().collect())
660}
661
662pub(crate) fn write_code_units(
666 mut ctx: impl AsContextMut<Data = StoreData>,
667 units: &[u16],
668) -> wasmtime::Result<Rooted<ArrayRef>> {
669 fuel::charge(&mut ctx, fuel::COPY, units.len() as u64)?;
670 let array_ty = string_array_type(ctx.as_context().engine());
671 let pre = ArrayRefPre::new(&mut ctx, array_ty);
672 ArrayRef::new_from_i16_slice(&mut ctx, &pre, units)
673}
674
675pub(crate) fn read_code_units(
683 mut ctx: impl AsContextMut<Data = StoreData>,
684 raw: Rooted<ArrayRef>,
685 name: &str,
686) -> wasmtime::Result<Vec<u16>> {
687 let len = raw.len(&mut ctx)?;
688 fuel::charge(&mut ctx, fuel::COPY, u64::from(len))?;
689 let len = usize::try_from(len).map_err(fatal_host_error)?;
690 let mut units = Vec::new();
691 units.try_reserve_exact(len).map_err(fatal_host_error)?;
692 units.resize(len, 0);
693 raw.copy_to_i16_slice(&mut ctx, &mut units)
694 .map_err(|error| wasmtime::Error::msg(format!("{name}: {error}")))?;
695 Ok(units)
696}
697
698pub(crate) fn read_code_units_range(
703 mut ctx: impl AsContextMut<Data = StoreData>,
704 raw: Rooted<ArrayRef>,
705 offset: usize,
706 len: usize,
707) -> wasmtime::Result<Vec<u16>> {
708 fuel::charge(&mut ctx, fuel::COPY, len as u64)?;
709 let offset = u32::try_from(offset).map_err(fatal_host_error)?;
710 let mut units = Vec::new();
711 units.try_reserve_exact(len).map_err(fatal_host_error)?;
712 units.resize(len, 0);
713 raw.read_i16(&mut ctx, offset, &mut units)
714 .map_err(fatal_host_error)?;
715 Ok(units)
716}
717
718pub(crate) fn read_code_unit(
720 mut ctx: impl AsContextMut<Data = StoreData>,
721 raw: Rooted<ArrayRef>,
722 index: usize,
723) -> wasmtime::Result<u16> {
724 let index = u32::try_from(index).map_err(fatal_host_error)?;
725 match raw.get(&mut ctx, index).map_err(fatal_host_error)? {
726 Val::I32(unit) => Ok(unit as u16),
727 other => Err(fatal_host_error(format!("code unit {index} is {other:?}"))),
728 }
729}
730
731pub struct HostAbi {
738 pub(crate) member_functions: std::collections::BTreeMap<String, wasmtime::Func>,
739 pub(crate) string_type: StructType,
740 pub(crate) uint8_type: StructType,
741 pub(crate) array_type: StructType,
742 pub(crate) raw_array_type: ArrayType,
743 pub(crate) boxed_number_type: StructType,
744 pub(crate) boxed_boolean_type: StructType,
745 pub(crate) field_names_type: ArrayType,
746 pub(crate) object_shape_type: StructType,
747 pub(crate) object_fields_type: ArrayType,
748 pub(crate) map_backing_type: StructType,
754 pub(crate) set_backing_type: StructType,
755 pub(crate) string_vtable: Global,
758 pub(crate) array_vtable: Global,
759 pub(crate) object_vtable: Global,
760 pub(crate) boxed_number_vtable: Global,
761 pub(crate) boxed_boolean_vtable: Global,
762 pub(crate) uint8_array_vtable: Global,
763 pub(crate) bigint_vtable: Global,
764 pub(crate) closure_vtable: Global,
765 pub(crate) regex_vtable: Global,
766 pub(crate) regex_match_box_vtable: Global,
767 pub(crate) opaque_vtable: Global,
770 pub(crate) temporal: super::prelude::temporal::TemporalAbi,
771 pub(crate) map_tombstone: Global,
774 pub(crate) collection_null: Global,
775 pub(crate) error_type: StructType,
776 pub(crate) error_subclass_type: StructType,
780 pub(crate) error_vtable: Global,
781 pub(crate) range_error_vtable: Global,
782 pub(crate) quota_exceeded_vtable: Global,
783 pub(crate) type_error_vtable: Global,
784 pub(crate) syntax_error_vtable: Global,
785 pub(crate) uri_error_vtable: Global,
786 pub(crate) reference_error_vtable: Global,
787 pub(crate) permission_denied_vtable: Global,
788 pub(crate) error_field_names: Global,
789 pub(crate) permission_denied_field_names: Global,
790 pub(crate) error_tag: wasmtime::Tag,
791}
792
793pub(crate) struct HostAbiHandles {
795 pub error_type: StructType,
796 pub error_subclass_type: StructType,
797 pub object_fields_type: ArrayType,
798 pub error_vtable: Global,
799 pub range_error_vtable: Global,
800 pub quota_exceeded_vtable: Global,
801 pub type_error_vtable: Global,
802 pub syntax_error_vtable: Global,
803 pub uri_error_vtable: Global,
804 pub reference_error_vtable: Global,
805 pub permission_denied_vtable: Global,
806 pub error_field_names: Global,
807 pub permission_denied_field_names: Global,
808}
809
810pub(crate) fn error_abi(caller: &Caller<'_, StoreData>) -> wasmtime::Result<HostAbiHandles> {
811 let abi = caller
812 .data()
813 .host_abi
814 .as_ref()
815 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
816 Ok(HostAbiHandles {
817 error_type: abi.error_type.clone(),
818 error_subclass_type: abi.error_subclass_type.clone(),
819 object_fields_type: abi.object_fields_type.clone(),
820 error_vtable: abi.error_vtable,
821 range_error_vtable: abi.range_error_vtable,
822 quota_exceeded_vtable: abi.quota_exceeded_vtable,
823 type_error_vtable: abi.type_error_vtable,
824 syntax_error_vtable: abi.syntax_error_vtable,
825 uri_error_vtable: abi.uri_error_vtable,
826 reference_error_vtable: abi.reference_error_vtable,
827 permission_denied_vtable: abi.permission_denied_vtable,
828 error_field_names: abi.error_field_names,
829 permission_denied_field_names: abi.permission_denied_field_names,
830 })
831}
832
833fn host_vtable(
836 ctx: &mut impl AsContextMut<Data = StoreData>,
837 select: impl FnOnce(&HostAbi) -> Global,
838) -> wasmtime::Result<Val> {
839 let global =
840 {
841 let abi =
842 ctx.as_context().data().host_abi.as_ref().ok_or_else(|| {
843 wasmtime::Error::msg("host_abi unset (prelude not instantiated)")
844 })?;
845 select(abi)
846 };
847 Ok(global.get(&mut *ctx))
848}
849
850pub(crate) fn host_string_vtable(
851 ctx: &mut impl AsContextMut<Data = StoreData>,
852) -> wasmtime::Result<Val> {
853 host_vtable(ctx, |abi| abi.string_vtable)
854}
855
856pub(crate) fn host_array_vtable(
857 ctx: &mut impl AsContextMut<Data = StoreData>,
858) -> wasmtime::Result<Val> {
859 host_vtable(ctx, |abi| abi.array_vtable)
860}
861
862pub(crate) fn host_object_vtable(
863 ctx: &mut impl AsContextMut<Data = StoreData>,
864) -> wasmtime::Result<Val> {
865 host_vtable(ctx, |abi| abi.object_vtable)
866}
867
868pub(crate) fn host_boxed_number_vtable(
869 ctx: &mut impl AsContextMut<Data = StoreData>,
870) -> wasmtime::Result<Val> {
871 host_vtable(ctx, |abi| abi.boxed_number_vtable)
872}
873
874pub(crate) fn host_boxed_boolean_vtable(
875 ctx: &mut impl AsContextMut<Data = StoreData>,
876) -> wasmtime::Result<Val> {
877 host_vtable(ctx, |abi| abi.boxed_boolean_vtable)
878}
879
880pub(crate) fn host_uint8_array_vtable(
881 ctx: &mut impl AsContextMut<Data = StoreData>,
882) -> wasmtime::Result<Val> {
883 host_vtable(ctx, |abi| abi.uint8_array_vtable)
884}
885
886pub(crate) fn host_bigint_vtable(
887 ctx: &mut impl AsContextMut<Data = StoreData>,
888) -> wasmtime::Result<Val> {
889 host_vtable(ctx, |abi| abi.bigint_vtable)
890}
891
892pub(crate) fn host_closure_vtable(
893 ctx: &mut impl AsContextMut<Data = StoreData>,
894) -> wasmtime::Result<Val> {
895 host_vtable(ctx, |abi| abi.closure_vtable)
896}
897
898pub(crate) fn host_regex_vtable(
899 ctx: &mut impl AsContextMut<Data = StoreData>,
900) -> wasmtime::Result<Val> {
901 host_vtable(ctx, |abi| abi.regex_vtable)
902}
903
904pub(crate) fn host_regex_match_box_vtable(
905 ctx: &mut impl AsContextMut<Data = StoreData>,
906) -> wasmtime::Result<Val> {
907 host_vtable(ctx, |abi| abi.regex_match_box_vtable)
908}
909
910pub(crate) fn host_opaque_vtable(
911 ctx: &mut impl AsContextMut<Data = StoreData>,
912) -> wasmtime::Result<Val> {
913 host_vtable(ctx, |abi| abi.opaque_vtable)
914}
915
916pub(crate) fn host_map_tombstone(
918 ctx: &mut impl AsContextMut<Data = StoreData>,
919) -> wasmtime::Result<Val> {
920 host_vtable(ctx, |abi| abi.map_tombstone)
921}
922pub(crate) fn host_collection_null(
924 ctx: &mut impl AsContextMut<Data = StoreData>,
925) -> wasmtime::Result<Val> {
926 host_vtable(ctx, |abi| abi.collection_null)
927}
928
929pub fn write_submilli_string_struct(
932 caller: &mut Caller<'_, StoreData>,
933 s: &str,
934) -> wasmtime::Result<Rooted<StructRef>> {
935 let units = encode_utf16(&mut *caller, s)?;
936 write_submilli_string_struct_units(caller, &units)
937}
938
939pub fn write_submilli_string_struct_units(
944 caller: &mut Caller<'_, StoreData>,
945 units: &[u16],
946) -> wasmtime::Result<Rooted<StructRef>> {
947 let raw = write_code_units(&mut *caller, units)?;
949 let string_type = {
950 let abi = caller
951 .data()
952 .host_abi
953 .as_ref()
954 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
955 abi.string_type.clone()
956 };
957 let vtable = host_string_vtable(caller)?;
958 let pre = StructRefPre::new(&mut *caller, string_type);
959 StructRef::new(
960 &mut *caller,
961 &pre,
962 &[vtable, Val::AnyRef(Some(raw.to_anyref())), Val::I64(0)],
963 )
964}
965
966pub fn write_submilli_uint8array_struct(
969 caller: &mut Caller<'_, StoreData>,
970 bytes: &[u8],
971) -> wasmtime::Result<Rooted<StructRef>> {
972 let array_ty = uint8_array_array_type(caller.engine());
973 let raw = write_uint8_array(&mut *caller, array_ty, bytes)?;
974 let uint8_type = {
975 let abi = caller
976 .data()
977 .host_abi
978 .as_ref()
979 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
980 abi.uint8_type.clone()
981 };
982 let vtable = host_uint8_array_vtable(caller)?;
983 let pre = StructRefPre::new(&mut *caller, uint8_type);
984 StructRef::new(
985 &mut *caller,
986 &pre,
987 &[vtable, Val::AnyRef(Some(raw.to_anyref()))],
988 )
989}
990
991pub(crate) fn read_boxed_number(
993 caller: &mut Caller<'_, StoreData>,
994 val: &Val,
995 context: &str,
996) -> wasmtime::Result<f64> {
997 let Val::AnyRef(Some(any)) = val else {
998 return Err(type_error(format!("{context}: expected a boxed number")));
999 };
1000 let st = any
1001 .as_struct(&mut *caller)?
1002 .ok_or_else(|| type_error(format!("{context}: expected a boxed number")))?;
1003 match st.field(&mut *caller, 1)? {
1004 Val::F64(bits) => Ok(f64::from_bits(bits)),
1005 other => Err(wasmtime::Error::msg(format!(
1006 "{context}: boxed-number payload is {other:?}"
1007 ))),
1008 }
1009}
1010
1011pub(crate) fn write_boxed_number_struct(
1014 caller: &mut Caller<'_, StoreData>,
1015 n: f64,
1016) -> wasmtime::Result<Rooted<StructRef>> {
1017 let boxed_type = {
1018 let abi = caller
1019 .data()
1020 .host_abi
1021 .as_ref()
1022 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1023 abi.boxed_number_type.clone()
1024 };
1025 let vtable = host_boxed_number_vtable(caller)?;
1026 let pre = StructRefPre::new(&mut *caller, boxed_type);
1027 StructRef::new(&mut *caller, &pre, &[vtable, Val::F64(n.to_bits())])
1028}
1029
1030pub fn write_submilli_array_struct(
1034 caller: &mut Caller<'_, StoreData>,
1035 elements: &[Val],
1036) -> wasmtime::Result<Rooted<StructRef>> {
1037 let len = super::array_storage::checked_length(elements.len())?;
1038 fuel::charge(&mut *caller, fuel::ELEM, u64::from(len))?;
1039 write_submilli_array_struct_precharged(caller, elements)
1040}
1041
1042pub(crate) fn write_submilli_array_struct_precharged(
1044 caller: &mut Caller<'_, StoreData>,
1045 elements: &[Val],
1046) -> wasmtime::Result<Rooted<StructRef>> {
1047 let len = super::array_storage::checked_length(elements.len())?;
1048 let (array_type, raw_array_type) = {
1049 let abi = caller
1050 .data()
1051 .host_abi
1052 .as_ref()
1053 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1054 (abi.array_type.clone(), abi.raw_array_type.clone())
1055 };
1056 let raw_pre = ArrayRefPre::new(&mut *caller, raw_array_type);
1057 let raw = ArrayRef::new_fixed(&mut *caller, &raw_pre, elements)?;
1058 let vtable = host_array_vtable(caller)?;
1059 let pre = StructRefPre::new(&mut *caller, array_type);
1060 StructRef::new(
1061 &mut *caller,
1062 &pre,
1063 &[
1064 vtable,
1065 Val::AnyRef(Some(raw.to_anyref())),
1066 Val::I32(len as i32),
1067 ],
1068 )
1069}
1070
1071#[derive(Debug)]
1077pub struct RangeError(pub String);
1078
1079impl std::fmt::Display for RangeError {
1080 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1081 f.write_str(&self.0)
1082 }
1083}
1084
1085impl std::error::Error for RangeError {}
1086
1087pub fn range_error(message: impl Into<String>) -> wasmtime::Error {
1089 wasmtime::Error::new(RangeError(message.into()))
1090}
1091
1092#[derive(Debug)]
1098pub struct QuotaExceededError(pub String);
1099
1100impl std::fmt::Display for QuotaExceededError {
1101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1102 f.write_str(&self.0)
1103 }
1104}
1105
1106impl std::error::Error for QuotaExceededError {}
1107
1108pub fn quota_exceeded_error(message: impl Into<String>) -> wasmtime::Error {
1110 wasmtime::Error::new(QuotaExceededError(message.into()))
1111}
1112
1113#[derive(Debug)]
1116pub struct SyntaxError(pub String);
1117
1118impl std::fmt::Display for SyntaxError {
1119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1120 f.write_str(&self.0)
1121 }
1122}
1123
1124impl std::error::Error for SyntaxError {}
1125
1126pub fn syntax_error(message: impl Into<String>) -> wasmtime::Error {
1128 wasmtime::Error::new(SyntaxError(message.into()))
1129}
1130
1131#[derive(Debug)]
1134pub struct UriError(pub String);
1135
1136impl std::fmt::Display for UriError {
1137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1138 f.write_str(&self.0)
1139 }
1140}
1141
1142impl std::error::Error for UriError {}
1143
1144pub fn uri_error(message: impl Into<String>) -> wasmtime::Error {
1146 wasmtime::Error::new(UriError(message.into()))
1147}
1148
1149#[derive(Debug)]
1155pub struct TypeError(pub String);
1156
1157impl std::fmt::Display for TypeError {
1158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1159 f.write_str(&self.0)
1160 }
1161}
1162
1163impl std::error::Error for TypeError {}
1164
1165pub fn type_error(message: impl Into<String>) -> wasmtime::Error {
1167 wasmtime::Error::new(TypeError(message.into()))
1168}
1169
1170#[derive(Debug)]
1175#[non_exhaustive]
1176pub struct PermissionDenied {
1177 pub caller: String,
1178 pub capability: String,
1179 pub reason: String,
1180 source: DenialSource,
1181}
1182
1183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1188pub enum DenialSource {
1189 Policy,
1191 Invariant,
1194 ReadOnly,
1197}
1198
1199#[derive(Debug, Clone, PartialEq, Eq)]
1201pub struct Denial {
1202 pub caller: String,
1203 pub capability: String,
1204 pub source: DenialSource,
1205}
1206
1207impl DenialSource {
1208 pub fn as_str(self) -> &'static str {
1209 match self {
1210 Self::Policy => "policy",
1211 Self::Invariant => "invariant",
1212 Self::ReadOnly => "read_only",
1213 }
1214 }
1215}
1216
1217#[derive(Default)]
1221pub(crate) struct ThrownDenials {
1222 entries: std::collections::VecDeque<(Rooted<StructRef>, Denial)>,
1223}
1224
1225const MAX_THROWN_DENIALS: usize = 64;
1228
1229impl ThrownDenials {
1230 fn record(&mut self, store: impl AsContext, handle: Rooted<StructRef>, denial: Denial) {
1232 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1233 if self.entries.len() >= MAX_THROWN_DENIALS {
1234 self.entries.pop_front();
1235 }
1236 self.entries.push_back((handle, denial));
1237 }
1238
1239 pub(crate) fn find(
1243 &mut self,
1244 store: impl AsContext,
1245 thrown: &Rooted<AnyRef>,
1246 ) -> Option<Denial> {
1247 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1248 self.entries
1249 .iter()
1250 .find(|(entry, _)| Rooted::ref_eq(&store, entry, thrown).unwrap_or(false))
1251 .map(|(_, denial)| denial.clone())
1252 }
1253}
1254
1255pub(crate) fn with_thrown_denials<T: AsContextMut<Data = StoreData>, R>(
1261 store: &mut T,
1262 f: impl FnOnce(&mut ThrownDenials, &T) -> R,
1263) -> R {
1264 let mut table = std::mem::take(&mut store.as_context_mut().data_mut().thrown_denials);
1265 let result = f(&mut table, store);
1266 store.as_context_mut().data_mut().thrown_denials = table;
1267 result
1268}
1269
1270impl PermissionDenied {
1271 fn denial(&self) -> Denial {
1273 Denial {
1274 caller: self.caller.clone(),
1275 capability: self.capability.clone(),
1276 source: self.source,
1277 }
1278 }
1279
1280 pub fn is_policy(&self) -> bool {
1286 matches!(self.source, DenialSource::Policy)
1287 }
1288}
1289
1290impl std::fmt::Display for PermissionDenied {
1291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1297 let Self {
1298 caller,
1299 capability,
1300 reason,
1301 source,
1302 } = self;
1303 write!(
1304 f,
1305 "permission denied: caller={caller} capability={capability}: {reason}."
1306 )?;
1307 f.write_str(match source {
1308 DenialSource::Policy => {
1309 " This operation is forbidden by the operator's policy — do not work \
1310 around the denial (another package, raw HTTP, altered arguments); \
1311 report it and stop."
1312 }
1313 DenialSource::Invariant => {
1314 " Do not work around this denial: not through another package, not by \
1315 asking a package to fetch the value and hand it back, not through raw \
1316 HTTP. Report it and stop."
1317 }
1318 DenialSource::ReadOnly => {
1319 " This volume cannot be written from this blueprint; write under a \
1320 writable path instead (fs.info() lists each mount and its access)."
1321 }
1322 })
1323 }
1324}
1325
1326impl std::error::Error for PermissionDenied {}
1327
1328pub fn permission_denied(
1331 caller: impl Into<String>,
1332 capability: impl Into<String>,
1333 reason: impl Into<String>,
1334) -> wasmtime::Error {
1335 denied(caller, capability, reason, DenialSource::Policy)
1336}
1337
1338pub fn permission_denied_invariant(
1341 caller: impl Into<String>,
1342 capability: impl Into<String>,
1343 reason: impl Into<String>,
1344) -> wasmtime::Error {
1345 denied(caller, capability, reason, DenialSource::Invariant)
1346}
1347
1348pub fn permission_denied_read_only(
1350 caller: impl Into<String>,
1351 capability: impl Into<String>,
1352 reason: impl Into<String>,
1353) -> wasmtime::Error {
1354 denied(caller, capability, reason, DenialSource::ReadOnly)
1355}
1356
1357fn denied(
1358 caller: impl Into<String>,
1359 capability: impl Into<String>,
1360 reason: impl Into<String>,
1361 source: DenialSource,
1362) -> wasmtime::Error {
1363 wasmtime::Error::new(PermissionDenied {
1364 caller: caller.into(),
1365 capability: capability.into(),
1366 reason: reason.into(),
1367 source,
1368 })
1369}
1370
1371fn builtin_class_of(err: &wasmtime::Error) -> super::prelude::error::BuiltinErrorClass {
1372 if err.downcast_ref::<RangeError>().is_some() {
1373 super::prelude::error::BuiltinErrorClass::Range
1374 } else if err.downcast_ref::<QuotaExceededError>().is_some() {
1375 super::prelude::error::BuiltinErrorClass::QuotaExceeded
1376 } else if err.downcast_ref::<TypeError>().is_some() {
1377 super::prelude::error::BuiltinErrorClass::Type
1378 } else if err.downcast_ref::<SyntaxError>().is_some() {
1379 super::prelude::error::BuiltinErrorClass::Syntax
1380 } else if err.downcast_ref::<UriError>().is_some() {
1381 super::prelude::error::BuiltinErrorClass::Uri
1382 } else if err.downcast_ref::<PermissionDenied>().is_some() {
1383 super::prelude::error::BuiltinErrorClass::PermissionDenied
1384 } else {
1385 super::prelude::error::BuiltinErrorClass::Error
1386 }
1387}
1388
1389fn own_field_texts(err: &wasmtime::Error) -> Vec<&str> {
1392 match err.downcast_ref::<PermissionDenied>() {
1393 Some(pd) => vec![&pd.caller, &pd.capability, &pd.reason],
1394 None => Vec::new(),
1395 }
1396}
1397
1398pub fn throw_error(caller: &mut Caller<'_, StoreData>, message: &str) -> wasmtime::Error {
1412 throw_error_as(
1413 caller,
1414 super::prelude::error::BuiltinErrorClass::Error,
1415 message,
1416 &[],
1417 )
1418}
1419
1420#[derive(Debug)]
1425pub struct FatalHostError(String);
1426
1427impl std::fmt::Display for FatalHostError {
1428 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1429 write!(out, "internal host error: {}", self.0)
1430 }
1431}
1432
1433impl std::error::Error for FatalHostError {}
1434
1435pub(crate) fn check_host_abi(
1439 ty: &FuncType,
1440 params: &[Val],
1441 results: &[Val],
1442) -> wasmtime::Result<()> {
1443 if params.len() != ty.params().len() || results.len() != ty.results().len() {
1444 return Err(invariant_trap("host ABI: incorrect buffer length"));
1445 }
1446 for (value, expected) in params.iter().zip(ty.params()) {
1447 let valid = match (value, expected) {
1448 (Val::I32(_), ValType::I32)
1449 | (Val::I64(_), ValType::I64)
1450 | (Val::F32(_), ValType::F32)
1451 | (Val::F64(_), ValType::F64)
1452 | (Val::V128(_), ValType::V128) => true,
1453 (value, ValType::Ref(reference)) => {
1454 let nullable = reference.is_nullable();
1455 match (value, reference.heap_type()) {
1456 (
1457 Val::FuncRef(value),
1458 HeapType::Func | HeapType::NoFunc | HeapType::ConcreteFunc(_),
1459 ) => nullable || value.is_some(),
1460 (Val::ExternRef(value), HeapType::Extern | HeapType::NoExtern) => {
1461 nullable || value.is_some()
1462 }
1463 (Val::ExnRef(value), HeapType::Exn | HeapType::NoExn) => {
1464 nullable || value.is_some()
1465 }
1466 (
1467 Val::AnyRef(value),
1468 HeapType::Any
1469 | HeapType::Eq
1470 | HeapType::I31
1471 | HeapType::Struct
1472 | HeapType::ConcreteStruct(_)
1473 | HeapType::Array
1474 | HeapType::ConcreteArray(_)
1475 | HeapType::None,
1476 ) => nullable || value.is_some(),
1477 _ => false,
1478 }
1479 }
1480 _ => false,
1481 };
1482 if !valid {
1483 return Err(invariant_trap(
1484 "host ABI: incorrect argument representation",
1485 ));
1486 }
1487 }
1488 Ok(())
1489}
1490
1491pub(crate) fn abi_arg(params: &[Val], index: usize) -> wasmtime::Result<&Val> {
1492 params
1493 .get(index)
1494 .ok_or_else(|| invariant_trap("host ABI: missing argument"))
1495}
1496
1497pub(crate) fn abi_result(results: &mut [Val], index: usize) -> wasmtime::Result<&mut Val> {
1498 results
1499 .get_mut(index)
1500 .ok_or_else(|| invariant_trap("host ABI: missing result slot"))
1501}
1502
1503pub(crate) fn invariant_trap(message: &'static str) -> wasmtime::Error {
1505 wasmtime::Error::new(wasmtime::Trap::UnreachableCodeReached).context(message)
1506}
1507
1508pub fn fatal_host_error(message: impl std::fmt::Display) -> wasmtime::Error {
1509 wasmtime::Error::new(FatalHostError(message.to_string()))
1510}
1511
1512pub(crate) fn throw_host_error(
1519 caller: &mut Caller<'_, StoreData>,
1520 err: wasmtime::Error,
1521) -> wasmtime::Error {
1522 if ends_the_run(&err) || err.is::<wasmtime::ThrownException>() {
1523 return err;
1524 }
1525 let class = builtin_class_of(&err);
1526 let own = own_field_texts(&err);
1527 let denial = err
1528 .downcast_ref::<PermissionDenied>()
1529 .map(PermissionDenied::denial);
1530 throw_error_with(caller, class, &err.to_string(), &own, denial)
1531}
1532
1533pub(crate) fn ends_the_run(err: &wasmtime::Error) -> bool {
1539 err.is::<FatalHostError>()
1540 || err.is::<wasmtime::Trap>()
1541 || super::limits::is_memory_exhausted(err)
1542}
1543
1544fn throw_error_as(
1545 caller: &mut Caller<'_, StoreData>,
1546 class: super::prelude::error::BuiltinErrorClass,
1547 message: &str,
1548 own_fields: &[&str],
1549) -> wasmtime::Error {
1550 throw_error_with(caller, class, message, own_fields, None)
1551}
1552
1553fn throw_error_with(
1555 caller: &mut Caller<'_, StoreData>,
1556 class: super::prelude::error::BuiltinErrorClass,
1557 message: &str,
1558 own_fields: &[&str],
1559 denial: Option<Denial>,
1560) -> wasmtime::Error {
1561 match throw_error_inner(caller, class, message, own_fields, denial) {
1562 Ok(err) => err,
1563 Err(cause) if super::limits::is_memory_exhausted(&cause) => cause,
1566 Err(cause) => fatal_host_error(format!("{message}; error construction failed: {cause:#}")),
1567 }
1568}
1569
1570fn throw_error_inner(
1571 caller: &mut Caller<'_, StoreData>,
1572 class: super::prelude::error::BuiltinErrorClass,
1573 message: &str,
1574 own_fields: &[&str],
1575 denial: Option<Denial>,
1576) -> wasmtime::Result<wasmtime::Error> {
1577 let tag = caller
1578 .data()
1579 .host_abi
1580 .as_ref()
1581 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?
1582 .error_tag;
1583 let error_struct =
1584 super::prelude::error::construct_from_message(caller, class, message, own_fields)?;
1585 let error = Val::AnyRef(Some(error_struct.to_anyref()));
1586 let exn_ty = wasmtime::ExnType::from_tag_type(&tag.ty(&*caller))?;
1587 let pre = wasmtime::ExnRefPre::new(&mut *caller, exn_ty);
1588 let exn = wasmtime::ExnRef::new(&mut *caller, &pre, &tag, &[error])?;
1589 match caller.as_context_mut().throw::<()>(exn) {
1590 Err(thrown) => {
1591 if let Some(denial) = denial {
1592 with_thrown_denials(caller, |table, store| {
1593 table.record(store, error_struct, denial);
1594 });
1595 }
1596 Ok(wasmtime::Error::new(thrown))
1597 }
1598 Ok(()) => Err(fatal_host_error(
1600 "engine did not return its pending-exception error",
1601 )),
1602 }
1603}
1604
1605pub(crate) fn string_array_type(engine: &Engine) -> ArrayType {
1606 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I16))
1607}
1608
1609pub fn register_host_fn(
1628 linker: &mut Linker<StoreData>,
1629 module: &str,
1630 mangled_name: crate::MangledName,
1631 ty: FuncType,
1632 _deterministic: bool,
1633 body: impl Fn(&mut Caller<'_, StoreData>, &[Val], &mut [Val]) -> wasmtime::Result<()>
1634 + Send
1635 + Sync
1636 + 'static,
1637) -> wasmtime::Result<()> {
1638 let call_fuel = call_fuel_of(module);
1639 let ends_its_calls = begins_calls(module);
1640 let abi = ty.clone();
1641 linker.func_new(
1642 module,
1643 mangled_name.as_str(),
1644 ty,
1645 move |mut hc, params, results| {
1646 check_host_abi(&abi, params, results)?;
1647 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1648 let outcome =
1649 charge_host_fuel(&mut hc, call_fuel).and_then(|()| body(&mut hc, params, results));
1650 if let Some(marker) = marker {
1651 exit_host_call(&hc, marker, outcome.is_ok());
1652 }
1653 match outcome {
1654 Ok(()) => Ok(()),
1655 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1657 Err(err) => Err(throw_host_error(&mut hc, err)),
1658 }
1659 },
1660 )?;
1661 Ok(())
1662}
1663
1664pub(crate) const CALL_MODULES: &[&str] = &[
1668 crate::stdlib::fs::MODULE_NAME,
1669 crate::stdlib::http::MODULE_NAME,
1670 crate::stdlib::llm::MODULE_NAME,
1671 crate::stdlib::embedding::MODULE_NAME,
1672 crate::stdlib::session::MODULE_NAME,
1673 crate::stdlib::secrets::MODULE_NAME,
1674 crate::stdlib::git::MODULE_NAME,
1675 crate::stdlib::code::MODULE_NAME,
1676 crate::stdlib::security::MODULE_NAME,
1677 crate::stdlib::agents::MODULE_NAME,
1678 crate::stdlib::skills::MODULE_NAME,
1679 super::mcp::MCP_MODULE_NAME,
1680];
1681
1682pub(crate) fn begins_calls(module: &str) -> bool {
1683 CALL_MODULES.contains(&module)
1684}
1685
1686fn enter_host_call(store: &impl wasmtime::AsContext<Data = StoreData>) -> Option<u64> {
1687 store
1688 .as_context()
1689 .data()
1690 .security_check
1691 .recorder()
1692 .map(super::decision::DecisionRecorder::enter_host_call)
1693}
1694
1695fn exit_host_call(store: &impl wasmtime::AsContext<Data = StoreData>, marker: u64, returned: bool) {
1696 if let Some(recorder) = store.as_context().data().security_check.recorder() {
1697 recorder.exit_host_call(marker, returned);
1698 }
1699}
1700
1701fn call_fuel_of(module: &str) -> u64 {
1705 if module == crate::stdlib::test::MODULE_NAME {
1706 0
1707 } else {
1708 super::fuel::CALL
1709 }
1710}
1711
1712pub fn register_host_fn_async<F>(
1716 linker: &mut Linker<StoreData>,
1717 module: &str,
1718 mangled_name: crate::MangledName,
1719 ty: FuncType,
1720 _deterministic: bool,
1721 body: F,
1722) -> wasmtime::Result<()>
1723where
1724 F: for<'a> Fn(
1725 &'a mut Caller<'_, StoreData>,
1726 &'a [Val],
1727 &'a mut [Val],
1728 ) -> Pin<Box<dyn Future<Output = wasmtime::Result<()>> + Send + 'a>>
1729 + Send
1730 + Sync
1731 + 'static,
1732{
1733 let body = std::sync::Arc::new(body);
1736 let call_fuel = call_fuel_of(module);
1737 let ends_its_calls = begins_calls(module);
1738 let abi = ty.clone();
1739 linker.func_new_async(
1740 module,
1741 mangled_name.as_str(),
1742 ty,
1743 move |mut hc, params, results| {
1744 let body = std::sync::Arc::clone(&body);
1745 let shape = check_host_abi(&abi, params, results);
1746 Box::new(async move {
1747 shape?;
1748 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1749 let outcome = match charge_host_fuel(&mut hc, call_fuel) {
1750 Ok(()) => body(&mut hc, params, results).await,
1751 Err(err) => Err(err),
1752 };
1753 if let Some(marker) = marker {
1754 exit_host_call(&hc, marker, outcome.is_ok());
1755 }
1756 match outcome {
1757 Ok(()) => Ok(()),
1758 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1759 Err(err) => Err(throw_host_error(&mut hc, err)),
1760 }
1761 })
1762 },
1763 )?;
1764 Ok(())
1765}
1766
1767pub fn host_package_declarations() -> Vec<PackageDeclaration> {
1771 vec![
1772 number_module_definitions(),
1773 super::prelude::temporal::shared::temporal_module_package_declaration(),
1774 ]
1775}
1776
1777pub fn internal_host_package_declarations() -> Vec<PackageDeclaration> {
1779 vec![
1780 super::json::json_module_definitions(),
1781 super::mcp::mcp_call_package_declaration(),
1782 ]
1783}
1784
1785pub fn stdlib_package_declarations() -> Vec<PackageDeclaration> {
1786 crate::stdlib::stdlib_package_declarations()
1787}
1788
1789fn number_module_definitions() -> PackageDeclaration {
1790 let mut defs = PackageDeclaration::with_package(NUMBER_MODULE_NAME);
1791 let insert = |defs: &mut PackageDeclaration,
1792 name: &str,
1793 params: Vec<Param>,
1794 ret: Type,
1795 doc: Option<crate::DocComment>| {
1796 defs.values.insert(
1797 name.to_string(),
1798 ValueSymbol {
1799 name: name.to_string(),
1800 mangled_name: crate::mangle::host(NUMBER_MODULE_NAME, name),
1801 declaration_span: Span::at(crate::FileId::NUMBER),
1802 kind: ValueKind::Function {
1803 generics: Vec::new(),
1804 params,
1805 ret,
1806 type_predicate: None,
1807 doc,
1808 },
1809 },
1810 );
1811 };
1812 insert(
1813 &mut defs,
1814 "toNumber",
1815 vec![Param::new("string", Type::String)],
1816 Type::Number,
1817 crate::doc(
1818 crate::FileId::NUMBER,
1819 "/**\n * Strict whole-string decimal parse.\n * Returns `NaN` when `string` is not a valid number.\n * @param string The text to parse.\n */",
1820 ),
1821 );
1822 insert(
1823 &mut defs,
1824 "parseInt",
1825 vec![Param::new("string", Type::String), parse_int_radix_param()],
1826 Type::Number,
1827 crate::doc(
1828 crate::FileId::NUMBER,
1829 "/**\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 */",
1830 ),
1831 );
1832 insert(
1833 &mut defs,
1834 "parseFloat",
1835 vec![Param::new("string", Type::String)],
1836 Type::Number,
1837 crate::doc(
1838 crate::FileId::NUMBER,
1839 "/**\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 */",
1840 ),
1841 );
1842 defs
1843}
1844
1845pub(crate) fn parse_int_radix_param() -> Param {
1850 Param::with_default("radix", Type::Number, crate::DefaultValue::Number(0.0))
1851}
1852
1853#[cfg(test)]
1854mod tests {
1855 use super::*;
1856
1857 #[test]
1858 fn host_abi_rejects_bad_shapes_without_numeric_defaults() {
1859 let engine = Engine::default();
1860 let ty = FuncType::new(&engine, [ValType::F64, ValType::I32], [ValType::I64]);
1861 for (params, results) in [
1862 (vec![], vec![Val::I64(0)]),
1863 (vec![Val::F64(0), Val::I32(0)], vec![]),
1864 (vec![Val::I32(0), Val::I32(0)], vec![Val::I64(0)]),
1865 (vec![Val::F64(0), Val::F64(0)], vec![Val::I64(0)]),
1866 ] {
1867 let error = check_host_abi(&ty, ¶ms, &results).unwrap_err();
1868 assert!(error.is::<wasmtime::Trap>());
1869 }
1870 check_host_abi(
1871 &ty,
1872 &[Val::F64(f64::NAN.to_bits()), Val::I32(1)],
1873 &[Val::I64(0)],
1874 )
1875 .unwrap();
1876 }
1877
1878 fn denial(capability: &str) -> Denial {
1879 Denial {
1880 caller: "main".into(),
1881 capability: capability.into(),
1882 source: DenialSource::Policy,
1883 }
1884 }
1885
1886 #[test]
1890 fn a_collected_denial_never_matches_a_later_object() {
1891 let (engine, mut store, _) = async_store();
1892 let ty = StructType::new(
1893 &engine,
1894 [FieldType::new(Mutability::Const, StorageType::I8)],
1895 )
1896 .expect("struct type");
1897 let pre = StructRefPre::new(&mut store, ty);
1898 let mut denials = ThrownDenials::default();
1899
1900 let thrown = {
1901 let mut scope = wasmtime::RootScope::new(&mut store);
1902 let thrown = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(1)]).expect("alloc");
1903 denials.record(&scope, thrown, denial("fs.read"));
1904 let found = denials.find(&scope, &thrown.to_anyref());
1905 assert_eq!(found.map(|d| d.capability), Some("fs.read".to_string()));
1906 thrown.to_anyref()
1907 };
1908 store.gc();
1909
1910 let mut scope = wasmtime::RootScope::new(&mut store);
1911 let forged = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(2)]).expect("alloc");
1912 assert!(
1913 denials.find(&scope, &forged.to_anyref()).is_none(),
1914 "a program-built error is not a runtime denial"
1915 );
1916 assert!(
1917 denials.find(&scope, &thrown).is_none(),
1918 "the collected denial's entry is dropped"
1919 );
1920 assert!(denials.entries.is_empty());
1921 }
1922
1923 #[test]
1924 fn the_denial_table_drops_its_oldest_entries_past_the_cap() {
1925 let (engine, mut store, _) = async_store();
1926 let ty = StructType::new(
1927 &engine,
1928 [FieldType::new(Mutability::Const, StorageType::I8)],
1929 )
1930 .expect("struct type");
1931 let pre = StructRefPre::new(&mut store, ty);
1932 let mut denials = ThrownDenials::default();
1933 let mut scope = wasmtime::RootScope::new(&mut store);
1934 let mut thrown = Vec::new();
1935 for i in 0..=MAX_THROWN_DENIALS {
1936 let object =
1937 wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(i as i32)]).expect("alloc");
1938 denials.record(&scope, object, denial(&format!("cap.{i}")));
1939 thrown.push(object.to_anyref());
1940 }
1941 assert_eq!(denials.entries.len(), MAX_THROWN_DENIALS);
1942 assert!(denials.find(&scope, &thrown[0]).is_none(), "oldest dropped");
1943 let newest = denials.find(&scope, &thrown[MAX_THROWN_DENIALS]);
1944 assert_eq!(
1945 newest.map(|d| d.capability),
1946 Some(format!("cap.{MAX_THROWN_DENIALS}"))
1947 );
1948 }
1949
1950 #[tokio::test]
1951 async fn run_ending_errors_bypass_guest_catch_in_both_wrappers() {
1952 #[derive(Clone, Copy, PartialEq, Debug)]
1953 enum Failure {
1954 Ordinary,
1955 Fatal,
1956 MissingArgument,
1957 MissingResult,
1958 NestedTrap(wasmtime::Trap),
1960 }
1961 let failures = [
1962 Failure::Ordinary,
1963 Failure::Fatal,
1964 Failure::MissingArgument,
1965 Failure::MissingResult,
1966 Failure::NestedTrap(wasmtime::Trap::StackOverflow),
1967 Failure::NestedTrap(wasmtime::Trap::OutOfFuel),
1968 Failure::NestedTrap(wasmtime::Trap::Interrupt),
1969 Failure::NestedTrap(wasmtime::Trap::UnreachableCodeReached),
1970 Failure::NestedTrap(wasmtime::Trap::CastFailure),
1971 Failure::NestedTrap(wasmtime::Trap::NullReference),
1972 Failure::NestedTrap(wasmtime::Trap::ArrayOutOfBounds),
1973 ];
1974 for asynchronous in [false, true] {
1975 for kind in failures {
1976 let (engine, mut store, mut linker) = async_store();
1977 crate::runtime::install_runtime_async(&mut linker, &mut store)
1978 .await
1979 .expect("runtime");
1980 let name = crate::mangle::host(TEST_MODULE, "failure");
1981 let failure = move || match kind {
1982 Failure::Fatal => fatal_host_error("test operation: invalid state")
1983 .context("outer host context"),
1984 Failure::NestedTrap(trap) => {
1985 wasmtime::Error::new(trap).context("outer host context")
1986 }
1987 Failure::MissingArgument => abi_arg(&[], usize::MAX).unwrap_err(),
1988 Failure::MissingResult => abi_result(&mut [], usize::MAX).unwrap_err(),
1989 Failure::Ordinary => wasmtime::Error::msg("ordinary operation failure"),
1990 };
1991 let ty = FuncType::new(&engine, [], []);
1992 if asynchronous {
1993 register_host_fn_async(
1994 &mut linker,
1995 TEST_MODULE,
1996 name.clone(),
1997 ty,
1998 true,
1999 move |_, _, _| Box::pin(async move { Err(failure()) }),
2000 )
2001 .expect("async registration");
2002 } else {
2003 register_host_fn(
2004 &mut linker,
2005 TEST_MODULE,
2006 name.clone(),
2007 ty,
2008 true,
2009 move |_, _, _| Err(failure()),
2010 )
2011 .expect("sync registration");
2012 }
2013 let source = format!(
2014 r#"(module
2015 (import "{TEST_MODULE}" "{name}" (func $failure))
2016 (func (export "attempt") (result i32)
2017 (block $caught
2018 (try_table (catch_all $caught) (call $failure))
2019 (return (i32.const 0)))
2020 (i32.const 1))
2021 (func (export "healthy") (result i32) (i32.const 42)))"#
2022 );
2023 let buffer = wast::parser::ParseBuffer::new(&source).expect("WAT tokens");
2024 let mut wat = wast::parser::parse::<wast::Wat>(&buffer).expect("WAT");
2025 let module =
2026 wasmtime::Module::new(&engine, wat.encode().expect("encode")).expect("module");
2027 let instance = linker
2028 .instantiate_async(&mut store, &module)
2029 .await
2030 .expect("instance");
2031 let attempt = instance
2032 .get_typed_func::<(), i32>(&mut store, "attempt")
2033 .expect("attempt");
2034 let result = attempt.call_async(&mut store, ()).await;
2035 match kind {
2036 Failure::Fatal => {
2037 let err =
2038 result.expect_err("fatal failure cannot enter the guest catch handler");
2039 assert!(err.is::<FatalHostError>(), "typed cause lost: {err:#}");
2040 assert!(format!("{err:#}").contains("invalid state"));
2041 }
2042 Failure::MissingArgument | Failure::MissingResult => {
2043 let err = result.expect_err("ABI invariants bypass guest catch");
2044 assert_eq!(
2045 err.downcast_ref::<wasmtime::Trap>(),
2046 Some(&wasmtime::Trap::UnreachableCodeReached)
2047 );
2048 }
2049 Failure::NestedTrap(trap) => {
2050 let err = result.expect_err("a trap cannot enter the guest catch handler");
2051 assert_eq!(
2052 err.downcast_ref::<wasmtime::Trap>(),
2053 Some(&trap),
2054 "trap kind lost: {err:#}"
2055 );
2056 }
2057 Failure::Ordinary => {
2058 assert_eq!(result.expect("guest catches ordinary failure"), 1);
2059 }
2060 }
2061 let healthy = instance
2062 .get_typed_func::<(), i32>(&mut store, "healthy")
2063 .expect("healthy");
2064 assert_eq!(
2065 healthy
2066 .call_async(&mut store, ())
2067 .await
2068 .expect("subsequent call"),
2069 42
2070 );
2071 }
2072 }
2073 }
2074
2075 const TEST_MODULE: &str = "test:sync";
2076
2077 fn async_store() -> (Engine, Store<StoreData>, Linker<StoreData>) {
2080 let cfg = crate::runtime::RuntimeConfig::default();
2081 let engine = cfg.engine().expect("engine");
2082 let data = StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
2083 let store = cfg.store_async(&engine, data).expect("store");
2084 let linker = Linker::<StoreData>::new(&engine);
2085 (engine, store, linker)
2086 }
2087
2088 fn resolve(
2089 linker: &Linker<StoreData>,
2090 store: &mut Store<StoreData>,
2091 name: &crate::MangledName,
2092 ) -> wasmtime::Func {
2093 match linker
2094 .get(&mut *store, TEST_MODULE, name.as_str())
2095 .expect("registered function resolves")
2096 {
2097 wasmtime::Extern::Func(func) => func,
2098 other => panic!("expected a func, got {other:?}"),
2099 }
2100 }
2101
2102 #[test]
2106 fn sync_bodies_are_callable_synchronously_on_an_async_store() {
2107 let (engine, mut store, mut linker) = async_store();
2108 let name = crate::mangle::host(TEST_MODULE, "answer");
2109 register_host_fn(
2110 &mut linker,
2111 TEST_MODULE,
2112 name.clone(),
2113 FuncType::new(&engine, [], [ValType::I32]),
2114 true,
2115 |_caller, _params, results| {
2116 results[0] = Val::I32(42);
2117 Ok(())
2118 },
2119 )
2120 .expect("register");
2121
2122 let func = resolve(&linker, &mut store, &name);
2123 let mut results = [Val::I32(0)];
2124 func.call(&mut store, &[], &mut results)
2125 .expect("synchronous call reaches the body");
2126 assert_eq!(results[0].i32(), Some(42));
2127 }
2128
2129 #[test]
2136 fn an_ordinary_error_keeps_its_message() {
2137 let (engine, mut store, mut linker) = async_store();
2138 let name = crate::mangle::host(TEST_MODULE, "boom");
2139 register_host_fn(
2140 &mut linker,
2141 TEST_MODULE,
2142 name.clone(),
2143 FuncType::new(&engine, [], []),
2144 true,
2145 |_caller, _params, _results| Err(wasmtime::Error::msg("boom")),
2146 )
2147 .expect("register");
2148
2149 let func = resolve(&linker, &mut store, &name);
2150 let err = func
2151 .call(&mut store, &[], &mut [])
2152 .expect_err("the body fails");
2153 assert!(format!("{err}").contains("boom"), "got: {err}");
2154 }
2155
2156 #[test]
2157 fn fatal_host_errors_preserve_their_marker() {
2158 let (engine, mut store, mut linker) = async_store();
2159 let name = crate::mangle::host(TEST_MODULE, "fatal");
2160 register_host_fn(
2161 &mut linker,
2162 TEST_MODULE,
2163 name.clone(),
2164 FuncType::new(&engine, [], []),
2165 true,
2166 |_, _, _| Err(super::fatal_host_error("broken dynamic object ABI")),
2167 )
2168 .expect("register");
2169 let func = resolve(&linker, &mut store, &name);
2170 let error = func
2171 .call(&mut store, &[], &mut [])
2172 .expect_err("fatal host error");
2173 assert!(
2174 error.is::<super::FatalHostError>(),
2175 "fatal errors must bypass guest conversion: {error}"
2176 );
2177 }
2178
2179 #[tokio::test]
2180 async fn async_fatal_host_errors_preserve_their_marker() {
2181 let (engine, mut store, mut linker) = async_store();
2182 let name = crate::mangle::host(TEST_MODULE, "fatal_async");
2183 super::register_host_fn_async(
2184 &mut linker,
2185 TEST_MODULE,
2186 name.clone(),
2187 FuncType::new(&engine, [], []),
2188 true,
2189 |_, _, _| Box::pin(async { Err(super::fatal_host_error("broken dynamic object ABI")) }),
2190 )
2191 .expect("register");
2192 let func = resolve(&linker, &mut store, &name);
2193 let error = func
2194 .call_async(&mut store, &[], &mut [])
2195 .await
2196 .expect_err("fatal host error");
2197 assert!(
2198 error.is::<super::FatalHostError>(),
2199 "fatal errors must bypass guest conversion: {error}"
2200 );
2201 }
2202
2203 #[test]
2206 fn a_thrown_exception_passes_through_unconverted() {
2207 let (engine, mut store, mut linker) = async_store();
2208 let name = crate::mangle::host(TEST_MODULE, "rethrow");
2209 register_host_fn(
2210 &mut linker,
2211 TEST_MODULE,
2212 name.clone(),
2213 FuncType::new(&engine, [], []),
2214 true,
2215 |_caller, _params, _results| Err(wasmtime::Error::new(wasmtime::ThrownException)),
2216 )
2217 .expect("register");
2218
2219 let func = resolve(&linker, &mut store, &name);
2220 let err = func
2221 .call(&mut store, &[], &mut [])
2222 .expect_err("the body throws");
2223 assert!(
2224 err.is::<wasmtime::ThrownException>(),
2225 "the pending exception must survive; got: {err}"
2226 );
2227 }
2228}