Skip to content

Commit 4cee78b

Browse files
authored
Merge pull request #20 from LLeavesG/fix/semantic-type-cleanup
Drop vacuous predicates and keep required phi copy sources
2 parents ad97f57 + 7c18290 commit 4cee78b

8 files changed

Lines changed: 631 additions & 30 deletions

File tree

dexdec/src/analysis/value_recovery/flow.rs

Lines changed: 90 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,17 @@ impl SsaCopyFlow {
217217
value
218218
}
219219

220+
fn omitted_source(&self, mut value: SsaVar) -> SsaVar {
221+
let mut visited = BTreeSet::new();
222+
while self.omitted.contains(&value) && visited.insert(value) {
223+
let Some(source) = self.sources.get(&value).copied() else {
224+
break;
225+
};
226+
value = source;
227+
}
228+
value
229+
}
230+
220231
fn argument(&self, value: SsaVar) -> InsnArg {
221232
InsnArg::reg_ssa(
222233
value.reg_num,
@@ -431,9 +442,14 @@ impl<'ir> ValueFlowGraph<'ir> {
431442
};
432443
for input in &phi.inputs {
433444
self.retained_phi_inputs.insert(input.value);
445+
let source = self.copies.omitted_source(input.value);
446+
self.retained_phi_inputs.insert(source);
434447
if excluded.contains_key(&input.value) {
435448
pending.push(input.value);
436449
}
450+
if source != input.value && excluded.contains_key(&source) {
451+
pending.push(source);
452+
}
437453
}
438454
}
439455
self.phis.retain(|phi| !recovered.contains(&phi.result));
@@ -800,35 +816,52 @@ impl<'ir> ValueFlowGraph<'ir> {
800816
}
801817

802818
fn required_phi_inputs(&self) -> BTreeSet<SsaVar> {
803-
let phis = self
804-
.phis
805-
.iter()
806-
.map(|phi| (phi.result, phi))
807-
.collect::<BTreeMap<_, _>>();
808-
let mut pending = self
819+
let pending = self
809820
.phis
810821
.iter()
811822
.filter(|phi| self.has_reaching_use(phi.result))
812823
.map(|phi| phi.result)
813824
.collect::<Vec<_>>();
814-
let mut required = self.retained_phi_inputs.clone();
815-
let mut visited = BTreeSet::new();
816-
while let Some(result) = pending.pop() {
817-
if !visited.insert(result) {
818-
continue;
825+
required_phi_input_closure(
826+
&self.phis,
827+
&self.copies,
828+
pending,
829+
self.retained_phi_inputs.clone(),
830+
)
831+
}
832+
}
833+
834+
fn required_phi_input_closure(
835+
phis: &[PhiMerge],
836+
copies: &SsaCopyFlow,
837+
mut pending: Vec<SsaVar>,
838+
mut required: BTreeSet<SsaVar>,
839+
) -> BTreeSet<SsaVar> {
840+
let phis = phis
841+
.iter()
842+
.map(|phi| (phi.result, phi))
843+
.collect::<BTreeMap<_, _>>();
844+
let mut visited = BTreeSet::new();
845+
while let Some(result) = pending.pop() {
846+
if !visited.insert(result) {
847+
continue;
848+
}
849+
let Some(phi) = phis.get(&result) else {
850+
continue;
851+
};
852+
for input in &phi.inputs {
853+
required.insert(input.value);
854+
let source = copies.omitted_source(input.value);
855+
required.insert(source);
856+
if phis.contains_key(&input.value) {
857+
pending.push(input.value);
819858
}
820-
let Some(phi) = phis.get(&result) else {
821-
continue;
822-
};
823-
for input in &phi.inputs {
824-
required.insert(input.value);
825-
if phis.contains_key(&input.value) {
826-
pending.push(input.value);
827-
}
859+
if source != input.value && phis.contains_key(&source) {
860+
pending.push(source);
828861
}
829862
}
830-
required
831863
}
864+
required
832865
}
833866

834867
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
@@ -875,8 +908,10 @@ impl SemanticVisitor for ControlSymbolClosure {
875908
#[cfg(test)]
876909
mod tests {
877910
use super::*;
911+
use crate::ir::analysis::PhiInput;
878912
use crate::ir::{
879-
InsnNode, InsnType, InstructionId, RegionId, SemanticCatch, SemanticExpression,
913+
BlockId, EdgeKind, InsnNode, InsnType, InstructionId, RegionId, SemanticCatch,
914+
SemanticExpression,
880915
};
881916

882917
fn operation(
@@ -945,4 +980,38 @@ mod tests {
945980

946981
assert!(graph.is_bound(binding));
947982
}
983+
984+
#[test]
985+
fn required_phi_inputs_retain_transitive_copy_sources() {
986+
let headers = SsaVar::new(12, 1);
987+
let handler_copy = SsaVar::new(13, 3);
988+
let null = SsaVar::new(13, 0);
989+
let merged = SsaVar::new(13, 4);
990+
let phis = vec![PhiMerge {
991+
block: BlockId::new(113),
992+
instruction: InstructionId::new(200),
993+
result: merged,
994+
inputs: vec![
995+
PhiInput {
996+
predecessor: BlockId::new(108),
997+
edge_kind: EdgeKind::Normal,
998+
value: handler_copy,
999+
},
1000+
PhiInput {
1001+
predecessor: BlockId::new(110),
1002+
edge_kind: EdgeKind::Normal,
1003+
value: null,
1004+
},
1005+
],
1006+
}];
1007+
let mut copies = SsaCopyFlow::default();
1008+
copies.sources.insert(handler_copy, headers);
1009+
copies.omitted.insert(handler_copy);
1010+
1011+
let required = required_phi_input_closure(&phis, &copies, vec![merged], BTreeSet::new());
1012+
1013+
assert!(required.contains(&handler_copy));
1014+
assert!(required.contains(&headers));
1015+
assert!(required.contains(&null));
1016+
}
9481017
}

dexdec/src/ir/region/control.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ impl ControlCandidate {
4040
let closure = ControlRegionClosure::new(cfg, facts, tree, handlers, entry, &domain);
4141
let blocks = closure.close(&blocks, matches!(&kind, RegionKind::Loop(_)))?;
4242
Self::refresh_loop_follow(cfg, facts, &mut kind, &blocks);
43+
// Domain clipping can drop the header when it sits outside the lexical
44+
// owner of the body (e.g. loop header above a nested try). Inserting
45+
// that truncated region would leave `entry` outside `blocks`.
46+
if !blocks.contains(&entry) {
47+
return Ok(());
48+
}
4349
if Self::already_inserted(tree, &kind, entry, &blocks) {
4450
return Ok(());
4551
}
@@ -55,6 +61,9 @@ impl ControlCandidate {
5561
let closure = ControlRegionClosure::new(cfg, facts, tree, handlers, entry, &domain);
5662
let core = closure.close(&core, matches!(&kind, RegionKind::Loop(_)))?;
5763
Self::refresh_loop_follow(cfg, facts, &mut kind, &core);
64+
if !core.contains(&entry) {
65+
return Ok(());
66+
}
5867
let placement = if Self::foreign_entries(facts, entry, &core).is_empty() {
5968
tree.insert_laminar_region(kind, entry, core)?
6069
} else {

dexdec/src/ir/region/tree.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,13 @@ impl SynchronizationPlacement {
14621462
cfg.block(*block)
14631463
.map(|block| (block.offset, block.id.raw()))
14641464
.unwrap_or((u32::MAX, block.raw()))
1465+
})
1466+
.or_else(|| {
1467+
remaining.iter().copied().min_by_key(|block| {
1468+
cfg.block(*block)
1469+
.map(|block| (block.offset, block.id.raw()))
1470+
.unwrap_or((u32::MAX, block.raw()))
1471+
})
14651472
});
14661473
}
14671474
}

dexdec/src/ir/semantic.rs

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,16 +1018,28 @@ impl SemanticNode {
10181018
}
10191019

10201020
pub fn guard(condition: SemanticPredicate, node: SemanticNode) -> Self {
1021-
if condition.constant_value() == Some(true) || matches!(node, Self::Empty) {
1022-
node
1023-
} else if condition.constant_value() == Some(false) {
1024-
Self::Empty
1025-
} else {
1026-
Self::If {
1021+
match condition.constant_value() {
1022+
Some(true) => return node,
1023+
Some(false) => return Self::Empty,
1024+
None => {}
1025+
}
1026+
if matches!(node, Self::Empty) {
1027+
// Vacuous branches must still evaluate effectful predicates.
1028+
// Value recovery can inline invokes into `If` conditions and leave
1029+
// both arms empty; dropping the condition would erase those effects.
1030+
if condition.effects().is_pure() {
1031+
return Self::Empty;
1032+
}
1033+
return Self::If {
10271034
condition: SemanticOperand::new(condition),
1028-
then_node: Box::new(node),
1035+
then_node: Box::new(Self::Empty),
10291036
else_node: None,
1030-
}
1037+
};
1038+
}
1039+
Self::If {
1040+
condition: SemanticOperand::new(condition),
1041+
then_node: Box::new(node),
1042+
else_node: None,
10311043
}
10321044
}
10331045

@@ -1042,7 +1054,17 @@ impl SemanticNode {
10421054
None => {}
10431055
}
10441056
match (then_node, else_node) {
1045-
(Self::Empty, None | Some(Self::Empty)) => Self::Empty,
1057+
(Self::Empty, None | Some(Self::Empty)) => {
1058+
if condition.effects().is_pure() {
1059+
Self::Empty
1060+
} else {
1061+
Self::If {
1062+
condition: SemanticOperand::new(condition),
1063+
then_node: Box::new(Self::Empty),
1064+
else_node: None,
1065+
}
1066+
}
1067+
}
10461068
(Self::Empty, Some(else_node)) => Self::guard(condition.negate(), else_node),
10471069
(then_node, None | Some(Self::Empty)) => Self::guard(condition, then_node),
10481070
(then_node, Some(else_node)) => Self::If {
@@ -1054,6 +1076,70 @@ impl SemanticNode {
10541076
}
10551077
}
10561078

1079+
#[cfg(test)]
1080+
mod branch_effect_tests {
1081+
use super::*;
1082+
use crate::ir::{
1083+
ArgType, InstructionId, InvokeType, LiteralArg, MemberReference, MethodDescriptor,
1084+
MethodReference,
1085+
};
1086+
1087+
fn invoke_predicate() -> SemanticPredicate {
1088+
let mut instruction = InsnNode::new(InsnType::Invoke, 0);
1089+
instruction.id = InstructionId::new(1);
1090+
instruction.payload.invoke_type = Some(InvokeType::Virtual);
1091+
instruction.payload.reference = Some(MemberReference::Method(MethodReference {
1092+
owner: ArgType::object("java/util/ArrayList"),
1093+
name: "remove".into(),
1094+
descriptor: MethodDescriptor {
1095+
parameters: vec![ArgType::object("java/lang/Object")],
1096+
return_type: ArgType::BOOLEAN,
1097+
},
1098+
}));
1099+
instruction.result = Some(RegisterArg::new_ssa(0, 0, ArgType::BOOLEAN));
1100+
let operation = SemanticOperation::from_instruction(instruction).expect("invoke");
1101+
SemanticPredicate::Test(operation)
1102+
}
1103+
1104+
#[test]
1105+
fn branch_keeps_effectful_predicate_when_arms_are_empty() {
1106+
let node = SemanticNode::branch(invoke_predicate(), SemanticNode::Empty, None);
1107+
match node {
1108+
SemanticNode::If {
1109+
condition,
1110+
then_node,
1111+
else_node,
1112+
} => {
1113+
assert!(matches!(then_node.as_ref(), SemanticNode::Empty));
1114+
assert!(else_node.is_none());
1115+
assert!(!condition.value.effects().is_pure());
1116+
}
1117+
other => panic!("expected vacuous if, got {other:?}"),
1118+
}
1119+
}
1120+
1121+
#[test]
1122+
fn branch_drops_pure_predicate_when_arms_are_empty() {
1123+
let mut instruction = InsnNode::new(InsnType::If, 0);
1124+
instruction.id = InstructionId::new(2);
1125+
instruction.payload.if_op = Some(IfOp::Ne);
1126+
let operation = SemanticOperation::from_parts(
1127+
instruction,
1128+
vec![
1129+
SemanticExpression::Register(RegisterArg::new_ssa(1, 0, ArgType::INT)),
1130+
SemanticExpression::Literal(LiteralArg::int(0)),
1131+
],
1132+
None,
1133+
);
1134+
let node = SemanticNode::branch(
1135+
SemanticPredicate::Test(operation),
1136+
SemanticNode::Empty,
1137+
None,
1138+
);
1139+
assert!(matches!(node, SemanticNode::Empty));
1140+
}
1141+
}
1142+
10571143
#[derive(Debug, Clone)]
10581144
pub struct SsaSemantics {
10591145
values: SsaValueGraph,

0 commit comments

Comments
 (0)