fix: keep generics while applying debug info (#2687)
This commit is contained in:
@@ -201,6 +201,11 @@ public class JadxArgs implements Closeable {
|
||||
*/
|
||||
private boolean runDebugChecks = false;
|
||||
|
||||
/**
|
||||
* Passes to exclude from processing.
|
||||
*/
|
||||
private final List<String> disabledPasses = new ArrayList<>();
|
||||
|
||||
private Map<String, String> pluginOptions = new HashMap<>();
|
||||
|
||||
private Set<String> disabledPlugins = new HashSet<>();
|
||||
@@ -803,6 +808,10 @@ public class JadxArgs implements Closeable {
|
||||
this.runDebugChecks = runDebugChecks;
|
||||
}
|
||||
|
||||
public List<String> getDisabledPasses() {
|
||||
return disabledPasses;
|
||||
}
|
||||
|
||||
public Map<String, String> getPluginOptions() {
|
||||
return pluginOptions;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -342,6 +345,19 @@ public class RootNode {
|
||||
preDecompilePasses = DebugChecks.insertPasses(preDecompilePasses);
|
||||
processClasses = new ProcessClass(DebugChecks.insertPasses(processClasses.getPasses()));
|
||||
}
|
||||
List<String> disabledPasses = args.getDisabledPasses();
|
||||
if (!disabledPasses.isEmpty()) {
|
||||
Set<String> disabledSet = new HashSet<>(disabledPasses);
|
||||
Predicate<IDexTreeVisitor> filter = p -> {
|
||||
if (disabledSet.contains(p.getName())) {
|
||||
LOG.debug("Disable pass: {}", p.getName());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
preDecompilePasses.removeIf(filter);
|
||||
processClasses.getPasses().removeIf(filter);
|
||||
}
|
||||
}
|
||||
|
||||
public void runPreDecompileStage() {
|
||||
|
||||
@@ -134,7 +134,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
public static boolean applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType type, String varName) {
|
||||
TypeUpdateResult result = mth.root().getTypeUpdate().applyWithWiderIgnoreUnknown(mth, ssaVar, type);
|
||||
TypeUpdateResult result = mth.root().getTypeUpdate().applyDebugInfo(mth, ssaVar, type);
|
||||
if (result == TypeUpdateResult.REJECT) {
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
LOG.debug("Reject debug info of type: {} and name: '{}' for {}, mth: {}", type, varName, ssaVar, mth);
|
||||
|
||||
@@ -73,8 +73,8 @@ public final class TypeUpdate {
|
||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_SAME);
|
||||
}
|
||||
|
||||
public TypeUpdateResult applyWithWiderIgnoreUnknown(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNORE_UNKNOWN);
|
||||
public TypeUpdateResult applyDebugInfo(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_APPLY_DEBUG);
|
||||
}
|
||||
|
||||
private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) {
|
||||
@@ -124,8 +124,9 @@ public final class TypeUpdate {
|
||||
|
||||
private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||
ArgType currentType = arg.getType();
|
||||
TypeUpdateFlags typeUpdateFlags = updateInfo.getFlags();
|
||||
if (Objects.equals(currentType, candidateType)) {
|
||||
if (!updateInfo.getFlags().isIgnoreSame()) {
|
||||
if (!typeUpdateFlags.isIgnoreSame()) {
|
||||
return SAME;
|
||||
}
|
||||
} else {
|
||||
@@ -143,7 +144,7 @@ public final class TypeUpdate {
|
||||
}
|
||||
return REJECT;
|
||||
}
|
||||
if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) {
|
||||
if (compareResult == TypeCompareEnum.UNKNOWN && typeUpdateFlags.isIgnoreUnknown()) {
|
||||
return REJECT;
|
||||
}
|
||||
if (arg.isTypeImmutable() && currentType != ArgType.UNKNOWN) {
|
||||
@@ -156,7 +157,13 @@ public final class TypeUpdate {
|
||||
}
|
||||
return REJECT;
|
||||
}
|
||||
if (compareResult.isWider() && !updateInfo.getFlags().isAllowWider()) {
|
||||
if (compareResult == TypeCompareEnum.WIDER_BY_GENERIC && typeUpdateFlags.isKeepGenerics()) {
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
LOG.debug("Type rejected for {}: candidate={} is removing generic from current={}", arg, candidateType, currentType);
|
||||
}
|
||||
return REJECT;
|
||||
}
|
||||
if (compareResult.isWider() && !typeUpdateFlags.isAllowWider()) {
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
LOG.debug("Type rejected for {}: candidate={} is wider than current={}", arg, candidateType, currentType);
|
||||
}
|
||||
|
||||
@@ -1,55 +1,61 @@
|
||||
package jadx.core.dex.visitors.typeinference;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.ALLOW_WIDER;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.IGNORE_SAME;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.IGNORE_UNKNOWN;
|
||||
import static jadx.core.dex.visitors.typeinference.TypeUpdateFlags.FlagsEnum.KEEP_GENERICS;
|
||||
|
||||
public class TypeUpdateFlags {
|
||||
private static final int ALLOW_WIDER = 1;
|
||||
private static final int IGNORE_SAME = 2;
|
||||
private static final int IGNORE_UNKNOWN = 4;
|
||||
|
||||
public static final TypeUpdateFlags FLAGS_EMPTY = build(0);
|
||||
public static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
|
||||
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER | IGNORE_SAME);
|
||||
public static final TypeUpdateFlags FLAGS_WIDER_IGNORE_UNKNOWN = build(ALLOW_WIDER | IGNORE_UNKNOWN);
|
||||
|
||||
private final int flags;
|
||||
|
||||
private static TypeUpdateFlags build(int flags) {
|
||||
return new TypeUpdateFlags(flags);
|
||||
enum FlagsEnum {
|
||||
ALLOW_WIDER,
|
||||
IGNORE_SAME,
|
||||
IGNORE_UNKNOWN,
|
||||
KEEP_GENERICS,
|
||||
}
|
||||
|
||||
private TypeUpdateFlags(int flags) {
|
||||
static final TypeUpdateFlags FLAGS_EMPTY = build();
|
||||
static final TypeUpdateFlags FLAGS_WIDER = build(ALLOW_WIDER);
|
||||
static final TypeUpdateFlags FLAGS_WIDER_IGNORE_SAME = build(ALLOW_WIDER, IGNORE_SAME);
|
||||
static final TypeUpdateFlags FLAGS_APPLY_DEBUG = build(ALLOW_WIDER, KEEP_GENERICS, IGNORE_UNKNOWN);
|
||||
|
||||
private final Set<FlagsEnum> flags;
|
||||
|
||||
private static TypeUpdateFlags build(FlagsEnum... flags) {
|
||||
EnumSet<FlagsEnum> set;
|
||||
if (flags.length == 0) {
|
||||
set = EnumSet.noneOf(FlagsEnum.class);
|
||||
} else {
|
||||
set = EnumSet.copyOf(List.of(flags));
|
||||
}
|
||||
return new TypeUpdateFlags(set);
|
||||
}
|
||||
|
||||
private TypeUpdateFlags(Set<FlagsEnum> flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public boolean isAllowWider() {
|
||||
return (flags & ALLOW_WIDER) != 0;
|
||||
return flags.contains(ALLOW_WIDER);
|
||||
}
|
||||
|
||||
public boolean isIgnoreSame() {
|
||||
return (flags & IGNORE_SAME) != 0;
|
||||
return flags.contains(IGNORE_SAME);
|
||||
}
|
||||
|
||||
public boolean isIgnoreUnknown() {
|
||||
return (flags & IGNORE_UNKNOWN) != 0;
|
||||
return flags.contains(IGNORE_UNKNOWN);
|
||||
}
|
||||
|
||||
public boolean isKeepGenerics() {
|
||||
return flags.contains(KEEP_GENERICS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (isAllowWider()) {
|
||||
sb.append("ALLOW_WIDER");
|
||||
}
|
||||
if (isIgnoreSame()) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append('|');
|
||||
}
|
||||
sb.append("IGNORE_SAME");
|
||||
}
|
||||
if (isIgnoreUnknown()) {
|
||||
if (sb.length() != 0) {
|
||||
sb.append('|');
|
||||
}
|
||||
sb.append("IGNORE_UNKNOWN");
|
||||
}
|
||||
return sb.toString();
|
||||
return flags.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package jadx.tests.integration.generics;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.NotYetImplemented;
|
||||
import jadx.tests.api.SmaliTest;
|
||||
|
||||
import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class MissingGenericsTypesTest extends SmaliTest {
|
||||
// @formatter:off
|
||||
/*
|
||||
private int x;
|
||||
|
||||
public void test() {
|
||||
Map<String, String> map = new HashMap();
|
||||
x = 1;
|
||||
for (String s : map.keySet()) {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
@NotYetImplemented
|
||||
public void test() {
|
||||
assertThat(getClassNodeFromSmaliWithPath("generics", "MissingGenericsTypesTest"))
|
||||
.code()
|
||||
.contains("Map<String");
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -32,13 +32,23 @@ public class TestMissingGenericsTypes2 extends SmaliTest {
|
||||
}
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
// @formatter:on
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.contains("for (String s : l) {")
|
||||
.doesNotContain("Iterator i");
|
||||
.doesNotContain("Iterator i")
|
||||
.containsOne("for (String s : l) {");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypes() {
|
||||
// prevent loop from converting to 'for-each' to keep iterator variable type in code
|
||||
getArgs().getDisabledPasses().add("LoopRegionVisitor");
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.doesNotContain("Iterator i")
|
||||
.containsOne("Iterator<String> it = "); // variable name reject along with type
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
.class public LMissingGenericsTypesTest;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
# instance fields
|
||||
.field private x:I
|
||||
|
||||
|
||||
# direct methods
|
||||
.method public constructor <init>()V
|
||||
.locals 0
|
||||
|
||||
.line 9
|
||||
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
|
||||
|
||||
return-void
|
||||
.end method
|
||||
|
||||
|
||||
# virtual methods
|
||||
.method public test()V
|
||||
.locals 3
|
||||
|
||||
.line 14
|
||||
new-instance v0, Ljava/util/HashMap;
|
||||
|
||||
invoke-direct {v0}, Ljava/util/HashMap;-><init>()V
|
||||
|
||||
const/4 v1, 0x1
|
||||
|
||||
.line 15
|
||||
iput v1, p0, LMissingGenericsTypesTest;->x:I
|
||||
|
||||
.line 16
|
||||
invoke-interface {v0}, Ljava/util/Map;->keySet()Ljava/util/Set;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
invoke-interface {v0}, Ljava/util/Set;->iterator()Ljava/util/Iterator;
|
||||
|
||||
move-result-object v0
|
||||
|
||||
:goto_0
|
||||
invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z
|
||||
|
||||
move-result v1
|
||||
|
||||
if-eqz v1, :cond_0
|
||||
|
||||
invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object;
|
||||
|
||||
move-result-object v1
|
||||
|
||||
check-cast v1, Ljava/lang/String;
|
||||
|
||||
.line 17
|
||||
sget-object v2, Ljava/lang/System;->out:Ljava/io/PrintStream;
|
||||
|
||||
invoke-virtual {v2, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
|
||||
|
||||
goto :goto_0
|
||||
|
||||
:cond_0
|
||||
return-void
|
||||
.end method
|
||||
Reference in New Issue
Block a user