wasm/validation/
read_constant_expression.rs

1use alloc::vec::Vec;
2
3use crate::core::indices::{FuncIdx, IdxVec, TypeIdx};
4use crate::core::reader::span::Span;
5use crate::core::reader::types::global::GlobalType;
6use crate::core::reader::WasmReader;
7use crate::core::utils::ToUsizeExt;
8use crate::{NumType, RefType, ValType, ValidationError};
9
10use super::validation_stack::ValidationStack;
11
12/// Read and validate constant expressions.
13///
14/// This function is used to validate that a constant expression produces the expected result. The main use case for
15/// this is to validate that an initialization expression for a global returns the correct value.
16///
17/// Note: to be valid, constant expressions may not leave garbage data on the stack. It may leave only what is expected
18/// and nothing more.
19///
20/// Valid constant instructions are:
21/// - Core: <https://webassembly.github.io/spec/core/valid/instructions.html#valid-constant>
22/// - Extended Proposal: <https://webassembly.github.io/extended-const/core/valid/instructions.html#valid-constant>
23///
24/// # The Wonders of `global.get`
25/// The `global.get` instruction is quite picky by nature. To make a long story short, there are two rules to follow to
26/// be able to use this expression.
27///
28/// ## 1. The referenced global must be imported
29/// Take the example code:
30/// ```wat
31/// (module
32///     (global (export "g") (mut i32) (
33///         i32.add (i32.const 1) (i32.const 2)
34///     ))
35///
36///     (global (export "h1") i32 (
37///         i32.const 1
38///     ))
39///
40///     (global (export "h2") i32 (
41///         global.get 1
42///     ))
43///
44///     (func (export "f")
45///         i32.const 100
46///         global.set 0))
47/// ```
48///
49/// When compiling with wat2wasm, the following error is thrown:
50/// ```wat
51/// Error: validate failed:
52/// test.wast:11:24: error: initializer expression can only reference an imported global
53///             global.get 1
54///                        ^
55/// ```
56///
57/// When compiling the code with the latest dev build of wasmtime, the following error is thrown:
58/// ```wat
59/// failed to parse WebAssembly module
60///
61/// Caused by:
62///     constant expression required: global.get of locally defined global (at offset 0x24)
63/// ```
64///
65/// ## 2. The referenced global must be immutable
66///
67///```wat
68/// (module
69///     (import "env" "g" (global (mut i32)))
70///     (global (export "h") (mut i32) (
71///         i32.add (i32.const 1) (global.get 0)
72///     ))
73///   )
74/// ```
75///
76/// When compiling with wat2wasm, the following error is thrown:
77/// ```wat
78/// Error: validate failed:
79/// test.wast:4:27: error: initializer expression cannot reference a mutable global
80///     i32.add (i32.const 1) (global.get 0)
81/// ```
82///
83/// # Note
84/// The following instructions are not yet supported:
85/// - `ref.null`
86/// - `ref.func`
87/// - `global.get`
88pub fn read_constant_expression(
89    wasm: &mut WasmReader,
90    stack: &mut ValidationStack,
91    // The globals slice should contain ONLY imported globals IF AND ONLY IF we are calling `read_constant_expression` for local globals instantiation
92    // As per https://webassembly.github.io/spec/core/valid/modules.html (bottom of the page):
93    //
94    //  Globals, however, are not recursive and not accessible within constant expressions when they are defined locally. The effect of defining the limited context C'
95    //   for validating certain definitions is that they can only access functions and imported globals and nothing else.
96    imported_globals: &[GlobalType],
97    c_funcs: &IdxVec<FuncIdx, TypeIdx>,
98) -> Result<(Span, Vec<FuncIdx>), ValidationError> {
99    let start_pc = wasm.pc;
100    let mut seen_func_idxs: Vec<FuncIdx> = Vec::new();
101
102    loop {
103        let Ok(first_instr_byte) = wasm.read_u8() else {
104            return Err(ValidationError::ExprMissingEnd);
105        };
106
107        #[cfg(not(debug_assertions))]
108        trace!("Read constant instruction byte {first_instr_byte:#X?} ({first_instr_byte})");
109
110        #[cfg(debug_assertions)]
111        trace!(
112            "Validation - Executing instruction {}",
113            opcode_byte_to_str(first_instr_byte)
114        );
115
116        use crate::core::reader::types::opcode::*;
117        match first_instr_byte {
118            END => {
119                // The code here for checking the global type was moved to where the global is actually validated
120                return Ok((Span::new(start_pc, wasm.pc - start_pc), seen_func_idxs));
121            }
122            GLOBAL_GET => {
123                // Unfortunately, we cannot use the GlobalIdx type yet, because
124                // constant expressions may only access imported globals.  Wasm
125                // specifies that only imported globals may be accessed (see
126                // comment on `imported_globals` parameter).
127                let imported_global_idx = wasm.read_var_u32()?;
128
129                let global = imported_globals
130                    .get(imported_global_idx.into_usize())
131                    .ok_or(ValidationError::InvalidGlobalIdx(imported_global_idx))?;
132
133                stack.push_valtype(global.ty);
134            }
135            I32_CONST => {
136                let _num = wasm.read_var_i32()?;
137                stack.push_valtype(ValType::NumType(NumType::I32));
138            }
139            F32_CONST => {
140                let _num = wasm.read_f32();
141                stack.push_valtype(ValType::NumType(NumType::F32));
142            }
143            F64_CONST => {
144                let _num = wasm.read_f64();
145                stack.push_valtype(ValType::NumType(NumType::F64));
146            }
147            I64_CONST => {
148                let _num = wasm.read_var_i64()?;
149                stack.push_valtype(ValType::NumType(NumType::I64));
150            }
151            REF_NULL => {
152                stack.push_valtype(ValType::RefType(RefType::read(wasm)?));
153            }
154            REF_FUNC => {
155                let func_idx = FuncIdx::read_and_validate(wasm, c_funcs)?;
156
157                // This func_idx is automatically in C.refs. No need to check.
158                // as we are single pass validating, add it to C.refs set.
159                seen_func_idxs.push(func_idx);
160
161                stack.push_valtype(ValType::RefType(crate::RefType::FuncRef));
162            }
163            FD_EXTENSIONS => {
164                use crate::core::reader::types::opcode::fd_extensions::*;
165
166                let Ok(second_instr) = wasm.read_var_u32() else {
167                    return Err(ValidationError::ExprMissingEnd);
168                };
169                match second_instr {
170                    V128_CONST => {
171                        for _ in 0..16 {
172                            let _data = wasm.read_u8()?;
173                        }
174                        stack.push_valtype(ValType::VecType);
175                    }
176                    0x00..=0x0B | 0x0D.. => {
177                        trace!("Encountered unknown multi-byte instruction in validation - constant expression - {first_instr_byte:x?} {second_instr}");
178                        return Err(ValidationError::InvalidInstr(first_instr_byte));
179                    }
180                }
181            }
182
183            0x00..=0x0A
184            | 0x0C..=0x22
185            | 0x24..=0x40
186            | 0x45..=0xBF
187            | 0xC0..=0xCF
188            | 0xD1
189            | 0xD3..=0xFC
190            | 0xFE..=0xFF => {
191                trace!("Encountered unknown instruction in validation - constant expression - {first_instr_byte:x?}");
192                return Err(ValidationError::InvalidInstr(first_instr_byte));
193            }
194        }
195    }
196}