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