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