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(crate) fn read_string_arg_units(
609 caller: &mut Caller<'_, StoreData>,
610 val: &Val,
611 name: &str,
612) -> wasmtime::Result<Vec<u16>> {
613 let any = match val {
614 Val::AnyRef(Some(any)) => *any,
615 _ => return Err(type_error(format!("{name} expects a string"))),
616 };
617 let string = any.unwrap_struct(&mut *caller)?;
618 let raw = match string.field(&mut *caller, 1)? {
619 Val::AnyRef(Some(raw)) => raw.unwrap_array(&mut *caller)?,
620 _ => {
621 return Err(fatal_host_error(format!(
622 "{name}: malformed string payload"
623 )));
624 }
625 };
626 read_code_units(caller, raw, name)
627}
628
629pub fn read_string_array_arg(
631 caller: &mut Caller<'_, StoreData>,
632 val: &Val,
633 name: &str,
634) -> wasmtime::Result<Vec<String>> {
635 let any = match val {
636 Val::AnyRef(Some(any)) => *any,
637 Val::AnyRef(None) => return Err(type_error(format!("{name} arg is null"))),
638 other => {
639 return Err(type_error(format!(
640 "{name} expects a string[], got {other:?}"
641 )));
642 }
643 };
644 let object = any
645 .as_struct(&mut *caller)?
646 .ok_or_else(|| type_error(format!("{name}: expected $Array struct")))?;
647 let storage = super::array_storage::ArrayStorage::from_struct(caller, object)?;
648 let raw = storage.backing;
649 let len = storage.len;
650 let mut out = Vec::new();
651 out.try_reserve_exact(len as usize)
652 .map_err(fatal_host_error)?;
653 for i in 0..len {
654 match raw.get(&mut *caller, i)? {
655 Val::AnyRef(Some(elem)) => out.push(read_string_from_anyref(caller, elem, name)?),
656 Val::AnyRef(None) => out.push(String::new()),
657 other => {
658 return Err(type_error(format!(
659 "{name}: slot {i} expects a $string, got {other:?}"
660 )));
661 }
662 }
663 }
664 Ok(out)
665}
666
667pub fn write_submilli_string(
670 mut ctx: impl AsContextMut<Data = StoreData>,
671 s: &str,
672) -> wasmtime::Result<Rooted<ArrayRef>> {
673 let units = encode_utf16(&mut ctx, s)?;
674 write_code_units(ctx, &units)
675}
676
677pub(crate) fn encode_utf16(
679 ctx: impl AsContextMut<Data = StoreData>,
680 s: &str,
681) -> wasmtime::Result<Vec<u16>> {
682 fuel::charge(ctx, fuel::SCAN, s.len() as u64)?;
683 let mut units = Vec::new();
686 units.try_reserve_exact(s.len()).map_err(fatal_host_error)?;
687 units.extend(s.encode_utf16());
688 Ok(units)
689}
690
691pub(crate) fn write_code_units(
695 mut ctx: impl AsContextMut<Data = StoreData>,
696 units: &[u16],
697) -> wasmtime::Result<Rooted<ArrayRef>> {
698 fuel::charge(&mut ctx, fuel::COPY, units.len() as u64)?;
699 let array_ty = string_array_type(ctx.as_context().engine());
700 let pre = ArrayRefPre::new(&mut ctx, array_ty);
701 ArrayRef::new_from_i16_slice(&mut ctx, &pre, units)
702}
703
704pub(crate) fn read_code_units(
712 mut ctx: impl AsContextMut<Data = StoreData>,
713 raw: Rooted<ArrayRef>,
714 name: &str,
715) -> wasmtime::Result<Vec<u16>> {
716 let len = raw.len(&mut ctx)?;
717 fuel::charge(&mut ctx, fuel::COPY, u64::from(len))?;
718 let len = usize::try_from(len).map_err(fatal_host_error)?;
719 let mut units = Vec::new();
720 units.try_reserve_exact(len).map_err(fatal_host_error)?;
721 units.resize(len, 0);
722 raw.copy_to_i16_slice(&mut ctx, &mut units)
723 .map_err(|error| wasmtime::Error::msg(format!("{name}: {error}")))?;
724 Ok(units)
725}
726
727pub(crate) fn read_code_units_range(
732 mut ctx: impl AsContextMut<Data = StoreData>,
733 raw: Rooted<ArrayRef>,
734 offset: usize,
735 len: usize,
736) -> wasmtime::Result<Vec<u16>> {
737 fuel::charge(&mut ctx, fuel::COPY, len as u64)?;
738 let offset = u32::try_from(offset).map_err(fatal_host_error)?;
739 let mut units = Vec::new();
740 units.try_reserve_exact(len).map_err(fatal_host_error)?;
741 units.resize(len, 0);
742 raw.read_i16(&mut ctx, offset, &mut units)
743 .map_err(fatal_host_error)?;
744 Ok(units)
745}
746
747pub(crate) fn read_code_unit(
749 mut ctx: impl AsContextMut<Data = StoreData>,
750 raw: Rooted<ArrayRef>,
751 index: usize,
752) -> wasmtime::Result<u16> {
753 let index = u32::try_from(index).map_err(fatal_host_error)?;
754 match raw.get(&mut ctx, index).map_err(fatal_host_error)? {
755 Val::I32(unit) => Ok(unit as u16),
756 other => Err(fatal_host_error(format!("code unit {index} is {other:?}"))),
757 }
758}
759
760pub struct HostAbi {
767 pub(crate) member_functions: std::collections::BTreeMap<String, wasmtime::Func>,
768 pub(crate) string_type: StructType,
769 pub(crate) uint8_type: StructType,
770 pub(crate) array_type: StructType,
771 pub(crate) raw_array_type: ArrayType,
772 pub(crate) boxed_number_type: StructType,
773 pub(crate) boxed_boolean_type: StructType,
774 pub(crate) field_names_type: ArrayType,
775 pub(crate) object_shape_type: StructType,
776 pub(crate) object_fields_type: ArrayType,
777 pub(crate) map_backing_type: StructType,
783 pub(crate) set_backing_type: StructType,
784 pub(crate) string_vtable: Global,
787 pub(crate) array_vtable: Global,
788 pub(crate) object_vtable: Global,
789 pub(crate) boxed_number_vtable: Global,
790 pub(crate) boxed_boolean_vtable: Global,
791 pub(crate) uint8_array_vtable: Global,
792 pub(crate) bigint_vtable: Global,
793 pub(crate) closure_vtable: Global,
794 pub(crate) regex_vtable: Global,
795 pub(crate) regex_match_box_vtable: Global,
796 pub(crate) opaque_vtable: Global,
799 pub(crate) temporal: super::prelude::temporal::TemporalAbi,
800 pub(crate) map_tombstone: Global,
803 pub(crate) collection_null: Global,
804 pub(crate) error_type: StructType,
805 pub(crate) error_subclass_type: StructType,
809 pub(crate) error_vtable: Global,
810 pub(crate) range_error_vtable: Global,
811 pub(crate) quota_exceeded_vtable: Global,
812 pub(crate) type_error_vtable: Global,
813 pub(crate) syntax_error_vtable: Global,
814 pub(crate) uri_error_vtable: Global,
815 pub(crate) reference_error_vtable: Global,
816 pub(crate) permission_denied_vtable: Global,
817 pub(crate) timeout_error_vtable: Global,
818 pub(crate) error_field_names: Global,
819 pub(crate) permission_denied_field_names: Global,
820 pub(crate) error_tag: wasmtime::Tag,
821}
822
823pub(crate) struct HostAbiHandles {
825 pub error_type: StructType,
826 pub error_subclass_type: StructType,
827 pub object_fields_type: ArrayType,
828 pub error_vtable: Global,
829 pub range_error_vtable: Global,
830 pub quota_exceeded_vtable: Global,
831 pub type_error_vtable: Global,
832 pub syntax_error_vtable: Global,
833 pub uri_error_vtable: Global,
834 pub reference_error_vtable: Global,
835 pub permission_denied_vtable: Global,
836 pub timeout_vtable: Global,
837 pub error_field_names: Global,
838 pub permission_denied_field_names: Global,
839}
840
841pub(crate) fn error_abi(caller: &Caller<'_, StoreData>) -> wasmtime::Result<HostAbiHandles> {
842 let abi = caller
843 .data()
844 .host_abi
845 .as_ref()
846 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
847 Ok(HostAbiHandles {
848 error_type: abi.error_type.clone(),
849 error_subclass_type: abi.error_subclass_type.clone(),
850 object_fields_type: abi.object_fields_type.clone(),
851 error_vtable: abi.error_vtable,
852 range_error_vtable: abi.range_error_vtable,
853 quota_exceeded_vtable: abi.quota_exceeded_vtable,
854 type_error_vtable: abi.type_error_vtable,
855 syntax_error_vtable: abi.syntax_error_vtable,
856 uri_error_vtable: abi.uri_error_vtable,
857 reference_error_vtable: abi.reference_error_vtable,
858 permission_denied_vtable: abi.permission_denied_vtable,
859 timeout_vtable: abi.timeout_error_vtable,
860 error_field_names: abi.error_field_names,
861 permission_denied_field_names: abi.permission_denied_field_names,
862 })
863}
864
865fn host_vtable(
868 ctx: &mut impl AsContextMut<Data = StoreData>,
869 select: impl FnOnce(&HostAbi) -> Global,
870) -> wasmtime::Result<Val> {
871 let global =
872 {
873 let abi =
874 ctx.as_context().data().host_abi.as_ref().ok_or_else(|| {
875 wasmtime::Error::msg("host_abi unset (prelude not instantiated)")
876 })?;
877 select(abi)
878 };
879 Ok(global.get(&mut *ctx))
880}
881
882pub(crate) fn host_string_vtable(
883 ctx: &mut impl AsContextMut<Data = StoreData>,
884) -> wasmtime::Result<Val> {
885 host_vtable(ctx, |abi| abi.string_vtable)
886}
887
888pub(crate) fn host_array_vtable(
889 ctx: &mut impl AsContextMut<Data = StoreData>,
890) -> wasmtime::Result<Val> {
891 host_vtable(ctx, |abi| abi.array_vtable)
892}
893
894pub(crate) fn host_object_vtable(
895 ctx: &mut impl AsContextMut<Data = StoreData>,
896) -> wasmtime::Result<Val> {
897 host_vtable(ctx, |abi| abi.object_vtable)
898}
899
900pub(crate) fn host_boxed_number_vtable(
901 ctx: &mut impl AsContextMut<Data = StoreData>,
902) -> wasmtime::Result<Val> {
903 host_vtable(ctx, |abi| abi.boxed_number_vtable)
904}
905
906pub(crate) fn host_boxed_boolean_vtable(
907 ctx: &mut impl AsContextMut<Data = StoreData>,
908) -> wasmtime::Result<Val> {
909 host_vtable(ctx, |abi| abi.boxed_boolean_vtable)
910}
911
912pub(crate) fn host_uint8_array_vtable(
913 ctx: &mut impl AsContextMut<Data = StoreData>,
914) -> wasmtime::Result<Val> {
915 host_vtable(ctx, |abi| abi.uint8_array_vtable)
916}
917
918pub(crate) fn host_bigint_vtable(
919 ctx: &mut impl AsContextMut<Data = StoreData>,
920) -> wasmtime::Result<Val> {
921 host_vtable(ctx, |abi| abi.bigint_vtable)
922}
923
924pub(crate) fn host_closure_vtable(
925 ctx: &mut impl AsContextMut<Data = StoreData>,
926) -> wasmtime::Result<Val> {
927 host_vtable(ctx, |abi| abi.closure_vtable)
928}
929
930pub(crate) fn host_regex_vtable(
931 ctx: &mut impl AsContextMut<Data = StoreData>,
932) -> wasmtime::Result<Val> {
933 host_vtable(ctx, |abi| abi.regex_vtable)
934}
935
936pub(crate) fn host_regex_match_box_vtable(
937 ctx: &mut impl AsContextMut<Data = StoreData>,
938) -> wasmtime::Result<Val> {
939 host_vtable(ctx, |abi| abi.regex_match_box_vtable)
940}
941
942pub(crate) fn host_opaque_vtable(
943 ctx: &mut impl AsContextMut<Data = StoreData>,
944) -> wasmtime::Result<Val> {
945 host_vtable(ctx, |abi| abi.opaque_vtable)
946}
947
948pub(crate) fn host_map_tombstone(
950 ctx: &mut impl AsContextMut<Data = StoreData>,
951) -> wasmtime::Result<Val> {
952 host_vtable(ctx, |abi| abi.map_tombstone)
953}
954pub(crate) fn host_collection_null(
956 ctx: &mut impl AsContextMut<Data = StoreData>,
957) -> wasmtime::Result<Val> {
958 host_vtable(ctx, |abi| abi.collection_null)
959}
960
961pub fn write_submilli_string_struct(
964 caller: &mut Caller<'_, StoreData>,
965 s: &str,
966) -> wasmtime::Result<Rooted<StructRef>> {
967 let units = encode_utf16(&mut *caller, s)?;
968 write_submilli_string_struct_units(caller, &units)
969}
970
971pub fn write_submilli_string_struct_units(
976 caller: &mut Caller<'_, StoreData>,
977 units: &[u16],
978) -> wasmtime::Result<Rooted<StructRef>> {
979 let raw = write_code_units(&mut *caller, units)?;
981 let string_type = {
982 let abi = caller
983 .data()
984 .host_abi
985 .as_ref()
986 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
987 abi.string_type.clone()
988 };
989 let vtable = host_string_vtable(caller)?;
990 let pre = StructRefPre::new(&mut *caller, string_type);
991 StructRef::new(
992 &mut *caller,
993 &pre,
994 &[vtable, Val::AnyRef(Some(raw.to_anyref())), Val::I64(0)],
995 )
996}
997
998pub fn write_submilli_uint8array_struct(
1001 caller: &mut Caller<'_, StoreData>,
1002 bytes: &[u8],
1003) -> wasmtime::Result<Rooted<StructRef>> {
1004 let array_ty = uint8_array_array_type(caller.engine());
1005 let raw = write_uint8_array(&mut *caller, array_ty, bytes)?;
1006 let uint8_type = {
1007 let abi = caller
1008 .data()
1009 .host_abi
1010 .as_ref()
1011 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1012 abi.uint8_type.clone()
1013 };
1014 let vtable = host_uint8_array_vtable(caller)?;
1015 let pre = StructRefPre::new(&mut *caller, uint8_type);
1016 StructRef::new(
1017 &mut *caller,
1018 &pre,
1019 &[vtable, Val::AnyRef(Some(raw.to_anyref()))],
1020 )
1021}
1022
1023pub(crate) fn read_boxed_number(
1025 caller: &mut Caller<'_, StoreData>,
1026 val: &Val,
1027 context: &str,
1028) -> wasmtime::Result<f64> {
1029 let Val::AnyRef(Some(any)) = val else {
1030 return Err(type_error(format!("{context}: expected a boxed number")));
1031 };
1032 let st = any
1033 .as_struct(&mut *caller)?
1034 .ok_or_else(|| type_error(format!("{context}: expected a boxed number")))?;
1035 match st.field(&mut *caller, 1)? {
1036 Val::F64(bits) => Ok(f64::from_bits(bits)),
1037 other => Err(wasmtime::Error::msg(format!(
1038 "{context}: boxed-number payload is {other:?}"
1039 ))),
1040 }
1041}
1042
1043pub(crate) fn write_boxed_number_struct(
1046 caller: &mut Caller<'_, StoreData>,
1047 n: f64,
1048) -> wasmtime::Result<Rooted<StructRef>> {
1049 let boxed_type = {
1050 let abi = caller
1051 .data()
1052 .host_abi
1053 .as_ref()
1054 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1055 abi.boxed_number_type.clone()
1056 };
1057 let vtable = host_boxed_number_vtable(caller)?;
1058 let pre = StructRefPre::new(&mut *caller, boxed_type);
1059 StructRef::new(&mut *caller, &pre, &[vtable, Val::F64(n.to_bits())])
1060}
1061
1062pub fn write_submilli_array_struct(
1066 caller: &mut Caller<'_, StoreData>,
1067 elements: &[Val],
1068) -> wasmtime::Result<Rooted<StructRef>> {
1069 let len = super::array_storage::checked_length(elements.len())?;
1070 fuel::charge(&mut *caller, fuel::ELEM, u64::from(len))?;
1071 write_submilli_array_struct_precharged(caller, elements)
1072}
1073
1074pub(crate) fn write_submilli_array_struct_precharged(
1076 caller: &mut Caller<'_, StoreData>,
1077 elements: &[Val],
1078) -> wasmtime::Result<Rooted<StructRef>> {
1079 let len = super::array_storage::checked_length(elements.len())?;
1080 let (array_type, raw_array_type) = {
1081 let abi = caller
1082 .data()
1083 .host_abi
1084 .as_ref()
1085 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1086 (abi.array_type.clone(), abi.raw_array_type.clone())
1087 };
1088 let raw_pre = ArrayRefPre::new(&mut *caller, raw_array_type);
1089 let raw = ArrayRef::new_fixed(&mut *caller, &raw_pre, elements)?;
1090 let vtable = host_array_vtable(caller)?;
1091 let pre = StructRefPre::new(&mut *caller, array_type);
1092 StructRef::new(
1093 &mut *caller,
1094 &pre,
1095 &[
1096 vtable,
1097 Val::AnyRef(Some(raw.to_anyref())),
1098 Val::I32(len as i32),
1099 ],
1100 )
1101}
1102
1103#[derive(Debug)]
1109pub struct RangeError(pub String);
1110
1111impl std::fmt::Display for RangeError {
1112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1113 f.write_str(&self.0)
1114 }
1115}
1116
1117impl std::error::Error for RangeError {}
1118
1119pub fn range_error(message: impl Into<String>) -> wasmtime::Error {
1121 wasmtime::Error::new(RangeError(message.into()))
1122}
1123
1124#[derive(Debug)]
1130pub struct QuotaExceededError(pub String);
1131
1132impl std::fmt::Display for QuotaExceededError {
1133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1134 f.write_str(&self.0)
1135 }
1136}
1137
1138impl std::error::Error for QuotaExceededError {}
1139
1140pub fn quota_exceeded_error(message: impl Into<String>) -> wasmtime::Error {
1142 wasmtime::Error::new(QuotaExceededError(message.into()))
1143}
1144
1145#[derive(Debug)]
1149pub struct TimeoutError(pub String);
1150
1151impl std::fmt::Display for TimeoutError {
1152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1153 f.write_str(&self.0)
1154 }
1155}
1156
1157impl std::error::Error for TimeoutError {}
1158
1159pub fn timeout_error(message: impl Into<String>) -> wasmtime::Error {
1161 wasmtime::Error::new(TimeoutError(message.into()))
1162}
1163
1164#[derive(Debug)]
1167pub struct SyntaxError(pub String);
1168
1169impl std::fmt::Display for SyntaxError {
1170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1171 f.write_str(&self.0)
1172 }
1173}
1174
1175impl std::error::Error for SyntaxError {}
1176
1177pub fn syntax_error(message: impl Into<String>) -> wasmtime::Error {
1179 wasmtime::Error::new(SyntaxError(message.into()))
1180}
1181
1182#[derive(Debug)]
1185pub struct UriError(pub String);
1186
1187impl std::fmt::Display for UriError {
1188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1189 f.write_str(&self.0)
1190 }
1191}
1192
1193impl std::error::Error for UriError {}
1194
1195pub fn uri_error(message: impl Into<String>) -> wasmtime::Error {
1197 wasmtime::Error::new(UriError(message.into()))
1198}
1199
1200#[derive(Debug)]
1206pub struct TypeError(pub String);
1207
1208impl std::fmt::Display for TypeError {
1209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1210 f.write_str(&self.0)
1211 }
1212}
1213
1214impl std::error::Error for TypeError {}
1215
1216pub fn type_error(message: impl Into<String>) -> wasmtime::Error {
1218 wasmtime::Error::new(TypeError(message.into()))
1219}
1220
1221#[derive(Debug)]
1226#[non_exhaustive]
1227pub struct PermissionDenied {
1228 pub caller: String,
1229 pub capability: String,
1230 pub reason: String,
1231 source: DenialSource,
1232}
1233
1234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1239pub enum DenialSource {
1240 Policy,
1242 Invariant,
1245 ReadOnly,
1248 Harness,
1250}
1251
1252#[derive(Debug, Clone, PartialEq, Eq)]
1254pub struct Denial {
1255 pub caller: String,
1256 pub capability: String,
1257 pub source: DenialSource,
1258}
1259
1260impl DenialSource {
1261 pub fn as_str(self) -> &'static str {
1262 match self {
1263 Self::Policy => "policy",
1264 Self::Invariant => "invariant",
1265 Self::ReadOnly => "read_only",
1266 Self::Harness => "harness",
1267 }
1268 }
1269}
1270
1271#[derive(Default)]
1275pub(crate) struct ThrownDenials {
1276 entries: std::collections::VecDeque<(Rooted<StructRef>, Denial)>,
1277}
1278
1279const MAX_THROWN_DENIALS: usize = 64;
1282
1283impl ThrownDenials {
1284 fn record(&mut self, store: impl AsContext, handle: Rooted<StructRef>, denial: Denial) {
1286 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1287 if self.entries.len() >= MAX_THROWN_DENIALS {
1288 self.entries.pop_front();
1289 }
1290 self.entries.push_back((handle, denial));
1291 }
1292
1293 pub(crate) fn find(
1297 &mut self,
1298 store: impl AsContext,
1299 thrown: &Rooted<AnyRef>,
1300 ) -> Option<Denial> {
1301 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1302 self.entries
1303 .iter()
1304 .find(|(entry, _)| Rooted::ref_eq(&store, entry, thrown).unwrap_or(false))
1305 .map(|(_, denial)| denial.clone())
1306 }
1307}
1308
1309pub(crate) fn with_thrown_denials<T: AsContextMut<Data = StoreData>, R>(
1315 store: &mut T,
1316 f: impl FnOnce(&mut ThrownDenials, &T) -> R,
1317) -> R {
1318 let mut table = std::mem::take(&mut store.as_context_mut().data_mut().thrown_denials);
1319 let result = f(&mut table, store);
1320 store.as_context_mut().data_mut().thrown_denials = table;
1321 result
1322}
1323
1324impl PermissionDenied {
1325 fn denial(&self) -> Denial {
1327 Denial {
1328 caller: self.caller.clone(),
1329 capability: self.capability.clone(),
1330 source: self.source,
1331 }
1332 }
1333
1334 pub fn is_policy(&self) -> bool {
1340 matches!(self.source, DenialSource::Policy)
1341 }
1342}
1343
1344impl std::fmt::Display for PermissionDenied {
1345 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1351 let Self {
1352 caller,
1353 capability,
1354 reason,
1355 source,
1356 } = self;
1357 write!(
1358 f,
1359 "permission denied: caller={caller} capability={capability}: {reason}."
1360 )?;
1361 f.write_str(match source {
1362 DenialSource::Policy => {
1363 " This request is forbidden by the operator's policy — do not work \
1364 around the denial (another package, raw HTTP, or other arguments to \
1365 reach the same thing); report it and stop."
1366 }
1367 DenialSource::Invariant => {
1368 " Do not work around this denial: not through another package, not by \
1369 asking a package to fetch the value and hand it back, not through raw \
1370 HTTP. Report it and stop."
1371 }
1372 DenialSource::ReadOnly => {
1373 " This volume cannot be written from this blueprint; write under a \
1374 writable path instead (fs.info() lists each mount and its access)."
1375 }
1376 DenialSource::Harness => {
1377 " The application refused this call — do not work around the refusal \
1378 (another tool, raw HTTP, or other arguments to reach the same thing); \
1379 report it and stop."
1380 }
1381 })
1382 }
1383}
1384
1385impl std::error::Error for PermissionDenied {}
1386
1387pub fn permission_denied(
1390 caller: impl Into<String>,
1391 capability: impl Into<String>,
1392 reason: impl Into<String>,
1393) -> wasmtime::Error {
1394 denied(caller, capability, reason, DenialSource::Policy)
1395}
1396
1397pub fn permission_denied_invariant(
1400 caller: impl Into<String>,
1401 capability: impl Into<String>,
1402 reason: impl Into<String>,
1403) -> wasmtime::Error {
1404 denied(caller, capability, reason, DenialSource::Invariant)
1405}
1406
1407pub fn permission_denied_read_only(
1409 caller: impl Into<String>,
1410 capability: impl Into<String>,
1411 reason: impl Into<String>,
1412) -> wasmtime::Error {
1413 denied(caller, capability, reason, DenialSource::ReadOnly)
1414}
1415
1416pub fn permission_denied_by_harness(
1418 caller: impl Into<String>,
1419 capability: impl Into<String>,
1420 reason: impl Into<String>,
1421) -> wasmtime::Error {
1422 denied(caller, capability, reason, DenialSource::Harness)
1423}
1424
1425fn denied(
1426 caller: impl Into<String>,
1427 capability: impl Into<String>,
1428 reason: impl Into<String>,
1429 source: DenialSource,
1430) -> wasmtime::Error {
1431 wasmtime::Error::new(PermissionDenied {
1432 caller: caller.into(),
1433 capability: capability.into(),
1434 reason: reason.into(),
1435 source,
1436 })
1437}
1438
1439fn builtin_class_of(err: &wasmtime::Error) -> super::prelude::error::BuiltinErrorClass {
1440 if err.downcast_ref::<RangeError>().is_some() {
1441 super::prelude::error::BuiltinErrorClass::Range
1442 } else if err.downcast_ref::<QuotaExceededError>().is_some() {
1443 super::prelude::error::BuiltinErrorClass::QuotaExceeded
1444 } else if err.downcast_ref::<TimeoutError>().is_some() {
1445 super::prelude::error::BuiltinErrorClass::Timeout
1446 } else if err.downcast_ref::<TypeError>().is_some() {
1447 super::prelude::error::BuiltinErrorClass::Type
1448 } else if err.downcast_ref::<SyntaxError>().is_some() {
1449 super::prelude::error::BuiltinErrorClass::Syntax
1450 } else if err.downcast_ref::<UriError>().is_some() {
1451 super::prelude::error::BuiltinErrorClass::Uri
1452 } else if err.downcast_ref::<PermissionDenied>().is_some() {
1453 super::prelude::error::BuiltinErrorClass::PermissionDenied
1454 } else {
1455 super::prelude::error::BuiltinErrorClass::Error
1456 }
1457}
1458
1459fn own_field_texts(err: &wasmtime::Error) -> Vec<&str> {
1462 match err.downcast_ref::<PermissionDenied>() {
1463 Some(pd) => vec![&pd.caller, &pd.capability, &pd.reason],
1464 None => Vec::new(),
1465 }
1466}
1467
1468pub fn throw_error(caller: &mut Caller<'_, StoreData>, message: &str) -> wasmtime::Error {
1482 throw_error_as(
1483 caller,
1484 super::prelude::error::BuiltinErrorClass::Error,
1485 message,
1486 &[],
1487 )
1488}
1489
1490#[derive(Debug)]
1495pub struct FatalHostError(String);
1496
1497impl std::fmt::Display for FatalHostError {
1498 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1499 write!(out, "internal host error: {}", self.0)
1500 }
1501}
1502
1503impl std::error::Error for FatalHostError {}
1504
1505pub(crate) fn check_host_abi(
1509 ty: &FuncType,
1510 params: &[Val],
1511 results: &[Val],
1512) -> wasmtime::Result<()> {
1513 if params.len() != ty.params().len() || results.len() != ty.results().len() {
1514 return Err(invariant_trap("host ABI: incorrect buffer length"));
1515 }
1516 for (value, expected) in params.iter().zip(ty.params()) {
1517 let valid = match (value, expected) {
1518 (Val::I32(_), ValType::I32)
1519 | (Val::I64(_), ValType::I64)
1520 | (Val::F32(_), ValType::F32)
1521 | (Val::F64(_), ValType::F64)
1522 | (Val::V128(_), ValType::V128) => true,
1523 (value, ValType::Ref(reference)) => {
1524 let nullable = reference.is_nullable();
1525 match (value, reference.heap_type()) {
1526 (
1527 Val::FuncRef(value),
1528 HeapType::Func | HeapType::NoFunc | HeapType::ConcreteFunc(_),
1529 ) => nullable || value.is_some(),
1530 (Val::ExternRef(value), HeapType::Extern | HeapType::NoExtern) => {
1531 nullable || value.is_some()
1532 }
1533 (Val::ExnRef(value), HeapType::Exn | HeapType::NoExn) => {
1534 nullable || value.is_some()
1535 }
1536 (
1537 Val::AnyRef(value),
1538 HeapType::Any
1539 | HeapType::Eq
1540 | HeapType::I31
1541 | HeapType::Struct
1542 | HeapType::ConcreteStruct(_)
1543 | HeapType::Array
1544 | HeapType::ConcreteArray(_)
1545 | HeapType::None,
1546 ) => nullable || value.is_some(),
1547 _ => false,
1548 }
1549 }
1550 _ => false,
1551 };
1552 if !valid {
1553 return Err(invariant_trap(
1554 "host ABI: incorrect argument representation",
1555 ));
1556 }
1557 }
1558 Ok(())
1559}
1560
1561pub(crate) fn abi_arg(params: &[Val], index: usize) -> wasmtime::Result<&Val> {
1562 params
1563 .get(index)
1564 .ok_or_else(|| invariant_trap("host ABI: missing argument"))
1565}
1566
1567pub(crate) fn abi_result(results: &mut [Val], index: usize) -> wasmtime::Result<&mut Val> {
1568 results
1569 .get_mut(index)
1570 .ok_or_else(|| invariant_trap("host ABI: missing result slot"))
1571}
1572
1573pub(crate) fn invariant_trap(message: &'static str) -> wasmtime::Error {
1575 wasmtime::Error::new(wasmtime::Trap::UnreachableCodeReached).context(message)
1576}
1577
1578pub fn fatal_host_error(message: impl std::fmt::Display) -> wasmtime::Error {
1579 wasmtime::Error::new(FatalHostError(message.to_string()))
1580}
1581
1582pub(crate) fn throw_host_error(
1589 caller: &mut Caller<'_, StoreData>,
1590 err: wasmtime::Error,
1591) -> wasmtime::Error {
1592 if ends_the_run(&err) || err.is::<wasmtime::ThrownException>() {
1593 return err;
1594 }
1595 let class = builtin_class_of(&err);
1596 let own = own_field_texts(&err);
1597 let denial = err
1598 .downcast_ref::<PermissionDenied>()
1599 .map(PermissionDenied::denial);
1600 throw_error_with(caller, class, &err.to_string(), &own, denial)
1601}
1602
1603pub(crate) fn ends_the_run(err: &wasmtime::Error) -> bool {
1609 err.is::<FatalHostError>()
1610 || err.is::<wasmtime::Trap>()
1611 || super::limits::is_memory_exhausted(err)
1612}
1613
1614fn throw_error_as(
1615 caller: &mut Caller<'_, StoreData>,
1616 class: super::prelude::error::BuiltinErrorClass,
1617 message: &str,
1618 own_fields: &[&str],
1619) -> wasmtime::Error {
1620 throw_error_with(caller, class, message, own_fields, None)
1621}
1622
1623fn throw_error_with(
1625 caller: &mut Caller<'_, StoreData>,
1626 class: super::prelude::error::BuiltinErrorClass,
1627 message: &str,
1628 own_fields: &[&str],
1629 denial: Option<Denial>,
1630) -> wasmtime::Error {
1631 match throw_error_inner(caller, class, message, own_fields, denial) {
1632 Ok(err) => err,
1633 Err(cause) if super::limits::is_memory_exhausted(&cause) => cause,
1636 Err(cause) => fatal_host_error(format!("{message}; error construction failed: {cause:#}")),
1637 }
1638}
1639
1640fn throw_error_inner(
1641 caller: &mut Caller<'_, StoreData>,
1642 class: super::prelude::error::BuiltinErrorClass,
1643 message: &str,
1644 own_fields: &[&str],
1645 denial: Option<Denial>,
1646) -> wasmtime::Result<wasmtime::Error> {
1647 let tag = caller
1648 .data()
1649 .host_abi
1650 .as_ref()
1651 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?
1652 .error_tag;
1653 let error_struct =
1654 super::prelude::error::construct_from_message(caller, class, message, own_fields)?;
1655 let error = Val::AnyRef(Some(error_struct.to_anyref()));
1656 let exn_ty = wasmtime::ExnType::from_tag_type(&tag.ty(&*caller))?;
1657 let pre = wasmtime::ExnRefPre::new(&mut *caller, exn_ty);
1658 let exn = wasmtime::ExnRef::new(&mut *caller, &pre, &tag, &[error])?;
1659 match caller.as_context_mut().throw::<()>(exn) {
1660 Err(thrown) => {
1661 if let Some(denial) = denial {
1662 with_thrown_denials(caller, |table, store| {
1663 table.record(store, error_struct, denial);
1664 });
1665 }
1666 Ok(wasmtime::Error::new(thrown))
1667 }
1668 Ok(()) => Err(fatal_host_error(
1670 "engine did not return its pending-exception error",
1671 )),
1672 }
1673}
1674
1675pub(crate) fn string_array_type(engine: &Engine) -> ArrayType {
1676 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I16))
1677}
1678
1679pub fn register_host_fn(
1698 linker: &mut Linker<StoreData>,
1699 module: &str,
1700 mangled_name: crate::MangledName,
1701 ty: FuncType,
1702 _deterministic: bool,
1703 body: impl Fn(&mut Caller<'_, StoreData>, &[Val], &mut [Val]) -> wasmtime::Result<()>
1704 + Send
1705 + Sync
1706 + 'static,
1707) -> wasmtime::Result<()> {
1708 let call_fuel = call_fuel_of(module);
1709 let ends_its_calls = begins_calls(module);
1710 let abi = ty.clone();
1711 linker.func_new(
1712 module,
1713 mangled_name.as_str(),
1714 ty,
1715 move |mut hc, params, results| {
1716 check_host_abi(&abi, params, results)?;
1717 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1718 let outcome =
1719 charge_host_fuel(&mut hc, call_fuel).and_then(|()| body(&mut hc, params, results));
1720 if let Some(marker) = marker {
1721 exit_host_call(&hc, marker, outcome.is_ok());
1722 }
1723 match outcome {
1724 Ok(()) => Ok(()),
1725 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1727 Err(err) => Err(throw_host_error(&mut hc, err)),
1728 }
1729 },
1730 )?;
1731 Ok(())
1732}
1733
1734pub(crate) const CALL_MODULES: &[&str] = &[
1738 crate::stdlib::fs::MODULE_NAME,
1739 crate::stdlib::http::MODULE_NAME,
1740 crate::stdlib::llm::MODULE_NAME,
1741 crate::stdlib::embedding::MODULE_NAME,
1742 crate::stdlib::session::MODULE_NAME,
1743 crate::stdlib::secrets::MODULE_NAME,
1744 crate::stdlib::variables::MODULE_NAME,
1745 crate::stdlib::git::MODULE_NAME,
1746 crate::stdlib::code::MODULE_NAME,
1747 crate::stdlib::security::MODULE_NAME,
1748 crate::stdlib::agents::MODULE_NAME,
1749 crate::stdlib::skills::MODULE_NAME,
1750 super::mcp::MCP_MODULE_NAME,
1751 super::tools::TOOLS_MODULE_NAME,
1752];
1753
1754pub(crate) fn begins_calls(module: &str) -> bool {
1755 CALL_MODULES.contains(&module)
1756}
1757
1758fn enter_host_call(store: &impl wasmtime::AsContext<Data = StoreData>) -> Option<u64> {
1759 store
1760 .as_context()
1761 .data()
1762 .security_check
1763 .recorder()
1764 .map(super::decision::DecisionRecorder::enter_host_call)
1765}
1766
1767fn exit_host_call(store: &impl wasmtime::AsContext<Data = StoreData>, marker: u64, returned: bool) {
1768 if let Some(recorder) = store.as_context().data().security_check.recorder() {
1769 recorder.exit_host_call(marker, returned);
1770 }
1771}
1772
1773fn call_fuel_of(module: &str) -> u64 {
1777 if module == crate::stdlib::test::MODULE_NAME {
1778 0
1779 } else {
1780 super::fuel::CALL
1781 }
1782}
1783
1784pub fn register_host_fn_async<F>(
1788 linker: &mut Linker<StoreData>,
1789 module: &str,
1790 mangled_name: crate::MangledName,
1791 ty: FuncType,
1792 _deterministic: bool,
1793 body: F,
1794) -> wasmtime::Result<()>
1795where
1796 F: for<'a> Fn(
1797 &'a mut Caller<'_, StoreData>,
1798 &'a [Val],
1799 &'a mut [Val],
1800 ) -> Pin<Box<dyn Future<Output = wasmtime::Result<()>> + Send + 'a>>
1801 + Send
1802 + Sync
1803 + 'static,
1804{
1805 let body = std::sync::Arc::new(body);
1808 let call_fuel = call_fuel_of(module);
1809 let ends_its_calls = begins_calls(module);
1810 let abi = ty.clone();
1811 linker.func_new_async(
1812 module,
1813 mangled_name.as_str(),
1814 ty,
1815 move |mut hc, params, results| {
1816 let body = std::sync::Arc::clone(&body);
1817 let shape = check_host_abi(&abi, params, results);
1818 Box::new(async move {
1819 shape?;
1820 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1821 let outcome = match charge_host_fuel(&mut hc, call_fuel) {
1822 Ok(()) => body(&mut hc, params, results).await,
1823 Err(err) => Err(err),
1824 };
1825 if let Some(marker) = marker {
1826 exit_host_call(&hc, marker, outcome.is_ok());
1827 }
1828 match outcome {
1829 Ok(()) => Ok(()),
1830 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1831 Err(err) => Err(throw_host_error(&mut hc, err)),
1832 }
1833 })
1834 },
1835 )?;
1836 Ok(())
1837}
1838
1839pub fn host_package_declarations() -> Vec<PackageDeclaration> {
1843 vec![
1844 number_module_definitions(),
1845 super::prelude::temporal::shared::temporal_module_package_declaration(),
1846 ]
1847}
1848
1849pub fn internal_host_package_declarations() -> Vec<PackageDeclaration> {
1851 vec![
1852 super::json::json_module_definitions(),
1853 super::mcp::mcp_call_package_declaration(),
1854 super::tools::tools_call_package_declaration(),
1855 ]
1856}
1857
1858pub fn stdlib_package_declarations() -> Vec<PackageDeclaration> {
1859 crate::stdlib::stdlib_package_declarations()
1860}
1861
1862fn number_module_definitions() -> PackageDeclaration {
1863 let mut defs = PackageDeclaration::with_package(NUMBER_MODULE_NAME);
1864 let insert = |defs: &mut PackageDeclaration,
1865 name: &str,
1866 params: Vec<Param>,
1867 ret: Type,
1868 doc: Option<crate::DocComment>| {
1869 defs.values.insert(
1870 name.to_string(),
1871 ValueSymbol {
1872 name: name.to_string(),
1873 mangled_name: crate::mangle::host(NUMBER_MODULE_NAME, name),
1874 declaration_span: Span::at(crate::FileId::NUMBER),
1875 kind: ValueKind::Function {
1876 generics: Vec::new(),
1877 params,
1878 ret,
1879 type_predicate: None,
1880 doc,
1881 },
1882 },
1883 );
1884 };
1885 insert(
1886 &mut defs,
1887 "toNumber",
1888 vec![Param::new("string", Type::String)],
1889 Type::Number,
1890 crate::doc(
1891 crate::FileId::NUMBER,
1892 "/**\n * Strict whole-string decimal parse.\n * Returns `NaN` when `string` is not a valid number.\n * @param string The text to parse.\n */",
1893 ),
1894 );
1895 insert(
1896 &mut defs,
1897 "parseInt",
1898 vec![Param::new("string", Type::String), parse_int_radix_param()],
1899 Type::Number,
1900 crate::doc(
1901 crate::FileId::NUMBER,
1902 "/**\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 */",
1903 ),
1904 );
1905 insert(
1906 &mut defs,
1907 "parseFloat",
1908 vec![Param::new("string", Type::String)],
1909 Type::Number,
1910 crate::doc(
1911 crate::FileId::NUMBER,
1912 "/**\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 */",
1913 ),
1914 );
1915 defs
1916}
1917
1918pub(crate) fn parse_int_radix_param() -> Param {
1923 Param::with_default("radix", Type::Number, crate::DefaultValue::Number(0.0))
1924}
1925
1926#[cfg(test)]
1927mod tests {
1928 use super::*;
1929
1930 #[test]
1931 fn host_abi_rejects_bad_shapes_without_numeric_defaults() {
1932 let engine = Engine::default();
1933 let ty = FuncType::new(&engine, [ValType::F64, ValType::I32], [ValType::I64]);
1934 for (params, results) in [
1935 (vec![], vec![Val::I64(0)]),
1936 (vec![Val::F64(0), Val::I32(0)], vec![]),
1937 (vec![Val::I32(0), Val::I32(0)], vec![Val::I64(0)]),
1938 (vec![Val::F64(0), Val::F64(0)], vec![Val::I64(0)]),
1939 ] {
1940 let error = check_host_abi(&ty, ¶ms, &results).unwrap_err();
1941 assert!(error.is::<wasmtime::Trap>());
1942 }
1943 check_host_abi(
1944 &ty,
1945 &[Val::F64(f64::NAN.to_bits()), Val::I32(1)],
1946 &[Val::I64(0)],
1947 )
1948 .unwrap();
1949 }
1950
1951 fn denial(capability: &str) -> Denial {
1952 Denial {
1953 caller: "main".into(),
1954 capability: capability.into(),
1955 source: DenialSource::Policy,
1956 }
1957 }
1958
1959 #[test]
1963 fn a_collected_denial_never_matches_a_later_object() {
1964 let (engine, mut store, _) = async_store();
1965 let ty = StructType::new(
1966 &engine,
1967 [FieldType::new(Mutability::Const, StorageType::I8)],
1968 )
1969 .expect("struct type");
1970 let pre = StructRefPre::new(&mut store, ty);
1971 let mut denials = ThrownDenials::default();
1972
1973 let thrown = {
1974 let mut scope = wasmtime::RootScope::new(&mut store);
1975 let thrown = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(1)]).expect("alloc");
1976 denials.record(&scope, thrown, denial("fs.read"));
1977 let found = denials.find(&scope, &thrown.to_anyref());
1978 assert_eq!(found.map(|d| d.capability), Some("fs.read".to_string()));
1979 thrown.to_anyref()
1980 };
1981 store.gc();
1982
1983 let mut scope = wasmtime::RootScope::new(&mut store);
1984 let forged = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(2)]).expect("alloc");
1985 assert!(
1986 denials.find(&scope, &forged.to_anyref()).is_none(),
1987 "a program-built error is not a runtime denial"
1988 );
1989 assert!(
1990 denials.find(&scope, &thrown).is_none(),
1991 "the collected denial's entry is dropped"
1992 );
1993 assert!(denials.entries.is_empty());
1994 }
1995
1996 #[test]
1997 fn the_denial_table_drops_its_oldest_entries_past_the_cap() {
1998 let (engine, mut store, _) = async_store();
1999 let ty = StructType::new(
2000 &engine,
2001 [FieldType::new(Mutability::Const, StorageType::I8)],
2002 )
2003 .expect("struct type");
2004 let pre = StructRefPre::new(&mut store, ty);
2005 let mut denials = ThrownDenials::default();
2006 let mut scope = wasmtime::RootScope::new(&mut store);
2007 let mut thrown = Vec::new();
2008 for i in 0..=MAX_THROWN_DENIALS {
2009 let object =
2010 wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(i as i32)]).expect("alloc");
2011 denials.record(&scope, object, denial(&format!("cap.{i}")));
2012 thrown.push(object.to_anyref());
2013 }
2014 assert_eq!(denials.entries.len(), MAX_THROWN_DENIALS);
2015 assert!(denials.find(&scope, &thrown[0]).is_none(), "oldest dropped");
2016 let newest = denials.find(&scope, &thrown[MAX_THROWN_DENIALS]);
2017 assert_eq!(
2018 newest.map(|d| d.capability),
2019 Some(format!("cap.{MAX_THROWN_DENIALS}"))
2020 );
2021 }
2022
2023 #[tokio::test]
2024 async fn run_ending_errors_bypass_guest_catch_in_both_wrappers() {
2025 #[derive(Clone, Copy, PartialEq, Debug)]
2026 enum Failure {
2027 Ordinary,
2028 Fatal,
2029 MissingArgument,
2030 MissingResult,
2031 NestedTrap(wasmtime::Trap),
2033 }
2034 let failures = [
2035 Failure::Ordinary,
2036 Failure::Fatal,
2037 Failure::MissingArgument,
2038 Failure::MissingResult,
2039 Failure::NestedTrap(wasmtime::Trap::StackOverflow),
2040 Failure::NestedTrap(wasmtime::Trap::OutOfFuel),
2041 Failure::NestedTrap(wasmtime::Trap::Interrupt),
2042 Failure::NestedTrap(wasmtime::Trap::UnreachableCodeReached),
2043 Failure::NestedTrap(wasmtime::Trap::CastFailure),
2044 Failure::NestedTrap(wasmtime::Trap::NullReference),
2045 Failure::NestedTrap(wasmtime::Trap::ArrayOutOfBounds),
2046 ];
2047 for asynchronous in [false, true] {
2048 for kind in failures {
2049 let (engine, mut store, mut linker) = async_store();
2050 crate::runtime::install_runtime_async(&mut linker, &mut store)
2051 .await
2052 .expect("runtime");
2053 let name = crate::mangle::host(TEST_MODULE, "failure");
2054 let failure = move || match kind {
2055 Failure::Fatal => fatal_host_error("test operation: invalid state")
2056 .context("outer host context"),
2057 Failure::NestedTrap(trap) => {
2058 wasmtime::Error::new(trap).context("outer host context")
2059 }
2060 Failure::MissingArgument => abi_arg(&[], usize::MAX).unwrap_err(),
2061 Failure::MissingResult => abi_result(&mut [], usize::MAX).unwrap_err(),
2062 Failure::Ordinary => wasmtime::Error::msg("ordinary operation failure"),
2063 };
2064 let ty = FuncType::new(&engine, [], []);
2065 if asynchronous {
2066 register_host_fn_async(
2067 &mut linker,
2068 TEST_MODULE,
2069 name.clone(),
2070 ty,
2071 true,
2072 move |_, _, _| Box::pin(async move { Err(failure()) }),
2073 )
2074 .expect("async registration");
2075 } else {
2076 register_host_fn(
2077 &mut linker,
2078 TEST_MODULE,
2079 name.clone(),
2080 ty,
2081 true,
2082 move |_, _, _| Err(failure()),
2083 )
2084 .expect("sync registration");
2085 }
2086 let source = format!(
2087 r#"(module
2088 (import "{TEST_MODULE}" "{name}" (func $failure))
2089 (func (export "attempt") (result i32)
2090 (block $caught
2091 (try_table (catch_all $caught) (call $failure))
2092 (return (i32.const 0)))
2093 (i32.const 1))
2094 (func (export "healthy") (result i32) (i32.const 42)))"#
2095 );
2096 let buffer = wast::parser::ParseBuffer::new(&source).expect("WAT tokens");
2097 let mut wat = wast::parser::parse::<wast::Wat>(&buffer).expect("WAT");
2098 let module =
2099 wasmtime::Module::new(&engine, wat.encode().expect("encode")).expect("module");
2100 let instance = linker
2101 .instantiate_async(&mut store, &module)
2102 .await
2103 .expect("instance");
2104 let attempt = instance
2105 .get_typed_func::<(), i32>(&mut store, "attempt")
2106 .expect("attempt");
2107 let result = attempt.call_async(&mut store, ()).await;
2108 match kind {
2109 Failure::Fatal => {
2110 let err =
2111 result.expect_err("fatal failure cannot enter the guest catch handler");
2112 assert!(err.is::<FatalHostError>(), "typed cause lost: {err:#}");
2113 assert!(format!("{err:#}").contains("invalid state"));
2114 }
2115 Failure::MissingArgument | Failure::MissingResult => {
2116 let err = result.expect_err("ABI invariants bypass guest catch");
2117 assert_eq!(
2118 err.downcast_ref::<wasmtime::Trap>(),
2119 Some(&wasmtime::Trap::UnreachableCodeReached)
2120 );
2121 }
2122 Failure::NestedTrap(trap) => {
2123 let err = result.expect_err("a trap cannot enter the guest catch handler");
2124 assert_eq!(
2125 err.downcast_ref::<wasmtime::Trap>(),
2126 Some(&trap),
2127 "trap kind lost: {err:#}"
2128 );
2129 }
2130 Failure::Ordinary => {
2131 assert_eq!(result.expect("guest catches ordinary failure"), 1);
2132 }
2133 }
2134 let healthy = instance
2135 .get_typed_func::<(), i32>(&mut store, "healthy")
2136 .expect("healthy");
2137 assert_eq!(
2138 healthy
2139 .call_async(&mut store, ())
2140 .await
2141 .expect("subsequent call"),
2142 42
2143 );
2144 }
2145 }
2146 }
2147
2148 const TEST_MODULE: &str = "test:sync";
2149
2150 fn async_store() -> (Engine, Store<StoreData>, Linker<StoreData>) {
2153 let cfg = crate::runtime::RuntimeConfig::default();
2154 let engine = cfg.engine().expect("engine");
2155 let data = StoreData::with_vfs(crate::runtime::Vfs::tempdir().expect("tempdir"));
2156 let store = cfg.store_async(&engine, data).expect("store");
2157 let linker = Linker::<StoreData>::new(&engine);
2158 (engine, store, linker)
2159 }
2160
2161 fn resolve(
2162 linker: &Linker<StoreData>,
2163 store: &mut Store<StoreData>,
2164 name: &crate::MangledName,
2165 ) -> wasmtime::Func {
2166 match linker
2167 .get(&mut *store, TEST_MODULE, name.as_str())
2168 .expect("registered function resolves")
2169 {
2170 wasmtime::Extern::Func(func) => func,
2171 other => panic!("expected a func, got {other:?}"),
2172 }
2173 }
2174
2175 #[test]
2179 fn sync_bodies_are_callable_synchronously_on_an_async_store() {
2180 let (engine, mut store, mut linker) = async_store();
2181 let name = crate::mangle::host(TEST_MODULE, "answer");
2182 register_host_fn(
2183 &mut linker,
2184 TEST_MODULE,
2185 name.clone(),
2186 FuncType::new(&engine, [], [ValType::I32]),
2187 true,
2188 |_caller, _params, results| {
2189 results[0] = Val::I32(42);
2190 Ok(())
2191 },
2192 )
2193 .expect("register");
2194
2195 let func = resolve(&linker, &mut store, &name);
2196 let mut results = [Val::I32(0)];
2197 func.call(&mut store, &[], &mut results)
2198 .expect("synchronous call reaches the body");
2199 assert_eq!(results[0].i32(), Some(42));
2200 }
2201
2202 #[test]
2209 fn an_ordinary_error_keeps_its_message() {
2210 let (engine, mut store, mut linker) = async_store();
2211 let name = crate::mangle::host(TEST_MODULE, "boom");
2212 register_host_fn(
2213 &mut linker,
2214 TEST_MODULE,
2215 name.clone(),
2216 FuncType::new(&engine, [], []),
2217 true,
2218 |_caller, _params, _results| Err(wasmtime::Error::msg("boom")),
2219 )
2220 .expect("register");
2221
2222 let func = resolve(&linker, &mut store, &name);
2223 let err = func
2224 .call(&mut store, &[], &mut [])
2225 .expect_err("the body fails");
2226 assert!(format!("{err}").contains("boom"), "got: {err}");
2227 }
2228
2229 #[test]
2230 fn fatal_host_errors_preserve_their_marker() {
2231 let (engine, mut store, mut linker) = async_store();
2232 let name = crate::mangle::host(TEST_MODULE, "fatal");
2233 register_host_fn(
2234 &mut linker,
2235 TEST_MODULE,
2236 name.clone(),
2237 FuncType::new(&engine, [], []),
2238 true,
2239 |_, _, _| Err(super::fatal_host_error("broken dynamic object ABI")),
2240 )
2241 .expect("register");
2242 let func = resolve(&linker, &mut store, &name);
2243 let error = func
2244 .call(&mut store, &[], &mut [])
2245 .expect_err("fatal host error");
2246 assert!(
2247 error.is::<super::FatalHostError>(),
2248 "fatal errors must bypass guest conversion: {error}"
2249 );
2250 }
2251
2252 #[tokio::test]
2253 async fn async_fatal_host_errors_preserve_their_marker() {
2254 let (engine, mut store, mut linker) = async_store();
2255 let name = crate::mangle::host(TEST_MODULE, "fatal_async");
2256 super::register_host_fn_async(
2257 &mut linker,
2258 TEST_MODULE,
2259 name.clone(),
2260 FuncType::new(&engine, [], []),
2261 true,
2262 |_, _, _| Box::pin(async { Err(super::fatal_host_error("broken dynamic object ABI")) }),
2263 )
2264 .expect("register");
2265 let func = resolve(&linker, &mut store, &name);
2266 let error = func
2267 .call_async(&mut store, &[], &mut [])
2268 .await
2269 .expect_err("fatal host error");
2270 assert!(
2271 error.is::<super::FatalHostError>(),
2272 "fatal errors must bypass guest conversion: {error}"
2273 );
2274 }
2275
2276 #[test]
2279 fn a_thrown_exception_passes_through_unconverted() {
2280 let (engine, mut store, mut linker) = async_store();
2281 let name = crate::mangle::host(TEST_MODULE, "rethrow");
2282 register_host_fn(
2283 &mut linker,
2284 TEST_MODULE,
2285 name.clone(),
2286 FuncType::new(&engine, [], []),
2287 true,
2288 |_caller, _params, _results| Err(wasmtime::Error::new(wasmtime::ThrownException)),
2289 )
2290 .expect("register");
2291
2292 let func = resolve(&linker, &mut store, &name);
2293 let err = func
2294 .call(&mut store, &[], &mut [])
2295 .expect_err("the body throws");
2296 assert!(
2297 err.is::<wasmtime::ThrownException>(),
2298 "the pending exception must survive; got: {err}"
2299 );
2300 }
2301}