use rustc_middle::{
mir::{self, TerminatorKind},
ty::{self, Instance, Ty, TypeVisitableExt, TypingEnv},
};
use rustc_span::{Spanned, sym};
use crate::{
fold::{Folder, Reach},
sinks::SinkTable,
state::{State, put},
value::{self, Bounds, Fact, Known, LenRel, Ranks, Value},
};
pub const DEPTH: u32 = 3;
pub const BUDGET: u32 = 4096;
#[derive(Debug, Clone, Copy, Default)]
pub struct Returns<'tcx> {
held: Fact<'tcx>,
walked: bool,
partial: bool,
}
impl<'tcx> Returns<'tcx> {
pub fn met(self, left: Fact<'tcx>) -> Self {
Self {
held: if self.walked {
self.held.joined(left)
} else {
left
},
walked: true,
..self
}
}
pub const fn is_new(self) -> bool {
!self.walked
}
pub const fn given_up() -> Self {
Self {
held: Fact::blank(),
walked: false,
partial: true,
}
}
pub fn claim(self) -> Fact<'tcx> {
if self.partial || !self.walked {
return Fact::default();
}
self.held
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Found<'tcx> {
pub left: Fact<'tcx>,
pub quiet: bool,
}
struct Carried<'tcx> {
slot: Option<mir::Local>,
base: Option<mir::Local>,
alike: bool,
fact: Fact<'tcx>,
}
pub const fn portable(value: Value<'_>) -> Option<Value<'_>> {
match value {
Value::Exact(_) | Value::Other(_) | Value::Within(_) => Some(value),
Value::Length(_) => None,
}
}
impl<'tcx> Folder<'_, 'tcx> {
pub fn inspect(
&mut self,
state: &State<'tcx>,
func: &mir::Operand<'tcx>,
args: &[Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
after: &mut State<'tcx>,
) -> Found<'tcx> {
let Some(ty) = self.ty_of(func) else {
return Found::default();
};
if let Some(left) = self.contracted(state, ty, args, destination) {
return Found { left, quiet: false };
}
self.folded(state, ty, args, destination, after)
}
fn contracted(
&self,
state: &State<'tcx>,
func: Ty<'tcx>,
args: &[Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Fact<'tcx>> {
let ty::FnDef(did, _) = *func.kind() else {
return None;
};
if self.tcx.crate_name(did.krate).as_str() != "core" {
return None;
}
let leaf = self.tcx.def_key(did).disambiguated_data.data.get_opt_name();
if !leaf.is_some_and(|name| {
matches!(
name.as_str(),
"len"
| "max"
| "min"
| "ctlz"
| "ctlz_nonzero"
| "cttz"
| "cttz_nonzero"
| "ctpop"
| "new"
| "get"
)
}) {
return None;
}
match SinkTable::def_path(self.tcx, did).as_str() {
"slice::len" => {
let of = self.root_slot(state, &args.first()?.node)?;
Some(Folder::measuring(Value::Length(of)))
}
"cmp::Ord::max" => self.chosen(state, true, args),
"cmp::Ord::min" => self.chosen(state, false, args),
"intrinsics::ctlz"
| "intrinsics::ctlz_nonzero"
| "intrinsics::cttz"
| "intrinsics::cttz_nonzero"
| "intrinsics::ctpop" => {
self.counted(args, destination).map(Fact::of)
}
"num::nonzero::new" => self.wrapped(state, args, destination),
"num::nonzero::get" => {
let receiver = args.first()?;
let source = self.ty_of(&receiver.node)?;
if !self.is_nonzero(source) {
return None;
}
self.apart_from_zero(
destination.ty(&self.mir.local_decls, self.tcx).ty,
)
.map(Fact::of)
}
_ => None,
}
}
fn folded(
&mut self,
state: &State<'tcx>,
func: Ty<'tcx>,
args: &[Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
after: &mut State<'tcx>,
) -> Found<'tcx> {
let Some(callee) = self.target(func) else {
return Found::default();
};
let mir = self.tcx.instance_mir(callee.def);
if mir.arg_count != args.len() {
return Found::default();
}
let mut folder = Folder::new(
self.tcx,
callee,
TypingEnv::fully_monomorphized(),
mir,
self.depth.saturating_add(1),
self.budget,
);
let entry = self.carried(state, &folder, args);
let reach = folder.run(entry);
self.budget = folder.budget;
self.handed_back(&folder, destination, after);
Found {
left: folder.returns.claim(),
quiet: self.silent(&folder, &reach),
}
}
fn target(&self, func: Ty<'tcx>) -> Option<Instance<'tcx>> {
if self.depth >= DEPTH || self.budget == 0 {
return None;
}
let ty::FnDef(did, generics) = *func.kind() else {
return None;
};
let generics = generics.no_bound_vars()?;
if generics.has_param() {
return None;
}
let callee = Instance::try_resolve(self.tcx, self.env, did, generics)
.ok()
.flatten()?;
if callee == self.inst {
return None;
}
let ty::InstanceKind::Item(def) = callee.def else {
return None;
};
self.tcx.is_mir_available(def).then_some(callee)
}
fn silent(&self, callee: &Folder<'_, 'tcx>, reach: &Reach) -> bool {
for (bb, data) in callee.mir.basic_blocks.iter_enumerated() {
if !reach.is_live(bb) {
continue;
}
let Some(term) = &data.terminator else {
return false;
};
let silent = match &term.kind {
TerminatorKind::Assert { .. } => reach.is_settled(bb),
TerminatorKind::Call { func, .. } => {
reach.is_quiet(bb) || self.defined(callee, func)
}
TerminatorKind::Drop { place, .. } => callee
.ty_at(place)
.is_some_and(|ty| !ty.needs_drop(self.tcx, callee.env)),
TerminatorKind::Goto { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. } => true,
_ => false,
};
if !silent {
return false;
}
}
true
}
fn defined(
&self,
callee: &Folder<'_, 'tcx>,
func: &mir::Operand<'tcx>,
) -> bool {
let Some(ty) = callee.ty_of(func) else {
return false;
};
let ty::FnDef(did, generics) = *ty.kind() else {
return false;
};
let Some(generics) = generics.no_bound_vars() else {
return false;
};
let Ok(Some(inst)) =
Instance::try_resolve(self.tcx, callee.env, did, generics)
else {
return false;
};
if !matches!(
inst.def,
ty::InstanceKind::Intrinsic(..)
| ty::InstanceKind::LlvmIntrinsic(..)
) {
return false;
}
let did = inst.def_id();
if SinkTable::is_sink(self.tcx, did) {
return false;
}
let name = self.tcx.item_name(did);
ty::layout::ValidityRequirement::from_intrinsic(name).is_none()
&& !matches!(name.as_str(), "catch_unwind" | "const_eval_select")
}
pub fn carried(
&self,
state: &State<'tcx>,
callee: &Folder<'_, 'tcx>,
args: &[Spanned<mir::Operand<'tcx>>],
) -> State<'tcx> {
let mut entry = callee.blank();
let carried: Vec<Carried<'tcx>> = args
.iter()
.enumerate()
.map(|(index, arg)| self.about(state, callee, index, arg))
.collect();
for (index, held) in carried.iter().enumerate() {
let local = mir::Local::from_usize(index.saturating_add(1));
if callee.escapes(local) || !held.alike {
continue;
}
let named = |of: mir::Local| {
let at = carried
.iter()
.position(|other| other.alike && other.slot == Some(of))?;
Some(mir::Local::from_usize(at.saturating_add(1)))
};
let mut order = Ranks::none_held();
for (rel, of) in held.fact.order.each() {
if let Some(there) = named(of) {
order.add(rel, there);
}
}
let paired = held.fact.paired.and_then(named);
let spans = held.fact.spans.and_then(named);
let over = held
.fact
.over
.and_then(|(of, step)| Some((named(of)?, step)));
let fact = Fact {
order,
paired,
over,
spans,
..held.fact
};
if fact == Fact::default() {
continue;
}
if let Some(slot) = entry.get_mut(local.as_usize()) {
*slot = fact;
}
}
Self::cut_alike(&carried, &mut entry);
self.handed_over(state, callee, &carried, &mut entry);
entry
}
fn cut_alike(carried: &[Carried<'tcx>], entry: &mut State<'tcx>) {
for (index, held) in carried.iter().enumerate() {
let Some(cut) = held.fact.spans.filter(|_| held.alike) else {
continue;
};
let Some(other) = carried.iter().enumerate().position(|(at, p)| {
at != index && p.alike && p.fact.spans == Some(cut)
}) else {
continue;
};
let local = mir::Local::from_usize(index.saturating_add(1));
let peer = mir::Local::from_usize(other.saturating_add(1));
if let Some(slot) = entry.get_mut(local.as_usize()) {
slot.paired = Some(peer);
}
}
}
fn handed_back(
&self,
callee: &Folder<'_, 'tcx>,
destination: mir::Place<'tcx>,
after: &mut State<'tcx>,
) {
if !destination.projection.is_empty() {
return;
}
for (path, fact) in &callee.returned {
if *fact == Fact::default() || !path.portable() {
continue;
}
let Some(slot) = self.places.at(path.rebased(destination.local))
else {
continue;
};
if self.escapes(slot) {
continue;
}
put(after, slot, *fact);
}
}
fn handed_over(
&self,
state: &State<'tcx>,
callee: &Folder<'_, 'tcx>,
carried: &[Carried<'tcx>],
entry: &mut State<'tcx>,
) {
for (slot, path) in callee.places.each() {
if callee.escapes(slot) || !path.portable() {
continue;
}
let Some(at) = path.base.as_usize().checked_sub(1) else {
continue;
};
let Some(held) = carried.get(at).filter(|held| held.alike) else {
continue;
};
let Some(mine) = held
.base
.and_then(|base| self.places.at(path.rebased(base)))
else {
continue;
};
let fact = Folder::abroad(Folder::known_at(state, mine));
if fact == Fact::default() {
continue;
}
put(entry, slot, fact);
}
}
fn about(
&self,
state: &State<'tcx>,
callee: &Folder<'_, 'tcx>,
index: usize,
arg: &Spanned<mir::Operand<'tcx>>,
) -> Carried<'tcx> {
let local = mir::Local::from_usize(index.saturating_add(1));
let slot = self.root_slot(state, &arg.node);
let param = callee
.mir
.local_decls
.get(local)
.and_then(|decl| callee.monomorphize(decl.ty));
let passed = self.ty_of(&arg.node);
let alike = param.is_some() && param == passed;
let held = self.fact(state, &arg.node);
let fact = Fact {
value: held.value.and_then(portable).filter(|value| {
param == value.ty()
}),
same: None,
..held
};
let base = match &arg.node {
mir::Operand::Copy(place) | mir::Operand::Move(place) => {
place.projection.is_empty().then_some(place.local)
}
mir::Operand::Constant(_) | mir::Operand::RuntimeChecks(_) => None,
};
Carried {
slot,
base,
alike,
fact,
}
}
fn wrapped(
&self,
state: &State<'tcx>,
args: &[Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Fact<'tcx>> {
let held = args.first()?;
let ty = self.ty_of(&held.node)?;
let zero = Known {
bits: 0,
ty,
width: self.width(ty)?,
};
let apart = value::compare(
mir::BinOp::Ne,
self.fact(state, &held.node),
Fact::of(Value::Exact(zero)),
)?;
if !apart {
return None;
}
let out = self.ty_at(&destination)?;
let ty::Adt(def, args) = out.kind() else {
return None;
};
if self.tcx.get_diagnostic_item(sym::Option) != Some(def.did()) {
return None;
}
let (at, _) = def
.variants()
.iter_enumerated()
.find(|(_, variant)| !variant.fields.is_empty())?;
Some(Fact {
tag: self.tag_of(def.did(), args, at),
..Fact::default()
})
}
fn counted(
&self,
args: &[Spanned<mir::Operand<'tcx>>],
destination: mir::Place<'tcx>,
) -> Option<Value<'tcx>> {
let counted = self.ty_of(&args.first()?.node)?;
let bits = u128::from(self.width(counted)?);
let ty = self.ty_at(&destination)?;
let width = self.width(ty)?;
let seed = Known { bits: 0, ty, width };
Bounds::new(seed, Known { bits, ..seed }).map(Value::Within)
}
fn chosen(
&self,
state: &State<'tcx>,
larger: bool,
args: &[Spanned<mir::Operand<'tcx>>],
) -> Option<Fact<'tcx>> {
let fact = Fact {
value: self.picked(state, larger, args),
order: self.ranked(state, larger, args),
..Fact::default()
};
(fact != Fact::default()).then_some(fact)
}
fn ranked(
&self,
state: &State<'tcx>,
larger: bool,
args: &[Spanned<mir::Operand<'tcx>>],
) -> Ranks {
let [left, right] = args else {
return Ranks::none_held();
};
let bound = |operand: &mir::Operand<'tcx>| {
let fact = self.fact(state, operand);
match fact.value {
Some(Value::Length(of)) => Ranks::of(LenRel::AtMost, of),
_ => fact.order,
}
};
let (left, right) = (bound(&left.node), bound(&right.node));
if larger {
return left.joined(right);
}
let mut both = left;
for (rel, of) in right.each() {
both.add(rel, of);
}
both
}
fn picked(
&self,
state: &State<'tcx>,
larger: bool,
args: &[Spanned<mir::Operand<'tcx>>],
) -> Option<Value<'tcx>> {
let [left, right] = args else {
return None;
};
let left = self.spread(state, &left.node)?;
let right = self.spread(state, &right.node)?;
let pick = |a: Known<'tcx>, b: Known<'tcx>| {
let above = a.order(b)? == std::cmp::Ordering::Greater;
Some(if above == larger { a } else { b })
};
Bounds::new(pick(left.lo, right.lo)?, pick(left.hi, right.hi)?)
.map(Value::Within)
}
pub fn spread(
&self,
state: &State<'tcx>,
operand: &mir::Operand<'tcx>,
) -> Option<Bounds<'tcx>> {
let ty = self.ty_of(operand)?;
let whole = self.whole(ty)?;
let Some(value) = value::sized(self.fact(state, operand)) else {
return Some(whole);
};
if value.ty() != Some(ty) {
return Some(whole);
}
Some(
Value::Within(whole)
.refined(value)
.bounds()
.unwrap_or(whole),
)
}
pub fn whole(&self, ty: Ty<'tcx>) -> Option<Bounds<'tcx>> {
let seed = Known {
bits: 0,
ty,
width: self.width(ty)?,
};
let top = match ty.kind() {
ty::Int(_) | ty::Uint(_) => seed.type_max(),
ty::Char => Known {
bits: u128::from(char::MAX as u32),
..seed
},
ty::Bool => Known { bits: 1, ..seed },
_ => return None,
};
Bounds::new(seed.type_min(), top)
}
pub fn is_nonzero(&self, ty: Ty<'tcx>) -> bool {
let ty::Adt(def, _) = ty.kind() else {
return false;
};
self.tcx.get_diagnostic_item(sym::NonZero) == Some(def.did())
}
pub fn apart_from_zero(&self, ty: Ty<'tcx>) -> Option<Value<'tcx>> {
let ty = self.monomorphize(ty)?;
let width = self.width(ty)?;
Some(Value::other_than(Known { bits: 0, ty, width }))
}
}