diff --git a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java index cdb64763e..5dc568e29 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/InsnGen.java @@ -29,6 +29,7 @@ import jadx.core.dex.instructions.args.InsnWrapArg; import jadx.core.dex.instructions.args.LiteralArg; import jadx.core.dex.instructions.args.Named; import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.instructions.mods.ConstructorInsn; import jadx.core.dex.instructions.mods.TernaryInsn; import jadx.core.dex.nodes.ClassNode; @@ -123,6 +124,9 @@ public class InsnGen { } public void declareVar(CodeWriter code, RegisterArg arg) { + if (arg.getSVar().contains(AFlag.FINAL)) { + code.add("final "); + } useType(code, arg.getType()); code.add(' '); code.add(mgen.getNameGen().assignArg(arg)); @@ -144,16 +148,19 @@ public class InsnGen { if (fieldNode != null) { FieldReplaceAttr replace = fieldNode.get(AType.FIELD_REPLACE); if (replace != null) { - FieldInfo info = replace.getFieldInfo(); - if (replace.isOuterClass()) { - useClass(code, info.getDeclClass()); - code.add(".this"); + switch (replace.getReplaceType()) { + case CLASS_INSTANCE: + useClass(code, replace.getClsRef()); + code.add(".this"); + break; + case VAR: + addArg(code, replace.getVarRef()); + break; } return; } } addArgDot(code, arg); - fieldNode = mth.dex().resolveField(field); if (fieldNode != null) { code.attachAnnotation(fieldNode); } @@ -531,30 +538,7 @@ public class InsnGen { throws CodegenException { ClassNode cls = mth.dex().resolveClass(insn.getClassType()); if (cls != null && cls.contains(AFlag.ANONYMOUS_CLASS) && !fallback) { - // anonymous class construction - ArgType parent; - if (cls.getInterfaces().size() == 1) { - parent = cls.getInterfaces().get(0); - } else { - parent = cls.getSuperClass(); - } - cls.add(AFlag.DONT_GENERATE); - MethodNode defCtr = cls.getDefaultConstructor(); - if (defCtr != null) { - if (RegionUtils.notEmpty(defCtr.getRegion())) { - defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR); - } else { - defCtr.add(AFlag.DONT_GENERATE); - } - } - code.add("new "); - if (parent == null) { - code.add("Object"); - } else { - useClass(code, parent); - } - code.add("() "); - new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code); + inlineAnonymousConstr(code, cls, insn); return; } if (insn.isSelf()) { @@ -568,7 +552,37 @@ public class InsnGen { code.add("new "); useClass(code, insn.getClassType()); } - generateMethodArguments(code, insn, 0, mth.dex().resolveMethod(insn.getCallMth())); + MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth()); + generateMethodArguments(code, insn, 0, callMth); + } + + private void inlineAnonymousConstr(CodeWriter code, ClassNode cls, ConstructorInsn insn) throws CodegenException { + // anonymous class construction + ArgType parent; + if (cls.getInterfaces().size() == 1) { + parent = cls.getInterfaces().get(0); + } else { + parent = cls.getSuperClass(); + } + cls.add(AFlag.DONT_GENERATE); + MethodNode defCtr = cls.getDefaultConstructor(); + if (defCtr != null) { + if (RegionUtils.notEmpty(defCtr.getRegion())) { + defCtr.add(AFlag.ANONYMOUS_CONSTRUCTOR); + } else { + defCtr.add(AFlag.DONT_GENERATE); + } + } + code.add("new "); + if (parent == null) { + code.add("Object"); + } else { + useClass(code, parent); + } + MethodNode callMth = mth.dex().resolveMethod(insn.getCallMth()); + generateMethodArguments(code, insn, 0, callMth); + code.add(' '); + new ClassGen(cls, mgen.getClassGen().getParentGen()).addClassBody(code); } private void makeInvoke(InvokeNode insn, CodeWriter code) throws CodegenException { @@ -631,6 +645,12 @@ public class InsnGen { boolean overloaded = callMth != null && callMth.isArgsOverload(); for (int i = k; i < argsCount; i++) { InsnArg arg = insn.getArg(i); + if (arg.isRegister()) { + SSAVar sVar = ((RegisterArg) arg).getSVar(); + if (sVar != null && sVar.contains(AFlag.SKIP_ARG)) { + continue; + } + } boolean cast = overloaded && processOverloadedArg(code, callMth, arg, i - startArgNum); if (!cast && i == argsCount - 1 && processVarArg(code, callMth, arg)) { continue; diff --git a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java index 5589cfb30..b087f2a31 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/MethodGen.java @@ -8,6 +8,7 @@ import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.trycatch.CatchAttr; @@ -126,6 +127,10 @@ public class MethodGen { if (paramsAnnotation != null) { annotationGen.addForParameter(argsCode, paramsAnnotation, i); } + SSAVar argSVar = arg.getSVar(); + if (argSVar!= null && argSVar.contains(AFlag.FINAL)) { + argsCode.add("final "); + } if (!it.hasNext() && mth.getAccessFlags().isVarArgs()) { // change last array argument to varargs ArgType type = arg.getType(); diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index f1c5f19c8..de36da802 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -8,6 +8,7 @@ public enum AFlag { LOOP_END, SYNTHETIC, + FINAL, // SSAVar attribute for make var final RETURN, // block contains only return instruction ORIG_RETURN, @@ -22,6 +23,7 @@ public enum AFlag { REMOVE, SKIP_FIRST_ARG, + SKIP_ARG, // skip argument in invoke call ANONYMOUS_CONSTRUCTOR, ANONYMOUS_CLASS, diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java index ff30c6f11..87ac110f0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/FieldReplaceAttr.java @@ -2,24 +2,39 @@ package jadx.core.dex.attributes.nodes; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.IAttribute; -import jadx.core.dex.info.FieldInfo; +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.instructions.args.InsnArg; public class FieldReplaceAttr implements IAttribute { - private final FieldInfo fieldInfo; - private final boolean isOuterClass; - - public FieldReplaceAttr(FieldInfo fieldInfo, boolean isOuterClass) { - this.fieldInfo = fieldInfo; - this.isOuterClass = isOuterClass; + public enum ReplaceWith { + CLASS_INSTANCE, + VAR } - public FieldInfo getFieldInfo() { - return fieldInfo; + private final ReplaceWith replaceType; + private final Object replaceObj; + + public FieldReplaceAttr(ClassInfo cls) { + this.replaceType = ReplaceWith.CLASS_INSTANCE; + this.replaceObj = cls; } - public boolean isOuterClass() { - return isOuterClass; + public FieldReplaceAttr(InsnArg reg) { + this.replaceType = ReplaceWith.VAR; + this.replaceObj = reg; + } + + public ReplaceWith getReplaceType() { + return replaceType; + } + + public ClassInfo getClsRef() { + return (ClassInfo) replaceObj; + } + + public InsnArg getVarRef() { + return (InsnArg) replaceObj; } @Override @@ -29,6 +44,6 @@ public class FieldReplaceAttr implements IAttribute { @Override public String toString() { - return "REPLACE: " + fieldInfo; + return "REPLACE: " + replaceType + " " + replaceObj; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java index 428a41e75..bf2693182 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/SSAVar.java @@ -1,5 +1,6 @@ package jadx.core.dex.instructions.args; +import jadx.core.dex.attributes.AttrNode; import jadx.core.dex.instructions.PhiInsn; import java.util.ArrayList; @@ -8,7 +9,7 @@ import java.util.List; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class SSAVar { +public class SSAVar extends AttrNode { private final int regNum; private final int version; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 5a2960c05..35e30f954 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -76,8 +76,7 @@ public class ClassModifier extends AbstractVisitor { } } if (found != 0) { - FieldInfo replace = FieldInfo.from(cls.dex(), parentClass, "this", parentClass.getType()); - field.addAttr(new FieldReplaceAttr(replace, true)); + field.addAttr(new FieldReplaceAttr(parentClass)); field.add(AFlag.DONT_GENERATE); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java b/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java index 20af5d155..371c85bb1 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/CodeShrinker.java @@ -210,7 +210,9 @@ public class CodeShrinker extends AbstractVisitor { // } SSAVar sVar = arg.getSVar(); // allow inline only one use arg or 'this' - if (sVar == null || sVar.getVariableUseCount() != 1 && !arg.isThis()) { + if (sVar == null + || sVar.getVariableUseCount() != 1 && !arg.isThis() + || sVar.contains(AFlag.DONT_INLINE)) { continue; } InsnNode assignInsn = sVar.getAssign().getParentInsn(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java index 6cae4a340..5bf1f0519 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DebugInfoVisitor.java @@ -18,6 +18,7 @@ public class DebugInfoVisitor extends AbstractVisitor { DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr); debugInfoParser.process(); + // set method source line from first instruction if (insnArr.length != 0) { for (InsnNode insn : insnArr) { if (insn != null) { @@ -30,7 +31,7 @@ public class DebugInfoVisitor extends AbstractVisitor { } } if (!mth.getReturnType().equals(ArgType.VOID)) { - // fix debug for splitter 'return' instructions + // fix debug info for splitter 'return' instructions for (BlockNode exit : mth.getExitBlocks()) { InsnNode ret = BlockUtils.getLastInsn(exit); if (ret == null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java index 969fac6f5..35c495e7d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/DotGraphVisitor.java @@ -23,8 +23,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class DotGraphVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(DotGraphVisitor.class); + private static final String NL = "\\l"; private static final boolean PRINT_DOMINATORS = false; @@ -52,6 +57,8 @@ public class DotGraphVisitor extends AbstractVisitor { this.dir = outDir; this.useRegions = useRegions; this.rawInsn = rawInsn; + LOG.debug("DOT {}{}graph dump dir: {}", + useRegions ? "regions " : "", rawInsn ? "raw " : "", outDir.getAbsolutePath()); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java index a384a34d6..e508ba59a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ModVisitor.java @@ -2,7 +2,11 @@ package jadx.core.dex.visitors; import jadx.core.codegen.TypeGen; import jadx.core.deobf.NameMapper; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.FieldReplaceAttr; +import jadx.core.dex.info.ClassInfo; +import jadx.core.dex.info.FieldInfo; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ConstClassNode; @@ -34,7 +38,10 @@ import jadx.core.utils.InstructionRemover; import jadx.core.utils.exceptions.JadxRuntimeException; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +50,11 @@ import org.slf4j.LoggerFactory; * Visitor for modify method instructions * (remove, replace, process exception handlers) */ +@JadxVisitor( + name = "ModVisitor", + desc = "Modify method instructions", + runBefore = CodeShrinker.class +) public class ModVisitor extends AbstractVisitor { private static final Logger LOG = LoggerFactory.getLogger(ModVisitor.class); @@ -197,7 +209,6 @@ public class ModVisitor extends AbstractVisitor { remover.add(insn); return; } - replaceInsn(block, insnNumber, co); if (co.isNewInstance()) { InsnNode newInstInsn = removeAssignChain(instArgAssignInsn, remover, InsnType.NEW_INSTANCE); if (newInstInsn != null) { @@ -217,8 +228,107 @@ public class ModVisitor extends AbstractVisitor { } ConstructorInsn replace = processConstructor(mth, co); if (replace != null) { - replaceInsn(block, insnNumber, replace); + co = replace; } + replaceInsn(block, insnNumber, co); + + processAnonymousConstructor(mth, co); + } + + private static void processAnonymousConstructor(MethodNode mth, ConstructorInsn co) { + MethodInfo callMth = co.getCallMth(); + MethodNode callMthNode = mth.dex().resolveMethod(callMth); + if (callMthNode == null) { + return; + } + ClassNode classNode = callMthNode.getParentClass(); + ClassInfo classInfo = classNode.getClassInfo(); + ClassNode parentClass = mth.getParentClass(); + if (!classInfo.isInner() + || !Character.isDigit(classInfo.getShortName().charAt(0)) + || !parentClass.getInnerClasses().contains(classNode)) { + return; + } + if (!classNode.getAccessFlags().isStatic() + && (callMth.getArgsCount() == 0 + || !callMth.getArgumentsTypes().get(0).equals(parentClass.getClassInfo().getType()))) { + return; + } + // TODO: calculate this constructor and other constructor usage + Map argsMap = getArgsToFieldsMapping(callMthNode, co); + if (argsMap.isEmpty()) { + return; + } + + // all checks passed + classNode.add(AFlag.ANONYMOUS_CLASS); + callMthNode.add(AFlag.DONT_GENERATE); + for (Map.Entry entry : argsMap.entrySet()) { + FieldNode field = entry.getValue(); + if (field == null) { + continue; + } + InsnArg arg = entry.getKey(); + field.addAttr(new FieldReplaceAttr(arg)); + field.add(AFlag.DONT_GENERATE); + if (arg.isRegister()) { + RegisterArg reg = (RegisterArg) arg; + SSAVar sVar = reg.getSVar(); + if (sVar != null) { + sVar.add(AFlag.FINAL); + sVar.add(AFlag.DONT_INLINE); + sVar.add(AFlag.SKIP_ARG); + } + } + } + } + + private static Map getArgsToFieldsMapping(MethodNode callMthNode, ConstructorInsn co) { + Map map = new LinkedHashMap(); + ClassNode parentClass = callMthNode.getParentClass(); + List argList = callMthNode.getArguments(false); + int startArg = parentClass.getAccessFlags().isStatic() ? 0 : 1; + int argsCount = argList.size(); + for (int i = startArg; i < argsCount; i++) { + RegisterArg arg = argList.get(i); + InsnNode useInsn = getParentInsnSkipMove(arg); + if (useInsn == null) { + return Collections.emptyMap(); + } + FieldNode fieldNode = null; + if (useInsn.getType() == InsnType.IPUT) { + FieldInfo field = (FieldInfo) ((IndexInsnNode) useInsn).getIndex(); + fieldNode = parentClass.searchField(field); + if (fieldNode == null || !fieldNode.getAccessFlags().isSynthetic()) { + return Collections.emptyMap(); + } + } else if (useInsn.getType() == InsnType.CONSTRUCTOR) { + ConstructorInsn superConstr = (ConstructorInsn) useInsn; + if (!superConstr.isSuper()) { + return Collections.emptyMap(); + } + } else { + return Collections.emptyMap(); + } + map.put(co.getArg(i), fieldNode); + } + return map; + } + + private static InsnNode getParentInsnSkipMove(RegisterArg arg) { + SSAVar sVar = arg.getSVar(); + if (sVar.getUseCount() != 1) { + return null; + } + RegisterArg useArg = sVar.getUseList().get(0); + InsnNode parentInsn = useArg.getParentInsn(); + if (parentInsn == null) { + return null; + } + if (parentInsn.getType() == InsnType.MOVE) { + return getParentInsnSkipMove(parentInsn.getResult()); + } + return parentInsn; } /** diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java index 26167f477..8552ab8aa 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/PrepareForCodeGen.java @@ -21,6 +21,11 @@ import java.util.List; * most of this modification breaks register dependencies, * so this pass must be just before CodeGen. */ +@JadxVisitor( + name = "PrepareForCodeGen", + desc = "Prepare instructions for code generation pass", + runAfter = {CodeShrinker.class, ClassModifier.class} +) public class PrepareForCodeGen extends AbstractVisitor { @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index d2b5ecf9f..b64490234 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -77,25 +77,7 @@ public class SimplifyVisitor extends AbstractVisitor { return convertFieldArith(mth, insn); case CHECK_CAST: - InsnArg castArg = insn.getArg(0); - ArgType argType = castArg.getType(); - - // Don't removes CHECK_CAST for wrapped INVOKE if invoked method returns different type - if (castArg.isInsnWrap()) { - InsnNode wrapInsn = ((InsnWrapArg) castArg).getWrapInsn(); - if (wrapInsn.getType() == InsnType.INVOKE) { - argType = ((InvokeNode) wrapInsn).getCallMth().getReturnType(); - } - } - ArgType castToType = (ArgType) ((IndexInsnNode) insn).getIndex(); - if (!ArgType.isCastNeeded(mth.dex(), argType, castToType)) { - InsnNode insnNode = new InsnNode(InsnType.MOVE, 1); - insnNode.setOffset(insn.getOffset()); - insnNode.setResult(insn.getResult()); - insnNode.addArg(castArg); - return insnNode; - } - break; + return processCast(mth, insn); case MOVE: InsnArg firstArg = insn.getArg(0); @@ -114,6 +96,28 @@ public class SimplifyVisitor extends AbstractVisitor { return null; } + private static InsnNode processCast(MethodNode mth, InsnNode insn) { + InsnArg castArg = insn.getArg(0); + ArgType argType = castArg.getType(); + + // Don't removes CHECK_CAST for wrapped INVOKE if invoked method returns different type + if (castArg.isInsnWrap()) { + InsnNode wrapInsn = ((InsnWrapArg) castArg).getWrapInsn(); + if (wrapInsn.getType() == InsnType.INVOKE) { + argType = ((InvokeNode) wrapInsn).getCallMth().getReturnType(); + } + } + ArgType castToType = (ArgType) ((IndexInsnNode) insn).getIndex(); + if (ArgType.isCastNeeded(mth.dex(), argType, castToType)) { + return null; + } + InsnNode insnNode = new InsnNode(InsnType.MOVE, 1); + insnNode.setOffset(insn.getOffset()); + insnNode.setResult(insn.getResult()); + insnNode.addArg(castArg); + return insnNode; + } + /** * Simplify 'cmp' instruction in if condition */ diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java index e7609380c..683eea50f 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/ProcessTryCatchRegions.java @@ -55,7 +55,7 @@ public class ProcessTryCatchRegions extends AbstractRegionVisitor { } private static void searchTryCatchDominators(MethodNode mth, Map tryBlocksMap) { - final Set tryBlocks = new HashSet(); + Set tryBlocks = new HashSet(); // collect all try/catch blocks for (BlockNode block : mth.getBasicBlocks()) { CatchAttr c = block.get(AType.CATCH_BLOCK); diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass10.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass10.java new file mode 100644 index 000000000..d823caddd --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass10.java @@ -0,0 +1,47 @@ +package jadx.tests.integration.inner; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import java.util.Random; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestAnonymousClass10 extends IntegrationTest { + + public static class TestCls { + + public A test() { + Random random = new Random(); + int a2 = random.nextInt(); + int a3 = a2 + 3; + return new A(this, a2, a3, 4, 5, random.nextDouble()) { + @Override + public void m() { + System.out.println(1); + } + }; + } + + public abstract class A { + public A(TestCls a1, int a2, int a3, int a4, int a5, double a6) { + } + + public abstract void m(); + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("return new A(this, a2, a2 + 3, 4, 5, random.nextDouble()) {")); + assertThat(code, not(containsString("synthetic"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass6.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass6.java new file mode 100644 index 000000000..7c2f4546c --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass6.java @@ -0,0 +1,36 @@ +package jadx.tests.integration.inner; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestAnonymousClass6 extends IntegrationTest { + + public static class TestCls { + public Runnable test(final double d) { + return new Runnable() { + public void run() { + System.out.println(d); + } + }; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("public Runnable test(final double d) {")); + assertThat(code, containsOne("return new Runnable() {")); + assertThat(code, containsOne("public void run() {")); + assertThat(code, containsOne("System.out.println(d);")); + assertThat(code, not(containsString("synthetic"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass7.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass7.java new file mode 100644 index 000000000..d35e77b65 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass7.java @@ -0,0 +1,37 @@ +package jadx.tests.integration.inner; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestAnonymousClass7 extends IntegrationTest { + + public static class TestCls { + public static Runnable test(final double d) { + return new Runnable() { + public void run() { + System.out.println(d); + } + }; + } + + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("public static Runnable test(final double d) {")); + assertThat(code, containsOne("return new Runnable() {")); + assertThat(code, containsOne("public void run() {")); + assertThat(code, containsOne("System.out.println(d);")); + assertThat(code, not(containsString("synthetic"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass8.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass8.java new file mode 100644 index 000000000..f2004c46e --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass8.java @@ -0,0 +1,39 @@ +package jadx.tests.integration.inner; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestAnonymousClass8 extends IntegrationTest { + + public static class TestCls { + + public final double d = Math.abs(4); + + public Runnable test() { + return new Runnable() { + public void run() { + System.out.println(d); + } + }; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("public Runnable test() {")); + assertThat(code, containsOne("return new Runnable() {")); + assertThat(code, containsOne("public void run() {")); + assertThat(code, containsOne("this.d);")); + assertThat(code, not(containsString("synthetic"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass9.java b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass9.java new file mode 100644 index 000000000..70977dcc6 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/inner/TestAnonymousClass9.java @@ -0,0 +1,45 @@ +package jadx.tests.integration.inner; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + +import org.junit.Test; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +public class TestAnonymousClass9 extends IntegrationTest { + + public static class TestCls { + + public Callable c = new Callable() { + @Override + public String call() throws Exception { + return "str"; + } + }; + + public Runnable test() { + return new FutureTask(this.c) { + public void run() { + System.out.println(6); + } + }; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("c = new Callable() {")); + assertThat(code, containsOne("return new FutureTask(this.c) {")); + assertThat(code, not(containsString("synthetic"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java b/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java index 3a586a504..dedfea0e8 100644 --- a/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java +++ b/jadx-core/src/test/java/jadx/tests/smali/TestConstructor.java @@ -17,7 +17,6 @@ public class TestConstructor extends SmaliTest { disableCompilation(); ClassNode cls = getClassNodeFromSmali("TestConstructor"); String code = cls.getCode().toString(); - System.out.println(code); assertThat(code, containsOne("new SomeObject(arg3);")); assertThat(code, not(containsString("= someObject")));