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