use super::events::extract_sink_arg_positions;
use super::state::{BindingKey, SsaTaintState};
use super::{
SsaTaintEvent, SsaTaintTransfer, detect_variant_inner_fact, run_ssa_taint_full, transfer_block,
transfer_inst,
};
use crate::cfg::{BodyId, Cfg, FuncSummaries};
use crate::labels::{Cap, SourceKind};
use crate::ssa::ir::{SsaBody, SsaOp, SsaValue, Terminator};
use crate::summary::GlobalSummaries;
use crate::symbol::Lang;
use crate::taint::domain::{TaintOrigin, VarTaint};
use petgraph::graph::NodeIndex;
use smallvec::SmallVec;
use std::collections::{HashMap, HashSet};
const MAX_PROBE_PARAMS: usize = 8;
#[allow(clippy::too_many_arguments)]
pub fn extract_ssa_func_summary(
ssa: &SsaBody,
cfg: &Cfg,
local_summaries: &FuncSummaries,
global_summaries: Option<&GlobalSummaries>,
lang: Lang,
namespace: &str,
interner: &crate::state::symbol::SymbolInterner,
param_count: usize,
module_aliases: Option<&HashMap<SsaValue, SmallVec<[String; 2]>>>,
locator: Option<&crate::summary::SinkSiteLocator<'_>>,
formal_param_names: Option<&[String]>,
formal_destructured_fields: Option<&[Vec<String>]>,
) -> crate::summary::ssa_summary::SsaFuncSummary {
extract_ssa_func_summary_full(
ssa,
cfg,
local_summaries,
global_summaries,
lang,
namespace,
interner,
param_count,
module_aliases,
locator,
formal_param_names,
None,
formal_destructured_fields,
)
}
#[allow(clippy::too_many_arguments)]
pub fn extract_ssa_func_summary_full(
ssa: &SsaBody,
cfg: &Cfg,
local_summaries: &FuncSummaries,
global_summaries: Option<&GlobalSummaries>,
lang: Lang,
namespace: &str,
interner: &crate::state::symbol::SymbolInterner,
param_count: usize,
module_aliases: Option<&HashMap<SsaValue, SmallVec<[String; 2]>>>,
locator: Option<&crate::summary::SinkSiteLocator<'_>>,
formal_param_names: Option<&[String]>,
ssa_summaries: Option<
&HashMap<crate::symbol::FuncKey, crate::summary::ssa_summary::SsaFuncSummary>,
>,
formal_destructured_fields: Option<&[Vec<String>]>,
) -> crate::summary::ssa_summary::SsaFuncSummary {
use crate::summary::SinkSite;
use crate::summary::ssa_summary::{SsaFuncSummary, TaintTransform};
let effective_params = param_count.min(MAX_PROBE_PARAMS);
let mut param_info: Vec<(usize, String, SsaValue)> = Vec::new();
for block in &ssa.blocks {
for inst in block.phis.iter().chain(block.body.iter()) {
if let SsaOp::Param { index } = &inst.op {
if *index < effective_params {
if let Some(name) = inst.var_name.as_ref() {
param_info.push((*index, name.clone(), inst.value));
}
}
}
}
}
let return_blocks: Vec<usize> = ssa
.blocks
.iter()
.enumerate()
.filter(|(_, b)| matches!(b.terminator, Terminator::Return(_)))
.map(|(i, _)| i)
.collect();
let all_param_values: std::collections::HashSet<SsaValue> =
param_info.iter().map(|(_, _, v)| *v).collect();
struct ReturnBlockObs {
derived_caps: Cap,
param_caps: Cap,
predicate_hash: u64,
known_true: u8,
known_false: u8,
abstract_value: Option<crate::abstract_interp::AbstractValue>,
path_fact: crate::abstract_interp::PathFact,
variant_inner_fact: Option<crate::abstract_interp::PathFact>,
param_validated_must: bool,
}
let run_probe = |seed: HashMap<BindingKey, VarTaint>,
probe_param_names: Option<&[&str]>|
-> (
Cap,
Vec<SsaTaintEvent>,
Option<crate::abstract_interp::AbstractValue>,
Vec<ReturnBlockObs>,
) {
let seed_ref = if seed.is_empty() { None } else { Some(&seed) };
let transfer = SsaTaintTransfer {
lang,
namespace,
interner,
local_summaries,
global_summaries,
interop_edges: &[],
owner_body_id: BodyId(0),
parent_body_id: None,
global_seed: seed_ref,
param_seed: None,
receiver_seed: None,
const_values: None,
type_facts: None,
ssa_summaries,
extra_labels: None,
base_aliases: None,
callee_bodies: None,
inline_cache: None,
context_depth: 0,
callback_bindings: None,
points_to: None,
dynamic_pts: None,
import_bindings: None,
promisify_aliases: None,
module_aliases,
static_map: None,
auto_seed_handler_params: false,
cross_file_bodies: None,
pointer_facts: None,
};
let (events, block_states) = run_ssa_taint_full(ssa, cfg, &transfer);
let mut total_derived_caps = Cap::empty();
let mut total_param_caps = Cap::empty();
let mut return_abstract: Option<crate::abstract_interp::AbstractValue> = None;
let mut per_return: Vec<ReturnBlockObs> = Vec::with_capacity(return_blocks.len());
for &bid in &return_blocks {
if let Some(entry) = &block_states[bid] {
let empty_induction = HashSet::new();
let exit = transfer_block(
&ssa.blocks[bid],
cfg,
ssa,
&transfer,
entry.clone(),
&empty_induction,
None,
);
let ret_val = match &ssa.blocks[bid].terminator {
Terminator::Return(rv) => rv.as_ref().copied(),
_ => None,
};
let mut block_derived_caps = Cap::empty();
let mut block_param_caps = Cap::empty();
if let Some(rv) = ret_val {
if let Some(taint) = exit.get(rv) {
if all_param_values.contains(&rv) {
block_param_caps |= taint.caps;
} else {
block_derived_caps |= taint.caps;
}
}
let rv_is_const = ssa.blocks[bid]
.body
.iter()
.chain(ssa.blocks[bid].phis.iter())
.any(|inst| inst.value == rv && matches!(inst.op, SsaOp::Const(_)));
if !all_param_values.contains(&rv) && !rv_is_const {
for (val, taint) in &exit.values {
if all_param_values.contains(val) {
block_param_caps |= taint.caps;
}
}
}
} else {
for (val, taint) in &exit.values {
if all_param_values.contains(val) {
block_param_caps |= taint.caps;
} else {
block_derived_caps |= taint.caps;
}
}
}
total_derived_caps |= block_derived_caps;
total_param_caps |= block_param_caps;
let mut block_abs: Option<crate::abstract_interp::AbstractValue> = None;
let mut block_path_fact = crate::abstract_interp::PathFact::top();
let mut block_variant_inner: Option<crate::abstract_interp::PathFact> = None;
if let Some(ref abs) = exit.abstract_state {
let abs_rv = ret_val.or_else(|| {
ssa.blocks[bid]
.body
.last()
.or_else(|| ssa.blocks[bid].phis.last())
.map(|inst| inst.value)
});
if let Some(rv) = abs_rv {
let av = abs.get(rv);
block_path_fact = av.path.clone();
if !av.is_top() {
block_abs = Some(av.clone());
return_abstract = Some(match return_abstract {
None => av,
Some(prev) => prev.join(&av),
});
}
block_variant_inner = detect_variant_inner_fact(rv, ssa, &exit);
}
}
let (predicate_hash, known_true, known_false) = summarise_return_predicates(&exit);
let param_validated_must = match probe_param_names {
Some(names) => names.iter().any(|name| match interner.get(name) {
Some(sym) => exit.validated_must.contains(sym),
None => false,
}),
None => false,
};
per_return.push(ReturnBlockObs {
derived_caps: block_derived_caps,
param_caps: block_param_caps,
predicate_hash,
known_true,
known_false,
abstract_value: block_abs,
path_fact: block_path_fact,
variant_inner_fact: block_variant_inner,
param_validated_must,
});
}
}
let return_caps = if !total_derived_caps.is_empty() {
total_derived_caps
} else {
total_param_caps
};
let return_abstract = return_abstract.filter(|v| !v.is_top());
(return_caps, events, return_abstract, per_return)
};
let (baseline_return_caps, _baseline_events, return_abstract, baseline_obs) =
run_probe(HashMap::new(), None);
let source_caps = baseline_return_caps;
let mut return_path_facts: SmallVec<[crate::summary::ssa_summary::PathFactReturnEntry; 2]> =
SmallVec::new();
if baseline_obs.len() >= 2 {
let mut merged: SmallVec<[crate::summary::ssa_summary::PathFactReturnEntry; 2]> =
SmallVec::new();
for obs in &baseline_obs {
let entry = crate::summary::ssa_summary::PathFactReturnEntry {
predicate_hash: obs.predicate_hash,
known_true: obs.known_true,
known_false: obs.known_false,
path_fact: obs.path_fact.clone(),
variant_inner_fact: obs.variant_inner_fact.clone(),
};
crate::summary::ssa_summary::merge_path_fact_return_paths(&mut merged, &[entry]);
}
let distinct_hashes = merged
.iter()
.map(|e| e.predicate_hash)
.collect::<std::collections::HashSet<_>>();
let has_signal = merged
.iter()
.any(|e| !e.path_fact.is_top() || e.variant_inner_fact.is_some());
if distinct_hashes.len() >= 2 && has_signal {
return_path_facts = merged;
}
}
let mut param_to_return = Vec::new();
let mut param_to_sink: Vec<(usize, SmallVec<[SinkSite; 1]>)> = Vec::new();
let mut param_to_sink_param = Vec::new();
let mut param_to_gate_filters: Vec<(usize, Cap)> = Vec::new();
let mut param_return_paths: Vec<(
usize,
SmallVec<[crate::summary::ssa_summary::ReturnPathTransform; 2]>,
)> = Vec::new();
let mut validated_params_to_return: SmallVec<[usize; 2]> = SmallVec::new();
for &(idx, ref var_name, _ssa_val) in ¶m_info {
let mut seed = HashMap::new();
let origin = TaintOrigin {
node: NodeIndex::new(0), source_kind: SourceKind::UserInput,
source_span: None,
};
let probe_taint = VarTaint {
caps: Cap::all(),
origins: SmallVec::from_elem(origin, 1),
uses_summary: false,
};
seed.insert(
BindingKey::new(var_name.as_str(), BodyId(0)),
probe_taint.clone(),
);
let slot_siblings: &[String] = formal_destructured_fields
.and_then(|d| d.get(idx))
.map(|v| v.as_slice())
.unwrap_or(&[]);
for sib in slot_siblings {
seed.insert(
BindingKey::new(sib.as_str(), BodyId(0)),
probe_taint.clone(),
);
}
let prefixes: Vec<String> = std::iter::once(var_name.clone())
.chain(slot_siblings.iter().cloned())
.map(|n| format!("{}.", n))
.collect();
for block in &ssa.blocks {
for inst in block.phis.iter().chain(block.body.iter()) {
if let SsaOp::Param { .. } = &inst.op {
if let Some(name) = inst.var_name.as_ref() {
if prefixes.iter().any(|p| name.starts_with(p)) {
seed.insert(
BindingKey::new(name.as_str(), BodyId(0)),
probe_taint.clone(),
);
}
}
}
}
}
let mut slot_names: Vec<&str> = Vec::with_capacity(1 + slot_siblings.len());
slot_names.push(var_name.as_str());
for sib in slot_siblings {
slot_names.push(sib.as_str());
}
let (return_caps, events, _, per_return_obs) = run_probe(seed, Some(slot_names.as_slice()));
let param_return_caps = return_caps & !source_caps;
if !param_return_caps.is_empty() {
let stripped = Cap::all() & !param_return_caps;
let transform = if stripped.is_empty() {
TaintTransform::Identity
} else {
TaintTransform::StripBits(stripped)
};
param_to_return.push((idx, transform));
}
if !param_return_caps.is_empty() && !per_return_obs.is_empty() {
let mut any_carrying_path = false;
let all_carrying_validated = per_return_obs.iter().all(|obs| {
let carries = !(obs.derived_caps & !source_caps).is_empty()
|| !(obs.param_caps & !source_caps).is_empty();
if carries {
any_carrying_path = true;
obs.param_validated_must
} else {
true
}
});
if any_carrying_path && all_carrying_validated {
validated_params_to_return.push(idx);
}
if std::env::var("NYX_DBG_VPR2").is_ok() {
eprintln!(
"VPR2 fp={:?} idx={} name={} any_carry={} all_validated={}",
formal_param_names, idx, var_name, any_carrying_path, all_carrying_validated
);
for (i, obs) in per_return_obs.iter().enumerate() {
eprintln!(
" ret[{}] derived={:?} param={:?} validated_must={}",
i, obs.derived_caps, obs.param_caps, obs.param_validated_must
);
}
}
}
if per_return_obs.len() >= 2 {
let mut per_path: SmallVec<[crate::summary::ssa_summary::ReturnPathTransform; 2]> =
SmallVec::new();
for obs in &per_return_obs {
let block_return_caps = if !obs.derived_caps.is_empty() {
obs.derived_caps
} else {
obs.param_caps
};
let block_contributed = block_return_caps & !source_caps;
let transform_kind = if block_contributed.is_empty() {
TaintTransform::StripBits(Cap::all())
} else {
let stripped = Cap::all() & !block_contributed;
if stripped.is_empty() {
TaintTransform::Identity
} else {
TaintTransform::StripBits(stripped)
}
};
crate::summary::ssa_summary::merge_return_paths(
&mut per_path,
&[crate::summary::ssa_summary::ReturnPathTransform {
transform: transform_kind,
path_predicate_hash: obs.predicate_hash,
known_true: obs.known_true,
known_false: obs.known_false,
abstract_contribution: obs.abstract_value.clone(),
}],
);
}
let distinct_hashes = per_path
.iter()
.map(|e| e.path_predicate_hash)
.collect::<std::collections::HashSet<_>>();
if distinct_hashes.len() >= 2 {
param_return_paths.push((idx, per_path));
}
}
let mut param_sites: SmallVec<[SinkSite; 1]> = SmallVec::new();
for event in &events {
if event.all_validated {
continue;
}
for pos in extract_sink_arg_positions(event, ssa) {
param_to_sink_param.push((idx, pos, event.sink_caps));
}
if !event.sink_caps.is_empty()
&& cfg[event.sink_node].call.gate_filters.len() > 1
&& !param_to_gate_filters
.iter()
.any(|&(i, c)| i == idx && c == event.sink_caps)
{
param_to_gate_filters.push((idx, event.sink_caps));
}
if event.sink_caps.is_empty() {
continue;
}
let site = match locator {
Some(loc) => {
loc.site_for_span(cfg[event.sink_node].classification_span(), event.sink_caps)
}
None => SinkSite::cap_only(event.sink_caps),
};
let key = site.dedup_key();
if !param_sites.iter().any(|s| s.dedup_key() == key) {
param_sites.push(site);
}
}
if !param_sites.is_empty() {
param_to_sink.push((idx, param_sites));
}
}
let (param_container_to_return, param_to_container_store) =
extract_container_flow_summary(ssa, lang, effective_params);
let points_to = crate::ssa::param_points_to::analyse_param_points_to(
ssa,
¶m_info,
effective_params,
formal_param_names,
Some(lang),
);
let return_type = infer_summary_return_type(ssa, lang);
let source_to_callback = if !source_caps.is_empty() && !param_info.is_empty() {
let baseline_transfer = SsaTaintTransfer {
lang,
namespace,
interner,
local_summaries,
global_summaries,
interop_edges: &[],
owner_body_id: BodyId(0),
parent_body_id: None,
global_seed: None,
param_seed: None,
receiver_seed: None,
const_values: None,
type_facts: None,
ssa_summaries,
extra_labels: None,
base_aliases: None,
callee_bodies: None,
inline_cache: None,
context_depth: 0,
callback_bindings: None,
points_to: None,
dynamic_pts: None,
import_bindings: None,
promisify_aliases: None,
module_aliases: None,
static_map: None,
auto_seed_handler_params: false,
cross_file_bodies: None,
pointer_facts: None,
};
detect_source_to_callback_from_states(
ssa,
cfg,
source_caps,
¶m_info,
&baseline_transfer,
)
} else {
vec![]
};
let abstract_transfer = derive_abstract_transfer(ssa, ¶m_info, return_abstract.as_ref());
SsaFuncSummary {
param_to_return,
param_to_sink,
source_caps,
param_to_sink_param,
param_to_gate_filters,
param_container_to_return,
param_to_container_store,
return_type,
return_abstract,
source_to_callback,
receiver_to_return: None,
receiver_to_sink: Cap::empty(),
abstract_transfer,
param_return_paths,
return_path_facts,
points_to,
field_points_to: crate::summary::points_to::FieldPointsToSummary::empty(),
typed_call_receivers: Vec::new(),
validated_params_to_return,
}
}
pub(super) fn summarise_return_predicates(state: &SsaTaintState) -> (u64, u8, u8) {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
if state.predicates.is_empty() && state.validated_must.is_empty() {
return (0, 0, 0);
}
let mut h = DefaultHasher::new();
state.validated_must.bits().hash(&mut h);
let mut sorted: smallvec::SmallVec<[(u32, u8, u8); 4]> = state
.predicates
.iter()
.map(|(id, s)| (id.0, s.known_true, s.known_false))
.collect();
sorted.sort_by_key(|(id, _, _)| *id);
for (id, kt, kf) in &sorted {
id.hash(&mut h);
kt.hash(&mut h);
kf.hash(&mut h);
}
let hash = h.finish();
let known_true = sorted
.iter()
.map(|(_, kt, _)| *kt)
.fold(u8::MAX, |a, b| a & b);
let known_false = sorted
.iter()
.map(|(_, _, kf)| *kf)
.fold(u8::MAX, |a, b| a & b);
let hash = if hash == 0 { 1 } else { hash };
(hash, known_true, known_false)
}
fn derive_abstract_transfer(
ssa: &SsaBody,
param_info: &[(usize, String, SsaValue)],
return_abstract: Option<&crate::abstract_interp::AbstractValue>,
) -> Vec<(usize, crate::abstract_interp::AbstractTransfer)> {
use crate::abstract_interp::{AbstractTransfer, IntervalTransfer, StringTransfer};
if param_info.is_empty() {
return Vec::new();
}
let mut defs: HashMap<SsaValue, &SsaOp> = HashMap::new();
for block in &ssa.blocks {
for inst in block.phis.iter().chain(block.body.iter()) {
defs.insert(inst.value, &inst.op);
}
}
fn trace_to_param(
v: SsaValue,
defs: &HashMap<SsaValue, &SsaOp>,
depth: usize,
) -> Option<usize> {
const MAX_DEPTH: usize = 8;
if depth > MAX_DEPTH {
return None;
}
match defs.get(&v)? {
SsaOp::Param { index } => Some(*index),
SsaOp::Assign(ops) if ops.len() == 1 => trace_to_param(ops[0], defs, depth + 1),
SsaOp::Phi(preds) => {
let mut result: Option<usize> = None;
for (_, pv) in preds {
let p = trace_to_param(*pv, defs, depth + 1)?;
match result {
None => result = Some(p),
Some(existing) if existing == p => {}
Some(_) => return None,
}
}
result
}
_ => None,
}
}
let mut identity_param: Option<usize> = None;
let mut identity_consistent = true;
for block in &ssa.blocks {
if let Terminator::Return(Some(rv)) = &block.terminator {
let traced = trace_to_param(*rv, &defs, 0);
match (identity_param, traced) {
(None, Some(p)) => identity_param = Some(p),
(Some(existing), Some(p)) if existing == p => {}
_ => {
identity_consistent = false;
break;
}
}
}
}
let baseline_invariant: Option<AbstractTransfer> = return_abstract.map(|av| {
let interval = match (av.interval.lo, av.interval.hi) {
(Some(lo), Some(hi)) if lo <= hi => IntervalTransfer::Clamped { lo, hi },
_ => IntervalTransfer::Top,
};
let string = match &av.string.prefix {
Some(p) if !p.is_empty() => StringTransfer::literal_prefix(p),
_ => StringTransfer::Unknown,
};
AbstractTransfer { interval, string }
});
let mut result: Vec<(usize, AbstractTransfer)> = Vec::new();
for (idx, _, _) in param_info {
let mut transfer = AbstractTransfer::top();
if identity_consistent && identity_param == Some(*idx) {
transfer.interval = IntervalTransfer::Identity;
transfer.string = StringTransfer::Identity;
} else if let Some(base) = baseline_invariant.as_ref() {
transfer = base.clone();
}
if !transfer.is_top() {
result.push((*idx, transfer));
}
}
result
}
fn detect_source_to_callback_from_states(
ssa: &SsaBody,
cfg: &Cfg,
source_caps: Cap,
param_info: &[(usize, String, SsaValue)],
transfer: &SsaTaintTransfer,
) -> Vec<(usize, Cap)> {
use crate::ssa::ir::SsaOp;
let param_name_to_index: HashMap<&str, usize> = param_info
.iter()
.map(|(idx, name, _)| (name.as_str(), *idx))
.collect();
let (_events, block_states) = run_ssa_taint_full(ssa, cfg, transfer);
let mut result: Vec<(usize, Cap)> = vec![];
for (bid, block) in ssa.blocks.iter().enumerate() {
let Some(entry_state) = &block_states[bid] else {
continue;
};
let mut state = entry_state.clone();
for inst in &block.body {
transfer_inst(inst, cfg, ssa, transfer, &mut state);
if let SsaOp::Call { callee, args, .. } = &inst.op {
if let Some(¶m_idx) = param_name_to_index.get(callee.as_str()) {
let any_arg_tainted = args.iter().any(|arg_vals| {
arg_vals
.iter()
.any(|v| state.get(*v).is_some_and(|t| !t.caps.is_empty()))
});
if any_arg_tainted && !result.iter().any(|(idx, _)| *idx == param_idx) {
result.push((param_idx, source_caps));
}
}
}
}
}
result
}
fn infer_summary_return_type(
ssa: &SsaBody,
lang: Lang,
) -> Option<crate::ssa::type_facts::TypeKind> {
for block in &ssa.blocks {
if !matches!(block.terminator, Terminator::Return(_)) {
continue;
}
if let Some(inst) = block.body.last()
&& let SsaOp::Call { callee, .. } = &inst.op
&& let Some(ty) = crate::ssa::type_facts::constructor_type(lang, callee)
{
return Some(ty);
}
}
None
}
fn build_inst_map(ssa: &SsaBody) -> HashMap<SsaValue, (SsaOp, Option<SsaValue>)> {
let mut map = HashMap::new();
for block in &ssa.blocks {
for inst in block.phis.iter().chain(block.body.iter()) {
map.insert(inst.value, (inst.op.clone(), None));
}
}
map
}
fn trace_to_param(
v: SsaValue,
ssa: &SsaBody,
inst_map: &HashMap<SsaValue, (SsaOp, Option<SsaValue>)>,
visited: &mut HashSet<SsaValue>,
) -> Option<usize> {
if !visited.insert(v) {
return None;
}
let (op, _) = inst_map.get(&v)?;
match op {
SsaOp::Param { index } => Some(*index),
SsaOp::Assign(uses) => {
for u in uses {
if let Some(idx) = trace_to_param(*u, ssa, inst_map, visited) {
return Some(idx);
}
}
None
}
SsaOp::Phi(operands) => {
for (_, op_val) in operands {
if let Some(idx) = trace_to_param(*op_val, ssa, inst_map, visited) {
return Some(idx);
}
}
None
}
_ => None,
}
}
pub(crate) fn extract_container_flow_summary(
ssa: &SsaBody,
lang: Lang,
formal_param_count: usize,
) -> (Vec<usize>, Vec<(usize, usize)>) {
use crate::ssa::pointsto::{ContainerOp, classify_container_op};
let inst_map = build_inst_map(ssa);
let mut container_to_return: HashSet<usize> = HashSet::new();
let mut container_store: Vec<(usize, usize)> = Vec::new();
for block in &ssa.blocks {
if !matches!(block.terminator, Terminator::Return(_)) {
continue;
}
for inst in block.phis.iter().chain(block.body.iter()) {
match &inst.op {
SsaOp::Assign(_) | SsaOp::Phi(_) => {
if let Some(idx) =
trace_to_param(inst.value, ssa, &inst_map, &mut HashSet::new())
&& idx < formal_param_count
{
container_to_return.insert(idx);
}
}
_ => {}
}
}
}
for block in &ssa.blocks {
for inst in block.body.iter() {
if let SsaOp::Call {
callee,
args,
receiver,
..
} = &inst.op
{
let op = match classify_container_op(callee, lang) {
Some(ContainerOp::Store { value_args, .. }) => value_args,
_ => continue,
};
let container_val = if let Some(v) = *receiver {
Some(v)
} else if lang == Lang::Go {
args.first().and_then(|a| a.first().copied())
} else if let Some(dot_pos) = callee.rfind('.') {
let receiver_name = &callee[..dot_pos];
args.iter()
.flat_map(|a| a.iter())
.find(|&&v| {
ssa.value_defs
.get(v.0 as usize)
.and_then(|d| d.var_name.as_deref())
== Some(receiver_name)
})
.copied()
} else {
None
};
let container_val = match container_val {
Some(v) => v,
None => continue,
};
let container_param =
match trace_to_param(container_val, ssa, &inst_map, &mut HashSet::new()) {
Some(idx) if idx < formal_param_count => idx,
_ => continue,
};
let arg_offset = if lang == Lang::Go && receiver.is_none() {
1usize
} else {
0
};
for &va_idx in &op {
let effective_idx = va_idx + arg_offset;
if let Some(arg_vals) = args.get(effective_idx) {
for &av in arg_vals {
if let Some(src_param) =
trace_to_param(av, ssa, &inst_map, &mut HashSet::new())
&& src_param < formal_param_count
&& src_param != container_param
&& !container_store.contains(&(src_param, container_param))
{
container_store.push((src_param, container_param));
}
}
}
}
}
}
}
let mut ctr: Vec<usize> = container_to_return.into_iter().collect();
ctr.sort();
container_store.sort();
(ctr, container_store)
}