use super::draw_dot::render_dot_string;
use crate::analysis::{
core::dataflow::{DataFlowAnalysis, default::DataFlowAnalyzer},
senryx::{
contracts::{property, property::PropertyContract},
matcher::parse_unsafe_api,
},
};
use crate::def_id::*;
use crate::{rap_debug, rap_warn};
use rustc_ast::ItemKind;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir::{
Attribute, ImplItemKind, Safety,
def::DefKind,
def_id::{CrateNum, DefId, DefIndex},
};
use rustc_middle::{
hir::place::PlaceBase,
mir::{
BasicBlock, BinOp, Body, Local, Operand, Place, PlaceElem, PlaceRef, ProjectionElem,
Rvalue, StatementKind, Terminator, TerminatorKind,
},
ty,
ty::{AssocKind, ConstKind, Mutability, Ty, TyCtxt, TyKind},
};
use rustc_span::{def_id::LocalDefId, kw, sym};
use serde::de;
use serde::{Deserialize, Serialize};
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
hash::Hash,
};
use syn::Expr;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum FnKind {
Fn,
Method,
Constructor,
Intrinsic,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct FnInfo {
pub def_id: DefId,
pub fn_safety: Safety,
pub fn_kind: FnKind,
}
impl FnInfo {
pub fn new(def_id: DefId, fn_safety: Safety, fn_kind: FnKind) -> Self {
FnInfo {
def_id,
fn_safety,
fn_kind,
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct AdtInfo {
pub def_id: DefId,
pub literal_cons_enabled: bool,
}
impl AdtInfo {
pub fn new(def_id: DefId, literal_cons_enabled: bool) -> Self {
AdtInfo {
def_id,
literal_cons_enabled,
}
}
}
pub fn check_visibility(tcx: TyCtxt, func_defid: DefId) -> bool {
if !tcx.visibility(func_defid).is_public() {
return false;
}
true
}
pub fn is_re_exported(tcx: TyCtxt, target_defid: DefId, module_defid: LocalDefId) -> bool {
for child in tcx.module_children_local(module_defid) {
if child.vis.is_public() {
if let Some(def_id) = child.res.opt_def_id() {
if def_id == target_defid {
return true;
}
}
}
}
false
}
pub fn print_hashset<T: std::fmt::Debug>(set: &HashSet<T>) {
for item in set {
println!("{:?}", item);
}
println!("---------------");
}
pub fn get_cleaned_def_path_name_ori(tcx: TyCtxt, def_id: DefId) -> String {
let def_id_str = format!("{:?}", def_id);
let mut parts: Vec<&str> = def_id_str.split("::").collect();
let mut remove_first = false;
if let Some(first_part) = parts.get_mut(0) {
if first_part.contains("core") {
*first_part = "core";
} else if first_part.contains("std") {
*first_part = "std";
} else if first_part.contains("alloc") {
*first_part = "alloc";
} else {
remove_first = true;
}
}
if remove_first && !parts.is_empty() {
parts.remove(0);
}
let new_parts: Vec<String> = parts
.into_iter()
.filter_map(|s| {
if s.contains("{") {
if remove_first {
get_struct_name(tcx, def_id)
} else {
None
}
} else {
Some(s.to_string())
}
})
.collect();
let mut cleaned_path = new_parts.join("::");
cleaned_path = cleaned_path.trim_end_matches(')').to_string();
cleaned_path
}
pub fn get_sp_tags_json() -> serde_json::Value {
let json_data: serde_json::Value =
serde_json::from_str(include_str!("data/std_sps.json")).expect("Unable to parse JSON");
json_data
}
pub fn get_std_api_signature_json() -> serde_json::Value {
let json_data: serde_json::Value =
serde_json::from_str(include_str!("data/std_sig.json")).expect("Unable to parse JSON");
json_data
}
pub fn get_sp_tags_and_args_json() -> serde_json::Value {
let json_data: serde_json::Value =
serde_json::from_str(include_str!("data/std_sps_args.json")).expect("Unable to parse JSON");
json_data
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ContractEntry {
pub tag: String,
pub args: Vec<String>,
}
pub fn get_std_contracts(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<ContractEntry> {
let cleaned_path_name = get_cleaned_def_path_name(tcx, def_id);
let json_data: serde_json::Value = get_sp_tags_and_args_json();
if let Some(entries) = json_data.get(&cleaned_path_name) {
if let Ok(contracts) = serde_json::from_value::<Vec<ContractEntry>>(entries.clone()) {
return contracts;
}
}
Vec::new()
}
pub fn get_sp(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<String> {
let cleaned_path_name = get_cleaned_def_path_name(tcx, def_id);
let json_data: serde_json::Value = get_sp_tags_json();
if let Some(function_info) = json_data.get(&cleaned_path_name) {
if let Some(sp_list) = function_info.get("0") {
let mut result = HashSet::new();
if let Some(sp_array) = sp_list.as_array() {
for sp in sp_array {
if let Some(sp_name) = sp.as_str() {
result.insert(sp_name.to_string());
}
}
}
return result;
}
}
HashSet::new()
}
pub fn get_struct_name(tcx: TyCtxt<'_>, def_id: DefId) -> Option<String> {
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty = tcx.type_of(impl_id).skip_binder();
let type_name = ty.to_string();
let struct_name = type_name
.split('<')
.next()
.unwrap_or("")
.split("::")
.last()
.unwrap_or("")
.to_string();
return Some(struct_name);
}
}
None
}
pub fn check_safety(tcx: TyCtxt<'_>, def_id: DefId) -> Safety {
let poly_fn_sig = tcx.fn_sig(def_id);
let fn_sig = poly_fn_sig.skip_binder();
fn_sig.safety()
}
pub fn get_type(tcx: TyCtxt<'_>, def_id: DefId) -> FnKind {
let mut node_type = 2;
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
match assoc_item.kind {
AssocKind::Fn { has_self, .. } => {
if has_self {
return FnKind::Method;
} else {
let fn_sig = tcx.fn_sig(def_id).skip_binder();
let output = fn_sig.output().skip_binder();
if output.is_param(0) {
return FnKind::Constructor;
}
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty = tcx.type_of(impl_id).skip_binder();
if output == ty {
return FnKind::Constructor;
}
}
match output.kind() {
TyKind::Ref(_, ref_ty, _) => {
if ref_ty.is_param(0) {
return FnKind::Constructor;
}
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty = tcx.type_of(impl_id).skip_binder();
if *ref_ty == ty {
return FnKind::Constructor;
}
}
}
TyKind::Adt(adt_def, substs) => {
if adt_def.is_enum()
&& (tcx.is_diagnostic_item(sym::Option, adt_def.did())
|| tcx.is_diagnostic_item(sym::Result, adt_def.did())
|| tcx.is_diagnostic_item(kw::Box, adt_def.did()))
{
let inner_ty = substs.type_at(0);
if inner_ty.is_param(0) {
return FnKind::Constructor;
}
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty_impl = tcx.type_of(impl_id).skip_binder();
if inner_ty == ty_impl {
return FnKind::Constructor;
}
}
}
}
_ => {}
}
}
}
_ => todo!(),
}
}
return FnKind::Fn;
}
pub fn get_adt_ty(tcx: TyCtxt, def_id: DefId) -> Option<Ty> {
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
if let Some(impl_id) = assoc_item.impl_container(tcx) {
return Some(tcx.type_of(impl_id).skip_binder());
}
}
None
}
pub fn get_adt_via_method(tcx: TyCtxt<'_>, method_def_id: DefId) -> Option<AdtInfo> {
let assoc_item = tcx.opt_associated_item(method_def_id)?;
let impl_id = assoc_item.impl_container(tcx)?;
let ty = tcx.type_of(impl_id).skip_binder();
let adt_def = ty.ty_adt_def()?;
let adt_def_id = adt_def.did();
let all_fields: Vec<_> = adt_def.all_fields().collect();
let total_count = all_fields.len();
if total_count == 0 {
return Some(AdtInfo::new(adt_def_id, true));
}
let pub_count = all_fields
.iter()
.filter(|field| tcx.visibility(field.did).is_public())
.count();
if pub_count == 0 {
return None;
}
Some(AdtInfo::new(adt_def_id, pub_count == total_count))
}
fn place_has_raw_deref<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, place: &Place<'tcx>) -> bool {
let mut local = place.local;
for proj in place.projection.iter() {
if let ProjectionElem::Deref = proj.kind() {
let ty = body.local_decls[local].ty;
if let TyKind::RawPtr(_, _) = ty.kind() {
return true;
}
}
}
false
}
pub fn get_rawptr_deref(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<Local> {
let mut raw_ptrs = HashSet::new();
if tcx.is_mir_available(def_id) {
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
if let StatementKind::Assign(box (lhs, rhs)) = &stmt.kind {
if place_has_raw_deref(tcx, &body, lhs) {
raw_ptrs.insert(lhs.local);
}
if let Rvalue::Use(op) = rhs {
match op {
Operand::Copy(place) | Operand::Move(place) => {
if place_has_raw_deref(tcx, &body, place) {
raw_ptrs.insert(place.local);
}
}
_ => {}
}
}
if let Rvalue::Ref(_, _, place) = rhs {
if place_has_raw_deref(tcx, &body, place) {
raw_ptrs.insert(place.local);
}
}
}
}
if let Some(terminator) = &bb.terminator {
match &terminator.kind {
rustc_middle::mir::TerminatorKind::Call { args, .. } => {
for arg in args {
match arg.node {
Operand::Copy(place) | Operand::Move(place) => {
if place_has_raw_deref(tcx, &body, &place) {
raw_ptrs.insert(place.local);
}
}
_ => {}
}
}
}
_ => {}
}
}
}
}
raw_ptrs
}
pub fn collect_global_local_pairs(tcx: TyCtxt<'_>, def_id: DefId) -> HashMap<DefId, Vec<Local>> {
let mut globals: HashMap<DefId, Vec<Local>> = HashMap::new();
if !tcx.is_mir_available(def_id) {
return globals;
}
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
for stmt in &bb.statements {
if let StatementKind::Assign(box (lhs, rhs)) = &stmt.kind {
if let Rvalue::Use(Operand::Constant(c)) = rhs {
if let Some(static_def_id) = c.check_static_ptr(tcx) {
globals.entry(static_def_id).or_default().push(lhs.local);
}
}
}
}
}
globals
}
pub fn get_unsafe_callees(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<DefId> {
let mut unsafe_callees = HashSet::new();
if tcx.is_mir_available(def_id) {
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
if let TerminatorKind::Call { func, .. } = &bb.terminator().kind {
if let Operand::Constant(func_constant) = func {
if let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() {
if check_safety(tcx, *callee_def_id) == Safety::Unsafe {
unsafe_callees.insert(*callee_def_id);
}
}
}
}
}
}
unsafe_callees
}
pub fn get_all_callees(tcx: TyCtxt<'_>, def_id: DefId) -> HashSet<DefId> {
let mut callees = HashSet::new();
if tcx.is_mir_available(def_id) {
let body = tcx.optimized_mir(def_id);
for bb in body.basic_blocks.iter() {
if let TerminatorKind::Call { func, .. } = &bb.terminator().kind {
if let Operand::Constant(func_constant) = func {
if let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() {
callees.insert(*callee_def_id);
}
}
}
}
}
callees
}
pub fn get_impls_for_struct(tcx: TyCtxt<'_>, struct_def_id: DefId) -> Vec<DefId> {
let mut impls = Vec::new();
for item_id in tcx.hir_crate_items(()).free_items() {
let item = tcx.hir_item(item_id);
if let rustc_hir::ItemKind::Impl(impl_details) = &item.kind {
if let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(_, path)) =
&impl_details.self_ty.kind
{
if let rustc_hir::def::Res::Def(_, def_id) = path.res {
if def_id == struct_def_id {
impls.push(item_id.owner_id.to_def_id());
}
}
}
}
}
impls
}
pub fn get_adt_def_id_by_adt_method(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty = tcx.type_of(impl_id).skip_binder();
if let Some(adt_def) = ty.ty_adt_def() {
return Some(adt_def.did());
}
}
}
None
}
pub fn get_pointee(matched_ty: Ty<'_>) -> Ty<'_> {
let pointee = if let ty::RawPtr(ty_mut, _) = matched_ty.kind() {
get_pointee(*ty_mut)
} else if let ty::Ref(_, referred_ty, _) = matched_ty.kind() {
get_pointee(*referred_ty)
} else {
matched_ty
};
pointee
}
pub fn is_ptr(matched_ty: Ty<'_>) -> bool {
if let ty::RawPtr(_, _) = matched_ty.kind() {
return true;
}
false
}
pub fn is_ref(matched_ty: Ty<'_>) -> bool {
if let ty::Ref(_, _, _) = matched_ty.kind() {
return true;
}
false
}
pub fn is_slice(matched_ty: Ty) -> Option<Ty> {
if let ty::Slice(inner) = matched_ty.kind() {
return Some(*inner);
}
None
}
pub fn has_mut_self_param(tcx: TyCtxt, def_id: DefId) -> bool {
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
match assoc_item.kind {
AssocKind::Fn { has_self, .. } => {
if has_self && tcx.is_mir_available(def_id) {
let body = tcx.optimized_mir(def_id);
let fst_arg = body.local_decls[Local::from_usize(1)].clone();
let ty = fst_arg.ty;
let is_mut_ref =
matches!(ty.kind(), ty::Ref(_, _, mutbl) if *mutbl == Mutability::Mut);
return fst_arg.mutability.is_mut() || is_mut_ref;
}
}
_ => (),
}
}
false
}
pub fn get_public_fields(tcx: TyCtxt, def_id: DefId) -> HashSet<usize> {
let adt_def = tcx.adt_def(def_id);
adt_def
.all_fields()
.enumerate()
.filter_map(|(index, field_def)| tcx.visibility(field_def.did).is_public().then_some(index))
.collect()
}
pub fn display_hashmap<K, V>(map: &HashMap<K, V>, level: usize)
where
K: Ord + Debug + Hash,
V: Debug,
{
let indent = " ".repeat(level);
let mut sorted_keys: Vec<_> = map.keys().collect();
sorted_keys.sort();
for key in sorted_keys {
if let Some(value) = map.get(key) {
println!("{}{:?}: {:?}", indent, key, value);
}
}
}
pub fn match_std_unsafe_chains_callee(tcx: TyCtxt<'_>, terminator: &Terminator<'_>) -> Vec<String> {
let mut results = Vec::new();
if let TerminatorKind::Call { func, .. } = &terminator.kind {
if let Operand::Constant(func_constant) = func {
if let ty::FnDef(callee_def_id, _raw_list) = func_constant.const_.ty().kind() {
let func_name = get_cleaned_def_path_name(tcx, *callee_def_id);
}
}
}
results
}
pub fn get_all_std_unsafe_callees(tcx: TyCtxt, def_id: DefId) -> Vec<String> {
let mut results = Vec::new();
let body = tcx.optimized_mir(def_id);
let bb_len = body.basic_blocks.len();
for i in 0..bb_len {
let callees = match_std_unsafe_callee(
tcx,
body.basic_blocks[BasicBlock::from_usize(i)]
.clone()
.terminator(),
);
results.extend(callees);
}
results
}
pub fn get_all_std_unsafe_callees_block_id(tcx: TyCtxt, def_id: DefId) -> Vec<usize> {
let mut results = Vec::new();
let body = tcx.optimized_mir(def_id);
let bb_len = body.basic_blocks.len();
for i in 0..bb_len {
if match_std_unsafe_callee(
tcx,
body.basic_blocks[BasicBlock::from_usize(i)]
.clone()
.terminator(),
)
.is_empty()
{
results.push(i);
}
}
results
}
pub fn match_std_unsafe_callee(tcx: TyCtxt<'_>, terminator: &Terminator<'_>) -> Vec<String> {
let mut results = Vec::new();
if let TerminatorKind::Call { func, .. } = &terminator.kind {
if let Operand::Constant(func_constant) = func {
if let ty::FnDef(callee_def_id, _raw_list) = func_constant.const_.ty().kind() {
let func_name = get_cleaned_def_path_name(tcx, *callee_def_id);
if parse_unsafe_api(&func_name).is_some() {
results.push(func_name);
}
}
}
}
results
}
pub fn is_strict_ty_convert<'tcx>(tcx: TyCtxt<'tcx>, src_ty: Ty<'tcx>, dst_ty: Ty<'tcx>) -> bool {
(is_strict_ty(tcx, src_ty) && dst_ty.is_mutable_ptr()) || is_strict_ty(tcx, dst_ty)
}
pub fn is_strict_ty<'tcx>(tcx: TyCtxt<'tcx>, ori_ty: Ty<'tcx>) -> bool {
let ty = get_pointee(ori_ty);
let mut flag = false;
if let TyKind::Adt(adt_def, substs) = ty.kind() {
if adt_def.is_struct() {
for field_def in adt_def.all_fields() {
flag |= is_strict_ty(tcx, field_def.ty(tcx, substs))
}
}
}
ty.is_bool() || ty.is_str() || flag
}
pub fn reverse_op(op: BinOp) -> BinOp {
match op {
BinOp::Lt => BinOp::Ge,
BinOp::Ge => BinOp::Lt,
BinOp::Le => BinOp::Gt,
BinOp::Gt => BinOp::Le,
BinOp::Eq => BinOp::Eq,
BinOp::Ne => BinOp::Ne,
_ => op,
}
}
pub fn generate_contract_from_std_annotation_json(
tcx: TyCtxt<'_>,
def_id: DefId,
) -> Vec<(usize, Vec<usize>, PropertyContract<'_>)> {
let mut results = Vec::new();
let std_contracts = get_std_contracts(tcx, def_id);
for entry in std_contracts {
let tag_name = entry.tag;
let raw_args = entry.args;
if raw_args.is_empty() {
continue;
}
let arg_index_str = &raw_args[0];
let local_id = if let Ok(arg_idx) = arg_index_str.parse::<usize>() {
arg_idx
} else {
rap_error!(
"JSON Contract Error: First argument must be an arg index number, got {}",
arg_index_str
);
continue;
};
let mut exprs: Vec<Expr> = Vec::new();
for arg_str in &raw_args {
match syn::parse_str::<Expr>(arg_str) {
Ok(expr) => exprs.push(expr),
Err(_) => {
rap_error!(
"JSON Contract Error: Failed to parse arg '{}' as Rust Expr for tag {}",
arg_str,
tag_name
);
}
}
}
if exprs.len() != raw_args.len() {
rap_error!(
"Parse std API args error: Failed to parse arg '{:?}'",
raw_args
);
continue;
}
let fields: Vec<usize> = Vec::new();
let contract = PropertyContract::new(tcx, def_id, &tag_name, &exprs);
results.push((local_id, fields, contract));
}
results
}
pub fn generate_contract_from_annotation_without_field_types(
tcx: TyCtxt,
def_id: DefId,
) -> Vec<(usize, Vec<usize>, PropertyContract)> {
let contracts_with_ty = generate_contract_from_annotation(tcx, def_id);
contracts_with_ty
.into_iter()
.map(|(local_id, fields_with_ty, contract)| {
let fields: Vec<usize> = fields_with_ty
.into_iter()
.map(|(field_idx, _)| field_idx)
.collect();
(local_id, fields, contract)
})
.collect()
}
pub fn is_verify_target_func(tcx: TyCtxt, def_id: DefId) -> bool {
for attr in tcx.get_all_attrs(def_id).into_iter() {
let attr_str = rustc_hir_pretty::attribute_to_string(&tcx, attr);
if attr_str.contains("#[rapx::proof(proof)]") {
return true;
}
}
false
}
pub fn generate_contract_from_annotation(
tcx: TyCtxt,
def_id: DefId,
) -> Vec<(usize, Vec<(usize, Ty)>, PropertyContract)> {
const REGISTER_TOOL: &str = "rapx";
let tool_attrs = tcx.get_all_attrs(def_id).into_iter().filter(|attr| {
if let Attribute::Unparsed(tool_attr) = attr {
if tool_attr.path.segments[0].as_str() == REGISTER_TOOL {
return true;
}
}
false
});
let mut results = Vec::new();
for attr in tool_attrs {
let attr_str = rustc_hir_pretty::attribute_to_string(&tcx, attr);
if attr_str.contains("#[rapx::proof(proof)]") {
continue;
}
rap_debug!("{:?}", attr_str);
let safety_attr = safety_parser::safety::parse_attr_and_get_properties(attr_str.as_str());
for par in safety_attr.iter() {
for property in par.tags.iter() {
let tag_name = property.tag.name();
let exprs = property.args.clone().into_vec();
let contract = PropertyContract::new(tcx, def_id, tag_name, &exprs);
let (local, fields) = parse_cis_local(tcx, def_id, exprs);
results.push((local, fields, contract));
}
}
}
results
}
pub fn parse_cis_local(tcx: TyCtxt, def_id: DefId, expr: Vec<Expr>) -> (usize, Vec<(usize, Ty)>) {
for e in expr {
if let Some((base, fields, _ty)) = parse_expr_into_local_and_ty(tcx, def_id, &e) {
return (base, fields);
}
}
(0, Vec::new())
}
pub fn parse_expr_into_local_and_ty<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
expr: &Expr,
) -> Option<(usize, Vec<(usize, Ty<'tcx>)>, Ty<'tcx>)> {
if let Some((base_ident, fields)) = access_ident_recursive(&expr) {
let (param_names, param_tys) = parse_signature(tcx, def_id);
if param_names[0] == "0".to_string() {
return None;
}
if let Some(param_index) = param_names.iter().position(|name| name == &base_ident) {
let mut current_ty = param_tys[param_index];
let mut field_indices = Vec::new();
for field_name in fields {
let peeled_ty = current_ty.peel_refs();
if let rustc_middle::ty::TyKind::Adt(adt_def, arg_list) = *peeled_ty.kind() {
let variant = adt_def.non_enum_variant();
if let Ok(field_idx) = field_name.parse::<usize>() {
if field_idx < variant.fields.len() {
current_ty = variant.fields[rustc_abi::FieldIdx::from_usize(field_idx)]
.ty(tcx, arg_list);
field_indices.push((field_idx, current_ty));
continue;
}
}
if let Some((idx, _)) = variant
.fields
.iter()
.enumerate()
.find(|(_, f)| f.ident(tcx).name.to_string() == field_name.clone())
{
current_ty =
variant.fields[rustc_abi::FieldIdx::from_usize(idx)].ty(tcx, arg_list);
field_indices.push((idx, current_ty));
}
else {
break; }
}
else {
break; }
}
return Some((param_index + 1, field_indices, current_ty));
}
}
None
}
pub fn parse_signature<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> (Vec<String>, Vec<Ty<'tcx>>) {
if def_id.as_local().is_some() {
return parse_local_signature(tcx, def_id);
} else {
rap_debug!("{:?} is not local def id.", def_id);
return parse_outside_signature(tcx, def_id);
};
}
fn parse_outside_signature<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> (Vec<String>, Vec<Ty<'tcx>>) {
let sig = tcx.fn_sig(def_id).skip_binder();
let param_tys: Vec<Ty<'tcx>> = sig.inputs().skip_binder().iter().copied().collect();
if let Some(args_name) = get_known_std_names(tcx, def_id) {
return (args_name, param_tys);
}
let args_name = (0..param_tys.len()).map(|i| format!("{}", i)).collect();
rap_debug!(
"function {:?} has arg: {:?}, arg types: {:?}",
def_id,
args_name,
param_tys
);
return (args_name, param_tys);
}
fn get_known_std_names<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Vec<String>> {
let std_func_name = get_cleaned_def_path_name(tcx, def_id);
let json_data: serde_json::Value = get_std_api_signature_json();
if let Some(arg_info) = json_data.get(&std_func_name) {
if let Some(args_name) = arg_info.as_array() {
if args_name.len() == 0 {
return Some(vec!["0".to_string()]);
}
let mut result = Vec::new();
for arg in args_name {
if let Some(sp_name) = arg.as_str() {
result.push(sp_name.to_string());
}
}
return Some(result);
}
}
None
}
pub fn parse_local_signature(tcx: TyCtxt, def_id: DefId) -> (Vec<String>, Vec<Ty>) {
let local_def_id = def_id.as_local().unwrap();
let hir_body = tcx.hir_body_owned_by(local_def_id);
if hir_body.params.len() == 0 {
return (vec!["0".to_string()], Vec::new());
}
let params = hir_body.params;
let typeck_results = tcx.typeck_body(hir_body.id());
let mut param_names = Vec::new();
let mut param_tys = Vec::new();
for param in params {
match param.pat.kind {
rustc_hir::PatKind::Binding(_, _, ident, _) => {
param_names.push(ident.name.to_string());
let ty = typeck_results.pat_ty(param.pat);
param_tys.push(ty);
}
_ => {
param_names.push(String::new());
param_tys.push(typeck_results.pat_ty(param.pat));
}
}
}
(param_names, param_tys)
}
pub fn access_ident_recursive(expr: &Expr) -> Option<(String, Vec<String>)> {
match expr {
Expr::Path(syn::ExprPath { path, .. }) => {
if path.segments.len() == 1 {
rap_debug!("expr2 {:?}", expr);
let ident = path.segments[0].ident.to_string();
Some((ident, Vec::new()))
} else {
None
}
}
Expr::Field(syn::ExprField { base, member, .. }) => {
let (base_ident, mut fields) =
if let Some((base_ident, fields)) = access_ident_recursive(base) {
(base_ident, fields)
} else {
return None;
};
let field_name = match member {
syn::Member::Named(ident) => ident.to_string(),
syn::Member::Unnamed(index) => index.index.to_string(),
};
fields.push(field_name);
Some((base_ident, fields))
}
_ => None,
}
}
pub fn parse_expr_into_number(expr: &Expr) -> Option<usize> {
if let Expr::Lit(expr_lit) = expr {
if let syn::Lit::Int(lit_int) = &expr_lit.lit {
return lit_int.base10_parse::<usize>().ok();
}
}
None
}
pub fn match_ty_with_ident(tcx: TyCtxt, def_id: DefId, type_ident: String) -> Option<Ty> {
if let Some(primitive_ty) = match_primitive_type(tcx, type_ident.clone()) {
return Some(primitive_ty);
}
return find_generic_param(tcx, def_id, type_ident.clone());
}
fn match_primitive_type(tcx: TyCtxt, type_ident: String) -> Option<Ty> {
match type_ident.as_str() {
"i8" => Some(tcx.types.i8),
"i16" => Some(tcx.types.i16),
"i32" => Some(tcx.types.i32),
"i64" => Some(tcx.types.i64),
"i128" => Some(tcx.types.i128),
"isize" => Some(tcx.types.isize),
"u8" => Some(tcx.types.u8),
"u16" => Some(tcx.types.u16),
"u32" => Some(tcx.types.u32),
"u64" => Some(tcx.types.u64),
"u128" => Some(tcx.types.u128),
"usize" => Some(tcx.types.usize),
"f16" => Some(tcx.types.f16),
"f32" => Some(tcx.types.f32),
"f64" => Some(tcx.types.f64),
"f128" => Some(tcx.types.f128),
"bool" => Some(tcx.types.bool),
"char" => Some(tcx.types.char),
"str" => Some(tcx.types.str_),
_ => None,
}
}
fn find_generic_param(tcx: TyCtxt, def_id: DefId, type_ident: String) -> Option<Ty> {
rap_debug!(
"Searching for generic param: {} in {:?}",
type_ident,
def_id
);
let (_, param_tys) = parse_signature(tcx, def_id);
rap_debug!("Function parameter types: {:?} of {:?}", param_tys, def_id);
for &ty in ¶m_tys {
if let Some(found) = find_generic_in_ty(tcx, ty, &type_ident) {
return Some(found);
}
}
None
}
fn find_generic_in_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, type_ident: &str) -> Option<Ty<'tcx>> {
match ty.kind() {
TyKind::Param(param_ty) => {
if param_ty.name.as_str() == type_ident {
return Some(ty);
}
}
TyKind::RawPtr(ty, _)
| TyKind::Ref(_, ty, _)
| TyKind::Slice(ty)
| TyKind::Array(ty, _) => {
if let Some(found) = find_generic_in_ty(tcx, *ty, type_ident) {
return Some(found);
}
}
TyKind::Tuple(tys) => {
for tuple_ty in tys.iter() {
if let Some(found) = find_generic_in_ty(tcx, tuple_ty, type_ident) {
return Some(found);
}
}
}
TyKind::Adt(adt_def, substs) => {
let name = tcx.item_name(adt_def.did()).to_string();
if name == type_ident {
return Some(ty);
}
for field in adt_def.all_fields() {
let field_ty = field.ty(tcx, substs);
if let Some(found) = find_generic_in_ty(tcx, field_ty, type_ident) {
return Some(found);
}
}
}
_ => {}
}
None
}
pub fn reflect_generic<'tcx>(
generic_mapping: &FxHashMap<String, Ty<'tcx>>,
func_name: &str,
ty: Ty<'tcx>,
) -> Ty<'tcx> {
let mut actual_ty = ty;
match ty.kind() {
TyKind::Param(param_ty) => {
let generic_name = param_ty.name.to_string();
if let Some(actual_ty_from_map) = generic_mapping.get(&generic_name) {
actual_ty = *actual_ty_from_map;
}
}
_ => {}
}
rap_debug!(
"peel generic ty for {:?}, actual_ty is {:?}",
func_name,
actual_ty
);
actual_ty
}
pub fn has_tainted_fields(tcx: TyCtxt, def_id: DefId, src_var: u32) -> bool {
let mut dataflow_analyzer = DataFlowAnalyzer::new(tcx, false);
dataflow_analyzer.build_graph(def_id);
let body = tcx.optimized_mir(def_id);
let params = &body.args_iter().collect::<Vec<_>>();
rap_info!("params {:?}", params);
let self_local = Local::from(src_var);
let flowing_params: Vec<Local> = params
.iter()
.filter(|&¶m_local| {
dataflow_analyzer.has_flow_between(def_id, self_local, param_local)
&& self_local != param_local
})
.copied()
.collect();
if !flowing_params.is_empty() {
rap_info!(
"Taint flow found from self to other parameters: {:?}",
flowing_params
);
true
} else {
false
}
}
pub fn get_all_std_unsafe_chains(tcx: TyCtxt, def_id: DefId) -> Vec<Vec<String>> {
let mut results = Vec::new();
let mut visited = HashSet::new(); let mut current_chain = Vec::new();
dfs_find_unsafe_chains(tcx, def_id, &mut current_chain, &mut results, &mut visited);
results
}
fn dfs_find_unsafe_chains(
tcx: TyCtxt,
def_id: DefId,
current_chain: &mut Vec<String>,
results: &mut Vec<Vec<String>>,
visited: &mut HashSet<DefId>,
) {
if visited.contains(&def_id) {
return;
}
visited.insert(def_id);
let current_func_name = get_cleaned_def_path_name(tcx, def_id);
current_chain.push(current_func_name.clone());
let unsafe_callees = find_unsafe_callees_in_function(tcx, def_id);
if unsafe_callees.is_empty() {
results.push(current_chain.clone());
} else {
for (callee_def_id, callee_name) in unsafe_callees {
dfs_find_unsafe_chains(tcx, callee_def_id, current_chain, results, visited);
}
}
current_chain.pop();
visited.remove(&def_id);
}
fn find_unsafe_callees_in_function(tcx: TyCtxt, def_id: DefId) -> Vec<(DefId, String)> {
let mut callees = Vec::new();
if let Some(body) = try_get_mir(tcx, def_id) {
for bb in body.basic_blocks.iter() {
if let Some(terminator) = &bb.terminator {
if let Some((callee_def_id, callee_name)) = extract_unsafe_callee(tcx, terminator) {
callees.push((callee_def_id, callee_name));
}
}
}
}
callees
}
fn extract_unsafe_callee(tcx: TyCtxt<'_>, terminator: &Terminator<'_>) -> Option<(DefId, String)> {
if let TerminatorKind::Call { func, .. } = &terminator.kind {
if let Operand::Constant(func_constant) = func {
if let ty::FnDef(callee_def_id, _) = func_constant.const_.ty().kind() {
if check_safety(tcx, *callee_def_id) == Safety::Unsafe {
let func_name = get_cleaned_def_path_name(tcx, *callee_def_id);
return Some((*callee_def_id, func_name));
}
}
}
}
None
}
fn try_get_mir(tcx: TyCtxt<'_>, def_id: DefId) -> Option<&rustc_middle::mir::Body<'_>> {
if tcx.is_mir_available(def_id) {
Some(tcx.optimized_mir(def_id))
} else {
None
}
}
pub fn get_cleaned_def_path_name(tcx: TyCtxt<'_>, def_id: DefId) -> String {
let def_id_str = format!("{:?}", def_id);
let mut parts: Vec<&str> = def_id_str.split("::").collect();
let mut remove_first = false;
if let Some(first_part) = parts.get_mut(0) {
if first_part.contains("core") {
*first_part = "core";
} else if first_part.contains("std") {
*first_part = "std";
} else if first_part.contains("alloc") {
*first_part = "alloc";
} else {
remove_first = true;
}
}
if remove_first && !parts.is_empty() {
parts.remove(0);
}
let new_parts: Vec<String> = parts
.into_iter()
.filter_map(|s| {
if s.contains("{") {
if remove_first {
get_struct_name(tcx, def_id)
} else {
None
}
} else {
Some(s.to_string())
}
})
.collect();
let mut cleaned_path = new_parts.join("::");
cleaned_path = cleaned_path.trim_end_matches(')').to_string();
cleaned_path
}
pub fn print_unsafe_chains(chains: &[Vec<String>]) {
if chains.is_empty() {
return;
}
println!("==============================");
println!("Found {} unsafe call chain(s):", chains.len());
for (i, chain) in chains.iter().enumerate() {
println!("Chain {}:", i + 1);
for (j, func_name) in chain.iter().enumerate() {
let indent = " ".repeat(j);
println!("{}{}-> {}", indent, if j > 0 { " " } else { "" }, func_name);
}
println!();
}
}
pub fn get_all_std_fns_by_rustc_public(tcx: TyCtxt) -> Vec<DefId> {
let mut all_std_fn_def = Vec::new();
let mut results = Vec::new();
let mut core_fn_def: Vec<_> = rustc_public::find_crates("core")
.iter()
.flat_map(|krate| krate.fn_defs())
.collect();
let mut std_fn_def: Vec<_> = rustc_public::find_crates("std")
.iter()
.flat_map(|krate| krate.fn_defs())
.collect();
let mut alloc_fn_def: Vec<_> = rustc_public::find_crates("alloc")
.iter()
.flat_map(|krate| krate.fn_defs())
.collect();
all_std_fn_def.append(&mut core_fn_def);
all_std_fn_def.append(&mut std_fn_def);
all_std_fn_def.append(&mut alloc_fn_def);
for fn_def in &all_std_fn_def {
let def_id = crate::def_id::to_internal(fn_def, tcx);
results.push(def_id);
}
results
}
pub fn generate_mir_cfg_dot<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
alias_sets: &Vec<FxHashSet<usize>>,
) -> Result<(), std::io::Error> {
let mir = tcx.optimized_mir(def_id);
let mut dot_content = String::new();
let alias_info_str = format!("Alias Sets: {:?}", alias_sets);
dot_content.push_str(&format!(
"digraph mir_cfg_{} {{\n",
get_cleaned_def_path_name(tcx, def_id)
));
dot_content.push_str(&format!(
" label = \"MIR CFG for {}\\n{}\\n\";\n",
tcx.def_path_str(def_id),
alias_info_str.replace("\"", "\\\"")
));
dot_content.push_str(" labelloc = \"t\";\n");
dot_content.push_str(" node [shape=box, fontname=\"Courier\", align=\"left\"];\n\n");
for (bb_index, bb_data) in mir.basic_blocks.iter_enumerated() {
let mut lines: Vec<String> = bb_data
.statements
.iter()
.map(|stmt| format!("{:?}", stmt))
.collect();
let mut node_style = String::new();
if let Some(terminator) = &bb_data.terminator {
let mut is_drop_related = false;
match &terminator.kind {
TerminatorKind::Drop { .. } => {
is_drop_related = true;
}
TerminatorKind::Call { func, .. } => {
if let Operand::Constant(c) = func {
if let ty::FnDef(def_id, _) = *c.ty().kind() {
if def_id == drop()
|| def_id == drop_in_place()
|| def_id == manually_drop()
|| dealloc_opt().map(|f| f == def_id).unwrap_or(false)
{
is_drop_related = true;
}
}
}
}
_ => {}
}
if is_drop_related {
node_style = ", style=\"filled\", fillcolor=\"#ffdddd\", color=\"red\"".to_string();
}
lines.push(format!("{:?}", terminator.kind));
} else {
lines.push("(no terminator)".to_string());
}
let label_content = lines.join("\\l");
let node_label = format!("BB{}:\\l{}\\l", bb_index.index(), label_content);
dot_content.push_str(&format!(
" BB{} [label=\"{}\"{}];\n",
bb_index.index(),
node_label.replace("\"", "\\\""),
node_style
));
if let Some(terminator) = &bb_data.terminator {
for target in terminator.successors() {
let edge_label = match terminator.kind {
_ => "".to_string(),
};
dot_content.push_str(&format!(
" BB{} -> BB{} [label=\"{}\"];\n",
bb_index.index(),
target.index(),
edge_label
));
}
}
}
dot_content.push_str("}\n");
let name = get_cleaned_def_path_name(tcx, def_id);
render_dot_string(name, dot_content);
rap_debug!("render dot for {:?}", def_id);
Ok(())
}
pub fn get_all_mutable_methods(tcx: TyCtxt, src_def_id: DefId) -> HashMap<DefId, HashSet<usize>> {
let mut std_results = HashMap::new();
if get_type(tcx, src_def_id) == FnKind::Constructor {
return std_results;
}
let all_std_fn_def = get_all_std_fns_by_rustc_public(tcx);
let target_adt_def = get_adt_def_id_by_adt_method(tcx, src_def_id);
let mut is_std = false;
for &def_id in &all_std_fn_def {
let adt_def = get_adt_def_id_by_adt_method(tcx, def_id);
if adt_def.is_some() && adt_def == target_adt_def && src_def_id != def_id {
if has_mut_self_param(tcx, def_id) {
std_results.insert(def_id, HashSet::new());
}
is_std = true;
}
}
if is_std {
return std_results;
}
let mut results = HashMap::new();
let public_fields = target_adt_def.map_or_else(HashSet::new, |def| get_public_fields(tcx, def));
let impl_vec = target_adt_def.map_or_else(Vec::new, |def| get_impls_for_struct(tcx, def));
for impl_id in impl_vec {
if !matches!(tcx.def_kind(impl_id), rustc_hir::def::DefKind::Impl { .. }) {
continue;
}
let associated_items = tcx.associated_items(impl_id);
for item in associated_items.in_definition_order() {
if let ty::AssocKind::Fn {
name: _,
has_self: _,
} = item.kind
{
let item_def_id = item.def_id;
if has_mut_self_param(tcx, item_def_id) {
let modified_fields = public_fields.clone();
results.insert(item_def_id, modified_fields);
}
}
}
}
results
}
pub fn get_cons(tcx: TyCtxt<'_>, def_id: DefId) -> Vec<DefId> {
let mut cons = Vec::new();
if tcx.def_kind(def_id) == DefKind::Fn || get_type(tcx, def_id) == FnKind::Constructor {
return cons;
}
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty = tcx.type_of(impl_id).skip_binder();
if let Some(adt_def) = ty.ty_adt_def() {
let adt_def_id = adt_def.did();
let impls = tcx.inherent_impls(adt_def_id);
for impl_def_id in impls {
for item in tcx.associated_item_def_ids(impl_def_id) {
if (tcx.def_kind(item) == DefKind::Fn
|| tcx.def_kind(item) == DefKind::AssocFn)
&& get_type(tcx, *item) == FnKind::Constructor
{
cons.push(*item);
}
}
}
}
}
}
cons
}
pub fn append_fn_with_types(tcx: TyCtxt, def_id: DefId) -> FnInfo {
FnInfo::new(def_id, check_safety(tcx, def_id), get_type(tcx, def_id))
}
pub fn search_constructor(tcx: TyCtxt, def_id: DefId) -> Vec<DefId> {
let mut constructors = Vec::new();
if let Some(assoc_item) = tcx.opt_associated_item(def_id) {
if let Some(impl_id) = assoc_item.impl_container(tcx) {
let ty = tcx.type_of(impl_id).skip_binder();
if let Some(adt_def) = ty.ty_adt_def() {
let adt_def_id = adt_def.did();
let impl_vec = get_impls_for_struct(tcx, adt_def_id);
for impl_id in impl_vec {
let associated_items = tcx.associated_items(impl_id);
for item in associated_items.in_definition_order() {
if let ty::AssocKind::Fn {
name: _,
has_self: _,
} = item.kind
{
let item_def_id = item.def_id;
if get_type(tcx, item_def_id) == FnKind::Constructor {
constructors.push(item_def_id);
}
}
}
}
}
}
}
constructors
}
pub fn get_ptr_deref_dummy_def_id(tcx: TyCtxt<'_>) -> Option<DefId> {
tcx.hir_crate_items(()).free_items().find_map(|item_id| {
let def_id = item_id.owner_id.to_def_id();
let name = tcx.opt_item_name(def_id)?;
(name.as_str() == "__raw_ptr_deref_dummy").then_some(def_id)
})
}