Skip to content

Commit

Permalink
analyze: emit inline annotations for debugging (#1071)
Browse files Browse the repository at this point in the history
This branch adds a new `annotate` module that allows providing
annotations to be attached to specific lines during rewriting, and uses
it to show pointer permissions/flags inline in the rewritten code. For
example:

```Rust
#[no_mangle]
pub unsafe extern "C" fn insertion_sort<'h0>(n: libc::c_int, p: &'h0 mut [(libc::c_int)]) {
// 10: p: typeof(_2) = *mut {g0} i32
// 10: p:   g0 = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB, (empty)
    let mut i: libc::c_int = 1 as libc::c_int;
    while i < n {
        let tmp: libc::c_int = *&(&(&*(p))[((i as isize) as usize) ..])[0];
        // 15: p.offset(i as isize): typeof(_9) = *mut {l9} i32
        // 15: p.offset(i as isize):   l9 = READ | UNIQUE, (empty)
        // 15: p: typeof(_10) = *mut {l11} i32
        // 15: p:   l11 = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB, (empty)
        let mut j: libc::c_int = i;
        while j > 0 as libc::c_int && *&(&(&*(p))[(((j - 1 as libc::c_int) as isize) as usize) ..])[0] > tmp {
        // 19: p.offset((j - 1 ... size): typeof(_21) = *mut {l23} i32
        // 19: p.offset((j - 1 ... size):   l23 = READ | UNIQUE, (empty)
        // 19: p: typeof(_22) = *mut {l25} i32
        // 19: p:   l25 = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB, (empty)
            *&mut (&mut (p)[((j as isize) as usize) ..])[0] = *&(&(&*(p))[(((j - 1 as libc::c_int) as isize) as usize) ..])[0];
            // 24: p.offset((j - 1 ... size): typeof(_30) = *mut {l34} i32
            // 24: p.offset((j - 1 ... size):   l34 = READ | UNIQUE, (empty)
            // 24: p: typeof(_31) = *mut {l36} i32
            // 24: p:   l36 = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB, (empty)
            // 24: p.offset(j as isize): typeof(_37) = *mut {l43} i32
            // 24: p.offset(j as isize):   l43 = READ | WRITE | UNIQUE, (empty)
            // 24: p: typeof(_38) = *mut {l45} i32
            // 24: p:   l45 = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB, (empty)
            j -= 1
        }
        *&mut (&mut (p)[((j as isize) as usize) ..])[0] = tmp;
        // 29: p.offset(j as isize): typeof(_46) = *mut {l54} i32
        // 29: p.offset(j as isize):   l54 = READ | WRITE | UNIQUE, (empty)
        // 29: p: typeof(_47) = *mut {l56} i32
        // 29: p:   l56 = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB, (empty)
        i += 1
    }
}
```

This is helpful for debugging, since it means we no longer need to
cross-reference line numbers in the debug output with the source code.
It's also easy to add more annotations in the future if it would be
useful.
  • Loading branch information
spernsteiner authored Apr 11, 2024
2 parents 982bafb + d400925 commit caf3e22
Show file tree
Hide file tree
Showing 7 changed files with 554 additions and 63 deletions.
159 changes: 125 additions & 34 deletions c2rust-analyze/src/analyze.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
use crate::annotate::AnnotationBuffer;
use crate::borrowck;
use crate::context::AnalysisCtxt;
use crate::context::AnalysisCtxtData;
use crate::context::FlagSet;
use crate::context::GlobalAnalysisCtxt;
use crate::context::GlobalAssignment;
use crate::context::LFnSig;
use crate::context::LTy;
use crate::context::LTyCtxt;
use crate::context::LocalAssignment;
use crate::context::PermissionSet;
use crate::context::PointerId;
use crate::context::PointerInfo;
use crate::context::{
self, AnalysisCtxt, AnalysisCtxtData, FlagSet, GlobalAnalysisCtxt, GlobalAssignment, LFnSig,
LTy, LTyCtxt, LocalAssignment, PermissionSet, PointerId, PointerInfo,
};
use crate::dataflow;
use crate::dataflow::DataflowConstraints;
use crate::equiv::GlobalEquivSet;
Expand Down Expand Up @@ -57,8 +50,7 @@ use rustc_middle::ty::Ty;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::TyKind;
use rustc_middle::ty::WithOptConstParam;
use rustc_span::Span;
use rustc_span::Symbol;
use rustc_span::{Span, Symbol};
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
Expand Down Expand Up @@ -115,6 +107,10 @@ impl<T> MaybeUnset<T> {
pub fn take(&mut self) -> T {
self.0.take().expect("value is not set")
}

pub fn is_set(&self) -> bool {
self.0.is_some()
}
}

impl<T> Deref for MaybeUnset<T> {
Expand Down Expand Up @@ -664,19 +660,19 @@ fn run(tcx: TyCtxt) {
pointee_type::generate_constraints(&acx, &mir)
}));

let pointee_constraints = match r {
Ok(x) => x,
let mut info = FuncInfo::default();
let local_pointee_types = LocalPointerTable::new(acx.num_pointers());
info.acx_data.set(acx.into_data());

match r {
Ok(pointee_constraints) => {
info.pointee_constraints.set(pointee_constraints);
}
Err(pd) => {
gacx.mark_fn_failed(ldid.to_def_id(), pd);
continue;
}
};

let local_pointee_types = LocalPointerTable::new(acx.num_pointers());
}

let mut info = FuncInfo::default();
info.acx_data.set(acx.into_data());
info.pointee_constraints.set(pointee_constraints);
info.local_pointee_types.set(local_pointee_types);
info.recent_writes.set(RecentWrites::new(&mir));
func_info.insert(ldid, info);
Expand Down Expand Up @@ -1101,15 +1097,16 @@ fn run(tcx: TyCtxt) {
field_ltys,
);
}));

info.acx_data.set(acx.into_data());

match r {
Ok(()) => {}
Err(pd) => {
gacx.mark_fn_failed(ldid.to_def_id(), pd);
continue;
}
}

info.acx_data.set(acx.into_data());
}

let mut num_changed = 0;
Expand Down Expand Up @@ -1156,15 +1153,16 @@ fn run(tcx: TyCtxt) {

acx.check_string_literal_perms(&asn);
}));

info.acx_data.set(acx.into_data());

match r {
Ok(()) => {}
Err(pd) => {
gacx.mark_fn_failed(ldid.to_def_id(), pd);
continue;
}
}

info.acx_data.set(acx.into_data());
}

// Check that these perms haven't changed.
Expand Down Expand Up @@ -1233,6 +1231,9 @@ fn run(tcx: TyCtxt) {
// for a single function makes FileCheck tests easier to write.
let mut func_reports = HashMap::<LocalDefId, String>::new();

// Buffer for annotations, which are inserted inline as comments when rewriting.
let mut ann = AnnotationBuffer::new(tcx);

// Generate rewrites for all functions.
let mut all_rewrites = Vec::new();

Expand Down Expand Up @@ -1310,15 +1311,16 @@ fn run(tcx: TyCtxt) {
all_rewrites.extend(expr_rewrites);
all_rewrites.extend(ty_rewrites);
}));

info.acx_data.set(acx.into_data());

match r {
Ok(()) => {}
Err(pd) => {
gacx.mark_fn_failed(ldid.to_def_id(), pd);
continue;
}
}

info.acx_data.set(acx.into_data());
}

// This call never panics, which is important because this is the fallback if the more
Expand Down Expand Up @@ -1456,6 +1458,54 @@ fn run(tcx: TyCtxt) {
if let Some(report) = func_reports.remove(&ldid) {
eprintln!("{}", report);
}

info.acx_data.set(acx.into_data());
}

// Generate annotations for all functions.
for ldid in tcx.hir().body_owners() {
// Skip any body owners that aren't present in `func_info`, and also get the info itself.
let info = match func_info.get_mut(&ldid) {
Some(x) => x,
None => continue,
};

if !info.acx_data.is_set() {
continue;
}

let ldid_const = WithOptConstParam::unknown(ldid);
let mir = tcx.mir_built(ldid_const);
let mir = mir.borrow();
let acx = gacx.function_context_with_data(&mir, info.acx_data.take());
let asn = gasn.and(&mut info.lasn);

// Generate inline annotations for pointer-typed locals
for (local, decl) in mir.local_decls.iter_enumerated() {
let span = local_span(decl);
let mut ptrs = Vec::new();
let ty_str = context::print_ty_with_pointer_labels(acx.local_tys[local], |ptr| {
if ptr.is_none() {
return String::new();
}
ptrs.push(ptr);
format!("{{{}}}", ptr)
});
if ptrs.is_empty() {
continue;
}
// TODO: emit addr_of when it's nontrivial
// TODO: emit pointee_types when nontrivial
ann.emit(span, format_args!("typeof({:?}) = {}", local, ty_str));
for ptr in ptrs {
ann.emit(
span,
format_args!(" {} = {:?}, {:?}", ptr, asn.perms()[ptr], asn.flags()[ptr]),
);
}
}

info.acx_data.set(acx.into_data());
}

// Print results for `static` items.
Expand Down Expand Up @@ -1492,6 +1542,33 @@ fn run(tcx: TyCtxt) {
let ty_flags = gasn.flags[pid];
eprintln!("{name:}: ({pid}) perms = {ty_perms:?}, flags = {ty_flags:?}");
}

// Emit annotations for fields
let span = match tcx.def_ident_span(did) {
Some(x) => x,
None => {
warn!("field {:?} has no def_ident_span to annotate", did);
continue;
}
};
let mut ptrs = Vec::new();
let ty_str = context::print_ty_with_pointer_labels(field_lty, |ptr| {
if ptr.is_none() {
return String::new();
}
ptrs.push(ptr);
format!("{{{}}}", ptr)
});
if ptrs.len() == 0 {
continue;
}
ann.emit(span, format_args!("typeof({}) = {}", name, ty_str));
for ptr in ptrs {
ann.emit(
span,
format_args!(" {} = {:?}, {:?}", ptr, gasn.perms[ptr], gasn.flags[ptr]),
);
}
}

let mut adt_dids = gacx.adt_metadata.table.keys().cloned().collect::<Vec<_>>();
Expand All @@ -1506,14 +1583,23 @@ fn run(tcx: TyCtxt) {
// Apply rewrites
// ----------------------------------

let annotations = ann.finish();

// Apply rewrite to all functions at once.
let mut update_files = rewrite::UpdateFiles::No;
if let Ok(val) = env::var("C2RUST_ANALYZE_REWRITE_IN_PLACE") {
if val == "1" {
update_files = rewrite::UpdateFiles::Yes;
if let Ok(val) = env::var("C2RUST_ANALYZE_REWRITE_MODE") {
match val.as_str() {
"none" => {}
"inplace" => {
update_files = rewrite::UpdateFiles::InPlace;
}
"alongside" => {
update_files = rewrite::UpdateFiles::Alongside;
}
_ => panic!("bad value {:?} for C2RUST_ANALYZE_REWRITE_MODE", val),
}
}
rewrite::apply_rewrites(tcx, all_rewrites, update_files);
rewrite::apply_rewrites(tcx, all_rewrites, annotations, update_files);

// ----------------------------------
// Report caught panics
Expand Down Expand Up @@ -1612,7 +1698,7 @@ fn make_sig_fixed(gasn: &mut GlobalAssignment, lsig: &LFnSig) {
}
}

fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String {
fn local_span(decl: &LocalDecl) -> Span {
let mut span = decl.source_info.span;
if let Some(ref info) = decl.local_info {
if let LocalInfo::User(ref binding_form) = **info {
Expand All @@ -1622,6 +1708,11 @@ fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String {
}
}
}
span
}

fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String {
let span = local_span(decl);
describe_span(tcx, span)
}

Expand Down
73 changes: 73 additions & 0 deletions c2rust-analyze/src/annotate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use log::warn;
use rustc_middle::ty::TyCtxt;
use rustc_span::{FileName, Span};
use std::collections::HashMap;
use std::fmt::Display;

pub struct AnnotationBuffer<'tcx> {
tcx: TyCtxt<'tcx>,
/// Map from `file_idx` to a list of annotations as `(line_number, text)` pairs.
m: HashMap<usize, Vec<(usize, String)>>,
}

impl<'tcx> AnnotationBuffer<'tcx> {
pub fn new(tcx: TyCtxt<'tcx>) -> AnnotationBuffer<'tcx> {
AnnotationBuffer {
tcx,
m: HashMap::new(),
}
}

pub fn _clear(&mut self) {
self.m.clear();
}

pub fn emit(&mut self, span: Span, msg: impl Display) {
if span.is_dummy() {
// `DUMMY_SP` covers the range `BytePos(0) .. BytePos(0)`. Whichever file happens to
// be added to the `SourceMap` first will be assigned a range starting at `BytePos(0)`,
// so the `SourceFile` lookup below would attach the annotation to that file. Rather
// than letting the annotation be attached to an arbitrary file, we warn and discard
// it.
warn!("discarding annotation on DUMMY_SP: {}", msg);
return;
}

let sm = self.tcx.sess.source_map();

let span = span.source_callsite();
let pos = span.lo();
let file_idx = sm.lookup_source_file_idx(pos);
let sf = &sm.files()[file_idx];
let line = sf.lookup_line(pos).unwrap_or(0);

let src = sm
.span_to_snippet(span)
.unwrap_or_else(|_| "<error>".into());
let src = src.split_ascii_whitespace().collect::<Vec<_>>().join(" ");
let (src1, src2, src3) = if src.len() > 20 {
(&src[..15], " ... ", &src[src.len() - 5..])
} else {
(&src[..], "", "")
};
self.m.entry(file_idx).or_insert_with(Vec::new).push((
line,
format!("{}: {}{}{}: {}", line + 1, src1, src2, src3, msg),
));
}

pub fn finish(self) -> HashMap<FileName, Vec<(usize, String)>> {
let mut m = HashMap::new();
let sm = self.tcx.sess.source_map();
for (file_idx, v) in self.m {
let sf = &sm.files()[file_idx];
let old = m.insert(sf.name.clone(), v);
assert!(
old.is_none(),
"found multiple SourceFiles named {:?}",
sf.name
);
}
m
}
}
Loading

0 comments on commit caf3e22

Please sign in to comment.