diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java index 5100082f078bb7a3d4d8704529b75382042ec7e4..1c20d42211cd351e752c6e212e0eec4eb3da03bc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DeboxingVisitor.java @@ -14,6 +14,7 @@ import jadx.core.dex.instructions.InvokeType; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.BlockNode; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; @@ -90,14 +91,16 @@ public class DeboxingVisitor extends AbstractVisitor { if (valueOfMths.contains(callMth)) { RegisterArg resArg = insnNode.getResult(); InsnArg arg = insnNode.getArg(0); - if (arg.isLiteral() && checkArgUsage(resArg)) { + if (arg.isLiteral()) { ArgType primitiveType = callMth.getArgumentsTypes().get(0); ArgType boxType = callMth.getReturnType(); if (isNeedExplicitCast(resArg, primitiveType, boxType)) { arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE); } - resArg.setType(primitiveType); arg.setType(primitiveType); + if (canChangeTypeToPrimitive(resArg)) { + resArg.setType(primitiveType); + } InsnNode constInsn = new InsnNode(InsnType.CONST, 1); constInsn.addArg(arg); @@ -122,17 +125,23 @@ public class DeboxingVisitor extends AbstractVisitor { return false; } - private boolean checkArgUsage(RegisterArg arg) { - for (RegisterArg useArg : arg.getSVar().getUseList()) { - InsnNode parentInsn = useArg.getParentInsn(); - if (parentInsn == null) { + private boolean canChangeTypeToPrimitive(RegisterArg arg) { + for (SSAVar ssaVar : arg.getSVar().getCodeVar().getSsaVars()) { + RegisterArg assignArg = ssaVar.getAssign(); + if (assignArg.contains(AFlag.IMMUTABLE_TYPE)) { return false; } - if (parentInsn.getType() == InsnType.INVOKE) { - InvokeNode invokeNode = (InvokeNode) parentInsn; - if (useArg.equals(invokeNode.getInstanceArg())) { + for (RegisterArg useArg : ssaVar.getUseList()) { + InsnNode parentInsn = useArg.getParentInsn(); + if (parentInsn == null) { return false; } + if (parentInsn.getType() == InsnType.INVOKE) { + InvokeNode invokeNode = (InvokeNode) parentInsn; + if (useArg.equals(invokeNode.getInstanceArg())) { + return false; + } + } } } return true; diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java new file mode 100644 index 0000000000000000000000000000000000000000..5a7247d56de259403be3192f8d72a849897e87cc --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing2.java @@ -0,0 +1,49 @@ +package jadx.tests.integration.others; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static jadx.tests.api.utils.JadxMatchers.countString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public class TestDeboxing2 extends IntegrationTest { + + public static class TestCls { + public long test(Long l) { + if (l == null) { + l = 0L; + } + return l; + } + + public void check() { + assertThat(test(null), is(0L)); + assertThat(test(0L), is(0L)); + assertThat(test(7L), is(7L)); + } + } + + @Test + public void test() { + noDebugInfo(); + + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("long test(Long l)")); + assertThat(code, containsOne("if (l == null) {")); + assertThat(code, containsOne("l = 0L;")); + + // checks for 'check' method + assertThat(code, containsOne("test(null)")); + assertThat(code, containsOne("test(0L)")); + assertThat(code, countString(2, "is(0L)")); + assertThat(code, containsOne("test(7L)")); + assertThat(code, containsOne("is(7L)")); + + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing3.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing3.java new file mode 100644 index 0000000000000000000000000000000000000000..0aebcb20d96bcd3f09f1a88280f398fd98bd40aa --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestDeboxing3.java @@ -0,0 +1,60 @@ +package jadx.tests.integration.others; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import jadx.NotYetImplemented; +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestDeboxing3 extends IntegrationTest { + + public static class TestCls { + + public static class Pair { + public F first; + public S second; + } + + private Map> cache = new HashMap<>(); + + public boolean test(String id, Long l) { + if (l == null) { + l = 900000L; + } + Pair pair = this.cache.get(id); + if (pair == null) { + return false; + } + return pair.first + l > System.currentTimeMillis(); + } + } + + @Test + public void test() { + noDebugInfo(); + + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("l = 900000L;")); + } + + @Test + @NotYetImplemented("Full deboxing and generics propagation") + public void testFull() { + noDebugInfo(); + + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("Pair pair = this.cache.get(id);")); + assertThat(code, containsOne("return pair.first + l > System.currentTimeMillis();")); + + } +}