Skip to content

Commit 9511a4f

Browse files
authored
analyze: allow overriding dataflow for specific permissions (#1088)
This adds a new internal feature for overriding dataflow analysis for specific permissions of specific pointers. The `propagate` method of `dataflow` now takes an additional `updates_forbidden` set, which has a `PermissionSet` mask for every `PointerId`, and avoids adding or removing a permission for a `ptr` if the corresponding bit is set in `updates_forbidden[ptr]`. When `updates_forbidden` is used, the resulting permissions after running `dataflow` might not actually satisfy the dataflow constraints. This is designed to support the PDG "`NON_NULL` override" feature, where information about nullability from the PDG can override static analysis results, though that feature is not part of the current PR.
2 parents 90ca19a + 9f3129d commit 9511a4f

File tree

6 files changed

+171
-18
lines changed

6 files changed

+171
-18
lines changed

c2rust-analyze/src/analyze.rs

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,8 @@ struct FuncInfo<'tcx> {
554554
/// get a complete [`Assignment`] for this function, which maps every [`PointerId`] in this
555555
/// function to a [`PermissionSet`] and [`FlagSet`].
556556
lasn: MaybeUnset<LocalAssignment>,
557+
/// Local part of the `updates_forbidden` mask.
558+
l_updates_forbidden: MaybeUnset<LocalPointerTable<PermissionSet>>,
557559
/// Constraints on pointee types gathered from the body of this function.
558560
pointee_constraints: MaybeUnset<pointee_type::ConstraintSet<'tcx>>,
559561
/// Local part of pointee type sets.
@@ -872,6 +874,8 @@ fn run(tcx: TyCtxt) {
872874
const INITIAL_FLAGS: FlagSet = FlagSet::empty();
873875

874876
let mut gasn = GlobalAssignment::new(gacx.num_pointers(), INITIAL_PERMS, INITIAL_FLAGS);
877+
let mut g_updates_forbidden = GlobalPointerTable::new(gacx.num_pointers());
878+
875879
for (ptr, &info) in gacx.ptr_info().iter() {
876880
if should_make_fixed(info) {
877881
gasn.flags[ptr].insert(FlagSet::FIXED);
@@ -897,6 +901,7 @@ fn run(tcx: TyCtxt) {
897901
for info in func_info.values_mut() {
898902
let num_pointers = info.acx_data.num_pointers();
899903
let mut lasn = LocalAssignment::new(num_pointers, INITIAL_PERMS, INITIAL_FLAGS);
904+
let l_updates_forbidden = LocalPointerTable::new(num_pointers);
900905

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

907912
info.lasn.set(lasn);
913+
info.l_updates_forbidden.set(l_updates_forbidden);
908914
}
909915

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

1085-
// For testing, putting #[c2rust_analyze_test::fail_before_analysis] on a function marks it as
1086-
// failed at this point.
1087-
for &ldid in &all_fn_ldids {
1088-
if !util::has_test_attr(tcx, ldid, TestAttr::FailBeforeAnalysis) {
1089-
continue;
1090-
}
1091-
gacx.mark_fn_failed(
1092-
ldid.to_def_id(),
1093-
DontRewriteFnReason::FAKE_INVALID_FOR_TESTING,
1094-
PanicDetail::new("explicit fail_before_analysis for testing".to_owned()),
1095-
);
1096-
}
1091+
apply_test_attr_fail_before_analysis(&mut gacx, &all_fn_ldids);
1092+
apply_test_attr_force_non_null_args(
1093+
&mut gacx,
1094+
&all_fn_ldids,
1095+
&mut func_info,
1096+
&mut gasn,
1097+
&mut g_updates_forbidden,
1098+
);
10971099

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

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

11301134
borrowck::borrowck_mir(
11311135
&acx,
11321136
&info.dataflow,
11331137
&mut asn.perms_mut(),
1138+
&updates_forbidden,
11341139
name.as_str(),
11351140
&mir,
11361141
field_ltys,
@@ -1818,6 +1823,57 @@ fn make_sig_fixed(gasn: &mut GlobalAssignment, lsig: &LFnSig) {
18181823
}
18191824
}
18201825

1826+
/// For testing, putting #[c2rust_analyze_test::fail_before_analysis] on a function marks it as
1827+
/// failed at this point.
1828+
fn apply_test_attr_fail_before_analysis(
1829+
gacx: &mut GlobalAnalysisCtxt,
1830+
all_fn_ldids: &[LocalDefId],
1831+
) {
1832+
let tcx = gacx.tcx;
1833+
for &ldid in all_fn_ldids {
1834+
if !util::has_test_attr(tcx, ldid, TestAttr::FailBeforeAnalysis) {
1835+
continue;
1836+
}
1837+
gacx.mark_fn_failed(
1838+
ldid.to_def_id(),
1839+
DontRewriteFnReason::FAKE_INVALID_FOR_TESTING,
1840+
PanicDetail::new("explicit fail_before_analysis for testing".to_owned()),
1841+
);
1842+
}
1843+
}
1844+
1845+
/// For testing, putting #[c2rust_analyze_test::force_non_null_args] on a function marks its
1846+
/// arguments as `NON_NULL` and also adds `NON_NULL` to the `updates_forbidden` mask.
1847+
fn apply_test_attr_force_non_null_args(
1848+
gacx: &mut GlobalAnalysisCtxt,
1849+
all_fn_ldids: &[LocalDefId],
1850+
func_info: &mut HashMap<LocalDefId, FuncInfo>,
1851+
gasn: &mut GlobalAssignment,
1852+
g_updates_forbidden: &mut GlobalPointerTable<PermissionSet>,
1853+
) {
1854+
let tcx = gacx.tcx;
1855+
for &ldid in all_fn_ldids {
1856+
if !util::has_test_attr(tcx, ldid, TestAttr::ForceNonNullArgs) {
1857+
continue;
1858+
}
1859+
1860+
let info = func_info.get_mut(&ldid).unwrap();
1861+
let mut asn = gasn.and(&mut info.lasn);
1862+
let mut updates_forbidden = g_updates_forbidden.and_mut(&mut info.l_updates_forbidden);
1863+
1864+
let lsig = &gacx.fn_sigs[&ldid.to_def_id()];
1865+
for arg_lty in lsig.inputs.iter().copied() {
1866+
for lty in arg_lty.iter() {
1867+
let ptr = lty.label;
1868+
if !ptr.is_none() {
1869+
asn.perms_mut()[ptr].insert(PermissionSet::NON_NULL);
1870+
updates_forbidden[ptr].insert(PermissionSet::NON_NULL);
1871+
}
1872+
}
1873+
}
1874+
}
1875+
}
1876+
18211877
fn local_span(decl: &LocalDecl) -> Span {
18221878
let mut span = decl.source_info.span;
18231879
if let Some(ref info) = decl.local_info {

c2rust-analyze/src/borrowck/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::context::AdtMetadataTable;
44
use crate::context::{AnalysisCtxt, PermissionSet};
55
use crate::dataflow::DataflowConstraints;
66
use crate::labeled_ty::{LabeledTy, LabeledTyCtxt};
7-
use crate::pointer_id::PointerTableMut;
7+
use crate::pointer_id::{PointerTable, PointerTableMut};
88
use crate::util::{describe_rvalue, RvalueDesc};
99
use indexmap::{IndexMap, IndexSet};
1010
use rustc_hir::def_id::DefId;
@@ -120,6 +120,7 @@ pub fn borrowck_mir<'tcx>(
120120
acx: &AnalysisCtxt<'_, 'tcx>,
121121
dataflow: &DataflowConstraints,
122122
hypothesis: &mut PointerTableMut<PermissionSet>,
123+
updates_forbidden: &PointerTable<PermissionSet>,
123124
name: &str,
124125
mir: &Body<'tcx>,
125126
field_ltys: HashMap<DefId, context::LTy<'tcx>>,
@@ -181,7 +182,7 @@ pub fn borrowck_mir<'tcx>(
181182
}
182183

183184
eprintln!("propagate");
184-
changed |= dataflow.propagate(hypothesis);
185+
changed |= dataflow.propagate(hypothesis, updates_forbidden);
185186
eprintln!("done propagating");
186187

187188
if !changed {

c2rust-analyze/src/dataflow/mod.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ impl DataflowConstraints {
4545
}
4646

4747
/// Update the pointer permissions in `hypothesis` to satisfy these constraints.
48-
pub fn propagate(&self, hypothesis: &mut PointerTableMut<PermissionSet>) -> bool {
48+
///
49+
/// If `restrict_updates[ptr]` has some flags set, then those flags will be left unchanged in
50+
/// `hypothesis[ptr]`.
51+
pub fn propagate(
52+
&self,
53+
hypothesis: &mut PointerTableMut<PermissionSet>,
54+
updates_forbidden: &PointerTable<PermissionSet>,
55+
) -> bool {
4956
eprintln!("=== propagating ===");
5057
eprintln!("constraints:");
5158
for c in &self.constraints {
@@ -118,27 +125,52 @@ impl DataflowConstraints {
118125
) -> PermissionSet {
119126
*val & !perms
120127
}
128+
129+
fn restrict_updates(
130+
&mut self,
131+
old: &PermissionSet,
132+
new: &PermissionSet,
133+
updates_forbidden: &PermissionSet,
134+
) -> PermissionSet {
135+
let (old, new, updates_forbidden) = (*old, *new, *updates_forbidden);
136+
(new & !updates_forbidden) | (old & updates_forbidden)
137+
}
121138
}
122139

123-
match self.propagate_inner(hypothesis, &mut PropagatePerms) {
140+
match self.propagate_inner(hypothesis, &mut PropagatePerms, Some(updates_forbidden)) {
124141
Ok(changed) => changed,
125142
Err(msg) => {
126143
panic!("{}", msg);
127144
}
128145
}
129146
}
130147

148+
/// Update `xs` by propagating dataflow information of type `T` according to the constraints
149+
/// recorded in `self`.
150+
///
151+
/// If `updates_forbidden` is provided, then the parts of `xs` indicated by `updates_forbidden`
152+
/// will not be modified. (Specifically, all updates will be filtered through the method
153+
/// `PropagateRules::restrict_updates`.)
131154
fn propagate_inner<T, R>(
132155
&self,
133156
xs: &mut PointerTableMut<T>,
134157
rules: &mut R,
158+
updates_forbidden: Option<&PointerTable<T>>,
135159
) -> Result<bool, String>
136160
where
137161
T: PartialEq,
138162
R: PropagateRules<T>,
139163
{
140164
let mut xs = TrackedPointerTable::new(xs.borrow_mut());
141165

166+
let restrict_updates = |rules: &mut R, ptr, old: &T, new: T| {
167+
if let Some(updates_forbidden) = updates_forbidden {
168+
rules.restrict_updates(old, &new, &updates_forbidden[ptr])
169+
} else {
170+
new
171+
}
172+
};
173+
142174
let mut changed = false;
143175
let mut i = 0;
144176
loop {
@@ -157,6 +189,8 @@ impl DataflowConstraints {
157189
let old_a = xs.get(a);
158190
let old_b = xs.get(b);
159191
let (new_a, new_b) = rules.subset(a, old_a, b, old_b);
192+
let new_a = restrict_updates(rules, a, old_a, new_a);
193+
let new_b = restrict_updates(rules, b, old_b, new_b);
160194
xs.set(a, new_a);
161195
xs.set(b, new_b);
162196
}
@@ -169,6 +203,8 @@ impl DataflowConstraints {
169203
let old_a = xs.get(a);
170204
let old_b = xs.get(b);
171205
let (new_a, new_b) = rules.subset_except(a, old_a, b, old_b, except);
206+
let new_a = restrict_updates(rules, a, old_a, new_a);
207+
let new_b = restrict_updates(rules, b, old_b, new_b);
172208
xs.set(a, new_a);
173209
xs.set(b, new_b);
174210
}
@@ -180,6 +216,7 @@ impl DataflowConstraints {
180216

181217
let old = xs.get(ptr);
182218
let new = rules.all_perms(ptr, perms, old);
219+
let new = restrict_updates(rules, ptr, old, new);
183220
xs.set(ptr, new);
184221
}
185222

@@ -190,6 +227,7 @@ impl DataflowConstraints {
190227

191228
let old = xs.get(ptr);
192229
let new = rules.no_perms(ptr, perms, old);
230+
let new = restrict_updates(rules, ptr, old, new);
193231
xs.set(ptr, new);
194232
}
195233
}
@@ -277,9 +315,19 @@ impl DataflowConstraints {
277315
) -> FlagSet {
278316
*val
279317
}
318+
319+
fn restrict_updates(
320+
&mut self,
321+
old: &FlagSet,
322+
new: &FlagSet,
323+
updates_forbidden: &FlagSet,
324+
) -> FlagSet {
325+
let (old, new, updates_forbidden) = (*old, *new, *updates_forbidden);
326+
(new & !updates_forbidden) | (old & updates_forbidden)
327+
}
280328
}
281329

282-
match self.propagate_inner(&mut flags, &mut Rules { perms }) {
330+
match self.propagate_inner(&mut flags, &mut Rules { perms }, None) {
283331
Ok(_changed) => {}
284332
Err(msg) => {
285333
panic!("{}", msg);
@@ -373,6 +421,9 @@ trait PropagateRules<T> {
373421
) -> (T, T);
374422
fn all_perms(&mut self, ptr: PointerId, perms: PermissionSet, val: &T) -> T;
375423
fn no_perms(&mut self, ptr: PointerId, perms: PermissionSet, val: &T) -> T;
424+
/// Apply a filter to restrict updates. The result is similar to `new`, but all flags marked
425+
/// in `updates_forbidden` are adjusted to match their `old` values.
426+
fn restrict_updates(&mut self, old: &T, new: &T, updates_forbidden: &T) -> T;
376427
}
377428

378429
pub fn generate_constraints<'tcx>(

c2rust-analyze/src/util.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,9 @@ pub enum TestAttr {
530530
FailBeforeRewriting,
531531
/// `#[c2rust_analyze_test::skip_rewrite]`: Skip generating rewrites for the function.
532532
SkipRewrite,
533+
/// `#[c2rust_analyze_test::force_non_null_args]`: Mark arguments as `NON_NULL` and don't allow
534+
/// that flag to be changed during dataflow analysis.
535+
ForceNonNullArgs,
533536
}
534537

535538
impl TestAttr {
@@ -539,6 +542,7 @@ impl TestAttr {
539542
TestAttr::FailBeforeAnalysis => "fail_before_analysis",
540543
TestAttr::FailBeforeRewriting => "fail_before_rewriting",
541544
TestAttr::SkipRewrite => "skip_rewrite",
545+
TestAttr::ForceNonNullArgs => "force_non_null_args",
542546
}
543547
}
544548
}

c2rust-analyze/tests/filecheck.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ define_tests! {
5656
insertion_sort_rewrites,
5757
known_fn,
5858
non_null,
59+
non_null_force,
5960
offset1,
6061
offset2,
6162
pointee,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![feature(register_tool)]
2+
#![register_tool(c2rust_analyze_test)]
3+
4+
// TODO: All the pointers here are currently inferred to be non-`UNIQUE`, even though the access
5+
// patterns look fine.
6+
7+
use std::ptr;
8+
9+
// CHECK-LABEL: final labeling for "f"
10+
fn f(cond: bool) {
11+
let x = 1_i32;
12+
// CHECK: ([[@LINE+1]]: mut y): {{.*}}, type = (empty)#
13+
let mut y = ptr::addr_of!(x);
14+
if cond {
15+
y = 0 as *const _;
16+
}
17+
// The expression `y` is considered nullable even though it's passed for argument `p` of `g`,
18+
// which is forced to be `NON_NULL`.
19+
// CHECK: ([[@LINE+1]]: y): {{.*}}, type = (empty)#
20+
g(cond, y);
21+
}
22+
23+
// CHECK-LABEL: final labeling for "g"
24+
// `p` should be non-null, as it's forced to be by the attribute. This emulates the "unsound" PDG
25+
// case, where a variable is forced to stay `NON_NULL` even though a null possibly flows into it.
26+
// CHECK: ([[@LINE+2]]: p): {{.*}}, type = NON_NULL#
27+
#[c2rust_analyze_test::force_non_null_args]
28+
fn g(cond: bool, p: *const i32) {
29+
// `q` is not forced to be `NON_NULL`, so it should be inferred nullable due to the null
30+
// assignment below.
31+
// CHECK: ([[@LINE+1]]: mut q): {{.*}}, type = (empty)#
32+
let mut q = p;
33+
if cond {
34+
q = 0 as *const _;
35+
}
36+
// `r` is derived from `q` (and is not forced), so it should also be nullable.
37+
// CHECK: ([[@LINE+1]]: r): {{.*}}, type = (empty)#
38+
let r = q;
39+
}
40+

0 commit comments

Comments
 (0)