fix: restore missing type parameter declarations (#1800)
This commit is contained in:
@@ -13,8 +13,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.DecompilationMode;
|
||||
import jadx.api.ICodeCache;
|
||||
@@ -56,8 +54,6 @@ import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
|
||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
private final RootNode root;
|
||||
private final IClassData clsData;
|
||||
|
||||
@@ -174,10 +170,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return ArgType.object(superType);
|
||||
}
|
||||
|
||||
public void updateGenericClsData(ArgType superClass, List<ArgType> interfaces, List<ArgType> generics) {
|
||||
public void updateGenericClsData(List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
|
||||
this.generics = generics;
|
||||
this.superClass = superClass;
|
||||
this.interfaces = interfaces;
|
||||
this.generics = generics;
|
||||
}
|
||||
|
||||
private static void processAttributes(ClassNode cls) {
|
||||
|
||||
@@ -2,8 +2,12 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -18,7 +22,6 @@ import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxException;
|
||||
|
||||
public class SignatureProcessor extends AbstractVisitor {
|
||||
|
||||
private RootNode root;
|
||||
|
||||
@Override
|
||||
@@ -55,12 +58,54 @@ public class SignatureProcessor extends AbstractVisitor {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cls.updateGenericClsData(superClass, interfaces, generics);
|
||||
generics = fixTypeParamDeclarations(cls, generics, superClass, interfaces);
|
||||
cls.updateGenericClsData(generics, superClass, interfaces);
|
||||
} catch (Exception e) {
|
||||
cls.addWarnComment("Failed to parse class signature: " + sp.getSignature(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add missing type parameters from super type and interfaces to make code compilable
|
||||
*/
|
||||
private static List<ArgType> fixTypeParamDeclarations(ClassNode cls,
|
||||
List<ArgType> generics, ArgType superClass, List<ArgType> interfaces) {
|
||||
if (interfaces.isEmpty() && superClass.equals(ArgType.OBJECT)) {
|
||||
return generics;
|
||||
}
|
||||
Set<String> typeParams = new HashSet<>();
|
||||
superClass.visitTypes(t -> addGenericType(typeParams, t));
|
||||
interfaces.forEach(i -> i.visitTypes(t -> addGenericType(typeParams, t)));
|
||||
if (typeParams.isEmpty()) {
|
||||
return generics;
|
||||
}
|
||||
List<ArgType> knownTypeParams;
|
||||
if (cls.isInner()) {
|
||||
knownTypeParams = new ArrayList<>(generics);
|
||||
cls.visitParentClasses(p -> knownTypeParams.addAll(p.getGenericTypeParameters()));
|
||||
} else {
|
||||
knownTypeParams = generics;
|
||||
}
|
||||
for (ArgType declTypeParam : knownTypeParams) {
|
||||
typeParams.remove(declTypeParam.getObject());
|
||||
}
|
||||
if (typeParams.isEmpty()) {
|
||||
return generics;
|
||||
}
|
||||
cls.addInfoComment("Add missing generic type declarations: " + typeParams);
|
||||
List<ArgType> fixedGenerics = new ArrayList<>(generics.size() + typeParams.size());
|
||||
fixedGenerics.addAll(generics);
|
||||
typeParams.stream().sorted().map(ArgType::genericType).forEach(fixedGenerics::add);
|
||||
return fixedGenerics;
|
||||
}
|
||||
|
||||
private static @Nullable Object addGenericType(Set<String> usedTypeParameters, ArgType t) {
|
||||
if (t.isGenericType()) {
|
||||
usedTypeParameters.add(t.getObject());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ArgType validateClsType(ClassNode cls, ArgType candidateType, ArgType currentType) {
|
||||
if (!candidateType.isObject()) {
|
||||
cls.addWarnComment("Incorrect class signature, class is not object: " + SignatureParser.getSignature(cls));
|
||||
|
||||
@@ -224,6 +224,11 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
return sortedClsNodes;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ClassNode searchTestCls(List<ClassNode> list, String shortClsName) {
|
||||
return searchCls(list, getTestPkg() + '.' + shortClsName);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ClassNode searchCls(List<ClassNode> list, String clsName) {
|
||||
for (ClassNode cls : list) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package jadx.tests.integration.inline;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestInstanceLambda extends SmaliTest {
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes", "SameParameterValue" })
|
||||
public static class TestCls {
|
||||
|
||||
public <T> Map<T, T> test(List<? extends T> list) {
|
||||
return toMap(list, Lambda.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smali test missing 'T' definition in 'Lambda<T>'
|
||||
*/
|
||||
private static class Lambda<T> implements Function<T, T> {
|
||||
public static final Lambda INSTANCE = new Lambda();
|
||||
|
||||
@Override
|
||||
public T apply(T t) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Map<T, T> toMap(List<? extends T> list, Function<T, T> valueMap) {
|
||||
return list.stream().collect(Collectors.toMap(k -> k, valueMap));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
noDebugInfo();
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmaliDisableInline() {
|
||||
args.setInlineAnonymousClasses(false);
|
||||
List<ClassNode> classNodes = loadFromSmaliFiles();
|
||||
assertThat(searchTestCls(classNodes, "Lambda"))
|
||||
.code()
|
||||
.containsOne("class Lambda<T> implements Function<T, T> {");
|
||||
assertThat(searchTestCls(classNodes, "TestCls"))
|
||||
.code()
|
||||
.containsOne("Lambda.INSTANCE");
|
||||
}
|
||||
|
||||
@NotYetImplemented("Inline lambda by instance field")
|
||||
@Test
|
||||
public void testSmali() {
|
||||
List<ClassNode> classNodes = loadFromSmaliFiles();
|
||||
assertThat(classNodes)
|
||||
.describedAs("Expect lambda to be inlined")
|
||||
.hasSize(1);
|
||||
assertThat(searchTestCls(classNodes, "TestCls"))
|
||||
.code()
|
||||
.doesNotContain("Lambda.INSTANCE");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
.class public Linline/Lambda;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.implements Ljava/util/function/Function;
|
||||
|
||||
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"Ljava/lang/Object;",
|
||||
"Ljava/util/function/Function",
|
||||
"<TT;TT;>;"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
.field public static final INSTANCE:Linline/Lambda;
|
||||
|
||||
.method static constructor <clinit>()V
|
||||
.registers 1
|
||||
new-instance v0, Linline/Lambda;
|
||||
invoke-direct {v0}, Linline/Lambda;-><init>()V
|
||||
sput-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method private constructor <init>()V
|
||||
.registers 1
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
return-void
|
||||
.end method
|
||||
|
||||
.method public final apply(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
.registers 2
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"(TT;)TT;"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
return-object p1
|
||||
.end method
|
||||
@@ -0,0 +1,56 @@
|
||||
.class public Linline/TestCls;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.method public test(Ljava/util/List;)Ljava/util/Map;
|
||||
.registers 3
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"<T:",
|
||||
"Ljava/lang/Object;",
|
||||
">(",
|
||||
"Ljava/util/List",
|
||||
"<+TT;>;)",
|
||||
"Ljava/util/Map",
|
||||
"<TT;TT;>;"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
sget-object v0, Linline/Lambda;->INSTANCE:Linline/Lambda;
|
||||
invoke-static {p1, v0}, Linline/TestCls;->toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
.end method
|
||||
|
||||
|
||||
.method private static synthetic lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
.registers 1
|
||||
return-object p0
|
||||
.end method
|
||||
|
||||
.method private static toMap(Ljava/util/List;Ljava/util/function/Function;)Ljava/util/Map;
|
||||
.registers 4
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"<T:",
|
||||
"Ljava/lang/Object;",
|
||||
">(",
|
||||
"Ljava/util/List",
|
||||
"<+TT;>;",
|
||||
"Ljava/util/function/Function",
|
||||
"<TT;TT;>;)",
|
||||
"Ljava/util/Map",
|
||||
"<TT;TT;>;"
|
||||
}
|
||||
.end annotation
|
||||
|
||||
invoke-interface {p0}, Ljava/util/List;->stream()Ljava/util/stream/Stream;
|
||||
move-result-object v0
|
||||
invoke-custom {}, call_site_0("apply", ()Ljava/util/function/Function;, (Ljava/lang/Object;)Ljava/lang/Object;, invoke-static@Linline/TestCls;->lambda$toMap$0(Ljava/lang/Object;)Ljava/lang/Object;, (Ljava/lang/Object;)Ljava/lang/Object;)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
|
||||
move-result-object v1
|
||||
invoke-static {v1, p1}, Ljava/util/stream/Collectors;->toMap(Ljava/util/function/Function;Ljava/util/function/Function;)Ljava/util/stream/Collector;
|
||||
move-result-object v1
|
||||
invoke-interface {v0, v1}, Ljava/util/stream/Stream;->collect(Ljava/util/stream/Collector;)Ljava/lang/Object;
|
||||
move-result-object v0
|
||||
check-cast v0, Ljava/util/Map;
|
||||
return-object v0
|
||||
.end method
|
||||
Reference in New Issue
Block a user