1use std::future::Future;
4use std::pin::Pin;
5
6use wasmtime::{
7 AnyRef, ArrayRef, ArrayRefPre, ArrayType, AsContext, AsContextMut, Caller, Engine, FieldType,
8 FuncType, Global, HeapType, Linker, Mutability, RefType, Rooted, StorageType, Store, StructRef,
9 StructRefPre, StructType, Val, ValType,
10};
11
12use crate::runtime::fuel::{self, charge_host_fuel};
13use crate::runtime::intrinsic_types::{build_intrinsic_types, intrinsic_types};
14pub(crate) use crate::runtime::intrinsic_types::{
15 intrinsic_array_type, intrinsic_bigint_type, intrinsic_string_type, intrinsic_uint8_array_type,
16};
17use crate::runtime::number::{parse_float_js, parse_int_with_radix, string_to_number_js};
18use crate::runtime::{StoreData, read_submilli_string};
19use crate::stdlib::Stdlib;
20use crate::{PackageDeclaration, Param, Span, Type, ValueKind, ValueSymbol};
21
22pub const NUMBER_MODULE_NAME: &str = "submilli:number";
23
24pub const INTERNAL_MODULE_NAME: &str = "__submilli_internal";
26
27pub async fn install_async(
32 linker: &mut Linker<StoreData>,
33 store: &mut Store<StoreData>,
34) -> wasmtime::Result<()> {
35 install_async_for(linker, store, Stdlib::core()).await
36}
37
38pub async fn install_async_for(
40 linker: &mut Linker<StoreData>,
41 store: &mut Store<StoreData>,
42 stdlib: Stdlib,
43) -> wasmtime::Result<()> {
44 install_host_functions_for(linker, stdlib)?;
45 install_store_bound(linker, store)
46}
47
48pub fn install_host_functions(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
49 install_host_functions_for(linker, Stdlib::core())
50}
51
52pub fn install_host_functions_for(
54 linker: &mut Linker<StoreData>,
55 stdlib: Stdlib,
56) -> wasmtime::Result<()> {
57 install_core_host_functions(linker)?;
58 stdlib.install_host_functions(linker)?;
59 super::mcp::install_mcp_async(linker)?;
60 super::tools::install_tools_async(linker)?;
61 Ok(())
62}
63
64pub fn install_store_bound(
67 linker: &mut Linker<StoreData>,
68 store: &mut Store<StoreData>,
69) -> wasmtime::Result<()> {
70 install_prelude(linker, store)?;
71 crate::stdlib::install_store_bound(linker, store)
72}
73
74fn install_prelude(
78 linker: &mut Linker<StoreData>,
79 store: &mut Store<StoreData>,
80) -> wasmtime::Result<()> {
81 let (vtables, error_host) = super::prelude::install_vtables(linker, store)?;
82 let intr = build_intrinsic_types(store.engine())?;
83 let map_tombstone =
84 super::prelude::map::build_tombstone(store, intr.object.clone(), vtables.object)?;
85 let collection_null =
86 super::prelude::map::build_tombstone(store, intr.object.clone(), vtables.object)?;
87 let temporal = super::prelude::temporal::install_abi(linker, store, &intr)?;
88 let error_subclass_type =
89 super::prelude::error::build_error_subclass_types(store.engine(), &intr)?.1;
90 let map_backing_type = super::prelude::map::map_backing_struct(store.engine(), &intr)?;
91 let set_backing_type = super::prelude::set::set_backing_struct(store.engine(), &intr)?;
92 let member_functions = super::prelude::member::functions(linker, store);
93 store.data_mut().host_abi = Some(HostAbi {
94 member_functions,
95 string_type: intr.string,
96 uint8_type: intr.uint8_array,
97 array_type: intr.array,
98 raw_array_type: intr.raw_array,
99 boxed_number_type: intr.boxed_number,
100 boxed_boolean_type: intr.boxed_boolean,
101 field_names_type: intr.field_names,
102 object_shape_type: intr.object_shape,
103 object_fields_type: intr.object_fields,
104 map_backing_type,
105 set_backing_type,
106 string_vtable: vtables.string,
107 array_vtable: vtables.array,
108 object_vtable: vtables.object,
109 boxed_number_vtable: vtables.boxed_number,
110 boxed_boolean_vtable: vtables.boxed_boolean,
111 uint8_array_vtable: vtables.uint8_array,
112 bigint_vtable: vtables.bigint,
113 closure_vtable: vtables.closure,
114 regex_vtable: vtables.regex,
115 opaque_vtable: vtables.opaque,
116 temporal,
117 map_tombstone,
118 collection_null,
119 error_type: intr.error,
120 error_subclass_type,
121 error_vtable: error_host.vtable,
122 range_error_vtable: error_host.range_vtable,
123 quota_exceeded_vtable: error_host.quota_exceeded_vtable,
124 type_error_vtable: error_host.type_vtable,
125 syntax_error_vtable: error_host.syntax_vtable,
126 uri_error_vtable: error_host.uri_vtable,
127 reference_error_vtable: error_host.reference_vtable,
128 permission_denied_vtable: error_host.permission_denied_vtable,
129 timeout_error_vtable: error_host.timeout_vtable,
130 missing_value_error_vtable: error_host.missing_value_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) opaque_vtable: Global,
789 pub(crate) temporal: super::prelude::temporal::TemporalAbi,
790 pub(crate) map_tombstone: Global,
793 pub(crate) collection_null: Global,
794 pub(crate) error_type: StructType,
795 pub(crate) error_subclass_type: StructType,
799 pub(crate) error_vtable: Global,
800 pub(crate) range_error_vtable: Global,
801 pub(crate) quota_exceeded_vtable: Global,
802 pub(crate) type_error_vtable: Global,
803 pub(crate) syntax_error_vtable: Global,
804 pub(crate) uri_error_vtable: Global,
805 pub(crate) reference_error_vtable: Global,
806 pub(crate) permission_denied_vtable: Global,
807 pub(crate) timeout_error_vtable: Global,
808 pub(crate) missing_value_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 missing_value_vtable: Global,
829 pub error_field_names: Global,
830 pub permission_denied_field_names: Global,
831}
832
833pub(crate) fn error_abi(caller: &Caller<'_, StoreData>) -> wasmtime::Result<HostAbiHandles> {
834 let abi = caller
835 .data()
836 .host_abi
837 .as_ref()
838 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
839 Ok(HostAbiHandles {
840 error_type: abi.error_type.clone(),
841 error_subclass_type: abi.error_subclass_type.clone(),
842 object_fields_type: abi.object_fields_type.clone(),
843 error_vtable: abi.error_vtable,
844 range_error_vtable: abi.range_error_vtable,
845 quota_exceeded_vtable: abi.quota_exceeded_vtable,
846 type_error_vtable: abi.type_error_vtable,
847 syntax_error_vtable: abi.syntax_error_vtable,
848 uri_error_vtable: abi.uri_error_vtable,
849 reference_error_vtable: abi.reference_error_vtable,
850 permission_denied_vtable: abi.permission_denied_vtable,
851 timeout_vtable: abi.timeout_error_vtable,
852 missing_value_vtable: abi.missing_value_error_vtable,
853 error_field_names: abi.error_field_names,
854 permission_denied_field_names: abi.permission_denied_field_names,
855 })
856}
857
858fn host_vtable(
861 ctx: &mut impl AsContextMut<Data = StoreData>,
862 select: impl FnOnce(&HostAbi) -> Global,
863) -> wasmtime::Result<Val> {
864 let global =
865 {
866 let abi =
867 ctx.as_context().data().host_abi.as_ref().ok_or_else(|| {
868 wasmtime::Error::msg("host_abi unset (prelude not instantiated)")
869 })?;
870 select(abi)
871 };
872 Ok(global.get(&mut *ctx))
873}
874
875pub(crate) fn host_string_vtable(
876 ctx: &mut impl AsContextMut<Data = StoreData>,
877) -> wasmtime::Result<Val> {
878 host_vtable(ctx, |abi| abi.string_vtable)
879}
880
881pub(crate) fn host_array_vtable(
882 ctx: &mut impl AsContextMut<Data = StoreData>,
883) -> wasmtime::Result<Val> {
884 host_vtable(ctx, |abi| abi.array_vtable)
885}
886
887pub(crate) fn host_object_vtable(
888 ctx: &mut impl AsContextMut<Data = StoreData>,
889) -> wasmtime::Result<Val> {
890 host_vtable(ctx, |abi| abi.object_vtable)
891}
892
893pub(crate) fn host_boxed_number_vtable(
894 ctx: &mut impl AsContextMut<Data = StoreData>,
895) -> wasmtime::Result<Val> {
896 host_vtable(ctx, |abi| abi.boxed_number_vtable)
897}
898
899pub(crate) fn host_boxed_boolean_vtable(
900 ctx: &mut impl AsContextMut<Data = StoreData>,
901) -> wasmtime::Result<Val> {
902 host_vtable(ctx, |abi| abi.boxed_boolean_vtable)
903}
904
905pub(crate) fn host_uint8_array_vtable(
906 ctx: &mut impl AsContextMut<Data = StoreData>,
907) -> wasmtime::Result<Val> {
908 host_vtable(ctx, |abi| abi.uint8_array_vtable)
909}
910
911pub(crate) fn host_bigint_vtable(
912 ctx: &mut impl AsContextMut<Data = StoreData>,
913) -> wasmtime::Result<Val> {
914 host_vtable(ctx, |abi| abi.bigint_vtable)
915}
916
917pub(crate) fn host_closure_vtable(
918 ctx: &mut impl AsContextMut<Data = StoreData>,
919) -> wasmtime::Result<Val> {
920 host_vtable(ctx, |abi| abi.closure_vtable)
921}
922
923pub(crate) fn host_regex_vtable(
924 ctx: &mut impl AsContextMut<Data = StoreData>,
925) -> wasmtime::Result<Val> {
926 host_vtable(ctx, |abi| abi.regex_vtable)
927}
928
929pub(crate) fn host_opaque_vtable(
930 ctx: &mut impl AsContextMut<Data = StoreData>,
931) -> wasmtime::Result<Val> {
932 host_vtable(ctx, |abi| abi.opaque_vtable)
933}
934
935pub(crate) fn host_map_tombstone(
937 ctx: &mut impl AsContextMut<Data = StoreData>,
938) -> wasmtime::Result<Val> {
939 host_vtable(ctx, |abi| abi.map_tombstone)
940}
941pub(crate) fn host_collection_null(
943 ctx: &mut impl AsContextMut<Data = StoreData>,
944) -> wasmtime::Result<Val> {
945 host_vtable(ctx, |abi| abi.collection_null)
946}
947
948pub fn write_submilli_string_struct(
951 caller: &mut Caller<'_, StoreData>,
952 s: &str,
953) -> wasmtime::Result<Rooted<StructRef>> {
954 let units = encode_utf16(&mut *caller, s)?;
955 write_submilli_string_struct_units(caller, &units)
956}
957
958pub fn write_submilli_string_struct_units(
963 caller: &mut Caller<'_, StoreData>,
964 units: &[u16],
965) -> wasmtime::Result<Rooted<StructRef>> {
966 let raw = write_code_units(&mut *caller, units)?;
968 let string_type = {
969 let abi = caller
970 .data()
971 .host_abi
972 .as_ref()
973 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
974 abi.string_type.clone()
975 };
976 let vtable = host_string_vtable(caller)?;
977 let pre = StructRefPre::new(&mut *caller, string_type);
978 StructRef::new(
979 &mut *caller,
980 &pre,
981 &[vtable, Val::AnyRef(Some(raw.to_anyref())), Val::I64(0)],
982 )
983}
984
985pub fn write_submilli_uint8array_struct(
988 caller: &mut Caller<'_, StoreData>,
989 bytes: &[u8],
990) -> wasmtime::Result<Rooted<StructRef>> {
991 let array_ty = uint8_array_array_type(caller.engine());
992 let raw = write_uint8_array(&mut *caller, array_ty, bytes)?;
993 let uint8_type = {
994 let abi = caller
995 .data()
996 .host_abi
997 .as_ref()
998 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
999 abi.uint8_type.clone()
1000 };
1001 let vtable = host_uint8_array_vtable(caller)?;
1002 let pre = StructRefPre::new(&mut *caller, uint8_type);
1003 StructRef::new(
1004 &mut *caller,
1005 &pre,
1006 &[vtable, Val::AnyRef(Some(raw.to_anyref()))],
1007 )
1008}
1009
1010pub(crate) fn read_boxed_number(
1012 caller: &mut Caller<'_, StoreData>,
1013 val: &Val,
1014 context: &str,
1015) -> wasmtime::Result<f64> {
1016 let Val::AnyRef(Some(any)) = val else {
1017 return Err(type_error(format!("{context}: expected a boxed number")));
1018 };
1019 let st = any
1020 .as_struct(&mut *caller)?
1021 .ok_or_else(|| type_error(format!("{context}: expected a boxed number")))?;
1022 match st.field(&mut *caller, 1)? {
1023 Val::F64(bits) => Ok(f64::from_bits(bits)),
1024 other => Err(wasmtime::Error::msg(format!(
1025 "{context}: boxed-number payload is {other:?}"
1026 ))),
1027 }
1028}
1029
1030pub(crate) fn write_boxed_boolean_struct(
1033 caller: &mut Caller<'_, StoreData>,
1034 value: bool,
1035) -> wasmtime::Result<Rooted<StructRef>> {
1036 let boxed_type = intrinsic_types(&mut *caller)?.boxed_boolean.clone();
1037 let vtable = host_boxed_boolean_vtable(caller)?;
1038 let pre = StructRefPre::new(&mut *caller, boxed_type);
1039 StructRef::new(&mut *caller, &pre, &[vtable, Val::I32(i32::from(value))])
1040}
1041
1042pub(crate) fn write_boxed_number_struct(
1045 caller: &mut Caller<'_, StoreData>,
1046 n: f64,
1047) -> wasmtime::Result<Rooted<StructRef>> {
1048 let boxed_type = {
1049 let abi = caller
1050 .data()
1051 .host_abi
1052 .as_ref()
1053 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1054 abi.boxed_number_type.clone()
1055 };
1056 let vtable = host_boxed_number_vtable(caller)?;
1057 let pre = StructRefPre::new(&mut *caller, boxed_type);
1058 StructRef::new(&mut *caller, &pre, &[vtable, Val::F64(n.to_bits())])
1059}
1060
1061pub fn write_submilli_array_struct(
1065 caller: &mut Caller<'_, StoreData>,
1066 elements: &[Val],
1067) -> wasmtime::Result<Rooted<StructRef>> {
1068 let len = super::array_storage::checked_length(elements.len())?;
1069 fuel::charge(&mut *caller, fuel::ELEM, u64::from(len))?;
1070 write_submilli_array_struct_precharged(caller, elements)
1071}
1072
1073pub(crate) fn write_submilli_array_struct_precharged(
1075 caller: &mut Caller<'_, StoreData>,
1076 elements: &[Val],
1077) -> wasmtime::Result<Rooted<StructRef>> {
1078 let len = super::array_storage::checked_length(elements.len())?;
1079 let (array_type, raw_array_type) = {
1080 let abi = caller
1081 .data()
1082 .host_abi
1083 .as_ref()
1084 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?;
1085 (abi.array_type.clone(), abi.raw_array_type.clone())
1086 };
1087 let raw_pre = ArrayRefPre::new(&mut *caller, raw_array_type);
1088 let raw = ArrayRef::new_fixed(&mut *caller, &raw_pre, elements)?;
1089 let vtable = host_array_vtable(caller)?;
1090 let pre = StructRefPre::new(&mut *caller, array_type);
1091 StructRef::new(
1092 &mut *caller,
1093 &pre,
1094 &[
1095 vtable,
1096 Val::AnyRef(Some(raw.to_anyref())),
1097 Val::I32(len as i32),
1098 ],
1099 )
1100}
1101
1102#[derive(Debug)]
1108pub struct RangeError(pub String);
1109
1110impl std::fmt::Display for RangeError {
1111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1112 f.write_str(&self.0)
1113 }
1114}
1115
1116impl std::error::Error for RangeError {}
1117
1118pub fn range_error(message: impl Into<String>) -> wasmtime::Error {
1120 wasmtime::Error::new(RangeError(message.into()))
1121}
1122
1123#[derive(Debug)]
1129pub struct QuotaExceededError(pub String);
1130
1131impl std::fmt::Display for QuotaExceededError {
1132 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1133 f.write_str(&self.0)
1134 }
1135}
1136
1137impl std::error::Error for QuotaExceededError {}
1138
1139pub fn quota_exceeded_error(message: impl Into<String>) -> wasmtime::Error {
1141 wasmtime::Error::new(QuotaExceededError(message.into()))
1142}
1143
1144#[derive(Debug)]
1148pub struct TimeoutError(pub String);
1149
1150impl std::fmt::Display for TimeoutError {
1151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1152 f.write_str(&self.0)
1153 }
1154}
1155
1156impl std::error::Error for TimeoutError {}
1157
1158pub fn timeout_error(message: impl Into<String>) -> wasmtime::Error {
1160 wasmtime::Error::new(TimeoutError(message.into()))
1161}
1162
1163#[derive(Debug)]
1168pub struct MissingValueError(pub String);
1169
1170impl std::fmt::Display for MissingValueError {
1171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1172 f.write_str(&self.0)
1173 }
1174}
1175
1176impl std::error::Error for MissingValueError {}
1177
1178pub fn missing_value_error(message: impl Into<String>) -> wasmtime::Error {
1180 wasmtime::Error::new(MissingValueError(message.into()))
1181}
1182
1183#[derive(Debug)]
1186pub struct SyntaxError(pub String);
1187
1188impl std::fmt::Display for SyntaxError {
1189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1190 f.write_str(&self.0)
1191 }
1192}
1193
1194impl std::error::Error for SyntaxError {}
1195
1196pub fn syntax_error(message: impl Into<String>) -> wasmtime::Error {
1198 wasmtime::Error::new(SyntaxError(message.into()))
1199}
1200
1201#[derive(Debug)]
1204pub struct UriError(pub String);
1205
1206impl std::fmt::Display for UriError {
1207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1208 f.write_str(&self.0)
1209 }
1210}
1211
1212impl std::error::Error for UriError {}
1213
1214pub fn uri_error(message: impl Into<String>) -> wasmtime::Error {
1216 wasmtime::Error::new(UriError(message.into()))
1217}
1218
1219#[derive(Debug)]
1225pub struct TypeError(pub String);
1226
1227impl std::fmt::Display for TypeError {
1228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1229 f.write_str(&self.0)
1230 }
1231}
1232
1233impl std::error::Error for TypeError {}
1234
1235pub fn type_error(message: impl Into<String>) -> wasmtime::Error {
1237 wasmtime::Error::new(TypeError(message.into()))
1238}
1239
1240#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1245#[non_exhaustive]
1246pub struct PermissionDenied {
1247 pub caller: String,
1248 pub capability: String,
1249 pub reason: String,
1250 source: DenialSource,
1251}
1252
1253#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1258pub enum DenialSource {
1259 Policy,
1261 Invariant,
1264 ReadOnly,
1267 Harness,
1269 Human,
1272 Admin,
1275}
1276
1277#[derive(Debug, Clone, PartialEq, Eq)]
1279pub struct Denial {
1280 pub caller: String,
1281 pub capability: String,
1282 pub source: DenialSource,
1283}
1284
1285impl DenialSource {
1286 pub fn as_str(self) -> &'static str {
1287 match self {
1288 Self::Policy => "policy",
1289 Self::Invariant => "invariant",
1290 Self::ReadOnly => "read_only",
1291 Self::Harness => "harness",
1292 Self::Human => "human",
1293 Self::Admin => "admin",
1294 }
1295 }
1296}
1297
1298#[derive(Default)]
1302pub(crate) struct ThrownDenials {
1303 entries: std::collections::VecDeque<(Rooted<StructRef>, Denial)>,
1304}
1305
1306const MAX_THROWN_DENIALS: usize = 64;
1309
1310impl ThrownDenials {
1311 fn record(&mut self, store: impl AsContext, handle: Rooted<StructRef>, denial: Denial) {
1313 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1314 if self.entries.len() >= MAX_THROWN_DENIALS {
1315 self.entries.pop_front();
1316 }
1317 self.entries.push_back((handle, denial));
1318 }
1319
1320 pub(crate) fn find(
1324 &mut self,
1325 store: impl AsContext,
1326 thrown: &Rooted<AnyRef>,
1327 ) -> Option<Denial> {
1328 self.entries.retain(|(entry, _)| entry.ty(&store).is_ok());
1329 self.entries
1330 .iter()
1331 .find(|(entry, _)| Rooted::ref_eq(&store, entry, thrown).unwrap_or(false))
1332 .map(|(_, denial)| denial.clone())
1333 }
1334}
1335
1336pub(crate) fn with_thrown_denials<T: AsContextMut<Data = StoreData>, R>(
1342 store: &mut T,
1343 f: impl FnOnce(&mut ThrownDenials, &T) -> R,
1344) -> R {
1345 let mut table = std::mem::take(&mut store.as_context_mut().data_mut().thrown_denials);
1346 let result = f(&mut table, store);
1347 store.as_context_mut().data_mut().thrown_denials = table;
1348 result
1349}
1350
1351impl PermissionDenied {
1352 fn denial(&self) -> Denial {
1354 Denial {
1355 caller: self.caller.clone(),
1356 capability: self.capability.clone(),
1357 source: self.source,
1358 }
1359 }
1360
1361 pub fn is_policy(&self) -> bool {
1367 matches!(self.source, DenialSource::Policy)
1368 }
1369}
1370
1371impl std::fmt::Display for PermissionDenied {
1372 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1378 let Self {
1379 caller,
1380 capability,
1381 reason,
1382 source,
1383 } = self;
1384 write!(
1385 f,
1386 "permission denied: caller={caller} capability={capability}: {reason}."
1387 )?;
1388 f.write_str(match source {
1389 DenialSource::Policy => {
1390 " This request is forbidden by the operator's policy — do not work \
1391 around the denial (another package, raw HTTP, or other arguments to \
1392 reach the same thing); report it and stop."
1393 }
1394 DenialSource::Invariant => {
1395 " Do not work around this denial: not through another package, not by \
1396 asking a package to fetch the value and hand it back, not through raw \
1397 HTTP. Report it and stop."
1398 }
1399 DenialSource::ReadOnly => {
1400 " This volume cannot be written from this blueprint; write under a \
1401 writable path instead (fs.info() lists each mount and its access)."
1402 }
1403 DenialSource::Harness => {
1404 " The application refused this call — do not work around the refusal \
1405 (another tool, raw HTTP, or other arguments to reach the same thing); \
1406 report it and stop."
1407 }
1408 DenialSource::Human => {
1409 " This call needed the person's approval and did not get it — do not \
1410 retry it or work around it (another package, raw HTTP, or other \
1411 arguments to reach the same thing); report it and stop."
1412 }
1413 DenialSource::Admin => {
1414 " This call needed an administrator's approval and did not get it — do \
1415 not retry it or work around it (another package, raw HTTP, or other \
1416 arguments to reach the same thing); report it and stop."
1417 }
1418 })
1419 }
1420}
1421
1422impl std::error::Error for PermissionDenied {}
1423
1424pub fn permission_denied(
1427 caller: impl Into<String>,
1428 capability: impl Into<String>,
1429 reason: impl Into<String>,
1430) -> wasmtime::Error {
1431 denied(caller, capability, reason, DenialSource::Policy)
1432}
1433
1434pub fn permission_denied_invariant(
1437 caller: impl Into<String>,
1438 capability: impl Into<String>,
1439 reason: impl Into<String>,
1440) -> wasmtime::Error {
1441 denied(caller, capability, reason, DenialSource::Invariant)
1442}
1443
1444pub fn permission_denied_read_only(
1446 caller: impl Into<String>,
1447 capability: impl Into<String>,
1448 reason: impl Into<String>,
1449) -> wasmtime::Error {
1450 denied(caller, capability, reason, DenialSource::ReadOnly)
1451}
1452
1453pub fn permission_denied_by_harness(
1455 caller: impl Into<String>,
1456 capability: impl Into<String>,
1457 reason: impl Into<String>,
1458) -> wasmtime::Error {
1459 denied(caller, capability, reason, DenialSource::Harness)
1460}
1461
1462pub fn permission_denied_by(
1464 source: DenialSource,
1465 caller: impl Into<String>,
1466 capability: impl Into<String>,
1467 reason: impl Into<String>,
1468) -> wasmtime::Error {
1469 denied(caller, capability, reason, source)
1470}
1471
1472fn denied(
1473 caller: impl Into<String>,
1474 capability: impl Into<String>,
1475 reason: impl Into<String>,
1476 source: DenialSource,
1477) -> wasmtime::Error {
1478 wasmtime::Error::new(PermissionDenied {
1479 caller: caller.into(),
1480 capability: capability.into(),
1481 reason: reason.into(),
1482 source,
1483 })
1484}
1485
1486fn builtin_class_of(err: &wasmtime::Error) -> super::prelude::error::BuiltinErrorClass {
1487 if err.downcast_ref::<RangeError>().is_some() {
1488 super::prelude::error::BuiltinErrorClass::Range
1489 } else if err.downcast_ref::<QuotaExceededError>().is_some() {
1490 super::prelude::error::BuiltinErrorClass::QuotaExceeded
1491 } else if err.downcast_ref::<TimeoutError>().is_some() {
1492 super::prelude::error::BuiltinErrorClass::Timeout
1493 } else if err.downcast_ref::<MissingValueError>().is_some() {
1494 super::prelude::error::BuiltinErrorClass::MissingValue
1495 } else if err.downcast_ref::<TypeError>().is_some() {
1496 super::prelude::error::BuiltinErrorClass::Type
1497 } else if err.downcast_ref::<SyntaxError>().is_some() {
1498 super::prelude::error::BuiltinErrorClass::Syntax
1499 } else if err.downcast_ref::<UriError>().is_some() {
1500 super::prelude::error::BuiltinErrorClass::Uri
1501 } else if err.downcast_ref::<PermissionDenied>().is_some() {
1502 super::prelude::error::BuiltinErrorClass::PermissionDenied
1503 } else {
1504 super::prelude::error::BuiltinErrorClass::Error
1505 }
1506}
1507
1508fn own_field_texts(err: &wasmtime::Error) -> Vec<&str> {
1511 match err.downcast_ref::<PermissionDenied>() {
1512 Some(pd) => vec![&pd.caller, &pd.capability, &pd.reason],
1513 None => Vec::new(),
1514 }
1515}
1516
1517pub fn throw_error(caller: &mut Caller<'_, StoreData>, message: &str) -> wasmtime::Error {
1531 throw_error_as(
1532 caller,
1533 super::prelude::error::BuiltinErrorClass::Error,
1534 message,
1535 &[],
1536 )
1537}
1538
1539#[derive(Debug)]
1544pub struct FatalHostError(String);
1545
1546impl std::fmt::Display for FatalHostError {
1547 fn fmt(&self, out: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1548 write!(out, "internal host error: {}", self.0)
1549 }
1550}
1551
1552impl std::error::Error for FatalHostError {}
1553
1554pub(crate) fn check_host_abi(
1558 ty: &FuncType,
1559 params: &[Val],
1560 results: &[Val],
1561) -> wasmtime::Result<()> {
1562 if params.len() != ty.params().len() || results.len() != ty.results().len() {
1563 return Err(invariant_trap("host ABI: incorrect buffer length"));
1564 }
1565 for (value, expected) in params.iter().zip(ty.params()) {
1566 let valid = match (value, expected) {
1567 (Val::I32(_), ValType::I32)
1568 | (Val::I64(_), ValType::I64)
1569 | (Val::F32(_), ValType::F32)
1570 | (Val::F64(_), ValType::F64)
1571 | (Val::V128(_), ValType::V128) => true,
1572 (value, ValType::Ref(reference)) => {
1573 let nullable = reference.is_nullable();
1574 match (value, reference.heap_type()) {
1575 (
1576 Val::FuncRef(value),
1577 HeapType::Func | HeapType::NoFunc | HeapType::ConcreteFunc(_),
1578 ) => nullable || value.is_some(),
1579 (Val::ExternRef(value), HeapType::Extern | HeapType::NoExtern) => {
1580 nullable || value.is_some()
1581 }
1582 (Val::ExnRef(value), HeapType::Exn | HeapType::NoExn) => {
1583 nullable || value.is_some()
1584 }
1585 (
1586 Val::AnyRef(value),
1587 HeapType::Any
1588 | HeapType::Eq
1589 | HeapType::I31
1590 | HeapType::Struct
1591 | HeapType::ConcreteStruct(_)
1592 | HeapType::Array
1593 | HeapType::ConcreteArray(_)
1594 | HeapType::None,
1595 ) => nullable || value.is_some(),
1596 _ => false,
1597 }
1598 }
1599 _ => false,
1600 };
1601 if !valid {
1602 return Err(invariant_trap(
1603 "host ABI: incorrect argument representation",
1604 ));
1605 }
1606 }
1607 Ok(())
1608}
1609
1610pub(crate) fn abi_arg(params: &[Val], index: usize) -> wasmtime::Result<&Val> {
1611 params
1612 .get(index)
1613 .ok_or_else(|| invariant_trap("host ABI: missing argument"))
1614}
1615
1616pub(crate) fn abi_result(results: &mut [Val], index: usize) -> wasmtime::Result<&mut Val> {
1617 results
1618 .get_mut(index)
1619 .ok_or_else(|| invariant_trap("host ABI: missing result slot"))
1620}
1621
1622pub(crate) fn invariant_trap(message: &'static str) -> wasmtime::Error {
1624 wasmtime::Error::new(wasmtime::Trap::UnreachableCodeReached).context(message)
1625}
1626
1627pub fn fatal_host_error(message: impl std::fmt::Display) -> wasmtime::Error {
1628 wasmtime::Error::new(FatalHostError(message.to_string()))
1629}
1630
1631pub(crate) fn throw_host_error(
1638 caller: &mut Caller<'_, StoreData>,
1639 err: wasmtime::Error,
1640) -> wasmtime::Error {
1641 if ends_the_run(&err) || err.is::<wasmtime::ThrownException>() {
1642 return err;
1643 }
1644 let class = builtin_class_of(&err);
1645 let own = own_field_texts(&err);
1646 let denial = err
1647 .downcast_ref::<PermissionDenied>()
1648 .map(PermissionDenied::denial);
1649 throw_error_with(caller, class, &err.to_string(), &own, denial)
1650}
1651
1652pub(crate) struct ThrownErrorParts {
1654 pub error: Rooted<StructRef>,
1655 pub name: String,
1656 pub message: String,
1657}
1658
1659pub(crate) fn thrown_error_parts(
1663 mut ctx: impl AsContextMut<Data = StoreData>,
1664 exn: Rooted<wasmtime::ExnRef>,
1665) -> Option<ThrownErrorParts> {
1666 let Val::AnyRef(Some(error_ref)) = exn.field(&mut ctx, 0).ok()? else {
1668 return None;
1669 };
1670 let error = error_ref.unwrap_struct(&mut ctx).ok()?;
1671 let Val::AnyRef(Some(payload_ref)) = error.field(&mut ctx, 2).ok()? else {
1673 return None;
1674 };
1675 let payload = payload_ref.unwrap_array(&mut ctx).ok()?;
1676 let message = payload
1677 .get(&mut ctx, super::prelude::error::MESSAGE_SLOT)
1678 .ok()?;
1679 let name = payload
1680 .get(&mut ctx, super::prelude::error::NAME_SLOT)
1681 .ok()?;
1682 let name = read_string_value(&mut ctx, name)?;
1683 let message = read_string_value(&mut ctx, message)?;
1684 Some(ThrownErrorParts {
1685 error,
1686 name,
1687 message,
1688 })
1689}
1690
1691pub(crate) fn read_string_value(
1693 mut ctx: impl AsContextMut<Data = StoreData>,
1694 value: Val,
1695) -> Option<String> {
1696 let Val::AnyRef(Some(s_ref)) = value else {
1697 return None;
1698 };
1699 let s_struct = s_ref.unwrap_struct(&mut ctx).ok()?;
1700 let Val::AnyRef(Some(raw_ref)) = s_struct.field(&mut ctx, 1).ok()? else {
1702 return None;
1703 };
1704 let arr = raw_ref.unwrap_array(&mut ctx).ok()?;
1705 read_submilli_string(&mut ctx, arr).ok()
1706}
1707
1708pub(crate) fn ends_the_run(err: &wasmtime::Error) -> bool {
1714 err.is::<FatalHostError>()
1715 || err.is::<wasmtime::Trap>()
1716 || super::limits::is_memory_exhausted(err)
1717}
1718
1719fn throw_error_as(
1720 caller: &mut Caller<'_, StoreData>,
1721 class: super::prelude::error::BuiltinErrorClass,
1722 message: &str,
1723 own_fields: &[&str],
1724) -> wasmtime::Error {
1725 throw_error_with(caller, class, message, own_fields, None)
1726}
1727
1728fn throw_error_with(
1730 caller: &mut Caller<'_, StoreData>,
1731 class: super::prelude::error::BuiltinErrorClass,
1732 message: &str,
1733 own_fields: &[&str],
1734 denial: Option<Denial>,
1735) -> wasmtime::Error {
1736 match throw_error_inner(caller, class, message, own_fields, denial) {
1737 Ok(err) => err,
1738 Err(cause) if ends_the_run(&cause) => cause,
1741 Err(cause) => fatal_host_error(format!("{message}; error construction failed: {cause:#}")),
1742 }
1743}
1744
1745fn throw_error_inner(
1746 caller: &mut Caller<'_, StoreData>,
1747 class: super::prelude::error::BuiltinErrorClass,
1748 message: &str,
1749 own_fields: &[&str],
1750 denial: Option<Denial>,
1751) -> wasmtime::Result<wasmtime::Error> {
1752 let tag = caller
1753 .data()
1754 .host_abi
1755 .as_ref()
1756 .ok_or_else(|| wasmtime::Error::msg("host_abi unset (prelude not instantiated)"))?
1757 .error_tag;
1758 let error_struct =
1759 super::prelude::error::construct_from_message(caller, class, message, own_fields)?;
1760 let error = Val::AnyRef(Some(error_struct.to_anyref()));
1761 let exn_ty = wasmtime::ExnType::from_tag_type(&tag.ty(&*caller))?;
1762 let pre = wasmtime::ExnRefPre::new(&mut *caller, exn_ty);
1763 let exn = wasmtime::ExnRef::new(&mut *caller, &pre, &tag, &[error])?;
1764 match caller.as_context_mut().throw::<()>(exn) {
1765 Err(thrown) => {
1766 if let Some(denial) = denial {
1767 with_thrown_denials(caller, |table, store| {
1768 table.record(store, error_struct, denial);
1769 });
1770 }
1771 Ok(wasmtime::Error::new(thrown))
1772 }
1773 Ok(()) => Err(fatal_host_error(
1775 "engine did not return its pending-exception error",
1776 )),
1777 }
1778}
1779
1780pub(crate) fn string_array_type(engine: &Engine) -> ArrayType {
1781 ArrayType::new(engine, FieldType::new(Mutability::Var, StorageType::I16))
1782}
1783
1784pub fn register_host_fn(
1801 linker: &mut Linker<StoreData>,
1802 module: &str,
1803 mangled_name: crate::MangledName,
1804 ty: FuncType,
1805 body: impl Fn(&mut Caller<'_, StoreData>, &[Val], &mut [Val]) -> wasmtime::Result<()>
1806 + Send
1807 + Sync
1808 + 'static,
1809) -> wasmtime::Result<()> {
1810 let call_fuel = call_fuel_of(module);
1811 let ends_its_calls = begins_calls(module);
1812 let abi = ty.clone();
1813 linker.func_new(
1814 module,
1815 mangled_name.as_str(),
1816 ty,
1817 move |mut hc, params, results| {
1818 check_host_abi(&abi, params, results)?;
1819 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1820 let outcome =
1821 charge_host_fuel(&mut hc, call_fuel).and_then(|()| body(&mut hc, params, results));
1822 if let Some(marker) = marker {
1823 exit_host_call(&hc, marker, outcome.is_ok());
1824 }
1825 match outcome {
1826 Ok(()) => Ok(()),
1827 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1829 Err(err) => Err(throw_host_error(&mut hc, err)),
1830 }
1831 },
1832 )?;
1833 Ok(())
1834}
1835
1836pub(crate) const CALL_MODULES: &[&str] = &[
1840 crate::stdlib::fs::MODULE_NAME,
1841 crate::stdlib::http::MODULE_NAME,
1842 crate::stdlib::llm::MODULE_NAME,
1843 crate::stdlib::embedding::MODULE_NAME,
1844 crate::stdlib::session::MODULE_NAME,
1845 crate::stdlib::secrets::MODULE_NAME,
1846 crate::stdlib::variables::MODULE_NAME,
1847 crate::stdlib::git::MODULE_NAME,
1848 crate::stdlib::code::MODULE_NAME,
1849 crate::stdlib::security::MODULE_NAME,
1850 crate::stdlib::agents::MODULE_NAME,
1851 crate::stdlib::skills::MODULE_NAME,
1852 super::mcp::MCP_MODULE_NAME,
1853 super::tools::TOOLS_MODULE_NAME,
1854];
1855
1856pub(crate) fn begins_calls(module: &str) -> bool {
1857 CALL_MODULES.contains(&module)
1858}
1859
1860fn enter_host_call(store: &impl wasmtime::AsContext<Data = StoreData>) -> Option<u64> {
1861 store
1862 .as_context()
1863 .data()
1864 .security_check
1865 .recorder()
1866 .map(super::decision::DecisionRecorder::enter_host_call)
1867}
1868
1869fn exit_host_call(store: &impl wasmtime::AsContext<Data = StoreData>, marker: u64, returned: bool) {
1870 if let Some(recorder) = store.as_context().data().security_check.recorder() {
1871 recorder.exit_host_call(marker, returned);
1872 }
1873}
1874
1875fn call_fuel_of(module: &str) -> u64 {
1879 if module == crate::stdlib::test::MODULE_NAME {
1880 0
1881 } else {
1882 super::fuel::CALL
1883 }
1884}
1885
1886pub fn register_host_fn_async<F>(
1890 linker: &mut Linker<StoreData>,
1891 module: &str,
1892 mangled_name: crate::MangledName,
1893 ty: FuncType,
1894 body: F,
1895) -> wasmtime::Result<()>
1896where
1897 F: for<'a> Fn(
1898 &'a mut Caller<'_, StoreData>,
1899 &'a [Val],
1900 &'a mut [Val],
1901 ) -> Pin<Box<dyn Future<Output = wasmtime::Result<()>> + Send + 'a>>
1902 + Send
1903 + Sync
1904 + 'static,
1905{
1906 let body = std::sync::Arc::new(body);
1909 let call_fuel = call_fuel_of(module);
1910 let ends_its_calls = begins_calls(module);
1911 let abi = ty.clone();
1912 linker.func_new_async(
1913 module,
1914 mangled_name.as_str(),
1915 ty,
1916 move |mut hc, params, results| {
1917 let body = std::sync::Arc::clone(&body);
1918 let shape = check_host_abi(&abi, params, results);
1919 Box::new(async move {
1920 shape?;
1921 let marker = ends_its_calls.then(|| enter_host_call(&hc)).flatten();
1922 let outcome = match charge_host_fuel(&mut hc, call_fuel) {
1923 Ok(()) => body(&mut hc, params, results).await,
1924 Err(err) => Err(err),
1925 };
1926 if let Some(marker) = marker {
1927 exit_host_call(&hc, marker, outcome.is_ok());
1928 }
1929 match outcome {
1930 Ok(()) => Ok(()),
1931 Err(err) if err.is::<wasmtime::ThrownException>() => Err(err),
1932 Err(err) => Err(throw_host_error(&mut hc, err)),
1933 }
1934 })
1935 },
1936 )?;
1937 Ok(())
1938}
1939
1940pub fn register_host_value_async<F>(
1944 linker: &mut Linker<StoreData>,
1945 module: &str,
1946 mangled_name: crate::MangledName,
1947 ty: FuncType,
1948 body: F,
1949) -> wasmtime::Result<()>
1950where
1951 F: for<'a> Fn(
1952 &'a mut Caller<'_, StoreData>,
1953 &'a [Val],
1954 ) -> Pin<Box<dyn Future<Output = wasmtime::Result<Val>> + Send + 'a>>
1955 + Send
1956 + Sync
1957 + 'static,
1958{
1959 if ty.results().len() != 1 {
1960 return Err(fatal_host_error(
1961 "value-returning host function requires one ABI result",
1962 ));
1963 }
1964 register_host_fn_async(
1965 linker,
1966 module,
1967 mangled_name,
1968 ty,
1969 move |caller, params, results| {
1970 let value = body(caller, params);
1971 Box::pin(async move {
1972 *abi_result(results, 0)? = value.await?;
1973 Ok(())
1974 })
1975 },
1976 )
1977}
1978
1979pub(crate) fn host_value_async<F>(
1981 store: impl AsContextMut<Data = StoreData>,
1982 ty: FuncType,
1983 body: F,
1984) -> wasmtime::Func
1985where
1986 F: for<'a> Fn(
1987 &'a mut Caller<'_, StoreData>,
1988 &'a [Val],
1989 ) -> Box<dyn Future<Output = wasmtime::Result<Val>> + Send + 'a>
1990 + Send
1991 + Sync
1992 + 'static,
1993{
1994 let body = std::sync::Arc::new(body);
1995 fuel::host_func_async(store, ty, move |mut caller, params, results| {
1996 let body = std::sync::Arc::clone(&body);
1997 Box::new(async move {
1998 let [result] = results else {
1999 return Err(fatal_host_error(
2000 "value-returning callback requires one ABI result",
2001 ));
2002 };
2003 *result = Box::into_pin(body(&mut caller, params))
2004 .await
2005 .map_err(|error| throw_host_error(&mut caller, error))?;
2006 Ok(())
2007 })
2008 })
2009}
2010
2011pub fn host_package_declarations() -> Vec<PackageDeclaration> {
2015 vec![
2016 number_module_definitions(),
2017 super::prelude::temporal::shared::temporal_module_package_declaration(),
2018 ]
2019}
2020
2021pub fn internal_host_package_declarations() -> Vec<PackageDeclaration> {
2023 vec![
2024 super::json::json_module_definitions(),
2025 super::mcp::mcp_call_package_declaration(),
2026 super::tools::tools_call_package_declaration(),
2027 number_coercion_definitions(),
2028 ]
2029}
2030
2031pub fn stdlib_package_declarations() -> Vec<PackageDeclaration> {
2032 crate::stdlib::stdlib_package_declarations()
2033}
2034
2035fn number_module_definitions() -> PackageDeclaration {
2036 let mut defs = PackageDeclaration::with_package(NUMBER_MODULE_NAME);
2037 insert_number_function(
2038 &mut defs,
2039 "parseInt",
2040 vec![Param::new("string", Type::String), parse_int_radix_param()],
2041 Type::Number,
2042 crate::doc(
2043 crate::FileId::NUMBER,
2044 "/**\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 */",
2045 ),
2046 );
2047 insert_number_function(
2048 &mut defs,
2049 "parseFloat",
2050 vec![Param::new("string", Type::String)],
2051 Type::Number,
2052 crate::doc(
2053 crate::FileId::NUMBER,
2054 "/**\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 */",
2055 ),
2056 );
2057 defs
2058}
2059
2060fn number_coercion_definitions() -> PackageDeclaration {
2063 let mut defs = PackageDeclaration::with_package(NUMBER_MODULE_NAME);
2064 insert_number_function(
2065 &mut defs,
2066 "toNumber",
2067 vec![Param::new("string", Type::String)],
2068 Type::Number,
2069 None,
2070 );
2071 defs
2072}
2073
2074fn insert_number_function(
2075 defs: &mut PackageDeclaration,
2076 name: &str,
2077 params: Vec<Param>,
2078 ret: Type,
2079 doc: Option<crate::DocComment>,
2080) {
2081 defs.values.insert(
2082 name.to_string(),
2083 ValueSymbol {
2084 name: name.to_string(),
2085 mangled_name: crate::mangle::host(NUMBER_MODULE_NAME, name),
2086 declaration_span: Span::at(crate::FileId::NUMBER),
2087 kind: ValueKind::Function {
2088 generics: Default::default(),
2089 params,
2090 ret,
2091 type_predicate: None,
2092 doc,
2093 },
2094 },
2095 );
2096}
2097
2098pub(crate) fn parse_int_radix_param() -> Param {
2103 Param::with_default("radix", Type::Number, crate::DefaultValue::Number(0.0))
2104}
2105
2106#[cfg(test)]
2107mod tests {
2108 use super::*;
2109
2110 #[test]
2111 fn host_abi_rejects_bad_shapes_without_numeric_defaults() {
2112 let engine = Engine::default();
2113 let ty = FuncType::new(&engine, [ValType::F64, ValType::I32], [ValType::I64]);
2114 for (params, results) in [
2115 (vec![], vec![Val::I64(0)]),
2116 (vec![Val::F64(0), Val::I32(0)], vec![]),
2117 (vec![Val::I32(0), Val::I32(0)], vec![Val::I64(0)]),
2118 (vec![Val::F64(0), Val::F64(0)], vec![Val::I64(0)]),
2119 ] {
2120 let error = check_host_abi(&ty, ¶ms, &results).unwrap_err();
2121 assert!(error.is::<wasmtime::Trap>());
2122 }
2123 check_host_abi(
2124 &ty,
2125 &[Val::F64(f64::NAN.to_bits()), Val::I32(1)],
2126 &[Val::I64(0)],
2127 )
2128 .unwrap();
2129 }
2130
2131 fn denial(capability: &str) -> Denial {
2132 Denial {
2133 caller: "main".into(),
2134 capability: capability.into(),
2135 source: DenialSource::Policy,
2136 }
2137 }
2138
2139 #[test]
2143 fn a_collected_denial_never_matches_a_later_object() {
2144 let (engine, mut store, _) = async_store();
2145 let ty = StructType::new(
2146 &engine,
2147 [FieldType::new(Mutability::Const, StorageType::I8)],
2148 )
2149 .expect("struct type");
2150 let pre = StructRefPre::new(&mut store, ty);
2151 let mut denials = ThrownDenials::default();
2152
2153 let thrown = {
2154 let mut scope = wasmtime::RootScope::new(&mut store);
2155 let thrown = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(1)]).expect("alloc");
2156 denials.record(&scope, thrown, denial("fs.read"));
2157 let found = denials.find(&scope, &thrown.to_anyref());
2158 assert_eq!(found.map(|d| d.capability), Some("fs.read".to_string()));
2159 thrown.to_anyref()
2160 };
2161 store.gc();
2162
2163 let mut scope = wasmtime::RootScope::new(&mut store);
2164 let forged = wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(2)]).expect("alloc");
2165 assert!(
2166 denials.find(&scope, &forged.to_anyref()).is_none(),
2167 "a program-built error is not a runtime denial"
2168 );
2169 assert!(
2170 denials.find(&scope, &thrown).is_none(),
2171 "the collected denial's entry is dropped"
2172 );
2173 assert!(denials.entries.is_empty());
2174 }
2175
2176 #[test]
2177 fn the_denial_table_drops_its_oldest_entries_past_the_cap() {
2178 let (engine, mut store, _) = async_store();
2179 let ty = StructType::new(
2180 &engine,
2181 [FieldType::new(Mutability::Const, StorageType::I8)],
2182 )
2183 .expect("struct type");
2184 let pre = StructRefPre::new(&mut store, ty);
2185 let mut denials = ThrownDenials::default();
2186 let mut scope = wasmtime::RootScope::new(&mut store);
2187 let mut thrown = Vec::new();
2188 for i in 0..=MAX_THROWN_DENIALS {
2189 let object =
2190 wasmtime::StructRef::new(&mut scope, &pre, &[Val::I32(i as i32)]).expect("alloc");
2191 denials.record(&scope, object, denial(&format!("cap.{i}")));
2192 thrown.push(object.to_anyref());
2193 }
2194 assert_eq!(denials.entries.len(), MAX_THROWN_DENIALS);
2195 assert!(denials.find(&scope, &thrown[0]).is_none(), "oldest dropped");
2196 let newest = denials.find(&scope, &thrown[MAX_THROWN_DENIALS]);
2197 assert_eq!(
2198 newest.map(|d| d.capability),
2199 Some(format!("cap.{MAX_THROWN_DENIALS}"))
2200 );
2201 }
2202
2203 #[tokio::test]
2204 async fn run_ending_errors_bypass_guest_catch_in_both_wrappers() {
2205 #[derive(Clone, Copy, PartialEq, Debug)]
2206 enum Failure {
2207 Ordinary,
2208 Fatal,
2209 MissingArgument,
2210 MissingResult,
2211 NestedTrap(wasmtime::Trap),
2213 }
2214 let failures = [
2215 Failure::Ordinary,
2216 Failure::Fatal,
2217 Failure::MissingArgument,
2218 Failure::MissingResult,
2219 Failure::NestedTrap(wasmtime::Trap::StackOverflow),
2220 Failure::NestedTrap(wasmtime::Trap::OutOfFuel),
2221 Failure::NestedTrap(wasmtime::Trap::Interrupt),
2222 Failure::NestedTrap(wasmtime::Trap::UnreachableCodeReached),
2223 Failure::NestedTrap(wasmtime::Trap::CastFailure),
2224 Failure::NestedTrap(wasmtime::Trap::NullReference),
2225 Failure::NestedTrap(wasmtime::Trap::ArrayOutOfBounds),
2226 ];
2227 for asynchronous in [false, true] {
2228 for kind in failures {
2229 let (engine, mut store, mut linker) = async_store();
2230 crate::runtime::install_runtime_async(&mut linker, &mut store)
2231 .await
2232 .expect("runtime");
2233 let name = crate::mangle::host(TEST_MODULE, "failure");
2234 let failure = move || match kind {
2235 Failure::Fatal => fatal_host_error("test operation: invalid state")
2236 .context("outer host context"),
2237 Failure::NestedTrap(trap) => {
2238 wasmtime::Error::new(trap).context("outer host context")
2239 }
2240 Failure::MissingArgument => abi_arg(&[], usize::MAX).unwrap_err(),
2241 Failure::MissingResult => abi_result(&mut [], usize::MAX).unwrap_err(),
2242 Failure::Ordinary => wasmtime::Error::msg("ordinary operation failure"),
2243 };
2244 let ty = FuncType::new(&engine, [], []);
2245 if asynchronous {
2246 register_host_fn_async(
2247 &mut linker,
2248 TEST_MODULE,
2249 name.clone(),
2250 ty,
2251 move |_, _, _| Box::pin(async move { Err(failure()) }),
2252 )
2253 .expect("async registration");
2254 } else {
2255 register_host_fn(
2256 &mut linker,
2257 TEST_MODULE,
2258 name.clone(),
2259 ty,
2260 move |_, _, _| Err(failure()),
2261 )
2262 .expect("sync registration");
2263 }
2264 let source = format!(
2265 r#"(module
2266 (import "{TEST_MODULE}" "{name}" (func $failure))
2267 (func (export "attempt") (result i32)
2268 (block $caught
2269 (try_table (catch_all $caught) (call $failure))
2270 (return (i32.const 0)))
2271 (i32.const 1))
2272 (func (export "healthy") (result i32) (i32.const 42)))"#
2273 );
2274 let buffer = wast::parser::ParseBuffer::new(&source).expect("WAT tokens");
2275 let mut wat = wast::parser::parse::<wast::Wat>(&buffer).expect("WAT");
2276 let module =
2277 wasmtime::Module::new(&engine, wat.encode().expect("encode")).expect("module");
2278 let instance = linker
2279 .instantiate_async(&mut store, &module)
2280 .await
2281 .expect("instance");
2282 let attempt = instance
2283 .get_typed_func::<(), i32>(&mut store, "attempt")
2284 .expect("attempt");
2285 let result = attempt.call_async(&mut store, ()).await;
2286 match kind {
2287 Failure::Fatal => {
2288 let err =
2289 result.expect_err("fatal failure cannot enter the guest catch handler");
2290 assert!(err.is::<FatalHostError>(), "typed cause lost: {err:#}");
2291 assert!(format!("{err:#}").contains("invalid state"));
2292 }
2293 Failure::MissingArgument | Failure::MissingResult => {
2294 let err = result.expect_err("ABI invariants bypass guest catch");
2295 assert_eq!(
2296 err.downcast_ref::<wasmtime::Trap>(),
2297 Some(&wasmtime::Trap::UnreachableCodeReached)
2298 );
2299 }
2300 Failure::NestedTrap(trap) => {
2301 let err = result.expect_err("a trap cannot enter the guest catch handler");
2302 assert_eq!(
2303 err.downcast_ref::<wasmtime::Trap>(),
2304 Some(&trap),
2305 "trap kind lost: {err:#}"
2306 );
2307 }
2308 Failure::Ordinary => {
2309 assert_eq!(result.expect("guest catches ordinary failure"), 1);
2310 }
2311 }
2312 let healthy = instance
2313 .get_typed_func::<(), i32>(&mut store, "healthy")
2314 .expect("healthy");
2315 assert_eq!(
2316 healthy
2317 .call_async(&mut store, ())
2318 .await
2319 .expect("subsequent call"),
2320 42
2321 );
2322 }
2323 }
2324 }
2325
2326 const TEST_MODULE: &str = "test:sync";
2327
2328 fn async_store() -> (Engine, Store<StoreData>, Linker<StoreData>) {
2331 let cfg = crate::runtime::RuntimeConfig::default();
2332 let engine = cfg.engine().expect("engine");
2333 let data = StoreData::with_vfs(
2334 "test-run".into(),
2335 crate::runtime::Vfs::tempdir().expect("tempdir"),
2336 );
2337 let store = cfg.store_async(&engine, data).expect("store");
2338 let linker = Linker::<StoreData>::new(&engine);
2339 (engine, store, linker)
2340 }
2341
2342 fn resolve(
2343 linker: &Linker<StoreData>,
2344 store: &mut Store<StoreData>,
2345 name: &crate::MangledName,
2346 ) -> wasmtime::Func {
2347 match linker
2348 .get(&mut *store, TEST_MODULE, name.as_str())
2349 .expect("registered function resolves")
2350 {
2351 wasmtime::Extern::Func(func) => func,
2352 other => panic!("expected a func, got {other:?}"),
2353 }
2354 }
2355
2356 #[test]
2360 fn sync_bodies_are_callable_synchronously_on_an_async_store() {
2361 let (engine, mut store, mut linker) = async_store();
2362 let name = crate::mangle::host(TEST_MODULE, "answer");
2363 register_host_fn(
2364 &mut linker,
2365 TEST_MODULE,
2366 name.clone(),
2367 FuncType::new(&engine, [], [ValType::I32]),
2368 |_caller, _params, results| {
2369 results[0] = Val::I32(42);
2370 Ok(())
2371 },
2372 )
2373 .expect("register");
2374
2375 let func = resolve(&linker, &mut store, &name);
2376 let mut results = [Val::I32(0)];
2377 func.call(&mut store, &[], &mut results)
2378 .expect("synchronous call reaches the body");
2379 assert_eq!(results[0].i32(), Some(42));
2380 }
2381
2382 #[test]
2389 fn an_ordinary_error_keeps_its_message() {
2390 let (engine, mut store, mut linker) = async_store();
2391 let name = crate::mangle::host(TEST_MODULE, "boom");
2392 register_host_fn(
2393 &mut linker,
2394 TEST_MODULE,
2395 name.clone(),
2396 FuncType::new(&engine, [], []),
2397 |_caller, _params, _results| Err(wasmtime::Error::msg("boom")),
2398 )
2399 .expect("register");
2400
2401 let func = resolve(&linker, &mut store, &name);
2402 let err = func
2403 .call(&mut store, &[], &mut [])
2404 .expect_err("the body fails");
2405 assert!(format!("{err}").contains("boom"), "got: {err}");
2406 }
2407
2408 #[test]
2409 fn fatal_host_errors_preserve_their_marker() {
2410 let (engine, mut store, mut linker) = async_store();
2411 let name = crate::mangle::host(TEST_MODULE, "fatal");
2412 register_host_fn(
2413 &mut linker,
2414 TEST_MODULE,
2415 name.clone(),
2416 FuncType::new(&engine, [], []),
2417 |_, _, _| Err(super::fatal_host_error("broken dynamic object ABI")),
2418 )
2419 .expect("register");
2420 let func = resolve(&linker, &mut store, &name);
2421 let error = func
2422 .call(&mut store, &[], &mut [])
2423 .expect_err("fatal host error");
2424 assert!(
2425 error.is::<super::FatalHostError>(),
2426 "fatal errors must bypass guest conversion: {error}"
2427 );
2428 }
2429
2430 #[tokio::test]
2431 async fn async_fatal_host_errors_preserve_their_marker() {
2432 let (engine, mut store, mut linker) = async_store();
2433 let name = crate::mangle::host(TEST_MODULE, "fatal_async");
2434 super::register_host_fn_async(
2435 &mut linker,
2436 TEST_MODULE,
2437 name.clone(),
2438 FuncType::new(&engine, [], []),
2439 |_, _, _| Box::pin(async { Err(super::fatal_host_error("broken dynamic object ABI")) }),
2440 )
2441 .expect("register");
2442 let func = resolve(&linker, &mut store, &name);
2443 let error = func
2444 .call_async(&mut store, &[], &mut [])
2445 .await
2446 .expect_err("fatal host error");
2447 assert!(
2448 error.is::<super::FatalHostError>(),
2449 "fatal errors must bypass guest conversion: {error}"
2450 );
2451 }
2452
2453 #[test]
2456 fn a_thrown_exception_passes_through_unconverted() {
2457 let (engine, mut store, mut linker) = async_store();
2458 let name = crate::mangle::host(TEST_MODULE, "rethrow");
2459 register_host_fn(
2460 &mut linker,
2461 TEST_MODULE,
2462 name.clone(),
2463 FuncType::new(&engine, [], []),
2464 |_caller, _params, _results| Err(wasmtime::Error::new(wasmtime::ThrownException)),
2465 )
2466 .expect("register");
2467
2468 let func = resolve(&linker, &mut store, &name);
2469 let err = func
2470 .call(&mut store, &[], &mut [])
2471 .expect_err("the body throws");
2472 assert!(
2473 err.is::<wasmtime::ThrownException>(),
2474 "the pending exception must survive; got: {err}"
2475 );
2476 }
2477}