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
65/// Raise a `MissingValueError` carrying `message`, under the same interning
66/// contract as [`emit_type_error_throw`].
67pub(crate) fn emit_missing_value_error_throw(
68    emitter: &mut FunctionEmitter,
69    ctx: &CodegenCtx,
70    message: &str,
71) {
72    emit_builtin_error_throw(emitter, ctx, "MissingValueError#constructor", message);
73}
74
75fn emit_builtin_error_throw(
76    emitter: &mut FunctionEmitter,
77    ctx: &CodegenCtx,
78    constructor: &str,
79    message: &str,
80) {
81    if ctx
82        .latch(emit_const_string_by_text(emitter, ctx, message))
83        .is_none()
84    {
85        return;
86    }
87    let Some(new_idx) = ctx.require(
88        ctx.symbols.prelude_func_idx(constructor),
89        "built-in error constructor imported from prelude",
90    ) else {
91        return;
92    };
93    emit_no_error_options(emitter);
94    emitter.instruction(Instruction::Call(new_idx));
95    emit_error_throw(emitter, ctx);
96}
97
98/// Push the absent `options` a built-in error constructor takes after its
99/// message, for an error the compiler raises itself.
100pub(crate) fn emit_no_error_options(emitter: &mut FunctionEmitter) {
101    emitter.instruction(Instruction::RefNull(wasm_encoder::HeapType::Abstract {
102        shared: false,
103        ty: wasm_encoder::AbstractHeapType::None,
104    }));
105}
106
107/// Emit the throw tail: with an `Error` value already on the stack, narrow it
108/// to `$Error` and raise the module's exception tag. The engine captures the
109/// throw-site backtrace at the `throw` op. The `Throw` diverges; a caller
110/// inside a typed block must emit a trailing `Unreachable` itself.
111pub(crate) fn emit_error_throw(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
112    let Some(error_idx) = ctx.require(
113        ctx.symbols.intrinsic_type_indices().map(|i| i.error),
114        "error intrinsic registered",
115    ) else {
116        return;
117    };
118    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(error_idx)));
119    let Some(tag_idx) = ctx.require(ctx.symbols.error_tag_idx(), "error tag registered") else {
120        return;
121    };
122    emitter.instruction(Instruction::Throw(tag_idx));
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use crate::TypedAst;
129    use crate::codegen::invariant_tests::{assert_internal, with_context};
130    use crate::codegen::{SymbolTable, tests::mock_symbols_with_intrinsics};
131
132    #[test]
133    fn missing_exception_metadata_is_a_compile_failure() {
134        for symbols in [SymbolTable::default(), mock_symbols_with_intrinsics()] {
135            with_context(&TypedAst::new(), &symbols, |ctx| {
136                emit_error_throw(&mut FunctionEmitter::new(ctx, &[]).unwrap(), ctx);
137                assert_internal(ctx.check_failure().unwrap_err());
138            });
139        }
140    }
141
142    #[test]
143    fn missing_type_error_constructor_is_a_compile_failure() {
144        let mut symbols = mock_symbols_with_intrinsics();
145        symbols.record_global(crate::mangle::prelude("string_vtable"), 0);
146        let mut strings = crate::codegen::StringPool::default();
147        strings.intern_text("test message");
148        with_context(&TypedAst::new(), &symbols, |ctx| {
149            let ctx = CodegenCtx {
150                strings: &strings,
151                failure: std::cell::Cell::new(None),
152                validator_steps_left: std::cell::Cell::new(ctx.validator_steps_left.get()),
153                validator_root: std::cell::Cell::new(None),
154                check_is_standalone: std::cell::Cell::new(false),
155                ..*ctx
156            };
157            emit_type_error_throw(
158                &mut FunctionEmitter::new(&ctx, &[]).unwrap(),
159                &ctx,
160                "test message",
161            );
162            assert_internal(ctx.check_failure().unwrap_err());
163        });
164    }
165}