From 45bbc53850cd889e82665a825a2f1002b1c50c20 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 16 Mar 2023 09:22:21 -0700 Subject: [PATCH 01/21] analyze: keep a copy of the callgraph in GlobalAnalysisCtxt --- c2rust-analyze/src/context.rs | 5 +++++ c2rust-analyze/src/main.rs | 26 ++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 4cf4123488..b366e289ab 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -272,6 +272,9 @@ pub struct GlobalAnalysisCtxt<'tcx> { ptr_info: GlobalPointerTable, + /// Map from a function to all of its callers. + pub fn_callers: HashMap>, + pub fn_sigs: HashMap>, pub field_ltys: HashMap>, @@ -522,6 +525,7 @@ impl<'tcx> GlobalAnalysisCtxt<'tcx> { tcx, lcx: LabeledTyCtxt::new(tcx), ptr_info: GlobalPointerTable::empty(), + fn_callers: HashMap::new(), fn_sigs: HashMap::new(), field_ltys: HashMap::new(), static_tys: HashMap::new(), @@ -566,6 +570,7 @@ impl<'tcx> GlobalAnalysisCtxt<'tcx> { tcx: _, lcx, ref mut ptr_info, + fn_callers: _, ref mut fn_sigs, ref mut field_ltys, ref mut static_tys, diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index ad33db90f0..85ce85cb5b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -300,12 +300,25 @@ fn run(tcx: TyCtxt) { // Follow a postorder traversal, so that callers are visited after their callees. This means // callee signatures will usually be up to date when we visit the call site. - let all_fn_ldids = fn_body_owners_postorder(tcx); + let (all_fn_ldids, fn_callers) = fn_body_owners_postorder(tcx); eprintln!("callgraph traversal order:"); for &ldid in &all_fn_ldids { eprintln!(" {:?}", ldid); } + gacx.fn_callers = fn_callers + .into_iter() + .map(|(ldid, caller_ldids)| { + ( + ldid.to_def_id(), + caller_ldids + .into_iter() + .map(|caller_ldid| caller_ldid.to_def_id()) + .collect(), + ) + }) + .collect(); + // Assign global `PointerId`s for all pointers that appear in function signatures. for &ldid in &all_fn_ldids { let sig = tcx.fn_sig(ldid.to_def_id()); @@ -741,10 +754,14 @@ fn all_static_items(tcx: TyCtxt) -> Vec { } /// Return all `LocalDefId`s for all `fn`s that are `body_owners`, ordered according to a postorder -/// traversal of the graph of references between bodies. -fn fn_body_owners_postorder(tcx: TyCtxt) -> Vec { +/// traversal of the graph of references between bodies. Also returns the callgraph itself, in the +/// form of a map from callee `LocalDefId` to a set of caller `LocalDefId`s. +fn fn_body_owners_postorder( + tcx: TyCtxt, +) -> (Vec, HashMap>) { let mut seen = HashSet::new(); let mut order = Vec::new(); + let mut callers = HashMap::<_, HashSet<_>>::new(); enum Visit { Pre(LocalDefId), Post(LocalDefId), @@ -773,6 +790,7 @@ fn fn_body_owners_postorder(tcx: TyCtxt) -> Vec { stack.push(Visit::Post(ldid)); for_each_callee(tcx, ldid, |callee_ldid| { stack.push(Visit::Pre(callee_ldid)); + callers.entry(callee_ldid).or_default().insert(ldid); }); } } @@ -783,7 +801,7 @@ fn fn_body_owners_postorder(tcx: TyCtxt) -> Vec { } } - order + (order, callers) } fn for_each_callee(tcx: TyCtxt, ldid: LocalDefId, f: impl FnMut(LocalDefId)) { From e284b8f3f36d12b7e7ec2297c241ad1571ebfbf9 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 16 Mar 2023 09:52:05 -0700 Subject: [PATCH 02/21] analyze: catch panics during analysis and recover --- c2rust-analyze/src/context.rs | 25 +++- c2rust-analyze/src/main.rs | 124 +++++++++++++----- c2rust-analyze/tests/filecheck.rs | 1 + c2rust-analyze/tests/filecheck/catch_panic.rs | 29 ++++ 4 files changed, 144 insertions(+), 35 deletions(-) create mode 100644 c2rust-analyze/tests/filecheck/catch_panic.rs diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index b366e289ab..78d8016499 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -25,7 +25,7 @@ use rustc_middle::ty::{ }; use rustc_type_ir::RegionKind::{ReEarlyBound, ReStatic}; use std::collections::HashMap; -use std::fmt::Debug; +use std::fmt::{Debug, Display}; use std::ops::Index; bitflags! { @@ -276,6 +276,9 @@ pub struct GlobalAnalysisCtxt<'tcx> { pub fn_callers: HashMap>, pub fn_sigs: HashMap>, + /// `DefId`s of functions where analysis failed, and a `String` explaining the reason for each + /// failure. + pub fns_failed: HashMap, pub field_ltys: HashMap>, @@ -527,6 +530,7 @@ impl<'tcx> GlobalAnalysisCtxt<'tcx> { ptr_info: GlobalPointerTable::empty(), fn_callers: HashMap::new(), fn_sigs: HashMap::new(), + fns_failed: HashMap::new(), field_ltys: HashMap::new(), static_tys: HashMap::new(), addr_of_static: HashMap::new(), @@ -572,6 +576,7 @@ impl<'tcx> GlobalAnalysisCtxt<'tcx> { ref mut ptr_info, fn_callers: _, ref mut fn_sigs, + fns_failed: _, ref mut field_ltys, ref mut static_tys, ref mut addr_of_static, @@ -625,6 +630,24 @@ impl<'tcx> GlobalAnalysisCtxt<'tcx> { self.assign_pointer_to_field(field); } } + + pub fn fn_failed(&mut self, did: DefId) -> bool { + self.fns_failed.contains_key(&did) + } + + pub fn mark_fn_failed(&mut self, did: DefId, reason: impl Display) { + if self.fns_failed.contains_key(&did) { + return; + } + + self.fns_failed.insert(did, reason.to_string()); + + // This is the first time marking `did` as failed, so also mark all of its callers. + let callers = self.fn_callers.get(&did).cloned().unwrap_or(Vec::new()); + for caller in callers { + self.mark_fn_failed(caller, format_args!("analysis failed on callee {:?}", did)); + } + } } impl<'a, 'tcx> AnalysisCtxt<'a, 'tcx> { diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 85ce85cb5b..7ddaa6403c 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -35,10 +35,12 @@ use rustc_middle::mir::{ }; use rustc_middle::ty::{Ty, TyCtxt, TyKind, WithOptConstParam}; use rustc_span::Span; +use std::any::Any; use std::collections::{HashMap, HashSet}; use std::env; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut, Index}; +use std::panic::{self, AssertUnwindSafe}; mod borrowck; mod c_void_casts; @@ -366,6 +368,11 @@ fn run(tcx: TyCtxt) { // computed during this the process is kept around for use in later passes. let mut global_equiv = GlobalEquivSet::new(gacx.num_pointers()); for &ldid in &all_fn_ldids { + // The function might already be marked as failed if one of its callees previously failed. + if gacx.fn_failed(ldid.to_def_id()) { + continue; + } + let ldid_const = WithOptConstParam::unknown(ldid); let mir = tcx.mir_built(ldid_const); let mir = mir.borrow(); @@ -373,32 +380,43 @@ fn run(tcx: TyCtxt) { let mut acx = gacx.function_context(&mir); - // Assign PointerIds to local types - assert!(acx.local_tys.is_empty()); - acx.local_tys = IndexVec::with_capacity(mir.local_decls.len()); - for (local, decl) in mir.local_decls.iter_enumerated() { - // TODO: set PointerInfo::ANNOTATED for the parts of the type with user annotations - let lty = match mir.local_kind(local) { - LocalKind::Var | LocalKind::Temp => acx.assign_pointer_ids(decl.ty), - LocalKind::Arg => { - debug_assert!(local.as_usize() >= 1 && local.as_usize() <= mir.arg_count); - lsig.inputs[local.as_usize() - 1] - } - LocalKind::ReturnPointer => lsig.output, - }; - let l = acx.local_tys.push(lty); - assert_eq!(local, l); + let r = panic::catch_unwind(AssertUnwindSafe(|| { + // Assign PointerIds to local types + assert!(acx.local_tys.is_empty()); + acx.local_tys = IndexVec::with_capacity(mir.local_decls.len()); + for (local, decl) in mir.local_decls.iter_enumerated() { + // TODO: set PointerInfo::ANNOTATED for the parts of the type with user annotations + let lty = match mir.local_kind(local) { + LocalKind::Var | LocalKind::Temp => acx.assign_pointer_ids(decl.ty), + LocalKind::Arg => { + debug_assert!(local.as_usize() >= 1 && local.as_usize() <= mir.arg_count); + lsig.inputs[local.as_usize() - 1] + } + LocalKind::ReturnPointer => lsig.output, + }; + let l = acx.local_tys.push(lty); + assert_eq!(local, l); + + let ptr = acx.new_pointer(PointerInfo::empty()); + let l = acx.addr_of_local.push(ptr); + assert_eq!(local, l); + } - let ptr = acx.new_pointer(PointerInfo::empty()); - let l = acx.addr_of_local.push(ptr); - assert_eq!(local, l); - } + label_rvalue_tys(&mut acx, &mir); + update_pointer_info(&mut acx, &mir); + + dataflow::generate_constraints(&acx, &mir) + })); - label_rvalue_tys(&mut acx, &mir); - update_pointer_info(&mut acx, &mir); + let (dataflow, equiv_constraints) = match r { + Ok(x) => x, + Err(e) => { + gacx.mark_fn_failed(ldid.to_def_id(), panic_to_string(e)); + continue; + } + }; // Compute local equivalence classes and dataflow constraints. - let (dataflow, equiv_constraints) = dataflow::generate_constraints(&acx, &mir); let mut local_equiv = LocalEquivSet::new(acx.num_pointers()); let mut equiv = global_equiv.and_mut(&mut local_equiv); for (a, b) in equiv_constraints { @@ -419,6 +437,10 @@ fn run(tcx: TyCtxt) { gacx.remap_pointers(&global_equiv_map, global_counter); for &ldid in &all_fn_ldids { + if gacx.fn_failed(ldid.to_def_id()) { + continue; + } + let info = func_info.get_mut(&ldid).unwrap(); let (local_counter, local_equiv_map) = info.local_equiv.renumber(&global_equiv_map); eprintln!("local_equiv_map = {local_equiv_map:?}"); @@ -481,6 +503,10 @@ fn run(tcx: TyCtxt) { loop_count += 1; let old_gasn = gasn.clone(); for &ldid in &all_fn_ldids { + if gacx.fn_failed(ldid.to_def_id()) { + continue; + } + let info = func_info.get_mut(&ldid).unwrap(); let ldid_const = WithOptConstParam::unknown(ldid); let name = tcx.item_name(ldid.to_def_id()); @@ -491,18 +517,27 @@ fn run(tcx: TyCtxt) { let acx = gacx.function_context_with_data(&mir, info.acx_data.take()); let mut asn = gasn.and(&mut info.lasn); - // `dataflow.propagate` and `borrowck_mir` both run until the assignment converges on a - // fixpoint, so there's no need to do multiple iterations here. - info.dataflow.propagate(&mut asn.perms_mut()); - - borrowck::borrowck_mir( - &acx, - &info.dataflow, - &mut asn.perms_mut(), - name.as_str(), - &mir, - field_ltys, - ); + let r = panic::catch_unwind(AssertUnwindSafe(|| { + // `dataflow.propagate` and `borrowck_mir` both run until the assignment converges + // on a fixpoint, so there's no need to do multiple iterations here. + info.dataflow.propagate(&mut asn.perms_mut()); + + borrowck::borrowck_mir( + &acx, + &info.dataflow, + &mut asn.perms_mut(), + name.as_str(), + &mir, + field_ltys, + ); + })); + match r { + Ok(()) => {} + Err(e) => { + gacx.mark_fn_failed(ldid.to_def_id(), panic_to_string(e)); + continue; + } + } info.acx_data.set(acx.into_data()); } @@ -614,6 +649,13 @@ fn run(tcx: TyCtxt) { // Apply rewrite to all functions at once. rewrite::apply_rewrites(tcx, all_rewrites); + + // Report errors that were caught previously + for ldid in tcx.hir().body_owners() { + if let Some(reason) = gacx.fns_failed.get(&ldid.to_def_id()) { + eprintln!("analysis of {:?} failed: {:?}", ldid, reason); + } + } } trait AssignPointerIds<'tcx> { @@ -840,6 +882,20 @@ fn for_each_callee(tcx: TyCtxt, ldid: LocalDefId, f: impl FnMut(LocalDefId)) { CalleeVisitor { tcx, mir, f }.visit_body(mir); } +fn panic_to_string(e: Box) -> String { + let e = match e.downcast::<&'static str>() { + Ok(s) => return s.to_string(), + Err(e) => e, + }; + + let e = match e.downcast::() { + Ok(s) => return *s, + Err(e) => e, + }; + + format!("unknown error: {:?}", e.type_id()) +} + struct AnalysisCallbacks; impl rustc_driver::Callbacks for AnalysisCallbacks { diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index e3c732b0ca..51df9b1b6a 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -40,6 +40,7 @@ define_tests! { as_ptr, call1, cast, + catch_panic, cell, clone1, extern_fn1, diff --git a/c2rust-analyze/tests/filecheck/catch_panic.rs b/c2rust-analyze/tests/filecheck/catch_panic.rs new file mode 100644 index 0000000000..6d9856e5bc --- /dev/null +++ b/c2rust-analyze/tests/filecheck/catch_panic.rs @@ -0,0 +1,29 @@ +// This test deliberately exercises unsupported code to check that c2rust-analyze can recover from +// panics during the analysis. + +use std::ptr::NonNull; + +// CHECK: final labeling for "good" +unsafe fn good(p: *mut u8) { + *p = 1; +} + +// Analysis of `bad` should fail because it calls an unsupported library function involving raw +// pointers, namely `NonNull::as_ptr`. +// CHECK-NOT: final labeling for "bad" +unsafe fn bad(p: NonNull) { + *p.as_ptr() = 1; +} + +// Analysis of `bad2` should also fail because it has a callee on which analysis failed. +// CHECK-NOT: final labeling for "bad2" +unsafe fn bad2(p: NonNull) { + bad(p); +} + +// CHECK: analysis of DefId({{.*}}::bad) failed: +// CHECK-SAME: UnknownDef +// CHECK-SAME: NonNull::::as_ptr + +// CHECK: analysis of DefId({{.*}}::bad2) failed: +// CHECK-SAME: analysis failed on callee DefId({{.*}}::bad) From dbdf047affe808f30cf6e9949cbebb0b75baffbf Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 28 Mar 2023 11:33:20 -0700 Subject: [PATCH 03/21] analyze: handle panics around rewriting --- c2rust-analyze/src/main.rs | 43 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 7ddaa6403c..d4653adab9 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -558,6 +558,11 @@ fn run(tcx: TyCtxt) { Some(x) => x, None => continue, }; + + if gacx.fn_failed(ldid.to_def_id()) { + continue; + } + let ldid_const = WithOptConstParam::unknown(ldid); let name = tcx.item_name(ldid.to_def_id()); let mir = tcx.mir_built(ldid_const); @@ -589,21 +594,31 @@ fn run(tcx: TyCtxt) { rewrite::dump_rewritten_local_tys(&acx, &asn, &mir, describe_local); eprintln!(); - let hir_body_id = tcx.hir().body_owned_by(ldid); - let expr_rewrites = rewrite::gen_expr_rewrites(&acx, &asn, &mir, hir_body_id); - let ty_rewrites = rewrite::gen_ty_rewrites(&acx, &asn, &mir, ldid); - // Print rewrites - eprintln!( - "\ngenerated {} expr rewrites + {} ty rewrites for {:?}:", - expr_rewrites.len(), - ty_rewrites.len(), - name - ); - for &(span, ref rw) in expr_rewrites.iter().chain(ty_rewrites.iter()) { - eprintln!(" {}: {}", describe_span(tcx, span), rw); + + let r = panic::catch_unwind(AssertUnwindSafe(|| { + let hir_body_id = tcx.hir().body_owned_by(ldid); + let expr_rewrites = rewrite::gen_expr_rewrites(&acx, &asn, &mir, hir_body_id); + let ty_rewrites = rewrite::gen_ty_rewrites(&acx, &asn, &mir, ldid); + // Print rewrites + eprintln!( + "\ngenerated {} expr rewrites + {} ty rewrites for {:?}:", + expr_rewrites.len(), + ty_rewrites.len(), + name + ); + for &(span, ref rw) in expr_rewrites.iter().chain(ty_rewrites.iter()) { + eprintln!(" {}: {}", describe_span(tcx, span), rw); + } + all_rewrites.extend(expr_rewrites); + all_rewrites.extend(ty_rewrites); + })); + match r { + Ok(()) => {} + Err(e) => { + gacx.mark_fn_failed(ldid.to_def_id(), panic_to_string(e)); + continue; + } } - all_rewrites.extend(expr_rewrites); - all_rewrites.extend(ty_rewrites); } // Print results for `static` items. From f9a28acfec31cbab91adfa7f633001fb28189071 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 28 Mar 2023 16:16:55 -0700 Subject: [PATCH 04/21] analyze: report caught panics in more detail --- Cargo.lock | 1 + c2rust-analyze/Cargo.toml | 1 + c2rust-analyze/src/context.rs | 12 ++- c2rust-analyze/src/main.rs | 47 +++++++----- c2rust-analyze/src/panic_detail.rs | 118 +++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 c2rust-analyze/src/panic_detail.rs diff --git a/Cargo.lock b/Cargo.lock index da8281698b..b99bac720a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,6 +205,7 @@ name = "c2rust-analyze" version = "0.18.0" dependencies = [ "assert_matches", + "backtrace", "bitflags", "c2rust-build-paths", "clap 4.2.7", diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index 0e5fcbef92..9ca8988e77 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -19,6 +19,7 @@ assert_matches = "1.5.0" indexmap = "1.9.2" env_logger = "0.10.0" log = "0.4.17" +backtrace = "0.3.67" [build-dependencies] c2rust-build-paths = { path = "../c2rust-build-paths", version = "0.18.0" } diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 78d8016499..c8016ade7a 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -1,6 +1,7 @@ use crate::borrowck::{AdtMetadata, FieldMetadata, OriginArg, OriginParam}; use crate::c_void_casts::CVoidCasts; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; +use crate::panic_detail::PanicDetail; use crate::pointer_id::{ GlobalPointerTable, LocalPointerTable, NextGlobalPointerId, NextLocalPointerId, PointerTable, PointerTableMut, @@ -278,7 +279,7 @@ pub struct GlobalAnalysisCtxt<'tcx> { pub fn_sigs: HashMap>, /// `DefId`s of functions where analysis failed, and a `String` explaining the reason for each /// failure. - pub fns_failed: HashMap, + pub fns_failed: HashMap, pub field_ltys: HashMap>, @@ -635,17 +636,20 @@ impl<'tcx> GlobalAnalysisCtxt<'tcx> { self.fns_failed.contains_key(&did) } - pub fn mark_fn_failed(&mut self, did: DefId, reason: impl Display) { + pub fn mark_fn_failed(&mut self, did: DefId, detail: PanicDetail) { if self.fns_failed.contains_key(&did) { return; } - self.fns_failed.insert(did, reason.to_string()); + self.fns_failed.insert(did, detail); // This is the first time marking `did` as failed, so also mark all of its callers. let callers = self.fn_callers.get(&did).cloned().unwrap_or(Vec::new()); for caller in callers { - self.mark_fn_failed(caller, format_args!("analysis failed on callee {:?}", did)); + self.mark_fn_failed( + caller, + PanicDetail::new(format!("analysis failed on callee {:?}", did)), + ); } } } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index d4653adab9..a6818e8c47 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -49,6 +49,7 @@ mod dataflow; mod equiv; mod labeled_ty; mod log; +mod panic_detail; mod pointer_id; mod rewrite; mod trivial; @@ -411,7 +412,7 @@ fn run(tcx: TyCtxt) { let (dataflow, equiv_constraints) = match r { Ok(x) => x, Err(e) => { - gacx.mark_fn_failed(ldid.to_def_id(), panic_to_string(e)); + gacx.mark_fn_failed(ldid.to_def_id(), panic_detail::catch(&e)); continue; } }; @@ -534,7 +535,7 @@ fn run(tcx: TyCtxt) { match r { Ok(()) => {} Err(e) => { - gacx.mark_fn_failed(ldid.to_def_id(), panic_to_string(e)); + gacx.mark_fn_failed(ldid.to_def_id(), panic_detail::catch(&e)); continue; } } @@ -615,7 +616,7 @@ fn run(tcx: TyCtxt) { match r { Ok(()) => {} Err(e) => { - gacx.mark_fn_failed(ldid.to_def_id(), panic_to_string(e)); + gacx.mark_fn_failed(ldid.to_def_id(), panic_detail::catch(&e)); continue; } } @@ -666,11 +667,32 @@ fn run(tcx: TyCtxt) { rewrite::apply_rewrites(tcx, all_rewrites); // Report errors that were caught previously + eprintln!("\nerror details:"); for ldid in tcx.hir().body_owners() { - if let Some(reason) = gacx.fns_failed.get(&ldid.to_def_id()) { - eprintln!("analysis of {:?} failed: {:?}", ldid, reason); + if let Some(detail) = gacx.fns_failed.get(&ldid.to_def_id()) { + if !detail.has_backtrace() { + continue; + } + eprintln!("\nerror in {:?}:\n{}", ldid, detail.to_string_full()); } } + + eprintln!("\nerror summary:"); + for ldid in tcx.hir().body_owners() { + if let Some(detail) = gacx.fns_failed.get(&ldid.to_def_id()) { + eprintln!( + "analysis of {:?} failed: {}", + ldid, + detail.to_string_short() + ); + } + } + + eprintln!( + "\nsaw errors in {} / {} functions", + gacx.fns_failed.len(), + all_fn_ldids.len() + ); } trait AssignPointerIds<'tcx> { @@ -897,20 +919,6 @@ fn for_each_callee(tcx: TyCtxt, ldid: LocalDefId, f: impl FnMut(LocalDefId)) { CalleeVisitor { tcx, mir, f }.visit_body(mir); } -fn panic_to_string(e: Box) -> String { - let e = match e.downcast::<&'static str>() { - Ok(s) => return s.to_string(), - Err(e) => e, - }; - - let e = match e.downcast::() { - Ok(s) => return *s, - Err(e) => e, - }; - - format!("unknown error: {:?}", e.type_id()) -} - struct AnalysisCallbacks; impl rustc_driver::Callbacks for AnalysisCallbacks { @@ -928,6 +936,7 @@ impl rustc_driver::Callbacks for AnalysisCallbacks { fn main() -> rustc_interface::interface::Result<()> { init_logger(); + panic::set_hook(Box::new(panic_detail::panic_hook)); let args = env::args().collect::>(); rustc_driver::RunCompiler::new(&args, &mut AnalysisCallbacks).run() } diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs new file mode 100644 index 0000000000..c4dd54a54f --- /dev/null +++ b/c2rust-analyze/src/panic_detail.rs @@ -0,0 +1,118 @@ +use backtrace::Backtrace; +use log::warn; +use std::any::Any; +use std::cell::Cell; +use std::fmt::Write as _; +use std::panic::{Location, PanicInfo}; + +#[derive(Clone, Debug)] +pub struct PanicDetail { + msg: String, + loc: Option, + relevant_loc: Option, + backtrace: Option, +} + +impl PanicDetail { + pub fn new(msg: String) -> PanicDetail { + PanicDetail { + msg, + loc: None, + relevant_loc: None, + backtrace: None, + } + } + + pub fn has_backtrace(&self) -> bool { + self.backtrace.is_some() + } + + pub fn to_string_short(&self) -> String { + let loc_str = self.relevant_loc.as_ref().map_or("[unknown]", |s| &*s); + format!("{}: {}", loc_str, self.msg.trim()) + } + + pub fn to_string_full(&self) -> String { + let mut s = String::new(); + let loc_str = self.loc.as_ref().map_or("[unknown]", |s| &*s); + writeln!(s, "panic at {}: {}", loc_str, self.msg).unwrap(); + if let Some(ref bt) = self.backtrace { + writeln!(s, "{:?}", bt).unwrap(); + } + s + } +} + +thread_local! { + static CURRENT_PANIC_DETAIL: Cell> = Cell::new(None); +} + +pub fn panic_hook(info: &PanicInfo) { + let bt = Backtrace::new(); + let detail = PanicDetail { + msg: panic_to_string(info.payload()), + loc: info.location().map(|l| l.to_string()), + relevant_loc: guess_relevant_loc(&bt), + backtrace: Some(bt), + }; + let old = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(Some(detail))); + if let Some(old) = old { + warn!("discarding old panic detail: {:?}", old); + } +} + +pub fn take_current() -> Option { + CURRENT_PANIC_DETAIL.with(|cell| cell.take()) +} + +pub fn catch(e: &(dyn Any + Send + 'static)) -> PanicDetail { + take_current().unwrap_or_else(|| { + let msg = panic_to_string(e); + warn!("missing panic detail; caught message {:?}", msg); + PanicDetail::new(msg) + }) +} + +fn guess_relevant_loc(bt: &Backtrace) -> Option { + for frame in bt.frames() { + for symbol in frame.symbols() { + let name = match symbol.name() { + Some(x) => x.to_string(), + None => continue, + }; + if name.starts_with("c2rust_analyze::dataflow") + || name.starts_with("c2rust_analyze::borrowck") + || name.starts_with("c2rust_analyze::rewrite") + || name.contains("type_of_rvalue") + || name.contains("lty_project") + { + let filename_str = match symbol.filename() { + Some(x) => x.display().to_string(), + None => "[unknown]".to_string(), + }; + return Some(format!( + "{} @ {}:{}:{}", + name, + filename_str, + symbol.lineno().unwrap_or(0), + symbol.colno().unwrap_or(0) + )); + } + } + } + None +} + +fn panic_to_string(e: &(dyn Any + Send + 'static)) -> String { + match e.downcast_ref::<&'static str>() { + Some(s) => return s.to_string(), + None => {} + } + + match e.downcast_ref::() { + Some(s) => return (*s).clone(), + None => {} + } + + format!("unknown error: {:?}", e.type_id()) +} From e75a6cf511e06642b665328f9e884a4f89ba5cbb Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 Mar 2023 10:40:01 -0700 Subject: [PATCH 05/21] analyze: fix warnings --- c2rust-analyze/src/context.rs | 2 +- c2rust-analyze/src/main.rs | 1 - c2rust-analyze/src/panic_detail.rs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index c8016ade7a..55b636139f 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -26,7 +26,7 @@ use rustc_middle::ty::{ }; use rustc_type_ir::RegionKind::{ReEarlyBound, ReStatic}; use std::collections::HashMap; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::ops::Index; bitflags! { diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index a6818e8c47..0ba6f08a1b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -35,7 +35,6 @@ use rustc_middle::mir::{ }; use rustc_middle::ty::{Ty, TyCtxt, TyKind, WithOptConstParam}; use rustc_span::Span; -use std::any::Any; use std::collections::{HashMap, HashSet}; use std::env; use std::fmt::{Debug, Display}; diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index c4dd54a54f..6899810955 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -3,7 +3,7 @@ use log::warn; use std::any::Any; use std::cell::Cell; use std::fmt::Write as _; -use std::panic::{Location, PanicInfo}; +use std::panic::PanicInfo; #[derive(Clone, Debug)] pub struct PanicDetail { From 7ac897d8a45a7be6da58e3f7fc75c823e72cffa9 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 Mar 2023 11:09:35 -0700 Subject: [PATCH 06/21] analyze: PanicDetail: use primary loc if no relevant loc is found --- c2rust-analyze/src/panic_detail.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index 6899810955..b2a8ebdf51 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -28,7 +28,11 @@ impl PanicDetail { } pub fn to_string_short(&self) -> String { - let loc_str = self.relevant_loc.as_ref().map_or("[unknown]", |s| &*s); + let loc_str = self + .relevant_loc + .as_ref() + .or(self.loc.as_ref()) + .map_or("[unknown]", |s| &*s); format!("{}: {}", loc_str, self.msg.trim()) } From 4a9fdce7a530d0ba33451ee4dde3e6b14ba79f6a Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 Mar 2023 11:09:44 -0700 Subject: [PATCH 07/21] analyze: add comments around panic recovery --- c2rust-analyze/src/context.rs | 4 ++-- c2rust-analyze/src/panic_detail.rs | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 55b636139f..dfd5275470 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -277,8 +277,8 @@ pub struct GlobalAnalysisCtxt<'tcx> { pub fn_callers: HashMap>, pub fn_sigs: HashMap>, - /// `DefId`s of functions where analysis failed, and a `String` explaining the reason for each - /// failure. + /// `DefId`s of functions where analysis failed, and a [`PanicDetail`] explaining the reason + /// for each failure. pub fns_failed: HashMap, pub field_ltys: HashMap>, diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index b2a8ebdf51..bfb7c09efd 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -5,6 +5,7 @@ use std::cell::Cell; use std::fmt::Write as _; use std::panic::PanicInfo; +/// Detailed information about a panic. #[derive(Clone, Debug)] pub struct PanicDetail { msg: String, @@ -14,6 +15,8 @@ pub struct PanicDetail { } impl PanicDetail { + /// Create a new `PanicDetail` containing only a message, with no location or backtrace + /// information. pub fn new(msg: String) -> PanicDetail { PanicDetail { msg, @@ -23,10 +26,12 @@ impl PanicDetail { } } + /// Returns `true` if this `PanicDetail` contains a backtrace. pub fn has_backtrace(&self) -> bool { self.backtrace.is_some() } + /// Return a short (usually one-line) description of this panic. pub fn to_string_short(&self) -> String { let loc_str = self .relevant_loc @@ -36,6 +41,7 @@ impl PanicDetail { format!("{}: {}", loc_str, self.msg.trim()) } + /// Return a full description of this panic, including a complete backtrace if available. pub fn to_string_full(&self) -> String { let mut s = String::new(); let loc_str = self.loc.as_ref().map_or("[unknown]", |s| &*s); @@ -51,6 +57,8 @@ thread_local! { static CURRENT_PANIC_DETAIL: Cell> = Cell::new(None); } +/// Panic hook for use with [`std::panic::set_hook`]. This builds a `PanicDetail` for each panic +/// and stores it for later retrieval by [`take_current`]. pub fn panic_hook(info: &PanicInfo) { let bt = Backtrace::new(); let detail = PanicDetail { @@ -65,10 +73,15 @@ pub fn panic_hook(info: &PanicInfo) { } } +/// Get the [`PanicDetail`] of the most recent panic. This clears the internal storage, so if this +/// is called twice in a row without an intervening panic, the second call always returns `None`. pub fn take_current() -> Option { CURRENT_PANIC_DETAIL.with(|cell| cell.take()) } +/// Get the [`PanicDetail`] of the most recent panic; if it's not available, build a placeholder +/// `PanicDetail` from the panic payload `e`. This is useful in conjunction with +/// [`std::panic::catch_unwind`]. pub fn catch(e: &(dyn Any + Send + 'static)) -> PanicDetail { take_current().unwrap_or_else(|| { let msg = panic_to_string(e); @@ -77,6 +90,9 @@ pub fn catch(e: &(dyn Any + Send + 'static)) -> PanicDetail { }) } +/// Crude heuristic to guess the first interesting location in a [`Backtrace`], skipping over +/// helper functions, wrappers, and panic machinery. The resulting location is used in the summary +/// message produced by [`PanicDetail::to_string_short`]. fn guess_relevant_loc(bt: &Backtrace) -> Option { for frame in bt.frames() { for symbol in frame.symbols() { From 540a7b0e387400b92641eed70c7f6df646f9614c Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 29 Mar 2023 15:10:16 -0700 Subject: [PATCH 08/21] analyze: include source span in panic messages when available --- c2rust-analyze/src/borrowck/type_check.rs | 3 +++ c2rust-analyze/src/dataflow/type_check.rs | 5 +++- c2rust-analyze/src/main.rs | 2 ++ c2rust-analyze/src/panic_detail.rs | 32 +++++++++++++++++++++++ c2rust-analyze/src/rewrite/expr/hir_op.rs | 2 ++ c2rust-analyze/src/rewrite/expr/mir_op.rs | 3 +++ 6 files changed, 46 insertions(+), 1 deletion(-) diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 41a42df0dc..529e9cd255 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -2,6 +2,7 @@ use crate::borrowck::atoms::{AllFacts, AtomMaps, Loan, Origin, Path, Point, SubP use crate::borrowck::{construct_adt_origins, LTy, LTyCtxt, Label, OriginParam}; use crate::c_void_casts::CVoidCasts; use crate::context::PermissionSet; +use crate::panic_detail; use crate::util::{self, ty_callee, Callee}; use crate::AdtMetadataTable; use assert_matches::assert_matches; @@ -429,6 +430,7 @@ impl<'tcx> TypeChecker<'tcx, '_> { } pub fn visit_statement(&mut self, stmt: &Statement<'tcx>) { + let _g = panic_detail::set_current_span(stmt.source_info.span); // TODO(spernsteiner): other `StatementKind`s will be handled in the future #[allow(clippy::single_match)] match stmt.kind { @@ -445,6 +447,7 @@ impl<'tcx> TypeChecker<'tcx, '_> { pub fn visit_terminator(&mut self, term: &Terminator<'tcx>) { eprintln!("borrowck: visit_terminator({:?})", term.kind); + let _g = panic_detail::set_current_span(term.source_info.span); // TODO(spernsteiner): other `TerminatorKind`s will be handled in the future #[allow(clippy::single_match)] match term.kind { diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index b9fdced4ee..c283c72a63 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -1,6 +1,7 @@ use super::DataflowConstraints; use crate::c_void_casts::CVoidCastDirection; use crate::context::{AnalysisCtxt, LTy, PermissionSet, PointerId}; +use crate::panic_detail; use crate::util::{describe_rvalue, is_null_const, ty_callee, Callee, RvalueDesc}; use assert_matches::assert_matches; use rustc_hir::def_id::DefId; @@ -317,11 +318,12 @@ impl<'tcx> TypeChecker<'tcx, '_> { pub fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { eprintln!("visit_statement({:?})", stmt); - if self.acx.c_void_casts.should_skip_stmt(loc) { return; } + let _g = panic_detail::set_current_span(stmt.source_info.span); + // TODO(spernsteiner): other `StatementKind`s will be handled in the future #[allow(clippy::single_match)] match stmt.kind { @@ -342,6 +344,7 @@ impl<'tcx> TypeChecker<'tcx, '_> { pub fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { eprintln!("visit_terminator({:?})", term.kind); let tcx = self.acx.tcx(); + let _g = panic_detail::set_current_span(term.source_info.span); // TODO(spernsteiner): other `TerminatorKind`s will be handled in the future #[allow(clippy::single_match)] match term.kind { diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 0ba6f08a1b..6be32194dd 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -173,6 +173,8 @@ fn label_rvalue_tys<'tcx>(acx: &mut AnalysisCtxt<'_, 'tcx>, mir: &Body<'tcx>) { continue; } + let _g = panic_detail::set_current_span(stmt.source_info.span); + let lty = match rv { Rvalue::Aggregate(ref kind, ref _ops) => match **kind { AggregateKind::Array(elem_ty) => { diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index bfb7c09efd..805be99a51 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -1,5 +1,6 @@ use backtrace::Backtrace; use log::warn; +use rustc_span::{Span, DUMMY_SP}; use std::any::Any; use std::cell::Cell; use std::fmt::Write as _; @@ -12,6 +13,7 @@ pub struct PanicDetail { loc: Option, relevant_loc: Option, backtrace: Option, + sp: Span, } impl PanicDetail { @@ -23,6 +25,7 @@ impl PanicDetail { loc: None, relevant_loc: None, backtrace: None, + sp: DUMMY_SP, } } @@ -46,6 +49,12 @@ impl PanicDetail { let mut s = String::new(); let loc_str = self.loc.as_ref().map_or("[unknown]", |s| &*s); writeln!(s, "panic at {}: {}", loc_str, self.msg).unwrap(); + if let Some(ref relevant_loc) = self.relevant_loc { + writeln!(s, "related location: {}", relevant_loc).unwrap(); + } + if !self.sp.is_dummy() { + writeln!(s, "source location: {:?}", self.sp).unwrap(); + } if let Some(ref bt) = self.backtrace { writeln!(s, "{:?}", bt).unwrap(); } @@ -66,6 +75,7 @@ pub fn panic_hook(info: &PanicInfo) { loc: info.location().map(|l| l.to_string()), relevant_loc: guess_relevant_loc(&bt), backtrace: Some(bt), + sp: CURRENT_SPAN.with(|cell| cell.get()), }; let old = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(Some(detail))); if let Some(old) = old { @@ -104,6 +114,7 @@ fn guess_relevant_loc(bt: &Backtrace) -> Option { || name.starts_with("c2rust_analyze::borrowck") || name.starts_with("c2rust_analyze::rewrite") || name.contains("type_of_rvalue") + || name.contains("TypeOf") || name.contains("lty_project") { let filename_str = match symbol.filename() { @@ -136,3 +147,24 @@ fn panic_to_string(e: &(dyn Any + Send + 'static)) -> String { format!("unknown error: {:?}", e.type_id()) } + +thread_local! { + static CURRENT_SPAN: Cell = Cell::new(DUMMY_SP); +} + +pub struct CurrentSpanGuard { + old: Span, +} + +impl Drop for CurrentSpanGuard { + fn drop(&mut self) { + CURRENT_SPAN.with(|cell| cell.set(self.old)); + } +} + +/// Set the current span. Returns a guard that will reset the current span to its previous value +/// on drop. +pub fn set_current_span(sp: Span) -> CurrentSpanGuard { + let old = CURRENT_SPAN.with(|cell| cell.replace(sp)); + CurrentSpanGuard { old } +} diff --git a/c2rust-analyze/src/rewrite/expr/hir_op.rs b/c2rust-analyze/src/rewrite/expr/hir_op.rs index 07055e599d..3011096abf 100644 --- a/c2rust-analyze/src/rewrite/expr/hir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/hir_op.rs @@ -1,3 +1,4 @@ +use crate::panic_detail; use crate::rewrite::expr::mir_op::{self, MirRewrite}; use crate::rewrite::span_index::SpanIndex; use crate::rewrite::{build_span_index, Rewrite, SoleLocationError}; @@ -255,6 +256,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for HirRewriteVisitor<'a, 'tcx> { } fn visit_expr(&mut self, ex: &'tcx hir::Expr<'tcx>) { + let _g = panic_detail::set_current_span(ex.span); let mut hir_rw = Rewrite::Identity; // This span will be used to apply the actual rewrite. diff --git a/c2rust-analyze/src/rewrite/expr/mir_op.rs b/c2rust-analyze/src/rewrite/expr/mir_op.rs index b414a3c7bb..5c5812008b 100644 --- a/c2rust-analyze/src/rewrite/expr/mir_op.rs +++ b/c2rust-analyze/src/rewrite/expr/mir_op.rs @@ -8,6 +8,7 @@ //! materialize adjustments only on code that's subject to some rewrite. use crate::context::{AnalysisCtxt, Assignment, FlagSet, LTy, PermissionSet}; +use crate::panic_detail; use crate::pointer_id::PointerTable; use crate::type_desc::{self, Ownership, Quantity, TypeDesc}; use crate::util::{ty_callee, Callee}; @@ -136,6 +137,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { } fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { + let _g = panic_detail::set_current_span(stmt.source_info.span); self.loc = loc; debug_assert!(self.sub_loc.is_empty()); @@ -227,6 +229,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { let tcx = self.acx.tcx(); + let _g = panic_detail::set_current_span(term.source_info.span); self.loc = loc; debug_assert!(self.sub_loc.is_empty()); From 5d03754ece06d5ec348f27037d01363409929228 Mon Sep 17 00:00:00 2001 From: spernsteiner Date: Wed, 3 May 2023 11:07:10 -0700 Subject: [PATCH 09/21] analyze: Apply suggestions from code review Co-authored-by: Khyber Sen --- c2rust-analyze/src/panic_detail.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index 805be99a51..28cb28e84c 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -135,14 +135,12 @@ fn guess_relevant_loc(bt: &Backtrace) -> Option { } fn panic_to_string(e: &(dyn Any + Send + 'static)) -> String { - match e.downcast_ref::<&'static str>() { - Some(s) => return s.to_string(), - None => {} + if let Some(s) = e.downcast_ref::<&'static str>() { + return s.to_string(); } - match e.downcast_ref::() { - Some(s) => return (*s).clone(), - None => {} + if let Some(s) = e.downcast_ref::() { + return (*s).clone(); } format!("unknown error: {:?}", e.type_id()) From 79f855bb0c5d620f67e71d56cef2b8fe65112aa3 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 3 May 2023 11:09:37 -0700 Subject: [PATCH 10/21] analyze: panic_detail: rename `sp` to `span` --- c2rust-analyze/src/panic_detail.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index 28cb28e84c..bfcc0ed1a9 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -13,7 +13,7 @@ pub struct PanicDetail { loc: Option, relevant_loc: Option, backtrace: Option, - sp: Span, + span: Span, } impl PanicDetail { @@ -25,7 +25,7 @@ impl PanicDetail { loc: None, relevant_loc: None, backtrace: None, - sp: DUMMY_SP, + span: DUMMY_SP, } } @@ -52,8 +52,8 @@ impl PanicDetail { if let Some(ref relevant_loc) = self.relevant_loc { writeln!(s, "related location: {}", relevant_loc).unwrap(); } - if !self.sp.is_dummy() { - writeln!(s, "source location: {:?}", self.sp).unwrap(); + if !self.span.is_dummy() { + writeln!(s, "source location: {:?}", self.span).unwrap(); } if let Some(ref bt) = self.backtrace { writeln!(s, "{:?}", bt).unwrap(); @@ -75,7 +75,7 @@ pub fn panic_hook(info: &PanicInfo) { loc: info.location().map(|l| l.to_string()), relevant_loc: guess_relevant_loc(&bt), backtrace: Some(bt), - sp: CURRENT_SPAN.with(|cell| cell.get()), + span: CURRENT_SPAN.with(|cell| cell.get()), }; let old = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(Some(detail))); if let Some(old) = old { @@ -162,7 +162,7 @@ impl Drop for CurrentSpanGuard { /// Set the current span. Returns a guard that will reset the current span to its previous value /// on drop. -pub fn set_current_span(sp: Span) -> CurrentSpanGuard { - let old = CURRENT_SPAN.with(|cell| cell.replace(sp)); +pub fn set_current_span(span: Span) -> CurrentSpanGuard { + let old = CURRENT_SPAN.with(|cell| cell.replace(span)); CurrentSpanGuard { old } } From 9298e0dee4059ce8fd94d216e4102294eb2b873f Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 3 May 2023 11:45:51 -0700 Subject: [PATCH 11/21] analyze: add panic_detail::catch_unwind wrapper --- c2rust-analyze/src/main.rs | 18 +++++++++--------- c2rust-analyze/src/panic_detail.rs | 21 +++++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 6be32194dd..c932a98a2d 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -382,7 +382,7 @@ fn run(tcx: TyCtxt) { let mut acx = gacx.function_context(&mir); - let r = panic::catch_unwind(AssertUnwindSafe(|| { + let r = panic_detail::catch_unwind(AssertUnwindSafe(|| { // Assign PointerIds to local types assert!(acx.local_tys.is_empty()); acx.local_tys = IndexVec::with_capacity(mir.local_decls.len()); @@ -412,8 +412,8 @@ fn run(tcx: TyCtxt) { let (dataflow, equiv_constraints) = match r { Ok(x) => x, - Err(e) => { - gacx.mark_fn_failed(ldid.to_def_id(), panic_detail::catch(&e)); + Err(pd) => { + gacx.mark_fn_failed(ldid.to_def_id(), pd); continue; } }; @@ -519,7 +519,7 @@ fn run(tcx: TyCtxt) { let acx = gacx.function_context_with_data(&mir, info.acx_data.take()); let mut asn = gasn.and(&mut info.lasn); - let r = panic::catch_unwind(AssertUnwindSafe(|| { + let r = panic_detail::catch_unwind(AssertUnwindSafe(|| { // `dataflow.propagate` and `borrowck_mir` both run until the assignment converges // on a fixpoint, so there's no need to do multiple iterations here. info.dataflow.propagate(&mut asn.perms_mut()); @@ -535,8 +535,8 @@ fn run(tcx: TyCtxt) { })); match r { Ok(()) => {} - Err(e) => { - gacx.mark_fn_failed(ldid.to_def_id(), panic_detail::catch(&e)); + Err(pd) => { + gacx.mark_fn_failed(ldid.to_def_id(), pd); continue; } } @@ -597,7 +597,7 @@ fn run(tcx: TyCtxt) { eprintln!(); - let r = panic::catch_unwind(AssertUnwindSafe(|| { + let r = panic_detail::catch_unwind(AssertUnwindSafe(|| { let hir_body_id = tcx.hir().body_owned_by(ldid); let expr_rewrites = rewrite::gen_expr_rewrites(&acx, &asn, &mir, hir_body_id); let ty_rewrites = rewrite::gen_ty_rewrites(&acx, &asn, &mir, ldid); @@ -616,8 +616,8 @@ fn run(tcx: TyCtxt) { })); match r { Ok(()) => {} - Err(e) => { - gacx.mark_fn_failed(ldid.to_def_id(), panic_detail::catch(&e)); + Err(pd) => { + gacx.mark_fn_failed(ldid.to_def_id(), pd); continue; } } diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index bfcc0ed1a9..d560b74d71 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -4,7 +4,7 @@ use rustc_span::{Span, DUMMY_SP}; use std::any::Any; use std::cell::Cell; use std::fmt::Write as _; -use std::panic::PanicInfo; +use std::panic::{self, PanicInfo, UnwindSafe}; /// Detailed information about a panic. #[derive(Clone, Debug)] @@ -85,18 +85,19 @@ pub fn panic_hook(info: &PanicInfo) { /// Get the [`PanicDetail`] of the most recent panic. This clears the internal storage, so if this /// is called twice in a row without an intervening panic, the second call always returns `None`. -pub fn take_current() -> Option { +fn take_current() -> Option { CURRENT_PANIC_DETAIL.with(|cell| cell.take()) } -/// Get the [`PanicDetail`] of the most recent panic; if it's not available, build a placeholder -/// `PanicDetail` from the panic payload `e`. This is useful in conjunction with -/// [`std::panic::catch_unwind`]. -pub fn catch(e: &(dyn Any + Send + 'static)) -> PanicDetail { - take_current().unwrap_or_else(|| { - let msg = panic_to_string(e); - warn!("missing panic detail; caught message {:?}", msg); - PanicDetail::new(msg) +/// Like `std::panic::catch_unwind`, but returns a `PanicDetail` instead of `Box` on +/// panic. +pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { + panic::catch_unwind(f).map_err(|e| { + take_current().unwrap_or_else(|| { + let msg = panic_to_string(&e); + warn!("missing panic detail; caught message {:?}", msg); + PanicDetail::new(msg) + }) }) } From f3cb0463d436b8acedb98d526fff7d9505908752 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 3 May 2023 13:42:22 -0700 Subject: [PATCH 12/21] analyze: panic_detail: call default panic hook when not in catch_unwind --- c2rust-analyze/src/main.rs | 5 +- c2rust-analyze/src/panic_detail.rs | 88 +++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index c932a98a2d..8d21522afc 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -937,7 +937,10 @@ impl rustc_driver::Callbacks for AnalysisCallbacks { fn main() -> rustc_interface::interface::Result<()> { init_logger(); - panic::set_hook(Box::new(panic_detail::panic_hook)); + let default_panic_hook = panic::take_hook(); + panic::set_hook(Box::new(move |info| { + panic_detail::panic_hook(&default_panic_hook, info) + })); let args = env::args().collect::>(); rustc_driver::RunCompiler::new(&args, &mut AnalysisCallbacks).run() } diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index d560b74d71..a1a1d20dc5 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -62,43 +62,79 @@ impl PanicDetail { } } +enum PanicState { + /// The current thread is not in a `panic_detail::catch_unwind` call. + OutsideCatchUnwind, + /// The current thread is inside a `panic_detail::catch_unwind` call, but no panic has occurred + /// yet. + InsideCatchUnwind, + /// The current thread panicked while inside `panic_detail::catch_unwind`, producing a + /// `PanicDetail`, and is in the process of unwinding. + Unwinding(PanicDetail), +} + thread_local! { - static CURRENT_PANIC_DETAIL: Cell> = Cell::new(None); + static CURRENT_PANIC_DETAIL: Cell = + Cell::new(PanicState::OutsideCatchUnwind); } /// Panic hook for use with [`std::panic::set_hook`]. This builds a `PanicDetail` for each panic /// and stores it for later retrieval by [`take_current`]. -pub fn panic_hook(info: &PanicInfo) { - let bt = Backtrace::new(); - let detail = PanicDetail { - msg: panic_to_string(info.payload()), - loc: info.location().map(|l| l.to_string()), - relevant_loc: guess_relevant_loc(&bt), - backtrace: Some(bt), - span: CURRENT_SPAN.with(|cell| cell.get()), - }; - let old = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(Some(detail))); - if let Some(old) = old { - warn!("discarding old panic detail: {:?}", old); - } -} +pub fn panic_hook(default_hook: &dyn Fn(&PanicInfo), info: &PanicInfo) { + CURRENT_PANIC_DETAIL.with(|cell| { + // Take the old value, replacing it with something arbitrary. + let old = cell.replace(PanicState::OutsideCatchUnwind); + match old { + PanicState::OutsideCatchUnwind => { + // No special behavior is needed. Call the default panic hook instead. + default_hook(info); + return; + } + PanicState::InsideCatchUnwind => {} + PanicState::Unwinding(pd) => { + warn!("discarding old panic detail: {:?}", pd); + } + } -/// Get the [`PanicDetail`] of the most recent panic. This clears the internal storage, so if this -/// is called twice in a row without an intervening panic, the second call always returns `None`. -fn take_current() -> Option { - CURRENT_PANIC_DETAIL.with(|cell| cell.take()) + // We are inside `panic_detail::catch_unwind`. Build a `PanicDetail` for this panic and + // save it. + let bt = Backtrace::new(); + let detail = PanicDetail { + msg: panic_to_string(info.payload()), + loc: info.location().map(|l| l.to_string()), + relevant_loc: guess_relevant_loc(&bt), + backtrace: Some(bt), + span: CURRENT_SPAN.with(|cell| cell.get()), + }; + cell.set(PanicState::Unwinding(detail)); + }); } /// Like `std::panic::catch_unwind`, but returns a `PanicDetail` instead of `Box` on /// panic. pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { - panic::catch_unwind(f).map_err(|e| { - take_current().unwrap_or_else(|| { - let msg = panic_to_string(&e); - warn!("missing panic detail; caught message {:?}", msg); - PanicDetail::new(msg) - }) - }) + let old = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(PanicState::InsideCatchUnwind)); + let r = panic::catch_unwind(f); + let new = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(old)); + + match r { + Ok(x) => { + debug_assert!(matches!(new, PanicState::InsideCatchUnwind)); + Ok(x) + } + Err(e) => { + debug_assert!(!matches!(new, PanicState::OutsideCatchUnwind)); + let pd = match new { + PanicState::Unwinding(pd) => pd, + _ => { + let msg = panic_to_string(&e); + warn!("missing panic detail; caught message {:?}", msg); + PanicDetail::new(msg) + } + }; + Err(pd) + } + } } /// Crude heuristic to guess the first interesting location in a [`Backtrace`], skipping over From cbf6dc1139a0a452abc07da1332373e2078b1206 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 3 May 2023 13:53:09 -0700 Subject: [PATCH 13/21] analyze: add "bad function calls good function" case in catch_panic test --- c2rust-analyze/tests/filecheck/catch_panic.rs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/c2rust-analyze/tests/filecheck/catch_panic.rs b/c2rust-analyze/tests/filecheck/catch_panic.rs index 6d9856e5bc..696c814410 100644 --- a/c2rust-analyze/tests/filecheck/catch_panic.rs +++ b/c2rust-analyze/tests/filecheck/catch_panic.rs @@ -15,15 +15,29 @@ unsafe fn bad(p: NonNull) { *p.as_ptr() = 1; } -// Analysis of `bad2` should also fail because it has a callee on which analysis failed. -// CHECK-NOT: final labeling for "bad2" -unsafe fn bad2(p: NonNull) { +// Analysis of `call_bad` should also fail because it has a callee on which analysis failed. +// CHECK-NOT: final labeling for "call_bad" +unsafe fn call_bad(p: NonNull) { bad(p); } +// Analysis of this function fails, but it also calls `good`. Failures should not propagate from +// caller to callee (only callee to caller), so analysis of `good` should still succeed. +// CHECK-NOT: final labeling for "bad_call_good" +unsafe fn bad_call_good(p: NonNull) { + *p.as_ptr() = 1; + good(p.as_ptr()); +} + // CHECK: analysis of DefId({{.*}}::bad) failed: // CHECK-SAME: UnknownDef // CHECK-SAME: NonNull::::as_ptr -// CHECK: analysis of DefId({{.*}}::bad2) failed: +// CHECK: analysis of DefId({{.*}}::call_bad) failed: // CHECK-SAME: analysis failed on callee DefId({{.*}}::bad) + +// CHECK: analysis of DefId({{.*}}::bad_call_good) failed: +// CHECK-SAME: UnknownDef +// CHECK-SAME: NonNull::::as_ptr + +// CHECK: saw errors in 3 / 4 functions From 4aeccbae052a08fe3691eab18282a4ab6edaa721 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 5 May 2023 15:23:08 -0700 Subject: [PATCH 14/21] analyze: panic_detail: panic instead of warn! on double unwind --- c2rust-analyze/src/panic_detail.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index a1a1d20dc5..e995d9bfaa 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -92,7 +92,7 @@ pub fn panic_hook(default_hook: &dyn Fn(&PanicInfo), info: &PanicInfo) { } PanicState::InsideCatchUnwind => {} PanicState::Unwinding(pd) => { - warn!("discarding old panic detail: {:?}", pd); + unreachable!("started unwinding while already unwinding (from {pd:?})"); } } From f84d0ba6401ae4e033438ed2990236dc28789a89 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 5 May 2023 15:28:56 -0700 Subject: [PATCH 15/21] analyze: apply suggestion from code review --- c2rust-analyze/src/panic_detail.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index e995d9bfaa..612fc05dc7 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -40,14 +40,14 @@ impl PanicDetail { .relevant_loc .as_ref() .or(self.loc.as_ref()) - .map_or("[unknown]", |s| &*s); + .map_or("[unknown]", |s| s); format!("{}: {}", loc_str, self.msg.trim()) } /// Return a full description of this panic, including a complete backtrace if available. pub fn to_string_full(&self) -> String { let mut s = String::new(); - let loc_str = self.loc.as_ref().map_or("[unknown]", |s| &*s); + let loc_str = self.loc.as_ref().map_or("[unknown]", |s| s); writeln!(s, "panic at {}: {}", loc_str, self.msg).unwrap(); if let Some(ref relevant_loc) = self.relevant_loc { writeln!(s, "related location: {}", relevant_loc).unwrap(); From 24b9284d7ef4c0738e03d09d9b7d0a21582f1f79 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 5 May 2023 15:29:06 -0700 Subject: [PATCH 16/21] analyze: panic_detail: provide a panic_detail::set_hook function --- c2rust-analyze/src/main.rs | 7 ++----- c2rust-analyze/src/panic_detail.rs | 7 ++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 8d21522afc..3a1f8ba713 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -39,7 +39,7 @@ use std::collections::{HashMap, HashSet}; use std::env; use std::fmt::{Debug, Display}; use std::ops::{Deref, DerefMut, Index}; -use std::panic::{self, AssertUnwindSafe}; +use std::panic::AssertUnwindSafe; mod borrowck; mod c_void_casts; @@ -937,10 +937,7 @@ impl rustc_driver::Callbacks for AnalysisCallbacks { fn main() -> rustc_interface::interface::Result<()> { init_logger(); - let default_panic_hook = panic::take_hook(); - panic::set_hook(Box::new(move |info| { - panic_detail::panic_hook(&default_panic_hook, info) - })); + panic_detail::set_hook(); let args = env::args().collect::>(); rustc_driver::RunCompiler::new(&args, &mut AnalysisCallbacks).run() } diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index 612fc05dc7..cfda14adad 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -80,7 +80,7 @@ thread_local! { /// Panic hook for use with [`std::panic::set_hook`]. This builds a `PanicDetail` for each panic /// and stores it for later retrieval by [`take_current`]. -pub fn panic_hook(default_hook: &dyn Fn(&PanicInfo), info: &PanicInfo) { +fn panic_hook(default_hook: &dyn Fn(&PanicInfo), info: &PanicInfo) { CURRENT_PANIC_DETAIL.with(|cell| { // Take the old value, replacing it with something arbitrary. let old = cell.replace(PanicState::OutsideCatchUnwind); @@ -110,6 +110,11 @@ pub fn panic_hook(default_hook: &dyn Fn(&PanicInfo), info: &PanicInfo) { }); } +pub fn set_hook() { + let default_panic_hook = panic::take_hook(); + panic::set_hook(Box::new(move |info| panic_hook(&default_panic_hook, info))); +} + /// Like `std::panic::catch_unwind`, but returns a `PanicDetail` instead of `Box` on /// panic. pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { From ce24f38e603f0ac6b48995ec556d296853d9af96 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 5 May 2023 15:27:39 -0700 Subject: [PATCH 17/21] analyze: panic_unwind: fix stale inter-doc link --- c2rust-analyze/src/panic_detail.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index cfda14adad..2af0239336 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -79,7 +79,7 @@ thread_local! { } /// Panic hook for use with [`std::panic::set_hook`]. This builds a `PanicDetail` for each panic -/// and stores it for later retrieval by [`take_current`]. +/// and stores it for later retrieval by [`catch_unwind`]. fn panic_hook(default_hook: &dyn Fn(&PanicInfo), info: &PanicInfo) { CURRENT_PANIC_DETAIL.with(|cell| { // Take the old value, replacing it with something arbitrary. From 172808dd7c5161adb28181595ae3728ce0547671 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 9 May 2023 12:08:19 -0700 Subject: [PATCH 18/21] analyze: don't catch panics when running certain tests --- c2rust-analyze/src/main.rs | 8 +++++++- c2rust-analyze/tests/analyze.rs | 2 +- c2rust-analyze/tests/common/mod.rs | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 3a1f8ba713..f13e99fd53 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -937,7 +937,13 @@ impl rustc_driver::Callbacks for AnalysisCallbacks { fn main() -> rustc_interface::interface::Result<()> { init_logger(); - panic_detail::set_hook(); + + let dont_catch = env::var_os("C2RUST_TEST_ANALYZE_DONT_CATCH_PANIC").is_some(); + if !dont_catch { + panic_detail::set_hook(); + } + let args = env::args().collect::>(); + rustc_driver::RunCompiler::new(&args, &mut AnalysisCallbacks).run() } diff --git a/c2rust-analyze/tests/analyze.rs b/c2rust-analyze/tests/analyze.rs index 5f2e13cace..d07165e648 100644 --- a/c2rust-analyze/tests/analyze.rs +++ b/c2rust-analyze/tests/analyze.rs @@ -10,7 +10,7 @@ fn check_for_missing_tests() { fn test(file_name: &str) { let analyze = Analyze::resolve(); let path = test_dir_for(file!(), true).join(file_name); - analyze.run(&path); + analyze.dont_catch_panic().run(&path); } macro_rules! define_test { diff --git a/c2rust-analyze/tests/common/mod.rs b/c2rust-analyze/tests/common/mod.rs index 53b767a324..dbf69aac9a 100644 --- a/c2rust-analyze/tests/common/mod.rs +++ b/c2rust-analyze/tests/common/mod.rs @@ -13,6 +13,7 @@ use clap::Parser; #[derive(Default)] pub struct Analyze { path: PathBuf, + dont_catch_panic: bool, } #[derive(Debug, Clone)] @@ -90,7 +91,17 @@ impl Analyze { let bin_deps_dir = current_exe.parent().unwrap(); let bin_dir = bin_deps_dir.parent().unwrap(); let path = bin_dir.join(env!("CARGO_PKG_NAME")); - Self { path } + Self { + path, + dont_catch_panic: false, + } + } + + pub fn dont_catch_panic(self) -> Self { + Self { + dont_catch_panic: true, + ..self + } } fn run_with_(&self, rs_path: &Path, mut modify_cmd: impl FnMut(&mut Command)) -> PathBuf { @@ -110,6 +121,9 @@ impl Analyze { let output_stderr = File::try_clone(&output_stdout).unwrap(); let mut cmd = Command::new(&self.path); + if self.dont_catch_panic { + cmd.env("C2RUST_TEST_ANALYZE_DONT_CATCH_PANIC", "1"); + } cmd.arg(&rs_path) .arg("-L") .arg(lib_dir) From 176ecb1ee6a9cd350e729dde93a6efaff1f4728b Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 18 May 2023 15:49:02 -0700 Subject: [PATCH 19/21] analyze: rename env var to C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC --- c2rust-analyze/src/main.rs | 2 +- c2rust-analyze/tests/common/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index f13e99fd53..fc34383fc6 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -938,7 +938,7 @@ impl rustc_driver::Callbacks for AnalysisCallbacks { fn main() -> rustc_interface::interface::Result<()> { init_logger(); - let dont_catch = env::var_os("C2RUST_TEST_ANALYZE_DONT_CATCH_PANIC").is_some(); + let dont_catch = env::var_os("C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC").is_some(); if !dont_catch { panic_detail::set_hook(); } diff --git a/c2rust-analyze/tests/common/mod.rs b/c2rust-analyze/tests/common/mod.rs index dbf69aac9a..43741b397a 100644 --- a/c2rust-analyze/tests/common/mod.rs +++ b/c2rust-analyze/tests/common/mod.rs @@ -122,7 +122,7 @@ impl Analyze { let mut cmd = Command::new(&self.path); if self.dont_catch_panic { - cmd.env("C2RUST_TEST_ANALYZE_DONT_CATCH_PANIC", "1"); + cmd.env("C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC", "1"); } cmd.arg(&rs_path) .arg("-L") From 47ebc33b46671457ae4aed5b4da5dd4b68f2f4c4 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 18 May 2023 15:53:56 -0700 Subject: [PATCH 20/21] analyze: disable panic catching in all tests except catch_panic.rs --- c2rust-analyze/tests/analyze.rs | 2 +- c2rust-analyze/tests/common/mod.rs | 22 ++++++++----------- c2rust-analyze/tests/filecheck/catch_panic.rs | 2 ++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/c2rust-analyze/tests/analyze.rs b/c2rust-analyze/tests/analyze.rs index d07165e648..5f2e13cace 100644 --- a/c2rust-analyze/tests/analyze.rs +++ b/c2rust-analyze/tests/analyze.rs @@ -10,7 +10,7 @@ fn check_for_missing_tests() { fn test(file_name: &str) { let analyze = Analyze::resolve(); let path = test_dir_for(file!(), true).join(file_name); - analyze.dont_catch_panic().run(&path); + analyze.run(&path); } macro_rules! define_test { diff --git a/c2rust-analyze/tests/common/mod.rs b/c2rust-analyze/tests/common/mod.rs index 43741b397a..489677792a 100644 --- a/c2rust-analyze/tests/common/mod.rs +++ b/c2rust-analyze/tests/common/mod.rs @@ -13,7 +13,6 @@ use clap::Parser; #[derive(Default)] pub struct Analyze { path: PathBuf, - dont_catch_panic: bool, } #[derive(Debug, Clone)] @@ -50,6 +49,13 @@ struct AnalyzeArgs { /// Environment variables for `c2rust-analyze`. #[clap(long, value_parser)] env: Vec, + + /// Enable catching panics during analysis and rewriting. This behavior is enabled by default + /// when running the tool manually, but for testing we disable it to detect errors more easily. + /// Tests that are meant to exercise the panic-catching behavior can explicitly enable it with + /// this flag. + #[arg(long)] + catch_panics: bool, } impl AnalyzeArgs { @@ -91,17 +97,7 @@ impl Analyze { let bin_deps_dir = current_exe.parent().unwrap(); let bin_dir = bin_deps_dir.parent().unwrap(); let path = bin_dir.join(env!("CARGO_PKG_NAME")); - Self { - path, - dont_catch_panic: false, - } - } - - pub fn dont_catch_panic(self) -> Self { - Self { - dont_catch_panic: true, - ..self - } + Self { path } } fn run_with_(&self, rs_path: &Path, mut modify_cmd: impl FnMut(&mut Command)) -> PathBuf { @@ -121,7 +117,7 @@ impl Analyze { let output_stderr = File::try_clone(&output_stdout).unwrap(); let mut cmd = Command::new(&self.path); - if self.dont_catch_panic { + if !args.catch_panics { cmd.env("C2RUST_ANALYZE_TEST_DONT_CATCH_PANIC", "1"); } cmd.arg(&rs_path) diff --git a/c2rust-analyze/tests/filecheck/catch_panic.rs b/c2rust-analyze/tests/filecheck/catch_panic.rs index 696c814410..5c5f647aa3 100644 --- a/c2rust-analyze/tests/filecheck/catch_panic.rs +++ b/c2rust-analyze/tests/filecheck/catch_panic.rs @@ -1,3 +1,5 @@ +//! --catch-panics + // This test deliberately exercises unsupported code to check that c2rust-analyze can recover from // panics during the analysis. From cd9d001f8177a039d4421e3482eff6b5aa1c7d6a Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 22 May 2023 10:44:40 -0700 Subject: [PATCH 21/21] analyze: only catch panics if panic_detail hook is set --- c2rust-analyze/src/panic_detail.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/c2rust-analyze/src/panic_detail.rs b/c2rust-analyze/src/panic_detail.rs index 2af0239336..df28a2ef77 100644 --- a/c2rust-analyze/src/panic_detail.rs +++ b/c2rust-analyze/src/panic_detail.rs @@ -1,5 +1,4 @@ use backtrace::Backtrace; -use log::warn; use rustc_span::{Span, DUMMY_SP}; use std::any::Any; use std::cell::Cell; @@ -117,6 +116,9 @@ pub fn set_hook() { /// Like `std::panic::catch_unwind`, but returns a `PanicDetail` instead of `Box` on /// panic. +/// +/// This will only catch the panic if the `panic_detail` hook has been set by calling this module's +/// [`set_hook`] function. pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result { let old = CURRENT_PANIC_DETAIL.with(|cell| cell.replace(PanicState::InsideCatchUnwind)); let r = panic::catch_unwind(f); @@ -132,9 +134,9 @@ pub fn catch_unwind R + UnwindSafe, R>(f: F) -> Result pd, _ => { - let msg = panic_to_string(&e); - warn!("missing panic detail; caught message {:?}", msg); - PanicDetail::new(msg) + // Our `panic_hook` always sets `PanicDetail::Unwinding`, so `set_hook` must + // not have been called. Propagate the panic normally instead of catching it. + panic::resume_unwind(e); } }; Err(pd)