Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

analyze: allow overriding dataflow for specific permissions #1088

Merged
merged 4 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 69 additions & 13 deletions c2rust-analyze/src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,8 @@ struct FuncInfo<'tcx> {
/// get a complete [`Assignment`] for this function, which maps every [`PointerId`] in this
/// function to a [`PermissionSet`] and [`FlagSet`].
lasn: MaybeUnset<LocalAssignment>,
/// Local part of the `updates_forbidden` mask.
l_updates_forbidden: MaybeUnset<LocalPointerTable<PermissionSet>>,
ahomescu marked this conversation as resolved.
Show resolved Hide resolved
/// Constraints on pointee types gathered from the body of this function.
pointee_constraints: MaybeUnset<pointee_type::ConstraintSet<'tcx>>,
/// Local part of pointee type sets.
Expand Down Expand Up @@ -872,6 +874,8 @@ fn run(tcx: TyCtxt) {
const INITIAL_FLAGS: FlagSet = FlagSet::empty();

let mut gasn = GlobalAssignment::new(gacx.num_pointers(), INITIAL_PERMS, INITIAL_FLAGS);
let mut g_updates_forbidden = GlobalPointerTable::new(gacx.num_pointers());

for (ptr, &info) in gacx.ptr_info().iter() {
if should_make_fixed(info) {
gasn.flags[ptr].insert(FlagSet::FIXED);
Expand All @@ -897,6 +901,7 @@ fn run(tcx: TyCtxt) {
for info in func_info.values_mut() {
let num_pointers = info.acx_data.num_pointers();
let mut lasn = LocalAssignment::new(num_pointers, INITIAL_PERMS, INITIAL_FLAGS);
let l_updates_forbidden = LocalPointerTable::new(num_pointers);

for (ptr, &info) in info.acx_data.local_ptr_info().iter() {
if should_make_fixed(info) {
Expand All @@ -905,6 +910,7 @@ fn run(tcx: TyCtxt) {
}

info.lasn.set(lasn);
info.l_updates_forbidden.set(l_updates_forbidden);
}

// Load permission info from PDG
Expand Down Expand Up @@ -1082,18 +1088,14 @@ fn run(tcx: TyCtxt) {
// Run dataflow solver and borrowck analysis
// ----------------------------------

// For testing, putting #[c2rust_analyze_test::fail_before_analysis] on a function marks it as
// failed at this point.
for &ldid in &all_fn_ldids {
if !util::has_test_attr(tcx, ldid, TestAttr::FailBeforeAnalysis) {
continue;
}
gacx.mark_fn_failed(
ldid.to_def_id(),
DontRewriteFnReason::FAKE_INVALID_FOR_TESTING,
PanicDetail::new("explicit fail_before_analysis for testing".to_owned()),
);
}
apply_test_attr_fail_before_analysis(&mut gacx, &all_fn_ldids);
apply_test_attr_force_non_null_args(
&mut gacx,
&all_fn_ldids,
&mut func_info,
&mut gasn,
&mut g_updates_forbidden,
);

eprintln!("=== ADT Metadata ===");
eprintln!("{:?}", gacx.adt_metadata);
Expand Down Expand Up @@ -1121,16 +1123,19 @@ fn run(tcx: TyCtxt) {
let field_ltys = gacx.field_ltys.clone();
let acx = gacx.function_context_with_data(&mir, info.acx_data.take());
let mut asn = gasn.and(&mut info.lasn);
let updates_forbidden = g_updates_forbidden.and(&info.l_updates_forbidden);

let r = panic_detail::catch_unwind(AssertUnwindSafe(|| {
// `dataflow.propagate` and `borrowck_mir` both run until the assignment converges
// on a fixpoint, so there's no need to do multiple iterations here.
info.dataflow.propagate(&mut asn.perms_mut());
info.dataflow
.propagate(&mut asn.perms_mut(), &updates_forbidden);

borrowck::borrowck_mir(
&acx,
&info.dataflow,
&mut asn.perms_mut(),
&updates_forbidden,
name.as_str(),
&mir,
field_ltys,
Expand Down Expand Up @@ -1818,6 +1823,57 @@ fn make_sig_fixed(gasn: &mut GlobalAssignment, lsig: &LFnSig) {
}
}

/// For testing, putting #[c2rust_analyze_test::fail_before_analysis] on a function marks it as
/// failed at this point.
fn apply_test_attr_fail_before_analysis(
gacx: &mut GlobalAnalysisCtxt,
all_fn_ldids: &[LocalDefId],
) {
let tcx = gacx.tcx;
for &ldid in all_fn_ldids {
if !util::has_test_attr(tcx, ldid, TestAttr::FailBeforeAnalysis) {
continue;
}
gacx.mark_fn_failed(
ldid.to_def_id(),
DontRewriteFnReason::FAKE_INVALID_FOR_TESTING,
PanicDetail::new("explicit fail_before_analysis for testing".to_owned()),
);
}
}

/// For testing, putting #[c2rust_analyze_test::force_non_null_args] on a function marks its
/// arguments as `NON_NULL` and also adds `NON_NULL` to the `updates_forbidden` mask.
fn apply_test_attr_force_non_null_args(
gacx: &mut GlobalAnalysisCtxt,
all_fn_ldids: &[LocalDefId],
func_info: &mut HashMap<LocalDefId, FuncInfo>,
gasn: &mut GlobalAssignment,
g_updates_forbidden: &mut GlobalPointerTable<PermissionSet>,
) {
let tcx = gacx.tcx;
for &ldid in all_fn_ldids {
if !util::has_test_attr(tcx, ldid, TestAttr::ForceNonNullArgs) {
continue;
}

let info = func_info.get_mut(&ldid).unwrap();
let mut asn = gasn.and(&mut info.lasn);
let mut updates_forbidden = g_updates_forbidden.and_mut(&mut info.l_updates_forbidden);

let lsig = &gacx.fn_sigs[&ldid.to_def_id()];
for arg_lty in lsig.inputs.iter().copied() {
for lty in arg_lty.iter() {
let ptr = lty.label;
if !ptr.is_none() {
asn.perms_mut()[ptr].insert(PermissionSet::NON_NULL);
updates_forbidden[ptr].insert(PermissionSet::NON_NULL);
}
}
}
}
}

fn local_span(decl: &LocalDecl) -> Span {
let mut span = decl.source_info.span;
if let Some(ref info) = decl.local_info {
Expand Down
5 changes: 3 additions & 2 deletions c2rust-analyze/src/borrowck/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::context::AdtMetadataTable;
use crate::context::{AnalysisCtxt, PermissionSet};
use crate::dataflow::DataflowConstraints;
use crate::labeled_ty::{LabeledTy, LabeledTyCtxt};
use crate::pointer_id::PointerTableMut;
use crate::pointer_id::{PointerTable, PointerTableMut};
use crate::util::{describe_rvalue, RvalueDesc};
use indexmap::{IndexMap, IndexSet};
use rustc_hir::def_id::DefId;
Expand Down Expand Up @@ -120,6 +120,7 @@ pub fn borrowck_mir<'tcx>(
acx: &AnalysisCtxt<'_, 'tcx>,
dataflow: &DataflowConstraints,
hypothesis: &mut PointerTableMut<PermissionSet>,
updates_forbidden: &PointerTable<PermissionSet>,
name: &str,
mir: &Body<'tcx>,
field_ltys: HashMap<DefId, context::LTy<'tcx>>,
Expand Down Expand Up @@ -181,7 +182,7 @@ pub fn borrowck_mir<'tcx>(
}

eprintln!("propagate");
changed |= dataflow.propagate(hypothesis);
changed |= dataflow.propagate(hypothesis, updates_forbidden);
eprintln!("done propagating");

if !changed {
Expand Down
57 changes: 54 additions & 3 deletions c2rust-analyze/src/dataflow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ impl DataflowConstraints {
}

/// Update the pointer permissions in `hypothesis` to satisfy these constraints.
pub fn propagate(&self, hypothesis: &mut PointerTableMut<PermissionSet>) -> bool {
///
/// If `restrict_updates[ptr]` has some flags set, then those flags will be left unchanged in
/// `hypothesis[ptr]`.
pub fn propagate(
&self,
hypothesis: &mut PointerTableMut<PermissionSet>,
updates_forbidden: &PointerTable<PermissionSet>,
) -> bool {
eprintln!("=== propagating ===");
eprintln!("constraints:");
for c in &self.constraints {
Expand Down Expand Up @@ -118,27 +125,52 @@ impl DataflowConstraints {
) -> PermissionSet {
*val & !perms
}

fn restrict_updates(
&mut self,
old: &PermissionSet,
new: &PermissionSet,
updates_forbidden: &PermissionSet,
) -> PermissionSet {
let (old, new, updates_forbidden) = (*old, *new, *updates_forbidden);
(new & !updates_forbidden) | (old & updates_forbidden)
}
}

match self.propagate_inner(hypothesis, &mut PropagatePerms) {
match self.propagate_inner(hypothesis, &mut PropagatePerms, Some(updates_forbidden)) {
Ok(changed) => changed,
Err(msg) => {
panic!("{}", msg);
}
}
}

/// Update `xs` by propagating dataflow information of type `T` according to the constraints
/// recorded in `self`.
///
/// If `updates_forbidden` is provided, then the parts of `xs` indicated by `updates_forbidden`
/// will not be modified. (Specifically, all updates will be filtered through the method
/// `PropagateRules::restrict_updates`.)
fn propagate_inner<T, R>(
&self,
xs: &mut PointerTableMut<T>,
rules: &mut R,
updates_forbidden: Option<&PointerTable<T>>,
) -> Result<bool, String>
where
T: PartialEq,
R: PropagateRules<T>,
{
let mut xs = TrackedPointerTable::new(xs.borrow_mut());

let restrict_updates = |rules: &mut R, ptr, old: &T, new: T| {
ahomescu marked this conversation as resolved.
Show resolved Hide resolved
if let Some(updates_forbidden) = updates_forbidden {
rules.restrict_updates(old, &new, &updates_forbidden[ptr])
} else {
new
}
};

let mut changed = false;
let mut i = 0;
loop {
Expand All @@ -157,6 +189,8 @@ impl DataflowConstraints {
let old_a = xs.get(a);
let old_b = xs.get(b);
let (new_a, new_b) = rules.subset(a, old_a, b, old_b);
let new_a = restrict_updates(rules, a, old_a, new_a);
let new_b = restrict_updates(rules, b, old_b, new_b);
xs.set(a, new_a);
xs.set(b, new_b);
}
Expand All @@ -169,6 +203,8 @@ impl DataflowConstraints {
let old_a = xs.get(a);
let old_b = xs.get(b);
let (new_a, new_b) = rules.subset_except(a, old_a, b, old_b, except);
let new_a = restrict_updates(rules, a, old_a, new_a);
let new_b = restrict_updates(rules, b, old_b, new_b);
xs.set(a, new_a);
xs.set(b, new_b);
}
Expand All @@ -180,6 +216,7 @@ impl DataflowConstraints {

let old = xs.get(ptr);
let new = rules.all_perms(ptr, perms, old);
let new = restrict_updates(rules, ptr, old, new);
xs.set(ptr, new);
}

Expand All @@ -190,6 +227,7 @@ impl DataflowConstraints {

let old = xs.get(ptr);
let new = rules.no_perms(ptr, perms, old);
let new = restrict_updates(rules, ptr, old, new);
xs.set(ptr, new);
}
}
Expand Down Expand Up @@ -277,9 +315,19 @@ impl DataflowConstraints {
) -> FlagSet {
*val
}

fn restrict_updates(
&mut self,
old: &FlagSet,
new: &FlagSet,
updates_forbidden: &FlagSet,
) -> FlagSet {
let (old, new, updates_forbidden) = (*old, *new, *updates_forbidden);
(new & !updates_forbidden) | (old & updates_forbidden)
}
}

match self.propagate_inner(&mut flags, &mut Rules { perms }) {
match self.propagate_inner(&mut flags, &mut Rules { perms }, None) {
Ok(_changed) => {}
Err(msg) => {
panic!("{}", msg);
Expand Down Expand Up @@ -373,6 +421,9 @@ trait PropagateRules<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;
/// Apply a filter to restrict updates. The result is similar to `new`, but all flags marked
/// in `updates_forbidden` are adjusted to match their `old` values.
fn restrict_updates(&mut self, old: &T, new: &T, updates_forbidden: &T) -> T;
ahomescu marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn generate_constraints<'tcx>(
Expand Down
4 changes: 4 additions & 0 deletions c2rust-analyze/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,9 @@ pub enum TestAttr {
FailBeforeRewriting,
/// `#[c2rust_analyze_test::skip_rewrite]`: Skip generating rewrites for the function.
SkipRewrite,
/// `#[c2rust_analyze_test::force_non_null_args]`: Mark arguments as `NON_NULL` and don't allow
/// that flag to be changed during dataflow analysis.
ForceNonNullArgs,
}

impl TestAttr {
Expand All @@ -539,6 +542,7 @@ impl TestAttr {
TestAttr::FailBeforeAnalysis => "fail_before_analysis",
TestAttr::FailBeforeRewriting => "fail_before_rewriting",
TestAttr::SkipRewrite => "skip_rewrite",
TestAttr::ForceNonNullArgs => "force_non_null_args",
}
}
}
Expand Down
1 change: 1 addition & 0 deletions c2rust-analyze/tests/filecheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ define_tests! {
insertion_sort_rewrites,
known_fn,
non_null,
non_null_force,
offset1,
offset2,
pointee,
Expand Down
40 changes: 40 additions & 0 deletions c2rust-analyze/tests/filecheck/non_null_force.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#![feature(register_tool)]
#![register_tool(c2rust_analyze_test)]

// TODO: All the pointers here are currently inferred to be non-`UNIQUE`, even though the access
// patterns look fine.

use std::ptr;

// CHECK-LABEL: final labeling for "f"
fn f(cond: bool) {
let x = 1_i32;
// CHECK: ([[@LINE+1]]: mut y): {{.*}}, type = (empty)#
let mut y = ptr::addr_of!(x);
if cond {
y = 0 as *const _;
}
// The expression `y` is considered nullable even though it's passed for argument `p` of `g`,
// which is forced to be `NON_NULL`.
// CHECK: ([[@LINE+1]]: y): {{.*}}, type = (empty)#
g(cond, y);
}

// CHECK-LABEL: final labeling for "g"
// `p` should be non-null, as it's forced to be by the attribute. This emulates the "unsound" PDG
// case, where a variable is forced to stay `NON_NULL` even though a null possibly flows into it.
// CHECK: ([[@LINE+2]]: p): {{.*}}, type = NON_NULL#
#[c2rust_analyze_test::force_non_null_args]
fn g(cond: bool, p: *const i32) {
// `q` is not forced to be `NON_NULL`, so it should be inferred nullable due to the null
// assignment below.
// CHECK: ([[@LINE+1]]: mut q): {{.*}}, type = (empty)#
let mut q = p;
if cond {
q = 0 as *const _;
}
// `r` is derived from `q` (and is not forced), so it should also be nullable.
// CHECK: ([[@LINE+1]]: r): {{.*}}, type = (empty)#
let r = q;
}

Loading