1use crate::{
42 mir::{BlockId, Function, InstId, InstKind, Module, Value},
43 pass::AnalysisPass,
44};
45use solar_data_structures::map::FxHashMap;
46use std::fmt;
47
48pub struct Validator;
50
51#[derive(Clone, Debug)]
53pub struct ValidationError {
54 pub message: String,
56 pub block: Option<BlockId>,
58 pub inst: Option<InstId>,
60}
61
62impl ValidationError {
63 fn new(message: impl Into<String>) -> Self {
64 Self { message: message.into(), block: None, inst: None }
65 }
66
67 fn at_block(message: impl Into<String>, block: BlockId) -> Self {
68 Self { message: message.into(), block: Some(block), inst: None }
69 }
70
71 fn at_inst(message: impl Into<String>, block: BlockId, inst: InstId) -> Self {
72 Self { message: message.into(), block: Some(block), inst: Some(inst) }
73 }
74}
75
76impl fmt::Display for ValidationError {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match (self.block, self.inst) {
79 (Some(b), Some(i)) => {
80 write!(f, "[bb{}, inst{}] {}", b.index(), i.index(), self.message)
81 }
82 (Some(b), None) => write!(f, "[bb{}] {}", b.index(), self.message),
83 (None, _) => write!(f, "{}", self.message),
84 }
85 }
86}
87
88impl Validator {
89 #[must_use]
91 pub fn validate_function(func: &Function) -> Vec<ValidationError> {
92 let mut errors = Vec::new();
93 let num_values = func.values.len();
94 let num_blocks = func.blocks.len();
95 let num_insts = func.instructions.len();
96
97 if num_blocks == 0 {
98 return errors;
99 }
100
101 let mut inst_def_count: FxHashMap<InstId, usize> = FxHashMap::default();
104 for v in func.values.iter() {
105 if let Value::Inst(inst_id) = v {
106 *inst_def_count.entry(*inst_id).or_default() += 1;
107 }
108 }
109 for (inst_id, count) in inst_def_count.iter() {
110 if *count > 1 {
111 errors.push(ValidationError::new(format!(
112 "instruction inst{} is defined by {count} Value entries (must be 1)",
113 inst_id.index()
114 )));
115 }
116 if inst_id.index() < num_insts && func.instructions[*inst_id].result_ty.is_none() {
118 errors.push(ValidationError::new(format!(
119 "instruction inst{} (`{:?}`) has a result Value entry but no result type",
120 inst_id.index(),
121 func.instructions[*inst_id].kind
122 )));
123 }
124 }
125
126 for (block_id, block) in func.blocks.iter_enumerated() {
128 let term = match &block.terminator {
130 Some(t) => t,
131 None => {
132 errors.push(ValidationError::at_block("block has no terminator", block_id));
133 continue;
134 }
135 };
136
137 let term_succs = term.successors();
138
139 for &succ in &term_succs {
141 if succ.index() >= num_blocks {
142 errors.push(ValidationError::at_block(
143 format!("terminator references nonexistent block bb{}", succ.index()),
144 block_id,
145 ));
146 continue;
147 }
148 if !func.blocks[succ].predecessors.contains(&block_id) {
149 errors.push(ValidationError::at_block(
150 format!(
151 "successor bb{} does not list bb{} as a predecessor",
152 succ.index(),
153 block_id.index()
154 ),
155 block_id,
156 ));
157 }
158 }
159
160 for &pred in &block.predecessors {
162 if pred.index() >= num_blocks {
163 errors.push(ValidationError::at_block(
164 format!(
165 "stored predecessor references nonexistent block bb{}",
166 pred.index()
167 ),
168 block_id,
169 ));
170 continue;
171 }
172 let Some(pred_term) = &func.blocks[pred].terminator else {
173 errors.push(ValidationError::at_block(
174 format!("stored predecessor bb{} has no terminator", pred.index()),
175 block_id,
176 ));
177 continue;
178 };
179 if !pred_term.successors().contains(&block_id) {
180 errors.push(ValidationError::at_block(
181 format!(
182 "stored predecessor bb{} does not branch to bb{}",
183 pred.index(),
184 block_id.index()
185 ),
186 block_id,
187 ));
188 }
189 }
190
191 for op in term.operands() {
193 if op.index() >= num_values {
194 errors.push(ValidationError::at_block(
195 format!(
196 "terminator references undefined value v{} (only {} values exist)",
197 op.index(),
198 num_values
199 ),
200 block_id,
201 ));
202 }
203 }
204
205 let block_preds: Vec<BlockId> = block.predecessors.iter().copied().collect();
207 for &inst_id in &block.instructions {
208 if inst_id.index() >= num_insts {
209 errors.push(ValidationError::at_block(
210 format!("block contains nonexistent inst{}", inst_id.index()),
211 block_id,
212 ));
213 continue;
214 }
215 let inst = func.instruction(inst_id);
216
217 for op in inst.kind.operands() {
219 if op.index() >= num_values {
220 errors.push(ValidationError::at_inst(
221 format!(
222 "instruction references undefined value v{} (only {} values exist)",
223 op.index(),
224 num_values
225 ),
226 block_id,
227 inst_id,
228 ));
229 }
230 }
231
232 if let InstKind::Phi(incoming) = &inst.kind {
234 for (pred_block, _) in incoming {
236 if pred_block.index() >= num_blocks {
237 errors.push(ValidationError::at_inst(
238 format!(
239 "phi incoming references nonexistent block bb{}",
240 pred_block.index()
241 ),
242 block_id,
243 inst_id,
244 ));
245 continue;
246 }
247 if !block_preds.contains(pred_block) {
248 errors.push(ValidationError::at_inst(
249 format!(
250 "phi incoming from bb{} but bb{} is not a predecessor",
251 pred_block.index(),
252 pred_block.index()
253 ),
254 block_id,
255 inst_id,
256 ));
257 }
258 }
259 for pred in &block_preds {
261 if !incoming.iter().any(|(b, _)| b == pred) {
262 errors.push(ValidationError::at_inst(
263 format!(
264 "phi missing incoming entry for predecessor bb{}",
265 pred.index()
266 ),
267 block_id,
268 inst_id,
269 ));
270 }
271 }
272 for (index, (pred_block, value)) in incoming.iter().enumerate() {
276 if incoming
277 .iter()
278 .take(index)
279 .any(|(other, other_value)| other == pred_block && other_value != value)
280 {
281 errors.push(ValidationError::at_inst(
282 format!(
283 "phi has conflicting incoming values for predecessor bb{}",
284 pred_block.index()
285 ),
286 block_id,
287 inst_id,
288 ));
289 }
290 }
291 }
292 }
293 }
294
295 if !func.blocks[func.entry_block].predecessors.is_empty() {
297 errors.push(ValidationError::at_block(
298 "entry block must have no predecessors",
299 func.entry_block,
300 ));
301 }
302
303 errors
304 }
305
306 #[must_use]
310 pub fn validate_module(module: &Module) -> Vec<ValidationError> {
311 let mut all = Vec::new();
312 for (id, func) in module.iter_functions() {
313 for mut err in Self::validate_function(func) {
314 err.message = format!("[fn{}] {}", id.index(), err.message);
315 all.push(err);
316 }
317 }
318 all
319 }
320}
321
322#[must_use]
324pub fn validate_function(func: &Function) -> Vec<ValidationError> {
325 Validator::validate_function(func)
326}
327
328#[must_use]
330pub fn validate_module(module: &Module) -> Vec<ValidationError> {
331 Validator::validate_module(module)
332}
333
334pub struct ValidatorAnalysis;
341
342impl AnalysisPass for ValidatorAnalysis {
343 type Result = Vec<ValidationError>;
344
345 fn name(&self) -> &str {
346 "validator"
347 }
348
349 fn run(&self, func: &Function) -> Vec<ValidationError> {
350 Validator::validate_function(func)
351 }
352}
353
354#[cfg(test)]
359mod tests {
360 use super::*;
361 use crate::mir::{BasicBlock, Function, FunctionBuilder, MirType, Terminator};
362 use solar_interface::{ColorChoice, Ident, Session};
363
364 fn with_session<F: FnOnce() + Send>(f: F) {
365 let sess = Session::builder().with_buffer_emitter(ColorChoice::Never).build();
366 sess.enter(f);
367 }
368
369 fn make_func() -> Function {
370 Function::new(Ident::DUMMY)
371 }
372
373 #[test]
374 fn valid_simple_function() {
375 with_session(|| {
376 let mut func = make_func();
377 {
378 let mut b = FunctionBuilder::new(&mut func);
379 let x = b.add_param(MirType::uint256());
380 let one = b.imm_u64(1);
381 let sum = b.add(x, one);
382 b.ret([sum]);
383 }
384 let errors = validate_function(&func);
385 assert!(errors.is_empty(), "expected valid function, got: {errors:#?}");
386 });
387 }
388
389 #[test]
390 fn missing_terminator_is_caught() {
391 with_session(|| {
392 let mut func = make_func();
393 {
395 let mut b = FunctionBuilder::new(&mut func);
396 let _p = b.add_param(MirType::uint256());
397 }
399 let errors = validate_function(&func);
400 assert!(
401 errors.iter().any(|e| e.message.contains("no terminator")),
402 "expected 'no terminator' error, got: {errors:#?}"
403 );
404 });
405 }
406
407 #[test]
408 fn bad_block_reference_is_caught() {
409 with_session(|| {
410 let mut func = make_func();
411 {
412 let mut b = FunctionBuilder::new(&mut func);
413 let x = b.add_param(MirType::uint256());
414 b.ret([x]);
415 }
416 let bad_block = BlockId::from_usize(99);
418 func.blocks[func.entry_block].terminator = Some(Terminator::Jump(bad_block));
419 let errors = validate_function(&func);
420 assert!(
421 errors.iter().any(|e| e.message.contains("nonexistent block")),
422 "expected error about nonexistent block, got: {errors:#?}"
423 );
424 });
425 }
426
427 #[test]
428 fn predecessor_back_link_is_caught() {
429 with_session(|| {
430 let mut func = make_func();
431 let target;
432 {
433 let mut b = FunctionBuilder::new(&mut func);
434 target = b.create_block();
435 b.jump(target);
436 b.switch_to_block(target);
437 b.stop();
438 }
439 assert!(validate_function(&func).is_empty());
440 func.blocks[target].predecessors.clear();
442 let errors = validate_function(&func);
443 assert!(
444 errors.iter().any(|e| e.message.contains("does not list bb0 as a predecessor")),
445 "expected predecessor back-link error, got: {errors:#?}"
446 );
447 });
448 }
449
450 #[test]
451 fn entry_block_with_predecessors_is_caught() {
452 with_session(|| {
453 let mut func = make_func();
454 {
457 let mut b = FunctionBuilder::new(&mut func);
458 b.stop();
459 }
460 func.blocks[func.entry_block].predecessors.push(func.entry_block);
462 let errors = validate_function(&func);
463 assert!(
464 errors.iter().any(|e| e.message.contains("entry block must have no predecessors")),
465 "expected entry-block error, got: {errors:#?}"
466 );
467 });
468 }
469
470 #[test]
471 fn validator_as_analysis_pass() {
472 use crate::pass::AnalysisManager;
473 with_session(|| {
474 let mut func = make_func();
475 {
476 let mut b = FunctionBuilder::new(&mut func);
477 let x = b.add_param(MirType::uint256());
478 b.ret([x]);
479 }
480 let mut am = AnalysisManager::new();
481 let errors = am.get_or_compute(&ValidatorAnalysis, &func);
482 assert!(errors.is_empty());
483 });
484 }
485
486 #[test]
487 fn empty_function_with_just_terminator_is_valid() {
488 with_session(|| {
489 let mut func = make_func();
490 {
491 let mut b = FunctionBuilder::new(&mut func);
492 b.stop();
493 }
494 let errors = validate_function(&func);
495 assert!(errors.is_empty(), "{errors:#?}");
496 });
497 }
498
499 #[test]
500 fn validation_error_display() {
501 let e1 = ValidationError::new("oops");
502 assert_eq!(format!("{e1}"), "oops");
503 let e2 = ValidationError::at_block("oops", BlockId::from_usize(3));
504 assert_eq!(format!("{e2}"), "[bb3] oops");
505 let e3 = ValidationError::at_inst("oops", BlockId::from_usize(3), InstId::from_usize(5));
506 assert_eq!(format!("{e3}"), "[bb3, inst5] oops");
507 }
508
509 #[allow(dead_code)]
511 fn _block_type_reference() -> Option<BasicBlock> {
512 None
513 }
514}