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 let func = Function {
176 name: "add_trusted".to_string(),
177 arity: 2,
178 param_names: vec!["a".to_string(), "b".to_string()],
179 locals_count: 2,
180 entry_point: 0,
181 body_length: 2,
182 is_closure: false,
183 captures_count: 0,
184 is_async: false,
185 ref_params: vec![],
186 ref_mutates: vec![],
187 mutable_captures: vec![],
188 frame_descriptor: None,
189 osr_entry_points: vec![],
190 };
191 let instructions = vec![
192 Instruction::simple(OpCode::AddIntTrusted),
193 Instruction::simple(OpCode::ReturnValue),
194 ];
195 let prog = make_program(vec![func], instructions);
196 let errs = verify_trusted_opcodes(&prog).unwrap_err();
197 assert_eq!(errs.len(), 1);
198 assert!(matches!(
199 &errs[0],
200 VerifyError::MissingFrameDescriptor { .. }
201 ));
202 }
203
204 #[test]
205 fn trusted_opcode_with_valid_frame_descriptor() {
206 let func = Function {
207 name: "add_trusted".to_string(),
208 arity: 2,
209 param_names: vec!["a".to_string(), "b".to_string()],
210 locals_count: 2,
211 entry_point: 0,
212 body_length: 2,
213 is_closure: false,
214 captures_count: 0,
215 is_async: false,
216 ref_params: vec![],
217 ref_mutates: vec![],
218 mutable_captures: vec![],
219 frame_descriptor: Some(FrameDescriptor::from_slots(vec![
220 SlotKind::Int64,
221 SlotKind::Int64,
222 ])),
223 osr_entry_points: vec![],
224 };
225 let instructions = vec![
226 Instruction::simple(OpCode::AddIntTrusted),
227 Instruction::simple(OpCode::ReturnValue),
228 ];
229 let prog = make_program(vec![func], instructions);
230 assert!(verify_trusted_opcodes(&prog).is_ok());
231 }
232
233 #[test]
234 fn is_trusted_method() {
235 assert!(OpCode::AddIntTrusted.is_trusted());
236 assert!(OpCode::DivNumberTrusted.is_trusted());
237 assert!(!OpCode::AddInt.is_trusted());
238 assert!(!OpCode::Add.is_trusted());
239 }
240
241 #[test]
242 fn trusted_variant_mapping() {
243 assert_eq!(
244 OpCode::AddInt.trusted_variant(),
245 Some(OpCode::AddIntTrusted)
246 );
247 assert_eq!(
248 OpCode::DivNumber.trusted_variant(),
249 Some(OpCode::DivNumberTrusted)
250 );
251 assert_eq!(OpCode::Add.trusted_variant(), None);
252 assert_eq!(OpCode::AddDecimal.trusted_variant(), None);
253 }
254}