oxide_core/bytecode/
validate.rs1use std::fmt;
7
8use super::{Capture, CodeUnit, Format, NO_SPILL, Opcode, flags};
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum ValidateErrorKind {
12 EmptyCode,
13 FallsOffEnd,
15 LayoutOverflow,
17 Register(u32),
18 WideRegister(u32),
19 CallWindow,
21 RegisterRange,
23 Target(u32),
24 Constant(u32),
25 Child(u32),
26 UnpublishedChild(u32),
28 Capture(u32),
29 ChildCapture(u32),
31 HandlerRange,
33 HandlerNesting,
36 LineTable,
39 Flags,
41 Site(u32),
43 Spill,
46}
47
48#[derive(Clone, Copy, Debug, PartialEq, Eq)]
49pub struct ValidateError {
50 pub at: Option<u32>,
52 pub kind: ValidateErrorKind,
53}
54
55impl fmt::Display for ValidateError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 write!(f, "invalid code unit: {:?}", self.kind)?;
58 if let Some(at) = self.at {
59 write!(f, " at {at}")?;
60 }
61 Ok(())
62 }
63}
64
65impl std::error::Error for ValidateError {}
66
67fn fail<T>(at: Option<u32>, kind: ValidateErrorKind) -> Result<T, ValidateError> {
68 Err(ValidateError { at, kind })
69}
70
71pub fn validate<'a>(
74 unit: &CodeUnit,
75 published: &dyn Fn(super::CodeUnitId) -> Option<&'a CodeUnit>,
76) -> Result<(), ValidateError> {
77 use ValidateErrorKind as E;
78 let len = unit.code.len() as u32;
79 let registers = unit.register_count as u32;
80 let Some(last) = unit.code.last() else {
81 return fail(None, E::EmptyCode);
82 };
83 if !last.op.is_terminator() {
84 return fail(Some(len - 1), E::FallsOffEnd);
85 }
86 if unit.temp_base() > registers {
87 return fail(None, E::LayoutOverflow);
88 }
89 if unit.has(flags::ARROW) && unit.has(flags::CONSTRUCTOR) {
90 return fail(None, E::Flags);
91 }
92 let positions = &unit.debug.positions;
93 if positions.windows(2).any(|pair| pair[0].pc >= pair[1].pc)
94 || positions
95 .last()
96 .is_some_and(|last| last.pc as usize >= unit.code.len())
97 {
98 return fail(None, E::LineTable);
99 }
100 for (index, &child) in unit.children.iter().enumerate() {
101 let Some(child_unit) = published(child) else {
102 return fail(Some(index as u32), E::UnpublishedChild(child.0));
103 };
104 for capture in &child_unit.captures {
105 match *capture {
106 Capture::None => {}
107 Capture::Register(r) if (r as u32) < registers => {}
108 Capture::Outer(i) if (i as usize) < unit.captures.len() => {}
109 Capture::Slot(i) if unit.spill_register != NO_SPILL && i < unit.spill_slots => {}
110 Capture::Register(r) | Capture::Outer(r) | Capture::Slot(r) => {
111 return fail(Some(index as u32), E::ChildCapture(r as u32));
112 }
113 }
114 }
115 }
116
117 for (pc, insn) in unit.code.iter().enumerate() {
118 let at = Some(pc as u32);
119 let register = |r: u8| {
120 if (r as u32) < registers {
121 Ok(())
122 } else {
123 fail(at, E::Register(r as u32))
124 }
125 };
126 let target = |t: u32| {
127 if t < len {
128 Ok(())
129 } else {
130 fail(at, E::Target(t))
131 }
132 };
133 match insn.op.format() {
134 Format::None | Format::Key => {}
135 Format::A | Format::AInt | Format::AKey | Format::ACount => {
136 register(insn.a)?;
137 }
138 Format::ACapture => {
139 register(insn.a)?;
140 if insn.imm as usize >= unit.captures.len() {
141 return fail(at, E::Capture(insn.imm));
142 }
143 }
144 Format::AB | Format::ABInt | Format::ABKey => {
145 register(insn.a)?;
146 register(insn.b)?;
147 }
148 Format::ABC => {
149 register(insn.a)?;
150 register(insn.b)?;
151 register(insn.c)?;
152 }
153 Format::AConst => {
154 register(insn.a)?;
155 let last = insn.imm as u64 + u64::from(insn.op == Opcode::NewRegExp);
157 if last >= u64::from(unit.constant_count) {
158 return fail(at, E::Constant(insn.imm));
159 }
160 }
161 Format::ABRange => {
162 register(insn.a)?;
163 if insn.imm > 0 && insn.b as u64 + insn.imm as u64 > registers as u64 {
164 return fail(at, E::RegisterRange);
165 }
166 }
167 Format::AChild => {
168 register(insn.a)?;
169 if insn.imm as usize >= unit.children.len() {
170 return fail(at, E::Child(insn.imm));
171 }
172 }
173 Format::Call => {
174 register(insn.a)?;
175 let operands = match insn.op {
178 Opcode::Call | Opcode::TailCall => 1,
179 Opcode::CallSpread | Opcode::NewSpread => 3,
180 _ => 2,
181 };
182 if insn.b as u32 + operands + insn.c as u32 > registers {
183 return fail(at, E::CallWindow);
184 }
185 }
186 Format::Jump => target(insn.imm)?,
187 Format::AJump | Format::AShortJump => {
188 register(insn.a)?;
189 target(insn.imm)?;
190 }
191 Format::ABJump => {
192 register(insn.a)?;
193 register(insn.b)?;
194 target(insn.imm)?;
195 }
196 Format::AWide => {
197 register(insn.a)?;
198 if insn.imm >= registers {
199 return fail(at, E::WideRegister(insn.imm));
200 }
201 }
202 }
203 if matches!(
205 insn.op,
206 Opcode::LoadSlot | Opcode::StoreSlot | Opcode::NewSpill
207 ) {
208 let spill = if insn.op == Opcode::NewSpill {
209 insn.a
210 } else {
211 insn.b
212 };
213 let slot = if insn.op == Opcode::NewSpill {
214 u32::from(unit.spill_slots).saturating_sub(1)
215 } else {
216 insn.imm
217 };
218 if unit.spill_register == NO_SPILL
219 || u16::from(spill) != unit.spill_register
220 || (insn.op != Opcode::NewSpill && slot >= u32::from(unit.spill_slots))
221 || (insn.op == Opcode::NewSpill && insn.imm != u32::from(unit.spill_slots))
222 {
223 return fail(at, E::Spill);
224 }
225 }
226 if let Some(site) = insn.site()
227 && site >= unit.ic_count
228 {
229 return fail(at, E::Site(site));
230 }
231 }
232
233 for (index, handler) in unit.handlers.iter().enumerate() {
234 let at = Some(index as u32);
235 if handler.start >= handler.end || handler.end > len {
236 return fail(at, E::HandlerRange);
237 }
238 if handler.target >= len {
239 return fail(at, E::Target(handler.target));
240 }
241 if handler.register as u32 >= registers {
242 return fail(at, E::Register(handler.register as u32));
243 }
244 for later in &unit.handlers[index + 1..] {
247 let disjoint = later.end <= handler.start || handler.end <= later.start;
248 let encloses = later.start <= handler.start && handler.end <= later.end;
249 if !disjoint && !encloses {
250 return fail(at, E::HandlerNesting);
251 }
252 }
253 }
254 Ok(())
255}