Skip to content

Commit 3b38d32

Browse files
authored
Merge pull request #21 from LLeavesG/fix/java-control-flow-output
Recover Java switch emission and preserve colliding source outputs
2 parents 4cee78b + 65dd1ff commit 3b38d32

15 files changed

Lines changed: 2614 additions & 89 deletions

File tree

dexdec/src/analysis/java_backend/anonymous_lowering.rs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2168,8 +2168,9 @@ impl AnonymousInstance {
21682168
values: captures,
21692169
identity: candidate.identity.as_ref(),
21702170
value_types,
2171+
substitute_this: true,
21712172
}
2172-
.rewrite_anonymous_body(&mut body);
2173+
.rewrite_root_anonymous_body(&mut body);
21732174
if let Some(identity) = candidate.identity.as_ref() {
21742175
AnonymousIdentitySubstitution { identity }.rewrite_anonymous_body(&mut body);
21752176
}
@@ -2655,9 +2656,19 @@ struct CaptureSubstitution<'a> {
26552656
values: BTreeMap<JavaIdentifier, JavaExpr>,
26562657
identity: Option<&'a JavaType>,
26572658
value_types: &'a BTreeMap<JavaIdentifier, LexicalValueType>,
2659+
// A nested anonymous body's `this` owns a separate field namespace. Its
2660+
// explicit QualifiedThis references can still target this capture owner.
2661+
substitute_this: bool,
26582662
}
26592663

26602664
impl JavaAstRewriter for CaptureSubstitution<'_> {
2665+
fn rewrite_anonymous_body(&mut self, body: &mut JavaAnonymousClassBody) {
2666+
let substitute_this = self.substitute_this;
2667+
self.substitute_this = false;
2668+
self.rewrite_anonymous_members(body);
2669+
self.substitute_this = substitute_this;
2670+
}
2671+
26612672
fn finish_expression(&mut self, expression: JavaExpr) -> JavaExpr {
26622673
match expression {
26632674
JavaExpr::Cast { ty, value }
@@ -2668,7 +2679,7 @@ impl JavaAstRewriter for CaptureSubstitution<'_> {
26682679
*value
26692680
}
26702681
JavaExpr::Field { owner, name }
2671-
if matches!(owner.as_ref(), JavaExpr::This)
2682+
if self.substitute_this && matches!(owner.as_ref(), JavaExpr::This)
26722683
|| matches!(
26732684
(owner.as_ref(), self.identity),
26742685
(JavaExpr::QualifiedThis(owner), Some(identity)) if owner == identity
@@ -2685,6 +2696,36 @@ impl JavaAstRewriter for CaptureSubstitution<'_> {
26852696
}
26862697

26872698
impl CaptureSubstitution<'_> {
2699+
fn rewrite_root_anonymous_body(&mut self, body: &mut JavaAnonymousClassBody) {
2700+
self.rewrite_anonymous_members(body);
2701+
}
2702+
2703+
fn rewrite_anonymous_members(&mut self, body: &mut JavaAnonymousClassBody) {
2704+
for field in &mut body.fields {
2705+
self.rewrite_annotations(&mut field.annotations);
2706+
field.initializer = field
2707+
.initializer
2708+
.take()
2709+
.map(|value| self.rewrite_expression(value));
2710+
}
2711+
for method in &mut body.methods {
2712+
self.rewrite_annotations(&mut method.annotations);
2713+
for parameter in &mut method.parameters {
2714+
self.rewrite_annotations(&mut parameter.annotations);
2715+
}
2716+
if let Some(body) = &mut method.body {
2717+
self.rewrite_body(body);
2718+
}
2719+
}
2720+
let substitute_this = self.substitute_this;
2721+
self.substitute_this = false;
2722+
for nested in &mut body.nested {
2723+
self.rewrite_type_declaration(nested);
2724+
}
2725+
self.substitute_this = substitute_this;
2726+
self.finish_anonymous_body(body);
2727+
}
2728+
26882729
fn capture_type(&self, expression: &JavaExpr) -> Option<&LexicalValueType> {
26892730
match expression {
26902731
JavaExpr::Name(name) if self.values.values().any(|captured| captured == expression) => {
@@ -2705,3 +2746,80 @@ impl CaptureSubstitution<'_> {
27052746
}
27062747
}
27072748
}
2749+
2750+
#[cfg(test)]
2751+
mod capture_substitution_tests {
2752+
use super::*;
2753+
use crate::language::java::JavaFieldDeclaration;
2754+
2755+
fn field(name: &str, initializer: JavaExpr) -> JavaFieldDeclaration {
2756+
JavaFieldDeclaration {
2757+
annotations: Vec::new(),
2758+
modifiers: Vec::new(),
2759+
ty: JavaType::Class(JavaClassType::from_source("java.lang.Object")),
2760+
name: JavaIdentifier::from_dex(name),
2761+
initializer: Some(initializer),
2762+
}
2763+
}
2764+
2765+
#[test]
2766+
fn captured_field_substitution_stops_at_nested_anonymous_body() {
2767+
let captured = JavaIdentifier::from_dex("captured");
2768+
let direct_reference = JavaExpr::Field {
2769+
owner: Box::new(JavaExpr::This),
2770+
name: captured.clone(),
2771+
};
2772+
let nested_reference = direct_reference.clone();
2773+
let identity = JavaType::Class(JavaClassType::from_source("example.ParentAnonymous"));
2774+
let enclosing_reference = JavaExpr::Field {
2775+
owner: Box::new(JavaExpr::QualifiedThis(identity.clone())),
2776+
name: captured.clone(),
2777+
};
2778+
let nested_body = JavaAnonymousClassBody {
2779+
fields: vec![
2780+
field("nestedUse", nested_reference.clone()),
2781+
field("enclosingUse", enclosing_reference),
2782+
],
2783+
methods: Vec::new(),
2784+
nested: Vec::new(),
2785+
};
2786+
let mut body = JavaAnonymousClassBody {
2787+
fields: vec![
2788+
field("directUse", direct_reference),
2789+
field(
2790+
"nested",
2791+
JavaExpr::New {
2792+
enclosing: None,
2793+
ty: JavaType::Class(JavaClassType::from_source("example.Listener")),
2794+
target_type: None,
2795+
args: Vec::new(),
2796+
anonymous_body: Some(Box::new(nested_body)),
2797+
},
2798+
),
2799+
],
2800+
methods: Vec::new(),
2801+
nested: Vec::new(),
2802+
};
2803+
let replacement = JavaExpr::Name(JavaIdentifier::from_dex("value"));
2804+
let value_types = BTreeMap::new();
2805+
2806+
CaptureSubstitution {
2807+
values: BTreeMap::from([(captured, replacement.clone())]),
2808+
identity: Some(&identity),
2809+
value_types: &value_types,
2810+
substitute_this: true,
2811+
}
2812+
.rewrite_root_anonymous_body(&mut body);
2813+
2814+
assert_eq!(body.fields[0].initializer, Some(replacement.clone()));
2815+
let Some(JavaExpr::New {
2816+
anonymous_body: Some(nested),
2817+
..
2818+
}) = body.fields[1].initializer.as_ref()
2819+
else {
2820+
panic!("expected nested anonymous body");
2821+
};
2822+
assert_eq!(nested.fields[0].initializer, Some(nested_reference));
2823+
assert_eq!(nested.fields[1].initializer, Some(replacement));
2824+
}
2825+
}

dexdec/src/analysis/java_backend/declaration_lowering.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -582,9 +582,10 @@ impl<'a> JavaTypeLowering<'a> {
582582
.ok_or(JavaDecompilerError::MalformedDeclarationStack)?;
583583
let mut declaration = lowered.declaration;
584584
let recovered_functions = lowered.liveness.apply(&mut declaration);
585-
let outer_aliases = self
586-
.outer_instances
587-
.iter()
585+
// Resolved simple owner names can collide across unrelated classes;
586+
// only aliases owned by this lexical model may rewrite its fields.
587+
let outer_aliases = class
588+
.outer_instances()
588589
.map(|(field, outer)| {
589590
Ok((
590591
self.names.resolve_type(&field.owner)?,

dexdec/src/analysis/java_backend/java_model/method.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::ir::{ty::ArgType, CFG};
33
use crate::language::java::{
44
AggregateInitializer, DefiniteAssignment, JavaAstNormalizer, JavaAstTransform, JavaIdentifier,
55
JavaInitializerExitLowering, JavaLowerer, JavaMethodCompletion, JavaModifier, JavaType,
6-
LexicalDeclarationPlacement,
6+
JavaVoidTailLinearizer, LexicalDeclarationPlacement,
77
};
88

99
use super::super::method_pipeline::MethodBodyAnalysis;
@@ -324,6 +324,15 @@ impl JavaMethodBody {
324324
.apply(&mut ast)
325325
.map_err(crate::language::java::JavaLoweringError::from)
326326
})?;
327+
if !class_initializer && self.return_type.as_ref() == Some(&ArgType::VOID) {
328+
let mut tail = JavaVoidTailLinearizer;
329+
crate::profile_scope!("java_backend.method_lower.void_tail", {
330+
match tail.apply(&mut ast) {
331+
Ok(_) => {}
332+
Err(never) => match never {},
333+
}
334+
});
335+
}
327336
if semantically_terminal {
328337
if let Some(return_type) = completion_type {
329338
let mut completion = JavaMethodCompletion::new(return_type);
@@ -498,7 +507,9 @@ impl JavaMethodDeclaration {
498507
&& parsed_signature.as_ref().is_some_and(|signature| {
499508
signature.has_unbound_type_variables(lexical_type_variables)
500509
});
501-
let explicit_signature = parsed_signature.filter(|_| !has_unbound_variables);
510+
let explicit_signature = parsed_signature.filter(|signature| {
511+
!has_unbound_variables && method_signature_is_java_denotable(signature)
512+
});
502513
let (signature, source_return_type) = match explicit_signature {
503514
Some(signature) => (Some(signature), None),
504515
None => {
@@ -660,6 +671,24 @@ impl JavaMethodDeclaration {
660671
}
661672
}
662673

674+
fn method_signature_is_java_denotable(
675+
signature: &crate::ir::generic_types::MethodSignature,
676+
) -> bool {
677+
signature.type_parameters.iter().all(|parameter| {
678+
parameter
679+
.class_bound
680+
.iter()
681+
.chain(&parameter.interface_bounds)
682+
.all(|bound| {
683+
!matches!(
684+
bound,
685+
crate::ir::generic_types::JvmTypeSignature::Array(_)
686+
| crate::ir::generic_types::JvmTypeSignature::BaseType(_)
687+
)
688+
})
689+
})
690+
}
691+
663692
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
664693
pub(in crate::analysis::java_backend) enum JavaMethodDeclarationKind {
665694
Method,
@@ -801,3 +830,30 @@ fn is_meaningful_param_name(name: &str) -> bool {
801830
}
802831
chars.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$')
803832
}
833+
834+
#[cfg(test)]
835+
mod tests {
836+
use crate::ir::generic_types::GenericSignatures;
837+
838+
use super::method_signature_is_java_denotable;
839+
840+
#[test]
841+
fn rejects_an_array_type_parameter_bound_in_a_method_signature() {
842+
let signature = GenericSignatures::method(
843+
"<C:[Ljava/lang/Object;:TR;R:Ljava/lang/Object;>\
844+
(TC;Lkotlin/jvm/functions/Function0<+TR;>;)TR;",
845+
)
846+
.expect("Kotlin array intersection signature");
847+
848+
assert!(!method_signature_is_java_denotable(&signature));
849+
}
850+
851+
#[test]
852+
fn accepts_a_java_denotable_method_signature() {
853+
let signature =
854+
GenericSignatures::method("<T:Ljava/lang/Object;>(Ljava/util/List<+TT;>;)TT;")
855+
.expect("ordinary generic method signature");
856+
857+
assert!(method_signature_is_java_denotable(&signature));
858+
}
859+
}

dexdec/src/analysis/java_backend/java_model/source_abi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ pub(in crate::analysis::java_backend) struct OuterInstanceField {
6363
}
6464

6565
impl OuterInstanceField {
66+
pub(in crate::analysis::java_backend) fn reference(&self) -> &FieldReference {
67+
&self.reference
68+
}
69+
6670
/// Proves the enclosing-instance field from constructor def-use rather
6771
/// than field names or type uniqueness. This is required for local and
6872
/// anonymous classes, where another captured value can have the same type

dexdec/src/analysis/java_backend/member_names.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use crate::ir::{ArgType, MethodDescriptor};
44
use crate::language::java::{
5-
JavaConstructorLayout, JavaFieldSymbol, JavaMemberNames, JavaMethodSymbol,
5+
JavaConstructorLayout, JavaFieldSymbol, JavaIdentifier, JavaMemberNames, JavaMethodSymbol,
66
};
77

88
use super::java_model::method::{JavaMethodDeclarationKind, JavaMethodModel};
@@ -13,6 +13,7 @@ pub(super) struct ClassMemberNames;
1313
impl ClassMemberNames {
1414
pub(super) fn collect(root: &JavaClassModel) -> JavaMemberNames {
1515
let mut fields = Vec::new();
16+
let mut hidden_fields = Vec::new();
1617
let mut methods = Vec::new();
1718
let mut constructors = Vec::new();
1819
let mut pending = vec![root];
@@ -24,6 +25,14 @@ impl ClassMemberNames {
2425
fields.extend(class.fields.iter().map(|field| {
2526
JavaFieldSymbol::new(owner.clone(), field.name.clone(), field.field_type.clone())
2627
}));
28+
hidden_fields.extend(class.outer_instance.iter().map(|outer| {
29+
let field = outer.reference();
30+
JavaFieldSymbol::new(
31+
field.owner.clone(),
32+
JavaIdentifier::from_dex(&field.name),
33+
field.field_type.clone(),
34+
)
35+
}));
2736
methods.extend(
2837
class
2938
.methods
@@ -37,7 +46,9 @@ impl ClassMemberNames {
3746
.filter_map(|method| Self::constructor(owner.clone(), method)),
3847
);
3948
}
40-
JavaMemberNames::allocate(fields, methods).with_constructor_layouts(constructors)
49+
JavaMemberNames::allocate(fields, methods)
50+
.with_hidden_fields(hidden_fields)
51+
.with_constructor_layouts(constructors)
4152
}
4253

4354
pub(super) fn method_only(

dexdec/src/analysis/value_recovery/flow.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct UseFact {
7373
repetitive: bool,
7474
evaluation_prefix: EffectSummary,
7575
context: UseContext,
76+
consumer: Option<InsnType>,
7677
site: Option<UseSite>,
7778
}
7879

0 commit comments

Comments
 (0)