Skip to content

Commit

Permalink
Dataflow analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
xFrednet committed Apr 22, 2024
1 parent c09e1c8 commit e3512de
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 36 deletions.
75 changes: 69 additions & 6 deletions clippy_lints/src/borrow_pats/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@
#![warn(unused)]

use super::prelude::*;
use super::visit_body;
use super::{calc_fn_arg_relations, visit_body};

mod pattern;
pub use pattern::*;
mod flow;
use flow::*;

use super::{PatternEnum, PatternStorage};

Expand All @@ -25,10 +27,12 @@ pub struct BodyAnalysis<'a, 'tcx> {
info: &'a AnalysisInfo<'tcx>,
pats: BTreeSet<BodyPat>,
data_flow: IndexVec<Local, SmallVec<[AssignInfo<'tcx>; 2]>>,
stats: BodyStats,
}

/// This indicates an assignment to `to`. In most cases, there is also a `from`.
#[expect(unused)]
///
/// TODO: Maybe handle &mut
#[derive(Debug, Copy, Clone)]
enum AssignInfo<'tcx> {
Place {
Expand All @@ -49,9 +53,9 @@ enum AssignInfo<'tcx> {
},
/// A value was constructed with this data
Ctor {
from: Place<'tcx>,
/// The `to` indicates the part of the target, that hols the from value.
to: Place<'tcx>,
from: Place<'tcx>,
},
}

Expand All @@ -76,21 +80,80 @@ impl<'a, 'tcx> BodyAnalysis<'a, 'tcx> {
info,
pats: BTreeSet::default(),
data_flow,
stats: Default::default(),
}
}

pub fn run(
info: &'a AnalysisInfo<'tcx>,
_def_id: LocalDefId,
def_id: LocalDefId,
hir_sig: &rustc_hir::FnSig<'_>,
context: BodyContext,
) -> (BodyInfo, BTreeSet<BodyPat>) {
) -> (BodyInfo, BTreeSet<BodyPat>, BodyStats) {
let mut anly = Self::new(info);

visit_body(&mut anly, info);
anly.check_fn_relations(def_id);

let body_info = BodyInfo::from_sig(hir_sig, context);
(body_info, anly.pats)
(body_info, anly.pats, anly.stats)
}

fn check_fn_relations(&mut self, def_id: LocalDefId) {
let mut rels = calc_fn_arg_relations(self.info.cx.tcx, def_id);
let return_rels = rels.remove(&RETURN_LOCAL).unwrap_or_default();

// TODO: Add special check for _0 = `const` | &'static _
self.check_return_relations(&return_rels);

// Argument relations
for (child, maybe_parents) in &rels {
self.check_arg_relation(child, maybe_parents)
}
}

fn check_return_relations(&mut self, sig_parents: &[Local]) {
self.stats.return_relations_signature = sig_parents.len();

let arg_ctn = self.info.body.arg_count;
let args: Vec<_> = (0..arg_ctn).map(|i| Local::from(i + 1)).collect();

let mut checker = DfWalker::new(self.info, &self.data_flow, RETURN_LOCAL, &args);
checker.walk();

for arg in &args {
if checker.found_connection(*arg) {
// These two branches are mutually exclusive:
if sig_parents.contains(arg) {
self.stats.return_relations_found += 1;
} else if !self.info.body.local_decls[*arg].ty.is_ref() {
println!("TODO: Track owned argument returned");
}
}
}

// let relation_count = 0;
// relation_count += 1;
// TODO: Also check for mut borrows here
// if relation_count == 0 && checker.has_const_assign() && !checker.has_computed_assign() {
// self.pats.insert(BodyPat)
// }
}


fn check_arg_relation(&mut self, child: &Local, maybe_parents: &[Local]) {
let mut checker = DfWalker::new(self.info, &self.data_flow, *child, maybe_parents);
checker.walk();

self.stats.arg_relations_signature += maybe_parents.len();
self.stats.arg_relations_found += checker.connection_count();

// Debugging
for maybe in maybe_parents {
if !checker.found_connection(*maybe) {
println!("Connection from {child:?} to {maybe:?} was not confirmed");
}
}
}
}

Expand Down
82 changes: 82 additions & 0 deletions clippy_lints/src/borrow_pats/body/flow.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use super::super::prelude::*;
use super::AssignInfo;

#[derive(Debug)]
pub struct DfWalker<'a, 'tcx> {
_info: &'a AnalysisInfo<'tcx>,
assignments: &'a IndexVec<Local, SmallVec<[AssignInfo<'tcx>; 2]>>,
child: Local,
maybe_parents: &'a [Local],
found_parents: Vec<Local>,
const_parent: bool,
computed_parent: bool,
}

impl<'a, 'tcx> DfWalker<'a, 'tcx> {
pub fn new(
info: &'a AnalysisInfo<'tcx>,
assignments: &'a IndexVec<Local, SmallVec<[AssignInfo<'tcx>; 2]>>,
child: Local,
maybe_parents: &'a [Local],
) -> Self {
Self {
_info: info,
assignments,
child,
maybe_parents,
found_parents: vec![],
const_parent: false,
computed_parent: false,
}
}

pub fn walk(&mut self) {
let mut seen = BitSet::new_empty(self.assignments.len());
let mut stack = Vec::with_capacity(16);
stack.push(self.child);

while let Some(parent) = stack.pop() {
if self.maybe_parents.contains(&parent) {
self.found_parents.push(parent);
}

for assign in &self.assignments[parent] {
match assign {
AssignInfo::Place { from, .. }
| AssignInfo::Dep { from, .. }
| AssignInfo::Ctor { from, .. } => {
let grandparent = from.local;
if seen.insert(grandparent) {
stack.push(grandparent);
}
},
AssignInfo::Const { .. } => {
self.const_parent = true;
},
AssignInfo::Calc { .. } => {
self.computed_parent = true;
},
}
}

}
}

pub fn connection_count(&self) -> usize {
self.found_parents.len()
}

pub fn found_connection(&self, maybe_parent: Local) -> bool {
self.found_parents.contains(&maybe_parent)
}

#[expect(unused)]
pub fn has_const_assign(&self) -> bool {
self.const_parent
}

#[expect(unused)]
pub fn has_computed_assign(&self) -> bool {
self.computed_parent
}
}
14 changes: 14 additions & 0 deletions clippy_lints/src/borrow_pats/body/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,17 @@ pub enum BodyContext {
/// The function is inside a trait definition.
TraitDef,
}

#[derive(Debug, Clone, Default)]
pub struct BodyStats {
/// Number of relations between the arguments and the return value accoring
/// to the function signature
pub return_relations_signature: usize,
/// Number of relations between the arguments and the return value that have
/// been found inside the body
pub return_relations_found: usize,
/// Number of relations between arguments according to the signature
pub arg_relations_signature: usize,
/// Number of relations between arguments that have been found in the body
pub arg_relations_found: usize,
}
9 changes: 6 additions & 3 deletions clippy_lints/src/borrow_pats/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,17 @@ impl BorrowPats {

if lint_level != Level::Allow {
let mut info = AnalysisInfo::new(cx, def_id);

let (body_info, body_pats, body_stats) = body::BodyAnalysis::run(&info, def_id, hir_sig, context);

if self.print_call_relations {
println!("# Relations for {body_name:?}");
println!("{:#?}", info.terms);
println!("Self relations: {:#?}", body_stats);
println!("Called function relations: {:#?}", info.terms);
println!();
return;
}

let (body_info, body_pats) = body::BodyAnalysis::run(&info, def_id, hir_sig, context);

for (local, local_info) in info.locals.iter().skip(1) {
match &local_info.kind {
LocalKind::Return => unreachable!("Skipped before"),
Expand Down
52 changes: 40 additions & 12 deletions clippy_lints/src/borrow_pats/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@ use std::collections::{BTreeMap, BTreeSet};
use clippy_utils::ty::{for_each_ref_region, for_each_region};
use rustc_ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{BasicBlock, Local, Operand};
use rustc_middle::ty::{FnSig, GenericArgsRef, GenericPredicates, Region, Ty, TyCtxt};
use rustc_middle::mir::{BasicBlock, Local, Operand, Place};
use rustc_middle::ty::{FnSig, GenericPredicates, Region, Ty, TyCtxt};
use rustc_span::source_map::Spanned;

use crate::borrow_pats::PlaceMagic;
use crate::borrow_pats::{LocalMagic, PlaceMagic};

mod visitor;
pub use visitor::*;

use super::prelude::RETURN_LOCAL;

const RETURN_RELEATION_INDEX: usize = usize::MAX;

pub struct PrintPrevent<T>(pub T);

impl<T> std::fmt::Debug for PrintPrevent<T> {
Expand All @@ -38,7 +42,7 @@ struct FuncReals<'tcx> {
}

impl<'tcx> FuncReals<'tcx> {
fn from_fn_def(tcx: TyCtxt<'tcx>, def_id: DefId, _args: GenericArgsRef<'tcx>) -> Self {
fn from_fn_def(tcx: TyCtxt<'tcx>, def_id: DefId) -> Self {
// FIXME: The proper and long therm solution would be to use HIR
// to find the call with generics that still have valid region markers.
// However, for now I need to get this zombie in the air and not pefect
Expand Down Expand Up @@ -96,7 +100,7 @@ impl<'tcx> FuncReals<'tcx> {
}
}

fn relations(&self, dest: Local, args: &Vec<Spanned<Operand<'tcx>>>) -> FxHashMap<Local, Vec<Local>> {
fn relations(&self, dest: Local, args: &[Spanned<Operand<'tcx>>]) -> FxHashMap<Local, Vec<Local>> {
let mut reals = FxHashMap::default();
let ret_rels = self.return_relations();
if !ret_rels.is_empty() {
Expand All @@ -105,7 +109,9 @@ impl<'tcx> FuncReals<'tcx> {
.filter_map(|idx| args[idx].node.place())
.map(|place| place.local)
.collect();
reals.insert(dest, locals);
if !locals.is_empty() {
reals.insert(dest, locals);
}
}

for (arg_index, arg_ty) in self.sig.inputs().iter().enumerate() {
Expand All @@ -127,7 +133,9 @@ impl<'tcx> FuncReals<'tcx> {
.filter_map(|idx| args[idx].node.place())
.map(|place| place.local)
.collect();
reals.insert(place.local, locals);
if !locals.is_empty() {
reals.insert(place.local, locals);
}
}
}

Expand All @@ -145,7 +153,7 @@ impl<'tcx> FuncReals<'tcx> {
/// This would return [1, 2], since the types in position 1 and 2 are related
/// to the return type.
fn return_relations(&self) -> FxHashSet<usize> {
self.find_relations(self.sig.output(), usize::MAX)
self.find_relations(self.sig.output(), RETURN_RELEATION_INDEX)
}

fn find_relations(&self, child_ty: Ty<'tcx>, child_index: usize) -> FxHashSet<usize> {
Expand Down Expand Up @@ -184,17 +192,37 @@ pub fn calc_call_local_relations<'tcx>(
tcx: TyCtxt<'tcx>,
func: &Operand<'tcx>,
dest: Local,
args: &Vec<Spanned<Operand<'tcx>>>,
args: &[Spanned<Operand<'tcx>>],
) -> FxHashMap<Local, Vec<Local>> {
if let Some((def_id, generic_args)) = func.const_fn_def() {
let builder = FuncReals::from_fn_def(tcx, def_id, generic_args);
if let Some((def_id, _generic_args)) = func.const_fn_def() {
let builder = FuncReals::from_fn_def(tcx, def_id);
let relations = builder.relations(dest, args);
relations
} else {
todo!()
}
}

pub fn calc_fn_arg_relations<'tcx>(tcx: TyCtxt<'tcx>, fn_id: LocalDefId) -> FxHashMap<Local, Vec<Local>> {
// This function is amazingly hacky, but at this point I really don't care anymore
let builder = FuncReals::from_fn_def(tcx, fn_id.into());
let arg_ctn = builder.sig.inputs().len();
let fake_args: Vec<_> = (0..arg_ctn)
.map(|idx| {
// `_0` is the return, the arguments start at `_1`
let place = Local::from_usize(idx + 1).as_place();
let place = unsafe { std::mem::transmute::<Place<'static>, Place<'tcx>>(place) };
Spanned {
node: Operand::Move(place),
span: rustc_span::DUMMY_SP,
}
})
.collect();

let relations = builder.relations(RETURN_LOCAL, &fake_args[..]);
relations
}

pub fn find_loop(
loops: &Vec<(BitSet<BasicBlock>, BasicBlock)>,
bb: BasicBlock,
Expand Down
Loading

0 comments on commit e3512de

Please sign in to comment.