From ef99412de174b9f4e33c6d2386ef7da46949696c Mon Sep 17 00:00:00 2001 From: Skylot <118523+skylot@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:27:29 +0000 Subject: [PATCH] fix: keep generics while applying debug info (#2687) --- .../src/main/java/jadx/api/JadxArgs.java | 9 +++ .../java/jadx/core/dex/nodes/RootNode.java | 16 ++++ .../debuginfo/DebugInfoApplyVisitor.java | 2 +- .../visitors/typeinference/TypeUpdate.java | 17 +++-- .../typeinference/TypeUpdateFlags.java | 74 ++++++++++--------- .../generics/MissingGenericsTypesTest.java | 32 -------- .../generics/TestMissingGenericsTypes2.java | 16 +++- .../generics/MissingGenericsTypesTest.smali | 64 ---------------- 8 files changed, 91 insertions(+), 139 deletions(-) delete mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/MissingGenericsTypesTest.java delete mode 100644 jadx-core/src/test/smali/generics/MissingGenericsTypesTest.smali diff --git a/jadx-core/src/main/java/jadx/api/JadxArgs.java b/jadx-core/src/main/java/jadx/api/JadxArgs.java index 7957224eb..882a1978b 100644 --- a/jadx-core/src/main/java/jadx/api/JadxArgs.java +++ b/jadx-core/src/main/java/jadx/api/JadxArgs.java @@ -201,6 +201,11 @@ public class JadxArgs implements Closeable { */ private boolean runDebugChecks = false; + /** + * Passes to exclude from processing. + */ + private final List disabledPasses = new ArrayList<>(); + private Map pluginOptions = new HashMap<>(); private Set disabledPlugins = new HashSet<>(); @@ -803,6 +808,10 @@ public class JadxArgs implements Closeable { this.runDebugChecks = runDebugChecks; } + public List getDisabledPasses() { + return disabledPasses; + } + public Map getPluginOptions() { return pluginOptions; } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java index 4d7a16982..2a64283a9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java @@ -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 disabledPasses = args.getDisabledPasses(); + if (!disabledPasses.isEmpty()) { + Set disabledSet = new HashSet<>(disabledPasses); + Predicate 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() { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java index e31f0cbd2..2c2804c1c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoApplyVisitor.java @@ -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); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 9eb870820..6395d7c82 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -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); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateFlags.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateFlags.java index e0dfc172a..c63ad2674 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateFlags.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateFlags.java @@ -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 flags; + + private static TypeUpdateFlags build(FlagsEnum... flags) { + EnumSet set; + if (flags.length == 0) { + set = EnumSet.noneOf(FlagsEnum.class); + } else { + set = EnumSet.copyOf(List.of(flags)); + } + return new TypeUpdateFlags(set); + } + + private TypeUpdateFlags(Set 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(); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/MissingGenericsTypesTest.java b/jadx-core/src/test/java/jadx/tests/integration/generics/MissingGenericsTypesTest.java deleted file mode 100644 index 0c2c7ffd7..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/generics/MissingGenericsTypesTest.java +++ /dev/null @@ -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 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 it = "); // variable name reject along with type } } diff --git a/jadx-core/src/test/smali/generics/MissingGenericsTypesTest.smali b/jadx-core/src/test/smali/generics/MissingGenericsTypesTest.smali deleted file mode 100644 index a7be5824c..000000000 --- a/jadx-core/src/test/smali/generics/MissingGenericsTypesTest.smali +++ /dev/null @@ -1,64 +0,0 @@ -.class public LMissingGenericsTypesTest; -.super Ljava/lang/Object; - -# instance fields -.field private x:I - - -# direct methods -.method public constructor ()V - .locals 0 - - .line 9 - invoke-direct {p0}, Ljava/lang/Object;->()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;->()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