From 10f668cd1330c9638d59b710c3759cf418beb75d Mon Sep 17 00:00:00 2001 From: LLeaves Date: Sun, 16 Aug 2026 21:50:13 +0800 Subject: [PATCH] fix(overrides): tolerate incomplete FunctionN instantiations Kotlin FunctionN and some SAM signatures omit a type argument. Treat those instantiations as raw and skip a broken class instead of aborting the whole archive. --- dexdec/src/analysis/method_override.rs | 41 +++-- dexdec/src/analysis/method_override/tests.rs | 150 +++++++++++++++++++ 2 files changed, 167 insertions(+), 24 deletions(-) diff --git a/dexdec/src/analysis/method_override.rs b/dexdec/src/analysis/method_override.rs index cdadec5..13a7868 100644 --- a/dexdec/src/analysis/method_override.rs +++ b/dexdec/src/analysis/method_override.rs @@ -134,16 +134,13 @@ pub(crate) fn collect_instantiated_super_types( if !seen.insert(details.descriptor.clone()) { continue; } - let instantiated = bind_class(hierarchy, &details, &instantiated_parent)?; + let Ok(instantiated) = bind_class(hierarchy, &details, &instantiated_parent) else { + continue; + }; for (idx, parent_ty) in instantiated.parents.iter().enumerate() { - let generic = instantiated - .generic_parents - .get(idx) - .cloned() - .ok_or_else(|| OverrideAnalysisError::MissingErasedParent { - class: instantiated.descriptor.clone(), - index: idx, - })?; + let Some(generic) = instantiated.generic_parents.get(idx).cloned() else { + continue; + }; queue.push_back((parent_ty.clone(), generic)); } out.push(instantiated); @@ -177,9 +174,13 @@ where T: OverrideAnalysisTarget, { for class in classes { - let ancestors = self.collect_super_types(class)?; + let Ok(ancestors) = self.collect_super_types(class) else { + continue; + }; for method in &class.methods { - let semantics = self.analyze_method(class, method, &ancestors)?; + let Ok(semantics) = self.analyze_method(class, method, &ancestors) else { + continue; + }; target.set_method_override( &method.reference.declaring_class, &method.reference.short_id, @@ -528,23 +529,15 @@ fn collect_scope_type_parameters( substitutions: &mut TypeSubstitution, ) -> OverrideResult<()> { let Some(signature) = declared.generic_signature.as_ref() else { - if type_arguments.is_empty() { - return Ok(()); - } - return Err(OverrideAnalysisError::GenericArity { - class: declared.descriptor.clone(), - expected: 0, - actual: type_arguments.len(), - }); + return Ok(()); }; let expected = signature.type_parameters.len(); let actual = type_arguments.len(); + // Kotlin FunctionN and some SAM/lambda signatures omit a type + // argument (commonly the return type). Keep analysis going as a + // raw instantiation instead of failing the whole archive. if actual != 0 && actual != expected { - return Err(OverrideAnalysisError::GenericArity { - class: declared.descriptor.clone(), - expected, - actual, - }); + return Ok(()); } collect_instantiated_class_type_parameters(signature, type_arguments, substitutions) } diff --git a/dexdec/src/analysis/method_override/tests.rs b/dexdec/src/analysis/method_override/tests.rs index 950dd25..16f5ddb 100644 --- a/dexdec/src/analysis/method_override/tests.rs +++ b/dexdec/src/analysis/method_override/tests.rs @@ -463,6 +463,156 @@ fn binds_outer_and_inner_generic_scopes() { ); } +#[test] +fn incomplete_functionn_instantiation_does_not_fail_scope_binding() { + let function3 = class_with_signature( + "Lkotlin/jvm/functions/Function3;", + ["Ljava/lang/Object;"], + [], + Some( + GenericSignatures::class( + "Ljava/lang/Object;", + ) + .expect("Function3 class signature"), + ), + "kotlin.jvm.functions", + ); + let args = ["java/lang/String", "java/lang/Integer", "java/lang/Long"] + .into_iter() + .map(|name| { + TypeArgument::Exact(JvmTypeSignature::ClassType(parse_class_type_signature( + name, + ))) + }) + .collect::>(); + let mut substitutions = TypeSubstitution::new(); + + collect_scope_type_parameters(&function3, &args, &mut substitutions) + .expect("Function3 instantiated with three arguments must not fail"); +} + +#[test] +fn incomplete_functionn_parent_does_not_abort_override_analysis() { + let function3 = class_with_signature( + "Lkotlin/jvm/functions/Function3;", + ["Ljava/lang/Object;"], + [method( + "Lkotlin/jvm/functions/Function3;", + "invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + vec![ + ArgType::object("java/lang/Object"), + ArgType::object("java/lang/Object"), + ArgType::object("java/lang/Object"), + ], + ArgType::object("java/lang/Object"), + 0x0401, + )], + Some( + GenericSignatures::class( + "Ljava/lang/Object;", + ) + .expect("Function3 class signature"), + ), + "kotlin.jvm.functions", + ); + let child = class_with_signature( + "Lcom/example/Callback;", + ["Lkotlin/jvm/functions/Function3;"], + [method( + "Lcom/example/Callback;", + "invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", + vec![ + ArgType::object("java/lang/Object"), + ArgType::object("java/lang/Object"), + ArgType::object("java/lang/Object"), + ], + ArgType::object("java/lang/Object"), + 0x0001, + )], + Some( + GenericSignatures::class( + "Ljava/lang/Object;Lkotlin/jvm/functions/Function3;", + ) + .expect("three-argument Function3 instantiation"), + ), + "com.example", + ); + let hierarchy = LocalHierarchy::new([function3, child.clone()]); + let analyzer = MethodOverrideAnalyzer::new(&hierarchy); + let ancestors = analyzer + .collect_super_types(&child) + .expect("ancestor walk must survive a Function3 arity mismatch"); + assert!(ancestors + .iter() + .any(|class| class.descriptor == "Lkotlin/jvm/functions/Function3;")); + + let mut sink = OverrideSink::default(); + analyzer + .analyze(&mut sink, std::slice::from_ref(&child)) + .expect("archive override analysis must not fail"); + assert!(sink.0.iter().any(|(class, method)| { + class == "Lcom/example/Callback;" + && method + == "invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;" + })); +} + +#[test] +fn override_analysis_skips_a_broken_class_and_continues() { + let healthy = class( + "Lcom/example/Ok;", + ["Ljava/lang/Object;"], + [method( + "Lcom/example/Ok;", + "size()I", + Vec::new(), + ArgType::INT, + 0x0001, + )], + "com.example", + ); + let broken = ClassDetails { + descriptor: "Lcom/example/Broken;".to_string(), + package: "com.example".to_string(), + access_flags: AccessInfo::for_class(0x0001), + parents: vec![ArgType::object("java/lang/Object")], + generic_parents: Vec::new(), + generic_signature: None, + instantiated_self: None, + methods: vec![method( + "Lcom/example/Broken;", + "size()I", + Vec::new(), + ArgType::INT, + 0x0001, + )], + }; + let hierarchy = LocalHierarchy::new([healthy.clone(), broken.clone()]); + let mut sink = OverrideSink::default(); + MethodOverrideAnalyzer::new(&hierarchy) + .analyze(&mut sink, &[broken, healthy]) + .expect("one broken class must not abort the archive"); + assert!( + sink.0.iter().any(|(class, _)| class == "Lcom/example/Ok;"), + "healthy class should still be analyzed" + ); +} + +#[derive(Default)] +struct OverrideSink(Vec<(String, String)>); + +impl OverrideAnalysisTarget for OverrideSink { + fn set_method_override( + &mut self, + declaring_class: &str, + method_short_id: &str, + _semantics: Option, + ) { + self.0 + .push((declaring_class.to_string(), method_short_id.to_string())); + } +} + struct TestHierarchy { app: ClassDetails, platform: PlatformClassSet,