cranelift_filetests/
test_verifier.rs

1//! Test command for checking the IR verifier.
2//!
3//! The `test verifier` test command looks for annotations on instructions like this:
4//!
5//! ```clif
6//!     jump block3 ; error: jump to non-existent block
7//! ```
8//!
9//! This annotation means that the verifier is expected to given an error for the jump instruction
10//! containing the substring "jump to non-existent block".
11
12use crate::match_directive::match_directive;
13use crate::subtest::{Context, SubTest};
14use cranelift_codegen::ir::Function;
15use cranelift_codegen::verify_function;
16use cranelift_reader::TestCommand;
17use std::borrow::{Borrow, Cow};
18use std::fmt::Write;
19
20struct TestVerifier;
21
22pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
23    assert_eq!(parsed.command, "verifier");
24    if !parsed.options.is_empty() {
25        anyhow::bail!("No options allowed on {}", parsed);
26    }
27    Ok(Box::new(TestVerifier))
28}
29
30impl SubTest for TestVerifier {
31    fn name(&self) -> &'static str {
32        "verifier"
33    }
34
35    fn needs_verifier(&self) -> bool {
36        // Running the verifier before this test would defeat its purpose.
37        false
38    }
39
40    fn run(&self, func: Cow<Function>, context: &Context) -> anyhow::Result<()> {
41        let func = func.borrow();
42
43        // Scan source annotations for "error:" directives.
44        let mut expected = Vec::new();
45
46        for comment in &context.details.comments {
47            if let Some(tail) = match_directive(comment.text, "error:") {
48                expected.push((comment.entity, tail));
49            }
50        }
51
52        match verify_function(func, context.flags_or_isa()) {
53            Ok(()) if expected.is_empty() => Ok(()),
54            Ok(()) => anyhow::bail!("passed, but expected errors: {:?}", expected),
55
56            Err(ref errors) if expected.is_empty() => {
57                anyhow::bail!("expected no error, but got:\n{}", errors);
58            }
59
60            Err(errors) => {
61                let mut errors = errors.0;
62                let mut msg = String::new();
63
64                // For each expected error, find a suitable match.
65                for expect in expected {
66                    let pos = errors
67                        .iter()
68                        .position(|err| err.location == expect.0 && err.message.contains(expect.1));
69
70                    match pos {
71                        None => {
72                            writeln!(msg, "  expected error {}: {}", expect.0, expect.1).unwrap();
73                        }
74                        Some(pos) => {
75                            errors.swap_remove(pos);
76                        }
77                    }
78                }
79
80                // Report remaining errors.
81                for err in errors {
82                    writeln!(msg, "unexpected error {err}").unwrap();
83                }
84
85                if msg.is_empty() {
86                    Ok(())
87                } else {
88                    anyhow::bail!("{}", msg);
89                }
90            }
91        }
92    }
93}