1use super::{BytecodeProgram, OpCode};
8use crate::type_tracking::SlotKind;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum VerifyError {
13 MissingFrameDescriptor {
15 function_name: String,
16 opcode: OpCode,
17 instruction_offset: usize,
18 },
19 UnknownSlotKind {
21 function_name: String,
22 opcode: OpCode,
23 instruction_offset: usize,
24 slot_index: usize,
25 },
26}
27
28impl std::fmt::Display for VerifyError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 VerifyError::MissingFrameDescriptor {
32 function_name,
33 opcode,
34 instruction_offset,
35 } => write!(
36 f,
37 "Trusted opcode {:?} at offset {} in function '{}' has no FrameDescriptor",
38 opcode, instruction_offset, function_name
39 ),
40 VerifyError::UnknownSlotKind {
41 function_name,
42 opcode,
43 instruction_offset,
44 slot_index,
45 } => write!(
46 f,
47 "Trusted opcode {:?} at offset {} in function '{}': slot {} has Unknown kind",
48 opcode, instruction_offset, function_name, slot_index
49 ),
50 }
51 }
52}
53
54impl std::error::Error for VerifyError {}
55
56pub fn verify_trusted_opcodes(program: &BytecodeProgram) -> Result<(), Vec<VerifyError>> {
61 let mut errors = Vec::new();
62
63 for func in &program.functions {
64 let start = func.entry_point;
69 let end = program
71 .functions
72 .iter()
73 .filter(|f| f.entry_point > start)
74 .map(|f| f.entry_point)
75 .min()
76 .unwrap_or(program.instructions.len());
77
78 for offset in start..end {
79 let Some(instruction) = program.instructions.get(offset) else {
80 break;
81 };
82 if !instruction.opcode.is_trusted() {
83 continue;
84 }
85
86 let Some(ref fd) = func.frame_descriptor else {
88 errors.push(VerifyError::MissingFrameDescriptor {
89 function_name: func.name.clone(),
90 opcode: instruction.opcode,
91 instruction_offset: offset,
92 });
93 continue;
94 };
95
96 if fd.is_empty() || fd.is_all_unknown() {
102 for (idx, slot) in fd.slots.iter().enumerate() {
104 if *slot == SlotKind::Unknown {
105 errors.push(VerifyError::UnknownSlotKind {
106 function_name: func.name.clone(),
107 opcode: instruction.opcode,
108 instruction_offset: offset,
109 slot_index: idx,
110 });
111 break; }
113 }
114 if fd.is_empty() {
116 errors.push(VerifyError::UnknownSlotKind {
117 function_name: func.name.clone(),
118 opcode: instruction.opcode,
119 instruction_offset: offset,
120 slot_index: 0,
121 });
122 }
123 }
124 }
125 }
126
127 if errors.is_empty() {
128 Ok(())
129 } else {
130 Err(errors)
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::bytecode::{Function, Instruction, OpCode};
138 use crate::type_tracking::FrameDescriptor;
139
140 fn make_program(functions: Vec<Function>, instructions: Vec<Instruction>) -> BytecodeProgram {
141 let mut prog = BytecodeProgram::new();
142 prog.functions = functions;
143 prog.instructions = instructions;
144 prog
145 }
146
147 #[test]
148 fn no_trusted_opcodes_passes() {
149 let func = Function {
150 name: "main".to_string(),
151 arity: 0,
152 param_names: vec![],
153 locals_count: 2,
154 entry_point: 0,
155 body_length: 2,
156 is_closure: false,
157 captures_count: 0,
158 is_async: false,
159 ref_params: vec![],
160 ref_mutates: vec![],
161 mutable_captures: vec![],
162 frame_descriptor: None,
163 osr_entry_points: vec![],
164 };
165 let instructions = vec![
166 Instruction::simple(OpCode::AddInt),
167 Instruction::simple(OpCode::ReturnValue),
168 ];
169 let prog = make_program(vec![func], instructions);
170 assert!(verify_trusted_opcodes(&prog).is_ok());
171 }
172
173 #[test]
174 fn trusted_opcode_missing_frame_descriptor() {
175 use crate::bytecode::Operand;
176 let func = Function {
177 name: "load_trusted".to_string(),
178 arity: 2,
179 param_names: vec!["a".to_string(), "b".to_string()],
180 locals_count: 2,
181 entry_point: 0,
182 body_length: 2,
183 is_closure: false,
184 captures_count: 0,
185 is_async: false,
186 ref_params: vec![],
187 ref_mutates: vec![],
188 mutable_captures: vec![],
189 frame_descriptor: None,
190 osr_entry_points: vec![],
191 };
192 let instructions = vec![
193 Instruction::new(OpCode::LoadLocalTrusted, Some(Operand::Local(0))),
194 Instruction::simple(OpCode::ReturnValue),
195 ];
196 let prog = make_program(vec![func], instructions);
197 let errs = verify_trusted_opcodes(&prog).unwrap_err();
198 assert_eq!(errs.len(), 1);
199 assert!(matches!(
200 &errs[0],
201 VerifyError::MissingFrameDescriptor { .. }
202 ));
203 }
204
205 #[test]
206 fn trusted_opcode_with_valid_frame_descriptor() {
207 use crate::bytecode::Operand;
208 let func = Function {
209 name: "load_trusted".to_string(),
210 arity: 2,
211 param_names: vec!["a".to_string(), "b".to_string()],
212 locals_count: 2,
213 entry_point: 0,
214 body_length: 2,
215 is_closure: false,
216 captures_count: 0,
217 is_async: false,
218 ref_params: vec![],
219 ref_mutates: vec![],
220 mutable_captures: vec![],
221 frame_descriptor: Some(FrameDescriptor::from_slots(vec![
222 SlotKind::Int64,
223 SlotKind::Int64,
224 ])),
225 osr_entry_points: vec![],
226 };
227 let instructions = vec![
228 Instruction::new(OpCode::LoadLocalTrusted, Some(Operand::Local(0))),
229 Instruction::simple(OpCode::ReturnValue),
230 ];
231 let prog = make_program(vec![func], instructions);
232 assert!(verify_trusted_opcodes(&prog).is_ok());
233 }
234
235 #[test]
236 fn is_trusted_method() {
237 assert!(OpCode::LoadLocalTrusted.is_trusted());
238 assert!(OpCode::JumpIfFalseTrusted.is_trusted());
239 assert!(!OpCode::AddInt.is_trusted());
240 assert!(!OpCode::Add.is_trusted());
241 }
242
243 #[test]
244 fn trusted_variant_mapping() {
245 assert_eq!(
246 OpCode::LoadLocal.trusted_variant(),
247 Some(OpCode::LoadLocalTrusted)
248 );
249 assert_eq!(
250 OpCode::JumpIfFalse.trusted_variant(),
251 Some(OpCode::JumpIfFalseTrusted)
252 );
253 assert_eq!(OpCode::Add.trusted_variant(), None);
254 assert_eq!(OpCode::AddInt.trusted_variant(), None);
255 }
256}