From 1082625b2b555d669732555e698175fabe1a4757 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 14 Mar 2022 16:21:10 -0700 Subject: [PATCH 01/38] analyze: initial commit --- c2rust-analyze/.gitignore | 2 + c2rust-analyze/Cargo.toml | 11 ++ c2rust-analyze/README.md | 11 ++ c2rust-analyze/build.rs | 15 ++ c2rust-analyze/rust-toolchain | 1 + c2rust-analyze/src/atoms.rs | 123 +++++++++++++ c2rust-analyze/src/dump.rs | 162 ++++++++++++++++++ c2rust-analyze/src/main.rs | 100 +++++++++++ examples/static-analysis/alias1.rs | 24 +++ examples/static-analysis/insertion_sort.rs | 20 +++ .../static-analysis/insertion_sort_ref.rs | 14 ++ 11 files changed, 483 insertions(+) create mode 100644 c2rust-analyze/.gitignore create mode 100644 c2rust-analyze/Cargo.toml create mode 100644 c2rust-analyze/README.md create mode 100644 c2rust-analyze/build.rs create mode 100644 c2rust-analyze/rust-toolchain create mode 100644 c2rust-analyze/src/atoms.rs create mode 100644 c2rust-analyze/src/dump.rs create mode 100644 c2rust-analyze/src/main.rs create mode 100644 examples/static-analysis/alias1.rs create mode 100644 examples/static-analysis/insertion_sort.rs create mode 100644 examples/static-analysis/insertion_sort_ref.rs diff --git a/c2rust-analyze/.gitignore b/c2rust-analyze/.gitignore new file mode 100644 index 0000000000..a61adfa020 --- /dev/null +++ b/c2rust-analyze/.gitignore @@ -0,0 +1,2 @@ +/target/ +/inspect/ diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml new file mode 100644 index 0000000000..df91ae7825 --- /dev/null +++ b/c2rust-analyze/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "c2rust-analyze" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +polonius-engine = "0.13.0" + +[workspace] diff --git a/c2rust-analyze/README.md b/c2rust-analyze/README.md new file mode 100644 index 0000000000..ed44c99048 --- /dev/null +++ b/c2rust-analyze/README.md @@ -0,0 +1,11 @@ +```sh +rustc --crate-type rlib instrument_support.rs +cargo run -- fib.rs -L ~/.rustup/toolchains/nightly-2022-02-14-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/ +./fib +``` + +The final `./fib` command should print several lines like +``` +[CALL] fib(5,) +``` +which come from the injected calls to `instrument_support::handle_call`. diff --git a/c2rust-analyze/build.rs b/c2rust-analyze/build.rs new file mode 100644 index 0000000000..ef32190135 --- /dev/null +++ b/c2rust-analyze/build.rs @@ -0,0 +1,15 @@ +use std::path::Path; +use std::process::Command; +use std::str; + +fn main() { + // Add the toolchain lib/ directory to `-L`. This fixes the linker error "cannot find + // -lLLVM-13-rust-1.60.0-nightly". + let out = Command::new("rustup") + .args(&["which", "rustc"]) + .output().unwrap(); + assert!(out.status.success()); + let rustc_path = Path::new(str::from_utf8(&out.stdout).unwrap().trim_end()); + let lib_dir = rustc_path.parent().unwrap().parent().unwrap().join("lib"); + println!("cargo:rustc-link-search={}", lib_dir.display()); +} diff --git a/c2rust-analyze/rust-toolchain b/c2rust-analyze/rust-toolchain new file mode 100644 index 0000000000..f24eb00eda --- /dev/null +++ b/c2rust-analyze/rust-toolchain @@ -0,0 +1 @@ +nightly-2022-02-14 diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/atoms.rs new file mode 100644 index 0000000000..f36f000281 --- /dev/null +++ b/c2rust-analyze/src/atoms.rs @@ -0,0 +1,123 @@ +use std::collections::hash_map::{HashMap, Entry}; +use std::hash::Hash; +use polonius_engine::{self, Atom, FactTypes}; +use rustc_middle::mir::{BasicBlock, Local}; + +macro_rules! define_atom_type { + ($Atom:ident) => { + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] + pub struct $Atom(usize); + + impl From for $Atom { + fn from(x: usize) -> $Atom { + $Atom(x) + } + } + + impl From<$Atom> for usize { + fn from(x: $Atom) -> usize { + x.0 + } + } + + impl Atom for $Atom { + fn index(self) -> usize { self.0 } + } + }; +} + +define_atom_type!(Origin); +define_atom_type!(Loan); +define_atom_type!(Point); +define_atom_type!(Variable); +define_atom_type!(Path); + + +#[derive(Clone, Copy, Debug, Default)] +pub struct AnalysisFactTypes; +impl FactTypes for AnalysisFactTypes { + type Origin = Origin; + type Loan = Loan; + type Point = Point; + type Variable = Variable; + type Path = Path; +} + +pub type AllFacts = polonius_engine::AllFacts; + + + +#[derive(Clone, Debug)] +struct AtomMap { + atom_to_thing: Vec, + thing_to_atom: HashMap, +} + +impl Default for AtomMap { + fn default() -> AtomMap { + AtomMap { + atom_to_thing: Vec::new(), + thing_to_atom: HashMap::new(), + } + } +} + +impl AtomMap { + pub fn new() -> AtomMap { + AtomMap { + atom_to_thing: Vec::new(), + thing_to_atom: HashMap::new(), + } + } + + pub fn add(&mut self, x: T) -> A { + match self.thing_to_atom.entry(x.clone()) { + Entry::Occupied(e) => { + *e.get() + }, + Entry::Vacant(e) => { + let atom = A::from(self.atom_to_thing.len()); + self.atom_to_thing.push(x); + e.insert(atom); + atom + }, + } + } + + pub fn get(&self, x: A) -> T { + self.atom_to_thing[x.into()].clone() + } +} + + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum SubPoint { + Start, + Mid, +} + + +#[derive(Clone, Debug, Default)] +pub struct AtomMaps { + point: AtomMap<(BasicBlock, usize, SubPoint), Point>, +} + +impl AtomMaps { + pub fn point(&mut self, bb: BasicBlock, idx: usize, sub: SubPoint) -> Point { + self.point.add((bb, idx, sub)) + } + + pub fn get_point(&self, x: Point) -> (BasicBlock, usize, SubPoint) { + self.point.get(x) + } + + pub fn variable(&mut self, l: Local) -> Variable { + Variable(l.as_usize()) + } + + pub fn get_variable(&mut self, x: Variable) -> Local { + Local::from_usize(x.0) + } +} + + diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs new file mode 100644 index 0000000000..085221b140 --- /dev/null +++ b/c2rust-analyze/src/dump.rs @@ -0,0 +1,162 @@ +/// Copied partly from rustc `compiler/rustc_borrowck/src/facts.rs`, which is dual-licensed MIT and +/// Apache 2.0. +use std::error::Error; +use std::fmt::Debug; +use std::fs::{self, File}; +use std::io::{BufWriter, Write}; +use std::path; +use crate::atoms::{AllFacts, AtomMaps, Origin, Loan, Point, Variable, Path}; + +pub fn dump_facts_to_dir( + facts: &AllFacts, + maps: &AtomMaps, + dir: impl AsRef, +) -> Result<(), Box> { + let dir: &path::Path = dir.as_ref(); + fs::create_dir_all(dir)?; + let wr = FactWriter { maps, dir }; + macro_rules! write_facts_to_path { + ($wr:ident . write_facts_to_path($this:ident . [ + $($field:ident,)* + ])) => { + $( + $wr.write_facts_to_path( + &$this.$field, + &format!("{}.facts", stringify!($field)) + )?; + )* + } + } + write_facts_to_path! { + wr.write_facts_to_path(facts.[ + //loan_issued_at, + //universal_region, + cfg_edge, + //loan_killed_at, + //subset_base, + //loan_invalidated_at, + //var_used_at, + //var_defined_at, + //var_dropped_at, + //use_of_var_derefs_origin, + //drop_of_var_derefs_origin, + //child_path, + //path_is_var, + //path_assigned_at_base, + //path_moved_at_base, + //path_accessed_at_base, + //known_placeholder_subset, + //placeholder, + ]) + } + Ok(()) +} + +struct FactWriter<'w> { + maps: &'w AtomMaps, + dir: &'w path::Path, +} + +impl<'w> FactWriter<'w> { + fn write_facts_to_path(&self, rows: &[T], file_name: &str) -> Result<(), Box> + where + T: FactRow, + { + let file = &self.dir.join(file_name); + let mut file = BufWriter::new(File::create(file)?); + for row in rows { + row.write(&mut file, self.maps)?; + } + Ok(()) + } +} + +trait FactRow { + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box>; +} + +impl FactRow for (A, B) +where + A: FactCell, + B: FactCell, +{ + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box> { + write_row(out, maps, &[&self.0, &self.1]) + } +} + +impl FactRow for (A, B, C) +where + A: FactCell, + B: FactCell, + C: FactCell, +{ + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box> { + write_row(out, maps, &[&self.0, &self.1, &self.2]) + } +} + +impl FactRow for (A, B, C, D) +where + A: FactCell, + B: FactCell, + C: FactCell, + D: FactCell, +{ + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box> { + write_row(out, maps, &[&self.0, &self.1, &self.2, &self.3]) + } +} + +fn write_row( + out: &mut dyn Write, + maps: &AtomMaps, + columns: &[&dyn FactCell], +) -> Result<(), Box> { + for (index, c) in columns.iter().enumerate() { + let tail = if index == columns.len() - 1 { "\n" } else { "\t" }; + write!(out, "{:?}{}", c.to_string(maps), tail)?; + } + Ok(()) +} + +trait FactCell { + fn to_string(&self, maps: &AtomMaps) -> String; +} + +impl FactCell for Point { + fn to_string(&self, maps: &AtomMaps) -> String { + let (bb, idx, sub) = maps.get_point(*self); + format!("{:?}({:?}[{}])", sub, bb, idx) + } +} + +/* +impl FactCell for A { + default fn to_string(&self, _maps: &AtomMaps) -> String { + format!("{:?}", self) + } +} + +impl FactCell for LocationIndex { + fn to_string(&self, maps: &AtomMaps) -> String { + format!("{:?}", maps.to_location(*self)) + } +} +*/ diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs new file mode 100644 index 0000000000..ce10971d76 --- /dev/null +++ b/c2rust-analyze/src/main.rs @@ -0,0 +1,100 @@ +#![feature(rustc_private)] +extern crate rustc_ast; +extern crate rustc_driver; +extern crate rustc_interface; +extern crate rustc_middle; +extern crate rustc_mir_build; +extern crate rustc_session; +extern crate rustc_span; +extern crate rustc_target; + +use std::env; +use std::hash::Hash; +use std::mem; +use polonius_engine::{self, Atom, FactTypes}; +use rustc_ast::ast::{Item, ItemKind, Visibility, VisibilityKind}; +use rustc_ast::node_id::NodeId; +use rustc_ast::ptr::P; +use rustc_driver::Compilation; +use rustc_interface::Queries; +use rustc_interface::interface::Compiler; +use rustc_middle::mir::{ + Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, + LocalDecl, Mutability, Rvalue, AggregateKind, Place, Operand, Statement, StatementKind, + BorrowKind, Constant, ConstantKind, +}; +use rustc_middle::mir::interpret::{Allocation, ConstValue}; +use rustc_middle::ty::{TyCtxt, RegionKind, WithOptConstParam}; +use rustc_middle::ty::query::{Providers, ExternProviders}; +use rustc_session::Session; +use rustc_span::DUMMY_SP; +use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; +use rustc_span::symbol::Ident; +use rustc_target::abi::Align; +use crate::atoms::{AllFacts, AtomMaps, SubPoint}; + +mod atoms; +mod dump; + + +fn inspect_mir<'tcx>( + tcx: TyCtxt<'tcx>, + def: WithOptConstParam, + mir: &Body<'tcx>, +) { + let mut facts = AllFacts::default(); + let mut maps = AtomMaps::default(); + + // Populate `cfg_edge` + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + for idx in 0 .. bb_data.statements.len() { + let start = maps.point(bb, idx, SubPoint::Start); + let mid = maps.point(bb, idx, SubPoint::Mid); + let next_start = maps.point(bb, idx + 1, SubPoint::Start); + facts.cfg_edge.push((start, mid)); + facts.cfg_edge.push((mid, next_start)); + } + + let term_idx = bb_data.statements.len(); + let term_start = maps.point(bb, term_idx, SubPoint::Start); + let term_mid = maps.point(bb, term_idx, SubPoint::Mid); + facts.cfg_edge.push((term_start, term_mid)); + for &succ in bb_data.terminator().successors() { + let succ_start = maps.point(succ, 0, SubPoint::Start); + facts.cfg_edge.push((term_mid, succ_start)); + } + } + + let name = tcx.item_name(def.to_global().did); + dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); +} + + +struct AnalysisCallbacks; + +impl rustc_driver::Callbacks for AnalysisCallbacks { + fn config(&mut self, config: &mut rustc_interface::Config) { + config.override_queries = Some(override_queries); + } +} + +fn override_queries( + sess: &Session, + providers: &mut Providers, + extern_providers: &mut ExternProviders, +) { + providers.mir_built = |tcx, def: WithOptConstParam| { + let mut providers = Providers::default(); + rustc_mir_build::provide(&mut providers); + let steal_mir = (providers.mir_built)(tcx, def); + + inspect_mir(tcx, def, &steal_mir.borrow()); + + steal_mir + }; +} + +fn main() -> rustc_interface::interface::Result<()> { + let mut args = env::args().collect::>(); + rustc_driver::RunCompiler::new(&args, &mut AnalysisCallbacks).run() +} diff --git a/examples/static-analysis/alias1.rs b/examples/static-analysis/alias1.rs new file mode 100644 index 0000000000..dc39bc0896 --- /dev/null +++ b/examples/static-analysis/alias1.rs @@ -0,0 +1,24 @@ +pub unsafe fn alias1_good(p: *mut i32) { + let q = p; + let r = p; + *r = 1; +} + +pub unsafe fn alias1_bad(p: *mut i32) { + let q = p; + let r = p; + *q = 1; +} + + +pub fn safe_alias1_good(p: &mut i32) { + let q = &mut *p; + let r = &mut *p; + *r = 1; +} + +pub fn safe_alias1_bad(p: &mut i32) { + let q = &mut *p; + let r = &mut *p; + *q = 1; +} diff --git a/examples/static-analysis/insertion_sort.rs b/examples/static-analysis/insertion_sort.rs new file mode 100644 index 0000000000..6faa543393 --- /dev/null +++ b/examples/static-analysis/insertion_sort.rs @@ -0,0 +1,20 @@ +#![allow(dead_code, mutable_transmutes, non_camel_case_types, non_snake_case, + non_upper_case_globals, unused_assignments, unused_mut)] +#![feature(rustc_private)] + +extern crate libc; + +#[no_mangle] +pub unsafe extern "C" fn insertion_sort(n: libc::c_int, p: *mut libc::c_int) { + let mut i: libc::c_int = 1 as libc::c_int; + while i < n { + let tmp: libc::c_int = *p.offset(i as isize); + let mut j: libc::c_int = i; + while j > 0 as libc::c_int && *p.offset((j - 1 as libc::c_int) as isize) > tmp { + *p.offset(j as isize) = *p.offset((j - 1 as libc::c_int) as isize); + j -= 1 + } + *p.offset(j as isize) = tmp; + i += 1 + } +} diff --git a/examples/static-analysis/insertion_sort_ref.rs b/examples/static-analysis/insertion_sort_ref.rs new file mode 100644 index 0000000000..179e2f9cbd --- /dev/null +++ b/examples/static-analysis/insertion_sort_ref.rs @@ -0,0 +1,14 @@ +type c_int = i32; +pub fn insertion_sort(n: c_int, p: &mut [c_int]) { + let mut i: c_int = 1 as c_int; + while i < n { + let tmp: c_int = p[i as usize]; + let mut j: c_int = i; + while j > 0 as c_int && p[(j - 1 as c_int) as usize] > tmp { + p[j as usize] = p[(j - 1 as c_int) as usize]; + j -= 1 + } + p[j as usize] = tmp; + i += 1 + } +} From 03c338ff519eba2353ae5bf2aba0bf227b1dbc96 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 14 Mar 2022 17:08:43 -0700 Subject: [PATCH 02/38] analyze: run polonius and dump outputs --- c2rust-analyze/Cargo.toml | 1 + c2rust-analyze/src/atoms.rs | 3 +- c2rust-analyze/src/dump.rs | 246 +++++++++++++++++++++++++++++++++--- c2rust-analyze/src/main.rs | 8 ++ 4 files changed, 236 insertions(+), 22 deletions(-) diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index df91ae7825..165fe56725 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] polonius-engine = "0.13.0" +rustc-hash = "1.1.0" [workspace] diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/atoms.rs index f36f000281..87e39e51e7 100644 --- a/c2rust-analyze/src/atoms.rs +++ b/c2rust-analyze/src/atoms.rs @@ -44,6 +44,7 @@ impl FactTypes for AnalysisFactTypes { } pub type AllFacts = polonius_engine::AllFacts; +pub type Output = polonius_engine::Output; @@ -115,7 +116,7 @@ impl AtomMaps { Variable(l.as_usize()) } - pub fn get_variable(&mut self, x: Variable) -> Local { + pub fn get_variable(&self, x: Variable) -> Local { Local::from_usize(x.0) } } diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs index 085221b140..337e6e07fb 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/dump.rs @@ -1,11 +1,14 @@ /// Copied partly from rustc `compiler/rustc_borrowck/src/facts.rs`, which is dual-licensed MIT and /// Apache 2.0. +use std::collections::{BTreeMap, BTreeSet}; use std::error::Error; -use std::fmt::Debug; +use std::fmt::{Debug, Write as _}; use std::fs::{self, File}; +use std::hash::Hash; use std::io::{BufWriter, Write}; use std::path; -use crate::atoms::{AllFacts, AtomMaps, Origin, Loan, Point, Variable, Path}; +use rustc_hash::{FxHashMap, FxHashSet}; +use crate::atoms::{AllFacts, Output, AtomMaps, Origin, Loan, Point, Variable, Path}; pub fn dump_facts_to_dir( facts: &AllFacts, @@ -52,6 +55,51 @@ pub fn dump_facts_to_dir( Ok(()) } +pub fn dump_output_to_dir( + output: &Output, + maps: &AtomMaps, + dir: impl AsRef, +) -> Result<(), Box> { + let dir: &path::Path = dir.as_ref(); + fs::create_dir_all(dir)?; + let wr = FactWriter { maps, dir }; + macro_rules! write_output_to_path { + ($wr:ident . write_output_to_path($this:ident . [ + $($field:ident,)* + ])) => { + let Output { $(ref $field,)* } = $this; + $( + $wr.write_output_to_path( + $field, + &format!("{}.output", stringify!($field)) + )?; + )* + } + } + write_output_to_path! { + wr.write_output_to_path(output.[ + errors, + subset_errors, + move_errors, + dump_enabled, + loan_live_at, + origin_contains_loan_at, + origin_contains_loan_anywhere, + origin_live_on_entry, + loan_invalidated_at, + subset, + subset_anywhere, + var_live_on_entry, + var_drop_live_on_entry, + path_maybe_initialized_on_exit, + path_maybe_uninitialized_on_exit, + known_contains, + var_maybe_partly_initialized_on_exit, + ]) + } + Ok(()) +} + struct FactWriter<'w> { maps: &'w AtomMaps, dir: &'w path::Path, @@ -69,6 +117,16 @@ impl<'w> FactWriter<'w> { } Ok(()) } + + fn write_output_to_path(&self, rows: &T, file_name: &str) -> Result<(), Box> + where + T: OutputTable, + { + let file = &self.dir.join(file_name); + let mut file = BufWriter::new(File::create(file)?); + rows.write(&mut file, self.maps)?; + Ok(()) + } } trait FactRow { @@ -81,8 +139,8 @@ trait FactRow { impl FactRow for (A, B) where - A: FactCell, - B: FactCell, + A: Render, + B: Render, { fn write( &self, @@ -95,9 +153,9 @@ where impl FactRow for (A, B, C) where - A: FactCell, - B: FactCell, - C: FactCell, + A: Render, + B: Render, + C: Render, { fn write( &self, @@ -110,10 +168,10 @@ where impl FactRow for (A, B, C, D) where - A: FactCell, - B: FactCell, - C: FactCell, - D: FactCell, + A: Render, + B: Render, + C: Render, + D: Render, { fn write( &self, @@ -127,7 +185,7 @@ where fn write_row( out: &mut dyn Write, maps: &AtomMaps, - columns: &[&dyn FactCell], + columns: &[&dyn Render], ) -> Result<(), Box> { for (index, c) in columns.iter().enumerate() { let tail = if index == columns.len() - 1 { "\n" } else { "\t" }; @@ -136,27 +194,173 @@ fn write_row( Ok(()) } -trait FactCell { + +trait OutputTable { + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box>; +} + +impl OutputTable for FxHashMap { + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box> { + for (k, v) in self { + write!(out, "{}: {}", k.to_string(maps), v.to_string(maps))?; + } + Ok(()) + } +} + +impl OutputTable for bool { + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box> { + write!(out, "{}", self)?; + Ok(()) + } +} + + +trait Render { fn to_string(&self, maps: &AtomMaps) -> String; } -impl FactCell for Point { +impl Render for FxHashMap { + fn to_string(&self, maps: &AtomMaps) -> String { + let mut s = String::new(); + write!(s, "{{").unwrap(); + let mut first = true; + for (k, v) in self { + if !first { + write!(s, ",").unwrap(); + } + write!(s, " {}: {}", k.to_string(maps), v.to_string(maps)).unwrap(); + } + if !first { + write!(s, " ").unwrap(); + } + write!(s, "}}").unwrap(); + s + } +} + +impl Render for FxHashSet { + fn to_string(&self, maps: &AtomMaps) -> String { + let mut s = String::new(); + write!(s, "{{").unwrap(); + let mut first = true; + for x in self { + if !first { + write!(s, ",").unwrap(); + } + write!(s, " {}", x.to_string(maps)).unwrap(); + } + if !first { + write!(s, " ").unwrap(); + } + write!(s, "}}").unwrap(); + s + } +} + +impl Render for BTreeMap { + fn to_string(&self, maps: &AtomMaps) -> String { + let mut s = String::new(); + write!(s, "{{").unwrap(); + let mut first = true; + for (k, v) in self { + if !first { + write!(s, ",").unwrap(); + } + write!(s, " {}: {}", k.to_string(maps), v.to_string(maps)).unwrap(); + } + if !first { + write!(s, " ").unwrap(); + } + write!(s, "}}").unwrap(); + s + } +} + +impl Render for BTreeSet { + fn to_string(&self, maps: &AtomMaps) -> String { + let mut s = String::new(); + write!(s, "{{").unwrap(); + let mut first = true; + for x in self { + if !first { + write!(s, ",").unwrap(); + } + write!(s, " {}", x.to_string(maps)).unwrap(); + } + if !first { + write!(s, " ").unwrap(); + } + write!(s, "}}").unwrap(); + s + } +} + +impl Render for Vec { + fn to_string(&self, maps: &AtomMaps) -> String { + let mut s = String::new(); + write!(s, "[").unwrap(); + let mut first = true; + for x in self { + if !first { + write!(s, ", ").unwrap(); + } + write!(s, "{}", x.to_string(maps)).unwrap(); + } + write!(s, "]").unwrap(); + s + } +} + +impl Render for (A, B) { + fn to_string(&self, maps: &AtomMaps) -> String { + format!("({}, {})", self.0.to_string(maps), self.1.to_string(maps)) + } +} + +impl Render for Origin { + fn to_string(&self, maps: &AtomMaps) -> String { + // TODO + format!("{:?}", self) + } +} + +impl Render for Loan { + fn to_string(&self, maps: &AtomMaps) -> String { + // TODO + format!("{:?}", self) + } +} + +impl Render for Point { fn to_string(&self, maps: &AtomMaps) -> String { let (bb, idx, sub) = maps.get_point(*self); format!("{:?}({:?}[{}])", sub, bb, idx) } } -/* -impl FactCell for A { - default fn to_string(&self, _maps: &AtomMaps) -> String { - format!("{:?}", self) +impl Render for Variable { + fn to_string(&self, maps: &AtomMaps) -> String { + // TODO + format!("{:?}", maps.get_variable(*self)) } } -impl FactCell for LocationIndex { +impl Render for Path { fn to_string(&self, maps: &AtomMaps) -> String { - format!("{:?}", maps.to_location(*self)) + // TODO + format!("{:?}", self) } } -*/ diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index ce10971d76..dd3e70798b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -1,5 +1,6 @@ #![feature(rustc_private)] extern crate rustc_ast; +extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_interface; extern crate rustc_middle; @@ -67,6 +68,13 @@ fn inspect_mir<'tcx>( let name = tcx.item_name(def.to_global().did); dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); + + let output = polonius_engine::Output::compute( + &facts, + polonius_engine::Algorithm::Naive, + true, + ); + dump::dump_output_to_dir(&output, &maps, format!("inspect/{}", name)).unwrap(); } From 7566581c7179a0fe758904ab5c5f64f8100594d6 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 15 Mar 2022 15:35:51 -0700 Subject: [PATCH 03/38] analyze: generate var_defined/used_at facts --- c2rust-analyze/src/atoms.rs | 6 +- c2rust-analyze/src/def_use.rs | 119 +++++++++++++++++++++++++++++ c2rust-analyze/src/dump.rs | 21 +++-- c2rust-analyze/src/main.rs | 14 +++- examples/static-analysis/alias1.rs | 38 +++++---- 5 files changed, 173 insertions(+), 25 deletions(-) create mode 100644 c2rust-analyze/src/def_use.rs diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/atoms.rs index 87e39e51e7..98debe495e 100644 --- a/c2rust-analyze/src/atoms.rs +++ b/c2rust-analyze/src/atoms.rs @@ -1,7 +1,7 @@ use std::collections::hash_map::{HashMap, Entry}; use std::hash::Hash; use polonius_engine::{self, Atom, FactTypes}; -use rustc_middle::mir::{BasicBlock, Local}; +use rustc_middle::mir::{BasicBlock, Local, Location}; macro_rules! define_atom_type { ($Atom:ident) => { @@ -108,6 +108,10 @@ impl AtomMaps { self.point.add((bb, idx, sub)) } + pub fn point_mid_location(&mut self, loc: Location) -> Point { + self.point(loc.block, loc.statement_index, SubPoint::Mid) + } + pub fn get_point(&self, x: Point) -> (BasicBlock, usize, SubPoint) { self.point.get(x) } diff --git a/c2rust-analyze/src/def_use.rs b/c2rust-analyze/src/def_use.rs new file mode 100644 index 0000000000..f8d105be71 --- /dev/null +++ b/c2rust-analyze/src/def_use.rs @@ -0,0 +1,119 @@ +use rustc_middle::mir::{Body, Place, Local, Location}; +use rustc_middle::mir::visit::{ + Visitor, PlaceContext, MutatingUseContext, NonMutatingUseContext, NonUseContext, +}; +use crate::atoms::{AllFacts, AtomMaps}; + + +// From `rustc_borrowck/src/def_use.rs`, licensed MIT/Apache2 +#[derive(Eq, PartialEq, Clone, Debug)] +pub enum DefUse { + Def, + Use, + Drop, +} + +// From `rustc_borrowck/src/def_use.rs`, licensed MIT/Apache2 +pub fn categorize(context: PlaceContext) -> Option { + match context { + /////////////////////////////////////////////////////////////////////////// + // DEFS + + PlaceContext::MutatingUse(MutatingUseContext::Store) | + + // We let Call define the result in both the success and + // unwind cases. This is not really correct, however it + // does not seem to be observable due to the way that we + // generate MIR. To do things properly, we would apply + // the def in call only to the input from the success + // path and not the unwind path. -nmatsakis + PlaceContext::MutatingUse(MutatingUseContext::Call) | + PlaceContext::MutatingUse(MutatingUseContext::AsmOutput) | + PlaceContext::MutatingUse(MutatingUseContext::Yield) | + + // Storage live and storage dead aren't proper defines, but we can ignore + // values that come before them. + PlaceContext::NonUse(NonUseContext::StorageLive) | + PlaceContext::NonUse(NonUseContext::StorageDead) => Some(DefUse::Def), + + /////////////////////////////////////////////////////////////////////////// + // REGULAR USES + // + // These are uses that occur *outside* of a drop. For the + // purposes of NLL, these are special in that **all** the + // lifetimes appearing in the variable must be live for each regular use. + + PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) | + PlaceContext::MutatingUse(MutatingUseContext::Projection) | + + // Borrows only consider their local used at the point of the borrow. + // This won't affect the results since we use this analysis for generators + // and we only care about the result at suspension points. Borrows cannot + // cross suspension points so this behavior is unproblematic. + PlaceContext::MutatingUse(MutatingUseContext::Borrow) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::SharedBorrow) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::ShallowBorrow) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::UniqueBorrow) | + + PlaceContext::MutatingUse(MutatingUseContext::AddressOf) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::AddressOf) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::Inspect) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy) | + PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) | + PlaceContext::NonUse(NonUseContext::AscribeUserTy) | + PlaceContext::MutatingUse(MutatingUseContext::Retag) => + Some(DefUse::Use), + + /////////////////////////////////////////////////////////////////////////// + // DROP USES + // + // These are uses that occur in a DROP (a MIR drop, not a + // call to `std::mem::drop()`). For the purposes of NLL, + // uses in drop are special because `#[may_dangle]` + // attributes can affect whether lifetimes must be live. + + PlaceContext::MutatingUse(MutatingUseContext::Drop) => + Some(DefUse::Drop), + + // Debug info is neither def nor use. + PlaceContext::NonUse(NonUseContext::VarDebugInfo) => None, + } +} + + +struct DefUseVisitor<'a> { + facts: &'a mut AllFacts, + maps: &'a mut AtomMaps, +} + +impl Visitor<'_> for DefUseVisitor<'_> { + fn visit_place(&mut self, place: &Place, context: PlaceContext, location: Location) { + self.super_place(place, context, location); + eprintln!("visit place {:?} with context {:?} = {:?} at {:?}", + place, context, categorize(context), location); + } + + fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) { + eprintln!("visit local {:?} with context {:?} = {:?} at {:?}", + local, context, categorize(context), location); + let var = self.maps.variable(*local); + let point = self.maps.point_mid_location(location); + match categorize(context) { + Some(DefUse::Def) => { + self.facts.var_defined_at.push((var, point)); + }, + Some(DefUse::Use) => { + self.facts.var_used_at.push((var, point)); + }, + Some(DefUse::Drop) => { + self.facts.var_dropped_at.push((var, point)); + }, + None => {}, + } + } +} + +pub fn visit(facts: &mut AllFacts, maps: &mut AtomMaps, mir: &Body) { + let mut v = DefUseVisitor { facts, maps }; + v.visit_body(mir); +} diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs index 337e6e07fb..457b6107a4 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/dump.rs @@ -38,9 +38,9 @@ pub fn dump_facts_to_dir( //loan_killed_at, //subset_base, //loan_invalidated_at, - //var_used_at, - //var_defined_at, - //var_dropped_at, + var_used_at, + var_defined_at, + var_dropped_at, //use_of_var_derefs_origin, //drop_of_var_derefs_origin, //child_path, @@ -203,14 +203,16 @@ trait OutputTable { ) -> Result<(), Box>; } -impl OutputTable for FxHashMap { +impl OutputTable for FxHashMap { fn write( &self, out: &mut dyn Write, maps: &AtomMaps, ) -> Result<(), Box> { - for (k, v) in self { - write!(out, "{}: {}", k.to_string(maps), v.to_string(maps))?; + let mut entries = self.iter().collect::>(); + entries.sort_by_key(|&(k, _)| k); + for (k, v) in entries { + writeln!(out, "{}: {}", k.to_string(maps), v.to_string(maps))?; } Ok(()) } @@ -222,7 +224,7 @@ impl OutputTable for bool { out: &mut dyn Write, maps: &AtomMaps, ) -> Result<(), Box> { - write!(out, "{}", self)?; + writeln!(out, "{}", self)?; Ok(()) } } @@ -241,6 +243,7 @@ impl Render for FxHashMap { if !first { write!(s, ",").unwrap(); } + first = false; write!(s, " {}: {}", k.to_string(maps), v.to_string(maps)).unwrap(); } if !first { @@ -260,6 +263,7 @@ impl Render for FxHashSet { if !first { write!(s, ",").unwrap(); } + first = false; write!(s, " {}", x.to_string(maps)).unwrap(); } if !first { @@ -279,6 +283,7 @@ impl Render for BTreeMap { if !first { write!(s, ",").unwrap(); } + first = false; write!(s, " {}: {}", k.to_string(maps), v.to_string(maps)).unwrap(); } if !first { @@ -298,6 +303,7 @@ impl Render for BTreeSet { if !first { write!(s, ",").unwrap(); } + first = false; write!(s, " {}", x.to_string(maps)).unwrap(); } if !first { @@ -317,6 +323,7 @@ impl Render for Vec { if !first { write!(s, ", ").unwrap(); } + first = false; write!(s, "{}", x.to_string(maps)).unwrap(); } write!(s, "]").unwrap(); diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index dd3e70798b..37c3343489 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -25,6 +25,7 @@ use rustc_middle::mir::{ BorrowKind, Constant, ConstantKind, }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; +use rustc_middle::mir::pretty; use rustc_middle::ty::{TyCtxt, RegionKind, WithOptConstParam}; use rustc_middle::ty::query::{Providers, ExternProviders}; use rustc_session::Session; @@ -35,6 +36,7 @@ use rustc_target::abi::Align; use crate::atoms::{AllFacts, AtomMaps, SubPoint}; mod atoms; +mod def_use; mod dump; @@ -46,9 +48,16 @@ fn inspect_mir<'tcx>( let mut facts = AllFacts::default(); let mut maps = AtomMaps::default(); + let name = tcx.item_name(def.to_global().did); + eprintln!("\nprocessing function {:?}", name); + //pretty::write_mir_fn(tcx, mir, &mut |_, _| Ok(()), &mut std::io::stdout()).unwrap(); + // Populate `cfg_edge` for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + eprintln!("{:?}:", bb); + for idx in 0 .. bb_data.statements.len() { + eprintln!(" {}: {:?}", idx, bb_data.statements[idx]); let start = maps.point(bb, idx, SubPoint::Start); let mid = maps.point(bb, idx, SubPoint::Mid); let next_start = maps.point(bb, idx + 1, SubPoint::Start); @@ -57,6 +66,7 @@ fn inspect_mir<'tcx>( } let term_idx = bb_data.statements.len(); + eprintln!(" {}: {:?}", term_idx, bb_data.terminator()); let term_start = maps.point(bb, term_idx, SubPoint::Start); let term_mid = maps.point(bb, term_idx, SubPoint::Mid); facts.cfg_edge.push((term_start, term_mid)); @@ -66,7 +76,9 @@ fn inspect_mir<'tcx>( } } - let name = tcx.item_name(def.to_global().did); + self::def_use::visit(&mut facts, &mut maps, mir); + + dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); let output = polonius_engine::Output::compute( diff --git a/examples/static-analysis/alias1.rs b/examples/static-analysis/alias1.rs index dc39bc0896..b08d3da620 100644 --- a/examples/static-analysis/alias1.rs +++ b/examples/static-analysis/alias1.rs @@ -1,24 +1,30 @@ -pub unsafe fn alias1_good(p: *mut i32) { - let q = p; - let r = p; - *r = 1; -} +use std::ptr; -pub unsafe fn alias1_bad(p: *mut i32) { - let q = p; - let r = p; +pub unsafe fn alias1_good() { + let mut x = 0; + let p = ptr::addr_of_mut!(x); + let q = ptr::addr_of_mut!(x); *q = 1; } - -pub fn safe_alias1_good(p: &mut i32) { - let q = &mut *p; - let r = &mut *p; - *r = 1; +pub unsafe fn alias1_bad() { + let mut x = 0; + let p = ptr::addr_of_mut!(x); + let q = ptr::addr_of_mut!(x); + *p = 1; } -pub fn safe_alias1_bad(p: &mut i32) { - let q = &mut *p; - let r = &mut *p; + +pub fn safe_alias1_good() { + let mut x = 0; + let p = &mut x; + let q = &mut x; *q = 1; } + +pub fn safe_alias1_bad() { + let mut x = 0; + let p = &mut x; + let q = &mut x; + *p = 1; +} From 9c8f495039957806bd9c10cae02e16601fb7f798 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 15 Mar 2022 16:59:18 -0700 Subject: [PATCH 04/38] analyze: generate path_assigned/moved/accessed_at facts --- c2rust-analyze/src/atoms.rs | 52 +++++++++++++++++++++++++++++++++-- c2rust-analyze/src/def_use.rs | 51 ++++++++++++++++++++++++++++++---- c2rust-analyze/src/dump.rs | 22 +++++++-------- c2rust-analyze/src/main.rs | 16 +++++++++-- 4 files changed, 117 insertions(+), 24 deletions(-) diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/atoms.rs index 98debe495e..457b95ec10 100644 --- a/c2rust-analyze/src/atoms.rs +++ b/c2rust-analyze/src/atoms.rs @@ -1,7 +1,8 @@ use std::collections::hash_map::{HashMap, Entry}; use std::hash::Hash; use polonius_engine::{self, Atom, FactTypes}; -use rustc_middle::mir::{BasicBlock, Local, Location}; +use rustc_middle::mir::{BasicBlock, Local, Location, Place, PlaceElem}; +use rustc_middle::ty::TyCtxt; macro_rules! define_atom_type { ($Atom:ident) => { @@ -85,6 +86,20 @@ impl AtomMap { } } + pub fn add_new(&mut self, x: T) -> (A, bool) { + match self.thing_to_atom.entry(x.clone()) { + Entry::Occupied(e) => { + (*e.get(), false) + }, + Entry::Vacant(e) => { + let atom = A::from(self.atom_to_thing.len()); + self.atom_to_thing.push(x); + e.insert(atom); + (atom, true) + }, + } + } + pub fn get(&self, x: A) -> T { self.atom_to_thing[x.into()].clone() } @@ -99,11 +114,12 @@ pub enum SubPoint { #[derive(Clone, Debug, Default)] -pub struct AtomMaps { +pub struct AtomMaps<'tcx> { point: AtomMap<(BasicBlock, usize, SubPoint), Point>, + path: AtomMap<(Local, &'tcx [PlaceElem<'tcx>]), Path>, } -impl AtomMaps { +impl<'tcx> AtomMaps<'tcx> { pub fn point(&mut self, bb: BasicBlock, idx: usize, sub: SubPoint) -> Point { self.point.add((bb, idx, sub)) } @@ -123,6 +139,36 @@ impl AtomMaps { pub fn get_variable(&self, x: Variable) -> Local { Local::from_usize(x.0) } + + pub fn path(&mut self, facts: &mut AllFacts, place: Place<'tcx>) -> Path { + self.path_slice(facts, place.local, place.projection) + } + + fn path_slice( + &mut self, + facts: &mut AllFacts, + local: Local, + projection: &'tcx [PlaceElem<'tcx>], + ) -> Path { + let (path, new) = self.path.add_new((local, projection)); + if new { + if projection.len() == 0 { + let var = self.variable(local); + facts.path_is_var.push((path, var)); + } else { + let parent = self.path_slice(facts, local, &projection[.. projection.len() - 1]); + // TODO: check ordering of arguments here + facts.child_path.push((parent, path)); + } + } + path + } + + pub fn get_path(&self, tcx: TyCtxt<'tcx>, x: Path) -> Place<'tcx> { + let (local, projection) = self.path.get(x); + let projection = tcx.intern_place_elems(projection); + Place { local, projection } + } } diff --git a/c2rust-analyze/src/def_use.rs b/c2rust-analyze/src/def_use.rs index f8d105be71..3758d0dcd1 100644 --- a/c2rust-analyze/src/def_use.rs +++ b/c2rust-analyze/src/def_use.rs @@ -1,7 +1,8 @@ -use rustc_middle::mir::{Body, Place, Local, Location}; +use rustc_middle::mir::{Body, Place, Local, Location, Statement, StatementKind}; use rustc_middle::mir::visit::{ Visitor, PlaceContext, MutatingUseContext, NonMutatingUseContext, NonUseContext, }; +use rustc_middle::ty::List; use crate::atoms::{AllFacts, AtomMaps}; @@ -81,16 +82,41 @@ pub fn categorize(context: PlaceContext) -> Option { } -struct DefUseVisitor<'a> { +struct DefUseVisitor<'tcx, 'a> { facts: &'a mut AllFacts, - maps: &'a mut AtomMaps, + maps: &'a mut AtomMaps<'tcx>, } -impl Visitor<'_> for DefUseVisitor<'_> { - fn visit_place(&mut self, place: &Place, context: PlaceContext, location: Location) { +impl<'tcx> Visitor<'tcx> for DefUseVisitor<'tcx, '_> { + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { self.super_place(place, context, location); eprintln!("visit place {:?} with context {:?} = {:?} at {:?}", place, context, categorize(context), location); + + if place.is_indirect() { + // TODO + return; + } + + let path = self.maps.path(self.facts, *place); + let point = self.maps.point_mid_location(location); + + // TODO: figure out when exactly paths should be recorded as assigned/accessed/moved + if let PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) = context { + self.facts.path_moved_at_base.push((path, point)); + return; + } + + match categorize(context) { + Some(DefUse::Def) => { + self.facts.path_assigned_at_base.push((path, point)); + }, + Some(DefUse::Use) => { + self.facts.path_accessed_at_base.push((path, point)); + }, + Some(DefUse::Drop) => {}, + None => {}, + } } fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) { @@ -111,9 +137,22 @@ impl Visitor<'_> for DefUseVisitor<'_> { None => {}, } } + + fn visit_statement(&mut self, stmt: &Statement<'tcx>, location: Location) { + self.super_statement(stmt, location); + eprintln!("visit stmt {:?} at {:?}", stmt, location); + + if let StatementKind::StorageDead(local) = stmt.kind { + // Observed: `StorageDead` emits `path_moved_at_base` at the `Mid` point. + let place = Place { local, projection: List::empty() }; + let path = self.maps.path(self.facts, place); + let point = self.maps.point_mid_location(location); + self.facts.path_moved_at_base.push((path, point)); + } + } } -pub fn visit(facts: &mut AllFacts, maps: &mut AtomMaps, mir: &Body) { +pub fn visit<'tcx>(facts: &mut AllFacts, maps: &mut AtomMaps<'tcx>, mir: &Body<'tcx>) { let mut v = DefUseVisitor { facts, maps }; v.visit_body(mir); } diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs index 457b6107a4..95b3a2c926 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/dump.rs @@ -43,11 +43,11 @@ pub fn dump_facts_to_dir( var_dropped_at, //use_of_var_derefs_origin, //drop_of_var_derefs_origin, - //child_path, - //path_is_var, - //path_assigned_at_base, - //path_moved_at_base, - //path_accessed_at_base, + child_path, + path_is_var, + path_assigned_at_base, + path_moved_at_base, + path_accessed_at_base, //known_placeholder_subset, //placeholder, ]) @@ -100,12 +100,12 @@ pub fn dump_output_to_dir( Ok(()) } -struct FactWriter<'w> { - maps: &'w AtomMaps, +struct FactWriter<'tcx, 'w> { + maps: &'w AtomMaps<'tcx>, dir: &'w path::Path, } -impl<'w> FactWriter<'w> { +impl FactWriter<'_, '_> { fn write_facts_to_path(&self, rows: &[T], file_name: &str) -> Result<(), Box> where T: FactRow, @@ -360,14 +360,12 @@ impl Render for Point { impl Render for Variable { fn to_string(&self, maps: &AtomMaps) -> String { - // TODO - format!("{:?}", maps.get_variable(*self)) + format!("_{}", usize::from(*self)) } } impl Render for Path { fn to_string(&self, maps: &AtomMaps) -> String { - // TODO - format!("{:?}", self) + format!("mp{}", usize::from(*self)) } } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 37c3343489..345876d72a 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -21,12 +21,12 @@ use rustc_interface::Queries; use rustc_interface::interface::Compiler; use rustc_middle::mir::{ Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, Mutability, Rvalue, AggregateKind, Place, Operand, Statement, StatementKind, - BorrowKind, Constant, ConstantKind, + LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, Operand, Statement, + StatementKind, BorrowKind, Constant, ConstantKind, }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; use rustc_middle::mir::pretty; -use rustc_middle::ty::{TyCtxt, RegionKind, WithOptConstParam}; +use rustc_middle::ty::{TyCtxt, RegionKind, WithOptConstParam, List}; use rustc_middle::ty::query::{Providers, ExternProviders}; use rustc_session::Session; use rustc_span::DUMMY_SP; @@ -76,6 +76,16 @@ fn inspect_mir<'tcx>( } } + // From rustc_borrowck::nll::populate_polonius_move_facts: "Non-arguments start out + // deinitialised; we simulate this with an initial move" + let entry_point = maps.point(START_BLOCK, 0, SubPoint::Start); + for local in mir.local_decls.indices() { + if mir.local_kind(local) != LocalKind::Arg { + let path = maps.path(&mut facts, Place { local, projection: List::empty() }); + facts.path_moved_at_base.push((path, entry_point)); + } + } + self::def_use::visit(&mut facts, &mut maps, mir); From 66a550bfe06bd30bd4764a45716b3b6d30471cd2 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 17 Mar 2022 13:27:32 -0700 Subject: [PATCH 05/38] analyze: generate use_of_var_derefs_region facts --- c2rust-analyze/src/atoms.rs | 7 + c2rust-analyze/src/dump.rs | 7 +- c2rust-analyze/src/labeled_ty.rs | 258 +++++++++++++++++++++++++++++++ c2rust-analyze/src/main.rs | 36 ++++- 4 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 c2rust-analyze/src/labeled_ty.rs diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/atoms.rs index 457b95ec10..aeb7cf1241 100644 --- a/c2rust-analyze/src/atoms.rs +++ b/c2rust-analyze/src/atoms.rs @@ -115,11 +115,18 @@ pub enum SubPoint { #[derive(Clone, Debug, Default)] pub struct AtomMaps<'tcx> { + next_origin: usize, point: AtomMap<(BasicBlock, usize, SubPoint), Point>, path: AtomMap<(Local, &'tcx [PlaceElem<'tcx>]), Path>, } impl<'tcx> AtomMaps<'tcx> { + pub fn origin(&mut self) -> Origin { + let idx = self.next_origin; + self.next_origin += 1; + Origin(idx) + } + pub fn point(&mut self, bb: BasicBlock, idx: usize, sub: SubPoint) -> Point { self.point.add((bb, idx, sub)) } diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs index 95b3a2c926..86ceb05bbc 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/dump.rs @@ -41,8 +41,8 @@ pub fn dump_facts_to_dir( var_used_at, var_defined_at, var_dropped_at, - //use_of_var_derefs_origin, - //drop_of_var_derefs_origin, + use_of_var_derefs_origin, + drop_of_var_derefs_origin, child_path, path_is_var, path_assigned_at_base, @@ -339,8 +339,7 @@ impl Render for (A, B) { impl Render for Origin { fn to_string(&self, maps: &AtomMaps) -> String { - // TODO - format!("{:?}", self) + format!("'_#{}r", usize::from(*self)) } } diff --git a/c2rust-analyze/src/labeled_ty.rs b/c2rust-analyze/src/labeled_ty.rs new file mode 100644 index 0000000000..8aab81868a --- /dev/null +++ b/c2rust-analyze/src/labeled_ty.rs @@ -0,0 +1,258 @@ +//! Provides a wrapper around `rustc::ty::Ty` with a label attached to each type constructor. We +//! use this, for example, to attach a Polonius `Origin` to every reference type. Labeled type +//! data is manipulated by reference, the same as with `Ty`s, and the data is stored in the same +//! arena as the underlying `Ty`s. +use rustc_arena::DroplessArena; +use rustc_middle::arena::Arena; +use rustc_middle::ty::{TyCtxt, Ty, TyKind}; +use std::fmt; +use std::marker::PhantomData; +use std::ops::Deref; + +/// The actual data for a labeled type. +/// +/// This struct shouldn't be constructed directly - instead, use `LabeledTyCtxt` methods to build +/// instances inside the tcx arena and return `LabeledTy` references. +/// +/// Labeled types have to mimic the tree structure of the underlying `Ty`, so that each type +/// constructor in the tree can have its own label. But maintaining a custom copy of +/// `TyKind` would be annoying, so instead, we let labeled types form arbitrary trees, and +/// make the `LabeledTyCtxt` responsible for making those trees match the `Ty`'s structure. +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct LabeledTyS<'tcx, L: 'tcx> { + /// The underlying type. + pub ty: Ty<'tcx>, + /// The arguments of this type constructor. The number and meaning of these arguments depends + /// on which type constructor this is (specifically, which `TyKind` variant is used for + /// `self.ty.sty`). + pub args: &'tcx [LabeledTy<'tcx, L>], + /// The label for the current type constructor. + pub label: L, +} + +/// A labeled type. Like `rustc::ty::Ty`, this is a reference to some arena-allocated data. +pub type LabeledTy<'tcx, L> = &'tcx LabeledTyS<'tcx, L>; + +impl<'tcx, L: fmt::Debug> fmt::Debug for LabeledTyS<'tcx, L> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}#{:?}{:?}", self.label, self.ty, self.args) + } +} + +impl<'tcx, L: Copy> LabeledTyS<'tcx, L> { + pub fn for_each_label(&'tcx self, callback: &mut F) { + callback(self.label); + for &arg in self.args { + arg.for_each_label(callback); + } + } +} + +/// Context for constructing `LabeledTy`s. +pub struct LabeledTyCtxt<'tcx, L: 'tcx> { + tcx: TyCtxt<'tcx>, + _marker: PhantomData, +} + +impl<'tcx, L> Clone for LabeledTyCtxt<'tcx, L> { + fn clone(&self) -> LabeledTyCtxt<'tcx, L> { + *self + } +} + +impl<'tcx, L> Copy for LabeledTyCtxt<'tcx, L> {} + +impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { + /// Build a new `LabeledTyCtxt`. The `arena` must be the same one used by the `TyCtxt` that + /// built the underlying `Ty`s to be labeled. + pub fn new(tcx: TyCtxt<'tcx>) -> LabeledTyCtxt<'tcx, L> { + LabeledTyCtxt { + tcx, + _marker: PhantomData, + } + } + + fn arena(&self) -> &'tcx DroplessArena { + &self.tcx.arena.dropless + } + + /// Manually construct a slice in the context's arena. + pub fn mk_slice(&self, ltys: &[LabeledTy<'tcx, L>]) -> &'tcx [LabeledTy<'tcx, L>] { + if ltys.is_empty() { + return &[]; + } + self.arena().alloc_slice(ltys) + } + + /// Manually construct a labeled type. Note that this does not do any checks on `args`! The + /// caller is responsible for making sure the number of arguments matches `ty.sty`. + pub fn mk( + &self, + ty: Ty<'tcx>, + args: &'tcx [LabeledTy<'tcx, L>], + label: L, + ) -> LabeledTy<'tcx, L> { + self.arena().alloc(LabeledTyS { + ty, + args, + label, + }) + } + + /// Label a `Ty` using a callback. The callback runs at every type constructor to produce a + /// label for that node in the tree. + pub fn label) -> L>( + &self, + ty: Ty<'tcx>, + f: &mut F, + ) -> LabeledTy<'tcx, L> { + use rustc_middle::ty::TyKind::*; + let label = f(ty); + match ty.kind() { + // Types with no arguments + Bool | Char | Int(_) | Uint(_) | Float(_) | Str | Foreign(_) | Never => { + self.mk(ty, &[], label) + } + + // Types with arguments + Adt(_, substs) => { + let args = substs.types().map(|t| self.label(t, f)).collect::>(); + self.mk(ty, self.mk_slice(&args), label) + } + Array(elem, _) => { + let args = [self.label(elem, f)]; + self.mk(ty, self.mk_slice(&args), label) + } + Slice(elem) => { + let args = [self.label(elem, f)]; + self.mk(ty, self.mk_slice(&args), label) + } + RawPtr(mty) => { + let args = [self.label(mty.ty, f)]; + self.mk(ty, self.mk_slice(&args), label) + } + Ref(_, mty, _) => { + let args = [self.label(mty, f)]; + self.mk(ty, self.mk_slice(&args), label) + } + FnDef(_, substs) => { + let args = substs + .types() + .map(|ty| self.label(ty, f)) + .collect::>(); + self.mk(ty, self.mk_slice(&args), label) + } + FnPtr(ref sig) => { + let args = sig + .skip_binder() + .inputs_and_output + .iter() + .map(|ty| self.label(ty, f)) + .collect::>(); + self.mk(ty, self.mk_slice(&args), label) + } + Tuple(ref elems) => { + let args = elems.types().map(|ty| self.label(ty, f)).collect::>(); + self.mk(ty, self.mk_slice(&args), label) + } + + // Types that aren't actually supported by this code yet + Dynamic(..) + | Closure(..) + | Generator(..) + | GeneratorWitness(..) + | Projection(..) + | Opaque(..) + | Param(..) + | Bound(..) + | Placeholder(..) + | Infer(..) + | Error(..) => self.mk(ty, &[], label), + } + } + + /// Label multiple `Ty`s using a callback. + pub fn label_slice(&self, tys: &[Ty<'tcx>], f: &mut F) -> &'tcx [LabeledTy<'tcx, L>] + where + F: FnMut(Ty<'tcx>) -> L, + { + self.mk_slice(&tys.iter().map(|ty| self.label(ty, f)).collect::>()) + } + + /// Substitute in arguments for any type parameter references (`Param`) in a labeled type. + /// Panics if `lty` contains a reference to a type parameter that is past the end of `substs` + /// (usually this means the caller is providing the wrong list of type arguments as `substs`). + /// + /// TODO: This produces a `LabeledTy` with the right structure, but doesn't actually do + /// substitution on the underlying `Ty`s! This means if you substitute `u32` for `T`, you can + /// end up with a `LabeledTy` whose `ty` is `S`, but whose args are `[u32]`. By some + /// miracle, this hasn't broken anything yet, but we may need to fix it eventually. + pub fn subst( + &self, + lty: LabeledTy<'tcx, L>, + substs: &[LabeledTy<'tcx, L>], + ) -> LabeledTy<'tcx, L> { + if let TyKind::Param(ref ty) = lty.ty.kind() { + if let Some(p) = substs.get(ty.index as usize) { + return p; + } + } + + self.mk( + lty.ty, + self.subst_slice(lty.args, substs), + lty.label.clone(), + ) + } + + /// Substitute arguments in multiple labeled types. + pub fn subst_slice( + &self, + ltys: &[LabeledTy<'tcx, L>], + substs: &[LabeledTy<'tcx, L>], + ) -> &'tcx [LabeledTy<'tcx, L>] { + self.mk_slice( + <ys + .iter() + .map(|lty| self.subst(lty, substs)) + .collect::>(), + ) + } + + /// Run a callback to replace the labels on a type. + pub fn relabel( + &self, + lty: LabeledTy<'tcx, L2>, + func: &mut F, + ) -> LabeledTy<'tcx, L> + where + F: FnMut(&L2) -> L, + { + let args = self.relabel_slice(lty.args, func); + self.mk(lty.ty, args, func(<y.label)) + } + + /// Replace the labels on several labeled types. + pub fn relabel_slice( + &self, + ltys: &'tcx [LabeledTy<'tcx, L2>], + func: &mut F, + ) -> &'tcx [LabeledTy<'tcx, L>] + where + F: FnMut(&L2) -> L, + { + let ltys = ltys + .iter() + .cloned() + .map(|lty| self.relabel(lty, func)) + .collect::>(); + self.mk_slice(<ys) + } +} + +impl<'tcx, L> Deref for LabeledTyCtxt<'tcx, L> { + type Target = TyCtxt<'tcx>; + fn deref(&self) -> &TyCtxt<'tcx> { + &self.tcx + } +} diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 345876d72a..8942f4e33f 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_data_structures; extern crate rustc_driver; @@ -26,7 +27,7 @@ use rustc_middle::mir::{ }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; use rustc_middle::mir::pretty; -use rustc_middle::ty::{TyCtxt, RegionKind, WithOptConstParam, List}; +use rustc_middle::ty::{TyCtxt, Ty, TyKind, RegionKind, WithOptConstParam, List}; use rustc_middle::ty::query::{Providers, ExternProviders}; use rustc_session::Session; use rustc_span::DUMMY_SP; @@ -34,10 +35,17 @@ use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; use crate::atoms::{AllFacts, AtomMaps, SubPoint}; +use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; mod atoms; mod def_use; mod dump; +mod labeled_ty; + + +pub type Label = Option; +pub type LTy<'tcx> = LabeledTy<'tcx, Label>; +pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, Label>; fn inspect_mir<'tcx>( @@ -86,6 +94,18 @@ fn inspect_mir<'tcx>( } } + // Populate `use_of_var_derefs_origin`. + let ltcx = LabeledTyCtxt::new(tcx); + for (local, decl) in mir.local_decls.iter_enumerated() { + let lty = assign_origins(ltcx, &mut facts, &mut maps, decl.ty); + let var = maps.variable(local); + lty.for_each_label(&mut |label| { + if let Some(origin) = label { + facts.use_of_var_derefs_origin.push((var, origin)); + } + }); + } + self::def_use::visit(&mut facts, &mut maps, mir); @@ -99,6 +119,20 @@ fn inspect_mir<'tcx>( dump::dump_output_to_dir(&output, &maps, format!("inspect/{}", name)).unwrap(); } +fn assign_origins<'tcx>( + ltcx: LTyCtxt<'tcx>, + facts: &mut AllFacts, + maps: &mut AtomMaps<'tcx>, + ty: Ty<'tcx>, +) -> LTy<'tcx> { + ltcx.label(ty, &mut |ty| match ty.kind() { + TyKind::Ref(_, _, _) => { + Some(maps.origin()) + }, + _ => None, + }) +} + struct AnalysisCallbacks; From 0ba67e31a7d0fa98d8ef1b8bb1f0c5b80f812166 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 17 Mar 2022 14:57:14 -0700 Subject: [PATCH 06/38] analyze: generate load_issued/invalidated_at facts --- c2rust-analyze/src/atoms.rs | 12 +++++ c2rust-analyze/src/def_use.rs | 97 +++++++++++++++++++++++++++++++++-- c2rust-analyze/src/dump.rs | 9 ++-- c2rust-analyze/src/main.rs | 38 +++++++++++++- 4 files changed, 146 insertions(+), 10 deletions(-) diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/atoms.rs index aeb7cf1241..987b78151f 100644 --- a/c2rust-analyze/src/atoms.rs +++ b/c2rust-analyze/src/atoms.rs @@ -116,6 +116,7 @@ pub enum SubPoint { #[derive(Clone, Debug, Default)] pub struct AtomMaps<'tcx> { next_origin: usize, + next_loan: usize, point: AtomMap<(BasicBlock, usize, SubPoint), Point>, path: AtomMap<(Local, &'tcx [PlaceElem<'tcx>]), Path>, } @@ -127,6 +128,12 @@ impl<'tcx> AtomMaps<'tcx> { Origin(idx) } + pub fn loan(&mut self) -> Loan { + let idx = self.next_loan; + self.next_loan += 1; + Loan(idx) + } + pub fn point(&mut self, bb: BasicBlock, idx: usize, sub: SubPoint) -> Point { self.point.add((bb, idx, sub)) } @@ -176,6 +183,11 @@ impl<'tcx> AtomMaps<'tcx> { let projection = tcx.intern_place_elems(projection); Place { local, projection } } + + pub fn get_path_projection(&self, tcx: TyCtxt<'tcx>, x: Path) -> &'tcx [PlaceElem<'tcx>] { + let (local, projection) = self.path.get(x); + projection + } } diff --git a/c2rust-analyze/src/def_use.rs b/c2rust-analyze/src/def_use.rs index 3758d0dcd1..616b787d5f 100644 --- a/c2rust-analyze/src/def_use.rs +++ b/c2rust-analyze/src/def_use.rs @@ -1,9 +1,13 @@ -use rustc_middle::mir::{Body, Place, Local, Location, Statement, StatementKind}; +use std::cmp; +use std::collections::HashMap; +use rustc_middle::mir::{ + Body, Place, Local, Location, Statement, StatementKind, ProjectionElem, BorrowKind, +}; use rustc_middle::mir::visit::{ Visitor, PlaceContext, MutatingUseContext, NonMutatingUseContext, NonUseContext, }; -use rustc_middle::ty::List; -use crate::atoms::{AllFacts, AtomMaps}; +use rustc_middle::ty::{TyCtxt, List}; +use crate::atoms::{AllFacts, AtomMaps, Path, Loan, SubPoint}; // From `rustc_borrowck/src/def_use.rs`, licensed MIT/Apache2 @@ -156,3 +160,90 @@ pub fn visit<'tcx>(facts: &mut AllFacts, maps: &mut AtomMaps<'tcx>, mir: &Body<' let mut v = DefUseVisitor { facts, maps }; v.visit_body(mir); } + + +struct LoanInvalidatedAtVisitor<'tcx, 'a> { + tcx: TyCtxt<'tcx>, + facts: &'a mut AllFacts, + maps: &'a mut AtomMaps<'tcx>, + loans: &'a HashMap>, +} + +impl<'tcx> LoanInvalidatedAtVisitor<'tcx, '_> { + /// Handle an access of a path overlapping `loan` in `context` at `location`. `borrow_kind` is + /// the original kind of the loan. + fn access_loan_at_location( + &mut self, + loan: Loan, + borrow_kind: BorrowKind, + context: PlaceContext, + location: Location, + ) { + let invalidate = match (borrow_kind, categorize(context)) { + (BorrowKind::Shared, Some(DefUse::Use)) => false, + (_, None) => false, + _ => true, + }; + if !invalidate { + return; + } + + let point = self.maps.point(location.block, location.statement_index, SubPoint::Start); + self.facts.loan_invalidated_at.push((point, loan)); + } +} + +impl<'tcx> Visitor<'tcx> for LoanInvalidatedAtVisitor<'tcx, '_> { + fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { + //self.super_place(place, context, location); + eprintln!("loan_invalidated_at: visit place {:?} with context {:?} = {:?} at {:?}", + place, context, categorize(context), location); + + if place.is_indirect() { + // TODO + return; + } + + let local_loans = self.loans.get(&place.local).map_or(&[] as &[_], |x| x); + for &(path, loan, borrow_kind) in local_loans { + let proj = self.maps.get_path_projection(self.tcx, path); + + // If `proj` is a prefix of `place.projection` or vice versa, then the paths overlap. + let common_len = cmp::min(proj.len(), place.projection.len()); + let overlap = proj[..common_len].iter().zip(place.projection[..common_len].iter()) + .all(|(&elem1, &elem2)| match (elem1, elem2) { + (ProjectionElem::Field(f1, _), ProjectionElem::Field(f2, _)) => f1 == f2, + (ProjectionElem::Index(_), ProjectionElem::Index(_)) => true, + // Conservatively assume that any unsupported variants overlap. + _ => true, + }); + if !overlap { + continue; + } + + self.access_loan_at_location(loan, borrow_kind, context, location); + } + } + + fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) { + eprintln!("loan_invalidated_at: visit local {:?} with context {:?} = {:?} at {:?}", + local, context, categorize(context), location); + + let local_loans = self.loans.get(&local).map_or(&[] as &[_], |x| x); + for &(path, loan, borrow_kind) in local_loans { + // All paths rooted in this local overlap the local. + self.access_loan_at_location(loan, borrow_kind, context, location); + } + } +} + +pub fn visit_loan_invalidated_at<'tcx>( + tcx: TyCtxt<'tcx>, + facts: &mut AllFacts, + maps: &mut AtomMaps<'tcx>, + loans: &HashMap>, + mir: &Body<'tcx>, +) { + let mut v = LoanInvalidatedAtVisitor { tcx, facts, maps, loans }; + v.visit_body(mir) +} diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs index 86ceb05bbc..b4c5cce706 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/dump.rs @@ -32,12 +32,12 @@ pub fn dump_facts_to_dir( } write_facts_to_path! { wr.write_facts_to_path(facts.[ - //loan_issued_at, + loan_issued_at, //universal_region, cfg_edge, - //loan_killed_at, + loan_killed_at, //subset_base, - //loan_invalidated_at, + loan_invalidated_at, var_used_at, var_defined_at, var_dropped_at, @@ -345,8 +345,7 @@ impl Render for Origin { impl Render for Loan { fn to_string(&self, maps: &AtomMaps) -> String { - // TODO - format!("{:?}", self) + format!("bw{}", usize::from(*self)) } } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 8942f4e33f..8cb2ac61ad 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -10,6 +10,7 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; +use std::collections::HashMap; use std::env; use std::hash::Hash; use std::mem; @@ -34,7 +35,7 @@ use rustc_span::DUMMY_SP; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; -use crate::atoms::{AllFacts, AtomMaps, SubPoint}; +use crate::atoms::{AllFacts, AtomMaps, SubPoint, Path, Loan}; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; mod atoms; @@ -56,6 +57,12 @@ fn inspect_mir<'tcx>( let mut facts = AllFacts::default(); let mut maps = AtomMaps::default(); + // Start the origin counter at 3. This has no effect on the semantics, but makes for easier + // diffs between our facts and the facts generated by rustc. + for _ in 0..3 { + let _ = maps.origin(); + } + let name = tcx.item_name(def.to_global().did); eprintln!("\nprocessing function {:?}", name); //pretty::write_mir_fn(tcx, mir, &mut |_, _| Ok(()), &mut std::io::stdout()).unwrap(); @@ -94,6 +101,29 @@ fn inspect_mir<'tcx>( } } + let mut loans = HashMap::>::new(); + // Populate `loan_issued_at` and `loans`. + // TODO: also populate `subset_base` here + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + for (idx, stmt) in bb_data.statements.iter().enumerate() { + let (lhs, rhs) = match stmt.kind { + StatementKind::Assign(ref x) => (&x.0, &x.1), + _ => continue, + }; + let (borrow_kind, place) = match *rhs { + Rvalue::Ref(_, bk, p) => (bk, p), + _ => continue, + }; + // TODO: need to do something with this origin (so it shows up in subset_base) + let origin = maps.origin(); + let path = maps.path(&mut facts, place); + let loan = maps.loan(); + loans.entry(place.local).or_default().push((path, loan, borrow_kind)); + let point = maps.point(bb, idx, SubPoint::Mid); + facts.loan_issued_at.push((origin, loan, point)); + } + } + // Populate `use_of_var_derefs_origin`. let ltcx = LabeledTyCtxt::new(tcx); for (local, decl) in mir.local_decls.iter_enumerated() { @@ -106,7 +136,11 @@ fn inspect_mir<'tcx>( }); } - self::def_use::visit(&mut facts, &mut maps, mir); + // Populate `loan_invalidated_at` + def_use::visit_loan_invalidated_at(tcx, &mut facts, &mut maps, &loans, mir); + + // Populate `var_defined/used/dropped_at` and `path_assigned/accessed_at_base`. + def_use::visit(&mut facts, &mut maps, mir); dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); From c2d619a3190c87e9c99957a226c90acf14aa2886 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 21 Mar 2022 16:54:38 -0700 Subject: [PATCH 07/38] analyze: generate subset_base facts --- c2rust-analyze/src/dump.rs | 2 +- c2rust-analyze/src/main.rs | 32 ++++--- c2rust-analyze/src/type_check.rs | 148 +++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 c2rust-analyze/src/type_check.rs diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/dump.rs index b4c5cce706..0e0d105a2e 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/dump.rs @@ -36,7 +36,7 @@ pub fn dump_facts_to_dir( //universal_region, cfg_edge, loan_killed_at, - //subset_base, + subset_base, loan_invalidated_at, var_used_at, var_defined_at, diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 8cb2ac61ad..c98e86bb5e 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -3,6 +3,7 @@ extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_data_structures; extern crate rustc_driver; +extern crate rustc_index; extern crate rustc_interface; extern crate rustc_middle; extern crate rustc_mir_build; @@ -42,6 +43,7 @@ mod atoms; mod def_use; mod dump; mod labeled_ty; +mod type_check; pub type Label = Option; @@ -101,8 +103,25 @@ fn inspect_mir<'tcx>( } } + // Populate `use_of_var_derefs_origin`, and generate `LTy`s for all locals. + let ltcx = LabeledTyCtxt::new(tcx); + let mut local_ltys = Vec::with_capacity(mir.local_decls.len()); + for (local, decl) in mir.local_decls.iter_enumerated() { + let lty = assign_origins(ltcx, &mut facts, &mut maps, decl.ty); + let var = maps.variable(local); + lty.for_each_label(&mut |label| { + if let Some(origin) = label { + facts.use_of_var_derefs_origin.push((var, origin)); + } + }); + local_ltys.push(lty); + } + let mut loans = HashMap::>::new(); // Populate `loan_issued_at` and `loans`. + type_check::visit(ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); + /* + // Populate `loan_issued_at` and `loans`. // TODO: also populate `subset_base` here for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { for (idx, stmt) in bb_data.statements.iter().enumerate() { @@ -123,18 +142,7 @@ fn inspect_mir<'tcx>( facts.loan_issued_at.push((origin, loan, point)); } } - - // Populate `use_of_var_derefs_origin`. - let ltcx = LabeledTyCtxt::new(tcx); - for (local, decl) in mir.local_decls.iter_enumerated() { - let lty = assign_origins(ltcx, &mut facts, &mut maps, decl.ty); - let var = maps.variable(local); - lty.for_each_label(&mut |label| { - if let Some(origin) = label { - facts.use_of_var_derefs_origin.push((var, origin)); - } - }); - } + */ // Populate `loan_invalidated_at` def_use::visit_loan_invalidated_at(tcx, &mut facts, &mut maps, &loans, mir); diff --git a/c2rust-analyze/src/type_check.rs b/c2rust-analyze/src/type_check.rs new file mode 100644 index 0000000000..b79c3d3bc9 --- /dev/null +++ b/c2rust-analyze/src/type_check.rs @@ -0,0 +1,148 @@ +use std::collections::HashMap; +use rustc_index::vec::IndexVec; +use rustc_middle::mir::{ + Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, + Local, LocalDecl, Location, ProjectionElem, +}; +use crate::{LTy, LTyCtxt}; +use crate::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; + + +struct TypeChecker<'tcx, 'a> { + ltcx: LTyCtxt<'tcx>, + facts: &'a mut AllFacts, + maps: &'a mut AtomMaps<'tcx>, + loans: &'a mut HashMap>, + local_ltys: &'a [LTy<'tcx>], + local_decls: &'a IndexVec>, + + current_location: Location, +} + +impl<'tcx> TypeChecker<'tcx, '_> { + fn current_point(&mut self, sub: SubPoint) -> Point { + self.maps.point( + self.current_location.block, + self.current_location.statement_index, + sub, + ) + } + + pub fn visit_place(&mut self, pl: Place<'tcx>) -> LTy<'tcx> { + let mut lty = self.local_ltys[pl.local.index()]; + for proj in pl.projection { + match proj { + ProjectionElem::Deref => { + assert_eq!(lty.args.len(), 1); + lty = lty.args[0]; + }, + + ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), + } + } + lty + } + + pub fn visit_operand(&mut self, op: &Operand<'tcx>) -> LTy<'tcx> { + match *op { + Operand::Copy(pl) | + Operand::Move(pl) => self.visit_place(pl), + Operand::Constant(ref c) => { + let ty = c.ty(); + self.ltcx.label(ty, &mut |_| None) + }, + } + } + + pub fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>) -> LTy<'tcx> { + match *rv { + Rvalue::Use(ref op) => self.visit_operand(op), + + Rvalue::Ref(_, borrow_kind, pl) => { + // Create a new origin and issue an associated loan. + let origin = self.maps.origin(); + let path = self.maps.path(self.facts, pl); + let loan = self.maps.loan(); + self.loans.entry(pl.local).or_default().push((path, loan, borrow_kind)); + let point = self.current_point(SubPoint::Mid); + self.facts.loan_issued_at.push((origin, loan, point)); + + // Return a type with the new loan on the outermost `ref`. + let ty = rv.ty(self.local_decls, *self.ltcx); + let pl_lty = self.visit_place(pl); + let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), Some(origin)); + lty + }, + + Rvalue::AddressOf(mutbl, pl) => { + // TODO + let ty = rv.ty(self.local_decls, *self.ltcx); + let pl_lty = self.visit_place(pl); + let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), None); + lty + }, + + ref rv => panic!("unsupported rvalue {:?}", rv), + } + } + + pub fn visit_statement(&mut self, stmt: &Statement<'tcx>) { + match stmt.kind { + StatementKind::Assign(ref x) => { + let (pl, ref rv) = **x; + let pl_lty = self.visit_place(pl); + let rv_lty = self.visit_rvalue(rv); + eprintln!("assign {:?} = {:?}", pl_lty, rv_lty); + + if let (Some(pl_origin), Some(rv_origin)) = (pl_lty.label, rv_lty.label) { + let point = self.current_point(SubPoint::Mid); + self.facts.subset_base.push((rv_origin, pl_origin, point)); + } + }, + _ => {}, + } + } + + pub fn visit_terminator(&mut self, term: &Terminator<'tcx>) { + match term.kind { + _ => {}, + } + } +} + +pub fn visit<'tcx>( + ltcx: LTyCtxt<'tcx>, + facts: &mut AllFacts, + maps: &mut AtomMaps<'tcx>, + loans: &mut HashMap>, + local_ltys: &[LTy<'tcx>], + mir: &Body<'tcx>, +) { + let mut tc = TypeChecker { + ltcx, + facts, + maps, + loans, + local_ltys, + local_decls: &mir.local_decls, + current_location: Location::START, + }; + + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + for (idx, stmt) in bb_data.statements.iter().enumerate() { + tc.current_location = Location { + block: bb, + statement_index: idx, + }; + tc.visit_statement(stmt); + } + + tc.current_location = Location { + block: bb, + statement_index: bb_data.statements.len(), + }; + tc.visit_terminator(bb_data.terminator()); + } +} + + From 304b1c9e57a067371e2ee6c1209f540e2de42ba2 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 28 Mar 2022 11:31:01 -0700 Subject: [PATCH 08/38] analyze: refactor: move polonius stuff into new borrowck module --- c2rust-analyze/src/{ => borrowck}/atoms.rs | 0 c2rust-analyze/src/{ => borrowck}/def_use.rs | 2 +- c2rust-analyze/src/{ => borrowck}/dump.rs | 2 +- c2rust-analyze/src/borrowck/mod.rs | 163 ++++++++++++++++++ .../src/{ => borrowck}/type_check.rs | 4 +- c2rust-analyze/src/main.rs | 131 +------------- 6 files changed, 170 insertions(+), 132 deletions(-) rename c2rust-analyze/src/{ => borrowck}/atoms.rs (100%) rename c2rust-analyze/src/{ => borrowck}/def_use.rs (99%) rename c2rust-analyze/src/{ => borrowck}/dump.rs (98%) create mode 100644 c2rust-analyze/src/borrowck/mod.rs rename c2rust-analyze/src/{ => borrowck}/type_check.rs (97%) diff --git a/c2rust-analyze/src/atoms.rs b/c2rust-analyze/src/borrowck/atoms.rs similarity index 100% rename from c2rust-analyze/src/atoms.rs rename to c2rust-analyze/src/borrowck/atoms.rs diff --git a/c2rust-analyze/src/def_use.rs b/c2rust-analyze/src/borrowck/def_use.rs similarity index 99% rename from c2rust-analyze/src/def_use.rs rename to c2rust-analyze/src/borrowck/def_use.rs index 616b787d5f..f72dc7caec 100644 --- a/c2rust-analyze/src/def_use.rs +++ b/c2rust-analyze/src/borrowck/def_use.rs @@ -7,7 +7,7 @@ use rustc_middle::mir::visit::{ Visitor, PlaceContext, MutatingUseContext, NonMutatingUseContext, NonUseContext, }; use rustc_middle::ty::{TyCtxt, List}; -use crate::atoms::{AllFacts, AtomMaps, Path, Loan, SubPoint}; +use crate::borrowck::atoms::{AllFacts, AtomMaps, Path, Loan, SubPoint}; // From `rustc_borrowck/src/def_use.rs`, licensed MIT/Apache2 diff --git a/c2rust-analyze/src/dump.rs b/c2rust-analyze/src/borrowck/dump.rs similarity index 98% rename from c2rust-analyze/src/dump.rs rename to c2rust-analyze/src/borrowck/dump.rs index 0e0d105a2e..acbf20c20d 100644 --- a/c2rust-analyze/src/dump.rs +++ b/c2rust-analyze/src/borrowck/dump.rs @@ -8,7 +8,7 @@ use std::hash::Hash; use std::io::{BufWriter, Write}; use std::path; use rustc_hash::{FxHashMap, FxHashSet}; -use crate::atoms::{AllFacts, Output, AtomMaps, Origin, Loan, Point, Variable, Path}; +use crate::borrowck::atoms::{AllFacts, Output, AtomMaps, Origin, Loan, Point, Variable, Path}; pub fn dump_facts_to_dir( facts: &AllFacts, diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs new file mode 100644 index 0000000000..8fe32cbacd --- /dev/null +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -0,0 +1,163 @@ +use std::collections::HashMap; +use std::env; +use std::hash::Hash; +use std::mem; +use polonius_engine::{self, Atom, FactTypes}; +use rustc_ast::ast::{Item, ItemKind, Visibility, VisibilityKind}; +use rustc_ast::node_id::NodeId; +use rustc_ast::ptr::P; +use rustc_driver::Compilation; +use rustc_interface::Queries; +use rustc_interface::interface::Compiler; +use rustc_middle::mir::{ + Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, + LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, Operand, Statement, + StatementKind, BorrowKind, Constant, ConstantKind, +}; +use rustc_middle::mir::interpret::{Allocation, ConstValue}; +use rustc_middle::mir::pretty; +use rustc_middle::ty::{TyCtxt, Ty, TyKind, RegionKind, WithOptConstParam, List}; +use rustc_middle::ty::query::{Providers, ExternProviders}; +use rustc_session::Session; +use rustc_span::DUMMY_SP; +use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; +use rustc_span::symbol::Ident; +use rustc_target::abi::Align; +use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; +use self::atoms::{AllFacts, AtomMaps, SubPoint, Path, Loan}; + + +mod atoms; +mod def_use; +mod dump; +mod type_check; + + +pub type Label = Option; +pub type LTy<'tcx> = LabeledTy<'tcx, Label>; +pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, Label>; + + +pub fn borrowck_mir<'tcx>( + tcx: TyCtxt<'tcx>, + def: WithOptConstParam, + mir: &Body<'tcx>, +) { + let mut facts = AllFacts::default(); + let mut maps = AtomMaps::default(); + + // Start the origin counter at 3. This has no effect on the semantics, but makes for easier + // diffs between our facts and the facts generated by rustc. + for _ in 0..3 { + let _ = maps.origin(); + } + + let name = tcx.item_name(def.to_global().did); + eprintln!("\nprocessing function {:?}", name); + //pretty::write_mir_fn(tcx, mir, &mut |_, _| Ok(()), &mut std::io::stdout()).unwrap(); + + // Populate `cfg_edge` + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + eprintln!("{:?}:", bb); + + for idx in 0 .. bb_data.statements.len() { + eprintln!(" {}: {:?}", idx, bb_data.statements[idx]); + let start = maps.point(bb, idx, SubPoint::Start); + let mid = maps.point(bb, idx, SubPoint::Mid); + let next_start = maps.point(bb, idx + 1, SubPoint::Start); + facts.cfg_edge.push((start, mid)); + facts.cfg_edge.push((mid, next_start)); + } + + let term_idx = bb_data.statements.len(); + eprintln!(" {}: {:?}", term_idx, bb_data.terminator()); + let term_start = maps.point(bb, term_idx, SubPoint::Start); + let term_mid = maps.point(bb, term_idx, SubPoint::Mid); + facts.cfg_edge.push((term_start, term_mid)); + for &succ in bb_data.terminator().successors() { + let succ_start = maps.point(succ, 0, SubPoint::Start); + facts.cfg_edge.push((term_mid, succ_start)); + } + } + + // From rustc_borrowck::nll::populate_polonius_move_facts: "Non-arguments start out + // deinitialised; we simulate this with an initial move" + let entry_point = maps.point(START_BLOCK, 0, SubPoint::Start); + for local in mir.local_decls.indices() { + if mir.local_kind(local) != LocalKind::Arg { + let path = maps.path(&mut facts, Place { local, projection: List::empty() }); + facts.path_moved_at_base.push((path, entry_point)); + } + } + + // Populate `use_of_var_derefs_origin`, and generate `LTy`s for all locals. + let ltcx = LabeledTyCtxt::new(tcx); + let mut local_ltys = Vec::with_capacity(mir.local_decls.len()); + for (local, decl) in mir.local_decls.iter_enumerated() { + let lty = assign_origins(ltcx, &mut facts, &mut maps, decl.ty); + let var = maps.variable(local); + lty.for_each_label(&mut |label| { + if let Some(origin) = label { + facts.use_of_var_derefs_origin.push((var, origin)); + } + }); + local_ltys.push(lty); + } + + let mut loans = HashMap::>::new(); + // Populate `loan_issued_at` and `loans`. + type_check::visit(ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); + /* + // Populate `loan_issued_at` and `loans`. + // TODO: also populate `subset_base` here + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + for (idx, stmt) in bb_data.statements.iter().enumerate() { + let (lhs, rhs) = match stmt.kind { + StatementKind::Assign(ref x) => (&x.0, &x.1), + _ => continue, + }; + let (borrow_kind, place) = match *rhs { + Rvalue::Ref(_, bk, p) => (bk, p), + _ => continue, + }; + // TODO: need to do something with this origin (so it shows up in subset_base) + let origin = maps.origin(); + let path = maps.path(&mut facts, place); + let loan = maps.loan(); + loans.entry(place.local).or_default().push((path, loan, borrow_kind)); + let point = maps.point(bb, idx, SubPoint::Mid); + facts.loan_issued_at.push((origin, loan, point)); + } + } + */ + + // Populate `loan_invalidated_at` + def_use::visit_loan_invalidated_at(tcx, &mut facts, &mut maps, &loans, mir); + + // Populate `var_defined/used/dropped_at` and `path_assigned/accessed_at_base`. + def_use::visit(&mut facts, &mut maps, mir); + + + dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); + + let output = polonius_engine::Output::compute( + &facts, + polonius_engine::Algorithm::Naive, + true, + ); + dump::dump_output_to_dir(&output, &maps, format!("inspect/{}", name)).unwrap(); +} + +fn assign_origins<'tcx>( + ltcx: LTyCtxt<'tcx>, + facts: &mut AllFacts, + maps: &mut AtomMaps<'tcx>, + ty: Ty<'tcx>, +) -> LTy<'tcx> { + ltcx.label(ty, &mut |ty| match ty.kind() { + TyKind::Ref(_, _, _) => { + Some(maps.origin()) + }, + _ => None, + }) +} diff --git a/c2rust-analyze/src/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs similarity index 97% rename from c2rust-analyze/src/type_check.rs rename to c2rust-analyze/src/borrowck/type_check.rs index b79c3d3bc9..5b32821f08 100644 --- a/c2rust-analyze/src/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -4,8 +4,8 @@ use rustc_middle::mir::{ Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, Local, LocalDecl, Location, ProjectionElem, }; -use crate::{LTy, LTyCtxt}; -use crate::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; +use crate::borrowck::{LTy, LTyCtxt}; +use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; struct TypeChecker<'tcx, 'a> { diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index c98e86bb5e..6d3bda3eda 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -36,19 +36,10 @@ use rustc_span::DUMMY_SP; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; -use crate::atoms::{AllFacts, AtomMaps, SubPoint, Path, Loan}; -use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; - -mod atoms; -mod def_use; -mod dump; -mod labeled_ty; -mod type_check; -pub type Label = Option; -pub type LTy<'tcx> = LabeledTy<'tcx, Label>; -pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, Label>; +mod borrowck; +mod labeled_ty; fn inspect_mir<'tcx>( @@ -56,123 +47,7 @@ fn inspect_mir<'tcx>( def: WithOptConstParam, mir: &Body<'tcx>, ) { - let mut facts = AllFacts::default(); - let mut maps = AtomMaps::default(); - - // Start the origin counter at 3. This has no effect on the semantics, but makes for easier - // diffs between our facts and the facts generated by rustc. - for _ in 0..3 { - let _ = maps.origin(); - } - - let name = tcx.item_name(def.to_global().did); - eprintln!("\nprocessing function {:?}", name); - //pretty::write_mir_fn(tcx, mir, &mut |_, _| Ok(()), &mut std::io::stdout()).unwrap(); - - // Populate `cfg_edge` - for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { - eprintln!("{:?}:", bb); - - for idx in 0 .. bb_data.statements.len() { - eprintln!(" {}: {:?}", idx, bb_data.statements[idx]); - let start = maps.point(bb, idx, SubPoint::Start); - let mid = maps.point(bb, idx, SubPoint::Mid); - let next_start = maps.point(bb, idx + 1, SubPoint::Start); - facts.cfg_edge.push((start, mid)); - facts.cfg_edge.push((mid, next_start)); - } - - let term_idx = bb_data.statements.len(); - eprintln!(" {}: {:?}", term_idx, bb_data.terminator()); - let term_start = maps.point(bb, term_idx, SubPoint::Start); - let term_mid = maps.point(bb, term_idx, SubPoint::Mid); - facts.cfg_edge.push((term_start, term_mid)); - for &succ in bb_data.terminator().successors() { - let succ_start = maps.point(succ, 0, SubPoint::Start); - facts.cfg_edge.push((term_mid, succ_start)); - } - } - - // From rustc_borrowck::nll::populate_polonius_move_facts: "Non-arguments start out - // deinitialised; we simulate this with an initial move" - let entry_point = maps.point(START_BLOCK, 0, SubPoint::Start); - for local in mir.local_decls.indices() { - if mir.local_kind(local) != LocalKind::Arg { - let path = maps.path(&mut facts, Place { local, projection: List::empty() }); - facts.path_moved_at_base.push((path, entry_point)); - } - } - - // Populate `use_of_var_derefs_origin`, and generate `LTy`s for all locals. - let ltcx = LabeledTyCtxt::new(tcx); - let mut local_ltys = Vec::with_capacity(mir.local_decls.len()); - for (local, decl) in mir.local_decls.iter_enumerated() { - let lty = assign_origins(ltcx, &mut facts, &mut maps, decl.ty); - let var = maps.variable(local); - lty.for_each_label(&mut |label| { - if let Some(origin) = label { - facts.use_of_var_derefs_origin.push((var, origin)); - } - }); - local_ltys.push(lty); - } - - let mut loans = HashMap::>::new(); - // Populate `loan_issued_at` and `loans`. - type_check::visit(ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); - /* - // Populate `loan_issued_at` and `loans`. - // TODO: also populate `subset_base` here - for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { - for (idx, stmt) in bb_data.statements.iter().enumerate() { - let (lhs, rhs) = match stmt.kind { - StatementKind::Assign(ref x) => (&x.0, &x.1), - _ => continue, - }; - let (borrow_kind, place) = match *rhs { - Rvalue::Ref(_, bk, p) => (bk, p), - _ => continue, - }; - // TODO: need to do something with this origin (so it shows up in subset_base) - let origin = maps.origin(); - let path = maps.path(&mut facts, place); - let loan = maps.loan(); - loans.entry(place.local).or_default().push((path, loan, borrow_kind)); - let point = maps.point(bb, idx, SubPoint::Mid); - facts.loan_issued_at.push((origin, loan, point)); - } - } - */ - - // Populate `loan_invalidated_at` - def_use::visit_loan_invalidated_at(tcx, &mut facts, &mut maps, &loans, mir); - - // Populate `var_defined/used/dropped_at` and `path_assigned/accessed_at_base`. - def_use::visit(&mut facts, &mut maps, mir); - - - dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); - - let output = polonius_engine::Output::compute( - &facts, - polonius_engine::Algorithm::Naive, - true, - ); - dump::dump_output_to_dir(&output, &maps, format!("inspect/{}", name)).unwrap(); -} - -fn assign_origins<'tcx>( - ltcx: LTyCtxt<'tcx>, - facts: &mut AllFacts, - maps: &mut AtomMaps<'tcx>, - ty: Ty<'tcx>, -) -> LTy<'tcx> { - ltcx.label(ty, &mut |ty| match ty.kind() { - TyKind::Ref(_, _, _) => { - Some(maps.origin()) - }, - _ => None, - }) + borrowck::borrowck_mir(tcx, def, mir); } From e86eff860b025a1cf112c87e13b1e240d141b556 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 7 Apr 2022 14:15:56 -0700 Subject: [PATCH 09/38] analyze: run polonius on a hypothetical rewrite --- c2rust-analyze/Cargo.toml | 1 + c2rust-analyze/src/borrowck/mod.rs | 84 ++++++++++++----------- c2rust-analyze/src/borrowck/type_check.rs | 58 +++++++++++----- c2rust-analyze/src/context.rs | 75 ++++++++++++++++++++ c2rust-analyze/src/labeled_ty.rs | 8 +-- c2rust-analyze/src/main.rs | 36 +++++++++- 6 files changed, 201 insertions(+), 61 deletions(-) create mode 100644 c2rust-analyze/src/context.rs diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index 165fe56725..2c38f194d5 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -8,5 +8,6 @@ edition = "2021" [dependencies] polonius-engine = "0.13.0" rustc-hash = "1.1.0" +bitflags = "1.3.2" [workspace] diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index 8fe32cbacd..c5586711ab 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -23,8 +23,9 @@ use rustc_span::DUMMY_SP; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; +use crate::context::{AnalysisCtxt, PermissionSet}; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; -use self::atoms::{AllFacts, AtomMaps, SubPoint, Path, Loan}; +use self::atoms::{AllFacts, AtomMaps, Output, SubPoint, Origin, Path, Loan}; mod atoms; @@ -33,16 +34,32 @@ mod dump; mod type_check; -pub type Label = Option; +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)] +struct Label { + origin: Option, + perm: PermissionSet, +} + pub type LTy<'tcx> = LabeledTy<'tcx, Label>; pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, Label>; pub fn borrowck_mir<'tcx>( - tcx: TyCtxt<'tcx>, - def: WithOptConstParam, + acx: &AnalysisCtxt<'tcx>, + hypothesis: &mut [PermissionSet], + name: &str, mir: &Body<'tcx>, ) { + run_polonius(acx, hypothesis, name, mir); +} + + +fn run_polonius<'tcx>( + acx: &AnalysisCtxt<'tcx>, + hypothesis: &[PermissionSet], + name: &str, + mir: &Body<'tcx>, +) -> (AllFacts, AtomMaps<'tcx>, Output) { let mut facts = AllFacts::default(); let mut maps = AtomMaps::default(); @@ -52,8 +69,6 @@ pub fn borrowck_mir<'tcx>( let _ = maps.origin(); } - let name = tcx.item_name(def.to_global().did); - eprintln!("\nprocessing function {:?}", name); //pretty::write_mir_fn(tcx, mir, &mut |_, _| Ok(()), &mut std::io::stdout()).unwrap(); // Populate `cfg_edge` @@ -91,13 +106,13 @@ pub fn borrowck_mir<'tcx>( } // Populate `use_of_var_derefs_origin`, and generate `LTy`s for all locals. - let ltcx = LabeledTyCtxt::new(tcx); + let ltcx = LabeledTyCtxt::new(acx.tcx); let mut local_ltys = Vec::with_capacity(mir.local_decls.len()); - for (local, decl) in mir.local_decls.iter_enumerated() { - let lty = assign_origins(ltcx, &mut facts, &mut maps, decl.ty); + for local in mir.local_decls.indices() { + let lty = assign_origins(ltcx, hypothesis, &mut facts, &mut maps, acx.local_tys[local]); let var = maps.variable(local); lty.for_each_label(&mut |label| { - if let Some(origin) = label { + if let Some(origin) = label.origin { facts.use_of_var_derefs_origin.push((var, origin)); } }); @@ -107,32 +122,9 @@ pub fn borrowck_mir<'tcx>( let mut loans = HashMap::>::new(); // Populate `loan_issued_at` and `loans`. type_check::visit(ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); - /* - // Populate `loan_issued_at` and `loans`. - // TODO: also populate `subset_base` here - for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { - for (idx, stmt) in bb_data.statements.iter().enumerate() { - let (lhs, rhs) = match stmt.kind { - StatementKind::Assign(ref x) => (&x.0, &x.1), - _ => continue, - }; - let (borrow_kind, place) = match *rhs { - Rvalue::Ref(_, bk, p) => (bk, p), - _ => continue, - }; - // TODO: need to do something with this origin (so it shows up in subset_base) - let origin = maps.origin(); - let path = maps.path(&mut facts, place); - let loan = maps.loan(); - loans.entry(place.local).or_default().push((path, loan, borrow_kind)); - let point = maps.point(bb, idx, SubPoint::Mid); - facts.loan_issued_at.push((origin, loan, point)); - } - } - */ // Populate `loan_invalidated_at` - def_use::visit_loan_invalidated_at(tcx, &mut facts, &mut maps, &loans, mir); + def_use::visit_loan_invalidated_at(acx.tcx, &mut facts, &mut maps, &loans, mir); // Populate `var_defined/used/dropped_at` and `path_assigned/accessed_at_base`. def_use::visit(&mut facts, &mut maps, mir); @@ -146,18 +138,30 @@ pub fn borrowck_mir<'tcx>( true, ); dump::dump_output_to_dir(&output, &maps, format!("inspect/{}", name)).unwrap(); + + (facts, maps, output) } fn assign_origins<'tcx>( ltcx: LTyCtxt<'tcx>, + hypothesis: &[PermissionSet], facts: &mut AllFacts, maps: &mut AtomMaps<'tcx>, - ty: Ty<'tcx>, + lty: crate::LTy<'tcx>, ) -> LTy<'tcx> { - ltcx.label(ty, &mut |ty| match ty.kind() { - TyKind::Ref(_, _, _) => { - Some(maps.origin()) - }, - _ => None, + ltcx.relabel(lty, &mut |lty| { + let perm = if lty.label.is_none() { + PermissionSet::empty() + } else { + hypothesis[lty.label.index()] + }; + match lty.ty.kind() { + TyKind::Ref(_, _, _) | + TyKind::RawPtr(_) => { + let origin = Some(maps.origin()); + Label { origin, perm } + }, + _ => Label { origin: None, perm }, + } }) } diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 5b32821f08..895a1109b3 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -4,8 +4,9 @@ use rustc_middle::mir::{ Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, Local, LocalDecl, Location, ProjectionElem, }; -use crate::borrowck::{LTy, LTyCtxt}; +use crate::borrowck::{LTy, LTyCtxt, Label}; use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; +use crate::context::PermissionSet; struct TypeChecker<'tcx, 'a> { @@ -49,36 +50,59 @@ impl<'tcx> TypeChecker<'tcx, '_> { Operand::Move(pl) => self.visit_place(pl), Operand::Constant(ref c) => { let ty = c.ty(); - self.ltcx.label(ty, &mut |_| None) + self.ltcx.label(ty, &mut |_| Label::default()) }, } } - pub fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>) -> LTy<'tcx> { + /// Create a new origin and issue an associated loan. The loan is issued at + /// `self.current_location`. + fn issue_loan( + &mut self, + pl: Place<'tcx>, + borrow_kind: BorrowKind, + ) -> Origin { + // Create a new origin and issue an associated loan. + let origin = self.maps.origin(); + let path = self.maps.path(self.facts, pl); + let loan = self.maps.loan(); + self.loans.entry(pl.local).or_default().push((path, loan, borrow_kind)); + let point = self.current_point(SubPoint::Mid); + self.facts.loan_issued_at.push((origin, loan, point)); + origin + } + + pub fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>, expect_ty: LTy<'tcx>) -> LTy<'tcx> { match *rv { Rvalue::Use(ref op) => self.visit_operand(op), Rvalue::Ref(_, borrow_kind, pl) => { - // Create a new origin and issue an associated loan. - let origin = self.maps.origin(); - let path = self.maps.path(self.facts, pl); - let loan = self.maps.loan(); - self.loans.entry(pl.local).or_default().push((path, loan, borrow_kind)); - let point = self.current_point(SubPoint::Mid); - self.facts.loan_issued_at.push((origin, loan, point)); + let perm = expect_ty.label.perm; + let origin = self.issue_loan(pl, borrow_kind); // Return a type with the new loan on the outermost `ref`. let ty = rv.ty(self.local_decls, *self.ltcx); let pl_lty = self.visit_place(pl); - let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), Some(origin)); + let label = Label { origin: Some(origin), perm }; + let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), label); lty }, - Rvalue::AddressOf(mutbl, pl) => { - // TODO + Rvalue::AddressOf(_, pl) => { + let perm = expect_ty.label.perm; + let borrow_kind = if perm.contains(PermissionSet::UNIQUE) { + BorrowKind::Mut { allow_two_phase_borrow: false } + } else { + BorrowKind::Shared + }; + + let origin = self.issue_loan(pl, borrow_kind); + + // Return a type with the new loan on the outermost `ref`. let ty = rv.ty(self.local_decls, *self.ltcx); let pl_lty = self.visit_place(pl); - let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), None); + let label = Label { origin: Some(origin), perm }; + let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), label); lty }, @@ -91,10 +115,12 @@ impl<'tcx> TypeChecker<'tcx, '_> { StatementKind::Assign(ref x) => { let (pl, ref rv) = **x; let pl_lty = self.visit_place(pl); - let rv_lty = self.visit_rvalue(rv); + let rv_lty = self.visit_rvalue(rv, pl_lty); eprintln!("assign {:?} = {:?}", pl_lty, rv_lty); - if let (Some(pl_origin), Some(rv_origin)) = (pl_lty.label, rv_lty.label) { + let pl_origin = pl_lty.label.origin; + let rv_origin = rv_lty.label.origin; + if let (Some(pl_origin), Some(rv_origin)) = (pl_origin, rv_origin) { let point = self.current_point(SubPoint::Mid); self.facts.subset_base.push((rv_origin, pl_origin, point)); } diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs new file mode 100644 index 0000000000..5bc0768a5e --- /dev/null +++ b/c2rust-analyze/src/context.rs @@ -0,0 +1,75 @@ +use std::cell::Cell; +use bitflags::bitflags; +use rustc_index::vec::IndexVec; +use rustc_middle::mir::Local; +use rustc_middle::ty::TyCtxt; +use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; + + +bitflags! { + #[derive(Default)] + pub struct PermissionSet: u16 { + /// The value(s) accessible through this pointer can be read. + const READ = 0x0001; + /// The value(s) accessible through this pointer can be written. + const WRITE = 0x0002; + /// This pointer is unique: using an alias not derived from this + /// pointer invalidates this pointer, after which it is not valid to use. + const UNIQUE = 0x0004; + /// This pointer is linear-typed. Copying a `LINEAR` pointer to another `LINEAR` location + /// moves the pointer and invalidates the source of the copy. (However, a + /// copy-and-downcast to a non-`LINEAR` location is a borrow, which does not invalidate the + /// source pointer.) + const LINEAR = 0x0008; + } +} + + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct PointerId(u32); + +impl PointerId { + pub const NONE: PointerId = PointerId(u32::MAX); + + pub fn index(self) -> usize { + self.0 as usize + } + + pub fn is_none(self) -> bool { + self == Self::NONE + } +} + + +pub type LTy<'tcx> = LabeledTy<'tcx, PointerId>; +pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, PointerId>; + +pub struct AnalysisCtxt<'tcx> { + pub tcx: TyCtxt<'tcx>, + pub lcx: LTyCtxt<'tcx>, + + pub local_tys: IndexVec>, + + next_ptr_id: Cell, +} + +impl<'tcx> AnalysisCtxt<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>) -> AnalysisCtxt<'tcx> { + AnalysisCtxt { + tcx, + lcx: LabeledTyCtxt::new(tcx), + local_tys: IndexVec::new(), + next_ptr_id: Cell::new(0), + } + } + + pub fn new_pointer(&self) -> PointerId { + let next = self.next_ptr_id.get(); + self.next_ptr_id.set(next + 1); + PointerId(next) + } + + pub fn num_pointers(&self) -> usize { + self.next_ptr_id.get() as usize + } +} diff --git a/c2rust-analyze/src/labeled_ty.rs b/c2rust-analyze/src/labeled_ty.rs index 8aab81868a..54ff3c5f73 100644 --- a/c2rust-analyze/src/labeled_ty.rs +++ b/c2rust-analyze/src/labeled_ty.rs @@ -226,20 +226,20 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { func: &mut F, ) -> LabeledTy<'tcx, L> where - F: FnMut(&L2) -> L, + F: FnMut(LabeledTy<'tcx, L2>) -> L, { let args = self.relabel_slice(lty.args, func); - self.mk(lty.ty, args, func(<y.label)) + self.mk(lty.ty, args, func(lty)) } /// Replace the labels on several labeled types. pub fn relabel_slice( &self, - ltys: &'tcx [LabeledTy<'tcx, L2>], + ltys: &[LabeledTy<'tcx, L2>], func: &mut F, ) -> &'tcx [LabeledTy<'tcx, L>] where - F: FnMut(&L2) -> L, + F: FnMut(LabeledTy<'tcx, L2>) -> L, { let ltys = ltys .iter() diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 6d3bda3eda..7607e0623b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -20,6 +20,7 @@ use rustc_ast::ast::{Item, ItemKind, Visibility, VisibilityKind}; use rustc_ast::node_id::NodeId; use rustc_ast::ptr::P; use rustc_driver::Compilation; +use rustc_index::vec::IndexVec; use rustc_interface::Queries; use rustc_interface::interface::Compiler; use rustc_middle::mir::{ @@ -36,9 +37,11 @@ use rustc_span::DUMMY_SP; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; +use crate::context::{AnalysisCtxt, PointerId, PermissionSet, LTy}; mod borrowck; +mod context; mod labeled_ty; @@ -47,7 +50,38 @@ fn inspect_mir<'tcx>( def: WithOptConstParam, mir: &Body<'tcx>, ) { - borrowck::borrowck_mir(tcx, def, mir); + let name = tcx.item_name(def.to_global().did); + eprintln!("\nprocessing function {:?}", name); + + let mut acx = AnalysisCtxt::new(tcx); + + // Label all pointers in local variables. + // TODO: also label pointers in Rvalue::Cast (and ShallowInitBox?) + assert!(acx.local_tys.len() == 0); + acx.local_tys = IndexVec::with_capacity(mir.local_decls.len()); + for (local, decl) in mir.local_decls.iter_enumerated() { + let lty = assign_pointer_ids(&acx, decl.ty); + let l = acx.local_tys.push(lty); + assert_eq!(local, l); + } + + let mut hypothesis = Vec::with_capacity(acx.num_pointers()); + for _ in 0 .. acx.num_pointers() { + hypothesis.push(PermissionSet::all()); + } + + borrowck::borrowck_mir(&acx, &mut hypothesis, name.as_str(), mir); +} + +fn assign_pointer_ids<'tcx>( + acx: &AnalysisCtxt<'tcx>, + ty: Ty<'tcx>, +) -> LTy<'tcx> { + acx.lcx.label(ty, &mut |ty| match ty.kind() { + TyKind::Ref(_, _, _) | + TyKind::RawPtr(_) => acx.new_pointer(), + _ => PointerId::NONE, + }) } From 9500bddf236238ea4dc2127572f12a10dc3693ea Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 14 Apr 2022 16:45:45 -0700 Subject: [PATCH 10/38] analyze: remove UNIQUE permission on error and propagate --- c2rust-analyze/src/borrowck/atoms.rs | 5 + c2rust-analyze/src/borrowck/def_use.rs | 2 + c2rust-analyze/src/borrowck/mod.rs | 89 +++++++++++++- c2rust-analyze/src/context.rs | 2 + c2rust-analyze/src/dataflow/mod.rs | 139 ++++++++++++++++++++++ c2rust-analyze/src/dataflow/type_check.rs | 112 +++++++++++++++++ c2rust-analyze/src/main.rs | 11 +- 7 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 c2rust-analyze/src/dataflow/mod.rs create mode 100644 c2rust-analyze/src/dataflow/type_check.rs diff --git a/c2rust-analyze/src/borrowck/atoms.rs b/c2rust-analyze/src/borrowck/atoms.rs index 987b78151f..f676705a69 100644 --- a/c2rust-analyze/src/borrowck/atoms.rs +++ b/c2rust-analyze/src/borrowck/atoms.rs @@ -146,6 +146,11 @@ impl<'tcx> AtomMaps<'tcx> { self.point.get(x) } + pub fn get_point_location(&self, x: Point) -> Location { + let (block, statement_index, _) = self.get_point(x); + Location { block, statement_index } + } + pub fn variable(&mut self, l: Local) -> Variable { Variable(l.as_usize()) } diff --git a/c2rust-analyze/src/borrowck/def_use.rs b/c2rust-analyze/src/borrowck/def_use.rs index f72dc7caec..98d66537f8 100644 --- a/c2rust-analyze/src/borrowck/def_use.rs +++ b/c2rust-analyze/src/borrowck/def_use.rs @@ -179,6 +179,8 @@ impl<'tcx> LoanInvalidatedAtVisitor<'tcx, '_> { context: PlaceContext, location: Location, ) { + eprintln!("access loan {:?} (kind {:?}) at location {:?} (context {:?} = {:?})", + loan, borrow_kind, location, context, categorize(context)); let invalidate = match (borrow_kind, categorize(context)) { (BorrowKind::Shared, Some(DefUse::Use)) => false, (_, None) => false, diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index c5586711ab..36c0d8327b 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::env; use std::hash::Hash; use std::mem; +use either::{Either, Left, Right}; use polonius_engine::{self, Atom, FactTypes}; use rustc_ast::ast::{Item, ItemKind, Visibility, VisibilityKind}; use rustc_ast::node_id::NodeId; @@ -23,7 +24,8 @@ use rustc_span::DUMMY_SP; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; -use crate::context::{AnalysisCtxt, PermissionSet}; +use crate::context::{AnalysisCtxt, PermissionSet, PointerId}; +use crate::dataflow::DataflowConstraints; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; use self::atoms::{AllFacts, AtomMaps, Output, SubPoint, Origin, Path, Loan}; @@ -46,11 +48,94 @@ pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, Label>; pub fn borrowck_mir<'tcx>( acx: &AnalysisCtxt<'tcx>, + dataflow: &DataflowConstraints, hypothesis: &mut [PermissionSet], name: &str, mir: &Body<'tcx>, ) { - run_polonius(acx, hypothesis, name, mir); + let mut i = 0; + loop { + eprintln!("run polonius"); + let (facts, maps, output) = run_polonius(acx, hypothesis, name, mir); + eprintln!("polonius: iteration {}: {} errors", i, output.errors.len()); + i += 1; + + if output.errors.len() == 0 { + break; + } + if i >= 20 { panic!() } + + let mut changed = false; + for (_, loans) in &output.errors { + for &loan in loans { + let issued_point = facts.loan_issued_at.iter().find(|&&(_, l, _)| l == loan) + .map(|&(_, _, point)| point) + .unwrap_or_else(|| panic!("loan {:?} was never issued?", loan)); + let issued_loc = maps.get_point_location(issued_point); + let stmt = mir.stmt_at(issued_loc).left().unwrap_or_else(|| { + panic!("loan {:?} was issued by a terminator (at {:?})?", loan, issued_loc); + }); + // TODO: + // - address of local: adjust `addr_of_local[l]` + // - address of deref + project: adjust deref'd operand + // - copy/move: adjust copied ptr + let pl = match stmt.kind { + StatementKind::Assign(ref x) => { + match x.1 { + Rvalue::Use(_) => todo!(), + Rvalue::Ref(_, _, pl) => pl, + Rvalue::AddressOf(_, pl) => pl, + // TODO: handle direct assignment from another pointer + ref rv => panic!( + "loan {:?} was issued by unknown rvalue {:?}?", loan, rv, + ), + } + }, + _ => panic!("loan {:?} was issued by non-assign stmt {:?}?", loan, stmt), + }; + eprintln!("want to drop UNIQUE from place {:?}", pl); + + let ptr = if let Some(l) = pl.as_local() { + acx.addr_of_local[l] + } else { + todo!(); + }; + + if hypothesis[ptr.index()].contains(PermissionSet::UNIQUE) { + hypothesis[ptr.index()].remove(PermissionSet::UNIQUE); + changed = true; + } + } + } + + eprintln!("propagate"); + changed |= dataflow.propagate(hypothesis); + eprintln!("done propagating"); + + if !changed { + eprintln!( + "{} unresolved borrowck errors in function {:?} (after {} iterations)", + output.errors.len(), + name, + i, + ); + break; + } + } + + eprintln!("final labeling for {:?}:", name); + let lcx2 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); + for (local, _) in mir.local_decls.iter_enumerated() { + let addr_of = hypothesis[acx.addr_of_local[local].index()]; + let ty = lcx2.relabel(acx.local_tys[local], &mut |lty| { + if lty.label == PointerId::NONE { + PermissionSet::empty() + } else { + hypothesis[lty.label.index()] + } + }); + eprintln!("{:?}: addr_of = {:?}, type = {:?}", local, addr_of, ty); + } } diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 5bc0768a5e..4d2a201159 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -49,6 +49,7 @@ pub struct AnalysisCtxt<'tcx> { pub lcx: LTyCtxt<'tcx>, pub local_tys: IndexVec>, + pub addr_of_local: IndexVec, next_ptr_id: Cell, } @@ -59,6 +60,7 @@ impl<'tcx> AnalysisCtxt<'tcx> { tcx, lcx: LabeledTyCtxt::new(tcx), local_tys: IndexVec::new(), + addr_of_local: IndexVec::new(), next_ptr_id: Cell::new(0), } } diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs new file mode 100644 index 0000000000..86572925db --- /dev/null +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -0,0 +1,139 @@ +use rustc_middle::mir::Body; +use crate::context::{PermissionSet, PointerId, AnalysisCtxt}; + +mod type_check; + + +#[derive(Clone, Debug)] +struct Edge { + /// For each bit in `mask`, a positive edge sets that bit in `dest` if it's set in `source`; a + /// negative edge clears the bit in `dest` if it's cleared in `source`. + positive: bool, + source: PointerId, + dest: PointerId, + mask: PermissionSet, +} + +#[derive(Clone, Debug)] +struct Fixed { + /// For each bit in `mask`, a positive fixed constraint sets the bit in `dest`; a negative + /// constraint clear the bit in `dest` instead. + positive: bool, + dest: PointerId, + mask: PermissionSet, +} + +#[derive(Clone, Debug, Default)] +pub struct DataflowConstraints { + fixed: Vec, + edges: Vec, +} + +impl DataflowConstraints { + fn add_edge( + &mut self, + positive: bool, + source: PointerId, + dest: PointerId, + mask: PermissionSet, + ) { + self.edges.push(Edge { positive, source, dest, mask }); + } + + fn add_fixed( + &mut self, + positive: bool, + dest: PointerId, + mask: PermissionSet, + ) { + self.fixed.push(Fixed { positive, dest, mask }); + } + + /// Update the pointer permissions in `hypothesis` to satisfy these constraints. + pub fn propagate(&self, hypothesis: &mut [PermissionSet]) -> bool { + match self.propagate_inner(hypothesis) { + Ok(changed) => changed, + Err(msg) => { + eprintln!("fixed:"); + for f in &self.fixed { + eprintln!(" {:?}", f); + } + eprintln!("edges:"); + for e in &self.edges { + eprintln!(" {:?}", e); + } + eprintln!("hypothesis:"); + for (i, p) in hypothesis.iter().enumerate() { + eprintln!(" {}: {:?}", i, p); + } + panic!("{}", msg); + }, + } + } + + fn propagate_inner(&self, hypothesis: &mut [PermissionSet]) -> Result { + for f in &self.fixed { + if f.positive { + hypothesis[f.dest.index()].insert(f.mask); + } else { + hypothesis[f.dest.index()].remove(f.mask); + } + } + + let mut changed = false; + let mut dirty = vec![true; hypothesis.len()]; + let mut i = 0; + loop { + if i > hypothesis.len() + self.edges.len() { + return Err(format!("infinite loop in dataflow edges")); + } + i += 1; + + let mut new_dirty = vec![false; hypothesis.len()]; + let mut any_new_dirty = false; + for e in &self.edges { + if !dirty[e.source.index()] { + continue; + } + let old = hypothesis[e.dest.index()]; + if e.positive { + hypothesis[e.dest.index()].insert(e.mask & hypothesis[e.source.index()]); + } else { + hypothesis[e.dest.index()].remove(e.mask & !hypothesis[e.source.index()]); + } + if hypothesis[e.dest.index()] != old { + eprintln!("changed {:?}: {:?} => {:?}", e.dest, old, hypothesis[e.dest.index()]); + new_dirty[e.dest.index()] = true; + any_new_dirty = true; + changed = true; + } + } + + if !any_new_dirty { + break; + } + dirty = new_dirty; + } + + for f in &self.fixed { + let ok = if f.positive { + hypothesis[f.dest.index()].contains(f.mask) + } else { + (!hypothesis[f.dest.index()]).contains(f.mask) + }; + if !ok { + return Err(format!("fixed dataflow constraint was violated: {:?}", f)); + } + } + + Ok(changed) + } +} + + +pub fn generate_constraints<'tcx>( + acx: &AnalysisCtxt<'tcx>, + mir: &Body<'tcx>, +) -> DataflowConstraints { + self::type_check::visit(acx, mir) +} diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs new file mode 100644 index 0000000000..b3756020fc --- /dev/null +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -0,0 +1,112 @@ +use std::collections::HashMap; +use rustc_index::vec::IndexVec; +use rustc_middle::mir::{ + Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, + Local, LocalDecl, Location, ProjectionElem, +}; +use crate::context::{PermissionSet, PointerId, AnalysisCtxt, LTy}; +use super::DataflowConstraints; + + +struct TypeChecker<'tcx, 'a> { + acx: &'a AnalysisCtxt<'tcx>, + constraints: DataflowConstraints, +} + +impl<'tcx> TypeChecker<'tcx, '_> { + fn add_edge(&mut self, src: PointerId, dest: PointerId) { + // Forward dataflow: if src is non-UNIQUE, then dest must be non-UNIQUE. + self.constraints.add_edge(false, src, dest, PermissionSet::UNIQUE); + // Backward dataflow: if dest is READ/WRITE, then src must also be READ/WRITE. + self.constraints.add_edge(true, dest, src, PermissionSet::READ | PermissionSet::WRITE); + } + + pub fn visit_place(&mut self, pl: Place<'tcx>) -> LTy<'tcx> { + let mut lty = self.acx.local_tys[pl.local]; + for proj in pl.projection { + match proj { + ProjectionElem::Deref => { + assert_eq!(lty.args.len(), 1); + lty = lty.args[0]; + }, + + ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), + } + } + lty + } + + pub fn visit_operand(&mut self, op: &Operand<'tcx>) -> LTy<'tcx> { + match *op { + Operand::Copy(pl) | + Operand::Move(pl) => self.visit_place(pl), + Operand::Constant(ref c) => { + let ty = c.ty(); + // TODO + self.acx.lcx.label(ty, &mut |_| PointerId::NONE) + }, + } + } + + pub fn visit_statement(&mut self, stmt: &Statement<'tcx>) { + match stmt.kind { + StatementKind::Assign(ref x) => { + let (pl, ref rv) = **x; + let pl_lty = self.visit_place(pl); + let pl_ptr = pl_lty.label; + if pl_ptr == PointerId::NONE { + return; + } + + match *rv { + Rvalue::Use(ref op) => { + // Direct assignment. + let op_lty = self.visit_operand(op); + let op_ptr = op_lty.label; + assert!(op_ptr != PointerId::NONE); + self.add_edge(op_ptr, pl_ptr); + }, + + Rvalue::AddressOf(_, pl) => { + if let Some(l) = pl.as_local() { + let addr_ptr = self.acx.addr_of_local[l]; + self.add_edge(addr_ptr, pl_ptr); + } + }, + + _ => { + eprintln!("TODO: handle assignment of {:?}", rv); + }, + } + }, + _ => {}, + } + } + + pub fn visit_terminator(&mut self, term: &Terminator<'tcx>) { + match term.kind { + _ => {}, + } + } +} + +pub fn visit<'tcx>( + acx: &AnalysisCtxt<'tcx>, + mir: &Body<'tcx>, +) -> DataflowConstraints { + let mut tc = TypeChecker { + acx, + constraints: DataflowConstraints::default(), + }; + + for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { + for (idx, stmt) in bb_data.statements.iter().enumerate() { + tc.visit_statement(stmt); + } + tc.visit_terminator(bb_data.terminator()); + } + + tc.constraints +} + + diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 7607e0623b..a0a96b3577 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -1,4 +1,5 @@ #![feature(rustc_private)] +extern crate either; extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_data_structures; @@ -42,6 +43,7 @@ use crate::context::{AnalysisCtxt, PointerId, PermissionSet, LTy}; mod borrowck; mod context; +mod dataflow; mod labeled_ty; @@ -63,14 +65,21 @@ fn inspect_mir<'tcx>( let lty = assign_pointer_ids(&acx, decl.ty); let l = acx.local_tys.push(lty); assert_eq!(local, l); + + let ptr = acx.new_pointer(); + let l = acx.addr_of_local.push(ptr); + assert_eq!(local, l); } + let dataflow = self::dataflow::generate_constraints(&acx, mir); + let mut hypothesis = Vec::with_capacity(acx.num_pointers()); for _ in 0 .. acx.num_pointers() { hypothesis.push(PermissionSet::all()); } + dataflow.propagate(&mut hypothesis); - borrowck::borrowck_mir(&acx, &mut hypothesis, name.as_str(), mir); + borrowck::borrowck_mir(&acx, &dataflow, &mut hypothesis, name.as_str(), mir); } fn assign_pointer_ids<'tcx>( From 661188e35929ee96bc7f344898133870b0e542d2 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 18 Apr 2022 13:45:51 -0700 Subject: [PATCH 11/38] analyze: handle reborrows when updating UNIQUE --- c2rust-analyze/rename_nll_facts.py | 139 ++++++++++++++++++++++ c2rust-analyze/src/borrowck/def_use.rs | 5 +- c2rust-analyze/src/borrowck/dump.rs | 16 ++- c2rust-analyze/src/borrowck/mod.rs | 56 +++++---- c2rust-analyze/src/borrowck/type_check.rs | 27 +++++ c2rust-analyze/src/context.rs | 55 ++++++++- c2rust-analyze/src/dataflow/mod.rs | 25 ++-- c2rust-analyze/src/dataflow/type_check.rs | 29 ++--- c2rust-analyze/src/main.rs | 1 + c2rust-analyze/src/util.rs | 50 ++++++++ examples/static-analysis/alias2.rs | 50 ++++++++ 11 files changed, 388 insertions(+), 65 deletions(-) create mode 100644 c2rust-analyze/rename_nll_facts.py create mode 100644 c2rust-analyze/src/util.rs create mode 100644 examples/static-analysis/alias2.rs diff --git a/c2rust-analyze/rename_nll_facts.py b/c2rust-analyze/rename_nll_facts.py new file mode 100644 index 0000000000..3374400087 --- /dev/null +++ b/c2rust-analyze/rename_nll_facts.py @@ -0,0 +1,139 @@ +''' +Usage: `python3 rename_nll_facts.py src ref dest` + +Renames atoms in `src/*.facts` to match the names used in `ref/*.facts`, then +writes the renamed facts to `dest/`. +''' + +import ast +from collections import defaultdict +import os +import sys + +src_dir, ref_dir, dest_dir = sys.argv[1:] + +# Map `src` loan/origin/path names to `ref` loan/origin/path names. We don't +# break this down by type because the names for each type don't collide anyway. +name_map = {} +# Set of `ref` names that appear as values in `name_map`. +ref_names_seen = set() + +def match_name(src_name, ref_name): + if src_name in name_map: + old_ref_name = name_map[src_name] + if ref_name != old_ref_name: + print('error: %r matches both %r and %r' % ( + src_name, old_ref_name, ref_name)) + return + else: + if ref_name in ref_names_seen: + print('error: %r matches %r, but %r is already used' % ( + src_name, ref_name, ref_name)) + return + name_map[src_name] = ref_name + ref_names_seen.add(ref_name) + +def match_loan(src_name, ref_name): + match_name(src_name, ref_name) + +def match_origin(src_name, ref_name): + match_name(src_name, ref_name) + +def match_path(src_name, ref_name): + match_name(src_name, ref_name) + + +def load(name): + with open(os.path.join(src_dir, name + '.facts')) as f: + src_rows = [[ast.literal_eval(s) for s in line.strip().split('\t')] + for line in f] + with open(os.path.join(ref_dir, name + '.facts')) as f: + ref_rows = [[ast.literal_eval(s) for s in line.strip().split('\t')] + for line in f] + return src_rows, ref_rows + + +# Match up paths using `path_is_var` and `path_assigned_at_base`. + +def match_path_is_var(): + src, ref = load('path_is_var') + ref_dct = {var: path for path, var in ref} + for path, var in src: + if var not in ref_dct: + continue + match_path(path, ref_dct[var]) + +match_path_is_var() + +def match_path_assigned_at_base(): + src, ref = load('path_assigned_at_base') + ref_dct = {point: path for path, point in ref} + for path, point in src: + if point not in ref_dct: + continue + match_path(path, ref_dct[point]) + +match_path_assigned_at_base() + +# Match up origins and loans using `loan_issued_at` + +def match_loan_issued_at(): + src, ref = load('loan_issued_at') + ref_dct = {point: (origin, loan) for origin, loan, point in ref} + for origin, loan, point in src: + if point not in ref_dct: + continue + match_origin(origin, ref_dct[point][0]) + match_origin(loan, ref_dct[point][1]) + +match_loan_issued_at() + +# Match up origins using `use_of_var_derefs_origin` + +def match_use_of_var_derefs_origin(): + src, ref = load('use_of_var_derefs_origin') + src_dct = defaultdict(list) + for var, origin in src: + src_dct[var].append(origin) + ref_dct = defaultdict(list) + for var, origin in ref: + ref_dct[var].append(origin) + for var in set(src_dct.keys()) & set(ref_dct.keys()): + src_origins = src_dct[var] + ref_origins = ref_dct[var] + if len(src_origins) != len(ref_origins): + print('error: var %r has %d origins in src but %d in ref' % ( + var, len(src_origins), len(ref_origins))) + continue + for src_origin, ref_origin in zip(src_origins, ref_origins): + match_origin(src_origin, ref_origin) + +match_use_of_var_derefs_origin() + + +# Rewrite `src` using the collected name mappings. + +os.makedirs(dest_dir, exist_ok=True) +for name in os.listdir(src_dir): + if name.startswith('.') or not name.endswith('.facts'): + continue + + with open(os.path.join(src_dir, name)) as src, \ + open(os.path.join(dest_dir, name), 'w') as dest: + for line in src: + src_parts = [ast.literal_eval(s) for s in line.strip().split('\t')] + dest_parts = [] + for part in src_parts: + if part.startswith('_') or part.startswith('Start') or part.startswith('Mid'): + dest_parts.append(part) + continue + + dest_part = name_map.get(part) + if dest_part is None: + print('error: no mapping for %r (used in %s: %r)' % ( + part, name, src_parts)) + dest_part = 'OLD:' + part + dest_parts.append(dest_part) + + dest.write('\t'.join('"%s"' % part for part in dest_parts) + '\n') + diff --git a/c2rust-analyze/src/borrowck/def_use.rs b/c2rust-analyze/src/borrowck/def_use.rs index 98d66537f8..6c9ea21982 100644 --- a/c2rust-analyze/src/borrowck/def_use.rs +++ b/c2rust-analyze/src/borrowck/def_use.rs @@ -99,7 +99,7 @@ impl<'tcx> Visitor<'tcx> for DefUseVisitor<'tcx, '_> { if place.is_indirect() { // TODO - return; + //return; } let path = self.maps.path(self.facts, *place); @@ -107,6 +107,7 @@ impl<'tcx> Visitor<'tcx> for DefUseVisitor<'tcx, '_> { // TODO: figure out when exactly paths should be recorded as assigned/accessed/moved if let PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) = context { + self.facts.path_accessed_at_base.push((path, point)); self.facts.path_moved_at_base.push((path, point)); return; } @@ -203,7 +204,7 @@ impl<'tcx> Visitor<'tcx> for LoanInvalidatedAtVisitor<'tcx, '_> { if place.is_indirect() { // TODO - return; + //return; } let local_loans = self.loans.get(&place.local).map_or(&[] as &[_], |x| x); diff --git a/c2rust-analyze/src/borrowck/dump.rs b/c2rust-analyze/src/borrowck/dump.rs index acbf20c20d..4df4e01c54 100644 --- a/c2rust-analyze/src/borrowck/dump.rs +++ b/c2rust-analyze/src/borrowck/dump.rs @@ -33,7 +33,7 @@ pub fn dump_facts_to_dir( write_facts_to_path! { wr.write_facts_to_path(facts.[ loan_issued_at, - //universal_region, + universal_region, cfg_edge, loan_killed_at, subset_base, @@ -48,8 +48,8 @@ pub fn dump_facts_to_dir( path_assigned_at_base, path_moved_at_base, path_accessed_at_base, - //known_placeholder_subset, - //placeholder, + known_placeholder_subset, + placeholder, ]) } Ok(()) @@ -137,6 +137,16 @@ trait FactRow { ) -> Result<(), Box>; } +impl FactRow for Origin { + fn write( + &self, + out: &mut dyn Write, + maps: &AtomMaps, + ) -> Result<(), Box> { + write_row(out, maps, &[self]) + } +} + impl FactRow for (A, B) where A: Render, diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index 36c0d8327b..e71854d28a 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -12,8 +12,8 @@ use rustc_interface::Queries; use rustc_interface::interface::Compiler; use rustc_middle::mir::{ Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, Operand, Statement, - StatementKind, BorrowKind, Constant, ConstantKind, + LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, PlaceRef, PlaceElem, Operand, + Statement, StatementKind, BorrowKind, Constant, ConstantKind, }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; use rustc_middle::mir::pretty; @@ -27,6 +27,7 @@ use rustc_target::abi::Align; use crate::context::{AnalysisCtxt, PermissionSet, PointerId}; use crate::dataflow::DataflowConstraints; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; +use crate::util::{describe_rvalue, RvalueDesc}; use self::atoms::{AllFacts, AtomMaps, Output, SubPoint, Origin, Path, Loan}; @@ -57,7 +58,10 @@ pub fn borrowck_mir<'tcx>( loop { eprintln!("run polonius"); let (facts, maps, output) = run_polonius(acx, hypothesis, name, mir); - eprintln!("polonius: iteration {}: {} errors", i, output.errors.len()); + eprintln!( + "polonius: iteration {}: {} errors, {} move_errors", + i, output.errors.len(), output.move_errors.len(), + ); i += 1; if output.errors.len() == 0 { @@ -75,31 +79,20 @@ pub fn borrowck_mir<'tcx>( let stmt = mir.stmt_at(issued_loc).left().unwrap_or_else(|| { panic!("loan {:?} was issued by a terminator (at {:?})?", loan, issued_loc); }); - // TODO: - // - address of local: adjust `addr_of_local[l]` - // - address of deref + project: adjust deref'd operand - // - copy/move: adjust copied ptr - let pl = match stmt.kind { - StatementKind::Assign(ref x) => { - match x.1 { - Rvalue::Use(_) => todo!(), - Rvalue::Ref(_, _, pl) => pl, - Rvalue::AddressOf(_, pl) => pl, - // TODO: handle direct assignment from another pointer - ref rv => panic!( - "loan {:?} was issued by unknown rvalue {:?}?", loan, rv, - ), - } + let ptr = match stmt.kind { + StatementKind::Assign(ref x) => match describe_rvalue(&x.1) { + Some(RvalueDesc::Project { base, proj: _ }) => { + acx.ptr_of(base) + .unwrap_or_else(|| panic!("missing pointer ID for {:?}", base)) + }, + Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => { + acx.addr_of_local[local] + }, + None => panic!("loan {:?} was issued by unknown rvalue {:?}?", loan, x.1), }, _ => panic!("loan {:?} was issued by non-assign stmt {:?}?", loan, stmt), }; - eprintln!("want to drop UNIQUE from place {:?}", pl); - - let ptr = if let Some(l) = pl.as_local() { - acx.addr_of_local[l] - } else { - todo!(); - }; + eprintln!("want to drop UNIQUE from pointer {:?}", ptr); if hypothesis[ptr.index()].contains(PermissionSet::UNIQUE) { hypothesis[ptr.index()].remove(PermissionSet::UNIQUE); @@ -145,6 +138,7 @@ fn run_polonius<'tcx>( name: &str, mir: &Body<'tcx>, ) -> (AllFacts, AtomMaps<'tcx>, Output) { + let tcx = acx.tcx; let mut facts = AllFacts::default(); let mut maps = AtomMaps::default(); @@ -181,17 +175,21 @@ fn run_polonius<'tcx>( } // From rustc_borrowck::nll::populate_polonius_move_facts: "Non-arguments start out - // deinitialised; we simulate this with an initial move" + // deinitialised; we simulate this with an initial move". On the other hand, arguments are + // considered assigned at the entry point. let entry_point = maps.point(START_BLOCK, 0, SubPoint::Start); for local in mir.local_decls.indices() { - if mir.local_kind(local) != LocalKind::Arg { + if mir.local_kind(local) == LocalKind::Arg { + let path = maps.path(&mut facts, Place { local, projection: List::empty() }); + facts.path_assigned_at_base.push((path, entry_point)); + } else { let path = maps.path(&mut facts, Place { local, projection: List::empty() }); facts.path_moved_at_base.push((path, entry_point)); } } // Populate `use_of_var_derefs_origin`, and generate `LTy`s for all locals. - let ltcx = LabeledTyCtxt::new(acx.tcx); + let ltcx = LabeledTyCtxt::new(tcx); let mut local_ltys = Vec::with_capacity(mir.local_decls.len()); for local in mir.local_decls.indices() { let lty = assign_origins(ltcx, hypothesis, &mut facts, &mut maps, acx.local_tys[local]); @@ -206,7 +204,7 @@ fn run_polonius<'tcx>( let mut loans = HashMap::>::new(); // Populate `loan_issued_at` and `loans`. - type_check::visit(ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); + type_check::visit(tcx, ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); // Populate `loan_invalidated_at` def_use::visit_loan_invalidated_at(acx.tcx, &mut facts, &mut maps, &loans, mir); diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 895a1109b3..5a87802eb1 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -4,12 +4,14 @@ use rustc_middle::mir::{ Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, Local, LocalDecl, Location, ProjectionElem, }; +use rustc_middle::ty::{TyCtxt, TyKind}; use crate::borrowck::{LTy, LTyCtxt, Label}; use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; use crate::context::PermissionSet; struct TypeChecker<'tcx, 'a> { + tcx: TyCtxt<'tcx>, ltcx: LTyCtxt<'tcx>, facts: &'a mut AllFacts, maps: &'a mut AtomMaps<'tcx>, @@ -69,11 +71,34 @@ impl<'tcx> TypeChecker<'tcx, '_> { self.loans.entry(pl.local).or_default().push((path, loan, borrow_kind)); let point = self.current_point(SubPoint::Mid); self.facts.loan_issued_at.push((origin, loan, point)); + eprintln!("issued loan {:?} = {:?} ({:?})", loan, pl, borrow_kind); origin } pub fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>, expect_ty: LTy<'tcx>) -> LTy<'tcx> { match *rv { + Rvalue::Use(Operand::Move(pl)) | + Rvalue::Use(Operand::Copy(pl)) if matches!(expect_ty.ty.kind(), TyKind::RawPtr(_)) => { + // Copy of a raw pointer. We treat this as a reborrow. + let perm = expect_ty.label.perm; + let borrow_kind = if perm.contains(PermissionSet::UNIQUE) { + BorrowKind::Mut { allow_two_phase_borrow: false } + } else { + BorrowKind::Shared + }; + + let pl_deref = self.tcx.mk_place_deref(pl); + let origin = self.issue_loan(pl_deref, borrow_kind); + + // Return a type with the new loan on the outermost `ref`. + let ty = rv.ty(self.local_decls, *self.ltcx); + let pl_lty = self.visit_place(pl_deref); + let label = Label { origin: Some(origin), perm }; + let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), label); + lty + + }, + Rvalue::Use(ref op) => self.visit_operand(op), Rvalue::Ref(_, borrow_kind, pl) => { @@ -137,6 +162,7 @@ impl<'tcx> TypeChecker<'tcx, '_> { } pub fn visit<'tcx>( + tcx: TyCtxt<'tcx>, ltcx: LTyCtxt<'tcx>, facts: &mut AllFacts, maps: &mut AtomMaps<'tcx>, @@ -145,6 +171,7 @@ pub fn visit<'tcx>( mir: &Body<'tcx>, ) { let mut tc = TypeChecker { + tcx, ltcx, facts, maps, diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 4d2a201159..46971c8c57 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -1,7 +1,7 @@ use std::cell::Cell; use bitflags::bitflags; use rustc_index::vec::IndexVec; -use rustc_middle::mir::Local; +use rustc_middle::mir::{Local, Place, PlaceRef, ProjectionElem}; use rustc_middle::ty::TyCtxt; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; @@ -74,4 +74,57 @@ impl<'tcx> AnalysisCtxt<'tcx> { pub fn num_pointers(&self) -> usize { self.next_ptr_id.get() as usize } + + pub fn type_of>(&self, x: T) -> LTy<'tcx> { + x.type_of(self) + } + + pub fn ptr_of>(&self, x: T) -> Option { + let ptr = self.type_of(x).label; + if ptr == PointerId::NONE { + None + } else { + Some(ptr) + } + } +} + + +pub trait TypeOf<'tcx> { + fn type_of(&self, acx: &AnalysisCtxt<'tcx>) -> LTy<'tcx>; +} + +impl<'tcx, T: TypeOf<'tcx>> TypeOf<'tcx> for &T { + fn type_of(&self, acx: &AnalysisCtxt<'tcx>) -> LTy<'tcx> { + (**self).type_of(acx) + } +} + +impl<'tcx> TypeOf<'tcx> for Local { + fn type_of(&self, acx: &AnalysisCtxt<'tcx>) -> LTy<'tcx> { + acx.local_tys[*self] + } +} + +impl<'tcx> TypeOf<'tcx> for Place<'tcx> { + fn type_of(&self, acx: &AnalysisCtxt<'tcx>) -> LTy<'tcx> { + acx.type_of(self.as_ref()) + } +} + +impl<'tcx> TypeOf<'tcx> for PlaceRef<'tcx> { + fn type_of(&self, acx: &AnalysisCtxt<'tcx>) -> LTy<'tcx> { + let mut ty = acx.type_of(self.local); + for proj in self.projection { + match *proj { + ProjectionElem::Deref => todo!("type_of Deref"), + ProjectionElem::Field(..) => todo!("type_of Field"), + ProjectionElem::Index(..) | + ProjectionElem::ConstantIndex { .. } => todo!("type_of Index"), + ProjectionElem::Subslice { .. } => todo!("type_of Subslice"), + ProjectionElem::Downcast(..) => todo!("type_of Downcast"), + } + } + ty + } } diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 86572925db..b3920e3ef8 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -51,21 +51,22 @@ impl DataflowConstraints { /// Update the pointer permissions in `hypothesis` to satisfy these constraints. pub fn propagate(&self, hypothesis: &mut [PermissionSet]) -> bool { + eprintln!("=== propagating ==="); + eprintln!("fixed:"); + for f in &self.fixed { + eprintln!(" {:?}", f); + } + eprintln!("edges:"); + for e in &self.edges { + eprintln!(" {:?}", e); + } + eprintln!("hypothesis:"); + for (i, p) in hypothesis.iter().enumerate() { + eprintln!(" {}: {:?}", i, p); + } match self.propagate_inner(hypothesis) { Ok(changed) => changed, Err(msg) => { - eprintln!("fixed:"); - for f in &self.fixed { - eprintln!(" {:?}", f); - } - eprintln!("edges:"); - for e in &self.edges { - eprintln!(" {:?}", e); - } - eprintln!("hypothesis:"); - for (i, p) in hypothesis.iter().enumerate() { - eprintln!(" {}: {:?}", i, p); - } panic!("{}", msg); }, } diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index b3756020fc..19615153e5 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -5,6 +5,7 @@ use rustc_middle::mir::{ Local, LocalDecl, Location, ProjectionElem, }; use crate::context::{PermissionSet, PointerId, AnalysisCtxt, LTy}; +use crate::util::{describe_rvalue, RvalueDesc}; use super::DataflowConstraints; @@ -58,26 +59,18 @@ impl<'tcx> TypeChecker<'tcx, '_> { return; } - match *rv { - Rvalue::Use(ref op) => { - // Direct assignment. - let op_lty = self.visit_operand(op); - let op_ptr = op_lty.label; - assert!(op_ptr != PointerId::NONE); - self.add_edge(op_ptr, pl_ptr); + let rv_ptr = match describe_rvalue(rv) { + Some(RvalueDesc::Project { base, proj: _ }) => { + self.acx.ptr_of(base) + .unwrap_or_else(|| panic!("missing pointer ID for {:?}", base)) }, - - Rvalue::AddressOf(_, pl) => { - if let Some(l) = pl.as_local() { - let addr_ptr = self.acx.addr_of_local[l]; - self.add_edge(addr_ptr, pl_ptr); - } - }, - - _ => { - eprintln!("TODO: handle assignment of {:?}", rv); + Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => { + self.acx.addr_of_local[local] }, - } + None => panic!("TODO: handle assignment of {:?}", rv), + }; + assert!(rv_ptr != PointerId::NONE); + self.add_edge(rv_ptr, pl_ptr); }, _ => {}, } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index a0a96b3577..c9f1aa654b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -45,6 +45,7 @@ mod borrowck; mod context; mod dataflow; mod labeled_ty; +mod util; fn inspect_mir<'tcx>( diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs new file mode 100644 index 0000000000..53a263fb8f --- /dev/null +++ b/c2rust-analyze/src/util.rs @@ -0,0 +1,50 @@ +use rustc_middle::mir::{PlaceRef, PlaceElem, Rvalue, Operand, Local}; + +pub enum RvalueDesc<'tcx> { + Project { + base: PlaceRef<'tcx>, + proj: &'tcx [PlaceElem<'tcx>], + }, + AddrOfLocal { + local: Local, + proj: &'tcx [PlaceElem<'tcx>], + }, +} + +pub fn describe_rvalue<'tcx>(rv: &Rvalue<'tcx>) -> Option> { + Some(match *rv { + Rvalue::Use(ref op) => match *op { + Operand::Move(pl) | + Operand::Copy(pl) => RvalueDesc::Project { + base: pl.as_ref(), + proj: &[], + }, + Operand::Constant(_) => return None, + }, + Rvalue::Ref(_, _, pl) | + Rvalue::AddressOf(_, pl) => { + let projection = &pl.projection[..]; + match projection.iter().rposition(|p| matches!(p, PlaceElem::Deref)) { + Some(i) => { + // `i` is the index of the last `ProjectionElem::Deref` in `pl`. + RvalueDesc::Project { + base: PlaceRef { + local: pl.local, + projection: &projection[..i], + }, + proj: &projection[i + 1 ..], + } + }, + None => { + // `pl` refers to a field/element of a local. + RvalueDesc::AddrOfLocal { + local: pl.local, + proj: projection, + } + }, + } + }, + _ => return None, + }) +} + diff --git a/examples/static-analysis/alias2.rs b/examples/static-analysis/alias2.rs new file mode 100644 index 0000000000..865107649e --- /dev/null +++ b/examples/static-analysis/alias2.rs @@ -0,0 +1,50 @@ +use std::ptr; + +pub unsafe fn alias2_copy_good(x: *mut i32) { + let p = x; + let q = x; + *q = 1; +} + +pub unsafe fn alias2_addr_of_good(x: *mut i32) { + let p = ptr::addr_of_mut!(*x); + let q = ptr::addr_of_mut!(*x); + *q = 1; +} + +pub unsafe fn alias2_copy_bad(x: *mut i32) { + let p = x; + let q = x; + *p = 1; +} + +pub unsafe fn alias2_addr_of_bad(x: *mut i32) { + let p = ptr::addr_of_mut!(*x); + let q = ptr::addr_of_mut!(*x); + *p = 1; +} + + +pub unsafe fn safe_alias2_copy_good(x: &mut i32) { + let p = x; + let q = x; + *q = 1; +} + +pub unsafe fn safe_alias2_addr_of_good(x: &mut i32) { + let p = &mut *x; + let q = &mut *x; + *q = 1; +} + +pub unsafe fn safe_alias2_copy_bad(x: &mut i32) { + let p = x; + let q = x; + *p = 1; +} + +pub unsafe fn safe_alias2_addr_of_bad(x: &mut i32) { + let p = &mut *x; + let q = &mut *x; + *p = 1; +} From d75e429e3c7ddaf9f448290e89259e1c66190f5c Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 2 May 2022 10:55:59 -0700 Subject: [PATCH 12/38] analyze: infer READ and WRITE permissions --- c2rust-analyze/src/dataflow/mod.rs | 166 +++++++++++++--------- c2rust-analyze/src/dataflow/type_check.rs | 115 +++++++++++---- c2rust-analyze/src/main.rs | 2 +- c2rust-analyze/src/util.rs | 1 + examples/static-analysis/alias3.rs | 25 ++++ 5 files changed, 211 insertions(+), 98 deletions(-) create mode 100644 examples/static-analysis/alias3.rs diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index b3920e3ef8..08de2a09f9 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -5,60 +5,51 @@ mod type_check; #[derive(Clone, Debug)] -struct Edge { - /// For each bit in `mask`, a positive edge sets that bit in `dest` if it's set in `source`; a - /// negative edge clears the bit in `dest` if it's cleared in `source`. - positive: bool, - source: PointerId, - dest: PointerId, - mask: PermissionSet, -} - -#[derive(Clone, Debug)] -struct Fixed { - /// For each bit in `mask`, a positive fixed constraint sets the bit in `dest`; a negative - /// constraint clear the bit in `dest` instead. - positive: bool, - dest: PointerId, - mask: PermissionSet, +enum Constraint { + /// Pointer `.0` must have a subset of the permissions of poniter `.1`. + Subset(PointerId, PointerId), + /// Pointer `.0` must have all the permissions in `.1`. + AllPerms(PointerId, PermissionSet), + /// Pointer `.0` must not have any of the permissions in `.1`. + NoPerms(PointerId, PermissionSet), } #[derive(Clone, Debug, Default)] pub struct DataflowConstraints { - fixed: Vec, - edges: Vec, + constraints: Vec, } impl DataflowConstraints { - fn add_edge( + fn add_subset( + &mut self, + a: PointerId, + b: PointerId, + ) { + self.constraints.push(Constraint::Subset(a, b)); + } + + fn add_all_perms( &mut self, - positive: bool, - source: PointerId, - dest: PointerId, - mask: PermissionSet, + ptr: PointerId, + perms: PermissionSet, ) { - self.edges.push(Edge { positive, source, dest, mask }); + self.constraints.push(Constraint::AllPerms(ptr, perms)); } - fn add_fixed( + fn add_no_perms( &mut self, - positive: bool, - dest: PointerId, - mask: PermissionSet, + ptr: PointerId, + perms: PermissionSet, ) { - self.fixed.push(Fixed { positive, dest, mask }); + self.constraints.push(Constraint::NoPerms(ptr, perms)); } /// Update the pointer permissions in `hypothesis` to satisfy these constraints. pub fn propagate(&self, hypothesis: &mut [PermissionSet]) -> bool { eprintln!("=== propagating ==="); - eprintln!("fixed:"); - for f in &self.fixed { - eprintln!(" {:?}", f); - } - eprintln!("edges:"); - for e in &self.edges { - eprintln!(" {:?}", e); + eprintln!("constraints:"); + for c in &self.constraints { + eprintln!(" {:?}", c); } eprintln!("hypothesis:"); for (i, p) in hypothesis.iter().enumerate() { @@ -73,40 +64,84 @@ impl DataflowConstraints { } fn propagate_inner(&self, hypothesis: &mut [PermissionSet]) -> Result { - for f in &self.fixed { - if f.positive { - hypothesis[f.dest.index()].insert(f.mask); - } else { - hypothesis[f.dest.index()].remove(f.mask); - } - } - let mut changed = false; let mut dirty = vec![true; hypothesis.len()]; let mut i = 0; loop { - if i > hypothesis.len() + self.edges.len() { + if i > hypothesis.len() + self.constraints.len() { return Err(format!("infinite loop in dataflow edges")); } i += 1; let mut new_dirty = vec![false; hypothesis.len()]; let mut any_new_dirty = false; - for e in &self.edges { - if !dirty[e.source.index()] { - continue; - } - let old = hypothesis[e.dest.index()]; - if e.positive { - hypothesis[e.dest.index()].insert(e.mask & hypothesis[e.source.index()]); - } else { - hypothesis[e.dest.index()].remove(e.mask & !hypothesis[e.source.index()]); - } - if hypothesis[e.dest.index()] != old { - eprintln!("changed {:?}: {:?} => {:?}", e.dest, old, hypothesis[e.dest.index()]); - new_dirty[e.dest.index()] = true; - any_new_dirty = true; - changed = true; + for c in &self.constraints { + match *c { + Constraint::Subset(a, b) => { + if !dirty[a.index()] && !dirty[b.index()] { + continue; + } + + // These should be `const`s, but that produces `error[E0015]: cannot call + // non-const operator in constants`. + + // Permissions that should be propagated "down": if the superset (`b`) + // doesn't have it, then the subset (`a`) should have it removed. + #[allow(bad_style)] + let PROPAGATE_DOWN = + PermissionSet::UNIQUE; + // Permissions that should be propagated "up": if the subset (`a`) has it, + // then the superset (`b`) should be given it. + #[allow(bad_style)] + let PROPAGATE_UP = + PermissionSet::READ | + PermissionSet::WRITE; + + let old_a = hypothesis[a.index()]; + let old_b = hypothesis[b.index()]; + let new_a = old_a & !(!old_b & PROPAGATE_DOWN); + let new_b = old_b | (old_a & PROPAGATE_UP); + if new_a != old_a { + eprintln!("changed {:?}: {:?} => {:?}", a, old_a, new_a); + hypothesis[a.index()] = new_a; + new_dirty[a.index()] = true; + any_new_dirty = true; + } + if new_b != old_b { + eprintln!("changed {:?}: {:?} => {:?}", b, old_b, new_b); + hypothesis[b.index()] = new_b; + new_dirty[b.index()] = true; + any_new_dirty = true; + } + }, + + Constraint::AllPerms(ptr, perms) => { + if !dirty[ptr.index()] { + continue; + } + let old = hypothesis[ptr.index()]; + let new = old | perms; + if new != old { + eprintln!("changed {:?}: {:?} => {:?}", ptr, old, new); + hypothesis[ptr.index()] = new; + new_dirty[ptr.index()] = true; + any_new_dirty = true; + } + }, + + Constraint::NoPerms(ptr, perms) => { + if !dirty[ptr.index()] { + continue; + } + let old = hypothesis[ptr.index()]; + let new = old & !perms; + if new != old { + hypothesis[ptr.index()] = new; + eprintln!("changed {:?}: {:?} => {:?}", ptr, old, new); + new_dirty[ptr.index()] = true; + any_new_dirty = true; + } + }, } } @@ -116,17 +151,6 @@ impl DataflowConstraints { dirty = new_dirty; } - for f in &self.fixed { - let ok = if f.positive { - hypothesis[f.dest.index()].contains(f.mask) - } else { - (!hypothesis[f.dest.index()]).contains(f.mask) - }; - if !ok { - return Err(format!("fixed dataflow constraint was violated: {:?}", f)); - } - } - Ok(changed) } } diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 19615153e5..3a2cdf28a5 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -1,14 +1,17 @@ use std::collections::HashMap; use rustc_index::vec::IndexVec; use rustc_middle::mir::{ - Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, - Local, LocalDecl, Location, ProjectionElem, + Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, PlaceRef, Operand, + BorrowKind, Local, LocalDecl, Location, ProjectionElem, Mutability, }; +use rustc_middle::mir::visit::{PlaceContext, NonMutatingUseContext, MutatingUseContext}; use crate::context::{PermissionSet, PointerId, AnalysisCtxt, LTy}; use crate::util::{describe_rvalue, RvalueDesc}; use super::DataflowConstraints; +/// Visitor that walks over the MIR, computing types of rvalues/operands/places and generating +/// constraints as a side effect. struct TypeChecker<'tcx, 'a> { acx: &'a AnalysisCtxt<'tcx>, constraints: DataflowConstraints, @@ -16,17 +19,42 @@ struct TypeChecker<'tcx, 'a> { impl<'tcx> TypeChecker<'tcx, '_> { fn add_edge(&mut self, src: PointerId, dest: PointerId) { - // Forward dataflow: if src is non-UNIQUE, then dest must be non-UNIQUE. - self.constraints.add_edge(false, src, dest, PermissionSet::UNIQUE); - // Backward dataflow: if dest is READ/WRITE, then src must also be READ/WRITE. - self.constraints.add_edge(true, dest, src, PermissionSet::READ | PermissionSet::WRITE); + // Copying `src` to `dest` can discard permissions, but can't add new ones. + self.constraints.add_subset(dest, src); } - pub fn visit_place(&mut self, pl: Place<'tcx>) -> LTy<'tcx> { + fn record_access(&mut self, ptr: PointerId, mutbl: Mutability) { + eprintln!("record_access({:?}, {:?})", ptr, mutbl); + if ptr == PointerId::NONE { + return; + } + match mutbl { + Mutability::Mut => { + self.constraints.add_all_perms(ptr, PermissionSet::READ | PermissionSet::WRITE); + }, + Mutability::Not => { + self.constraints.add_all_perms(ptr, PermissionSet::READ); + }, + } + } + + pub fn visit_place(&mut self, pl: Place<'tcx>, ctx: PlaceContext) -> LTy<'tcx> { + self.visit_place_ref(pl.as_ref(), ctx) + } + + pub fn visit_place_ref(&mut self, pl: PlaceRef<'tcx>, ctx: PlaceContext) -> LTy<'tcx> { let mut lty = self.acx.local_tys[pl.local]; + let mut prev_deref_ptr = None; + for proj in pl.projection { match proj { ProjectionElem::Deref => { + // All derefs except the last are loads, to retrieve the pointer for the next + // deref. The last deref is either a load or a store, depending on `ctx`. + if let Some(ptr) = prev_deref_ptr.take() { + self.record_access(ptr, Mutability::Not); + } + prev_deref_ptr = Some(lty.label); assert_eq!(lty.args.len(), 1); lty = lty.args[0]; }, @@ -34,13 +62,54 @@ impl<'tcx> TypeChecker<'tcx, '_> { ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), } } + + if let Some(ptr) = prev_deref_ptr.take() { + match ctx { + PlaceContext::NonMutatingUse(..) => { + self.record_access(ptr, Mutability::Not); + }, + PlaceContext::MutatingUse(..) => { + self.record_access(ptr, Mutability::Mut); + }, + PlaceContext::NonUse(..) => {}, + } + } + lty } - pub fn visit_operand(&mut self, op: &Operand<'tcx>) -> LTy<'tcx> { + pub fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>) -> PointerId { + eprintln!("visit_rvalue({:?}), desc = {:?}", rv, describe_rvalue(rv)); + + match describe_rvalue(rv) { + Some(RvalueDesc::Project { base, proj: _ }) => { + let ctx = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); + let base_ty = self.visit_place_ref(base, ctx); + base_ty.label + }, + Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => { + self.acx.addr_of_local[local] + }, + None => match *rv { + Rvalue::Use(ref op) => self.visit_operand(op).label, + _ => panic!("TODO: handle assignment of {:?}", rv), + }, + } + } + + pub fn visit_operand( + &mut self, + op: &Operand<'tcx>, + ) -> LTy<'tcx> { match *op { - Operand::Copy(pl) | - Operand::Move(pl) => self.visit_place(pl), + Operand::Copy(pl) => { + let ctx = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); + self.visit_place(pl, ctx) + }, + Operand::Move(pl) => { + let ctx = PlaceContext::NonMutatingUse(NonMutatingUseContext::Move); + self.visit_place(pl, ctx) + }, Operand::Constant(ref c) => { let ty = c.ty(); // TODO @@ -50,27 +119,21 @@ impl<'tcx> TypeChecker<'tcx, '_> { } pub fn visit_statement(&mut self, stmt: &Statement<'tcx>) { + eprintln!("visit_statement({:?})", stmt); match stmt.kind { StatementKind::Assign(ref x) => { let (pl, ref rv) = **x; - let pl_lty = self.visit_place(pl); + let ctx = PlaceContext::MutatingUse(MutatingUseContext::Store); + let pl_lty = self.visit_place(pl, ctx); let pl_ptr = pl_lty.label; - if pl_ptr == PointerId::NONE { - return; - } - let rv_ptr = match describe_rvalue(rv) { - Some(RvalueDesc::Project { base, proj: _ }) => { - self.acx.ptr_of(base) - .unwrap_or_else(|| panic!("missing pointer ID for {:?}", base)) - }, - Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => { - self.acx.addr_of_local[local] - }, - None => panic!("TODO: handle assignment of {:?}", rv), - }; - assert!(rv_ptr != PointerId::NONE); - self.add_edge(rv_ptr, pl_ptr); + let rv_ptr = self.visit_rvalue(rv); + + if pl_ptr != PointerId::NONE || rv_ptr != PointerId::NONE { + assert!(pl_ptr != PointerId::NONE); + assert!(rv_ptr != PointerId::NONE); + self.add_edge(rv_ptr, pl_ptr); + } }, _ => {}, } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index c9f1aa654b..164bc3e302 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -76,7 +76,7 @@ fn inspect_mir<'tcx>( let mut hypothesis = Vec::with_capacity(acx.num_pointers()); for _ in 0 .. acx.num_pointers() { - hypothesis.push(PermissionSet::all()); + hypothesis.push(PermissionSet::UNIQUE); } dataflow.propagate(&mut hypothesis); diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index 53a263fb8f..23e718dbcd 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -1,5 +1,6 @@ use rustc_middle::mir::{PlaceRef, PlaceElem, Rvalue, Operand, Local}; +#[derive(Debug)] pub enum RvalueDesc<'tcx> { Project { base: PlaceRef<'tcx>, diff --git a/examples/static-analysis/alias3.rs b/examples/static-analysis/alias3.rs new file mode 100644 index 0000000000..9620bf4b94 --- /dev/null +++ b/examples/static-analysis/alias3.rs @@ -0,0 +1,25 @@ +use std::ptr; + +pub unsafe fn alias3_copy_bad1(x: *mut i32) { + let p = x; + let q = x; + *q = *p; +} + +pub unsafe fn alias3_copy_bad2(x: *mut i32) { + let p = x; + let q = x; + *p = *q; +} + +pub unsafe fn alias3_addr_of_bad1(x: *mut i32) { + let p = ptr::addr_of_mut!(*x); + let q = ptr::addr_of_mut!(*x); + *q = *p; +} + +pub unsafe fn alias3_addr_of_bad2(x: *mut i32) { + let p = ptr::addr_of_mut!(*x); + let q = ptr::addr_of_mut!(*x); + *p = *q; +} From b76966f8be55cdfb7013b4d7ccd4ef00ff78c794 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 2 May 2022 13:09:00 -0700 Subject: [PATCH 13/38] analyze: propagate permissions through offset() calls --- c2rust-analyze/src/borrowck/type_check.rs | 39 ++++++++++++++++++----- c2rust-analyze/src/context.rs | 7 ++++ c2rust-analyze/src/dataflow/type_check.rs | 38 ++++++++++++++++++---- c2rust-analyze/src/main.rs | 1 + c2rust-analyze/src/util.rs | 39 ++++++++++++++++++++++- examples/static-analysis/offset1.rs | 19 +++++++++++ 6 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 examples/static-analysis/offset1.rs diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 5a87802eb1..80633e7ffd 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -8,6 +8,7 @@ use rustc_middle::ty::{TyCtxt, TyKind}; use crate::borrowck::{LTy, LTyCtxt, Label}; use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; use crate::context::PermissionSet; +use crate::util::{self, Callee}; struct TypeChecker<'tcx, 'a> { @@ -135,27 +136,49 @@ impl<'tcx> TypeChecker<'tcx, '_> { } } + fn do_assign(&mut self, pl_lty: LTy<'tcx>, rv_lty: LTy<'tcx>) { + eprintln!("assign {:?} = {:?}", pl_lty, rv_lty); + + let pl_origin = pl_lty.label.origin; + let rv_origin = rv_lty.label.origin; + if let (Some(pl_origin), Some(rv_origin)) = (pl_origin, rv_origin) { + let point = self.current_point(SubPoint::Mid); + self.facts.subset_base.push((rv_origin, pl_origin, point)); + } + } + pub fn visit_statement(&mut self, stmt: &Statement<'tcx>) { match stmt.kind { StatementKind::Assign(ref x) => { let (pl, ref rv) = **x; let pl_lty = self.visit_place(pl); let rv_lty = self.visit_rvalue(rv, pl_lty); - eprintln!("assign {:?} = {:?}", pl_lty, rv_lty); - - let pl_origin = pl_lty.label.origin; - let rv_origin = rv_lty.label.origin; - if let (Some(pl_origin), Some(rv_origin)) = (pl_origin, rv_origin) { - let point = self.current_point(SubPoint::Mid); - self.facts.subset_base.push((rv_origin, pl_origin, point)); - } + self.do_assign(pl_lty, rv_lty); }, _ => {}, } } pub fn visit_terminator(&mut self, term: &Terminator<'tcx>) { + eprintln!("borrowck: visit_terminator({:?})", term.kind); match term.kind { + TerminatorKind::Call { ref func, ref args, destination, .. } => { + let func_ty = func.ty(self.local_decls, *self.ltcx); + eprintln!("callee = {:?}", util::ty_callee(*self.ltcx, func_ty)); + match util::ty_callee(*self.ltcx, func_ty) { + Some(Callee::PtrOffset { .. }) => { + // We handle this like a pointer assignment. + + // `destination` must be `Some` because the function doesn't diverge. + let destination = destination.unwrap(); + let pl_lty = self.visit_place(destination.0); + assert!(args.len() == 2); + let rv_lty = self.visit_operand(&args[0]); + self.do_assign(pl_lty, rv_lty); + }, + None => {}, + } + }, _ => {}, } } diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 46971c8c57..41badf3e6e 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -21,6 +21,13 @@ bitflags! { /// copy-and-downcast to a non-`LINEAR` location is a borrow, which does not invalidate the /// source pointer.) const LINEAR = 0x0008; + /// This pointer can be offset in the positive direction. + /// + /// Offsetting the pointer in an unknown direction requires both `OFFSET_ADD` and + /// `OFFSET_SUB`. Offsetting by zero requires neither `OFFSET_ADD` nor `OFFSET_SUB`. + const OFFSET_ADD = 0x0010; + /// This pointer can be offset in the negative direction. + const OFFSET_SUB = 0x0020; } } diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 3a2cdf28a5..ff773b8291 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -6,7 +6,7 @@ use rustc_middle::mir::{ }; use rustc_middle::mir::visit::{PlaceContext, NonMutatingUseContext, MutatingUseContext}; use crate::context::{PermissionSet, PointerId, AnalysisCtxt, LTy}; -use crate::util::{describe_rvalue, RvalueDesc}; +use crate::util::{self, describe_rvalue, RvalueDesc, Callee}; use super::DataflowConstraints; @@ -14,6 +14,7 @@ use super::DataflowConstraints; /// constraints as a side effect. struct TypeChecker<'tcx, 'a> { acx: &'a AnalysisCtxt<'tcx>, + mir: &'a Body<'tcx>, constraints: DataflowConstraints, } @@ -118,6 +119,14 @@ impl<'tcx> TypeChecker<'tcx, '_> { } } + fn do_assign(&mut self, pl_ptr: PointerId, rv_ptr: PointerId) { + if pl_ptr != PointerId::NONE || rv_ptr != PointerId::NONE { + assert!(pl_ptr != PointerId::NONE); + assert!(rv_ptr != PointerId::NONE); + self.add_edge(rv_ptr, pl_ptr); + } + } + pub fn visit_statement(&mut self, stmt: &Statement<'tcx>) { eprintln!("visit_statement({:?})", stmt); match stmt.kind { @@ -129,18 +138,34 @@ impl<'tcx> TypeChecker<'tcx, '_> { let rv_ptr = self.visit_rvalue(rv); - if pl_ptr != PointerId::NONE || rv_ptr != PointerId::NONE { - assert!(pl_ptr != PointerId::NONE); - assert!(rv_ptr != PointerId::NONE); - self.add_edge(rv_ptr, pl_ptr); - } + self.do_assign(pl_ptr, rv_ptr); }, _ => {}, } } pub fn visit_terminator(&mut self, term: &Terminator<'tcx>) { + eprintln!("visit_terminator({:?})", term.kind); + let tcx = self.acx.tcx; match term.kind { + TerminatorKind::Call { ref func, ref args, destination, .. } => { + let func_ty = func.ty(self.mir, tcx); + eprintln!("callee = {:?}", util::ty_callee(tcx, func_ty)); + match util::ty_callee(tcx, func_ty) { + Some(Callee::PtrOffset { .. }) => { + // We handle this like a pointer assignment. + + // `destination` must be `Some` because the function doesn't diverge. + let destination = destination.unwrap(); + let ctx = PlaceContext::MutatingUse(MutatingUseContext::Store); + let pl_lty = self.visit_place(destination.0, ctx); + assert!(args.len() == 2); + let rv_lty = self.visit_operand(&args[0]); + self.do_assign(pl_lty.label, rv_lty.label); + }, + None => {}, + } + }, _ => {}, } } @@ -152,6 +177,7 @@ pub fn visit<'tcx>( ) -> DataflowConstraints { let mut tc = TypeChecker { acx, + mir, constraints: DataflowConstraints::default(), }; diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 164bc3e302..471db7f70d 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -4,6 +4,7 @@ extern crate rustc_arena; extern crate rustc_ast; extern crate rustc_data_structures; extern crate rustc_driver; +extern crate rustc_hir; extern crate rustc_index; extern crate rustc_interface; extern crate rustc_middle; diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index 23e718dbcd..e602ec6f4c 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -1,4 +1,6 @@ -use rustc_middle::mir::{PlaceRef, PlaceElem, Rvalue, Operand, Local}; +use rustc_hir::def::DefKind; +use rustc_middle::mir::{PlaceRef, PlaceElem, Rvalue, Operand, Local, Mutability}; +use rustc_middle::ty::{TyCtxt, Ty, TyKind, DefIdTree}; #[derive(Debug)] pub enum RvalueDesc<'tcx> { @@ -49,3 +51,38 @@ pub fn describe_rvalue<'tcx>(rv: &Rvalue<'tcx>) -> Option> { }) } + +#[derive(Debug)] +pub enum Callee<'tcx> { + PtrOffset { pointee_ty: Ty<'tcx>, mutbl: Mutability }, +} + +pub fn ty_callee<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { + let (did, substs) = match *ty.kind() { + TyKind::FnDef(did, substs) => (did, substs), + _ => return None, + }; + let name = tcx.item_name(did); + let poly_sig = tcx.fn_sig(did); + let sig = poly_sig.skip_binder(); + + match name.as_str() { + "offset" => { + // The `offset` inherent method of `*const T` and `*mut T`. + let parent_did = tcx.parent(did)?; + if tcx.def_kind(parent_did) != DefKind::Impl { + return None; + } + if tcx.impl_trait_ref(parent_did).is_some() { + return None; + } + let parent_impl_ty = tcx.type_of(parent_did); + let (pointee_ty, mutbl) = match parent_impl_ty.kind() { + TyKind::RawPtr(tm) => (tm.ty, tm.mutbl), + _ => return None, + }; + Some(Callee::PtrOffset { pointee_ty, mutbl }) + }, + _ => None, + } +} diff --git a/examples/static-analysis/offset1.rs b/examples/static-analysis/offset1.rs new file mode 100644 index 0000000000..94713c104d --- /dev/null +++ b/examples/static-analysis/offset1.rs @@ -0,0 +1,19 @@ +use std::ptr; + +pub unsafe fn offset1_const(x: *mut i32) -> i32 { + *x.offset(1) +} + +pub unsafe fn offset1_unknown(x: *mut i32, off: isize) -> i32 { + *x.offset(off) +} + +/* +pub unsafe fn offset1_usize(x: *mut i32, off: usize) -> i32 { + *x.offset(off as isize) +} +*/ + +pub unsafe fn offset1_immut(x: *const i32, off: isize) -> i32 { + *x.offset(off) +} From f7b329ca0e9907ae5bed7fc10b0916190a8b858f Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 2 May 2022 13:15:18 -0700 Subject: [PATCH 14/38] analyze: require OFFSET_ADD and OFFSET_SUB for offset() args --- c2rust-analyze/src/dataflow/mod.rs | 4 +++- c2rust-analyze/src/dataflow/type_check.rs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 08de2a09f9..7edc904607 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -95,7 +95,9 @@ impl DataflowConstraints { #[allow(bad_style)] let PROPAGATE_UP = PermissionSet::READ | - PermissionSet::WRITE; + PermissionSet::WRITE | + PermissionSet::OFFSET_ADD | + PermissionSet::OFFSET_SUB; let old_a = hypothesis[a.index()]; let old_b = hypothesis[b.index()]; diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index ff773b8291..83d15bee3f 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -162,6 +162,8 @@ impl<'tcx> TypeChecker<'tcx, '_> { assert!(args.len() == 2); let rv_lty = self.visit_operand(&args[0]); self.do_assign(pl_lty.label, rv_lty.label); + let perms = PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB; + self.constraints.add_all_perms(rv_lty.label, perms); }, None => {}, } From de2dcee0ba7e7d44cb74cd3b4d6665ca126e0202 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 2 May 2022 16:54:38 -0700 Subject: [PATCH 15/38] analyze: handle constructs seen in insertion_sort --- c2rust-analyze/src/borrowck/mod.rs | 5 ++-- c2rust-analyze/src/borrowck/type_check.rs | 33 ++++++++++++++++++++--- c2rust-analyze/src/dataflow/type_check.rs | 22 +++++++++++++-- examples/static-analysis/offset2.rs | 13 +++++++++ 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 examples/static-analysis/offset2.rs diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index e71854d28a..70161de9be 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -118,7 +118,7 @@ pub fn borrowck_mir<'tcx>( eprintln!("final labeling for {:?}:", name); let lcx2 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); - for (local, _) in mir.local_decls.iter_enumerated() { + for (local, decl) in mir.local_decls.iter_enumerated() { let addr_of = hypothesis[acx.addr_of_local[local].index()]; let ty = lcx2.relabel(acx.local_tys[local], &mut |lty| { if lty.label == PointerId::NONE { @@ -127,7 +127,8 @@ pub fn borrowck_mir<'tcx>( hypothesis[lty.label.index()] } }); - eprintln!("{:?}: addr_of = {:?}, type = {:?}", local, addr_of, ty); + eprintln!("{:?}: addr_of = {:?}, type = {:?}, info = {:?}", + local, addr_of, ty, decl.local_info); } } diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 80633e7ffd..99a1053e65 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; use rustc_index::vec::IndexVec; use rustc_middle::mir::{ - Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, Operand, BorrowKind, - Local, LocalDecl, Location, ProjectionElem, + Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, BinOp, Place, Operand, + BorrowKind, Local, LocalDecl, Location, ProjectionElem, }; use rustc_middle::ty::{TyCtxt, TyKind}; use crate::borrowck::{LTy, LTyCtxt, Label}; use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; -use crate::context::PermissionSet; +use crate::context::{PermissionSet, PointerId}; use crate::util::{self, Callee}; @@ -41,6 +41,15 @@ impl<'tcx> TypeChecker<'tcx, '_> { lty = lty.args[0]; }, + ProjectionElem::Field(f, _field_ty) => { + match lty.ty.kind() { + TyKind::Tuple(..) => { + lty = lty.args[f.as_usize()]; + }, + _ => todo!("field of {:?}", lty), + } + }, + ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), } } @@ -132,6 +141,24 @@ impl<'tcx> TypeChecker<'tcx, '_> { lty }, + Rvalue::BinaryOp(BinOp::Offset, _) | + Rvalue::CheckedBinaryOp(BinOp::Offset, _) => todo!("visit_rvalue BinOp::Offset"), + Rvalue::BinaryOp(_, ref ab) | + Rvalue::CheckedBinaryOp(_, ref ab) => { + let ty = rv.ty(self.local_decls, *self.ltcx); + self.ltcx.label(ty, &mut |ty| { + assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..))); + Label::default() + }) + }, + + Rvalue::Cast(_, _, ty) => { + self.ltcx.label(ty, &mut |ty| { + assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..))); + Label::default() + }) + }, + ref rv => panic!("unsupported rvalue {:?}", rv), } } diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 83d15bee3f..740b0dbc75 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; use rustc_index::vec::IndexVec; use rustc_middle::mir::{ - Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, Place, PlaceRef, Operand, - BorrowKind, Local, LocalDecl, Location, ProjectionElem, Mutability, + Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, BinOp, Place, PlaceRef, + Operand, BorrowKind, Local, LocalDecl, Location, ProjectionElem, Mutability, }; use rustc_middle::mir::visit::{PlaceContext, NonMutatingUseContext, MutatingUseContext}; +use rustc_middle::ty::TyKind; use crate::context::{PermissionSet, PointerId, AnalysisCtxt, LTy}; use crate::util::{self, describe_rvalue, RvalueDesc, Callee}; use super::DataflowConstraints; @@ -60,6 +61,15 @@ impl<'tcx> TypeChecker<'tcx, '_> { lty = lty.args[0]; }, + ProjectionElem::Field(f, _field_ty) => { + match lty.ty.kind() { + TyKind::Tuple(..) => { + lty = lty.args[f.as_usize()]; + }, + _ => todo!("field of {:?}", lty), + } + }, + ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), } } @@ -93,6 +103,14 @@ impl<'tcx> TypeChecker<'tcx, '_> { }, None => match *rv { Rvalue::Use(ref op) => self.visit_operand(op).label, + Rvalue::BinaryOp(BinOp::Offset, _) => todo!("visit_rvalue BinOp::Offset"), + Rvalue::BinaryOp(..) => PointerId::NONE, + Rvalue::CheckedBinaryOp(BinOp::Offset, _) => todo!("visit_rvalue BinOp::Offset"), + Rvalue::CheckedBinaryOp(..) => PointerId::NONE, + Rvalue::Cast(_, _, ty) => { + assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..))); + PointerId::NONE + }, _ => panic!("TODO: handle assignment of {:?}", rv), }, } diff --git a/examples/static-analysis/offset2.rs b/examples/static-analysis/offset2.rs new file mode 100644 index 0000000000..f9cfae3169 --- /dev/null +++ b/examples/static-analysis/offset2.rs @@ -0,0 +1,13 @@ +use std::ptr; + +pub unsafe fn offset2_good(x: *mut i32, off: isize) { + let p = x.offset(off); + let q = x.offset(off); + *q = 1; +} + +pub unsafe fn offset2_bad(x: *mut i32, off: isize) { + let p = x.offset(off); + let q = x.offset(off); + *p = 1; +} From b8d0dac462859d77472f41bf0876e0df648b9489 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 5 May 2022 14:09:54 -0700 Subject: [PATCH 16/38] analyze: include variable names and source code in "final labeling" output --- c2rust-analyze/src/borrowck/mod.rs | 44 +++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index 70161de9be..cfd6e0957a 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -12,8 +12,8 @@ use rustc_interface::Queries; use rustc_interface::interface::Compiler; use rustc_middle::mir::{ Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, PlaceRef, PlaceElem, Operand, - Statement, StatementKind, BorrowKind, Constant, ConstantKind, + LocalDecl, LocalKind, LocalInfo, BindingForm, Mutability, Rvalue, AggregateKind, Place, + PlaceRef, PlaceElem, Operand, Statement, StatementKind, BorrowKind, Constant, ConstantKind, }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; use rustc_middle::mir::pretty; @@ -127,8 +127,12 @@ pub fn borrowck_mir<'tcx>( hypothesis[lty.label.index()] } }); - eprintln!("{:?}: addr_of = {:?}, type = {:?}, info = {:?}", - local, addr_of, ty, decl.local_info); + eprintln!("{:?} ({}): addr_of = {:?}, type = {:?}", + local, + describe_local(acx.tcx, decl), + addr_of, + ty, + ); } } @@ -249,3 +253,35 @@ fn assign_origins<'tcx>( } }) } + +fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String { + let mut span = decl.source_info.span; + if let Some(ref info) = decl.local_info { + if let LocalInfo::User(ref binding_form) = **info { + let binding_form = binding_form.as_ref().assert_crate_local(); + if let BindingForm::Var(ref v) = *binding_form { + span = v.pat_span; + } + } + } + + let s = tcx.sess.source_map().span_to_snippet(span).unwrap(); + let s = { + let mut s2 = String::new(); + for word in s.split_ascii_whitespace() { + if s2.len() > 0 { + s2.push(' '); + } + s2.push_str(word); + } + s2 + }; + + let (src1, src2, src3) = if s.len() > 20 { + (&s[..10], " ... ", &s[s.len() - 10 ..]) + } else { + (&s[..], "", "") + }; + let line = tcx.sess.source_map().lookup_char_pos(span.lo()).line; + format!("{}: {}{}{}", line, src1, src2, src3) +} From f30b994ac2ff3610653a76425088935de42b355b Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 5 May 2022 17:01:03 -0700 Subject: [PATCH 17/38] analyze: adjust "final labeling" output for long expressions --- c2rust-analyze/src/borrowck/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index cfd6e0957a..eadfc7ac94 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -278,7 +278,7 @@ fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String { }; let (src1, src2, src3) = if s.len() > 20 { - (&s[..10], " ... ", &s[s.len() - 10 ..]) + (&s[..15], " ... ", &s[s.len() - 5 ..]) } else { (&s[..], "", "") }; From 168c24de06404330ea7954d6b8e98c4a7b487ed8 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 5 May 2022 17:02:27 -0700 Subject: [PATCH 18/38] analyze: convert examples to a proper test suite --- c2rust-analyze/build.rs | 14 ++-- c2rust-analyze/tests/filecheck.rs | 59 ++++++++++++++++ c2rust-analyze/tests/filecheck/alias1.rs | 40 +++++++++++ c2rust-analyze/tests/filecheck/alias2.rs | 70 +++++++++++++++++++ c2rust-analyze/tests/filecheck/alias3.rs | 35 ++++++++++ .../tests/filecheck/insertion_sort.rs | 32 +++++++++ c2rust-analyze/tests/filecheck/offset1.rs | 31 ++++++++ c2rust-analyze/tests/filecheck/offset2.rs | 25 +++++++ examples/static-analysis/alias1.rs | 30 -------- examples/static-analysis/alias2.rs | 50 ------------- examples/static-analysis/alias3.rs | 25 ------- examples/static-analysis/insertion_sort.rs | 20 ------ .../static-analysis/insertion_sort_ref.rs | 14 ---- examples/static-analysis/offset1.rs | 19 ----- examples/static-analysis/offset2.rs | 13 ---- 15 files changed, 302 insertions(+), 175 deletions(-) create mode 100644 c2rust-analyze/tests/filecheck.rs create mode 100644 c2rust-analyze/tests/filecheck/alias1.rs create mode 100644 c2rust-analyze/tests/filecheck/alias2.rs create mode 100644 c2rust-analyze/tests/filecheck/alias3.rs create mode 100644 c2rust-analyze/tests/filecheck/insertion_sort.rs create mode 100644 c2rust-analyze/tests/filecheck/offset1.rs create mode 100644 c2rust-analyze/tests/filecheck/offset2.rs delete mode 100644 examples/static-analysis/alias1.rs delete mode 100644 examples/static-analysis/alias2.rs delete mode 100644 examples/static-analysis/alias3.rs delete mode 100644 examples/static-analysis/insertion_sort.rs delete mode 100644 examples/static-analysis/insertion_sort_ref.rs delete mode 100644 examples/static-analysis/offset1.rs delete mode 100644 examples/static-analysis/offset2.rs diff --git a/c2rust-analyze/build.rs b/c2rust-analyze/build.rs index ef32190135..bbd8f16794 100644 --- a/c2rust-analyze/build.rs +++ b/c2rust-analyze/build.rs @@ -1,15 +1,21 @@ +use std::env; use std::path::Path; use std::process::Command; use std::str; fn main() { + let rustc = env::var("RUSTC").unwrap(); // Add the toolchain lib/ directory to `-L`. This fixes the linker error "cannot find // -lLLVM-13-rust-1.60.0-nightly". - let out = Command::new("rustup") - .args(&["which", "rustc"]) + let out = Command::new(&rustc) + .args(&["--print", "sysroot"]) .output().unwrap(); assert!(out.status.success()); - let rustc_path = Path::new(str::from_utf8(&out.stdout).unwrap().trim_end()); - let lib_dir = rustc_path.parent().unwrap().parent().unwrap().join("lib"); + let sysroot = Path::new(str::from_utf8(&out.stdout).unwrap().trim_end()); + let lib_dir = sysroot.join("lib"); println!("cargo:rustc-link-search={}", lib_dir.display()); + + let target = env::var("HOST").unwrap(); + let target_lib_dir = lib_dir.join("rustlib").join(target).join("lib"); + println!("cargo:rustc-env=C2RUST_TARGET_LIB_DIR={}", target_lib_dir.display()); } diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs new file mode 100644 index 0000000000..7ccf7af0e0 --- /dev/null +++ b/c2rust-analyze/tests/filecheck.rs @@ -0,0 +1,59 @@ +use std::env; +use std::fs; +use std::os::unix::io::{AsRawFd, FromRawFd}; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::str; + +#[test] +fn filecheck() { + let lib_dir = env::var("C2RUST_TARGET_LIB_DIR").unwrap(); + let lib_dir = &lib_dir; + eprintln!("{:?}", lib_dir); + + let filecheck_bin = env::var("FILECHECK").unwrap_or_else(|_| "FileCheck".into()); + + for entry in fs::read_dir("tests/filecheck").unwrap() { + let entry = entry.unwrap(); + + if !entry.file_type().unwrap().is_file() { + continue; + } + + let name = entry.file_name(); + let name = name.to_str().unwrap(); + if name.starts_with(".") || !name.ends_with(".rs") { + continue; + } + + eprintln!("{:?}", entry.path()); + + let mut filecheck = Command::new(&filecheck_bin) + .arg(entry.path()) + .stdin(Stdio::piped()) + .spawn().unwrap(); + let pipe_fd = filecheck.stdin.as_ref().unwrap().as_raw_fd(); + let mut analyze = Command::new(env!("CARGO_BIN_EXE_c2rust-analyze")) + .arg(entry.path()) + .arg("-L").arg(lib_dir) + .arg("--crate-type").arg("rlib") + .arg("--cfg").arg("compiling_for_test") + .stdout(unsafe { Stdio::from_raw_fd(pipe_fd) }) + .stderr(unsafe { Stdio::from_raw_fd(pipe_fd) }) + .spawn().unwrap(); + + let filecheck_status = filecheck.wait().unwrap(); + assert!( + filecheck_status.success(), + "{:?}: FileCheck failed with status {:?}", + entry.path(), filecheck_status, + ); + + let analyze_status = analyze.wait().unwrap(); + assert!( + analyze_status.success(), + "{:?}: c2rust-analyze failed with status {:?}", + entry.path(), analyze_status, + ); + } +} diff --git a/c2rust-analyze/tests/filecheck/alias1.rs b/c2rust-analyze/tests/filecheck/alias1.rs new file mode 100644 index 0000000000..f5be6b6527 --- /dev/null +++ b/c2rust-analyze/tests/filecheck/alias1.rs @@ -0,0 +1,40 @@ +use std::ptr; + +// CHECK-LABEL: final labeling for "alias1_good" +pub unsafe fn alias1_good() { + // CHECK-DAG: ([[#@LINE+1]]: mut x): addr_of = READ | WRITE | UNIQUE, + let mut x = 0; + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = UNIQUE# + let p = ptr::addr_of_mut!(x); + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ | WRITE | UNIQUE# + let q = ptr::addr_of_mut!(x); + *q = 1; +} + +// CHECK-LABEL: final labeling for "alias1_bad" +pub unsafe fn alias1_bad() { + // CHECK-DAG: ([[#@LINE+1]]: mut x): addr_of = READ | WRITE, + let mut x = 0; + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# + let p = ptr::addr_of_mut!(x); + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = (empty)# + let q = ptr::addr_of_mut!(x); + *p = 1; +} + + +#[cfg(not(compiling_for_test))] +pub fn safe_alias1_good() { + let mut x = 0; + let p = &mut x; + let q = &mut x; + *q = 1; +} + +#[cfg(not(compiling_for_test))] +pub fn safe_alias1_bad() { + let mut x = 0; + let p = &mut x; + let q = &mut x; + *p = 1; +} diff --git a/c2rust-analyze/tests/filecheck/alias2.rs b/c2rust-analyze/tests/filecheck/alias2.rs new file mode 100644 index 0000000000..ade4f144f2 --- /dev/null +++ b/c2rust-analyze/tests/filecheck/alias2.rs @@ -0,0 +1,70 @@ +use std::ptr; + +// CHECK-LABEL: final labeling for "alias2_copy_good" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE | UNIQUE# +pub unsafe fn alias2_copy_good(x: *mut i32) { + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = UNIQUE# + let p = x; + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ | WRITE | UNIQUE# + let q = x; + *q = 1; +} + +// CHECK-LABEL: final labeling for "alias2_addr_of_good" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE | UNIQUE# +pub unsafe fn alias2_addr_of_good(x: *mut i32) { + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = UNIQUE# + let p = ptr::addr_of_mut!(*x); + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ | WRITE | UNIQUE# + let q = ptr::addr_of_mut!(*x); + *q = 1; +} + +// CHECK-LABEL: final labeling for "alias2_copy_bad" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +pub unsafe fn alias2_copy_bad(x: *mut i32) { + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# + let p = x; + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = (empty)# + let q = x; + *p = 1; +} + +// CHECK-LABEL: final labeling for "alias2_addr_of_bad" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +pub unsafe fn alias2_addr_of_bad(x: *mut i32) { + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# + let p = ptr::addr_of_mut!(*x); + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = (empty)# + let q = ptr::addr_of_mut!(*x); + *p = 1; +} + + +#[cfg(not(compiling_for_test))] +pub unsafe fn safe_alias2_copy_good(x: &mut i32) { + let p = x; + let q = x; + *q = 1; +} + +#[cfg(not(compiling_for_test))] +pub unsafe fn safe_alias2_addr_of_good(x: &mut i32) { + let p = &mut *x; + let q = &mut *x; + *q = 1; +} + +#[cfg(not(compiling_for_test))] +pub unsafe fn safe_alias2_copy_bad(x: &mut i32) { + let p = x; + let q = x; + *p = 1; +} + +#[cfg(not(compiling_for_test))] +pub unsafe fn safe_alias2_addr_of_bad(x: &mut i32) { + let p = &mut *x; + let q = &mut *x; + *p = 1; +} diff --git a/c2rust-analyze/tests/filecheck/alias3.rs b/c2rust-analyze/tests/filecheck/alias3.rs new file mode 100644 index 0000000000..21f607e140 --- /dev/null +++ b/c2rust-analyze/tests/filecheck/alias3.rs @@ -0,0 +1,35 @@ +use std::ptr; + +// CHECK-LABEL: final labeling for "alias3_copy_bad1" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +pub unsafe fn alias3_copy_bad1(x: *mut i32) { + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ# + let p = x; + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ | WRITE# + let q = x; + *q = *p; +} + +// CHECK-LABEL: final labeling for "alias3_copy_bad2" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +pub unsafe fn alias3_copy_bad2(x: *mut i32) { + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# + let p = x; + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ# + let q = x; + *p = *q; +} + +#[cfg(not(compiling_for_test))] +pub unsafe fn alias3_addr_of_bad1(x: *mut i32) { + let p = ptr::addr_of_mut!(*x); + let q = ptr::addr_of_mut!(*x); + *q = *p; +} + +#[cfg(not(compiling_for_test))] +pub unsafe fn alias3_addr_of_bad2(x: *mut i32) { + let p = ptr::addr_of_mut!(*x); + let q = ptr::addr_of_mut!(*x); + *p = *q; +} diff --git a/c2rust-analyze/tests/filecheck/insertion_sort.rs b/c2rust-analyze/tests/filecheck/insertion_sort.rs new file mode 100644 index 0000000000..a7c1d93cfe --- /dev/null +++ b/c2rust-analyze/tests/filecheck/insertion_sort.rs @@ -0,0 +1,32 @@ +#![allow(dead_code, mutable_transmutes, non_camel_case_types, non_snake_case, + non_upper_case_globals, unused_assignments, unused_mut)] +#![feature(rustc_private)] + +extern crate libc; + +#[no_mangle] +// CHECK-LABEL: final labeling for "insertion_sort" +// CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB# +pub unsafe extern "C" fn insertion_sort(n: libc::c_int, p: *mut libc::c_int) { + let mut i: libc::c_int = 1 as libc::c_int; + while i < n { + // CHECK-DAG: ([[#@LINE+2]]: p): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: p.offset(i as isize)): {{.*}}type = READ | UNIQUE# + let tmp: libc::c_int = *p.offset(i as isize); + let mut j: libc::c_int = i; + // CHECK-DAG: ([[#@LINE+2]]: p): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: p.offset{{.*}}): {{.*}}type = READ | UNIQUE# + while j > 0 as libc::c_int && *p.offset((j - 1 as libc::c_int) as isize) > tmp { + // CHECK-DAG: ([[#@LINE+4]]: p): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+3]]: p): {{.*}}type = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+2]]: p.offset((j {{.*}}): {{.*}}type = READ | UNIQUE# + // CHECK-DAG: ([[#@LINE+1]]: p.offset(j {{.*}}): {{.*}}type = READ | WRITE | UNIQUE# + *p.offset(j as isize) = *p.offset((j - 1 as libc::c_int) as isize); + j -= 1 + } + // CHECK-DAG: ([[#@LINE+2]]: p): {{.*}}type = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: p.offset(j {{.*}}): {{.*}}type = READ | WRITE | UNIQUE# + *p.offset(j as isize) = tmp; + i += 1 + } +} diff --git a/c2rust-analyze/tests/filecheck/offset1.rs b/c2rust-analyze/tests/filecheck/offset1.rs new file mode 100644 index 0000000000..82dbbfb19e --- /dev/null +++ b/c2rust-analyze/tests/filecheck/offset1.rs @@ -0,0 +1,31 @@ +use std::ptr; + +// CHECK-LABEL: final labeling for "offset1_const" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# +pub unsafe fn offset1_const(x: *mut i32) -> i32 { + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: x.offset(1)): {{.*}}type = READ | UNIQUE# + *x.offset(1) +} + +// CHECK-LABEL: final labeling for "offset1_unknown" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# +pub unsafe fn offset1_unknown(x: *mut i32, off: isize) -> i32 { + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: x.offset(off)): {{.*}}type = READ | UNIQUE# + *x.offset(off) +} + +/* +pub unsafe fn offset1_usize(x: *mut i32, off: usize) -> i32 { + *x.offset(off as isize) +} +*/ + +// CHECK-LABEL: final labeling for "offset1_immut" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# +pub unsafe fn offset1_immut(x: *const i32, off: isize) -> i32 { + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: x.offset(off)): {{.*}}type = READ | UNIQUE# + *x.offset(off) +} diff --git a/c2rust-analyze/tests/filecheck/offset2.rs b/c2rust-analyze/tests/filecheck/offset2.rs new file mode 100644 index 0000000000..df2d1c93c1 --- /dev/null +++ b/c2rust-analyze/tests/filecheck/offset2.rs @@ -0,0 +1,25 @@ +use std::ptr; + +// CHECK-LABEL: final labeling for "offset2_good" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB# +pub unsafe fn offset2_good(x: *mut i32, off: isize) { + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = UNIQUE# + let p = x.offset(off); + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | WRITE | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ | WRITE | UNIQUE# + let q = x.offset(off); + *q = 1; +} + +// CHECK-LABEL: final labeling for "offset2_bad" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE | OFFSET_ADD | OFFSET_SUB# +pub unsafe fn offset2_bad(x: *mut i32, off: isize) { + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | WRITE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# + let p = x.offset(off); + // CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = (empty)# + let q = x.offset(off); + *p = 1; +} diff --git a/examples/static-analysis/alias1.rs b/examples/static-analysis/alias1.rs deleted file mode 100644 index b08d3da620..0000000000 --- a/examples/static-analysis/alias1.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::ptr; - -pub unsafe fn alias1_good() { - let mut x = 0; - let p = ptr::addr_of_mut!(x); - let q = ptr::addr_of_mut!(x); - *q = 1; -} - -pub unsafe fn alias1_bad() { - let mut x = 0; - let p = ptr::addr_of_mut!(x); - let q = ptr::addr_of_mut!(x); - *p = 1; -} - - -pub fn safe_alias1_good() { - let mut x = 0; - let p = &mut x; - let q = &mut x; - *q = 1; -} - -pub fn safe_alias1_bad() { - let mut x = 0; - let p = &mut x; - let q = &mut x; - *p = 1; -} diff --git a/examples/static-analysis/alias2.rs b/examples/static-analysis/alias2.rs deleted file mode 100644 index 865107649e..0000000000 --- a/examples/static-analysis/alias2.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::ptr; - -pub unsafe fn alias2_copy_good(x: *mut i32) { - let p = x; - let q = x; - *q = 1; -} - -pub unsafe fn alias2_addr_of_good(x: *mut i32) { - let p = ptr::addr_of_mut!(*x); - let q = ptr::addr_of_mut!(*x); - *q = 1; -} - -pub unsafe fn alias2_copy_bad(x: *mut i32) { - let p = x; - let q = x; - *p = 1; -} - -pub unsafe fn alias2_addr_of_bad(x: *mut i32) { - let p = ptr::addr_of_mut!(*x); - let q = ptr::addr_of_mut!(*x); - *p = 1; -} - - -pub unsafe fn safe_alias2_copy_good(x: &mut i32) { - let p = x; - let q = x; - *q = 1; -} - -pub unsafe fn safe_alias2_addr_of_good(x: &mut i32) { - let p = &mut *x; - let q = &mut *x; - *q = 1; -} - -pub unsafe fn safe_alias2_copy_bad(x: &mut i32) { - let p = x; - let q = x; - *p = 1; -} - -pub unsafe fn safe_alias2_addr_of_bad(x: &mut i32) { - let p = &mut *x; - let q = &mut *x; - *p = 1; -} diff --git a/examples/static-analysis/alias3.rs b/examples/static-analysis/alias3.rs deleted file mode 100644 index 9620bf4b94..0000000000 --- a/examples/static-analysis/alias3.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::ptr; - -pub unsafe fn alias3_copy_bad1(x: *mut i32) { - let p = x; - let q = x; - *q = *p; -} - -pub unsafe fn alias3_copy_bad2(x: *mut i32) { - let p = x; - let q = x; - *p = *q; -} - -pub unsafe fn alias3_addr_of_bad1(x: *mut i32) { - let p = ptr::addr_of_mut!(*x); - let q = ptr::addr_of_mut!(*x); - *q = *p; -} - -pub unsafe fn alias3_addr_of_bad2(x: *mut i32) { - let p = ptr::addr_of_mut!(*x); - let q = ptr::addr_of_mut!(*x); - *p = *q; -} diff --git a/examples/static-analysis/insertion_sort.rs b/examples/static-analysis/insertion_sort.rs deleted file mode 100644 index 6faa543393..0000000000 --- a/examples/static-analysis/insertion_sort.rs +++ /dev/null @@ -1,20 +0,0 @@ -#![allow(dead_code, mutable_transmutes, non_camel_case_types, non_snake_case, - non_upper_case_globals, unused_assignments, unused_mut)] -#![feature(rustc_private)] - -extern crate libc; - -#[no_mangle] -pub unsafe extern "C" fn insertion_sort(n: libc::c_int, p: *mut libc::c_int) { - let mut i: libc::c_int = 1 as libc::c_int; - while i < n { - let tmp: libc::c_int = *p.offset(i as isize); - let mut j: libc::c_int = i; - while j > 0 as libc::c_int && *p.offset((j - 1 as libc::c_int) as isize) > tmp { - *p.offset(j as isize) = *p.offset((j - 1 as libc::c_int) as isize); - j -= 1 - } - *p.offset(j as isize) = tmp; - i += 1 - } -} diff --git a/examples/static-analysis/insertion_sort_ref.rs b/examples/static-analysis/insertion_sort_ref.rs deleted file mode 100644 index 179e2f9cbd..0000000000 --- a/examples/static-analysis/insertion_sort_ref.rs +++ /dev/null @@ -1,14 +0,0 @@ -type c_int = i32; -pub fn insertion_sort(n: c_int, p: &mut [c_int]) { - let mut i: c_int = 1 as c_int; - while i < n { - let tmp: c_int = p[i as usize]; - let mut j: c_int = i; - while j > 0 as c_int && p[(j - 1 as c_int) as usize] > tmp { - p[j as usize] = p[(j - 1 as c_int) as usize]; - j -= 1 - } - p[j as usize] = tmp; - i += 1 - } -} diff --git a/examples/static-analysis/offset1.rs b/examples/static-analysis/offset1.rs deleted file mode 100644 index 94713c104d..0000000000 --- a/examples/static-analysis/offset1.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::ptr; - -pub unsafe fn offset1_const(x: *mut i32) -> i32 { - *x.offset(1) -} - -pub unsafe fn offset1_unknown(x: *mut i32, off: isize) -> i32 { - *x.offset(off) -} - -/* -pub unsafe fn offset1_usize(x: *mut i32, off: usize) -> i32 { - *x.offset(off as isize) -} -*/ - -pub unsafe fn offset1_immut(x: *const i32, off: isize) -> i32 { - *x.offset(off) -} diff --git a/examples/static-analysis/offset2.rs b/examples/static-analysis/offset2.rs deleted file mode 100644 index f9cfae3169..0000000000 --- a/examples/static-analysis/offset2.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::ptr; - -pub unsafe fn offset2_good(x: *mut i32, off: isize) { - let p = x.offset(off); - let q = x.offset(off); - *q = 1; -} - -pub unsafe fn offset2_bad(x: *mut i32, off: isize) { - let p = x.offset(off); - let q = x.offset(off); - *p = 1; -} From b17a2948648b774d4b601d94cdb572d0e90c4e8f Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Fri, 6 May 2022 13:56:35 -0700 Subject: [PATCH 19/38] analyze: add test for p.offset(..).offset(..) --- c2rust-analyze/tests/filecheck/offset1.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/c2rust-analyze/tests/filecheck/offset1.rs b/c2rust-analyze/tests/filecheck/offset1.rs index 82dbbfb19e..4ee614e9b1 100644 --- a/c2rust-analyze/tests/filecheck/offset1.rs +++ b/c2rust-analyze/tests/filecheck/offset1.rs @@ -29,3 +29,12 @@ pub unsafe fn offset1_immut(x: *const i32, off: isize) -> i32 { // CHECK-DAG: ([[#@LINE+1]]: x.offset(off)): {{.*}}type = READ | UNIQUE# *x.offset(off) } + +// CHECK-LABEL: final labeling for "offset1_double" +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# +pub unsafe fn offset1_double(x: *mut i32, off: isize) -> i32 { + // CHECK-DAG: ([[#@LINE+3]]: x): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+2]]: x.offset(off)): {{.*}}type = READ | UNIQUE | OFFSET_ADD | OFFSET_SUB# + // CHECK-DAG: ([[#@LINE+1]]: x.offset{{.*}}...{{.*}}): {{.*}}type = READ | UNIQUE# + *x.offset(off).offset(off) +} From 6e8ca3f73eecb0a47e38be803143da1a3333c077 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 26 May 2022 11:16:46 -0700 Subject: [PATCH 20/38] analyze: refactor dataflow update tracking --- c2rust-analyze/src/dataflow/mod.rs | 108 ++++++++++++++++++----------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 7edc904607..8b17fa1af5 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -1,3 +1,5 @@ +use std::mem; + use rustc_middle::mir::Body; use crate::context::{PermissionSet, PointerId, AnalysisCtxt}; @@ -64,8 +66,9 @@ impl DataflowConstraints { } fn propagate_inner(&self, hypothesis: &mut [PermissionSet]) -> Result { + let mut hypothesis = TrackedSlice::new(hypothesis); + let mut changed = false; - let mut dirty = vec![true; hypothesis.len()]; let mut i = 0; loop { if i > hypothesis.len() + self.constraints.len() { @@ -73,12 +76,10 @@ impl DataflowConstraints { } i += 1; - let mut new_dirty = vec![false; hypothesis.len()]; - let mut any_new_dirty = false; for c in &self.constraints { match *c { Constraint::Subset(a, b) => { - if !dirty[a.index()] && !dirty[b.index()] { + if !hypothesis.dirty(a.index()) && !hypothesis.dirty(b.index()) { continue; } @@ -99,58 +100,35 @@ impl DataflowConstraints { PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB; - let old_a = hypothesis[a.index()]; - let old_b = hypothesis[b.index()]; - let new_a = old_a & !(!old_b & PROPAGATE_DOWN); - let new_b = old_b | (old_a & PROPAGATE_UP); - if new_a != old_a { - eprintln!("changed {:?}: {:?} => {:?}", a, old_a, new_a); - hypothesis[a.index()] = new_a; - new_dirty[a.index()] = true; - any_new_dirty = true; - } - if new_b != old_b { - eprintln!("changed {:?}: {:?} => {:?}", b, old_b, new_b); - hypothesis[b.index()] = new_b; - new_dirty[b.index()] = true; - any_new_dirty = true; - } + let old_a = *hypothesis.get(a.index()); + let old_b = *hypothesis.get(b.index()); + hypothesis.set(a.index(), old_a & !(!old_b & PROPAGATE_DOWN)); + hypothesis.set(b.index(), old_b | (old_a & PROPAGATE_UP)); }, Constraint::AllPerms(ptr, perms) => { - if !dirty[ptr.index()] { + if !hypothesis.dirty(ptr.index()) { continue; } - let old = hypothesis[ptr.index()]; - let new = old | perms; - if new != old { - eprintln!("changed {:?}: {:?} => {:?}", ptr, old, new); - hypothesis[ptr.index()] = new; - new_dirty[ptr.index()] = true; - any_new_dirty = true; - } + let old = *hypothesis.get(ptr.index()); + hypothesis.set(ptr.index(), old | perms); }, Constraint::NoPerms(ptr, perms) => { - if !dirty[ptr.index()] { + if !hypothesis.dirty(ptr.index()) { continue; } - let old = hypothesis[ptr.index()]; - let new = old & !perms; - if new != old { - hypothesis[ptr.index()] = new; - eprintln!("changed {:?}: {:?} => {:?}", ptr, old, new); - new_dirty[ptr.index()] = true; - any_new_dirty = true; - } + let old = *hypothesis.get(ptr.index()); + hypothesis.set(ptr.index(), old & !perms); }, } } - if !any_new_dirty { + if !hypothesis.any_new_dirty() { break; } - dirty = new_dirty; + hypothesis.swap_dirty(); + changed = true; } Ok(changed) @@ -158,6 +136,56 @@ impl DataflowConstraints { } +struct TrackedSlice<'a, T> { + xs: &'a mut [T], + dirty: Vec, + new_dirty: Vec, + any_new_dirty: bool, +} + +impl<'a, T: PartialEq> TrackedSlice<'a, T> { + pub fn new(xs: &'a mut [T]) -> TrackedSlice<'a, T> { + let n = xs.len(); + TrackedSlice { + xs, + dirty: vec![true; n], + new_dirty: vec![false; n], + any_new_dirty: false, + } + } + + pub fn len(&self) -> usize { + self.xs.len() + } + + pub fn get(&self, i: usize) -> &T { + &self.xs[i] + } + + pub fn dirty(&self, i: usize) -> bool { + self.dirty[i] + } + + pub fn any_new_dirty(&self) -> bool { + self.any_new_dirty + } + + pub fn set(&mut self, i: usize, x: T) { + if x != self.xs[i] { + self.xs[i] = x; + self.new_dirty[i] = true; + self.any_new_dirty = true; + } + } + + pub fn swap_dirty(&mut self) { + mem::swap(&mut self.dirty, &mut self.new_dirty); + self.new_dirty.fill(false); + self.any_new_dirty = false; + } +} + + pub fn generate_constraints<'tcx>( acx: &AnalysisCtxt<'tcx>, mir: &Body<'tcx>, From 35d8926965d4ed2f95019d0cada5abbf50af4046 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 26 May 2022 13:27:48 -0700 Subject: [PATCH 21/38] analyze: refactor dataflow propagation to allow pluggable update rules --- c2rust-analyze/src/dataflow/mod.rs | 119 ++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 34 deletions(-) diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 8b17fa1af5..8ef3e29030 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -57,7 +57,62 @@ impl DataflowConstraints { for (i, p) in hypothesis.iter().enumerate() { eprintln!(" {}: {:?}", i, p); } - match self.propagate_inner(hypothesis) { + + struct PropagatePerms; + impl PropagateRules for PropagatePerms { + fn subset( + &mut self, + a_ptr: PointerId, + a_val: &PermissionSet, + b_ptr: PointerId, + b_val: &PermissionSet, + ) -> (PermissionSet, PermissionSet) { + let old_a = *a_val; + let old_b = *b_val; + + // These should be `const`s, but that produces `error[E0015]: cannot call + // non-const operator in constants`. + + // Permissions that should be propagated "down": if the superset (`b`) + // doesn't have it, then the subset (`a`) should have it removed. + #[allow(bad_style)] + let PROPAGATE_DOWN = + PermissionSet::UNIQUE; + // Permissions that should be propagated "up": if the subset (`a`) has it, + // then the superset (`b`) should be given it. + #[allow(bad_style)] + let PROPAGATE_UP = + PermissionSet::READ | + PermissionSet::WRITE | + PermissionSet::OFFSET_ADD | + PermissionSet::OFFSET_SUB; + + ( + old_a & !(!old_b & PROPAGATE_DOWN), + old_b | (old_a & PROPAGATE_UP), + ) + } + + fn all_perms( + &mut self, + ptr: PointerId, + perms: PermissionSet, + val: &PermissionSet, + ) -> PermissionSet { + *val | perms + } + + fn no_perms( + &mut self, + ptr: PointerId, + perms: PermissionSet, + val: &PermissionSet, + ) -> PermissionSet { + *val & !perms + } + } + + match self.propagate_inner(hypothesis, &mut PropagatePerms) { Ok(changed) => changed, Err(msg) => { panic!("{}", msg); @@ -65,13 +120,14 @@ impl DataflowConstraints { } } - fn propagate_inner(&self, hypothesis: &mut [PermissionSet]) -> Result { - let mut hypothesis = TrackedSlice::new(hypothesis); + fn propagate_inner(&self, xs: &mut [T], rules: &mut R) -> Result + where T: PartialEq, R: PropagateRules { + let mut xs = TrackedSlice::new(xs); let mut changed = false; let mut i = 0; loop { - if i > hypothesis.len() + self.constraints.len() { + if i > xs.len() + self.constraints.len() { return Err(format!("infinite loop in dataflow edges")); } i += 1; @@ -79,55 +135,43 @@ impl DataflowConstraints { for c in &self.constraints { match *c { Constraint::Subset(a, b) => { - if !hypothesis.dirty(a.index()) && !hypothesis.dirty(b.index()) { + if !xs.dirty(a.index()) && !xs.dirty(b.index()) { continue; } - // These should be `const`s, but that produces `error[E0015]: cannot call - // non-const operator in constants`. - - // Permissions that should be propagated "down": if the superset (`b`) - // doesn't have it, then the subset (`a`) should have it removed. - #[allow(bad_style)] - let PROPAGATE_DOWN = - PermissionSet::UNIQUE; - // Permissions that should be propagated "up": if the subset (`a`) has it, - // then the superset (`b`) should be given it. - #[allow(bad_style)] - let PROPAGATE_UP = - PermissionSet::READ | - PermissionSet::WRITE | - PermissionSet::OFFSET_ADD | - PermissionSet::OFFSET_SUB; - - let old_a = *hypothesis.get(a.index()); - let old_b = *hypothesis.get(b.index()); - hypothesis.set(a.index(), old_a & !(!old_b & PROPAGATE_DOWN)); - hypothesis.set(b.index(), old_b | (old_a & PROPAGATE_UP)); + let old_a = xs.get(a.index()); + let old_b = xs.get(b.index()); + let (new_a, new_b) = rules.subset(a, old_a, b, old_b); + xs.set(a.index(), new_a); + xs.set(b.index(), new_b); }, Constraint::AllPerms(ptr, perms) => { - if !hypothesis.dirty(ptr.index()) { + if !xs.dirty(ptr.index()) { continue; } - let old = *hypothesis.get(ptr.index()); - hypothesis.set(ptr.index(), old | perms); + + let old = xs.get(ptr.index()); + let new = rules.all_perms(ptr, perms, old); + xs.set(ptr.index(), new); }, Constraint::NoPerms(ptr, perms) => { - if !hypothesis.dirty(ptr.index()) { + if !xs.dirty(ptr.index()) { continue; } - let old = *hypothesis.get(ptr.index()); - hypothesis.set(ptr.index(), old & !perms); + + let old = xs.get(ptr.index()); + let new = rules.no_perms(ptr, perms, old); + xs.set(ptr.index(), new); }, } } - if !hypothesis.any_new_dirty() { + if !xs.any_new_dirty() { break; } - hypothesis.swap_dirty(); + xs.swap_dirty(); changed = true; } @@ -186,6 +230,13 @@ impl<'a, T: PartialEq> TrackedSlice<'a, T> { } +trait PropagateRules { + fn subset(&mut self, a_ptr: PointerId, a_val: &T, b_ptr: PointerId, b_val: &T) -> (T, T); + fn all_perms(&mut self, ptr: PointerId, perms: PermissionSet, val: &T) -> T; + fn no_perms(&mut self, ptr: PointerId, perms: PermissionSet, val: &T) -> T; +} + + pub fn generate_constraints<'tcx>( acx: &AnalysisCtxt<'tcx>, mir: &Body<'tcx>, From f47567722ef7dfa95065be095634b7594cb9f945 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 26 May 2022 15:08:20 -0700 Subject: [PATCH 22/38] analyze: determine which pointers should use &Cell --- c2rust-analyze/src/borrowck/mod.rs | 53 +--------------- c2rust-analyze/src/context.rs | 14 +++++ c2rust-analyze/src/dataflow/mod.rs | 71 +++++++++++++++++++++- c2rust-analyze/src/main.rs | 77 +++++++++++++++++++++++- c2rust-analyze/tests/filecheck/alias1.rs | 6 +- c2rust-analyze/tests/filecheck/alias2.rs | 6 +- c2rust-analyze/tests/filecheck/alias3.rs | 18 ++++-- 7 files changed, 178 insertions(+), 67 deletions(-) diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index eadfc7ac94..b31ebd1c7d 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -12,7 +12,7 @@ use rustc_interface::Queries; use rustc_interface::interface::Compiler; use rustc_middle::mir::{ Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, LocalKind, LocalInfo, BindingForm, Mutability, Rvalue, AggregateKind, Place, + LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, PlaceRef, PlaceElem, Operand, Statement, StatementKind, BorrowKind, Constant, ConstantKind, }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; @@ -115,25 +115,6 @@ pub fn borrowck_mir<'tcx>( break; } } - - eprintln!("final labeling for {:?}:", name); - let lcx2 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); - for (local, decl) in mir.local_decls.iter_enumerated() { - let addr_of = hypothesis[acx.addr_of_local[local].index()]; - let ty = lcx2.relabel(acx.local_tys[local], &mut |lty| { - if lty.label == PointerId::NONE { - PermissionSet::empty() - } else { - hypothesis[lty.label.index()] - } - }); - eprintln!("{:?} ({}): addr_of = {:?}, type = {:?}", - local, - describe_local(acx.tcx, decl), - addr_of, - ty, - ); - } } @@ -253,35 +234,3 @@ fn assign_origins<'tcx>( } }) } - -fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String { - let mut span = decl.source_info.span; - if let Some(ref info) = decl.local_info { - if let LocalInfo::User(ref binding_form) = **info { - let binding_form = binding_form.as_ref().assert_crate_local(); - if let BindingForm::Var(ref v) = *binding_form { - span = v.pat_span; - } - } - } - - let s = tcx.sess.source_map().span_to_snippet(span).unwrap(); - let s = { - let mut s2 = String::new(); - for word in s.split_ascii_whitespace() { - if s2.len() > 0 { - s2.push(' '); - } - s2.push_str(word); - } - s2 - }; - - let (src1, src2, src3) = if s.len() > 20 { - (&s[..15], " ... ", &s[s.len() - 5 ..]) - } else { - (&s[..], "", "") - }; - let line = tcx.sess.source_map().lookup_char_pos(span.lo()).line; - format!("{}: {}{}{}", line, src1, src2, src3) -} diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 41badf3e6e..7fb5868ff0 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -32,6 +32,20 @@ bitflags! { } +bitflags! { + /// Additional flags describing a given pointer type. These are mainly derived from + /// `PermissionSet`, but don't follow the normal subtyping rules and propagation algorithm. + #[derive(Default)] + pub struct FlagSet: u16 { + /// The pointee type is wrapped in `Cell`. This is tracked separately from the + /// `PermissionSet` since it depends on the past/future uses of the pointer in an unusual + /// way, and it can't be freely discarded (or its inverse freely added) as is the case for + /// everything in `PermissionSet`. + const CELL = 0x0001; + } +} + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct PointerId(u32); diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 8ef3e29030..3911e27491 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -1,14 +1,14 @@ use std::mem; use rustc_middle::mir::Body; -use crate::context::{PermissionSet, PointerId, AnalysisCtxt}; +use crate::context::{PermissionSet, FlagSet, PointerId, AnalysisCtxt}; mod type_check; #[derive(Clone, Debug)] enum Constraint { - /// Pointer `.0` must have a subset of the permissions of poniter `.1`. + /// Pointer `.0` must have a subset of the permissions of pointer `.1`. Subset(PointerId, PointerId), /// Pointer `.0` must have all the permissions in `.1`. AllPerms(PointerId, PermissionSet), @@ -177,6 +177,73 @@ impl DataflowConstraints { Ok(changed) } + + /// Update the pointer permissions in `hypothesis` to satisfy these constraints. + pub fn propagate_cell(&self, perms: &[PermissionSet], flags: &mut [FlagSet]) { + // All pointers that are WRITE and not UNIQUE must have a type like `&Cell<_>`. + for (p, f) in perms.iter().zip(flags.iter_mut()) { + if p.contains(PermissionSet::WRITE) && !p.contains(PermissionSet::UNIQUE) { + f.insert(FlagSet::CELL); + } + } + + struct Rules<'a> { + perms: &'a [PermissionSet], + } + impl PropagateRules for Rules<'_> { + fn subset( + &mut self, + a_ptr: PointerId, + a_val: &FlagSet, + b_ptr: PointerId, + b_val: &FlagSet, + ) -> (FlagSet, FlagSet) { + // Propagate `CELL` both forward and backward. On the backward side, if `b` has + // both `WRITE` and `UNIQUE`, then we remove `CELL`, since `&mut T` can be + // converted to `&Cell`. + let mut a_flags = *a_val; + let mut b_flags = *b_val; + if a_flags.contains(FlagSet::CELL) { + b_flags.insert(FlagSet::CELL); + } + if b_flags.contains(FlagSet::CELL) { + a_flags.insert(FlagSet::CELL); + } + + let b_perms = self.perms[b_ptr.index()]; + if b_perms.contains(PermissionSet::WRITE | PermissionSet::UNIQUE) { + b_flags.remove(FlagSet::CELL); + } + + (a_flags, b_flags) + } + + fn all_perms( + &mut self, + ptr: PointerId, + perms: PermissionSet, + val: &FlagSet, + ) -> FlagSet { + *val + } + + fn no_perms( + &mut self, + ptr: PointerId, + perms: PermissionSet, + val: &FlagSet, + ) -> FlagSet { + *val + } + } + + match self.propagate_inner(flags, &mut Rules { perms }) { + Ok(_changed) => {}, + Err(msg) => { + panic!("{}", msg); + }, + } + } } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 471db7f70d..01a4c64d91 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -27,8 +27,8 @@ use rustc_interface::Queries; use rustc_interface::interface::Compiler; use rustc_middle::mir::{ Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, Operand, Statement, - StatementKind, BorrowKind, Constant, ConstantKind, + LocalDecl, LocalKind, LocalInfo, BindingForm, Mutability, Rvalue, AggregateKind, Place, + Operand, Statement, StatementKind, BorrowKind, Constant, ConstantKind, }; use rustc_middle::mir::interpret::{Allocation, ConstValue}; use rustc_middle::mir::pretty; @@ -39,7 +39,7 @@ use rustc_span::DUMMY_SP; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; -use crate::context::{AnalysisCtxt, PointerId, PermissionSet, LTy}; +use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; mod borrowck; @@ -82,6 +82,45 @@ fn inspect_mir<'tcx>( dataflow.propagate(&mut hypothesis); borrowck::borrowck_mir(&acx, &dataflow, &mut hypothesis, name.as_str(), mir); + + let mut flags = vec![FlagSet::empty(); acx.num_pointers()]; + dataflow.propagate_cell(&hypothesis, &mut flags); + + + eprintln!("final labeling for {:?}:", name); + let lcx1 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); + let lcx2 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); + for (local, decl) in mir.local_decls.iter_enumerated() { + let addr_of1 = hypothesis[acx.addr_of_local[local].index()]; + let ty1 = lcx1.relabel(acx.local_tys[local], &mut |lty| { + if lty.label == PointerId::NONE { + PermissionSet::empty() + } else { + hypothesis[lty.label.index()] + } + }); + eprintln!("{:?} ({}): addr_of = {:?}, type = {:?}", + local, + describe_local(acx.tcx, decl), + addr_of1, + ty1, + ); + + let addr_of2 = flags[acx.addr_of_local[local].index()]; + let ty2 = lcx2.relabel(acx.local_tys[local], &mut |lty| { + if lty.label == PointerId::NONE { + FlagSet::empty() + } else { + flags[lty.label.index()] + } + }); + eprintln!("{:?} ({}): addr_of flags = {:?}, type flags = {:?}", + local, + describe_local(acx.tcx, decl), + addr_of2, + ty2, + ); + } } fn assign_pointer_ids<'tcx>( @@ -95,6 +134,38 @@ fn assign_pointer_ids<'tcx>( }) } +fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String { + let mut span = decl.source_info.span; + if let Some(ref info) = decl.local_info { + if let LocalInfo::User(ref binding_form) = **info { + let binding_form = binding_form.as_ref().assert_crate_local(); + if let BindingForm::Var(ref v) = *binding_form { + span = v.pat_span; + } + } + } + + let s = tcx.sess.source_map().span_to_snippet(span).unwrap(); + let s = { + let mut s2 = String::new(); + for word in s.split_ascii_whitespace() { + if s2.len() > 0 { + s2.push(' '); + } + s2.push_str(word); + } + s2 + }; + + let (src1, src2, src3) = if s.len() > 20 { + (&s[..15], " ... ", &s[s.len() - 5 ..]) + } else { + (&s[..], "", "") + }; + let line = tcx.sess.source_map().lookup_char_pos(span.lo()).line; + format!("{}: {}{}{}", line, src1, src2, src3) +} + struct AnalysisCallbacks; diff --git a/c2rust-analyze/tests/filecheck/alias1.rs b/c2rust-analyze/tests/filecheck/alias1.rs index f5be6b6527..8de6926c54 100644 --- a/c2rust-analyze/tests/filecheck/alias1.rs +++ b/c2rust-analyze/tests/filecheck/alias1.rs @@ -13,11 +13,13 @@ pub unsafe fn alias1_good() { // CHECK-LABEL: final labeling for "alias1_bad" pub unsafe fn alias1_bad() { - // CHECK-DAG: ([[#@LINE+1]]: mut x): addr_of = READ | WRITE, + // CHECK-DAG: ([[#@LINE+2]]: mut x): addr_of = READ | WRITE, + // CHECK-DAG: ([[#@LINE+1]]: mut x): addr_of flags = CELL, let mut x = 0; // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# let p = ptr::addr_of_mut!(x); - // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = (empty)# + // CHECK-DAG: ([[#@LINE+2]]: q): {{.*}}type = (empty)# + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type flags = CELL# let q = ptr::addr_of_mut!(x); *p = 1; } diff --git a/c2rust-analyze/tests/filecheck/alias2.rs b/c2rust-analyze/tests/filecheck/alias2.rs index ade4f144f2..504590accc 100644 --- a/c2rust-analyze/tests/filecheck/alias2.rs +++ b/c2rust-analyze/tests/filecheck/alias2.rs @@ -21,7 +21,8 @@ pub unsafe fn alias2_addr_of_good(x: *mut i32) { } // CHECK-LABEL: final labeling for "alias2_copy_bad" -// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type flags = CELL# pub unsafe fn alias2_copy_bad(x: *mut i32) { // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# let p = x; @@ -31,7 +32,8 @@ pub unsafe fn alias2_copy_bad(x: *mut i32) { } // CHECK-LABEL: final labeling for "alias2_addr_of_bad" -// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type flags = CELL# pub unsafe fn alias2_addr_of_bad(x: *mut i32) { // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# let p = ptr::addr_of_mut!(*x); diff --git a/c2rust-analyze/tests/filecheck/alias3.rs b/c2rust-analyze/tests/filecheck/alias3.rs index 21f607e140..40d70a5099 100644 --- a/c2rust-analyze/tests/filecheck/alias3.rs +++ b/c2rust-analyze/tests/filecheck/alias3.rs @@ -1,21 +1,27 @@ use std::ptr; // CHECK-LABEL: final labeling for "alias3_copy_bad1" -// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type flags = CELL# pub unsafe fn alias3_copy_bad1(x: *mut i32) { - // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ# + // CHECK-DAG: ([[#@LINE+2]]: p): {{.*}}type = READ# + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type flags = CELL# let p = x; - // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ | WRITE# + // CHECK-DAG: ([[#@LINE+2]]: q): {{.*}}type = READ | WRITE# + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type flags = CELL# let q = x; *q = *p; } // CHECK-LABEL: final labeling for "alias3_copy_bad2" -// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+2]]: x): {{.*}}type = READ | WRITE# +// CHECK-DAG: ([[#@LINE+1]]: x): {{.*}}type flags = CELL# pub unsafe fn alias3_copy_bad2(x: *mut i32) { - // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type = READ | WRITE# + // CHECK-DAG: ([[#@LINE+2]]: p): {{.*}}type = READ | WRITE# + // CHECK-DAG: ([[#@LINE+1]]: p): {{.*}}type flags = CELL# let p = x; - // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type = READ# + // CHECK-DAG: ([[#@LINE+2]]: q): {{.*}}type = READ# + // CHECK-DAG: ([[#@LINE+1]]: q): {{.*}}type flags = CELL# let q = x; *p = *q; } From c455f36b041cc047ba88d22cf37096004821252e Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 14 Jun 2022 15:23:03 -0700 Subject: [PATCH 23/38] analyze: compute new types based on pointer permissions --- c2rust-analyze/src/labeled_ty.rs | 84 +++++++++++++++++++- c2rust-analyze/src/main.rs | 12 +++ c2rust-analyze/src/type_desc.rs | 127 +++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 c2rust-analyze/src/type_desc.rs diff --git a/c2rust-analyze/src/labeled_ty.rs b/c2rust-analyze/src/labeled_ty.rs index 54ff3c5f73..9b70ead9d0 100644 --- a/c2rust-analyze/src/labeled_ty.rs +++ b/c2rust-analyze/src/labeled_ty.rs @@ -2,12 +2,14 @@ //! use this, for example, to attach a Polonius `Origin` to every reference type. Labeled type //! data is manipulated by reference, the same as with `Ty`s, and the data is stored in the same //! arena as the underlying `Ty`s. -use rustc_arena::DroplessArena; -use rustc_middle::arena::Arena; -use rustc_middle::ty::{TyCtxt, Ty, TyKind}; +use std::convert::TryInto; use std::fmt; use std::marker::PhantomData; use std::ops::Deref; +use rustc_arena::DroplessArena; +use rustc_middle::arena::Arena; +use rustc_middle::ty::{TyCtxt, Ty, TyKind, TypeAndMut}; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; /// The actual data for a labeled type. /// @@ -248,6 +250,82 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { .collect::>(); self.mk_slice(<ys) } + + /// Perform a bottom-up rewrite on a type and convert it to unlabeled form. + pub fn rewrite_unlabeled(&self, lty: LabeledTy<'tcx, L>, func: &mut F) -> Ty<'tcx> + where F: FnMut(Ty<'tcx>, &[Ty<'tcx>], L) -> Ty<'tcx> { + use rustc_middle::ty::TyKind::*; + let args = lty.args.iter() + .map(|<y| self.rewrite_unlabeled(lty, func)) + .collect::>(); + + let ty = match *lty.ty.kind() { + Bool | Char | Int(_) | Uint(_) | Float(_) | Str | Foreign(_) | Never => { + lty.ty + }, + + Adt(adt, substs) => { + // Copy `substs`, but replace all types with those from `args`. + let mut it = args.iter().cloned(); + let substs = self.tcx.mk_substs(substs.iter().map(|arg| match arg.unpack() { + GenericArgKind::Type(_) => it.next().unwrap().into(), + GenericArgKind::Lifetime(rg) => GenericArg::from(rg), + GenericArgKind::Const(cn) => GenericArg::from(cn), + })); + assert!(it.next().is_none()); + self.tcx.mk_adt(adt, substs) + }, + Array(_, len) => { + let &[elem]: &[_; 1] = args[..].try_into().unwrap(); + self.tcx.mk_ty(Array(elem, len)) + } + Slice(_) => { + let &[elem]: &[_; 1] = args[..].try_into().unwrap(); + self.tcx.mk_slice(elem) + } + RawPtr(mty) => { + let &[target]: &[_; 1] = args[..].try_into().unwrap(); + self.tcx.mk_ptr(TypeAndMut { ty: target, mutbl: mty.mutbl }) + } + Ref(rg, _, mutbl) => { + let &[target]: &[_; 1] = args[..].try_into().unwrap(); + self.tcx.mk_ref(rg, TypeAndMut { ty: target, mutbl }) + } + FnDef(def_id, substs) => { + // Copy `substs`, but replace all types with those from `args`. + let mut it = args.iter().cloned(); + let substs = self.tcx.mk_substs(substs.iter().map(|arg| match arg.unpack() { + GenericArgKind::Type(_) => it.next().unwrap().into(), + GenericArgKind::Lifetime(rg) => GenericArg::from(rg), + GenericArgKind::Const(cn) => GenericArg::from(cn), + })); + assert!(it.next().is_none()); + self.tcx.mk_fn_def(def_id, substs) + } + FnPtr(ref sig) => { + // TODO: replace all the types under the binder + todo!() + } + Tuple(_) => { + self.tcx.mk_tup(args.iter().cloned()) + } + + // Types that aren't actually supported by this code yet + Dynamic(..) + | Closure(..) + | Generator(..) + | GeneratorWitness(..) + | Projection(..) + | Opaque(..) + | Param(..) + | Bound(..) + | Placeholder(..) + | Infer(..) + | Error(..) => lty.ty, + }; + + func(ty, &args, lty.label) + } } impl<'tcx, L> Deref for LabeledTyCtxt<'tcx, L> { diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 01a4c64d91..5f2d12819b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -46,6 +46,7 @@ mod borrowck; mod context; mod dataflow; mod labeled_ty; +mod type_desc; mod util; @@ -121,6 +122,17 @@ fn inspect_mir<'tcx>( ty2, ); } + + eprintln!("\ntype assignment for {:?}:", name); + for (local, decl) in mir.local_decls.iter_enumerated() { + // TODO: apply `Cell` if `addr_of_local` indicates it's needed + let ty = type_desc::convert_type(&acx, acx.local_tys[local], &hypothesis, &flags); + eprintln!("{:?} ({}): {:?}", + local, + describe_local(acx.tcx, decl), + ty, + ); + } } fn assign_pointer_ids<'tcx>( diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs new file mode 100644 index 0000000000..eec80b8697 --- /dev/null +++ b/c2rust-analyze/src/type_desc.rs @@ -0,0 +1,127 @@ +use rustc_hir::def::{DefKind, Res}; +use rustc_middle::mir::Mutability; +use rustc_middle::ty::{Ty, TyCtxt, ReErased}; +use rustc_middle::ty::subst::GenericArg; +use crate::context::{PermissionSet, FlagSet, AnalysisCtxt, LTy, PointerId}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum Ownership { + /// E.g. `*const T` + Raw, + /// E.g. `*mut T` + RawMut, + /// E.g. `&T` + Imm, + /// E.g. `&Cell` + Cell, + /// E.g. `&mut T` + Mut, + /// E.g. `Rc` + Rc, + /// E.g. `Box` + Box, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] +pub enum Quantity { + /// E.g. `&T` + Single, + /// E.g. `&[T]` + Slice, + /// E.g. `OffsetPtr` + OffsetPtr, +} + +fn perms_to_desc( + perms: PermissionSet, + flags: FlagSet, +) -> (Ownership, Quantity) { + let own = if perms.contains(PermissionSet::UNIQUE | PermissionSet::WRITE) { + Ownership::Mut + } else if flags.contains(FlagSet::CELL) { + Ownership::Cell + } else { + // Anything with WRITE and not UNIQUE should have CELL set, and use the previous case. + assert!(!perms.contains(PermissionSet::WRITE)); + Ownership::Imm + }; + + let qty = if perms.contains(PermissionSet::OFFSET_SUB) { + // TODO: should be Quantity::OffsetPtr, but that's not implemented yet + Quantity::Slice + } else if perms.contains(PermissionSet::OFFSET_ADD) { + Quantity::Slice + } else { + Quantity::Single + }; + + (own, qty) +} + +fn mk_cell<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + let core_crate = tcx.crates(()).iter().cloned() + .find(|&krate| tcx.crate_name(krate).as_str() == "core") + .expect("failed to find crate `core`"); + + let cell_mod_child = tcx.module_children(core_crate.as_def_id()).iter() + .find(|child| child.ident.as_str() == "cell") + .expect("failed to find module `core::cell`"); + let cell_mod = match cell_mod_child.res { + Res::Def(DefKind::Mod, did) => did, + ref r => panic!("unexpected resolution {:?} for `core::cell`", r), + }; + + let cell_struct_child = tcx.module_children(cell_mod).iter() + .find(|child| child.ident.as_str() == "Cell") + .expect("failed to find struct `core::cell::Cell`"); + let cell_struct = match cell_struct_child.res { + Res::Def(DefKind::Struct, did) => did, + ref r => panic!("unexpected resolution {:?} for `core::cell::Cell`", r), + }; + + let cell_adt = tcx.adt_def(cell_struct); + let substs = tcx.mk_substs([GenericArg::from(ty)].into_iter()); + tcx.mk_adt(cell_adt, substs) +} + +pub fn convert_type<'tcx>( + acx: &AnalysisCtxt<'tcx>, + lty: LTy<'tcx>, + perms: &[PermissionSet], + flags: &[FlagSet], +) -> Ty<'tcx> { + let tcx = acx.tcx; + acx.lcx.rewrite_unlabeled(lty, &mut |ty, args, label| { + if label == PointerId::NONE { + return ty; + } + let ptr = label; + + let (own, qty) = perms_to_desc(perms[ptr.index()], flags[ptr.index()]); + + assert_eq!(args.len(), 1); + let mut ty = args[0]; + + if own == Ownership::Cell { + ty = mk_cell(tcx, ty); + } + + ty = match qty { + Quantity::Single => ty, + Quantity::Slice => tcx.mk_slice(ty), + Quantity::OffsetPtr => todo!(), + }; + + ty = match own { + Ownership::Raw => tcx.mk_imm_ptr(ty), + Ownership::RawMut => tcx.mk_mut_ptr(ty), + Ownership::Imm => tcx.mk_imm_ref(tcx.mk_region(ReErased), ty), + Ownership::Cell => tcx.mk_imm_ref(tcx.mk_region(ReErased), ty), + Ownership::Mut => tcx.mk_mut_ref(tcx.mk_region(ReErased), ty), + Ownership::Rc => todo!(), + Ownership::Box => todo!(), + }; + + ty + }) +} From 56e9b86104a4a7fd719777e690b0e2ed1b3636a6 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 30 Jun 2022 14:11:17 -0700 Subject: [PATCH 24/38] analyze: handle Deref and Field projections in Context::type_of --- c2rust-analyze/src/context.rs | 16 +++++++++++++--- c2rust-analyze/src/labeled_ty.rs | 6 ++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 7fb5868ff0..19aac2e151 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -2,7 +2,7 @@ use std::cell::Cell; use bitflags::bitflags; use rustc_index::vec::IndexVec; use rustc_middle::mir::{Local, Place, PlaceRef, ProjectionElem}; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{TyCtxt, TyKind}; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; @@ -138,8 +138,18 @@ impl<'tcx> TypeOf<'tcx> for PlaceRef<'tcx> { let mut ty = acx.type_of(self.local); for proj in self.projection { match *proj { - ProjectionElem::Deref => todo!("type_of Deref"), - ProjectionElem::Field(..) => todo!("type_of Field"), + ProjectionElem::Deref => { + assert!(matches!(ty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..))); + assert_eq!(ty.args.len(), 1); + ty = ty.args[0]; + }, + ProjectionElem::Field(f, _) => match ty.kind() { + TyKind::Tuple(_) => { + ty = ty.args[f.index()]; + }, + TyKind::Adt(..) => todo!("type_of Field(Adt)"), + _ => panic!("Field projection is unsupported on type {:?}", ty), + } ProjectionElem::Index(..) | ProjectionElem::ConstantIndex { .. } => todo!("type_of Index"), ProjectionElem::Subslice { .. } => todo!("type_of Subslice"), diff --git a/c2rust-analyze/src/labeled_ty.rs b/c2rust-analyze/src/labeled_ty.rs index 9b70ead9d0..7088d16a10 100644 --- a/c2rust-analyze/src/labeled_ty.rs +++ b/c2rust-analyze/src/labeled_ty.rs @@ -32,6 +32,12 @@ pub struct LabeledTyS<'tcx, L: 'tcx> { pub label: L, } +impl<'tcx, L> LabeledTyS<'tcx, L> { + pub fn kind(&self) -> &'tcx TyKind<'tcx> { + self.ty.kind() + } +} + /// A labeled type. Like `rustc::ty::Ty`, this is a reference to some arena-allocated data. pub type LabeledTy<'tcx, L> = &'tcx LabeledTyS<'tcx, L>; From 2def6de4967498f074fc7901b03cfb674bdb92bf Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 30 Jun 2022 14:15:09 -0700 Subject: [PATCH 25/38] analyze: generate and print expr rewrites --- c2rust-analyze/src/expr_rewrite.rs | 389 +++++++++++++++++++++++++++++ c2rust-analyze/src/main.rs | 18 +- c2rust-analyze/src/type_desc.rs | 2 +- 3 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 c2rust-analyze/src/expr_rewrite.rs diff --git a/c2rust-analyze/src/expr_rewrite.rs b/c2rust-analyze/src/expr_rewrite.rs new file mode 100644 index 0000000000..57af1ba1d3 --- /dev/null +++ b/c2rust-analyze/src/expr_rewrite.rs @@ -0,0 +1,389 @@ +use rustc_middle::mir::{ + Body, BasicBlock, Location, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, + Operand, Place, +}; +use rustc_span::{Span, DUMMY_SP}; +use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; +use crate::type_desc::{self, Ownership, Quantity}; +use crate::util::{self, Callee}; + + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ExprLoc { + pub stmt: Location, + pub span: Span, + pub sub: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum SubLoc { + /// The LHS of an assignment or call. `StatementKind::Assign/TerminatorKind::Call -> Place` + Dest, + /// The RHS of an assignment. `StatementKind::Assign -> Rvalue` + AssignRvalue, + /// The Nth argument of a call. `TerminatorKind::Call -> Operand` + CallArg(usize), + /// The Nth operand of an rvalue. `Rvalue -> Operand` + RvalueOperand(usize), + /// The place referenced by an operand. `Operand::Move/Operand::Copy -> Place` + OperandPlace, + /// The pointer used in the Nth innermost deref within a place. `Place -> Place` + PlacePointer(usize), +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum RewriteKind { + /// Replace `ptr.offset(i)` with something like `&ptr[i..]`. + OffsetSlice { mutbl: bool }, + /// Replace `slice` with `&slice[0]`. + SliceFirst { mutbl: bool }, + /// Replace `ptr` with `&*ptr`, converting `&mut T` to `&T`. + MutToImm, +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ExprRewrite { + pub loc: ExprLoc, + pub kinds: Vec, +} + + +struct ExprRewriteVisitor<'a, 'tcx> { + acx: &'a AnalysisCtxt<'tcx>, + perms: &'a [PermissionSet], + flags: &'a [FlagSet], + rewrites: &'a mut Vec, + mir: &'a Body<'tcx>, + loc: ExprLoc, +} + +impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { + pub fn new( + acx: &'a AnalysisCtxt<'tcx>, + perms: &'a [PermissionSet], + flags: &'a [FlagSet], + rewrites: &'a mut Vec, + mir: &'a Body<'tcx>, + ) -> ExprRewriteVisitor<'a, 'tcx> { + ExprRewriteVisitor { + acx, perms, flags, rewrites, mir, + loc: ExprLoc { + stmt: Location { + block: BasicBlock::from_usize(0), + statement_index: 0, + }, + span: DUMMY_SP, + sub: Vec::new(), + }, + } + } + + fn enter R, R>(&mut self, sub: SubLoc, f: F) -> R { + self.loc.sub.push(sub); + let r = f(self); + self.loc.sub.pop(); + r + } + + fn enter_dest R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::Dest, f) + } + + fn enter_assign_rvalue R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::AssignRvalue, f) + } + + fn enter_call_arg R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::CallArg(i), f) + } + + fn enter_rvalue_operand R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::RvalueOperand(i), f) + } + + fn enter_operand_place R, R>(&mut self, f: F) -> R { + self.enter(SubLoc::OperandPlace, f) + } + + fn enter_place_pointer R, R>(&mut self, i: usize, f: F) -> R { + self.enter(SubLoc::PlacePointer(i), f) + } + + + fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { + self.loc = ExprLoc { + stmt: loc, + span: stmt.source_info.span, + sub: Vec::new(), + }; + + match stmt.kind { + StatementKind::Assign(ref x) => { + let (pl, ref rv) = **x; + let pl_ty = self.acx.type_of(pl); + self.enter_assign_rvalue(|v| v.visit_rvalue(rv, pl_ty)); + // TODO: visit place + }, + StatementKind::FakeRead(..) => {}, + StatementKind::SetDiscriminant { .. } => todo!("statement {:?}", stmt), + StatementKind::StorageLive(..) => {}, + StatementKind::StorageDead(..) => {}, + StatementKind::Retag(..) => {}, + StatementKind::AscribeUserType(..) => {}, + StatementKind::Coverage(..) => {}, + StatementKind::CopyNonOverlapping(..) => todo!("statement {:?}", stmt), + StatementKind::Nop => {}, + } + } + + fn visit_terminator(&mut self, term: &Terminator<'tcx>, loc: Location) { + let tcx = self.acx.tcx; + self.loc = ExprLoc { + stmt: loc, + span: term.source_info.span, + sub: Vec::new(), + }; + + match term.kind { + TerminatorKind::Goto { .. } => {}, + TerminatorKind::SwitchInt { .. } => {}, + TerminatorKind::Resume => {}, + TerminatorKind::Abort => {}, + TerminatorKind::Return => {}, + TerminatorKind::Unreachable => {}, + TerminatorKind::Drop { .. } => {}, + TerminatorKind::DropAndReplace { .. } => {}, + TerminatorKind::Call { ref func, ref args, destination, .. } => { + let func_ty = func.ty(self.mir, tcx); + let pl_ty = destination.map(|(pl, _)| self.acx.type_of(pl)); + + if let Some(callee) = util::ty_callee(tcx, func_ty) { + // Special cases for particular functions. + let pl_ty = pl_ty.unwrap(); + match callee { + Callee::PtrOffset { .. } => { + // TODO + // - get own,qty for output (pl_ty) + // - increase qty to Quantity::Slice for own2,qty2 + // - cast argument to own2,qty2 + // - emit OffsetSlice + // - if output is not Slice, emit SliceFirst + self.visit_ptr_offset(&args[0], pl_ty); + + + return; + }, + } + } + + // General case: cast `args` to match the signature of `func`. + let poly_sig = func_ty.fn_sig(tcx); + let sig = tcx.erase_late_bound_regions(poly_sig); + + for (i, op) in args.iter().enumerate() { + if i >= sig.inputs().len() { + // This is a call to a variadic function, and we've gone past the end of + // the declared arguments. + // TODO: insert a cast to turn `op` back into its original declared type + // (i.e. upcast the chosen reference type back to a raw pointer) + continue; + } + + // TODO: get the `LTy` to use for the callee's argument + // let expect_ty = ...; + // self.enter_call_arg(i, |v| v.visit_operand(op, expect_ty)); + } + }, + TerminatorKind::Assert { .. } => {}, + TerminatorKind::Yield { .. } => {}, + TerminatorKind::GeneratorDrop => {}, + TerminatorKind::FalseEdge { .. } => {}, + TerminatorKind::FalseUnwind { .. } => {}, + TerminatorKind::InlineAsm { .. } => todo!("terminator {:?}", term), + } + } + + fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>, expect_ty: LTy<'tcx>) { + // TODO: most of these cases need to recurse into operands/places to find derefs + match *rv { + Rvalue::Use(ref op) => { + self.enter_rvalue_operand(0, |v| v.visit_operand(op, expect_ty)); + }, + Rvalue::Repeat(ref op, _) => { + // TODO + }, + Rvalue::Ref(rg, kind, pl) => { + // TODO + }, + Rvalue::ThreadLocalRef(def_id) => { + // TODO + }, + Rvalue::AddressOf(mutbl, pl) => { + // TODO + }, + Rvalue::Len(pl) => { + // TODO + }, + Rvalue::Cast(kind, ref op, ty) => { + // TODO + }, + Rvalue::BinaryOp(bop, ref ops) => { + // TODO + }, + Rvalue::CheckedBinaryOp(bop, ref ops) => { + // TODO + }, + Rvalue::NullaryOp(..) => {}, + Rvalue::UnaryOp(uop, ref op) => { + // TODO + }, + Rvalue::Discriminant(pl) => { + // TODO + }, + Rvalue::Aggregate(ref kind, ref ops) => { + // TODO + }, + Rvalue::ShallowInitBox(ref op, ty) => { + // TODO + }, + } + } + + fn visit_operand(&mut self, op: &Operand<'tcx>, expect_ty: LTy<'tcx>) { + match *op { + Operand::Copy(pl) | + Operand::Move(pl) => { + if let Some(ptr) = self.acx.ptr_of(pl) { + let expect_ptr = expect_ty.label; + self.emit_ptr_cast(ptr, expect_ptr); + } + + // TODO: walk over `pl` to handle all derefs (casts, `*x` -> `(*x).get()`) + }, + Operand::Constant(..) => {}, + } + } + + fn visit_operand_desc( + &mut self, + op: &Operand<'tcx>, + expect_own: Ownership, + expect_qty: Quantity, + ) { + match *op { + Operand::Copy(pl) | + Operand::Move(pl) => { + if let Some(ptr) = self.acx.ptr_of(pl) { + self.emit_cast(ptr, expect_own, expect_qty); + } + + // TODO: walk over `pl` to handle all derefs (casts, `*x` -> `(*x).get()`) + }, + Operand::Constant(..) => {}, + } + } + + fn visit_ptr_offset(&mut self, op: &Operand<'tcx>, result_ty: LTy<'tcx>) { + // Compute the expected type for the argument, and emit a cast if needed. + let result_ptr = result_ty.label; + let (result_own, result_qty) = type_desc::perms_to_desc( + self.perms[result_ptr.index()], self.flags[result_ptr.index()]); + + let arg_expect_own = result_own; + // TODO: infer `arg_expect_qty` based on the type of offset this is (positive / unknown) + let arg_expect_qty = match result_qty { + Quantity::Single => Quantity::Slice, + Quantity::Slice => Quantity::Slice, + Quantity::OffsetPtr => todo!("OffsetPtr"), + }; + + self.enter_call_arg(0, |v| v.visit_operand_desc(op, result_own, arg_expect_qty)); + + // Emit `OffsetSlice` for the offset itself. + let mutbl = match result_own { + Ownership::Mut => true, + _ => false, + }; + + self.emit(RewriteKind::OffsetSlice { mutbl }); + + // If the result is `Single`, also insert an upcast. + if result_qty == Quantity::Single { + self.emit(RewriteKind::SliceFirst { mutbl }); + } + } + + + fn emit(&mut self, rw: RewriteKind) { + if let Some(er) = self.rewrites.last_mut() { + if er.loc == self.loc { + er.kinds.push(rw); + return; + } + } + + self.rewrites.push(ExprRewrite { + loc: self.loc.clone(), + kinds: vec![rw], + }); + } + + fn emit_ptr_cast(&mut self, ptr: PointerId, expect_ptr: PointerId) { + assert!(expect_ptr != PointerId::NONE); + + let (own2, qty2) = type_desc::perms_to_desc( + self.perms[expect_ptr.index()], self.flags[expect_ptr.index()]); + + self.emit_cast(ptr, own2, qty2); + } + + fn emit_cast(&mut self, ptr: PointerId, expect_own: Ownership, expect_qty: Quantity) { + assert!(ptr != PointerId::NONE); + + let (own1, qty1) = type_desc::perms_to_desc( + self.perms[ptr.index()], self.flags[ptr.index()]); + let (own2, qty2) = (expect_own, expect_qty); + + if (own1, qty1) == (own2, qty2) { + return; + } + + if qty1 == qty2 && (own1, own2) == (Ownership::Mut, Ownership::Imm) { + self.emit(RewriteKind::MutToImm); + return; + } + + eprintln!("unsupported cast kind: {:?} {:?} -> {:?}", + self.perms[ptr.index()], (own1, qty1), (own2, qty2)); + } +} + + +pub fn gen_expr_rewrites<'tcx>( + acx: &AnalysisCtxt<'tcx>, + perms: &[PermissionSet], + flags: &[FlagSet], + mir: &Body<'tcx>, +) -> Vec { + // - walk over statements/terminators + // - Assign: find RHS operands that need casting to match LHS + // - Call: special case for `ptr.offset(i)` + + let mut out = Vec::new(); + + let mut v = ExprRewriteVisitor::new(acx, perms, flags, &mut out, mir); + + for (bb_id, bb) in mir.basic_blocks().iter_enumerated() { + for (i, stmt) in bb.statements.iter().enumerate() { + let loc = Location { block: bb_id, statement_index: i }; + v.visit_statement(stmt, loc); + } + + if let Some(ref term) = bb.terminator { + let loc = Location { block: bb_id, statement_index: bb.statements.len() }; + v.visit_terminator(term, loc); + } + } + + out +} diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index 5f2d12819b..b74ff42338 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -35,7 +35,7 @@ use rustc_middle::mir::pretty; use rustc_middle::ty::{TyCtxt, Ty, TyKind, RegionKind, WithOptConstParam, List}; use rustc_middle::ty::query::{Providers, ExternProviders}; use rustc_session::Session; -use rustc_span::DUMMY_SP; +use rustc_span::{Span, DUMMY_SP}; use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; use rustc_span::symbol::Ident; use rustc_target::abi::Align; @@ -45,6 +45,7 @@ use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; mod borrowck; mod context; mod dataflow; +mod expr_rewrite; mod labeled_ty; mod type_desc; mod util; @@ -133,6 +134,18 @@ fn inspect_mir<'tcx>( ty, ); } + + eprintln!(""); + let rewrites = expr_rewrite::gen_expr_rewrites(&acx, &hypothesis, &flags, mir); + for rw in &rewrites { + eprintln!( + "at {:?} ({}, {:?}):", + rw.loc.stmt, describe_span(tcx, rw.loc.span), rw.loc.sub, + ); + for kind in &rw.kinds { + eprintln!(" {:?}", kind); + } + } } fn assign_pointer_ids<'tcx>( @@ -156,7 +169,10 @@ fn describe_local(tcx: TyCtxt, decl: &LocalDecl) -> String { } } } + describe_span(tcx, span) +} +fn describe_span(tcx: TyCtxt, span: Span) -> String { let s = tcx.sess.source_map().span_to_snippet(span).unwrap(); let s = { let mut s2 = String::new(); diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs index eec80b8697..533c1fb34b 100644 --- a/c2rust-analyze/src/type_desc.rs +++ b/c2rust-analyze/src/type_desc.rs @@ -32,7 +32,7 @@ pub enum Quantity { OffsetPtr, } -fn perms_to_desc( +pub fn perms_to_desc( perms: PermissionSet, flags: FlagSet, ) -> (Ownership, Quantity) { From f212c9adf86328876e0a0c5f13dabaee6c548f0b Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 12 Jul 2022 09:28:15 -0700 Subject: [PATCH 26/38] analyze: fix filecheck test runner failing to find c2rust-analyze binary --- c2rust-analyze/tests/filecheck.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index 7ccf7af0e0..e29c3f72f8 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -9,7 +9,6 @@ use std::str; fn filecheck() { let lib_dir = env::var("C2RUST_TARGET_LIB_DIR").unwrap(); let lib_dir = &lib_dir; - eprintln!("{:?}", lib_dir); let filecheck_bin = env::var("FILECHECK").unwrap_or_else(|_| "FileCheck".into()); @@ -28,19 +27,24 @@ fn filecheck() { eprintln!("{:?}", entry.path()); - let mut filecheck = Command::new(&filecheck_bin) + let mut filecheck_cmd = Command::new(&filecheck_bin); + filecheck_cmd .arg(entry.path()) - .stdin(Stdio::piped()) - .spawn().unwrap(); + .stdin(Stdio::piped()); + let mut filecheck = filecheck_cmd.spawn().unwrap(); let pipe_fd = filecheck.stdin.as_ref().unwrap().as_raw_fd(); - let mut analyze = Command::new(env!("CARGO_BIN_EXE_c2rust-analyze")) + let mut analyze_cmd = Command::new("cargo"); + analyze_cmd + .arg("run") + .arg("--manifest-path").arg(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))) + .arg("--") .arg(entry.path()) .arg("-L").arg(lib_dir) .arg("--crate-type").arg("rlib") .arg("--cfg").arg("compiling_for_test") .stdout(unsafe { Stdio::from_raw_fd(pipe_fd) }) - .stderr(unsafe { Stdio::from_raw_fd(pipe_fd) }) - .spawn().unwrap(); + .stderr(unsafe { Stdio::from_raw_fd(pipe_fd) }); + let mut analyze = analyze_cmd.spawn().unwrap(); let filecheck_status = filecheck.wait().unwrap(); assert!( From e9c7f65ae1b579b2b2e1af51100b3b14494d6181 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 12 Jul 2022 11:52:49 -0700 Subject: [PATCH 27/38] analyze: use shared rust-toolchain --- c2rust-analyze/rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 120000 c2rust-analyze/rust-toolchain diff --git a/c2rust-analyze/rust-toolchain b/c2rust-analyze/rust-toolchain deleted file mode 100644 index f24eb00eda..0000000000 --- a/c2rust-analyze/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2022-02-14 diff --git a/c2rust-analyze/rust-toolchain b/c2rust-analyze/rust-toolchain new file mode 120000 index 0000000000..9327ba4034 --- /dev/null +++ b/c2rust-analyze/rust-toolchain @@ -0,0 +1 @@ +../rust-toolchain \ No newline at end of file From dbe3e7be5b99a82763ed4ded5638f10c1f58ec97 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 18 Jul 2022 09:35:29 -0700 Subject: [PATCH 28/38] analyze: remove /target/ from .gitignore target/ is already covered by a rule from the parent .gitignore. --- c2rust-analyze/.gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/c2rust-analyze/.gitignore b/c2rust-analyze/.gitignore index a61adfa020..0881da6cac 100644 --- a/c2rust-analyze/.gitignore +++ b/c2rust-analyze/.gitignore @@ -1,2 +1 @@ -/target/ /inspect/ From 72cbf53525434a8c237c4760a7b7327e4353ccda Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 18 Jul 2022 10:27:36 -0700 Subject: [PATCH 29/38] analyze: add c2rust-analyze to workspace --- Cargo.lock | 26 ++++++++++++++++++++++++++ Cargo.toml | 1 + c2rust-analyze/Cargo.toml | 1 - 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c723495711..c3d386aef9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,15 @@ dependencies = [ "serde", ] +[[package]] +name = "c2rust-analyze" +version = "0.1.0" +dependencies = [ + "bitflags", + "polonius-engine", + "rustc-hash", +] + [[package]] name = "c2rust-asm-casts" version = "0.2.0" @@ -705,6 +714,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "datafrog" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0afaad2b26fa326569eb264b1363e8ae3357618c43982b3f285f0774ce76b69" + [[package]] name = "digest" version = "0.8.1" @@ -1513,6 +1528,17 @@ version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +[[package]] +name = "polonius-engine" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4e8e505342045d397d0b6674dcb82d6faf5cf40484d30eeb88fc82ef14e903f" +dependencies = [ + "datafrog", + "log", + "rustc-hash", +] + [[package]] name = "prettyplease" version = "0.1.11" diff --git a/Cargo.toml b/Cargo.toml index bfaa5a58cf..55f79663fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "c2rust", + "c2rust-analyze", "c2rust-transpile", "c2rust-ast-builder", "c2rust-ast-exporter", diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index 2c38f194d5..d2505d04e3 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -10,4 +10,3 @@ polonius-engine = "0.13.0" rustc-hash = "1.1.0" bitflags = "1.3.2" -[workspace] From 6356224ca684089bb2df57e63580351e78b1e370 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 18 Jul 2022 10:27:45 -0700 Subject: [PATCH 30/38] analyze: add rust-analyzer section to Cargo.toml --- c2rust-analyze/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index d2505d04e3..33b122b148 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -10,3 +10,5 @@ polonius-engine = "0.13.0" rustc-hash = "1.1.0" bitflags = "1.3.2" +[package.metadata.rust-analyzer] +rustc_private = true From 722c0c0c4c39b8dc611bebf29613d9afecbfdf51 Mon Sep 17 00:00:00 2001 From: Khyber Sen Date: Mon, 18 Jul 2022 12:02:45 -0700 Subject: [PATCH 31/38] Add `*.rlib` to the `c2rust-analyze` `.gitignore` as it's produced by `cargo test`. --- c2rust-analyze/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/c2rust-analyze/.gitignore b/c2rust-analyze/.gitignore index 0881da6cac..c0dc9bcc98 100644 --- a/c2rust-analyze/.gitignore +++ b/c2rust-analyze/.gitignore @@ -1 +1,2 @@ /inspect/ +*.rlib From 3dcade1b52c83a31edb153b675a7afb2d33053c5 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Mon, 18 Jul 2022 17:05:43 -0700 Subject: [PATCH 32/38] analyze: fix warnings --- c2rust-analyze/src/borrowck/atoms.rs | 9 +++-- c2rust-analyze/src/borrowck/def_use.rs | 2 +- c2rust-analyze/src/borrowck/dump.rs | 12 +++--- c2rust-analyze/src/borrowck/mod.rs | 37 ++++-------------- c2rust-analyze/src/borrowck/type_check.rs | 12 +++--- c2rust-analyze/src/dataflow/mod.rs | 21 +++++----- c2rust-analyze/src/dataflow/type_check.rs | 8 ++-- c2rust-analyze/src/expr_rewrite.rs | 47 ++++++++++------------- c2rust-analyze/src/labeled_ty.rs | 7 ++-- c2rust-analyze/src/main.rs | 32 ++++----------- c2rust-analyze/src/type_desc.rs | 3 +- c2rust-analyze/src/util.rs | 4 +- 12 files changed, 76 insertions(+), 118 deletions(-) diff --git a/c2rust-analyze/src/borrowck/atoms.rs b/c2rust-analyze/src/borrowck/atoms.rs index f676705a69..04d53d31e0 100644 --- a/c2rust-analyze/src/borrowck/atoms.rs +++ b/c2rust-analyze/src/borrowck/atoms.rs @@ -65,6 +65,7 @@ impl Default for AtomMap { } impl AtomMap { + #[allow(dead_code)] pub fn new() -> AtomMap { AtomMap { atom_to_thing: Vec::new(), @@ -155,7 +156,7 @@ impl<'tcx> AtomMaps<'tcx> { Variable(l.as_usize()) } - pub fn get_variable(&self, x: Variable) -> Local { + pub fn _get_variable(&self, x: Variable) -> Local { Local::from_usize(x.0) } @@ -183,14 +184,14 @@ impl<'tcx> AtomMaps<'tcx> { path } - pub fn get_path(&self, tcx: TyCtxt<'tcx>, x: Path) -> Place<'tcx> { + pub fn _get_path(&self, tcx: TyCtxt<'tcx>, x: Path) -> Place<'tcx> { let (local, projection) = self.path.get(x); let projection = tcx.intern_place_elems(projection); Place { local, projection } } - pub fn get_path_projection(&self, tcx: TyCtxt<'tcx>, x: Path) -> &'tcx [PlaceElem<'tcx>] { - let (local, projection) = self.path.get(x); + pub fn get_path_projection(&self, _tcx: TyCtxt<'tcx>, x: Path) -> &'tcx [PlaceElem<'tcx>] { + let (_local, projection) = self.path.get(x); projection } } diff --git a/c2rust-analyze/src/borrowck/def_use.rs b/c2rust-analyze/src/borrowck/def_use.rs index 6c9ea21982..bfd4678bd2 100644 --- a/c2rust-analyze/src/borrowck/def_use.rs +++ b/c2rust-analyze/src/borrowck/def_use.rs @@ -233,7 +233,7 @@ impl<'tcx> Visitor<'tcx> for LoanInvalidatedAtVisitor<'tcx, '_> { local, context, categorize(context), location); let local_loans = self.loans.get(&local).map_or(&[] as &[_], |x| x); - for &(path, loan, borrow_kind) in local_loans { + for &(_path, loan, borrow_kind) in local_loans { // All paths rooted in this local overlap the local. self.access_loan_at_location(loan, borrow_kind, context, location); } diff --git a/c2rust-analyze/src/borrowck/dump.rs b/c2rust-analyze/src/borrowck/dump.rs index 4df4e01c54..b552f99160 100644 --- a/c2rust-analyze/src/borrowck/dump.rs +++ b/c2rust-analyze/src/borrowck/dump.rs @@ -2,7 +2,7 @@ /// Apache 2.0. use std::collections::{BTreeMap, BTreeSet}; use std::error::Error; -use std::fmt::{Debug, Write as _}; +use std::fmt::Write as _; use std::fs::{self, File}; use std::hash::Hash; use std::io::{BufWriter, Write}; @@ -232,7 +232,7 @@ impl OutputTable for bool { fn write( &self, out: &mut dyn Write, - maps: &AtomMaps, + _maps: &AtomMaps, ) -> Result<(), Box> { writeln!(out, "{}", self)?; Ok(()) @@ -348,13 +348,13 @@ impl Render for (A, B) { } impl Render for Origin { - fn to_string(&self, maps: &AtomMaps) -> String { + fn to_string(&self, _maps: &AtomMaps) -> String { format!("'_#{}r", usize::from(*self)) } } impl Render for Loan { - fn to_string(&self, maps: &AtomMaps) -> String { + fn to_string(&self, _maps: &AtomMaps) -> String { format!("bw{}", usize::from(*self)) } } @@ -367,13 +367,13 @@ impl Render for Point { } impl Render for Variable { - fn to_string(&self, maps: &AtomMaps) -> String { + fn to_string(&self, _maps: &AtomMaps) -> String { format!("_{}", usize::from(*self)) } } impl Render for Path { - fn to_string(&self, maps: &AtomMaps) -> String { + fn to_string(&self, _maps: &AtomMaps) -> String { format!("mp{}", usize::from(*self)) } } diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index b31ebd1c7d..90ed90bfbe 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -1,30 +1,9 @@ use std::collections::HashMap; -use std::env; use std::hash::Hash; -use std::mem; -use either::{Either, Left, Right}; -use polonius_engine::{self, Atom, FactTypes}; -use rustc_ast::ast::{Item, ItemKind, Visibility, VisibilityKind}; -use rustc_ast::node_id::NodeId; -use rustc_ast::ptr::P; -use rustc_driver::Compilation; -use rustc_interface::Queries; -use rustc_interface::interface::Compiler; -use rustc_middle::mir::{ - Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, LocalKind, Mutability, Rvalue, AggregateKind, Place, - PlaceRef, PlaceElem, Operand, Statement, StatementKind, BorrowKind, Constant, ConstantKind, -}; -use rustc_middle::mir::interpret::{Allocation, ConstValue}; -use rustc_middle::mir::pretty; -use rustc_middle::ty::{TyCtxt, Ty, TyKind, RegionKind, WithOptConstParam, List}; -use rustc_middle::ty::query::{Providers, ExternProviders}; -use rustc_session::Session; -use rustc_span::DUMMY_SP; -use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; -use rustc_span::symbol::Ident; -use rustc_target::abi::Align; -use crate::context::{AnalysisCtxt, PermissionSet, PointerId}; +use polonius_engine; +use rustc_middle::mir::{Body, START_BLOCK, Local, LocalKind, Place, StatementKind, BorrowKind}; +use rustc_middle::ty::{TyKind, List}; +use crate::context::{AnalysisCtxt, PermissionSet}; use crate::dataflow::DataflowConstraints; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; use crate::util::{describe_rvalue, RvalueDesc}; @@ -38,9 +17,9 @@ mod type_check; #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)] -struct Label { - origin: Option, - perm: PermissionSet, +pub struct Label { + pub origin: Option, + pub perm: PermissionSet, } pub type LTy<'tcx> = LabeledTy<'tcx, Label>; @@ -214,7 +193,7 @@ fn run_polonius<'tcx>( fn assign_origins<'tcx>( ltcx: LTyCtxt<'tcx>, hypothesis: &[PermissionSet], - facts: &mut AllFacts, + _facts: &mut AllFacts, maps: &mut AtomMaps<'tcx>, lty: crate::LTy<'tcx>, ) -> LTy<'tcx> { diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 99a1053e65..69a84be96c 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -7,7 +7,7 @@ use rustc_middle::mir::{ use rustc_middle::ty::{TyCtxt, TyKind}; use crate::borrowck::{LTy, LTyCtxt, Label}; use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; -use crate::context::{PermissionSet, PointerId}; +use crate::context::PermissionSet; use crate::util::{self, Callee}; @@ -143,18 +143,20 @@ impl<'tcx> TypeChecker<'tcx, '_> { Rvalue::BinaryOp(BinOp::Offset, _) | Rvalue::CheckedBinaryOp(BinOp::Offset, _) => todo!("visit_rvalue BinOp::Offset"), - Rvalue::BinaryOp(_, ref ab) | - Rvalue::CheckedBinaryOp(_, ref ab) => { + Rvalue::BinaryOp(_, ref _ab) | + Rvalue::CheckedBinaryOp(_, ref _ab) => { let ty = rv.ty(self.local_decls, *self.ltcx); self.ltcx.label(ty, &mut |ty| { - assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..))); + assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..)), + "pointer BinaryOp NYI"); Label::default() }) }, Rvalue::Cast(_, _, ty) => { self.ltcx.label(ty, &mut |ty| { - assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..))); + assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..)), + "pointer Cast NYI"); Label::default() }) }, diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 3911e27491..73f6f5114a 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -38,7 +38,8 @@ impl DataflowConstraints { self.constraints.push(Constraint::AllPerms(ptr, perms)); } - fn add_no_perms( + #[allow(dead_code)] + fn _add_no_perms( &mut self, ptr: PointerId, perms: PermissionSet, @@ -62,9 +63,9 @@ impl DataflowConstraints { impl PropagateRules for PropagatePerms { fn subset( &mut self, - a_ptr: PointerId, + _a_ptr: PointerId, a_val: &PermissionSet, - b_ptr: PointerId, + _b_ptr: PointerId, b_val: &PermissionSet, ) -> (PermissionSet, PermissionSet) { let old_a = *a_val; @@ -95,7 +96,7 @@ impl DataflowConstraints { fn all_perms( &mut self, - ptr: PointerId, + _ptr: PointerId, perms: PermissionSet, val: &PermissionSet, ) -> PermissionSet { @@ -104,7 +105,7 @@ impl DataflowConstraints { fn no_perms( &mut self, - ptr: PointerId, + _ptr: PointerId, perms: PermissionSet, val: &PermissionSet, ) -> PermissionSet { @@ -193,7 +194,7 @@ impl DataflowConstraints { impl PropagateRules for Rules<'_> { fn subset( &mut self, - a_ptr: PointerId, + _a_ptr: PointerId, a_val: &FlagSet, b_ptr: PointerId, b_val: &FlagSet, @@ -220,8 +221,8 @@ impl DataflowConstraints { fn all_perms( &mut self, - ptr: PointerId, - perms: PermissionSet, + _ptr: PointerId, + _perms: PermissionSet, val: &FlagSet, ) -> FlagSet { *val @@ -229,8 +230,8 @@ impl DataflowConstraints { fn no_perms( &mut self, - ptr: PointerId, - perms: PermissionSet, + _ptr: PointerId, + _perms: PermissionSet, val: &FlagSet, ) -> FlagSet { *val diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 740b0dbc75..03ad1551fa 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; -use rustc_index::vec::IndexVec; use rustc_middle::mir::{ Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, BinOp, Place, PlaceRef, - Operand, BorrowKind, Local, LocalDecl, Location, ProjectionElem, Mutability, + Operand, ProjectionElem, Mutability, }; use rustc_middle::mir::visit::{PlaceContext, NonMutatingUseContext, MutatingUseContext}; use rustc_middle::ty::TyKind; @@ -201,8 +199,8 @@ pub fn visit<'tcx>( constraints: DataflowConstraints::default(), }; - for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { - for (idx, stmt) in bb_data.statements.iter().enumerate() { + for bb_data in mir.basic_blocks().iter() { + for stmt in bb_data.statements.iter() { tc.visit_statement(stmt); } tc.visit_terminator(bb_data.terminator()); diff --git a/c2rust-analyze/src/expr_rewrite.rs b/c2rust-analyze/src/expr_rewrite.rs index 57af1ba1d3..217bfa4c03 100644 --- a/c2rust-analyze/src/expr_rewrite.rs +++ b/c2rust-analyze/src/expr_rewrite.rs @@ -1,6 +1,6 @@ use rustc_middle::mir::{ Body, BasicBlock, Location, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, - Operand, Place, + Operand, }; use rustc_span::{Span, DUMMY_SP}; use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; @@ -85,7 +85,8 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { r } - fn enter_dest R, R>(&mut self, f: F) -> R { + #[allow(dead_code)] + fn _enter_dest R, R>(&mut self, f: F) -> R { self.enter(SubLoc::Dest, f) } @@ -101,11 +102,13 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter(SubLoc::RvalueOperand(i), f) } - fn enter_operand_place R, R>(&mut self, f: F) -> R { + #[allow(dead_code)] + fn _enter_operand_place R, R>(&mut self, f: F) -> R { self.enter(SubLoc::OperandPlace, f) } - fn enter_place_pointer R, R>(&mut self, i: usize, f: F) -> R { + #[allow(dead_code)] + fn _enter_place_pointer R, R>(&mut self, i: usize, f: F) -> R { self.enter(SubLoc::PlacePointer(i), f) } @@ -162,15 +165,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { let pl_ty = pl_ty.unwrap(); match callee { Callee::PtrOffset { .. } => { - // TODO - // - get own,qty for output (pl_ty) - // - increase qty to Quantity::Slice for own2,qty2 - // - cast argument to own2,qty2 - // - emit OffsetSlice - // - if output is not Slice, emit SliceFirst self.visit_ptr_offset(&args[0], pl_ty); - - return; }, } @@ -180,7 +175,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { let poly_sig = func_ty.fn_sig(tcx); let sig = tcx.erase_late_bound_regions(poly_sig); - for (i, op) in args.iter().enumerate() { + for (i, _op) in args.iter().enumerate() { if i >= sig.inputs().len() { // This is a call to a variadic function, and we've gone past the end of // the declared arguments. @@ -209,41 +204,41 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Rvalue::Use(ref op) => { self.enter_rvalue_operand(0, |v| v.visit_operand(op, expect_ty)); }, - Rvalue::Repeat(ref op, _) => { + Rvalue::Repeat(ref _op, _) => { // TODO }, - Rvalue::Ref(rg, kind, pl) => { + Rvalue::Ref(_rg, _kind, _pl) => { // TODO }, - Rvalue::ThreadLocalRef(def_id) => { + Rvalue::ThreadLocalRef(_def_id) => { // TODO }, - Rvalue::AddressOf(mutbl, pl) => { + Rvalue::AddressOf(_mutbl, _pl) => { // TODO }, - Rvalue::Len(pl) => { + Rvalue::Len(_pl) => { // TODO }, - Rvalue::Cast(kind, ref op, ty) => { + Rvalue::Cast(_kind, ref _op, _ty) => { // TODO }, - Rvalue::BinaryOp(bop, ref ops) => { + Rvalue::BinaryOp(_bop, ref _ops) => { // TODO }, - Rvalue::CheckedBinaryOp(bop, ref ops) => { + Rvalue::CheckedBinaryOp(_bop, ref _ops) => { // TODO }, Rvalue::NullaryOp(..) => {}, - Rvalue::UnaryOp(uop, ref op) => { + Rvalue::UnaryOp(_uop, ref _op) => { // TODO }, - Rvalue::Discriminant(pl) => { + Rvalue::Discriminant(_pl) => { // TODO }, - Rvalue::Aggregate(ref kind, ref ops) => { + Rvalue::Aggregate(ref _kind, ref _ops) => { // TODO }, - Rvalue::ShallowInitBox(ref op, ty) => { + Rvalue::ShallowInitBox(ref _op, _ty) => { // TODO }, } @@ -297,7 +292,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Quantity::OffsetPtr => todo!("OffsetPtr"), }; - self.enter_call_arg(0, |v| v.visit_operand_desc(op, result_own, arg_expect_qty)); + self.enter_call_arg(0, |v| v.visit_operand_desc(op, arg_expect_own, arg_expect_qty)); // Emit `OffsetSlice` for the offset itself. let mutbl = match result_own { diff --git a/c2rust-analyze/src/labeled_ty.rs b/c2rust-analyze/src/labeled_ty.rs index 7088d16a10..e6c5e3a7f4 100644 --- a/c2rust-analyze/src/labeled_ty.rs +++ b/c2rust-analyze/src/labeled_ty.rs @@ -7,7 +7,6 @@ use std::fmt; use std::marker::PhantomData; use std::ops::Deref; use rustc_arena::DroplessArena; -use rustc_middle::arena::Arena; use rustc_middle::ty::{TyCtxt, Ty, TyKind, TypeAndMut}; use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; @@ -180,7 +179,8 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { } /// Label multiple `Ty`s using a callback. - pub fn label_slice(&self, tys: &[Ty<'tcx>], f: &mut F) -> &'tcx [LabeledTy<'tcx, L>] + #[allow(dead_code)] + pub fn _label_slice(&self, tys: &[Ty<'tcx>], f: &mut F) -> &'tcx [LabeledTy<'tcx, L>] where F: FnMut(Ty<'tcx>) -> L, { @@ -195,6 +195,7 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { /// substitution on the underlying `Ty`s! This means if you substitute `u32` for `T`, you can /// end up with a `LabeledTy` whose `ty` is `S`, but whose args are `[u32]`. By some /// miracle, this hasn't broken anything yet, but we may need to fix it eventually. + #[allow(dead_code)] pub fn subst( &self, lty: LabeledTy<'tcx, L>, @@ -308,7 +309,7 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { assert!(it.next().is_none()); self.tcx.mk_fn_def(def_id, substs) } - FnPtr(ref sig) => { + FnPtr(ref _sig) => { // TODO: replace all the types under the binder todo!() } diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index b74ff42338..df5626d86b 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -13,32 +13,14 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; -use std::collections::HashMap; use std::env; -use std::hash::Hash; -use std::mem; -use polonius_engine::{self, Atom, FactTypes}; -use rustc_ast::ast::{Item, ItemKind, Visibility, VisibilityKind}; -use rustc_ast::node_id::NodeId; -use rustc_ast::ptr::P; -use rustc_driver::Compilation; use rustc_index::vec::IndexVec; -use rustc_interface::Queries; -use rustc_interface::interface::Compiler; -use rustc_middle::mir::{ - Body, BasicBlock, BasicBlockData, START_BLOCK, Terminator, TerminatorKind, SourceInfo, Local, - LocalDecl, LocalKind, LocalInfo, BindingForm, Mutability, Rvalue, AggregateKind, Place, - Operand, Statement, StatementKind, BorrowKind, Constant, ConstantKind, -}; -use rustc_middle::mir::interpret::{Allocation, ConstValue}; -use rustc_middle::mir::pretty; -use rustc_middle::ty::{TyCtxt, Ty, TyKind, RegionKind, WithOptConstParam, List}; +use rustc_middle::mir::{Body, LocalDecl, LocalInfo, BindingForm}; +use rustc_middle::ty::{TyCtxt, Ty, TyKind, WithOptConstParam}; use rustc_middle::ty::query::{Providers, ExternProviders}; use rustc_session::Session; -use rustc_span::{Span, DUMMY_SP}; -use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_INDEX}; -use rustc_span::symbol::Ident; -use rustc_target::abi::Align; +use rustc_span::Span; +use rustc_span::def_id::LocalDefId; use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; @@ -204,9 +186,9 @@ impl rustc_driver::Callbacks for AnalysisCallbacks { } fn override_queries( - sess: &Session, + _sess: &Session, providers: &mut Providers, - extern_providers: &mut ExternProviders, + _extern_providers: &mut ExternProviders, ) { providers.mir_built = |tcx, def: WithOptConstParam| { let mut providers = Providers::default(); @@ -220,6 +202,6 @@ fn override_queries( } fn main() -> rustc_interface::interface::Result<()> { - let mut args = env::args().collect::>(); + let args = env::args().collect::>(); rustc_driver::RunCompiler::new(&args, &mut AnalysisCallbacks).run() } diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs index 533c1fb34b..dab22e9fa5 100644 --- a/c2rust-analyze/src/type_desc.rs +++ b/c2rust-analyze/src/type_desc.rs @@ -1,9 +1,9 @@ use rustc_hir::def::{DefKind, Res}; -use rustc_middle::mir::Mutability; use rustc_middle::ty::{Ty, TyCtxt, ReErased}; use rustc_middle::ty::subst::GenericArg; use crate::context::{PermissionSet, FlagSet, AnalysisCtxt, LTy, PointerId}; +#[allow(dead_code)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum Ownership { /// E.g. `*const T` @@ -22,6 +22,7 @@ pub enum Ownership { Box, } +#[allow(dead_code)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum Quantity { /// E.g. `&T` diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index e602ec6f4c..8861df81e7 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -58,13 +58,11 @@ pub enum Callee<'tcx> { } pub fn ty_callee<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { - let (did, substs) = match *ty.kind() { + let (did, _substs) = match *ty.kind() { TyKind::FnDef(did, substs) => (did, substs), _ => return None, }; let name = tcx.item_name(did); - let poly_sig = tcx.fn_sig(did); - let sig = poly_sig.skip_binder(); match name.as_str() { "offset" => { From 0fa9ab3f7db39a923daa76116f8f09f4d27dae0f Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 19 Jul 2022 11:49:25 -0700 Subject: [PATCH 33/38] analyze: fix warnings in tests --- c2rust-analyze/tests/filecheck.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index e29c3f72f8..1d85670ad0 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -1,9 +1,7 @@ use std::env; use std::fs; use std::os::unix::io::{AsRawFd, FromRawFd}; -use std::path::Path; use std::process::{Command, Stdio}; -use std::str; #[test] fn filecheck() { From 5f70d422bb64a911ae0515658712f5470742aa87 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 19 Jul 2022 11:50:06 -0700 Subject: [PATCH 34/38] analyze: use rustc-private-link to find rustc library paths --- Cargo.lock | 2 ++ c2rust-analyze/Cargo.toml | 4 ++++ c2rust-analyze/build.rs | 22 +++++----------------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3d386aef9..b15eae1ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -221,7 +221,9 @@ version = "0.1.0" dependencies = [ "bitflags", "polonius-engine", + "print_bytes", "rustc-hash", + "rustc-private-link", ] [[package]] diff --git a/c2rust-analyze/Cargo.toml b/c2rust-analyze/Cargo.toml index 33b122b148..767dbe42a4 100644 --- a/c2rust-analyze/Cargo.toml +++ b/c2rust-analyze/Cargo.toml @@ -10,5 +10,9 @@ polonius-engine = "0.13.0" rustc-hash = "1.1.0" bitflags = "1.3.2" +[build-dependencies] +rustc-private-link = { path = "../rustc-private-link" } +print_bytes = "0.6" + [package.metadata.rust-analyzer] rustc_private = true diff --git a/c2rust-analyze/build.rs b/c2rust-analyze/build.rs index bbd8f16794..b917afbe87 100644 --- a/c2rust-analyze/build.rs +++ b/c2rust-analyze/build.rs @@ -1,21 +1,9 @@ -use std::env; -use std::path::Path; -use std::process::Command; -use std::str; +use rustc_private_link::SysRoot; fn main() { - let rustc = env::var("RUSTC").unwrap(); - // Add the toolchain lib/ directory to `-L`. This fixes the linker error "cannot find - // -lLLVM-13-rust-1.60.0-nightly". - let out = Command::new(&rustc) - .args(&["--print", "sysroot"]) - .output().unwrap(); - assert!(out.status.success()); - let sysroot = Path::new(str::from_utf8(&out.stdout).unwrap().trim_end()); - let lib_dir = sysroot.join("lib"); - println!("cargo:rustc-link-search={}", lib_dir.display()); + let sysroot = SysRoot::resolve(); + sysroot.link_rustc_private(); - let target = env::var("HOST").unwrap(); - let target_lib_dir = lib_dir.join("rustlib").join(target).join("lib"); - println!("cargo:rustc-env=C2RUST_TARGET_LIB_DIR={}", target_lib_dir.display()); + print!("cargo:rustc-env=C2RUST_TARGET_LIB_DIR="); + print_bytes::println_bytes(&sysroot.rustlib()); } From 621853103835d9b2f911ff4a48e36bb41b368715 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Tue, 19 Jul 2022 15:09:30 -0700 Subject: [PATCH 35/38] analyze: update README --- c2rust-analyze/README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/c2rust-analyze/README.md b/c2rust-analyze/README.md index ed44c99048..b80e6eb058 100644 --- a/c2rust-analyze/README.md +++ b/c2rust-analyze/README.md @@ -1,11 +1,7 @@ ```sh -rustc --crate-type rlib instrument_support.rs -cargo run -- fib.rs -L ~/.rustup/toolchains/nightly-2022-02-14-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/ -./fib +cargo run --bin c2rust-analyze -- tests/filecheck/insertion_sort.rs -L "$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/lib" --crate-type rlib ``` -The final `./fib` command should print several lines like -``` -[CALL] fib(5,) -``` -which come from the injected calls to `instrument_support::handle_call`. +This should produce a large amount of debug output, including a table at the +end listing the type and expression rewrites the analysis has inferred for the +`insertion_sort` function. From 0a0ab79b92caa582cc9f33625316145e58da655c Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 20 Jul 2022 15:17:07 -0700 Subject: [PATCH 36/38] analyze: change cfg for extra debugging code in tests/filecheck/*.rs --- c2rust-analyze/tests/filecheck.rs | 1 - c2rust-analyze/tests/filecheck/alias1.rs | 6 ++++-- c2rust-analyze/tests/filecheck/alias2.rs | 8 ++++---- c2rust-analyze/tests/filecheck/alias3.rs | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index 1d85670ad0..d5f5cb5afd 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -39,7 +39,6 @@ fn filecheck() { .arg(entry.path()) .arg("-L").arg(lib_dir) .arg("--crate-type").arg("rlib") - .arg("--cfg").arg("compiling_for_test") .stdout(unsafe { Stdio::from_raw_fd(pipe_fd) }) .stderr(unsafe { Stdio::from_raw_fd(pipe_fd) }); let mut analyze = analyze_cmd.spawn().unwrap(); diff --git a/c2rust-analyze/tests/filecheck/alias1.rs b/c2rust-analyze/tests/filecheck/alias1.rs index 8de6926c54..4eac063c91 100644 --- a/c2rust-analyze/tests/filecheck/alias1.rs +++ b/c2rust-analyze/tests/filecheck/alias1.rs @@ -25,7 +25,9 @@ pub unsafe fn alias1_bad() { } -#[cfg(not(compiling_for_test))] +// The safe versions of these functions are useful for debugging Polonius fact generation, but +// aren't checked when running tests. +#[cfg(debug_polonius_facts)] pub fn safe_alias1_good() { let mut x = 0; let p = &mut x; @@ -33,7 +35,7 @@ pub fn safe_alias1_good() { *q = 1; } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub fn safe_alias1_bad() { let mut x = 0; let p = &mut x; diff --git a/c2rust-analyze/tests/filecheck/alias2.rs b/c2rust-analyze/tests/filecheck/alias2.rs index 504590accc..b95cc129c6 100644 --- a/c2rust-analyze/tests/filecheck/alias2.rs +++ b/c2rust-analyze/tests/filecheck/alias2.rs @@ -43,28 +43,28 @@ pub unsafe fn alias2_addr_of_bad(x: *mut i32) { } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub unsafe fn safe_alias2_copy_good(x: &mut i32) { let p = x; let q = x; *q = 1; } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub unsafe fn safe_alias2_addr_of_good(x: &mut i32) { let p = &mut *x; let q = &mut *x; *q = 1; } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub unsafe fn safe_alias2_copy_bad(x: &mut i32) { let p = x; let q = x; *p = 1; } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub unsafe fn safe_alias2_addr_of_bad(x: &mut i32) { let p = &mut *x; let q = &mut *x; diff --git a/c2rust-analyze/tests/filecheck/alias3.rs b/c2rust-analyze/tests/filecheck/alias3.rs index 40d70a5099..a56d36aa39 100644 --- a/c2rust-analyze/tests/filecheck/alias3.rs +++ b/c2rust-analyze/tests/filecheck/alias3.rs @@ -26,14 +26,14 @@ pub unsafe fn alias3_copy_bad2(x: *mut i32) { *p = *q; } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub unsafe fn alias3_addr_of_bad1(x: *mut i32) { let p = ptr::addr_of_mut!(*x); let q = ptr::addr_of_mut!(*x); *q = *p; } -#[cfg(not(compiling_for_test))] +#[cfg(debug_polonius_facts)] pub unsafe fn alias3_addr_of_bad2(x: *mut i32) { let p = ptr::addr_of_mut!(*x); let q = ptr::addr_of_mut!(*x); From d3a0dc2ca16ca8353c1df8f8ec410102163f222a Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Wed, 20 Jul 2022 17:01:38 -0700 Subject: [PATCH 37/38] analyze: autodetect path to FileCheck in tests --- c2rust-analyze/tests/filecheck.rs | 42 ++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index d5f5cb5afd..3deb16a226 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -3,12 +3,52 @@ use std::fs; use std::os::unix::io::{AsRawFd, FromRawFd}; use std::process::{Command, Stdio}; +fn detect_filecheck() -> Option<&'static str> { + let candidates = [ + "FileCheck", + "/usr/local/opt/llvm/bin/FileCheck", + "FileCheck-14", + "/usr/local/opt/llvm@14/bin/FileCheck", + "FileCheck-13", + "/usr/local/opt/llvm@13/bin/FileCheck", + "FileCheck-12", + "/usr/local/opt/llvm@12/bin/FileCheck", + "FileCheck-11", + "/usr/local/opt/llvm@11/bin/FileCheck", + "FileCheck-10", + "/usr/local/opt/llvm@10/bin/FileCheck", + "FileCheck-9", + "/usr/local/opt/llvm@9/bin/FileCheck", + "FileCheck-8", + "/usr/local/opt/llvm@8/bin/FileCheck", + "FileCheck-7", + "FileCheck-7.0", + "/usr/local/opt/llvm@7/bin/FileCheck", + ]; + + for filecheck in candidates { + let result = Command::new(filecheck) + .arg("--version") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status(); + if result.is_ok() { + return Some(filecheck); + } + } + None +} + #[test] fn filecheck() { let lib_dir = env::var("C2RUST_TARGET_LIB_DIR").unwrap(); let lib_dir = &lib_dir; - let filecheck_bin = env::var("FILECHECK").unwrap_or_else(|_| "FileCheck".into()); + let filecheck_bin = env::var("FILECHECK") + .ok() + .or_else(|| detect_filecheck().map(|s| s.to_owned())) + .unwrap_or_else(|| panic!("FileCheck not found - set FILECHECK=/path/to/FileCheck")); + eprintln!("detected FILECHECK={}", filecheck_bin); for entry in fs::read_dir("tests/filecheck").unwrap() { let entry = entry.unwrap(); From b5a15ce68f6f9b1b6d380c8e0fcb6a148c94d0c8 Mon Sep 17 00:00:00 2001 From: Stuart Pernsteiner Date: Thu, 28 Jul 2022 16:54:23 -0700 Subject: [PATCH 38/38] analyze: cargo fmt --- c2rust-analyze/src/borrowck/atoms.rs | 34 ++--- c2rust-analyze/src/borrowck/def_use.rs | 103 ++++++++++----- c2rust-analyze/src/borrowck/dump.rs | 60 +++------ c2rust-analyze/src/borrowck/mod.rs | 91 ++++++++----- c2rust-analyze/src/borrowck/type_check.rs | 129 +++++++++--------- c2rust-analyze/src/context.rs | 18 +-- c2rust-analyze/src/dataflow/mod.rs | 53 +++----- c2rust-analyze/src/dataflow/type_check.rs | 85 ++++++------ c2rust-analyze/src/expr_rewrite.rs | 152 ++++++++++++---------- c2rust-analyze/src/labeled_ty.rs | 106 +++++++-------- c2rust-analyze/src/main.rs | 50 +++---- c2rust-analyze/src/type_desc.rs | 22 ++-- c2rust-analyze/src/util.rs | 31 +++-- c2rust-analyze/tests/filecheck.rs | 19 +-- 14 files changed, 482 insertions(+), 471 deletions(-) diff --git a/c2rust-analyze/src/borrowck/atoms.rs b/c2rust-analyze/src/borrowck/atoms.rs index 04d53d31e0..971d56e751 100644 --- a/c2rust-analyze/src/borrowck/atoms.rs +++ b/c2rust-analyze/src/borrowck/atoms.rs @@ -1,8 +1,8 @@ -use std::collections::hash_map::{HashMap, Entry}; -use std::hash::Hash; use polonius_engine::{self, Atom, FactTypes}; use rustc_middle::mir::{BasicBlock, Local, Location, Place, PlaceElem}; use rustc_middle::ty::TyCtxt; +use std::collections::hash_map::{Entry, HashMap}; +use std::hash::Hash; macro_rules! define_atom_type { ($Atom:ident) => { @@ -22,7 +22,9 @@ macro_rules! define_atom_type { } impl Atom for $Atom { - fn index(self) -> usize { self.0 } + fn index(self) -> usize { + self.0 + } } }; } @@ -33,7 +35,6 @@ define_atom_type!(Point); define_atom_type!(Variable); define_atom_type!(Path); - #[derive(Clone, Copy, Debug, Default)] pub struct AnalysisFactTypes; impl FactTypes for AnalysisFactTypes { @@ -47,8 +48,6 @@ impl FactTypes for AnalysisFactTypes { pub type AllFacts = polonius_engine::AllFacts; pub type Output = polonius_engine::Output; - - #[derive(Clone, Debug)] struct AtomMap { atom_to_thing: Vec, @@ -75,29 +74,25 @@ impl AtomMap { pub fn add(&mut self, x: T) -> A { match self.thing_to_atom.entry(x.clone()) { - Entry::Occupied(e) => { - *e.get() - }, + Entry::Occupied(e) => *e.get(), Entry::Vacant(e) => { let atom = A::from(self.atom_to_thing.len()); self.atom_to_thing.push(x); e.insert(atom); atom - }, + } } } pub fn add_new(&mut self, x: T) -> (A, bool) { match self.thing_to_atom.entry(x.clone()) { - Entry::Occupied(e) => { - (*e.get(), false) - }, + Entry::Occupied(e) => (*e.get(), false), Entry::Vacant(e) => { let atom = A::from(self.atom_to_thing.len()); self.atom_to_thing.push(x); e.insert(atom); (atom, true) - }, + } } } @@ -106,14 +101,12 @@ impl AtomMap { } } - #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum SubPoint { Start, Mid, } - #[derive(Clone, Debug, Default)] pub struct AtomMaps<'tcx> { next_origin: usize, @@ -149,7 +142,10 @@ impl<'tcx> AtomMaps<'tcx> { pub fn get_point_location(&self, x: Point) -> Location { let (block, statement_index, _) = self.get_point(x); - Location { block, statement_index } + Location { + block, + statement_index, + } } pub fn variable(&mut self, l: Local) -> Variable { @@ -176,7 +172,7 @@ impl<'tcx> AtomMaps<'tcx> { let var = self.variable(local); facts.path_is_var.push((path, var)); } else { - let parent = self.path_slice(facts, local, &projection[.. projection.len() - 1]); + let parent = self.path_slice(facts, local, &projection[..projection.len() - 1]); // TODO: check ordering of arguments here facts.child_path.push((parent, path)); } @@ -195,5 +191,3 @@ impl<'tcx> AtomMaps<'tcx> { projection } } - - diff --git a/c2rust-analyze/src/borrowck/def_use.rs b/c2rust-analyze/src/borrowck/def_use.rs index bfd4678bd2..6494ba0ded 100644 --- a/c2rust-analyze/src/borrowck/def_use.rs +++ b/c2rust-analyze/src/borrowck/def_use.rs @@ -1,14 +1,13 @@ -use std::cmp; -use std::collections::HashMap; -use rustc_middle::mir::{ - Body, Place, Local, Location, Statement, StatementKind, ProjectionElem, BorrowKind, -}; +use crate::borrowck::atoms::{AllFacts, AtomMaps, Loan, Path, SubPoint}; use rustc_middle::mir::visit::{ - Visitor, PlaceContext, MutatingUseContext, NonMutatingUseContext, NonUseContext, + MutatingUseContext, NonMutatingUseContext, NonUseContext, PlaceContext, Visitor, }; -use rustc_middle::ty::{TyCtxt, List}; -use crate::borrowck::atoms::{AllFacts, AtomMaps, Path, Loan, SubPoint}; - +use rustc_middle::mir::{ + Body, BorrowKind, Local, Location, Place, ProjectionElem, Statement, StatementKind, +}; +use rustc_middle::ty::{List, TyCtxt}; +use std::cmp; +use std::collections::HashMap; // From `rustc_borrowck/src/def_use.rs`, licensed MIT/Apache2 #[derive(Eq, PartialEq, Clone, Debug)] @@ -85,7 +84,6 @@ pub fn categorize(context: PlaceContext) -> Option { } } - struct DefUseVisitor<'tcx, 'a> { facts: &'a mut AllFacts, maps: &'a mut AtomMaps<'tcx>, @@ -94,8 +92,13 @@ struct DefUseVisitor<'tcx, 'a> { impl<'tcx> Visitor<'tcx> for DefUseVisitor<'tcx, '_> { fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { self.super_place(place, context, location); - eprintln!("visit place {:?} with context {:?} = {:?} at {:?}", - place, context, categorize(context), location); + eprintln!( + "visit place {:?} with context {:?} = {:?} at {:?}", + place, + context, + categorize(context), + location + ); if place.is_indirect() { // TODO @@ -115,31 +118,36 @@ impl<'tcx> Visitor<'tcx> for DefUseVisitor<'tcx, '_> { match categorize(context) { Some(DefUse::Def) => { self.facts.path_assigned_at_base.push((path, point)); - }, + } Some(DefUse::Use) => { self.facts.path_accessed_at_base.push((path, point)); - }, - Some(DefUse::Drop) => {}, - None => {}, + } + Some(DefUse::Drop) => {} + None => {} } } fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) { - eprintln!("visit local {:?} with context {:?} = {:?} at {:?}", - local, context, categorize(context), location); + eprintln!( + "visit local {:?} with context {:?} = {:?} at {:?}", + local, + context, + categorize(context), + location + ); let var = self.maps.variable(*local); let point = self.maps.point_mid_location(location); match categorize(context) { Some(DefUse::Def) => { self.facts.var_defined_at.push((var, point)); - }, + } Some(DefUse::Use) => { self.facts.var_used_at.push((var, point)); - }, + } Some(DefUse::Drop) => { self.facts.var_dropped_at.push((var, point)); - }, - None => {}, + } + None => {} } } @@ -149,7 +157,10 @@ impl<'tcx> Visitor<'tcx> for DefUseVisitor<'tcx, '_> { if let StatementKind::StorageDead(local) = stmt.kind { // Observed: `StorageDead` emits `path_moved_at_base` at the `Mid` point. - let place = Place { local, projection: List::empty() }; + let place = Place { + local, + projection: List::empty(), + }; let path = self.maps.path(self.facts, place); let point = self.maps.point_mid_location(location); self.facts.path_moved_at_base.push((path, point)); @@ -162,12 +173,11 @@ pub fn visit<'tcx>(facts: &mut AllFacts, maps: &mut AtomMaps<'tcx>, mir: &Body<' v.visit_body(mir); } - struct LoanInvalidatedAtVisitor<'tcx, 'a> { tcx: TyCtxt<'tcx>, facts: &'a mut AllFacts, maps: &'a mut AtomMaps<'tcx>, - loans: &'a HashMap>, + loans: &'a HashMap>, } impl<'tcx> LoanInvalidatedAtVisitor<'tcx, '_> { @@ -180,8 +190,14 @@ impl<'tcx> LoanInvalidatedAtVisitor<'tcx, '_> { context: PlaceContext, location: Location, ) { - eprintln!("access loan {:?} (kind {:?}) at location {:?} (context {:?} = {:?})", - loan, borrow_kind, location, context, categorize(context)); + eprintln!( + "access loan {:?} (kind {:?}) at location {:?} (context {:?} = {:?})", + loan, + borrow_kind, + location, + context, + categorize(context) + ); let invalidate = match (borrow_kind, categorize(context)) { (BorrowKind::Shared, Some(DefUse::Use)) => false, (_, None) => false, @@ -191,7 +207,9 @@ impl<'tcx> LoanInvalidatedAtVisitor<'tcx, '_> { return; } - let point = self.maps.point(location.block, location.statement_index, SubPoint::Start); + let point = self + .maps + .point(location.block, location.statement_index, SubPoint::Start); self.facts.loan_invalidated_at.push((point, loan)); } } @@ -199,8 +217,13 @@ impl<'tcx> LoanInvalidatedAtVisitor<'tcx, '_> { impl<'tcx> Visitor<'tcx> for LoanInvalidatedAtVisitor<'tcx, '_> { fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) { //self.super_place(place, context, location); - eprintln!("loan_invalidated_at: visit place {:?} with context {:?} = {:?} at {:?}", - place, context, categorize(context), location); + eprintln!( + "loan_invalidated_at: visit place {:?} with context {:?} = {:?} at {:?}", + place, + context, + categorize(context), + location + ); if place.is_indirect() { // TODO @@ -213,7 +236,9 @@ impl<'tcx> Visitor<'tcx> for LoanInvalidatedAtVisitor<'tcx, '_> { // If `proj` is a prefix of `place.projection` or vice versa, then the paths overlap. let common_len = cmp::min(proj.len(), place.projection.len()); - let overlap = proj[..common_len].iter().zip(place.projection[..common_len].iter()) + let overlap = proj[..common_len] + .iter() + .zip(place.projection[..common_len].iter()) .all(|(&elem1, &elem2)| match (elem1, elem2) { (ProjectionElem::Field(f1, _), ProjectionElem::Field(f2, _)) => f1 == f2, (ProjectionElem::Index(_), ProjectionElem::Index(_)) => true, @@ -229,8 +254,13 @@ impl<'tcx> Visitor<'tcx> for LoanInvalidatedAtVisitor<'tcx, '_> { } fn visit_local(&mut self, local: &Local, context: PlaceContext, location: Location) { - eprintln!("loan_invalidated_at: visit local {:?} with context {:?} = {:?} at {:?}", - local, context, categorize(context), location); + eprintln!( + "loan_invalidated_at: visit local {:?} with context {:?} = {:?} at {:?}", + local, + context, + categorize(context), + location + ); let local_loans = self.loans.get(&local).map_or(&[] as &[_], |x| x); for &(_path, loan, borrow_kind) in local_loans { @@ -247,6 +277,11 @@ pub fn visit_loan_invalidated_at<'tcx>( loans: &HashMap>, mir: &Body<'tcx>, ) { - let mut v = LoanInvalidatedAtVisitor { tcx, facts, maps, loans }; + let mut v = LoanInvalidatedAtVisitor { + tcx, + facts, + maps, + loans, + }; v.visit_body(mir) } diff --git a/c2rust-analyze/src/borrowck/dump.rs b/c2rust-analyze/src/borrowck/dump.rs index b552f99160..be9fa03485 100644 --- a/c2rust-analyze/src/borrowck/dump.rs +++ b/c2rust-analyze/src/borrowck/dump.rs @@ -1,3 +1,5 @@ +use crate::borrowck::atoms::{AllFacts, AtomMaps, Loan, Origin, Output, Path, Point, Variable}; +use rustc_hash::{FxHashMap, FxHashSet}; /// Copied partly from rustc `compiler/rustc_borrowck/src/facts.rs`, which is dual-licensed MIT and /// Apache 2.0. use std::collections::{BTreeMap, BTreeSet}; @@ -7,8 +9,6 @@ use std::fs::{self, File}; use std::hash::Hash; use std::io::{BufWriter, Write}; use std::path; -use rustc_hash::{FxHashMap, FxHashSet}; -use crate::borrowck::atoms::{AllFacts, Output, AtomMaps, Origin, Loan, Point, Variable, Path}; pub fn dump_facts_to_dir( facts: &AllFacts, @@ -130,19 +130,11 @@ impl FactWriter<'_, '_> { } trait FactRow { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box>; + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box>; } impl FactRow for Origin { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box> { + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box> { write_row(out, maps, &[self]) } } @@ -152,11 +144,7 @@ where A: Render, B: Render, { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box> { + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box> { write_row(out, maps, &[&self.0, &self.1]) } } @@ -167,11 +155,7 @@ where B: Render, C: Render, { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box> { + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box> { write_row(out, maps, &[&self.0, &self.1, &self.2]) } } @@ -183,11 +167,7 @@ where C: Render, D: Render, { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box> { + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box> { write_row(out, maps, &[&self.0, &self.1, &self.2, &self.3]) } } @@ -198,27 +178,22 @@ fn write_row( columns: &[&dyn Render], ) -> Result<(), Box> { for (index, c) in columns.iter().enumerate() { - let tail = if index == columns.len() - 1 { "\n" } else { "\t" }; + let tail = if index == columns.len() - 1 { + "\n" + } else { + "\t" + }; write!(out, "{:?}{}", c.to_string(maps), tail)?; } Ok(()) } - trait OutputTable { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box>; + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box>; } impl OutputTable for FxHashMap { - fn write( - &self, - out: &mut dyn Write, - maps: &AtomMaps, - ) -> Result<(), Box> { + fn write(&self, out: &mut dyn Write, maps: &AtomMaps) -> Result<(), Box> { let mut entries = self.iter().collect::>(); entries.sort_by_key(|&(k, _)| k); for (k, v) in entries { @@ -229,17 +204,12 @@ impl OutputTable for FxHashMap { } impl OutputTable for bool { - fn write( - &self, - out: &mut dyn Write, - _maps: &AtomMaps, - ) -> Result<(), Box> { + fn write(&self, out: &mut dyn Write, _maps: &AtomMaps) -> Result<(), Box> { writeln!(out, "{}", self)?; Ok(()) } } - trait Render { fn to_string(&self, maps: &AtomMaps) -> String; } diff --git a/c2rust-analyze/src/borrowck/mod.rs b/c2rust-analyze/src/borrowck/mod.rs index 90ed90bfbe..b8433060b2 100644 --- a/c2rust-analyze/src/borrowck/mod.rs +++ b/c2rust-analyze/src/borrowck/mod.rs @@ -1,21 +1,19 @@ -use std::collections::HashMap; -use std::hash::Hash; -use polonius_engine; -use rustc_middle::mir::{Body, START_BLOCK, Local, LocalKind, Place, StatementKind, BorrowKind}; -use rustc_middle::ty::{TyKind, List}; +use self::atoms::{AllFacts, AtomMaps, Loan, Origin, Output, Path, SubPoint}; use crate::context::{AnalysisCtxt, PermissionSet}; use crate::dataflow::DataflowConstraints; use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; use crate::util::{describe_rvalue, RvalueDesc}; -use self::atoms::{AllFacts, AtomMaps, Output, SubPoint, Origin, Path, Loan}; - +use polonius_engine; +use rustc_middle::mir::{Body, BorrowKind, Local, LocalKind, Place, StatementKind, START_BLOCK}; +use rustc_middle::ty::{List, TyKind}; +use std::collections::HashMap; +use std::hash::Hash; mod atoms; mod def_use; mod dump; mod type_check; - #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Default)] pub struct Label { pub origin: Option, @@ -25,7 +23,6 @@ pub struct Label { pub type LTy<'tcx> = LabeledTy<'tcx, Label>; pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, Label>; - pub fn borrowck_mir<'tcx>( acx: &AnalysisCtxt<'tcx>, dataflow: &DataflowConstraints, @@ -39,34 +36,43 @@ pub fn borrowck_mir<'tcx>( let (facts, maps, output) = run_polonius(acx, hypothesis, name, mir); eprintln!( "polonius: iteration {}: {} errors, {} move_errors", - i, output.errors.len(), output.move_errors.len(), + i, + output.errors.len(), + output.move_errors.len(), ); i += 1; if output.errors.len() == 0 { break; } - if i >= 20 { panic!() } + if i >= 20 { + panic!() + } let mut changed = false; for (_, loans) in &output.errors { for &loan in loans { - let issued_point = facts.loan_issued_at.iter().find(|&&(_, l, _)| l == loan) + let issued_point = facts + .loan_issued_at + .iter() + .find(|&&(_, l, _)| l == loan) .map(|&(_, _, point)| point) .unwrap_or_else(|| panic!("loan {:?} was never issued?", loan)); let issued_loc = maps.get_point_location(issued_point); let stmt = mir.stmt_at(issued_loc).left().unwrap_or_else(|| { - panic!("loan {:?} was issued by a terminator (at {:?})?", loan, issued_loc); + panic!( + "loan {:?} was issued by a terminator (at {:?})?", + loan, issued_loc + ); }); let ptr = match stmt.kind { StatementKind::Assign(ref x) => match describe_rvalue(&x.1) { - Some(RvalueDesc::Project { base, proj: _ }) => { - acx.ptr_of(base) - .unwrap_or_else(|| panic!("missing pointer ID for {:?}", base)) - }, + Some(RvalueDesc::Project { base, proj: _ }) => acx + .ptr_of(base) + .unwrap_or_else(|| panic!("missing pointer ID for {:?}", base)), Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => { acx.addr_of_local[local] - }, + } None => panic!("loan {:?} was issued by unknown rvalue {:?}?", loan, x.1), }, _ => panic!("loan {:?} was issued by non-assign stmt {:?}?", loan, stmt), @@ -96,7 +102,6 @@ pub fn borrowck_mir<'tcx>( } } - fn run_polonius<'tcx>( acx: &AnalysisCtxt<'tcx>, hypothesis: &[PermissionSet], @@ -119,7 +124,7 @@ fn run_polonius<'tcx>( for (bb, bb_data) in mir.basic_blocks().iter_enumerated() { eprintln!("{:?}:", bb); - for idx in 0 .. bb_data.statements.len() { + for idx in 0..bb_data.statements.len() { eprintln!(" {}: {:?}", idx, bb_data.statements[idx]); let start = maps.point(bb, idx, SubPoint::Start); let mid = maps.point(bb, idx, SubPoint::Mid); @@ -145,10 +150,22 @@ fn run_polonius<'tcx>( let entry_point = maps.point(START_BLOCK, 0, SubPoint::Start); for local in mir.local_decls.indices() { if mir.local_kind(local) == LocalKind::Arg { - let path = maps.path(&mut facts, Place { local, projection: List::empty() }); + let path = maps.path( + &mut facts, + Place { + local, + projection: List::empty(), + }, + ); facts.path_assigned_at_base.push((path, entry_point)); } else { - let path = maps.path(&mut facts, Place { local, projection: List::empty() }); + let path = maps.path( + &mut facts, + Place { + local, + projection: List::empty(), + }, + ); facts.path_moved_at_base.push((path, entry_point)); } } @@ -157,7 +174,13 @@ fn run_polonius<'tcx>( let ltcx = LabeledTyCtxt::new(tcx); let mut local_ltys = Vec::with_capacity(mir.local_decls.len()); for local in mir.local_decls.indices() { - let lty = assign_origins(ltcx, hypothesis, &mut facts, &mut maps, acx.local_tys[local]); + let lty = assign_origins( + ltcx, + hypothesis, + &mut facts, + &mut maps, + acx.local_tys[local], + ); let var = maps.variable(local); lty.for_each_label(&mut |label| { if let Some(origin) = label.origin { @@ -169,7 +192,15 @@ fn run_polonius<'tcx>( let mut loans = HashMap::>::new(); // Populate `loan_issued_at` and `loans`. - type_check::visit(tcx, ltcx, &mut facts, &mut maps, &mut loans, &local_ltys, mir); + type_check::visit( + tcx, + ltcx, + &mut facts, + &mut maps, + &mut loans, + &local_ltys, + mir, + ); // Populate `loan_invalidated_at` def_use::visit_loan_invalidated_at(acx.tcx, &mut facts, &mut maps, &loans, mir); @@ -177,14 +208,9 @@ fn run_polonius<'tcx>( // Populate `var_defined/used/dropped_at` and `path_assigned/accessed_at_base`. def_use::visit(&mut facts, &mut maps, mir); - dump::dump_facts_to_dir(&facts, &maps, format!("inspect/{}", name)).unwrap(); - let output = polonius_engine::Output::compute( - &facts, - polonius_engine::Algorithm::Naive, - true, - ); + let output = polonius_engine::Output::compute(&facts, polonius_engine::Algorithm::Naive, true); dump::dump_output_to_dir(&output, &maps, format!("inspect/{}", name)).unwrap(); (facts, maps, output) @@ -204,11 +230,10 @@ fn assign_origins<'tcx>( hypothesis[lty.label.index()] }; match lty.ty.kind() { - TyKind::Ref(_, _, _) | - TyKind::RawPtr(_) => { + TyKind::Ref(_, _, _) | TyKind::RawPtr(_) => { let origin = Some(maps.origin()); Label { origin, perm } - }, + } _ => Label { origin: None, perm }, } }) diff --git a/c2rust-analyze/src/borrowck/type_check.rs b/c2rust-analyze/src/borrowck/type_check.rs index 69a84be96c..4620ed6776 100644 --- a/c2rust-analyze/src/borrowck/type_check.rs +++ b/c2rust-analyze/src/borrowck/type_check.rs @@ -1,15 +1,14 @@ -use std::collections::HashMap; +use crate::borrowck::atoms::{AllFacts, AtomMaps, Loan, Origin, Path, Point, SubPoint}; +use crate::borrowck::{LTy, LTyCtxt, Label}; +use crate::context::PermissionSet; +use crate::util::{self, Callee}; use rustc_index::vec::IndexVec; use rustc_middle::mir::{ - Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, BinOp, Place, Operand, - BorrowKind, Local, LocalDecl, Location, ProjectionElem, + BinOp, Body, BorrowKind, Local, LocalDecl, Location, Operand, Place, ProjectionElem, Rvalue, + Statement, StatementKind, Terminator, TerminatorKind, }; use rustc_middle::ty::{TyCtxt, TyKind}; -use crate::borrowck::{LTy, LTyCtxt, Label}; -use crate::borrowck::atoms::{AllFacts, AtomMaps, Point, SubPoint, Path, Loan, Origin}; -use crate::context::PermissionSet; -use crate::util::{self, Callee}; - +use std::collections::HashMap; struct TypeChecker<'tcx, 'a> { tcx: TyCtxt<'tcx>, @@ -39,15 +38,13 @@ impl<'tcx> TypeChecker<'tcx, '_> { ProjectionElem::Deref => { assert_eq!(lty.args.len(), 1); lty = lty.args[0]; - }, + } - ProjectionElem::Field(f, _field_ty) => { - match lty.ty.kind() { - TyKind::Tuple(..) => { - lty = lty.args[f.as_usize()]; - }, - _ => todo!("field of {:?}", lty), + ProjectionElem::Field(f, _field_ty) => match lty.ty.kind() { + TyKind::Tuple(..) => { + lty = lty.args[f.as_usize()]; } + _ => todo!("field of {:?}", lty), }, ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), @@ -58,27 +55,25 @@ impl<'tcx> TypeChecker<'tcx, '_> { pub fn visit_operand(&mut self, op: &Operand<'tcx>) -> LTy<'tcx> { match *op { - Operand::Copy(pl) | - Operand::Move(pl) => self.visit_place(pl), + Operand::Copy(pl) | Operand::Move(pl) => self.visit_place(pl), Operand::Constant(ref c) => { let ty = c.ty(); self.ltcx.label(ty, &mut |_| Label::default()) - }, + } } } /// Create a new origin and issue an associated loan. The loan is issued at /// `self.current_location`. - fn issue_loan( - &mut self, - pl: Place<'tcx>, - borrow_kind: BorrowKind, - ) -> Origin { + fn issue_loan(&mut self, pl: Place<'tcx>, borrow_kind: BorrowKind) -> Origin { // Create a new origin and issue an associated loan. let origin = self.maps.origin(); let path = self.maps.path(self.facts, pl); let loan = self.maps.loan(); - self.loans.entry(pl.local).or_default().push((path, loan, borrow_kind)); + self.loans + .entry(pl.local) + .or_default() + .push((path, loan, borrow_kind)); let point = self.current_point(SubPoint::Mid); self.facts.loan_issued_at.push((origin, loan, point)); eprintln!("issued loan {:?} = {:?} ({:?})", loan, pl, borrow_kind); @@ -87,12 +82,15 @@ impl<'tcx> TypeChecker<'tcx, '_> { pub fn visit_rvalue(&mut self, rv: &Rvalue<'tcx>, expect_ty: LTy<'tcx>) -> LTy<'tcx> { match *rv { - Rvalue::Use(Operand::Move(pl)) | - Rvalue::Use(Operand::Copy(pl)) if matches!(expect_ty.ty.kind(), TyKind::RawPtr(_)) => { + Rvalue::Use(Operand::Move(pl)) | Rvalue::Use(Operand::Copy(pl)) + if matches!(expect_ty.ty.kind(), TyKind::RawPtr(_)) => + { // Copy of a raw pointer. We treat this as a reborrow. let perm = expect_ty.label.perm; let borrow_kind = if perm.contains(PermissionSet::UNIQUE) { - BorrowKind::Mut { allow_two_phase_borrow: false } + BorrowKind::Mut { + allow_two_phase_borrow: false, + } } else { BorrowKind::Shared }; @@ -103,11 +101,13 @@ impl<'tcx> TypeChecker<'tcx, '_> { // Return a type with the new loan on the outermost `ref`. let ty = rv.ty(self.local_decls, *self.ltcx); let pl_lty = self.visit_place(pl_deref); - let label = Label { origin: Some(origin), perm }; + let label = Label { + origin: Some(origin), + perm, + }; let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), label); lty - - }, + } Rvalue::Use(ref op) => self.visit_operand(op), @@ -118,15 +118,20 @@ impl<'tcx> TypeChecker<'tcx, '_> { // Return a type with the new loan on the outermost `ref`. let ty = rv.ty(self.local_decls, *self.ltcx); let pl_lty = self.visit_place(pl); - let label = Label { origin: Some(origin), perm }; + let label = Label { + origin: Some(origin), + perm, + }; let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), label); lty - }, + } Rvalue::AddressOf(_, pl) => { let perm = expect_ty.label.perm; let borrow_kind = if perm.contains(PermissionSet::UNIQUE) { - BorrowKind::Mut { allow_two_phase_borrow: false } + BorrowKind::Mut { + allow_two_phase_borrow: false, + } } else { BorrowKind::Shared }; @@ -136,30 +141,35 @@ impl<'tcx> TypeChecker<'tcx, '_> { // Return a type with the new loan on the outermost `ref`. let ty = rv.ty(self.local_decls, *self.ltcx); let pl_lty = self.visit_place(pl); - let label = Label { origin: Some(origin), perm }; + let label = Label { + origin: Some(origin), + perm, + }; let lty = self.ltcx.mk(ty, self.ltcx.mk_slice(&[pl_lty]), label); lty - }, + } - Rvalue::BinaryOp(BinOp::Offset, _) | - Rvalue::CheckedBinaryOp(BinOp::Offset, _) => todo!("visit_rvalue BinOp::Offset"), - Rvalue::BinaryOp(_, ref _ab) | - Rvalue::CheckedBinaryOp(_, ref _ab) => { + Rvalue::BinaryOp(BinOp::Offset, _) | Rvalue::CheckedBinaryOp(BinOp::Offset, _) => { + todo!("visit_rvalue BinOp::Offset") + } + Rvalue::BinaryOp(_, ref _ab) | Rvalue::CheckedBinaryOp(_, ref _ab) => { let ty = rv.ty(self.local_decls, *self.ltcx); self.ltcx.label(ty, &mut |ty| { - assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..)), - "pointer BinaryOp NYI"); + assert!( + !matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..)), + "pointer BinaryOp NYI" + ); Label::default() }) - }, + } - Rvalue::Cast(_, _, ty) => { - self.ltcx.label(ty, &mut |ty| { - assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..)), - "pointer Cast NYI"); - Label::default() - }) - }, + Rvalue::Cast(_, _, ty) => self.ltcx.label(ty, &mut |ty| { + assert!( + !matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..)), + "pointer Cast NYI" + ); + Label::default() + }), ref rv => panic!("unsupported rvalue {:?}", rv), } @@ -183,15 +193,20 @@ impl<'tcx> TypeChecker<'tcx, '_> { let pl_lty = self.visit_place(pl); let rv_lty = self.visit_rvalue(rv, pl_lty); self.do_assign(pl_lty, rv_lty); - }, - _ => {}, + } + _ => {} } } pub fn visit_terminator(&mut self, term: &Terminator<'tcx>) { eprintln!("borrowck: visit_terminator({:?})", term.kind); match term.kind { - TerminatorKind::Call { ref func, ref args, destination, .. } => { + TerminatorKind::Call { + ref func, + ref args, + destination, + .. + } => { let func_ty = func.ty(self.local_decls, *self.ltcx); eprintln!("callee = {:?}", util::ty_callee(*self.ltcx, func_ty)); match util::ty_callee(*self.ltcx, func_ty) { @@ -204,11 +219,11 @@ impl<'tcx> TypeChecker<'tcx, '_> { assert!(args.len() == 2); let rv_lty = self.visit_operand(&args[0]); self.do_assign(pl_lty, rv_lty); - }, - None => {}, + } + None => {} } - }, - _ => {}, + } + _ => {} } } } @@ -249,5 +264,3 @@ pub fn visit<'tcx>( tc.visit_terminator(bb_data.terminator()); } } - - diff --git a/c2rust-analyze/src/context.rs b/c2rust-analyze/src/context.rs index 19aac2e151..665eea31e6 100644 --- a/c2rust-analyze/src/context.rs +++ b/c2rust-analyze/src/context.rs @@ -1,10 +1,9 @@ -use std::cell::Cell; +use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; use bitflags::bitflags; use rustc_index::vec::IndexVec; use rustc_middle::mir::{Local, Place, PlaceRef, ProjectionElem}; use rustc_middle::ty::{TyCtxt, TyKind}; -use crate::labeled_ty::{LabeledTy, LabeledTyCtxt}; - +use std::cell::Cell; bitflags! { #[derive(Default)] @@ -31,7 +30,6 @@ bitflags! { } } - bitflags! { /// Additional flags describing a given pointer type. These are mainly derived from /// `PermissionSet`, but don't follow the normal subtyping rules and propagation algorithm. @@ -45,7 +43,6 @@ bitflags! { } } - #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] pub struct PointerId(u32); @@ -61,7 +58,6 @@ impl PointerId { } } - pub type LTy<'tcx> = LabeledTy<'tcx, PointerId>; pub type LTyCtxt<'tcx> = LabeledTyCtxt<'tcx, PointerId>; @@ -110,7 +106,6 @@ impl<'tcx> AnalysisCtxt<'tcx> { } } - pub trait TypeOf<'tcx> { fn type_of(&self, acx: &AnalysisCtxt<'tcx>) -> LTy<'tcx>; } @@ -142,16 +137,17 @@ impl<'tcx> TypeOf<'tcx> for PlaceRef<'tcx> { assert!(matches!(ty.kind(), TyKind::Ref(..) | TyKind::RawPtr(..))); assert_eq!(ty.args.len(), 1); ty = ty.args[0]; - }, + } ProjectionElem::Field(f, _) => match ty.kind() { TyKind::Tuple(_) => { ty = ty.args[f.index()]; - }, + } TyKind::Adt(..) => todo!("type_of Field(Adt)"), _ => panic!("Field projection is unsupported on type {:?}", ty), + }, + ProjectionElem::Index(..) | ProjectionElem::ConstantIndex { .. } => { + todo!("type_of Index") } - ProjectionElem::Index(..) | - ProjectionElem::ConstantIndex { .. } => todo!("type_of Index"), ProjectionElem::Subslice { .. } => todo!("type_of Subslice"), ProjectionElem::Downcast(..) => todo!("type_of Downcast"), } diff --git a/c2rust-analyze/src/dataflow/mod.rs b/c2rust-analyze/src/dataflow/mod.rs index 73f6f5114a..2555c79aa8 100644 --- a/c2rust-analyze/src/dataflow/mod.rs +++ b/c2rust-analyze/src/dataflow/mod.rs @@ -1,11 +1,10 @@ use std::mem; +use crate::context::{AnalysisCtxt, FlagSet, PermissionSet, PointerId}; use rustc_middle::mir::Body; -use crate::context::{PermissionSet, FlagSet, PointerId, AnalysisCtxt}; mod type_check; - #[derive(Clone, Debug)] enum Constraint { /// Pointer `.0` must have a subset of the permissions of pointer `.1`. @@ -22,28 +21,16 @@ pub struct DataflowConstraints { } impl DataflowConstraints { - fn add_subset( - &mut self, - a: PointerId, - b: PointerId, - ) { + fn add_subset(&mut self, a: PointerId, b: PointerId) { self.constraints.push(Constraint::Subset(a, b)); } - fn add_all_perms( - &mut self, - ptr: PointerId, - perms: PermissionSet, - ) { + fn add_all_perms(&mut self, ptr: PointerId, perms: PermissionSet) { self.constraints.push(Constraint::AllPerms(ptr, perms)); } #[allow(dead_code)] - fn _add_no_perms( - &mut self, - ptr: PointerId, - perms: PermissionSet, - ) { + fn _add_no_perms(&mut self, ptr: PointerId, perms: PermissionSet) { self.constraints.push(Constraint::NoPerms(ptr, perms)); } @@ -77,16 +64,14 @@ impl DataflowConstraints { // Permissions that should be propagated "down": if the superset (`b`) // doesn't have it, then the subset (`a`) should have it removed. #[allow(bad_style)] - let PROPAGATE_DOWN = - PermissionSet::UNIQUE; + let PROPAGATE_DOWN = PermissionSet::UNIQUE; // Permissions that should be propagated "up": if the subset (`a`) has it, // then the superset (`b`) should be given it. #[allow(bad_style)] - let PROPAGATE_UP = - PermissionSet::READ | - PermissionSet::WRITE | - PermissionSet::OFFSET_ADD | - PermissionSet::OFFSET_SUB; + let PROPAGATE_UP = PermissionSet::READ + | PermissionSet::WRITE + | PermissionSet::OFFSET_ADD + | PermissionSet::OFFSET_SUB; ( old_a & !(!old_b & PROPAGATE_DOWN), @@ -117,12 +102,15 @@ impl DataflowConstraints { Ok(changed) => changed, Err(msg) => { panic!("{}", msg); - }, + } } } fn propagate_inner(&self, xs: &mut [T], rules: &mut R) -> Result - where T: PartialEq, R: PropagateRules { + where + T: PartialEq, + R: PropagateRules, + { let mut xs = TrackedSlice::new(xs); let mut changed = false; @@ -145,7 +133,7 @@ impl DataflowConstraints { let (new_a, new_b) = rules.subset(a, old_a, b, old_b); xs.set(a.index(), new_a); xs.set(b.index(), new_b); - }, + } Constraint::AllPerms(ptr, perms) => { if !xs.dirty(ptr.index()) { @@ -155,7 +143,7 @@ impl DataflowConstraints { let old = xs.get(ptr.index()); let new = rules.all_perms(ptr, perms, old); xs.set(ptr.index(), new); - }, + } Constraint::NoPerms(ptr, perms) => { if !xs.dirty(ptr.index()) { @@ -165,7 +153,7 @@ impl DataflowConstraints { let old = xs.get(ptr.index()); let new = rules.no_perms(ptr, perms, old); xs.set(ptr.index(), new); - }, + } } } @@ -239,15 +227,14 @@ impl DataflowConstraints { } match self.propagate_inner(flags, &mut Rules { perms }) { - Ok(_changed) => {}, + Ok(_changed) => {} Err(msg) => { panic!("{}", msg); - }, + } } } } - struct TrackedSlice<'a, T> { xs: &'a mut [T], dirty: Vec, @@ -297,14 +284,12 @@ impl<'a, T: PartialEq> TrackedSlice<'a, T> { } } - trait PropagateRules { fn subset(&mut self, a_ptr: PointerId, a_val: &T, b_ptr: PointerId, b_val: &T) -> (T, T); fn all_perms(&mut self, ptr: PointerId, perms: PermissionSet, val: &T) -> T; fn no_perms(&mut self, ptr: PointerId, perms: PermissionSet, val: &T) -> T; } - pub fn generate_constraints<'tcx>( acx: &AnalysisCtxt<'tcx>, mir: &Body<'tcx>, diff --git a/c2rust-analyze/src/dataflow/type_check.rs b/c2rust-analyze/src/dataflow/type_check.rs index 03ad1551fa..f2de458150 100644 --- a/c2rust-analyze/src/dataflow/type_check.rs +++ b/c2rust-analyze/src/dataflow/type_check.rs @@ -1,13 +1,12 @@ +use super::DataflowConstraints; +use crate::context::{AnalysisCtxt, LTy, PermissionSet, PointerId}; +use crate::util::{self, describe_rvalue, Callee, RvalueDesc}; +use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext}; use rustc_middle::mir::{ - Body, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, BinOp, Place, PlaceRef, - Operand, ProjectionElem, Mutability, + BinOp, Body, Mutability, Operand, Place, PlaceRef, ProjectionElem, Rvalue, Statement, + StatementKind, Terminator, TerminatorKind, }; -use rustc_middle::mir::visit::{PlaceContext, NonMutatingUseContext, MutatingUseContext}; use rustc_middle::ty::TyKind; -use crate::context::{PermissionSet, PointerId, AnalysisCtxt, LTy}; -use crate::util::{self, describe_rvalue, RvalueDesc, Callee}; -use super::DataflowConstraints; - /// Visitor that walks over the MIR, computing types of rvalues/operands/places and generating /// constraints as a side effect. @@ -30,11 +29,12 @@ impl<'tcx> TypeChecker<'tcx, '_> { } match mutbl { Mutability::Mut => { - self.constraints.add_all_perms(ptr, PermissionSet::READ | PermissionSet::WRITE); - }, + self.constraints + .add_all_perms(ptr, PermissionSet::READ | PermissionSet::WRITE); + } Mutability::Not => { self.constraints.add_all_perms(ptr, PermissionSet::READ); - }, + } } } @@ -57,15 +57,13 @@ impl<'tcx> TypeChecker<'tcx, '_> { prev_deref_ptr = Some(lty.label); assert_eq!(lty.args.len(), 1); lty = lty.args[0]; - }, + } - ProjectionElem::Field(f, _field_ty) => { - match lty.ty.kind() { - TyKind::Tuple(..) => { - lty = lty.args[f.as_usize()]; - }, - _ => todo!("field of {:?}", lty), + ProjectionElem::Field(f, _field_ty) => match lty.ty.kind() { + TyKind::Tuple(..) => { + lty = lty.args[f.as_usize()]; } + _ => todo!("field of {:?}", lty), }, ref proj => panic!("unsupported projection {:?} in {:?}", proj, pl), @@ -76,11 +74,11 @@ impl<'tcx> TypeChecker<'tcx, '_> { match ctx { PlaceContext::NonMutatingUse(..) => { self.record_access(ptr, Mutability::Not); - }, + } PlaceContext::MutatingUse(..) => { self.record_access(ptr, Mutability::Mut); - }, - PlaceContext::NonUse(..) => {}, + } + PlaceContext::NonUse(..) => {} } } @@ -95,10 +93,8 @@ impl<'tcx> TypeChecker<'tcx, '_> { let ctx = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); let base_ty = self.visit_place_ref(base, ctx); base_ty.label - }, - Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => { - self.acx.addr_of_local[local] - }, + } + Some(RvalueDesc::AddrOfLocal { local, proj: _ }) => self.acx.addr_of_local[local], None => match *rv { Rvalue::Use(ref op) => self.visit_operand(op).label, Rvalue::BinaryOp(BinOp::Offset, _) => todo!("visit_rvalue BinOp::Offset"), @@ -108,30 +104,27 @@ impl<'tcx> TypeChecker<'tcx, '_> { Rvalue::Cast(_, _, ty) => { assert!(!matches!(ty.kind(), TyKind::RawPtr(..) | TyKind::Ref(..))); PointerId::NONE - }, + } _ => panic!("TODO: handle assignment of {:?}", rv), }, - } + } } - pub fn visit_operand( - &mut self, - op: &Operand<'tcx>, - ) -> LTy<'tcx> { + pub fn visit_operand(&mut self, op: &Operand<'tcx>) -> LTy<'tcx> { match *op { Operand::Copy(pl) => { let ctx = PlaceContext::NonMutatingUse(NonMutatingUseContext::Copy); self.visit_place(pl, ctx) - }, + } Operand::Move(pl) => { let ctx = PlaceContext::NonMutatingUse(NonMutatingUseContext::Move); self.visit_place(pl, ctx) - }, + } Operand::Constant(ref c) => { let ty = c.ty(); // TODO self.acx.lcx.label(ty, &mut |_| PointerId::NONE) - }, + } } } @@ -155,8 +148,8 @@ impl<'tcx> TypeChecker<'tcx, '_> { let rv_ptr = self.visit_rvalue(rv); self.do_assign(pl_ptr, rv_ptr); - }, - _ => {}, + } + _ => {} } } @@ -164,7 +157,12 @@ impl<'tcx> TypeChecker<'tcx, '_> { eprintln!("visit_terminator({:?})", term.kind); let tcx = self.acx.tcx; match term.kind { - TerminatorKind::Call { ref func, ref args, destination, .. } => { + TerminatorKind::Call { + ref func, + ref args, + destination, + .. + } => { let func_ty = func.ty(self.mir, tcx); eprintln!("callee = {:?}", util::ty_callee(tcx, func_ty)); match util::ty_callee(tcx, func_ty) { @@ -180,19 +178,16 @@ impl<'tcx> TypeChecker<'tcx, '_> { self.do_assign(pl_lty.label, rv_lty.label); let perms = PermissionSet::OFFSET_ADD | PermissionSet::OFFSET_SUB; self.constraints.add_all_perms(rv_lty.label, perms); - }, - None => {}, + } + None => {} } - }, - _ => {}, + } + _ => {} } } } -pub fn visit<'tcx>( - acx: &AnalysisCtxt<'tcx>, - mir: &Body<'tcx>, -) -> DataflowConstraints { +pub fn visit<'tcx>(acx: &AnalysisCtxt<'tcx>, mir: &Body<'tcx>) -> DataflowConstraints { let mut tc = TypeChecker { acx, mir, @@ -208,5 +203,3 @@ pub fn visit<'tcx>( tc.constraints } - - diff --git a/c2rust-analyze/src/expr_rewrite.rs b/c2rust-analyze/src/expr_rewrite.rs index 217bfa4c03..e3900d09da 100644 --- a/c2rust-analyze/src/expr_rewrite.rs +++ b/c2rust-analyze/src/expr_rewrite.rs @@ -1,12 +1,11 @@ +use crate::context::{AnalysisCtxt, FlagSet, LTy, PermissionSet, PointerId}; +use crate::type_desc::{self, Ownership, Quantity}; +use crate::util::{self, Callee}; use rustc_middle::mir::{ - Body, BasicBlock, Location, Statement, StatementKind, Terminator, TerminatorKind, Rvalue, - Operand, + BasicBlock, Body, Location, Operand, Rvalue, Statement, StatementKind, Terminator, + TerminatorKind, }; use rustc_span::{Span, DUMMY_SP}; -use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; -use crate::type_desc::{self, Ownership, Quantity}; -use crate::util::{self, Callee}; - #[derive(Clone, PartialEq, Eq, Debug)] pub struct ExprLoc { @@ -47,7 +46,6 @@ pub struct ExprRewrite { pub kinds: Vec, } - struct ExprRewriteVisitor<'a, 'tcx> { acx: &'a AnalysisCtxt<'tcx>, perms: &'a [PermissionSet], @@ -66,7 +64,11 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { mir: &'a Body<'tcx>, ) -> ExprRewriteVisitor<'a, 'tcx> { ExprRewriteVisitor { - acx, perms, flags, rewrites, mir, + acx, + perms, + flags, + rewrites, + mir, loc: ExprLoc { stmt: Location { block: BasicBlock::from_usize(0), @@ -112,7 +114,6 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { self.enter(SubLoc::PlacePointer(i), f) } - fn visit_statement(&mut self, stmt: &Statement<'tcx>, loc: Location) { self.loc = ExprLoc { stmt: loc, @@ -126,16 +127,16 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { let pl_ty = self.acx.type_of(pl); self.enter_assign_rvalue(|v| v.visit_rvalue(rv, pl_ty)); // TODO: visit place - }, - StatementKind::FakeRead(..) => {}, + } + StatementKind::FakeRead(..) => {} StatementKind::SetDiscriminant { .. } => todo!("statement {:?}", stmt), - StatementKind::StorageLive(..) => {}, - StatementKind::StorageDead(..) => {}, - StatementKind::Retag(..) => {}, - StatementKind::AscribeUserType(..) => {}, - StatementKind::Coverage(..) => {}, + StatementKind::StorageLive(..) => {} + StatementKind::StorageDead(..) => {} + StatementKind::Retag(..) => {} + StatementKind::AscribeUserType(..) => {} + StatementKind::Coverage(..) => {} StatementKind::CopyNonOverlapping(..) => todo!("statement {:?}", stmt), - StatementKind::Nop => {}, + StatementKind::Nop => {} } } @@ -148,15 +149,20 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { }; match term.kind { - TerminatorKind::Goto { .. } => {}, - TerminatorKind::SwitchInt { .. } => {}, - TerminatorKind::Resume => {}, - TerminatorKind::Abort => {}, - TerminatorKind::Return => {}, - TerminatorKind::Unreachable => {}, - TerminatorKind::Drop { .. } => {}, - TerminatorKind::DropAndReplace { .. } => {}, - TerminatorKind::Call { ref func, ref args, destination, .. } => { + TerminatorKind::Goto { .. } => {} + TerminatorKind::SwitchInt { .. } => {} + TerminatorKind::Resume => {} + TerminatorKind::Abort => {} + TerminatorKind::Return => {} + TerminatorKind::Unreachable => {} + TerminatorKind::Drop { .. } => {} + TerminatorKind::DropAndReplace { .. } => {} + TerminatorKind::Call { + ref func, + ref args, + destination, + .. + } => { let func_ty = func.ty(self.mir, tcx); let pl_ty = destination.map(|(pl, _)| self.acx.type_of(pl)); @@ -167,7 +173,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Callee::PtrOffset { .. } => { self.visit_ptr_offset(&args[0], pl_ty); return; - }, + } } } @@ -178,7 +184,7 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { for (i, _op) in args.iter().enumerate() { if i >= sig.inputs().len() { // This is a call to a variadic function, and we've gone past the end of - // the declared arguments. + // the declared arguments. // TODO: insert a cast to turn `op` back into its original declared type // (i.e. upcast the chosen reference type back to a raw pointer) continue; @@ -188,12 +194,12 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { // let expect_ty = ...; // self.enter_call_arg(i, |v| v.visit_operand(op, expect_ty)); } - }, - TerminatorKind::Assert { .. } => {}, - TerminatorKind::Yield { .. } => {}, - TerminatorKind::GeneratorDrop => {}, - TerminatorKind::FalseEdge { .. } => {}, - TerminatorKind::FalseUnwind { .. } => {}, + } + TerminatorKind::Assert { .. } => {} + TerminatorKind::Yield { .. } => {} + TerminatorKind::GeneratorDrop => {} + TerminatorKind::FalseEdge { .. } => {} + TerminatorKind::FalseUnwind { .. } => {} TerminatorKind::InlineAsm { .. } => todo!("terminator {:?}", term), } } @@ -203,59 +209,58 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { match *rv { Rvalue::Use(ref op) => { self.enter_rvalue_operand(0, |v| v.visit_operand(op, expect_ty)); - }, + } Rvalue::Repeat(ref _op, _) => { // TODO - }, + } Rvalue::Ref(_rg, _kind, _pl) => { // TODO - }, + } Rvalue::ThreadLocalRef(_def_id) => { // TODO - }, + } Rvalue::AddressOf(_mutbl, _pl) => { // TODO - }, + } Rvalue::Len(_pl) => { // TODO - }, + } Rvalue::Cast(_kind, ref _op, _ty) => { // TODO - }, + } Rvalue::BinaryOp(_bop, ref _ops) => { // TODO - }, + } Rvalue::CheckedBinaryOp(_bop, ref _ops) => { // TODO - }, - Rvalue::NullaryOp(..) => {}, + } + Rvalue::NullaryOp(..) => {} Rvalue::UnaryOp(_uop, ref _op) => { // TODO - }, + } Rvalue::Discriminant(_pl) => { // TODO - }, + } Rvalue::Aggregate(ref _kind, ref _ops) => { // TODO - }, + } Rvalue::ShallowInitBox(ref _op, _ty) => { // TODO - }, + } } } fn visit_operand(&mut self, op: &Operand<'tcx>, expect_ty: LTy<'tcx>) { match *op { - Operand::Copy(pl) | - Operand::Move(pl) => { + Operand::Copy(pl) | Operand::Move(pl) => { if let Some(ptr) = self.acx.ptr_of(pl) { let expect_ptr = expect_ty.label; self.emit_ptr_cast(ptr, expect_ptr); } // TODO: walk over `pl` to handle all derefs (casts, `*x` -> `(*x).get()`) - }, - Operand::Constant(..) => {}, + } + Operand::Constant(..) => {} } } @@ -266,15 +271,14 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { expect_qty: Quantity, ) { match *op { - Operand::Copy(pl) | - Operand::Move(pl) => { + Operand::Copy(pl) | Operand::Move(pl) => { if let Some(ptr) = self.acx.ptr_of(pl) { self.emit_cast(ptr, expect_own, expect_qty); } // TODO: walk over `pl` to handle all derefs (casts, `*x` -> `(*x).get()`) - }, - Operand::Constant(..) => {}, + } + Operand::Constant(..) => {} } } @@ -282,7 +286,9 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { // Compute the expected type for the argument, and emit a cast if needed. let result_ptr = result_ty.label; let (result_own, result_qty) = type_desc::perms_to_desc( - self.perms[result_ptr.index()], self.flags[result_ptr.index()]); + self.perms[result_ptr.index()], + self.flags[result_ptr.index()], + ); let arg_expect_own = result_own; // TODO: infer `arg_expect_qty` based on the type of offset this is (positive / unknown) @@ -292,7 +298,9 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { Quantity::OffsetPtr => todo!("OffsetPtr"), }; - self.enter_call_arg(0, |v| v.visit_operand_desc(op, arg_expect_own, arg_expect_qty)); + self.enter_call_arg(0, |v| { + v.visit_operand_desc(op, arg_expect_own, arg_expect_qty) + }); // Emit `OffsetSlice` for the offset itself. let mutbl = match result_own { @@ -308,7 +316,6 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { } } - fn emit(&mut self, rw: RewriteKind) { if let Some(er) = self.rewrites.last_mut() { if er.loc == self.loc { @@ -327,7 +334,9 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { assert!(expect_ptr != PointerId::NONE); let (own2, qty2) = type_desc::perms_to_desc( - self.perms[expect_ptr.index()], self.flags[expect_ptr.index()]); + self.perms[expect_ptr.index()], + self.flags[expect_ptr.index()], + ); self.emit_cast(ptr, own2, qty2); } @@ -335,8 +344,8 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { fn emit_cast(&mut self, ptr: PointerId, expect_own: Ownership, expect_qty: Quantity) { assert!(ptr != PointerId::NONE); - let (own1, qty1) = type_desc::perms_to_desc( - self.perms[ptr.index()], self.flags[ptr.index()]); + let (own1, qty1) = + type_desc::perms_to_desc(self.perms[ptr.index()], self.flags[ptr.index()]); let (own2, qty2) = (expect_own, expect_qty); if (own1, qty1) == (own2, qty2) { @@ -348,12 +357,15 @@ impl<'a, 'tcx> ExprRewriteVisitor<'a, 'tcx> { return; } - eprintln!("unsupported cast kind: {:?} {:?} -> {:?}", - self.perms[ptr.index()], (own1, qty1), (own2, qty2)); + eprintln!( + "unsupported cast kind: {:?} {:?} -> {:?}", + self.perms[ptr.index()], + (own1, qty1), + (own2, qty2) + ); } } - pub fn gen_expr_rewrites<'tcx>( acx: &AnalysisCtxt<'tcx>, perms: &[PermissionSet], @@ -370,12 +382,18 @@ pub fn gen_expr_rewrites<'tcx>( for (bb_id, bb) in mir.basic_blocks().iter_enumerated() { for (i, stmt) in bb.statements.iter().enumerate() { - let loc = Location { block: bb_id, statement_index: i }; + let loc = Location { + block: bb_id, + statement_index: i, + }; v.visit_statement(stmt, loc); } if let Some(ref term) = bb.terminator { - let loc = Location { block: bb_id, statement_index: bb.statements.len() }; + let loc = Location { + block: bb_id, + statement_index: bb.statements.len(), + }; v.visit_terminator(term, loc); } } diff --git a/c2rust-analyze/src/labeled_ty.rs b/c2rust-analyze/src/labeled_ty.rs index e6c5e3a7f4..5d96bc92ee 100644 --- a/c2rust-analyze/src/labeled_ty.rs +++ b/c2rust-analyze/src/labeled_ty.rs @@ -2,13 +2,13 @@ //! use this, for example, to attach a Polonius `Origin` to every reference type. Labeled type //! data is manipulated by reference, the same as with `Ty`s, and the data is stored in the same //! arena as the underlying `Ty`s. +use rustc_arena::DroplessArena; +use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; +use rustc_middle::ty::{Ty, TyCtxt, TyKind, TypeAndMut}; use std::convert::TryInto; use std::fmt; use std::marker::PhantomData; use std::ops::Deref; -use rustc_arena::DroplessArena; -use rustc_middle::ty::{TyCtxt, Ty, TyKind, TypeAndMut}; -use rustc_middle::ty::subst::{GenericArg, GenericArgKind}; /// The actual data for a labeled type. /// @@ -99,20 +99,12 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { args: &'tcx [LabeledTy<'tcx, L>], label: L, ) -> LabeledTy<'tcx, L> { - self.arena().alloc(LabeledTyS { - ty, - args, - label, - }) + self.arena().alloc(LabeledTyS { ty, args, label }) } /// Label a `Ty` using a callback. The callback runs at every type constructor to produce a /// label for that node in the tree. - pub fn label) -> L>( - &self, - ty: Ty<'tcx>, - f: &mut F, - ) -> LabeledTy<'tcx, L> { + pub fn label) -> L>(&self, ty: Ty<'tcx>, f: &mut F) -> LabeledTy<'tcx, L> { use rustc_middle::ty::TyKind::*; let label = f(ty); match ty.kind() { @@ -159,22 +151,18 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { self.mk(ty, self.mk_slice(&args), label) } Tuple(ref elems) => { - let args = elems.types().map(|ty| self.label(ty, f)).collect::>(); + let args = elems + .types() + .map(|ty| self.label(ty, f)) + .collect::>(); self.mk(ty, self.mk_slice(&args), label) } // Types that aren't actually supported by this code yet - Dynamic(..) - | Closure(..) - | Generator(..) - | GeneratorWitness(..) - | Projection(..) - | Opaque(..) - | Param(..) - | Bound(..) - | Placeholder(..) - | Infer(..) - | Error(..) => self.mk(ty, &[], label), + Dynamic(..) | Closure(..) | Generator(..) | GeneratorWitness(..) | Projection(..) + | Opaque(..) | Param(..) | Bound(..) | Placeholder(..) | Infer(..) | Error(..) => { + self.mk(ty, &[], label) + } } } @@ -229,11 +217,7 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { } /// Run a callback to replace the labels on a type. - pub fn relabel( - &self, - lty: LabeledTy<'tcx, L2>, - func: &mut F, - ) -> LabeledTy<'tcx, L> + pub fn relabel(&self, lty: LabeledTy<'tcx, L2>, func: &mut F) -> LabeledTy<'tcx, L> where F: FnMut(LabeledTy<'tcx, L2>) -> L, { @@ -260,28 +244,32 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { /// Perform a bottom-up rewrite on a type and convert it to unlabeled form. pub fn rewrite_unlabeled(&self, lty: LabeledTy<'tcx, L>, func: &mut F) -> Ty<'tcx> - where F: FnMut(Ty<'tcx>, &[Ty<'tcx>], L) -> Ty<'tcx> { + where + F: FnMut(Ty<'tcx>, &[Ty<'tcx>], L) -> Ty<'tcx>, + { use rustc_middle::ty::TyKind::*; - let args = lty.args.iter() + let args = lty + .args + .iter() .map(|<y| self.rewrite_unlabeled(lty, func)) .collect::>(); let ty = match *lty.ty.kind() { - Bool | Char | Int(_) | Uint(_) | Float(_) | Str | Foreign(_) | Never => { - lty.ty - }, + Bool | Char | Int(_) | Uint(_) | Float(_) | Str | Foreign(_) | Never => lty.ty, Adt(adt, substs) => { // Copy `substs`, but replace all types with those from `args`. let mut it = args.iter().cloned(); - let substs = self.tcx.mk_substs(substs.iter().map(|arg| match arg.unpack() { - GenericArgKind::Type(_) => it.next().unwrap().into(), - GenericArgKind::Lifetime(rg) => GenericArg::from(rg), - GenericArgKind::Const(cn) => GenericArg::from(cn), - })); + let substs = self + .tcx + .mk_substs(substs.iter().map(|arg| match arg.unpack() { + GenericArgKind::Type(_) => it.next().unwrap().into(), + GenericArgKind::Lifetime(rg) => GenericArg::from(rg), + GenericArgKind::Const(cn) => GenericArg::from(cn), + })); assert!(it.next().is_none()); self.tcx.mk_adt(adt, substs) - }, + } Array(_, len) => { let &[elem]: &[_; 1] = args[..].try_into().unwrap(); self.tcx.mk_ty(Array(elem, len)) @@ -292,7 +280,10 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { } RawPtr(mty) => { let &[target]: &[_; 1] = args[..].try_into().unwrap(); - self.tcx.mk_ptr(TypeAndMut { ty: target, mutbl: mty.mutbl }) + self.tcx.mk_ptr(TypeAndMut { + ty: target, + mutbl: mty.mutbl, + }) } Ref(rg, _, mutbl) => { let &[target]: &[_; 1] = args[..].try_into().unwrap(); @@ -301,11 +292,13 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { FnDef(def_id, substs) => { // Copy `substs`, but replace all types with those from `args`. let mut it = args.iter().cloned(); - let substs = self.tcx.mk_substs(substs.iter().map(|arg| match arg.unpack() { - GenericArgKind::Type(_) => it.next().unwrap().into(), - GenericArgKind::Lifetime(rg) => GenericArg::from(rg), - GenericArgKind::Const(cn) => GenericArg::from(cn), - })); + let substs = self + .tcx + .mk_substs(substs.iter().map(|arg| match arg.unpack() { + GenericArgKind::Type(_) => it.next().unwrap().into(), + GenericArgKind::Lifetime(rg) => GenericArg::from(rg), + GenericArgKind::Const(cn) => GenericArg::from(cn), + })); assert!(it.next().is_none()); self.tcx.mk_fn_def(def_id, substs) } @@ -313,22 +306,13 @@ impl<'tcx, L: Copy> LabeledTyCtxt<'tcx, L> { // TODO: replace all the types under the binder todo!() } - Tuple(_) => { - self.tcx.mk_tup(args.iter().cloned()) - } + Tuple(_) => self.tcx.mk_tup(args.iter().cloned()), // Types that aren't actually supported by this code yet - Dynamic(..) - | Closure(..) - | Generator(..) - | GeneratorWitness(..) - | Projection(..) - | Opaque(..) - | Param(..) - | Bound(..) - | Placeholder(..) - | Infer(..) - | Error(..) => lty.ty, + Dynamic(..) | Closure(..) | Generator(..) | GeneratorWitness(..) | Projection(..) + | Opaque(..) | Param(..) | Bound(..) | Placeholder(..) | Infer(..) | Error(..) => { + lty.ty + } }; func(ty, &args, lty.label) diff --git a/c2rust-analyze/src/main.rs b/c2rust-analyze/src/main.rs index df5626d86b..761fcb0191 100644 --- a/c2rust-analyze/src/main.rs +++ b/c2rust-analyze/src/main.rs @@ -13,16 +13,15 @@ extern crate rustc_session; extern crate rustc_span; extern crate rustc_target; -use std::env; +use crate::context::{AnalysisCtxt, FlagSet, LTy, PermissionSet, PointerId}; use rustc_index::vec::IndexVec; -use rustc_middle::mir::{Body, LocalDecl, LocalInfo, BindingForm}; -use rustc_middle::ty::{TyCtxt, Ty, TyKind, WithOptConstParam}; -use rustc_middle::ty::query::{Providers, ExternProviders}; +use rustc_middle::mir::{BindingForm, Body, LocalDecl, LocalInfo}; +use rustc_middle::ty::query::{ExternProviders, Providers}; +use rustc_middle::ty::{Ty, TyCtxt, TyKind, WithOptConstParam}; use rustc_session::Session; -use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use crate::context::{AnalysisCtxt, PointerId, PermissionSet, FlagSet, LTy}; - +use rustc_span::Span; +use std::env; mod borrowck; mod context; @@ -32,12 +31,7 @@ mod labeled_ty; mod type_desc; mod util; - -fn inspect_mir<'tcx>( - tcx: TyCtxt<'tcx>, - def: WithOptConstParam, - mir: &Body<'tcx>, -) { +fn inspect_mir<'tcx>(tcx: TyCtxt<'tcx>, def: WithOptConstParam, mir: &Body<'tcx>) { let name = tcx.item_name(def.to_global().did); eprintln!("\nprocessing function {:?}", name); @@ -60,7 +54,7 @@ fn inspect_mir<'tcx>( let dataflow = self::dataflow::generate_constraints(&acx, mir); let mut hypothesis = Vec::with_capacity(acx.num_pointers()); - for _ in 0 .. acx.num_pointers() { + for _ in 0..acx.num_pointers() { hypothesis.push(PermissionSet::UNIQUE); } dataflow.propagate(&mut hypothesis); @@ -70,7 +64,6 @@ fn inspect_mir<'tcx>( let mut flags = vec![FlagSet::empty(); acx.num_pointers()]; dataflow.propagate_cell(&hypothesis, &mut flags); - eprintln!("final labeling for {:?}:", name); let lcx1 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); let lcx2 = crate::labeled_ty::LabeledTyCtxt::new(acx.tcx); @@ -83,7 +76,8 @@ fn inspect_mir<'tcx>( hypothesis[lty.label.index()] } }); - eprintln!("{:?} ({}): addr_of = {:?}, type = {:?}", + eprintln!( + "{:?} ({}): addr_of = {:?}, type = {:?}", local, describe_local(acx.tcx, decl), addr_of1, @@ -98,7 +92,8 @@ fn inspect_mir<'tcx>( flags[lty.label.index()] } }); - eprintln!("{:?} ({}): addr_of flags = {:?}, type flags = {:?}", + eprintln!( + "{:?} ({}): addr_of flags = {:?}, type flags = {:?}", local, describe_local(acx.tcx, decl), addr_of2, @@ -110,11 +105,7 @@ fn inspect_mir<'tcx>( for (local, decl) in mir.local_decls.iter_enumerated() { // TODO: apply `Cell` if `addr_of_local` indicates it's needed let ty = type_desc::convert_type(&acx, acx.local_tys[local], &hypothesis, &flags); - eprintln!("{:?} ({}): {:?}", - local, - describe_local(acx.tcx, decl), - ty, - ); + eprintln!("{:?} ({}): {:?}", local, describe_local(acx.tcx, decl), ty,); } eprintln!(""); @@ -122,7 +113,9 @@ fn inspect_mir<'tcx>( for rw in &rewrites { eprintln!( "at {:?} ({}, {:?}):", - rw.loc.stmt, describe_span(tcx, rw.loc.span), rw.loc.sub, + rw.loc.stmt, + describe_span(tcx, rw.loc.span), + rw.loc.sub, ); for kind in &rw.kinds { eprintln!(" {:?}", kind); @@ -130,13 +123,9 @@ fn inspect_mir<'tcx>( } } -fn assign_pointer_ids<'tcx>( - acx: &AnalysisCtxt<'tcx>, - ty: Ty<'tcx>, -) -> LTy<'tcx> { +fn assign_pointer_ids<'tcx>(acx: &AnalysisCtxt<'tcx>, ty: Ty<'tcx>) -> LTy<'tcx> { acx.lcx.label(ty, &mut |ty| match ty.kind() { - TyKind::Ref(_, _, _) | - TyKind::RawPtr(_) => acx.new_pointer(), + TyKind::Ref(_, _, _) | TyKind::RawPtr(_) => acx.new_pointer(), _ => PointerId::NONE, }) } @@ -168,7 +157,7 @@ fn describe_span(tcx: TyCtxt, span: Span) -> String { }; let (src1, src2, src3) = if s.len() > 20 { - (&s[..15], " ... ", &s[s.len() - 5 ..]) + (&s[..15], " ... ", &s[s.len() - 5..]) } else { (&s[..], "", "") }; @@ -176,7 +165,6 @@ fn describe_span(tcx: TyCtxt, span: Span) -> String { format!("{}: {}{}{}", line, src1, src2, src3) } - struct AnalysisCallbacks; impl rustc_driver::Callbacks for AnalysisCallbacks { diff --git a/c2rust-analyze/src/type_desc.rs b/c2rust-analyze/src/type_desc.rs index dab22e9fa5..daf12963c0 100644 --- a/c2rust-analyze/src/type_desc.rs +++ b/c2rust-analyze/src/type_desc.rs @@ -1,7 +1,7 @@ +use crate::context::{AnalysisCtxt, FlagSet, LTy, PermissionSet, PointerId}; use rustc_hir::def::{DefKind, Res}; -use rustc_middle::ty::{Ty, TyCtxt, ReErased}; use rustc_middle::ty::subst::GenericArg; -use crate::context::{PermissionSet, FlagSet, AnalysisCtxt, LTy, PointerId}; +use rustc_middle::ty::{ReErased, Ty, TyCtxt}; #[allow(dead_code)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] @@ -33,10 +33,7 @@ pub enum Quantity { OffsetPtr, } -pub fn perms_to_desc( - perms: PermissionSet, - flags: FlagSet, -) -> (Ownership, Quantity) { +pub fn perms_to_desc(perms: PermissionSet, flags: FlagSet) -> (Ownership, Quantity) { let own = if perms.contains(PermissionSet::UNIQUE | PermissionSet::WRITE) { Ownership::Mut } else if flags.contains(FlagSet::CELL) { @@ -60,11 +57,16 @@ pub fn perms_to_desc( } fn mk_cell<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { - let core_crate = tcx.crates(()).iter().cloned() + let core_crate = tcx + .crates(()) + .iter() + .cloned() .find(|&krate| tcx.crate_name(krate).as_str() == "core") .expect("failed to find crate `core`"); - let cell_mod_child = tcx.module_children(core_crate.as_def_id()).iter() + let cell_mod_child = tcx + .module_children(core_crate.as_def_id()) + .iter() .find(|child| child.ident.as_str() == "cell") .expect("failed to find module `core::cell`"); let cell_mod = match cell_mod_child.res { @@ -72,7 +74,9 @@ fn mk_cell<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { ref r => panic!("unexpected resolution {:?} for `core::cell`", r), }; - let cell_struct_child = tcx.module_children(cell_mod).iter() + let cell_struct_child = tcx + .module_children(cell_mod) + .iter() .find(|child| child.ident.as_str() == "Cell") .expect("failed to find struct `core::cell::Cell`"); let cell_struct = match cell_struct_child.res { diff --git a/c2rust-analyze/src/util.rs b/c2rust-analyze/src/util.rs index 8861df81e7..0780520905 100644 --- a/c2rust-analyze/src/util.rs +++ b/c2rust-analyze/src/util.rs @@ -1,6 +1,6 @@ use rustc_hir::def::DefKind; -use rustc_middle::mir::{PlaceRef, PlaceElem, Rvalue, Operand, Local, Mutability}; -use rustc_middle::ty::{TyCtxt, Ty, TyKind, DefIdTree}; +use rustc_middle::mir::{Local, Mutability, Operand, PlaceElem, PlaceRef, Rvalue}; +use rustc_middle::ty::{DefIdTree, Ty, TyCtxt, TyKind}; #[derive(Debug)] pub enum RvalueDesc<'tcx> { @@ -17,17 +17,18 @@ pub enum RvalueDesc<'tcx> { pub fn describe_rvalue<'tcx>(rv: &Rvalue<'tcx>) -> Option> { Some(match *rv { Rvalue::Use(ref op) => match *op { - Operand::Move(pl) | - Operand::Copy(pl) => RvalueDesc::Project { + Operand::Move(pl) | Operand::Copy(pl) => RvalueDesc::Project { base: pl.as_ref(), proj: &[], }, Operand::Constant(_) => return None, }, - Rvalue::Ref(_, _, pl) | - Rvalue::AddressOf(_, pl) => { + Rvalue::Ref(_, _, pl) | Rvalue::AddressOf(_, pl) => { let projection = &pl.projection[..]; - match projection.iter().rposition(|p| matches!(p, PlaceElem::Deref)) { + match projection + .iter() + .rposition(|p| matches!(p, PlaceElem::Deref)) + { Some(i) => { // `i` is the index of the last `ProjectionElem::Deref` in `pl`. RvalueDesc::Project { @@ -35,26 +36,28 @@ pub fn describe_rvalue<'tcx>(rv: &Rvalue<'tcx>) -> Option> { local: pl.local, projection: &projection[..i], }, - proj: &projection[i + 1 ..], + proj: &projection[i + 1..], } - }, + } None => { // `pl` refers to a field/element of a local. RvalueDesc::AddrOfLocal { local: pl.local, proj: projection, } - }, + } } - }, + } _ => return None, }) } - #[derive(Debug)] pub enum Callee<'tcx> { - PtrOffset { pointee_ty: Ty<'tcx>, mutbl: Mutability }, + PtrOffset { + pointee_ty: Ty<'tcx>, + mutbl: Mutability, + }, } pub fn ty_callee<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> { @@ -80,7 +83,7 @@ pub fn ty_callee<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option> _ => return None, }; Some(Callee::PtrOffset { pointee_ty, mutbl }) - }, + } _ => None, } } diff --git a/c2rust-analyze/tests/filecheck.rs b/c2rust-analyze/tests/filecheck.rs index 3deb16a226..a8b0631414 100644 --- a/c2rust-analyze/tests/filecheck.rs +++ b/c2rust-analyze/tests/filecheck.rs @@ -66,19 +66,20 @@ fn filecheck() { eprintln!("{:?}", entry.path()); let mut filecheck_cmd = Command::new(&filecheck_bin); - filecheck_cmd - .arg(entry.path()) - .stdin(Stdio::piped()); + filecheck_cmd.arg(entry.path()).stdin(Stdio::piped()); let mut filecheck = filecheck_cmd.spawn().unwrap(); let pipe_fd = filecheck.stdin.as_ref().unwrap().as_raw_fd(); let mut analyze_cmd = Command::new("cargo"); analyze_cmd .arg("run") - .arg("--manifest-path").arg(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))) + .arg("--manifest-path") + .arg(format!("{}/Cargo.toml", env!("CARGO_MANIFEST_DIR"))) .arg("--") .arg(entry.path()) - .arg("-L").arg(lib_dir) - .arg("--crate-type").arg("rlib") + .arg("-L") + .arg(lib_dir) + .arg("--crate-type") + .arg("rlib") .stdout(unsafe { Stdio::from_raw_fd(pipe_fd) }) .stderr(unsafe { Stdio::from_raw_fd(pipe_fd) }); let mut analyze = analyze_cmd.spawn().unwrap(); @@ -87,14 +88,16 @@ fn filecheck() { assert!( filecheck_status.success(), "{:?}: FileCheck failed with status {:?}", - entry.path(), filecheck_status, + entry.path(), + filecheck_status, ); let analyze_status = analyze.wait().unwrap(); assert!( analyze_status.success(), "{:?}: c2rust-analyze failed with status {:?}", - entry.path(), analyze_status, + entry.path(), + analyze_status, ); } }