Improvements to error reporting (#470)
* Fixed error reporting. * Fixed compile time error when wasm feature is disabled. * Fixed valid instructions not being printed in print_function_error. * Fixed errors print_function_error not writing valid instructions after end. * Made multiple checks non-fatal. * verify_global_values is no longer fatal. * Slightly better formatting of errors in pretty_verifier_error.
This commit is contained in:
committed by
Dan Gohman
parent
304134d351
commit
e2badb0ad6
@@ -21,12 +21,12 @@ extern crate serde_derive;
|
||||
#[cfg(feature = "disas")]
|
||||
extern crate capstone;
|
||||
extern crate pretty_env_logger;
|
||||
extern crate term;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "wasm")] {
|
||||
extern crate cranelift_entity;
|
||||
extern crate cranelift_wasm;
|
||||
extern crate term;
|
||||
extern crate wabt;
|
||||
mod wasm;
|
||||
}
|
||||
|
||||
@@ -25,17 +25,23 @@ pub fn pretty_verifier_error<'a>(
|
||||
|
||||
// TODO: Use drain_filter here when it gets stabilized
|
||||
let mut i = 0;
|
||||
let mut wrote_error = false;
|
||||
|
||||
while i != errors.len() {
|
||||
if let ir::entities::AnyEntity::Inst(_) = errors[i].location {
|
||||
i += 1;
|
||||
} else {
|
||||
let err = errors.remove(i);
|
||||
|
||||
writeln!(w, "Miscellaneous error: {}\n", err).unwrap()
|
||||
} else {
|
||||
i += 1;
|
||||
writeln!(w, "verifier at {}", err).unwrap();
|
||||
wrote_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
if wrote_error {
|
||||
w.push('\n');
|
||||
}
|
||||
|
||||
decorate_function(
|
||||
&mut PrettyVerifierError(func_w.unwrap_or(Box::new(PlainWriter)), &mut errors),
|
||||
&mut w,
|
||||
@@ -81,24 +87,41 @@ fn pretty_function_error(
|
||||
) -> fmt::Result {
|
||||
// TODO: Use drain_filter here when it gets stabilized
|
||||
let mut i = 0;
|
||||
let mut printed_instr = false;
|
||||
|
||||
while i != errors.len() {
|
||||
match errors[i].location {
|
||||
ir::entities::AnyEntity::Inst(inst) if inst == cur_inst => {
|
||||
let err = errors.remove(i);
|
||||
|
||||
if !printed_instr {
|
||||
func_w.write_instruction(w, func, isa, cur_inst, indent)?;
|
||||
printed_instr = true;
|
||||
}
|
||||
|
||||
write!(w, "{1:0$}^", indent, "")?;
|
||||
for _c in cur_inst.to_string().chars() {
|
||||
write!(w, "~")?;
|
||||
}
|
||||
writeln!(w, " verifier {}\n", err.to_string())?;
|
||||
writeln!(w, " verifier {}", err.to_string())?;
|
||||
}
|
||||
ir::entities::AnyEntity::Inst(_) => i += 1,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
if printed_instr {
|
||||
w.write_char('\n')?;
|
||||
} else {
|
||||
writeln!(
|
||||
w,
|
||||
"{1:0$}{2}",
|
||||
indent,
|
||||
"",
|
||||
func.dfg.display_inst(cur_inst, isa)
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -230,6 +230,17 @@ impl VerifierErrors {
|
||||
pub fn has_error(&self) -> bool {
|
||||
!self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Return a `VerifierStepResult` that is fatal if at least one error was reported,
|
||||
/// and non-fatal otherwise.
|
||||
#[inline]
|
||||
pub fn as_result(&self) -> VerifierStepResult<()> {
|
||||
if self.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<VerifierError>> for VerifierErrors {
|
||||
@@ -244,6 +255,16 @@ impl Into<Vec<VerifierError>> for VerifierErrors {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<VerifierResult<()>> for VerifierErrors {
|
||||
fn into(self) -> VerifierResult<()> {
|
||||
if self.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for VerifierErrors {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
for err in &self.0 {
|
||||
@@ -309,14 +330,15 @@ impl<'a> Verifier<'a> {
|
||||
fn verify_global_values(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
|
||||
let mut seen = SparseSet::new();
|
||||
|
||||
for gv in self.func.global_values.keys() {
|
||||
'gvs: for gv in self.func.global_values.keys() {
|
||||
seen.clear();
|
||||
seen.insert(gv);
|
||||
|
||||
let mut cur = gv;
|
||||
while let ir::GlobalValueData::Deref { base, .. } = self.func.global_values[cur] {
|
||||
if seen.insert(base).is_some() {
|
||||
return fatal!(errors, gv, "deref cycle: {}", DisplayList(seen.as_slice()));
|
||||
report!(errors, gv, "deref cycle: {}", DisplayList(seen.as_slice()));
|
||||
continue 'gvs;
|
||||
}
|
||||
|
||||
cur = base;
|
||||
@@ -328,11 +350,12 @@ impl<'a> Verifier<'a> {
|
||||
.special_param(ir::ArgumentPurpose::VMContext)
|
||||
.is_none()
|
||||
{
|
||||
return fatal!(errors, cur, "undeclared vmctx reference {}", cur);
|
||||
report!(errors, cur, "undeclared vmctx reference {}", cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid global values shouldn't stop us from verifying the rest of the function
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -437,7 +460,7 @@ impl<'a> Verifier<'a> {
|
||||
// All used values must be attached to something.
|
||||
let original = self.func.dfg.resolve_aliases(arg);
|
||||
if !self.func.dfg.value_is_attached(original) {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"argument {} -> {} is not attached",
|
||||
@@ -448,7 +471,7 @@ impl<'a> Verifier<'a> {
|
||||
}
|
||||
|
||||
for &res in self.func.dfg.inst_results(inst) {
|
||||
self.verify_inst_result(inst, res, errors)?;
|
||||
self.verify_inst_result(inst, res, errors).is_ok();
|
||||
}
|
||||
|
||||
match self.func.dfg[inst] {
|
||||
@@ -594,7 +617,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !self.func.dfg.ext_funcs.is_valid(f) {
|
||||
fatal!(errors, inst, "invalid function reference {}", f)
|
||||
nonfatal!(errors, inst, "invalid function reference {}", f)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -607,7 +630,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !self.func.stack_slots.is_valid(ss) {
|
||||
fatal!(errors, inst, "invalid stack slot {}", ss)
|
||||
nonfatal!(errors, inst, "invalid stack slot {}", ss)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -620,7 +643,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !self.func.global_values.is_valid(gv) {
|
||||
fatal!(errors, inst, "invalid global value {}", gv)
|
||||
nonfatal!(errors, inst, "invalid global value {}", gv)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -633,7 +656,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !self.func.heaps.is_valid(heap) {
|
||||
fatal!(errors, inst, "invalid heap {}", heap)
|
||||
nonfatal!(errors, inst, "invalid heap {}", heap)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -646,7 +669,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !self.func.tables.is_valid(table) {
|
||||
fatal!(errors, inst, "invalid table {}", table)
|
||||
nonfatal!(errors, inst, "invalid table {}", table)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -659,7 +682,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !l.is_valid(&self.func.dfg.value_lists) {
|
||||
fatal!(errors, inst, "invalid value list reference {:?}", l)
|
||||
nonfatal!(errors, inst, "invalid value list reference {:?}", l)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -672,7 +695,7 @@ impl<'a> Verifier<'a> {
|
||||
errors: &mut VerifierErrors,
|
||||
) -> VerifierStepResult<()> {
|
||||
if !self.func.jump_tables.is_valid(j) {
|
||||
fatal!(errors, inst, "invalid jump table reference {}", j)
|
||||
nonfatal!(errors, inst, "invalid jump table reference {}", j)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -686,7 +709,7 @@ impl<'a> Verifier<'a> {
|
||||
) -> VerifierStepResult<()> {
|
||||
let dfg = &self.func.dfg;
|
||||
if !dfg.value_is_valid(v) {
|
||||
fatal!(errors, loc_inst, "invalid value reference {}", v)
|
||||
nonfatal!(errors, loc_inst, "invalid value reference {}", v)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -897,7 +920,7 @@ impl<'a> Verifier<'a> {
|
||||
for (i, &arg) in self.func.dfg.ebb_params(ebb).iter().enumerate() {
|
||||
let arg_type = self.func.dfg.value_type(arg);
|
||||
if arg_type != expected_types[i].value_type {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
ebb,
|
||||
"entry block parameter {} expected to have type {}, got {}",
|
||||
@@ -908,7 +931,8 @@ impl<'a> Verifier<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
errors.as_result()
|
||||
}
|
||||
|
||||
fn typecheck(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
|
||||
@@ -920,7 +944,7 @@ impl<'a> Verifier<'a> {
|
||||
let ctrl_type = self.func.dfg.ctrl_typevar(inst);
|
||||
|
||||
if !value_typeset.contains(ctrl_type) {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"has an invalid controlling type {}",
|
||||
@@ -935,11 +959,12 @@ impl<'a> Verifier<'a> {
|
||||
types::VOID
|
||||
};
|
||||
|
||||
self.typecheck_results(inst, ctrl_type, errors)?;
|
||||
self.typecheck_fixed_args(inst, ctrl_type, errors)?;
|
||||
self.typecheck_variable_args(inst, errors)?;
|
||||
self.typecheck_return(inst, errors)?;
|
||||
self.typecheck_special(inst, ctrl_type, errors)?;
|
||||
// Typechecking instructions is never fatal
|
||||
self.typecheck_results(inst, ctrl_type, errors).is_ok();
|
||||
self.typecheck_fixed_args(inst, ctrl_type, errors).is_ok();
|
||||
self.typecheck_variable_args(inst, errors).is_ok();
|
||||
self.typecheck_return(inst, errors).is_ok();
|
||||
self.typecheck_special(inst, ctrl_type, errors).is_ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -956,7 +981,7 @@ impl<'a> Verifier<'a> {
|
||||
let expected_type = self.func.dfg.compute_result_type(inst, i, ctrl_type);
|
||||
if let Some(expected_type) = expected_type {
|
||||
if result_type != expected_type {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"expected result {} ({}) to have type {}, found {}",
|
||||
@@ -967,14 +992,14 @@ impl<'a> Verifier<'a> {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return fatal!(errors, inst, "has more result values than expected");
|
||||
return nonfatal!(errors, inst, "has more result values than expected");
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// There aren't any more result types left.
|
||||
if self.func.dfg.compute_result_type(inst, i, ctrl_type) != None {
|
||||
return fatal!(errors, inst, "has fewer result values than expected");
|
||||
return nonfatal!(errors, inst, "has fewer result values than expected");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -992,7 +1017,7 @@ impl<'a> Verifier<'a> {
|
||||
match constraints.value_argument_constraint(i, ctrl_type) {
|
||||
ResolvedConstraint::Bound(expected_type) => {
|
||||
if arg_type != expected_type {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"arg {} ({}) has type {}, expected {}",
|
||||
@@ -1005,7 +1030,7 @@ impl<'a> Verifier<'a> {
|
||||
}
|
||||
ResolvedConstraint::Free(type_set) => {
|
||||
if !type_set.contains(arg_type) {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"arg {} ({}) with type {} failed to satisfy type set {:?}",
|
||||
@@ -1040,7 +1065,7 @@ impl<'a> Verifier<'a> {
|
||||
for (_, ebb) in self.func.jump_tables[table].entries() {
|
||||
let arg_count = self.func.dfg.num_ebb_params(ebb);
|
||||
if arg_count != 0 {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"takes no arguments, but had target {} with {} arguments",
|
||||
@@ -1094,7 +1119,7 @@ impl<'a> Verifier<'a> {
|
||||
let arg = variable_args[i];
|
||||
let arg_type = self.func.dfg.value_type(arg);
|
||||
if expected_type != arg_type {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"arg {} ({}) has type {}, expected {}",
|
||||
@@ -1107,7 +1132,7 @@ impl<'a> Verifier<'a> {
|
||||
i += 1;
|
||||
}
|
||||
if i != variable_args.len() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"mismatched argument count for `{}`: got {}, expected {}",
|
||||
@@ -1199,7 +1224,7 @@ impl<'a> Verifier<'a> {
|
||||
let args = self.func.dfg.inst_variable_args(inst);
|
||||
let expected_types = &self.func.signature.returns;
|
||||
if args.len() != expected_types.len() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"arguments of return must match function signature"
|
||||
@@ -1208,7 +1233,7 @@ impl<'a> Verifier<'a> {
|
||||
for (i, (&arg, &expected_type)) in args.iter().zip(expected_types).enumerate() {
|
||||
let arg_type = self.func.dfg.value_type(arg);
|
||||
if arg_type != expected_type.value_type {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"arg {} ({}) has type {}, must match function signature of {}",
|
||||
@@ -1236,7 +1261,7 @@ impl<'a> Verifier<'a> {
|
||||
match opcode {
|
||||
Opcode::Bextend | Opcode::Uextend | Opcode::Sextend | Opcode::Fpromote => {
|
||||
if arg_type.lane_count() != ctrl_type.lane_count() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"input {} and output {} must have same number of lanes",
|
||||
@@ -1245,7 +1270,7 @@ impl<'a> Verifier<'a> {
|
||||
);
|
||||
}
|
||||
if arg_type.lane_bits() >= ctrl_type.lane_bits() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"input {} must be smaller than output {}",
|
||||
@@ -1256,7 +1281,7 @@ impl<'a> Verifier<'a> {
|
||||
}
|
||||
Opcode::Breduce | Opcode::Ireduce | Opcode::Fdemote => {
|
||||
if arg_type.lane_count() != ctrl_type.lane_count() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"input {} and output {} must have same number of lanes",
|
||||
@@ -1265,7 +1290,7 @@ impl<'a> Verifier<'a> {
|
||||
);
|
||||
}
|
||||
if arg_type.lane_bits() <= ctrl_type.lane_bits() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"input {} must be larger than output {}",
|
||||
@@ -1296,22 +1321,24 @@ impl<'a> Verifier<'a> {
|
||||
|
||||
let missing_succs: Vec<Ebb> = expected_succs.difference(&got_succs).cloned().collect();
|
||||
if !missing_succs.is_empty() {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
ebb,
|
||||
"cfg lacked the following successor(s) {:?}",
|
||||
missing_succs
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let excess_succs: Vec<Ebb> = got_succs.difference(&expected_succs).cloned().collect();
|
||||
if !excess_succs.is_empty() {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
ebb,
|
||||
"cfg had unexpected successor(s) {:?}",
|
||||
excess_succs
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
expected_preds.extend(
|
||||
@@ -1323,22 +1350,24 @@ impl<'a> Verifier<'a> {
|
||||
|
||||
let missing_preds: Vec<Inst> = expected_preds.difference(&got_preds).cloned().collect();
|
||||
if !missing_preds.is_empty() {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
ebb,
|
||||
"cfg lacked the following predecessor(s) {:?}",
|
||||
missing_preds
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let excess_preds: Vec<Inst> = got_preds.difference(&expected_preds).cloned().collect();
|
||||
if !excess_preds.is_empty() {
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
ebb,
|
||||
"cfg had unexpected predecessor(s) {:?}",
|
||||
excess_preds
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
expected_succs.clear();
|
||||
@@ -1346,7 +1375,7 @@ impl<'a> Verifier<'a> {
|
||||
expected_preds.clear();
|
||||
got_preds.clear();
|
||||
}
|
||||
Ok(())
|
||||
errors.as_result()
|
||||
}
|
||||
|
||||
/// If the verifier has been set up with an ISA, make sure that the recorded encoding for the
|
||||
@@ -1375,7 +1404,7 @@ impl<'a> Verifier<'a> {
|
||||
).peekable();
|
||||
|
||||
if encodings.peek().is_none() {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"Instruction failed to re-encode {}",
|
||||
@@ -1403,7 +1432,7 @@ impl<'a> Verifier<'a> {
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"encoding {} should be {}{}",
|
||||
@@ -1446,7 +1475,7 @@ impl<'a> Verifier<'a> {
|
||||
// Provide the ISA default encoding as a hint.
|
||||
match self.func.encode(inst, isa) {
|
||||
Ok(enc) => {
|
||||
return fatal!(
|
||||
return nonfatal!(
|
||||
errors,
|
||||
inst,
|
||||
"{} must have an encoding (e.g., {})",
|
||||
@@ -1454,7 +1483,7 @@ impl<'a> Verifier<'a> {
|
||||
isa.encoding_info().display(enc)
|
||||
)
|
||||
}
|
||||
Err(_) => return fatal!(errors, inst, "{} must have an encoding", text),
|
||||
Err(_) => return nonfatal!(errors, inst, "{} must have an encoding", text),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1468,7 +1497,7 @@ impl<'a> Verifier<'a> {
|
||||
let inst = self.func.layout.last_inst(ebb).unwrap();
|
||||
if self.func.dfg[inst].opcode().is_return() && Some(ebb) != self.func.layout.last_ebb()
|
||||
{
|
||||
return fatal!(
|
||||
report!(
|
||||
errors,
|
||||
inst,
|
||||
"Internal return not allowed with return_at_end=1"
|
||||
@@ -1476,7 +1505,7 @@ impl<'a> Verifier<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
errors.as_result()
|
||||
}
|
||||
|
||||
pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult<()> {
|
||||
|
||||
Reference in New Issue
Block a user