From 05863881f8d5a1e4c1aa721bb51d8795e6fb78ab Mon Sep 17 00:00:00 2001 From: nitram84 <58364572+nitram84@users.noreply.github.com> Date: Wed, 5 Nov 2025 22:04:22 +0100 Subject: [PATCH] fix: detect more for-each loops (also prevent issues with missing generics types) (PR #2689) --- .../visitors/regions/LoopRegionVisitor.java | 2 +- .../generics/TestMissingGenericsTypes2.java | 44 ++++++++ .../generics/TestMissingGenericsTypes2.smali | 101 ++++++++++++++++++ 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/generics/TestMissingGenericsTypes2.java create mode 100644 jadx-core/src/test/smali/generics/TestMissingGenericsTypes2.smali diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java index bcd03bae1..09d5608a7 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/LoopRegionVisitor.java @@ -396,7 +396,7 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor if (insn.getType() == InsnType.INVOKE) { InvokeNode inv = (InvokeNode) insn; MethodInfo callMth = inv.getCallMth(); - if (inv.getInvokeType() == InvokeType.INTERFACE + if ((inv.getInvokeType() == InvokeType.INTERFACE || inv.getInvokeType() == InvokeType.VIRTUAL) && callMth.getShortId().equals(mthId)) { if (declClsFullName == null) { return true; diff --git a/jadx-core/src/test/java/jadx/tests/integration/generics/TestMissingGenericsTypes2.java b/jadx-core/src/test/java/jadx/tests/integration/generics/TestMissingGenericsTypes2.java new file mode 100644 index 000000000..d3edfac9b --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/generics/TestMissingGenericsTypes2.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.generics; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestMissingGenericsTypes2 extends SmaliTest { + // @formatter:off + /* + package generics; + + import java.util.Iterator; + + public class TestMissingGenericsTypes2 implements Iterable { + + @Override + public Iterator iterator() { + return null; + } + + public void test(TestMissingGenericsTypes2 l) { + Iterator i = l.iterator(); // <-- This generics type was removed in smali + while (i.hasNext()) { + String s = i.next(); + doSomething(s); + } + } + + private void doSomething(String s) { + } + } + */ + // @formatter:on + + @Test + public void test() { + assertThat(getClassNodeFromSmali()) + .code() + .contains("for (String s : l) {") + .doesNotContain("Iterator i"); + } +} diff --git a/jadx-core/src/test/smali/generics/TestMissingGenericsTypes2.smali b/jadx-core/src/test/smali/generics/TestMissingGenericsTypes2.smali new file mode 100644 index 000000000..7959e510a --- /dev/null +++ b/jadx-core/src/test/smali/generics/TestMissingGenericsTypes2.smali @@ -0,0 +1,101 @@ +.class public Lgenerics/TestMissingGenericsTypes2; +.super Ljava/lang/Object; +.source "TestMissingGenericsTypes2.java" + +# interfaces +.implements Ljava/lang/Iterable; + + +# annotations +.annotation system Ldalvik/annotation/Signature; + value = { + "", + "Ljava/lang/Object;", + "Ljava/lang/Iterable<", + "TT;>;" + } +.end annotation + + +# direct methods +.method public constructor ()V + .registers 1 + + .local p0, "this":Lgenerics/TestMissingGenericsTypes2;, "Lgenerics/TestMissingGenericsTypes2;" + invoke-direct {p0}, Ljava/lang/Object;->()V + + return-void +.end method + +.method private doSomething(Ljava/lang/String;)V + .registers 2 + .param p1, "s" # Ljava/lang/String; + + .local p0, "this":Lgenerics/TestMissingGenericsTypes2;, "Lgenerics/TestMissingGenericsTypes2;" + return-void +.end method + + +# virtual methods +.method public iterator()Ljava/util/Iterator; + .registers 2 + .annotation system Ldalvik/annotation/Signature; + value = { + "()", + "Ljava/util/Iterator<", + "TT;>;" + } + .end annotation + + .local p0, "this":Lgenerics/TestMissingGenericsTypes2;, "Lgenerics/TestMissingGenericsTypes2;" + const/4 v0, 0x0 + + return-object v0 +.end method + +.method public test(Lgenerics/TestMissingGenericsTypes2;)V + .registers 4 + .annotation system Ldalvik/annotation/Signature; + value = { + "(", + "Lgenerics/TestMissingGenericsTypes2<", + "Ljava/lang/String;", + ">;)V" + } + .end annotation + + .local p0, "this":Lgenerics/TestMissingGenericsTypes2;, "Lgenerics/TestMissingGenericsTypes2;" + .local p1, "l":Lgenerics/TestMissingGenericsTypes2;, "Lgenerics/TestMissingGenericsTypes2;" + invoke-virtual {p1}, Lgenerics/TestMissingGenericsTypes2;->iterator()Ljava/util/Iterator; + + move-result-object v0 + + # original: + # .local v0, "i":Ljava/util/Iterator;, "Ljava/util/Iterator;" + # manipulated: removed generic type + .local v0, "i":Ljava/util/Iterator; + + :goto_4 + invoke-interface {v0}, Ljava/util/Iterator;->hasNext()Z + + move-result v1 + + if-eqz v1, :cond_14 + + invoke-interface {v0}, Ljava/util/Iterator;->next()Ljava/lang/Object; + + move-result-object v1 + + check-cast v1, Ljava/lang/String; + + .local v1, "s":Ljava/lang/String; + invoke-direct {p0, v1}, Lgenerics/TestMissingGenericsTypes2;->doSomething(Ljava/lang/String;)V + + .end local v1 # "s":Ljava/lang/String; + goto :goto_4 + + :cond_14 + return-void +.end method