wasm/validation/
read_constant_expression.rs

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