Skip to main content

submilli_engine/codegen/
throw.rs

1//! Shared emission for raising a catchable `Error`: the native-`throw` tail
2//! used by every wasm throw site (user `throw`, array bounds, `as` cast
3//! mismatch, JSON shape mismatch), plus the `TypeError` and `ReferenceError`
4//! raises built on it and their messages.
5//!
6//! A throw whose message belongs to one subsystem keeps its constructor and
7//! constant there — `bounds::emit_index_oob_throw` does. What lands here is
8//! what has no better home: the shared tail, and messages raised from emitters
9//! that don't own a module of their own.
10//!
11//! Every constant here must also be interned by `codegen::analysis`. The throw
12//! reads its message out of the string pool; a missing entry is an internal
13//! compile failure rather than a guest exception.
14
15use wasm_encoder::{HeapType, Instruction};
16
17use crate::codegen::CodegenCtx;
18use crate::codegen::function_emitter::{FunctionEmitter, emit_const_string_by_text};
19
20/// Thrown by `x!` when the value turns out to be `null` or `undefined`.
21pub const NON_NULL_ASSERT_MESSAGE: &str = "non-null assertion failed: value is null or undefined";
22
23/// Thrown when a property write resolves, at runtime, to a getter with no
24/// setter — reachable only through a receiver whose static type does not say
25/// which implementation backs the property.
26pub const READ_ONLY_PROPERTY_MESSAGE: &str =
27    "cannot assign to a property backed by a getter with no setter";
28
29/// Thrown when a function that must return a value reaches its end. The
30/// missing-return rule leaves that possible only through an exhaustive
31/// `switch` whose value a call or closure moved outside the type a narrowing
32/// gave it. JavaScript returns `undefined` there, which the declared return
33/// type cannot hold.
34pub const MISSING_RETURN_VALUE_MESSAGE: &str =
35    "function ended without returning a value: no `case` of an exhaustive `switch` matched";
36
37/// Whether a value-returning body in `ta` can reach its end, and so throws
38/// [`MISSING_RETURN_VALUE_MESSAGE`] there. Emission and the analysis pass that
39/// interns the message both ask this, so they agree.
40pub(crate) fn body_end_may_be_reached(ta: &crate::TypedAst) -> bool {
41    !ta.exhaustive_switches.is_empty()
42}
43
44/// Raise a `TypeError` carrying `message`. Every message thrown this way must
45/// also be interned by the analysis pass, or the string pool has no entry to
46/// point at. Diverges, like [`emit_error_throw`].
47pub(crate) fn emit_type_error_throw(
48    emitter: &mut FunctionEmitter,
49    ctx: &CodegenCtx,
50    message: &str,
51) {
52    emit_builtin_error_throw(emitter, ctx, "TypeError#constructor", message);
53}
54
55/// Raise a `ReferenceError` carrying `message`, under the same interning
56/// contract as [`emit_type_error_throw`].
57pub(crate) fn emit_reference_error_throw(
58    emitter: &mut FunctionEmitter,
59    ctx: &CodegenCtx,
60    message: &str,
61) {
62    emit_builtin_error_throw(emitter, ctx, "ReferenceError#constructor", message);
63}
64
65fn emit_builtin_error_throw(
66    emitter: &mut FunctionEmitter,
67    ctx: &CodegenCtx,
68    constructor: &str,
69    message: &str,
70) {
71    if ctx
72        .latch(emit_const_string_by_text(emitter, ctx, message))
73        .is_none()
74    {
75        return;
76    }
77    let Some(new_idx) = ctx.require(
78        ctx.symbols.prelude_func_idx(constructor),
79        "built-in error constructor imported from prelude",
80    ) else {
81        return;
82    };
83    emitter.instruction(Instruction::Call(new_idx));
84    emit_error_throw(emitter, ctx);
85}
86
87/// Emit the throw tail: with an `Error` value already on the stack, narrow it
88/// to `$Error` and raise the module's exception tag. The engine captures the
89/// throw-site backtrace at the `throw` op. The `Throw` diverges; a caller
90/// inside a typed block must emit a trailing `Unreachable` itself.
91pub(crate) fn emit_error_throw(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
92    let Some(error_idx) = ctx.require(
93        ctx.symbols.intrinsic_type_indices().map(|i| i.error),
94        "error intrinsic registered",
95    ) else {
96        return;
97    };
98    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(error_idx)));
99    let Some(tag_idx) = ctx.require(ctx.symbols.error_tag_idx(), "error tag registered") else {
100        return;
101    };
102    emitter.instruction(Instruction::Throw(tag_idx));
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::TypedAst;
109    use crate::codegen::invariant_tests::{assert_internal, with_context};
110    use crate::codegen::{SymbolTable, tests::mock_symbols_with_intrinsics};
111
112    #[test]
113    fn missing_exception_metadata_is_a_compile_failure() {
114        for symbols in [SymbolTable::default(), mock_symbols_with_intrinsics()] {
115            with_context(&TypedAst::new(), &symbols, |ctx| {
116                emit_error_throw(&mut FunctionEmitter::new(ctx, &[]).unwrap(), ctx);
117                assert_internal(ctx.check_failure().unwrap_err());
118            });
119        }
120    }
121
122    #[test]
123    fn missing_type_error_constructor_is_a_compile_failure() {
124        let mut symbols = mock_symbols_with_intrinsics();
125        symbols.record_global(crate::mangle::prelude("string_vtable"), 0);
126        let mut strings = crate::codegen::StringPool::default();
127        strings.intern_text("test message");
128        with_context(&TypedAst::new(), &symbols, |ctx| {
129            let ctx = CodegenCtx {
130                strings: &strings,
131                failure: std::cell::Cell::new(None),
132                validator_steps_left: std::cell::Cell::new(ctx.validator_steps_left.get()),
133                validator_root: std::cell::Cell::new(None),
134                check_is_standalone: std::cell::Cell::new(false),
135                ..*ctx
136            };
137            emit_type_error_throw(
138                &mut FunctionEmitter::new(&ctx, &[]).unwrap(),
139                &ctx,
140                "test message",
141            );
142            assert_internal(ctx.check_failure().unwrap_err());
143        });
144    }
145}