use std::collections::{BTreeMap, BTreeSet};
use crate::{
analysis::{
CmpKind, ConstValue, SsaBlock, SsaEvaluator, SsaInstruction, SsaOp, SsaVarId,
VariableOrigin,
},
deobfuscation::passes::unflattening::tracer::{
context::{ContextSnapshot, TreeTraceContext},
helpers::{const_producer_target, detect_expression_switch, resolve_call_result},
types::{InstructionWithValues, StopReason, TraceNode, TraceTerminator},
},
};
enum WorkItem<'a> {
TraceBlock { block: usize, depth: usize },
StateTransitionLink {
parent_node: TraceNode,
from_state: i64,
to_state: i64,
target_block: usize,
},
BranchFalseArm {
parent_node: TraceNode,
block_idx: usize,
condition: SsaVarId,
false_target: usize,
depth: usize,
snapshot: ContextSnapshot<'a>,
case_counts_snapshot: Option<Vec<u8>>,
is_expr_switch: bool,
},
BranchCombine {
parent_node: TraceNode,
block_idx: usize,
condition: SsaVarId,
true_node: TraceNode,
expr_switch_restore: Option<(usize, bool)>,
},
SwitchNextCase {
parent_node: TraceNode,
block_idx: usize,
value: SsaVarId,
targets: Vec<usize>,
default_target: usize,
depth: usize,
snapshot: ContextSnapshot<'a>,
completed_cases: Vec<(i64, Box<TraceNode>)>,
next_case_index: usize,
},
SwitchCombine {
parent_node: TraceNode,
block_idx: usize,
value: SsaVarId,
cases: Vec<(i64, Box<TraceNode>)>,
},
}
pub fn trace_from_block(
ctx: &mut TreeTraceContext<'_>,
block_idx: usize,
depth: usize,
) -> TraceNode {
let mut work_stack: Vec<WorkItem<'_>> = Vec::new();
let mut current_result: Option<TraceNode> = None;
work_stack.push(WorkItem::TraceBlock {
block: block_idx,
depth,
});
loop {
let Some(item) = work_stack.pop() else {
return current_result.expect("trace work stack empty but no result");
};
match item {
WorkItem::TraceBlock { block, depth } => {
let result = trace_from_block_linear(ctx, block, depth, &mut work_stack);
current_result = Some(result);
}
WorkItem::StateTransitionLink {
mut parent_node,
from_state,
to_state,
target_block,
} => {
let child = current_result
.take()
.expect("StateTransitionLink: missing child");
parent_node.set_terminator(TraceTerminator::StateTransition {
from_state,
to_state,
target_block,
continues: Box::new(child),
});
current_result = Some(parent_node);
}
WorkItem::BranchFalseArm {
parent_node,
block_idx,
condition,
false_target,
depth,
snapshot,
case_counts_snapshot,
is_expr_switch,
} => {
let true_node = current_result
.take()
.expect("BranchFalseArm: missing true_node");
let saved_case_counts = if case_counts_snapshot.is_none() {
Some(ctx.case_counts_snapshot())
} else {
None
};
ctx.restore(snapshot);
if let Some(counts) = saved_case_counts {
ctx.set_case_counts(counts);
} else if let Some(ref counts) = case_counts_snapshot {
ctx.set_case_counts(counts.clone());
}
let expr_restore = if is_expr_switch {
Some(ctx.enter_expr_switch_false_arm())
} else {
None
};
ctx.evaluator_mut().set_predecessor(Some(block_idx));
work_stack.push(WorkItem::BranchCombine {
parent_node,
block_idx,
condition,
true_node,
expr_switch_restore: expr_restore,
});
work_stack.push(WorkItem::TraceBlock {
block: false_target,
depth: depth + 1,
});
}
WorkItem::BranchCombine {
mut parent_node,
block_idx,
condition,
true_node,
expr_switch_restore,
} => {
let false_node = current_result
.take()
.expect("BranchCombine: missing false_node");
if let Some(saved) = expr_switch_restore {
ctx.exit_expr_switch_false_arm(saved);
}
parent_node.set_terminator(TraceTerminator::UserBranch {
block: block_idx,
condition,
true_branch: Box::new(true_node),
false_branch: Box::new(false_node),
});
current_result = Some(parent_node);
}
WorkItem::SwitchNextCase {
parent_node,
block_idx,
value,
targets,
default_target,
depth,
snapshot,
mut completed_cases,
next_case_index,
} => {
if let Some(prev_result) = current_result.take() {
if next_case_index > 0 {
#[allow(clippy::cast_possible_wrap)]
let case_value = (next_case_index - 1) as i64;
completed_cases.push((case_value, Box::new(prev_result)));
}
}
if next_case_index < targets.len() {
ctx.restore(snapshot.clone_snapshot());
ctx.evaluator_mut().set_predecessor(Some(block_idx));
let target = targets[next_case_index];
work_stack.push(WorkItem::SwitchNextCase {
parent_node,
block_idx,
value,
targets,
default_target,
depth,
snapshot,
completed_cases,
next_case_index: next_case_index + 1,
});
work_stack.push(WorkItem::TraceBlock {
block: target,
depth,
});
} else {
ctx.restore(snapshot);
ctx.evaluator_mut().set_predecessor(Some(block_idx));
work_stack.push(WorkItem::SwitchCombine {
parent_node,
block_idx,
value,
cases: completed_cases,
});
work_stack.push(WorkItem::TraceBlock {
block: default_target,
depth,
});
}
}
WorkItem::SwitchCombine {
mut parent_node,
block_idx,
value,
cases,
} => {
let default_node = current_result
.take()
.expect("SwitchCombine: missing default_node");
parent_node.set_terminator(TraceTerminator::UserSwitch {
block: block_idx,
value,
cases,
default: Box::new(default_node),
});
current_result = Some(parent_node);
}
}
}
}
fn trace_from_block_linear<'a>(
ctx: &mut TreeTraceContext<'a>,
block_idx: usize,
depth: usize,
work_stack: &mut Vec<WorkItem<'a>>,
) -> TraceNode {
let mut transition_chain: Vec<(TraceNode, i64, usize)> = Vec::new();
let mut entry_block = block_idx;
let result = loop {
let (leaf, fork) = trace_from_block_inner(ctx, entry_block, depth);
if let Some(fork) = fork {
let to_state = ctx.current_state().unwrap_or(0);
for (parent, from_state, target_block) in transition_chain.drain(..) {
work_stack.push(WorkItem::StateTransitionLink {
parent_node: parent,
from_state,
to_state,
target_block,
});
}
match fork {
ForkRequest::Branch {
block_idx,
condition,
true_target,
false_target,
snapshot,
case_counts_snapshot,
is_expr_switch,
} => {
ctx.evaluator_mut().set_predecessor(Some(block_idx));
work_stack.push(WorkItem::BranchFalseArm {
parent_node: leaf,
block_idx,
condition,
false_target,
depth,
snapshot,
case_counts_snapshot,
is_expr_switch,
});
work_stack.push(WorkItem::TraceBlock {
block: true_target,
depth: depth + 1,
});
}
ForkRequest::Switch {
block_idx,
value,
targets,
default_target,
snapshot,
is_foreign,
} => {
let fork_depth = if is_foreign { depth } else { depth + 1 };
if targets.is_empty() {
ctx.evaluator_mut().set_predecessor(Some(block_idx));
return leaf;
}
ctx.evaluator_mut().set_predecessor(Some(block_idx));
let first_target = targets[0];
work_stack.push(WorkItem::SwitchNextCase {
parent_node: leaf,
block_idx,
value,
targets,
default_target,
depth: fork_depth,
snapshot,
completed_cases: Vec::new(),
next_case_index: 1,
});
work_stack.push(WorkItem::TraceBlock {
block: first_target,
depth: fork_depth,
});
}
}
return TraceNode::new(0, 0);
}
if let Some((from_state, target_block)) = leaf.pending_state_transition() {
transition_chain.push((leaf, from_state, target_block));
entry_block = target_block;
continue;
}
break leaf;
};
let to_state = ctx.current_state().unwrap_or(0);
let mut leaf = result;
while let Some((mut parent, from_state, target_block)) = transition_chain.pop() {
parent.set_terminator(TraceTerminator::StateTransition {
from_state,
to_state,
target_block,
continues: Box::new(leaf),
});
leaf = parent;
}
leaf
}
enum ForkRequest<'a> {
Branch {
block_idx: usize,
condition: SsaVarId,
true_target: usize,
false_target: usize,
snapshot: ContextSnapshot<'a>,
case_counts_snapshot: Option<Vec<u8>>,
is_expr_switch: bool,
},
Switch {
block_idx: usize,
value: SsaVarId,
targets: Vec<usize>,
default_target: usize,
snapshot: ContextSnapshot<'a>,
is_foreign: bool,
},
}
fn trace_from_block_inner<'a>(
ctx: &mut TreeTraceContext<'a>,
block_idx: usize,
depth: usize,
) -> (TraceNode, Option<ForkRequest<'a>>) {
let mut node = TraceNode::new(ctx.next_id(), block_idx);
if depth > ctx.max_tree_depth() {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::MaxVisitsExceeded,
});
return (node, None);
}
if ctx.is_visited(block_idx) {
let state = ctx.current_state().unwrap_or(0);
node.set_terminator(TraceTerminator::LoopBack {
target_block: block_idx,
state,
});
return (node, None);
}
if !ctx.is_dispatch_target(block_idx) {
ctx.mark_visited(block_idx);
}
let ssa = ctx.ssa();
let mut current_block = block_idx;
loop {
if ctx.check_visit_budget() {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::MaxVisitsExceeded,
});
return (node, None);
}
let is_dispatcher = ctx.is_dispatcher_block(current_block);
if !is_dispatcher
&& current_block != block_idx
&& node.blocks_visited.len() > 1
&& node.blocks_visited[..node.blocks_visited.len() - 1].contains(¤t_block)
{
let state = ctx.current_state().unwrap_or(0);
node.set_terminator(TraceTerminator::LoopBack {
target_block: current_block,
state,
});
return (node, None);
}
let is_dispatcher_reentry = is_dispatcher
&& node.blocks_visited.len() > 1
&& node.blocks_visited[..node.blocks_visited.len() - 1].contains(¤t_block);
if is_dispatcher_reentry {
if let Some(block) = ssa.block(current_block) {
for instr in block.instructions() {
if let Some(def) = instr.def() {
ctx.evaluator_mut().set_unknown(def);
}
}
if let Some(state_var) = ctx.state_var() {
for phi in block.phi_nodes() {
if phi.result() == state_var {
for op in phi.operands() {
let op_pred = op.predecessor();
if node.blocks_visited.contains(&op_pred)
&& ctx.evaluator().get_concrete(op.value()).is_some()
{
ctx.evaluator_mut().set_predecessor(Some(op_pred));
break;
}
}
}
}
}
}
}
let Some(block) = ssa.block(current_block) else {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow {
block: current_block,
},
});
return (node, None);
};
if node.blocks_visited.len() > 1 {
let prev = node.blocks_visited[node.blocks_visited.len() - 2];
ctx.evaluator_mut().set_predecessor(Some(prev));
}
if is_dispatcher {
bridge_phi_operands(ctx, current_block);
}
ctx.evaluator_mut().evaluate_phis(current_block);
if let Some(block) = ssa.block(current_block) {
for phi in block.phi_nodes() {
if !phi.operands().is_empty()
&& phi.operands().iter().all(|op| ctx.is_tainted(op.value()))
{
ctx.taint(phi.result());
}
}
}
for instr in block.instructions() {
let step = trace_instruction(ctx, instr, current_block);
node.add_instruction(step);
if let SsaOp::Copy { dest, src } = instr.op() {
if ctx.evaluator().get(*dest).is_none() {
if let Some(src_var) = ssa.variable(*src) {
if let VariableOrigin::Local(local_idx) = src_var.origin() {
for var in ssa.variables() {
if var.id() != *src
&& matches!(var.origin(), VariableOrigin::Local(li) if li == local_idx)
{
if let Some(val) = ctx.evaluator().get(var.id()).cloned() {
ctx.evaluator_mut().set_symbolic_expr(*dest, val);
break;
}
}
}
}
}
}
}
}
match handle_terminator(ctx, block, current_block, &mut node, depth) {
TerminatorResult::Continue(next) => {
node.visit_block(next);
current_block = next;
}
TerminatorResult::Done => return (node, None),
TerminatorResult::StateTransition {
from_state,
target_block,
} => {
node.set_pending_state_transition(from_state, target_block);
return (node, None);
}
TerminatorResult::ForkBranch {
block_idx,
condition,
true_target,
false_target,
snapshot,
case_counts_snapshot,
is_expr_switch,
} => {
return (
node,
Some(ForkRequest::Branch {
block_idx,
condition,
true_target,
false_target,
snapshot,
case_counts_snapshot,
is_expr_switch,
}),
);
}
TerminatorResult::ForkSwitch {
block_idx,
value,
targets,
default_target,
snapshot,
is_foreign,
} => {
return (
node,
Some(ForkRequest::Switch {
block_idx,
value,
targets,
default_target,
snapshot,
is_foreign,
}),
);
}
}
}
}
fn bridge_phi_operands(ctx: &mut TreeTraceContext<'_>, block_idx: usize) {
let ssa = ctx.ssa();
let (Some(sv), Some(block)) = (ctx.state_var(), ssa.block(block_idx)) else {
return;
};
let pred = ctx.evaluator().predecessor();
for phi in block.phi_nodes() {
if phi.result() == sv {
if let Some(op) = phi
.operands()
.iter()
.find(|op| pred.is_some_and(|p| op.predecessor() == p))
{
let op_var = op.value();
if ctx.evaluator().get(op_var).is_none() {
if let Some(pred_idx) = pred {
if let Some(pred_block) = ssa.block(pred_idx) {
let bridged = pred_block
.instructions()
.iter()
.rev()
.filter(|i| !i.is_terminator())
.find_map(|i| {
i.def().and_then(|d| {
ctx.evaluator().get(d).cloned().map(|v| (d, v))
})
});
if let Some((_def_var, val)) = bridged {
ctx.evaluator_mut().set_symbolic_expr(op_var, val);
}
}
}
}
}
}
}
}
enum TerminatorResult<'a> {
Continue(usize),
Done,
StateTransition {
from_state: i64,
target_block: usize,
},
ForkBranch {
block_idx: usize,
condition: SsaVarId,
true_target: usize,
false_target: usize,
snapshot: ContextSnapshot<'a>,
case_counts_snapshot: Option<Vec<u8>>,
is_expr_switch: bool,
},
ForkSwitch {
block_idx: usize,
value: SsaVarId,
targets: Vec<usize>,
default_target: usize,
snapshot: ContextSnapshot<'a>,
is_foreign: bool,
},
}
fn handle_terminator<'a>(
ctx: &mut TreeTraceContext<'a>,
block: &SsaBlock,
block_idx: usize,
node: &mut TraceNode,
depth: usize,
) -> TerminatorResult<'a> {
let Some(terminator) = block.instructions().last() else {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow { block: block_idx },
});
return TerminatorResult::Done;
};
match terminator.op() {
SsaOp::Jump { target } => TerminatorResult::Continue(*target),
SsaOp::Leave { target } => TerminatorResult::Continue(*target),
SsaOp::Branch {
condition,
true_target,
false_target,
} => {
if ctx.is_tainted(*condition) {
match ctx
.evaluator()
.get_concrete(*condition)
.and_then(ConstValue::as_i64)
{
Some(v) if v != 0 => TerminatorResult::Continue(*true_target),
Some(_) => TerminatorResult::Continue(*false_target),
None => {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow { block: block_idx },
});
TerminatorResult::Done
}
}
} else {
handle_user_branch_fork(
ctx,
block_idx,
*true_target,
*false_target,
*condition,
depth,
)
}
}
SsaOp::BranchCmp {
left,
right,
cmp,
unsigned,
true_target,
false_target,
} => {
let left_val = ctx.evaluator().get_concrete(*left).cloned();
let right_val = ctx.evaluator().get_concrete(*right).cloned();
let tainted = ctx.is_tainted(*left) || ctx.is_tainted(*right);
if tainted {
if let (Some(l), Some(r)) = (&left_val, &right_val) {
if SsaEvaluator::evaluate_comparison(l, r, *cmp, *unsigned) {
return TerminatorResult::Continue(*true_target);
}
return TerminatorResult::Continue(*false_target);
}
}
if *cmp == CmpKind::Eq && is_overflow_dispatch_site(ctx, block_idx) {
let overflow_seed = match (left_val.clone(), right_val.clone()) {
(None, Some(r)) => Some((*left, r)),
(Some(l), None) => Some((*right, l)),
_ => None,
};
if let Some((unknown_var, const_val)) = overflow_seed {
let snapshot = ctx.snapshot();
if let Some(state_var) = ctx.state_var() {
ctx.evaluator_mut()
.set_concrete(state_var, const_val.clone());
}
ctx.evaluator_mut().set_concrete(unknown_var, const_val);
let case_counts_snapshot = Some(ctx.case_counts_snapshot());
return TerminatorResult::ForkBranch {
block_idx,
condition: unknown_var,
true_target: *true_target,
false_target: *false_target,
snapshot,
case_counts_snapshot,
is_expr_switch: false,
};
}
}
if tainted {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow { block: block_idx },
});
TerminatorResult::Done
} else {
let is_expr_switch = detect_expression_switch(
ctx.ssa(),
*true_target,
*false_target,
ctx.state_tainted(),
)
.is_some();
if ctx.no_fork() && !is_expr_switch {
let is_cff_state_transition =
is_conditional_state_transition(ctx, *true_target, *false_target);
if !is_cff_state_transition {
return TerminatorResult::Continue(*true_target);
}
}
build_fork_branch(
ctx,
block_idx,
*true_target,
*false_target,
*left,
is_expr_switch,
)
}
}
SsaOp::Switch {
value,
targets,
default,
} => handle_switch(ctx, node, block_idx, value, targets, default, depth),
SsaOp::Return { .. } | SsaOp::Throw { .. } => {
node.set_terminator(TraceTerminator::Exit { block: block_idx });
TerminatorResult::Done
}
_ => {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow { block: block_idx },
});
TerminatorResult::Done
}
}
}
fn handle_user_branch_fork<'a>(
ctx: &mut TreeTraceContext<'a>,
block_idx: usize,
true_target: usize,
false_target: usize,
condition: SsaVarId,
_: usize,
) -> TerminatorResult<'a> {
let is_expr_switch =
detect_expression_switch(ctx.ssa(), true_target, false_target, ctx.state_tainted())
.is_some();
if ctx.no_fork() && !is_expr_switch {
return TerminatorResult::Continue(true_target);
}
build_fork_branch(
ctx,
block_idx,
true_target,
false_target,
condition,
is_expr_switch,
)
}
fn build_fork_branch<'a>(
ctx: &mut TreeTraceContext<'a>,
block_idx: usize,
true_target: usize,
false_target: usize,
condition: SsaVarId,
is_expr_switch: bool,
) -> TerminatorResult<'a> {
let snapshot = ctx.snapshot();
let case_counts_snapshot = if is_expr_switch {
None
} else {
Some(ctx.case_counts_snapshot())
};
TerminatorResult::ForkBranch {
block_idx,
condition,
true_target,
false_target,
snapshot,
case_counts_snapshot,
is_expr_switch,
}
}
fn is_conditional_state_transition(
ctx: &TreeTraceContext<'_>,
true_target: usize,
false_target: usize,
) -> bool {
let Some(db) = ctx.dispatcher_block() else {
return false;
};
let true_merge = ctx.ssa().block(true_target).and_then(const_producer_target);
let false_merge = ctx
.ssa()
.block(false_target)
.and_then(const_producer_target);
match (true_merge, false_merge) {
(Some(tm), Some(fm)) if tm == fm => {
let mut block = tm;
for _ in 0..3 {
if block == db {
return true;
}
match ctx.ssa().block(block).and_then(|b| b.terminator_op()) {
Some(SsaOp::Jump { target }) => block = *target,
_ => break,
}
}
block == db
}
_ => false,
}
}
fn is_overflow_dispatch_site(ctx: &TreeTraceContext<'_>, block_idx: usize) -> bool {
let Some(dispatcher) = ctx.dispatcher_block() else {
return false;
};
if block_idx == dispatcher {
return false;
}
const MAX_HOPS: usize = 8;
let mut frontier: Vec<usize> = vec![block_idx];
let mut visited: BTreeSet<usize> = BTreeSet::new();
visited.insert(block_idx);
for _ in 0..MAX_HOPS {
let mut next_frontier: Vec<usize> = Vec::new();
for &b in &frontier {
for pred in ctx.ssa().block_predecessors(b) {
if pred == dispatcher || ctx.is_other_dispatcher(pred) {
return true;
}
if let Some(pred_block) = ctx.ssa().block(pred) {
if matches!(pred_block.terminator_op(), Some(SsaOp::Switch { .. })) {
return true;
}
}
if !visited.insert(pred) {
continue;
}
let Some(pred_block) = ctx.ssa().block(pred) else {
continue;
};
let is_chain_block = pred_block.instructions().iter().all(|instr| {
matches!(
instr.op(),
SsaOp::Const { .. }
| SsaOp::Copy { .. }
| SsaOp::Jump { .. }
| SsaOp::Nop
| SsaOp::BranchCmp { .. }
)
});
if is_chain_block {
next_frontier.push(pred);
}
}
}
if next_frontier.is_empty() {
break;
}
frontier = next_frontier;
}
false
}
fn default_has_overflow_check(ctx: &TreeTraceContext<'_>, default: usize) -> bool {
const MAX_HOPS: usize = 4;
let mut current = default;
for _ in 0..MAX_HOPS {
let Some(block) = ctx.ssa().block(current) else {
return false;
};
match block.terminator_op() {
Some(SsaOp::BranchCmp {
cmp: CmpKind::Eq,
left,
right,
..
}) => {
return ctx.evaluator().get_concrete(*left).is_some()
|| ctx.evaluator().get_concrete(*right).is_some();
}
Some(SsaOp::Jump { target }) => {
let is_pure = block.instructions().iter().all(|instr| {
matches!(
instr.op(),
SsaOp::Const { .. } | SsaOp::Copy { .. } | SsaOp::Jump { .. } | SsaOp::Nop
)
});
if !is_pure {
return false;
}
current = *target;
}
_ => return false,
}
}
false
}
fn handle_switch<'a>(
ctx: &mut TreeTraceContext<'a>,
node: &mut TraceNode,
block_idx: usize,
value: &SsaVarId,
targets: &[usize],
default: &usize,
_: usize,
) -> TerminatorResult<'a> {
let is_dispatcher = ctx.is_dispatcher_block(block_idx);
let is_argument = ctx
.ssa()
.variable(*value)
.is_some_and(|v| matches!(v.origin(), VariableOrigin::Argument(_)));
if !is_argument && (is_dispatcher || ctx.is_tainted(*value)) {
let concrete_value = ctx
.evaluator()
.get_concrete(*value)
.and_then(ConstValue::as_u64)
.or_else(|| {
let var = ctx.ssa().variable(*value)?;
let site = var.def_site();
let is_entry = site.block == 0 && site.instruction.is_none();
if is_entry && matches!(var.origin(), VariableOrigin::Local(_)) {
Some(0)
} else {
None
}
});
if let Some(idx) = concrete_value {
if is_dispatcher {
if let Some(state_var) = ctx.state_var() {
if ctx.evaluator().get(state_var).is_none() {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow { block: block_idx },
});
return TerminatorResult::Done;
}
}
}
#[allow(clippy::cast_possible_truncation)]
let idx_usize = idx as usize;
let target = if idx_usize < targets.len() {
targets[idx_usize]
} else {
*default
};
let from_state = ctx.current_state().unwrap_or(0);
if ctx.is_case_loop(idx_usize, targets.len()) {
let state = ctx.current_state().unwrap_or(0);
let mut loop_node = TraceNode::new(ctx.next_id(), target);
loop_node.set_terminator(TraceTerminator::LoopBack {
target_block: target,
state,
});
node.set_terminator(TraceTerminator::StateTransition {
from_state,
to_state: state,
target_block: target,
continues: Box::new(loop_node),
});
return TerminatorResult::Done;
}
ctx.record_case_dispatch(idx_usize);
ctx.record_case_state(idx_usize, from_state);
ctx.evaluator_mut().set_predecessor(Some(block_idx));
TerminatorResult::StateTransition {
from_state,
target_block: target,
}
} else if is_dispatcher {
node.set_terminator(TraceTerminator::Stopped {
reason: StopReason::UnknownControlFlow { block: block_idx },
});
TerminatorResult::Done
} else {
handle_user_switch(ctx, block_idx, value, targets, default)
}
} else {
handle_user_switch(ctx, block_idx, value, targets, default)
}
}
fn handle_user_switch<'a>(
ctx: &mut TreeTraceContext<'a>,
block_idx: usize,
value: &SsaVarId,
targets: &[usize],
default: &usize,
) -> TerminatorResult<'a> {
if ctx.no_fork() {
let target = ctx
.evaluator()
.get_concrete(*value)
.and_then(|v| v.as_u64())
.and_then(|idx| targets.get(idx as usize).copied())
.unwrap_or_else(|| targets.first().copied().unwrap_or(*default));
return TerminatorResult::Continue(target);
}
let is_foreign = ctx.is_other_dispatcher(block_idx);
let snapshot = ctx.snapshot();
TerminatorResult::ForkSwitch {
block_idx,
value: *value,
targets: targets.to_vec(),
default_target: *default,
snapshot,
is_foreign,
}
}
fn trace_instruction(
ctx: &mut TreeTraceContext<'_>,
instr: &SsaInstruction,
block_idx: usize,
) -> InstructionWithValues {
let input_values: BTreeMap<SsaVarId, i64> = instr
.uses()
.iter()
.filter_map(|&var| {
ctx.evaluator()
.get_concrete(var)
.and_then(ConstValue::as_i64)
.map(|v| (var, v))
})
.collect();
if ctx.any_tainted(&instr.uses()) {
if let Some(def) = instr.def() {
ctx.taint(def);
}
}
ctx.evaluator_mut().evaluate_op(instr.op());
if let SsaOp::Call {
dest: Some(dest),
method,
args,
} = instr.op()
{
if let Some(assembly) = ctx.assembly() {
let concrete_args: Option<Vec<ConstValue>> = args
.iter()
.map(|&a| ctx.evaluator().get_concrete(a).cloned())
.collect();
if let Some(concrete_args) = concrete_args {
if let Some(result) = resolve_call_result(
assembly,
method.token(),
&concrete_args,
ctx.evaluator().pointer_size(),
) {
ctx.evaluator_mut().set_concrete(*dest, result);
}
}
}
}
let output_value = instr
.def()
.and_then(|d| ctx.evaluator().get_concrete(d))
.and_then(ConstValue::as_i64);
InstructionWithValues {
instruction: instr.clone(),
block_idx,
input_values,
output_value,
}
}