diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index a1efbfb8dc8..4da023c84f5 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -67,6 +67,7 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.ssa.SSATransform; import jadx.core.dex.visitors.typeinference.FinishTypeInference; +import jadx.core.dex.visitors.typeinference.FixTypesVisitor; import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.dex.visitors.usage.UsageInfoVisitor; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -141,7 +142,9 @@ public static List getRegionsModePasses(JadxArgs args) { if (args.isDebugInfo()) { passes.add(new DebugInfoApplyVisitor()); } + passes.add(new FixTypesVisitor()); passes.add(new FinishTypeInference()); + if (args.getUseKotlinMethodsForVarNames() != JadxArgs.UseKotlinMethodsForVarNames.DISABLE) { passes.add(new ProcessKotlinInternals()); } @@ -216,6 +219,7 @@ public static List getSimpleModePasses(JadxArgs args) { if (args.isDebugInfo()) { passes.add(new DebugInfoApplyVisitor()); } + passes.add(new FixTypesVisitor()); passes.add(new FinishTypeInference()); passes.add(new CodeRenameVisitor()); passes.add(new DeboxingVisitor()); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java index b3c5ca424a1..def34d22c4e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/IndexInsnNode.java @@ -2,6 +2,7 @@ import java.util.Objects; +import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.nodes.InsnNode; import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; @@ -23,6 +24,10 @@ public void updateIndex(Object index) { this.index = index; } + public ArgType getIndexAsType() { + return (ArgType) index; + } + @Override public IndexInsnNode copy() { return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount())); diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java index bc32e332b9e..0f2aa514b96 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/InvokeNode.java @@ -103,6 +103,6 @@ public boolean isSame(InsnNode obj) { @Override public String toString() { - return baseString() + " type: " + type + " call: " + mth + attributesString(); + return baseString() + " " + type + " call: " + mth + attributesString(); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java index 8b184162988..665ddc946c5 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnArg.java @@ -16,8 +16,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException; /** - * Instruction argument, - * argument can be register, literal or instruction + * Instruction argument. + * Can be: register, literal, instruction or name */ public abstract class InsnArg extends Typed { @@ -209,7 +209,20 @@ public static InsnArg wrapArg(InsnNode insn) { } public boolean isZeroLiteral() { - return isLiteral() && (((LiteralArg) this)).getLiteral() == 0; + return false; + } + + public boolean isZeroConst() { + if (isZeroLiteral()) { + return true; + } + if (isInsnWrap()) { + InsnNode wrapInsn = ((InsnWrapArg) this).getWrapInsn(); + if (wrapInsn.getType() == InsnType.CONST) { + return wrapInsn.getArg(0).isZeroLiteral(); + } + } + return false; } public boolean isFalse() { @@ -265,6 +278,9 @@ public boolean isSameConst(InsnArg other) { } public boolean isSameVar(RegisterArg arg) { + if (arg == null) { + return false; + } if (isRegister()) { return ((RegisterArg) this).sameRegAndSVar(arg); } @@ -280,4 +296,8 @@ protected final T copyCommonParams(T copy) { public InsnArg duplicate() { return this; } + + public String toShortString() { + return this.toString(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java index 6794c76508a..326ae29f724 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/LiteralArg.java @@ -58,6 +58,11 @@ public boolean isLiteral() { return true; } + @Override + public boolean isZeroLiteral() { + return literal == 0; + } + public boolean isInteger() { switch (type.getPrimitiveType()) { case INT: @@ -125,6 +130,11 @@ public boolean equals(Object o) { return literal == that.literal && getType().equals(that.getType()); } + @Override + public String toShortString() { + return Long.toString(literal); + } + @Override public String toString() { try { diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/NamedArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/NamedArg.java index 3b04e8de67c..b2f353353cb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/NamedArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/NamedArg.java @@ -48,6 +48,11 @@ public boolean equals(Object o) { return name.equals(((NamedArg) o).name); } + @Override + public String toShortString() { + return name; + } + @Override public String toString() { return '(' + name + ' ' + type + ')'; diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java index fd4791fd36d..3fff9073938 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/RegisterArg.java @@ -215,6 +215,16 @@ public boolean equals(Object obj) { && Objects.equals(sVar, other.getSVar()); } + @Override + public String toShortString() { + StringBuilder sb = new StringBuilder(); + sb.append("r").append(regNum); + if (sVar != null) { + sb.append('v').append(sVar.getVersion()); + } + return sb.toString(); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); 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 afb4f2c9408..44d011c3763 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 @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -23,9 +24,12 @@ import jadx.core.utils.StringUtils; import jadx.core.utils.exceptions.JadxRuntimeException; -public class SSAVar { +public class SSAVar implements Comparable { private static final Logger LOG = LoggerFactory.getLogger(SSAVar.class); + private static final Comparator SSA_VAR_COMPARATOR = + Comparator.comparingInt(SSAVar::getRegNum).thenComparingInt(SSAVar::getVersion); + private final int regNum; private final int version; @@ -256,34 +260,6 @@ public boolean isCodeVarSet() { return codeVar != null; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof SSAVar)) { - return false; - } - SSAVar ssaVar = (SSAVar) o; - return regNum == ssaVar.regNum && version == ssaVar.version; - } - - @Override - public int hashCode() { - return 31 * regNum + version; - } - - public String toShortString() { - return "r" + regNum + 'v' + version; - } - - @Override - public String toString() { - return toShortString() - + (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "") - + ' ' + typeInfo.getType(); - } - public String getDetailedVarInfo(MethodNode mth) { Set types = new HashSet<>(); Set names = Collections.emptySet(); @@ -323,4 +299,37 @@ public String getDetailedVarInfo(MethodNode mth) { } return sb.toString(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof SSAVar)) { + return false; + } + SSAVar ssaVar = (SSAVar) o; + return regNum == ssaVar.regNum && version == ssaVar.version; + } + + @Override + public int hashCode() { + return 31 * regNum + version; + } + + @Override + public int compareTo(@NotNull SSAVar o) { + return SSA_VAR_COMPARATOR.compare(this, o); + } + + public String toShortString() { + return "r" + regNum + 'v' + version; + } + + @Override + public String toString() { + return toShortString() + + (StringUtils.notEmpty(getName()) ? " '" + getName() + "' " : "") + + ' ' + typeInfo.getType(); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java index 000e1846aa3..474aada1bdb 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ConstInlineVisitor.java @@ -26,6 +26,7 @@ import jadx.core.dex.visitors.typeinference.TypeInferenceVisitor; import jadx.core.utils.InsnRemover; import jadx.core.utils.exceptions.JadxException; +import jadx.core.utils.exceptions.JadxRuntimeException; @JadxVisitor( name = "Constants Inline", @@ -73,8 +74,7 @@ private static void checkInsn(MethodNode mth, InsnNode insn, List toRe if (!constArg.isLiteral()) { return; } - long lit = ((LiteralArg) constArg).getLiteral(); - if (lit == 0 && forbidNullInlines(sVar)) { + if (constArg.isZeroLiteral() && forbidNullInlines(sVar)) { // all usages forbids inlining return; } @@ -134,20 +134,15 @@ private static boolean forbidNullInlines(SSAVar sVar) { } private static boolean forbidNullArgInline(InsnNode insn, RegisterArg useArg) { - switch (insn.getType()) { - case MOVE: - case CAST: - case CHECK_CAST: - // result is null, chain checks - return forbidNullInlines(insn.getResult().getSVar()); - - default: - if (!canUseNull(insn, useArg)) { - useArg.add(AFlag.DONT_INLINE_CONST); - return true; - } - return false; + if (insn.getType() == InsnType.MOVE) { + // result is null, chain checks + return forbidNullInlines(insn.getResult().getSVar()); + } + if (!canUseNull(insn, useArg)) { + useArg.add(AFlag.DONT_INLINE_CONST); + return true; } + return false; } private static boolean canUseNull(InsnNode insn, RegisterArg useArg) { @@ -269,9 +264,7 @@ private static boolean replaceArg(MethodNode mth, RegisterArg arg, InsnArg const fieldNode.addUseIn(mth); } } else { - if (needExplicitCast(useInsn, litArg)) { - litArg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE); - } + addExplicitCast(useInsn, litArg); } } else { if (!useInsn.replaceArg(arg, constArg.duplicate())) { @@ -282,18 +275,33 @@ private static boolean replaceArg(MethodNode mth, RegisterArg arg, InsnArg const return true; } - private static boolean needExplicitCast(InsnNode insn, LiteralArg arg) { + private static void addExplicitCast(InsnNode insn, LiteralArg arg) { if (insn instanceof BaseInvokeNode) { BaseInvokeNode callInsn = (BaseInvokeNode) insn; MethodInfo callMth = callInsn.getCallMth(); - int offset = callInsn.getFirstArgOffset(); - int argIndex = insn.getArgIndex(arg); - ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset); - if (argType.isPrimitive()) { - arg.setType(argType); - return argType.equals(ArgType.BYTE); + if (callInsn.getInstanceArg() == arg) { + // instance arg is null, force cast + if (!arg.isZeroLiteral()) { + throw new JadxRuntimeException("Unexpected instance arg in invoke"); + } + ArgType castType = callMth.getDeclClass().getType(); + InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1); + castInsn.addArg(arg); + castInsn.add(AFlag.EXPLICIT_CAST); + InsnArg wrapCast = InsnArg.wrapArg(castInsn); + wrapCast.setType(castType); + insn.replaceArg(arg, wrapCast); + } else { + int offset = callInsn.getFirstArgOffset(); + int argIndex = insn.getArgIndex(arg); + ArgType argType = callMth.getArgumentsTypes().get(argIndex - offset); + if (argType.isPrimitive()) { + arg.setType(argType); + if (argType.equals(ArgType.BYTE)) { + arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE); + } + } } } - return false; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java index 6df5e766f17..be856c50d12 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MarkMethodsForInline.java @@ -99,7 +99,7 @@ private static boolean isSyntheticAccessPattern(MethodNode mth, InsnNode firstIn && retInsn.getArg(0).isSameVar(firstInsn.getResult()) && firstInsn.getArg(0).isSameVar(mthRegs.get(0)); case SGET: - return mthRegs.size() == 0 + return mthRegs.isEmpty() && retInsn.getArg(0).isSameVar(firstInsn.getResult()); case IPUT: @@ -113,7 +113,7 @@ private static boolean isSyntheticAccessPattern(MethodNode mth, InsnNode firstIn && firstInsn.getArg(0).isSameVar(mthRegs.get(0)); case INVOKE: - return mthRegs.size() >= 1 + return !mthRegs.isEmpty() && firstInsn.getArg(0).isSameVar(mthRegs.get(0)) && retInsn.getArg(0).isSameVar(firstInsn.getResult()); default: diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java index 007b48bc491..a5cbff756cc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/MethodInvokeVisitor.java @@ -5,6 +5,9 @@ import java.util.Map; import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import jadx.api.ICodeWriter; import jadx.core.Consts; import jadx.core.dex.attributes.AFlag; @@ -28,6 +31,7 @@ import jadx.core.dex.visitors.shrink.CodeShrinkVisitor; import jadx.core.dex.visitors.typeinference.TypeCompare; import jadx.core.dex.visitors.typeinference.TypeCompareEnum; +import jadx.core.utils.InsnUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -43,6 +47,8 @@ } ) public class MethodInvokeVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(MethodInvokeVisitor.class); + private RootNode root; @Override @@ -116,7 +122,8 @@ private void processOverloaded(MethodNode parentMth, BaseInvokeNode invokeInsn, int argsOffset = invokeInsn.getFirstArgOffset(); List compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset); List castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes); - applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes); + List resultCastTypes = expandTypes(parentMth, effectiveMthDetails, castTypes); + applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, resultCastTypes); } /** @@ -180,7 +187,13 @@ private void applyArgsCast(BaseInvokeNode invokeInsn, int argsOffset, List searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List overloadedMethods, List compilerVarTypes) { - // try compile types + // try compiler types if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) { return compilerVarTypes; } @@ -300,6 +313,27 @@ private boolean replaceUnknownTypes(List castTypes, List mthAr return changed; } + /** + * Use generified types if available + */ + private List expandTypes(MethodNode parentMth, IMethodDetails methodDetails, List castTypes) { + TypeCompare typeCompare = parentMth.root().getTypeCompare(); + List mthArgTypes = methodDetails.getArgTypes(); + int argsCount = castTypes.size(); + List list = new ArrayList<>(argsCount); + for (int i = 0; i < argsCount; i++) { + ArgType mthType = mthArgTypes.get(i); + ArgType castType = castTypes.get(i); + TypeCompareEnum result = typeCompare.compareTypes(mthType, castType); + if (result == TypeCompareEnum.NARROW_BY_GENERIC) { + list.add(mthType); + } else { + list.add(castType); + } + } + return list; + } + private boolean isOverloadResolved(IMethodDetails expectedMthDetails, List overloadedMethods, List castTypes) { if (overloadedMethods.isEmpty()) { return false; @@ -387,12 +421,22 @@ private ArgType getCompilerVarType(InsnArg arg) { } if (arg instanceof InsnWrapArg) { InsnWrapArg wrapArg = (InsnWrapArg) arg; - InsnNode wrapInsn = wrapArg.getWrapInsn(); - if (wrapInsn.getResult() != null) { - return wrapInsn.getResult().getType(); - } - return arg.getType(); + return getInsnCompilerType(arg, wrapArg.getWrapInsn()); } throw new JadxRuntimeException("Unknown var type for: " + arg); } + + private static ArgType getInsnCompilerType(InsnArg arg, InsnNode insn) { + switch (insn.getType()) { + case CAST: + case CHECK_CAST: + return ((IndexInsnNode) insn).getIndexAsType(); + + default: + if (insn.getResult() != null) { + return insn.getResult().getType(); + } + return arg.getType(); + } + } } 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 1c4c9e1ea97..ad6ae92d96c 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 @@ -389,6 +389,11 @@ private static boolean checkArrSizes(MethodNode mth, NewArrayNode newArrInsn, Fi private static void removeCheckCast(MethodNode mth, BlockNode block, int i, IndexInsnNode insn) { InsnArg castArg = insn.getArg(0); + if (castArg.isZeroLiteral()) { + // always keep cast for 'null' + insn.add(AFlag.EXPLICIT_CAST); + return; + } ArgType castType = (ArgType) insn.getIndex(); if (!ArgType.isCastNeeded(mth.root(), castArg.getType(), castType)) { RegisterArg result = insn.getResult(); 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 944c573fbab..7d3f41b85d0 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 @@ -25,7 +25,9 @@ import jadx.core.dex.info.FieldInfo; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.ArithOp; +import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.InvokeNode; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.InsnWrapArg; @@ -82,6 +84,7 @@ public void visit(MethodNode mth) throws JadxException { removeParenthesis(block); modifyArith(block); checkConstUsage(block); + addNullCasts(mth, block); } moveConstructorInConstructor(mth); collectFieldsUsageInAnnotations(mth, mth); @@ -379,4 +382,27 @@ private void checkEncodedValue(MethodNode mth, EncodedValue encodedValue) { break; } } + + private void addNullCasts(MethodNode mth, BlockNode block) { + for (InsnNode insn : block.getInstructions()) { + switch (insn.getType()) { + case INVOKE: + verifyNullCast(mth, ((InvokeNode) insn).getInstanceArg()); + break; + + case ARRAY_LENGTH: + verifyNullCast(mth, insn.getArg(0)); + break; + } + } + } + + private void verifyNullCast(MethodNode mth, InsnArg arg) { + if (arg != null && arg.isZeroConst()) { + ArgType castType = arg.getType(); + IndexInsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1); + castInsn.addArg(InsnArg.lit(0, castType)); + arg.wrapInstruction(mth, castInsn); + } + } } 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 ea532595324..e31f0cbd213 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 @@ -89,7 +89,7 @@ private static void searchDebugInfoByOffset(MethodNode mth, SSAVar ssaVar) { return; } OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max(); - if (!max.isPresent()) { + if (max.isEmpty()) { return; } int startOffset = getInsnOffsetByArg(ssaVar.getAssign()); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java index 5864404179b..75624fedced 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/regions/variables/ProcessVariables.java @@ -84,7 +84,7 @@ public void processBlock(MethodNode mth, IBlock container) { if (insn.canRemoveResult()) { // remove unused result remove = true; - } else if (insn.isConstInsn()) { + } else if (canRemoveInsn(insn)) { // remove whole insn insn.add(AFlag.REMOVE); insn.add(AFlag.DONT_GENERATE); @@ -101,6 +101,22 @@ public void processBlock(MethodNode mth, IBlock container) { } } + /** + * Remove insn if a result is not used + */ + private boolean canRemoveInsn(InsnNode insn) { + if (insn.isConstInsn()) { + return true; + } + switch (insn.getType()) { + case CAST: + case CHECK_CAST: + return true; + default: + return false; + } + } + private boolean isVarUnused(MethodNode mth, @Nullable SSAVar ssaVar) { if (ssaVar == null) { return true; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java index 11270f5c870..c848f20ce43 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FinishTypeInference.java @@ -30,4 +30,9 @@ public void visit(MethodNode mth) { } }); } + + @Override + public String getName() { + return "FinishTypeInference"; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java new file mode 100644 index 00000000000..5041be98e97 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/FixTypesVisitor.java @@ -0,0 +1,844 @@ +package jadx.core.dex.visitors.typeinference; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jadx.core.Consts; +import jadx.core.clsp.ClspGraph; +import jadx.core.dex.attributes.AFlag; +import jadx.core.dex.attributes.AType; +import jadx.core.dex.attributes.nodes.PhiListAttr; +import jadx.core.dex.instructions.ArithNode; +import jadx.core.dex.instructions.ArithOp; +import jadx.core.dex.instructions.IndexInsnNode; +import jadx.core.dex.instructions.InsnType; +import jadx.core.dex.instructions.PhiInsn; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.LiteralArg; +import jadx.core.dex.instructions.args.PrimitiveType; +import jadx.core.dex.instructions.args.RegisterArg; +import jadx.core.dex.instructions.args.SSAVar; +import jadx.core.dex.instructions.mods.TernaryInsn; +import jadx.core.dex.nodes.BlockNode; +import jadx.core.dex.nodes.InsnNode; +import jadx.core.dex.nodes.MethodNode; +import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.AbstractVisitor; +import jadx.core.dex.visitors.InitCodeVariables; +import jadx.core.dex.visitors.JadxVisitor; +import jadx.core.dex.visitors.ModVisitor; +import jadx.core.dex.visitors.blocks.BlockSplitter; +import jadx.core.utils.BlockUtils; +import jadx.core.utils.InsnList; +import jadx.core.utils.InsnUtils; +import jadx.core.utils.ListUtils; +import jadx.core.utils.Utils; +import jadx.core.utils.exceptions.JadxOverflowException; + +@JadxVisitor( + name = "Fix Types Visitor", + desc = "Try various methods to fix unresolved types", + runAfter = { + TypeInferenceVisitor.class + }, + runBefore = { + FinishTypeInference.class + } +) +public final class FixTypesVisitor extends AbstractVisitor { + private static final Logger LOG = LoggerFactory.getLogger(FixTypesVisitor.class); + + private final TypeInferenceVisitor typeInference = new TypeInferenceVisitor(); + + private TypeUpdate typeUpdate; + private List> resolvers; + + @Override + public void init(RootNode root) { + this.typeUpdate = root.getTypeUpdate(); + this.typeInference.init(root); + this.resolvers = Arrays.asList( + this::tryRestoreTypeVarCasts, + this::tryInsertCasts, + this::tryDeduceTypes, + this::trySplitConstInsns, + this::tryToFixIncompatiblePrimitives, + this::tryToForceImmutableTypes, + this::tryInsertAdditionalMove, + this::runMultiVariableSearch, + this::tryRemoveGenerics); + } + + @Override + public void visit(MethodNode mth) { + if (mth.isNoCode() || checkTypes(mth)) { + return; + } + try { + for (Function resolver : resolvers) { + if (resolver.apply(mth) && checkTypes(mth)) { + break; + } + } + } catch (Exception e) { + mth.addError("Types fix failed", e); + } + } + + /** + * Check if all types resolved + */ + private static boolean checkTypes(MethodNode mth) { + for (SSAVar var : mth.getSVars()) { + ArgType type = var.getTypeInfo().getType(); + if (!type.isTypeKnown()) { + return false; + } + } + return true; + } + + private boolean runMultiVariableSearch(MethodNode mth) { + try { + TypeSearch typeSearch = new TypeSearch(mth); + if (!typeSearch.run()) { + mth.addWarnComment("Multi-variable type inference failed"); + } + for (SSAVar var : mth.getSVars()) { + if (!var.getTypeInfo().getType().isTypeKnown()) { + return false; + } + } + return true; + } catch (Exception e) { + mth.addWarnComment("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e)); + return false; + } + } + + private boolean setBestType(MethodNode mth, SSAVar ssaVar) { + try { + return calculateFromBounds(mth, ssaVar); + } catch (JadxOverflowException e) { + throw e; + } catch (Exception e) { + mth.addWarnComment("Failed to calculate best type for var: " + ssaVar, e); + return false; + } + } + + private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) { + TypeInfo typeInfo = ssaVar.getTypeInfo(); + Set bounds = typeInfo.getBounds(); + Optional bestTypeOpt = selectBestTypeFromBounds(bounds); + if (bestTypeOpt.isEmpty()) { + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.warn("Failed to select best type from bounds, count={} : ", bounds.size()); + for (ITypeBound bound : bounds) { + LOG.warn(" {}", bound); + } + } + return false; + } + ArgType candidateType = bestTypeOpt.get(); + TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, candidateType); + if (result == TypeUpdateResult.REJECT) { + if (Consts.DEBUG_TYPE_INFERENCE) { + if (ssaVar.getTypeInfo().getType().equals(candidateType)) { + LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); + } else if (candidateType.isTypeKnown()) { + LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); + } + } + return false; + } + return result == TypeUpdateResult.CHANGED; + } + + private Optional selectBestTypeFromBounds(Set bounds) { + return bounds.stream() + .map(ITypeBound::getType) + .filter(Objects::nonNull) + .max(typeUpdate.getTypeCompare().getComparator()); + } + + private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) { + List types = makePossibleTypesList(type, var); + if (types.isEmpty()) { + return false; + } + for (ArgType candidateType : types) { + TypeUpdateResult result = typeUpdate.apply(mth, var, candidateType); + if (result == TypeUpdateResult.CHANGED) { + return true; + } + } + return false; + } + + private List makePossibleTypesList(ArgType type, @Nullable SSAVar var) { + if (type.isArray()) { + List list = new ArrayList<>(); + for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement(), null)) { + list.add(ArgType.array(arrElemType)); + } + return list; + } + if (var != null) { + for (ITypeBound b : var.getTypeInfo().getBounds()) { + ArgType boundType = b.getType(); + if (boundType.isObject() || boundType.isArray()) { + // don't add primitive types + return Collections.emptyList(); + } + } + } + List list = new ArrayList<>(); + for (PrimitiveType possibleType : type.getPossibleTypes()) { + if (possibleType == PrimitiveType.VOID) { + continue; + } + list.add(ArgType.convertFromPrimitiveType(possibleType)); + } + return list; + } + + private boolean tryDeduceTypes(MethodNode mth) { + boolean fixed = false; + for (SSAVar ssaVar : mth.getSVars()) { + if (deduceType(mth, ssaVar)) { + fixed = true; + } + } + return fixed; + } + + @SuppressWarnings("RedundantIfStatement") + private boolean deduceType(MethodNode mth, SSAVar var) { + if (var.isTypeImmutable()) { + return false; + } + ArgType type = var.getTypeInfo().getType(); + if (type.isTypeKnown()) { + return false; + } + // try best type from bounds again + if (setBestType(mth, var)) { + return true; + } + // try all possible types (useful for primitives) + if (tryPossibleTypes(mth, var, type)) { + return true; + } + // for objects try super types + if (tryWiderObjects(mth, var)) { + return true; + } + return false; + } + + private boolean tryRemoveGenerics(MethodNode mth) { + boolean resolved = true; + for (SSAVar var : mth.getSVars()) { + ArgType type = var.getTypeInfo().getType(); + if (!type.isTypeKnown() + && !var.isTypeImmutable() + && !tryRawType(mth, var)) { + resolved = false; + } + } + return resolved; + } + + private boolean tryRawType(MethodNode mth, SSAVar var) { + Set objTypes = new LinkedHashSet<>(); + for (ITypeBound bound : var.getTypeInfo().getBounds()) { + ArgType boundType = bound.getType(); + if (boundType.isTypeKnown() && boundType.isObject()) { + objTypes.add(boundType); + } + } + if (objTypes.isEmpty()) { + return false; + } + for (ArgType objType : objTypes) { + if (checkRawType(mth, var, objType)) { + mth.addDebugComment("Type inference failed for " + var.toShortString() + "." + + " Raw type applied. Possible types: " + Utils.listToString(objTypes)); + return true; + } + } + return false; + } + + private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) { + if (objType.isObject() && objType.containsGeneric()) { + ArgType rawType = objType.isGenericType() ? ArgType.OBJECT : ArgType.object(objType.getObject()); + TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, rawType); + return result == TypeUpdateResult.CHANGED; + } + return false; + } + + /** + * Fix check casts to type var extend type: + *
+ * {@code T var = (Comparable) obj; => T var = (T) obj; } + */ + private boolean tryRestoreTypeVarCasts(MethodNode mth) { + int changed = 0; + List mthSVars = mth.getSVars(); + for (SSAVar var : mthSVars) { + changed += restoreTypeVarCasts(var); + } + if (changed == 0) { + return false; + } + if (Consts.DEBUG_TYPE_INFERENCE) { + mth.addDebugComment("Restore " + changed + " type vars casts"); + } + typeInference.initTypeBounds(mth); + return typeInference.runTypePropagation(mth); + } + + private int restoreTypeVarCasts(SSAVar var) { + TypeInfo typeInfo = var.getTypeInfo(); + Set bounds = typeInfo.getBounds(); + if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) { + return 0; + } + List casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance); + if (casts.isEmpty()) { + return 0; + } + ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN); + if (!bestType.isGenericType()) { + return 0; + } + List extendTypes = bestType.getExtendTypes(); + if (extendTypes.size() != 1) { + return 0; + } + int fixed = 0; + ArgType extendType = extendTypes.get(0); + for (ITypeBound bound : casts) { + TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound; + ArgType castType = cast.getType(); + TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType); + if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) { + cast.getInsn().updateIndex(bestType); + fixed++; + } + } + return fixed; + } + + @SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" }) + private boolean tryInsertCasts(MethodNode mth) { + int added = 0; + List mthSVars = mth.getSVars(); + int varsCount = mthSVars.size(); + for (int i = 0; i < varsCount; i++) { + SSAVar var = mthSVars.get(i); + ArgType type = var.getTypeInfo().getType(); + if (!type.isTypeKnown() && !var.isTypeImmutable()) { + added += tryInsertVarCast(mth, var); + } + } + if (added != 0) { + InitCodeVariables.rerun(mth); + typeInference.initTypeBounds(mth); + return typeInference.runTypePropagation(mth); + } + return false; + } + + private int tryInsertVarCast(MethodNode mth, SSAVar var) { + for (ITypeBound bound : var.getTypeInfo().getBounds()) { + ArgType boundType = bound.getType(); + if (boundType.isTypeKnown() + && !boundType.equals(var.getTypeInfo().getType()) + && boundType.containsTypeVariable() + && !mth.root().getTypeUtils().containsUnknownTypeVar(mth, boundType)) { + if (insertAssignCast(mth, var, boundType)) { + return 1; + } + return insertUseCasts(mth, var); + } + } + return 0; + } + + private int insertUseCasts(MethodNode mth, SSAVar var) { + List useList = var.getUseList(); + if (useList.isEmpty()) { + return 0; + } + int useCasts = 0; + for (RegisterArg useReg : new ArrayList<>(useList)) { + if (insertSoftUseCast(mth, useReg)) { + useCasts++; + } + } + return useCasts; + } + + private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) { + RegisterArg assignArg = var.getAssign(); + InsnNode assignInsn = assignArg.getParentInsn(); + if (assignInsn == null || assignInsn.getType() == InsnType.PHI) { + return false; + } + BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn); + if (assignBlock == null) { + return false; + } + assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth)); + IndexInsnNode castInsn = makeSoftCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType); + return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn); + } + + private boolean insertSoftUseCast(MethodNode mth, RegisterArg useArg) { + InsnNode useInsn = useArg.getParentInsn(); + if (useInsn == null || useInsn.getType() == InsnType.PHI) { + return false; + } + if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroConst()) { + // cast isn't needed if compare with null + return false; + } + BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn); + if (useBlock == null) { + return false; + } + IndexInsnNode castInsn = makeSoftCastInsn( + useArg.duplicateWithNewSSAVar(mth), + useArg.duplicate(), + useArg.getInitType()); + useInsn.replaceArg(useArg, castInsn.getResult().duplicate()); + boolean success = BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn); + if (Consts.DEBUG_TYPE_INFERENCE && success) { + LOG.info("Insert soft cast for {} before {} in {}", useArg, useInsn, useBlock); + } + return success; + } + + private IndexInsnNode makeSoftCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) { + IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1); + castInsn.setResult(result); + castInsn.addArg(arg); + castInsn.add(AFlag.SOFT_CAST); + castInsn.add(AFlag.SYNTHETIC); + return castInsn; + } + + private boolean trySplitConstInsns(MethodNode mth) { + boolean constSplit = false; + for (SSAVar var : new ArrayList<>(mth.getSVars())) { + if (checkAndSplitConstInsn(mth, var)) { + constSplit = true; + } + } + if (!constSplit) { + return false; + } + InitCodeVariables.rerun(mth); + typeInference.initTypeBounds(mth); + return typeInference.runTypePropagation(mth); + } + + private boolean checkAndSplitConstInsn(MethodNode mth, SSAVar var) { + ArgType type = var.getTypeInfo().getType(); + if (type.isTypeKnown() || var.isTypeImmutable()) { + return false; + } + return splitByPhi(mth, var) || dupConst(mth, var); + } + + private boolean dupConst(MethodNode mth, SSAVar var) { + InsnNode assignInsn = var.getAssign().getAssignInsn(); + if (!InsnUtils.isInsnType(assignInsn, InsnType.CONST)) { + return false; + } + if (var.getUseList().size() < 2) { + return false; + } + BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn); + if (assignBlock == null) { + return false; + } + assignInsn.remove(AFlag.DONT_INLINE); + int insertIndex = 1 + BlockUtils.getInsnIndexInBlock(assignBlock, assignInsn); + List useList = new ArrayList<>(var.getUseList()); + for (int i = 0, useCount = useList.size(); i < useCount; i++) { + RegisterArg useArg = useList.get(i); + useArg.remove(AFlag.DONT_INLINE_CONST); + if (i == 0) { + continue; + } + InsnNode useInsn = useArg.getParentInsn(); + if (useInsn == null) { + continue; + } + InsnNode newInsn = assignInsn.copyWithNewSsaVar(mth); + assignBlock.getInstructions().add(insertIndex, newInsn); + useInsn.replaceArg(useArg, newInsn.getResult().duplicate()); + } + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.debug("Duplicate const insn {} times: {} in {}", useList.size(), assignInsn, assignBlock); + } + return true; + } + + /** + * For every PHI make separate CONST insn + */ + private static boolean splitByPhi(MethodNode mth, SSAVar var) { + if (var.getUsedInPhi().size() < 2) { + return false; + } + InsnNode assignInsn = var.getAssign().getAssignInsn(); + InsnNode constInsn = InsnUtils.checkInsnType(assignInsn, InsnType.CONST); + if (constInsn == null) { + return false; + } + BlockNode blockNode = BlockUtils.getBlockByInsn(mth, constInsn); + if (blockNode == null) { + return false; + } + boolean first = true; + for (PhiInsn phiInsn : var.getUsedInPhi()) { + if (first) { + first = false; + continue; + } + InsnNode copyInsn = constInsn.copyWithNewSsaVar(mth); + copyInsn.add(AFlag.SYNTHETIC); + BlockUtils.insertAfterInsn(blockNode, constInsn, copyInsn); + + RegisterArg phiArg = phiInsn.getArgBySsaVar(var); + phiInsn.replaceArg(phiArg, copyInsn.getResult().duplicate()); + } + return true; + } + + private boolean tryInsertAdditionalMove(MethodNode mth) { + int insnsAdded = 0; + for (BlockNode block : mth.getBasicBlocks()) { + PhiListAttr phiListAttr = block.get(AType.PHI_LIST); + if (phiListAttr != null) { + for (PhiInsn phiInsn : phiListAttr.getList()) { + insnsAdded += tryInsertAdditionalInsn(mth, phiInsn); + } + } + } + if (insnsAdded == 0) { + return false; + } + if (Consts.DEBUG_TYPE_INFERENCE) { + mth.addDebugComment("Additional " + insnsAdded + " move instructions added to help type inference"); + } + InitCodeVariables.rerun(mth); + typeInference.initTypeBounds(mth); + if (typeInference.runTypePropagation(mth) && checkTypes(mth)) { + return true; + } + return tryDeduceTypes(mth); + } + + /** + * Add MOVE instruction before PHI in bound blocks to make 'soft' type link. + * This allows using different types in blocks merged by PHI. + */ + private int tryInsertAdditionalInsn(MethodNode mth, PhiInsn phiInsn) { + ArgType phiType = getCommonTypeForPhiArgs(phiInsn); + if (phiType != null && phiType.isTypeKnown()) { + // all args have the same known type => nothing to do here + return 0; + } + // check if instructions can be inserted + if (insertMovesForPhi(mth, phiInsn, false) == 0) { + return 0; + } + // check passed => apply + return insertMovesForPhi(mth, phiInsn, true); + } + + @Nullable + private ArgType getCommonTypeForPhiArgs(PhiInsn phiInsn) { + ArgType phiArgType = null; + for (InsnArg arg : phiInsn.getArguments()) { + ArgType type = arg.getType(); + if (phiArgType == null) { + phiArgType = type; + } else if (!phiArgType.equals(type)) { + return null; + } + } + return phiArgType; + } + + private int insertMovesForPhi(MethodNode mth, PhiInsn phiInsn, boolean apply) { + int argsCount = phiInsn.getArgsCount(); + int count = 0; + for (int argIndex = 0; argIndex < argsCount; argIndex++) { + RegisterArg reg = phiInsn.getArg(argIndex); + BlockNode startBlock = phiInsn.getBlockByArgIndex(argIndex); + BlockNode blockNode = checkBlockForInsnInsert(startBlock); + if (blockNode == null) { + mth.addDebugComment("Failed to insert an additional move for type inference into block " + startBlock); + return 0; + } + boolean add = true; + SSAVar var = reg.getSVar(); + InsnNode assignInsn = var.getAssign().getAssignInsn(); + if (assignInsn != null) { + InsnType assignType = assignInsn.getType(); + if (assignType == InsnType.CONST + || (assignType == InsnType.MOVE && var.getUseCount() == 1)) { + add = false; + } + } + if (add) { + count++; + if (apply) { + insertMove(mth, blockNode, phiInsn, reg); + } + } + } + return count; + } + + private void insertMove(MethodNode mth, BlockNode blockNode, PhiInsn phiInsn, RegisterArg reg) { + SSAVar var = reg.getSVar(); + int regNum = reg.getRegNum(); + RegisterArg resultArg = reg.duplicate(regNum, null); + SSAVar newSsaVar = mth.makeNewSVar(resultArg); + RegisterArg arg = reg.duplicate(regNum, var); + + InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1); + moveInsn.setResult(resultArg); + moveInsn.addArg(arg); + moveInsn.add(AFlag.SYNTHETIC); + blockNode.getInstructions().add(moveInsn); + + phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar)); + } + + @Nullable + private BlockNode checkBlockForInsnInsert(BlockNode blockNode) { + if (blockNode.isSynthetic()) { + return null; + } + InsnNode lastInsn = BlockUtils.getLastInsn(blockNode); + if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) { + // can't insert move in a block with 'separate' instruction => try previous block by simple path + List preds = blockNode.getPredecessors(); + if (preds.size() == 1) { + return checkBlockForInsnInsert(preds.get(0)); + } + return null; + } + return blockNode; + } + + private boolean tryWiderObjects(MethodNode mth, SSAVar var) { + Set objTypes = new LinkedHashSet<>(); + for (ITypeBound bound : var.getTypeInfo().getBounds()) { + ArgType boundType = bound.getType(); + if (boundType.isTypeKnown() && boundType.isObject()) { + objTypes.add(boundType); + } + } + if (objTypes.isEmpty()) { + return false; + } + ClspGraph clsp = mth.root().getClsp(); + for (ArgType objType : objTypes) { + for (String ancestor : clsp.getSuperTypes(objType.getObject())) { + ArgType ancestorType = ArgType.object(ancestor); + TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, ancestorType); + if (result == TypeUpdateResult.CHANGED) { + return true; + } + } + } + return false; + } + + @SuppressWarnings("ForLoopReplaceableByForEach") + private boolean tryToFixIncompatiblePrimitives(MethodNode mth) { + boolean fixed = false; + List ssaVars = mth.getSVars(); + int ssaVarsCount = ssaVars.size(); + // new vars will be added at a list end if fix is applied (can't use for-each loop here) + for (int i = 0; i < ssaVarsCount; i++) { + if (processIncompatiblePrimitives(mth, ssaVars.get(i))) { + fixed = true; + } + } + if (!fixed) { + return false; + } + InitCodeVariables.rerun(mth); + typeInference.initTypeBounds(mth); + return typeInference.runTypePropagation(mth); + } + + private boolean processIncompatiblePrimitives(MethodNode mth, SSAVar var) { + TypeInfo typeInfo = var.getTypeInfo(); + if (typeInfo.getType().isTypeKnown()) { + return false; + } + boolean assigned = false; + for (ITypeBound bound : typeInfo.getBounds()) { + ArgType boundType = bound.getType(); + switch (bound.getBound()) { + case ASSIGN: + if (!boundType.contains(PrimitiveType.BOOLEAN)) { + return false; + } + assigned = true; + break; + case USE: + if (!boundType.canBeAnyNumber()) { + return false; + } + break; + } + } + if (!assigned) { + return false; + } + + boolean fixed = false; + for (RegisterArg arg : new ArrayList<>(var.getUseList())) { + if (fixBooleanUsage(mth, arg)) { + fixed = true; + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.info("Fixed boolean usage for arg {} from {}", arg, arg.getParentInsn()); + } + } + } + return fixed; + } + + private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) { + ArgType boundType = boundArg.getInitType(); + if (boundType == ArgType.BOOLEAN + || (boundType.isTypeKnown() && !boundType.isPrimitive())) { + return false; + } + InsnNode insn = boundArg.getParentInsn(); + if (insn == null || insn.getType() == InsnType.IF) { + return false; + } + BlockNode blockNode = BlockUtils.getBlockByInsn(mth, insn); + if (blockNode == null) { + return false; + } + List insnList = blockNode.getInstructions(); + int insnIndex = InsnList.getIndex(insnList, insn); + if (insnIndex == -1) { + return false; + } + InsnType insnType = insn.getType(); + if (insnType == InsnType.CAST) { + // replace cast + ArgType type = (ArgType) ((IndexInsnNode) insn).getIndex(); + TernaryInsn convertInsn = prepareBooleanConvertInsn(insn.getResult(), boundArg, type); + BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn); + return true; + } + if (insnType == InsnType.ARITH) { + ArithNode arithInsn = (ArithNode) insn; + if (arithInsn.getOp() == ArithOp.XOR && arithInsn.getArgsCount() == 2) { + // replace (boolean ^ 1) with (!boolean) + InsnArg secondArg = arithInsn.getArg(1); + if (secondArg.isLiteral() && ((LiteralArg) secondArg).getLiteral() == 1) { + InsnNode convertInsn = notBooleanToInt(arithInsn, boundArg); + BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn); + return true; + } + } + } + + // insert before insn + RegisterArg resultArg = boundArg.duplicateWithNewSSAVar(mth); + TernaryInsn convertInsn = prepareBooleanConvertInsn(resultArg, boundArg, boundType); + insnList.add(insnIndex, convertInsn); + insn.replaceArg(boundArg, convertInsn.getResult().duplicate()); + return true; + } + + private InsnNode notBooleanToInt(ArithNode insn, RegisterArg boundArg) { + InsnNode notInsn = new InsnNode(InsnType.NOT, 1); + notInsn.addArg(boundArg.duplicate()); + notInsn.add(AFlag.SYNTHETIC); + + ArgType resType = insn.getResult().getType(); + if (resType.canBePrimitive(PrimitiveType.BOOLEAN)) { + notInsn.setResult(insn.getResult()); + return notInsn; + } + InsnArg notArg = InsnArg.wrapArg(notInsn); + notArg.setType(ArgType.BOOLEAN); + TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(insn.getResult(), notArg, ArgType.INT); + convertInsn.add(AFlag.SYNTHETIC); + return convertInsn; + } + + private TernaryInsn prepareBooleanConvertInsn(RegisterArg resultArg, RegisterArg boundArg, ArgType useType) { + RegisterArg useArg = boundArg.getSVar().getAssign().duplicate(); + TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(resultArg, useArg, useType); + convertInsn.add(AFlag.SYNTHETIC); + return convertInsn; + } + + private boolean tryToForceImmutableTypes(MethodNode mth) { + boolean fixed = false; + for (SSAVar ssaVar : mth.getSVars()) { + ArgType type = ssaVar.getTypeInfo().getType(); + if (!type.isTypeKnown() && ssaVar.isTypeImmutable()) { + if (forceImmutableType(ssaVar)) { + fixed = true; + } + } + } + if (!fixed) { + return false; + } + return typeInference.runTypePropagation(mth); + } + + private boolean forceImmutableType(SSAVar ssaVar) { + for (RegisterArg useArg : ssaVar.getUseList()) { + InsnNode parentInsn = useArg.getParentInsn(); + if (parentInsn != null) { + InsnType insnType = parentInsn.getType(); + if (insnType == InsnType.AGET || insnType == InsnType.APUT) { + ssaVar.setType(ssaVar.getImmutableType()); + return true; + } + } + } + return false; + } + + @Override + public String getName() { + return "FixTypesVisitor"; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundCheckCastAssign.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundCheckCastAssign.java index 7359bc08474..5ce98cd3bbd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundCheckCastAssign.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeBoundCheckCastAssign.java @@ -8,7 +8,7 @@ import jadx.core.dex.nodes.RootNode; /** - * Allow to ignore down casts (return arg type instead cast type) + * Allow ignoring down casts (return arg type instead cast type) * Such casts will be removed later. */ public final class TypeBoundCheckCastAssign implements ITypeBoundDynamic { @@ -36,7 +36,7 @@ public ArgType getType() { } private ArgType getReturnType(ArgType argType) { - ArgType castType = (ArgType) insn.getIndex(); + ArgType castType = insn.getIndexAsType(); TypeCompareEnum result = root.getTypeCompare().compareTypes(argType, castType); return result.isNarrow() ? argType : castType; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java index 05e3bbd4a9b..7ec4181bfd9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompare.java @@ -18,6 +18,7 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT; +import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.CONFLICT_BY_GENERIC; import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.EQUAL; import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW; import static jadx.core.dex.visitors.typeinference.TypeCompareEnum.NARROW_BY_GENERIC; @@ -265,6 +266,9 @@ private TypeCompareEnum compareGenericTypeWithObject(ArgType genericType, ArgTyp if (objType.isGenericType()) { return compareTypeVariables(genericType, objType); } + if (objType.isWildcard()) { + return CONFLICT_BY_GENERIC; + } boolean rootObject = objType.equals(ArgType.OBJECT); List extendTypes = genericType.getExtendTypes(); if (extendTypes.isEmpty()) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java index a96023d93e8..c659fad8b38 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeCompareEnum.java @@ -7,6 +7,7 @@ public enum TypeCompareEnum { WIDER, WIDER_BY_GENERIC, // same basic type without generic CONFLICT, + CONFLICT_BY_GENERIC, // same basic type, conflict in generics UNKNOWN; public TypeCompareEnum invert() { @@ -24,6 +25,7 @@ public TypeCompareEnum invert() { return NARROW_BY_GENERIC; case CONFLICT: + case CONFLICT_BY_GENERIC: case EQUAL: case UNKNOWN: default: @@ -51,7 +53,7 @@ public boolean isNarrowOrEqual() { return isEqual() || isNarrow(); } - public boolean isGeneric() { - return this == WIDER_BY_GENERIC || this == NARROW_BY_GENERIC; + public boolean isConflict() { + return this == CONFLICT || this == CONFLICT_BY_GENERIC; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java index 7a62c2bb999..4eac287305b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInferenceVisitor.java @@ -1,29 +1,19 @@ package jadx.core.dex.visitors.typeinference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.function.Function; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.Consts; -import jadx.core.clsp.ClspGraph; import jadx.core.dex.attributes.AFlag; import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.AnonymousClassAttr; -import jadx.core.dex.attributes.nodes.PhiListAttr; import jadx.core.dex.info.ClassInfo; -import jadx.core.dex.instructions.ArithNode; -import jadx.core.dex.instructions.ArithOp; import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.IndexInsnNode; import jadx.core.dex.instructions.InsnType; @@ -33,12 +23,9 @@ import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.LiteralArg; -import jadx.core.dex.instructions.args.PrimitiveType; 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.BlockNode; import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.IMethodDetails; import jadx.core.dex.nodes.InsnNode; @@ -49,16 +36,8 @@ import jadx.core.dex.visitors.AbstractVisitor; import jadx.core.dex.visitors.AttachMethodDetails; import jadx.core.dex.visitors.ConstInlineVisitor; -import jadx.core.dex.visitors.InitCodeVariables; import jadx.core.dex.visitors.JadxVisitor; -import jadx.core.dex.visitors.ModVisitor; -import jadx.core.dex.visitors.blocks.BlockSplitter; import jadx.core.dex.visitors.ssa.SSATransform; -import jadx.core.utils.BlockUtils; -import jadx.core.utils.InsnList; -import jadx.core.utils.InsnUtils; -import jadx.core.utils.ListUtils; -import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxOverflowException; @JadxVisitor( @@ -75,24 +54,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor { private RootNode root; private TypeUpdate typeUpdate; - private List> resolvers; @Override public void init(RootNode root) { this.root = root; this.typeUpdate = root.getTypeUpdate(); - this.resolvers = Arrays.asList( - this::initTypeBounds, - this::runTypePropagation, - this::tryRestoreTypeVarCasts, - this::tryInsertCasts, - this::tryDeduceTypes, - this::trySplitConstInsns, - this::tryToFixIncompatiblePrimitives, - this::tryToForceImmutableTypes, - this::tryInsertAdditionalMove, - this::runMultiVariableSearch, - this::tryRemoveGenerics); } @Override @@ -103,73 +69,39 @@ public void visit(MethodNode mth) { if (Consts.DEBUG_TYPE_INFERENCE) { LOG.info("Start type inference in method: {}", mth); } - assignImmutableTypes(mth); try { - for (Function resolver : resolvers) { - if (resolver.apply(mth) && checkTypes(mth)) { - return; - } - } + assignImmutableTypes(mth); + initTypeBounds(mth); + runTypePropagation(mth); } catch (Exception e) { - mth.addError("Type inference failed with exception", e); - } - } - - /** - * Check if all types resolved - */ - private static boolean checkTypes(MethodNode mth) { - for (SSAVar var : mth.getSVars()) { - ArgType type = var.getTypeInfo().getType(); - if (!type.isTypeKnown()) { - return false; - } + mth.addError("Type inference failed", e); } - return true; } /** * Collect initial type bounds from assign and usages */ - private boolean initTypeBounds(MethodNode mth) { + void initTypeBounds(MethodNode mth) { List ssaVars = mth.getSVars(); ssaVars.forEach(this::attachBounds); ssaVars.forEach(this::mergePhiBounds); if (Consts.DEBUG_TYPE_INFERENCE) { - ssaVars.forEach(ssaVar -> LOG.debug("Type bounds for {}: {}", ssaVar.toShortString(), ssaVar.getTypeInfo().getBounds())); + ssaVars.stream().sorted() + .forEach(ssaVar -> LOG.debug("Type bounds for {}: {}", ssaVar.toShortString(), ssaVar.getTypeInfo().getBounds())); } - return false; } /** * Guess type from usage and try to set it to current variable * and all connected instructions with {@link TypeUpdate#apply(MethodNode, SSAVar, ArgType)} */ - private boolean runTypePropagation(MethodNode mth) { + boolean runTypePropagation(MethodNode mth) { List ssaVars = mth.getSVars(); ssaVars.forEach(var -> setImmutableType(mth, var)); ssaVars.forEach(var -> setBestType(mth, var)); return true; } - private boolean runMultiVariableSearch(MethodNode mth) { - try { - TypeSearch typeSearch = new TypeSearch(mth); - if (!typeSearch.run()) { - mth.addWarnComment("Multi-variable type inference failed"); - } - for (SSAVar var : mth.getSVars()) { - if (!var.getTypeInfo().getType().isTypeKnown()) { - return false; - } - } - return true; - } catch (Exception e) { - mth.addWarnComment("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e)); - return false; - } - } - private void setImmutableType(MethodNode mth, SSAVar ssaVar) { try { ArgType immutableType = ssaVar.getImmutableType(); @@ -186,18 +118,17 @@ private void setImmutableType(MethodNode mth, SSAVar ssaVar) { } } - private boolean setBestType(MethodNode mth, SSAVar ssaVar) { + private void setBestType(MethodNode mth, SSAVar ssaVar) { try { - return calculateFromBounds(mth, ssaVar); + calculateFromBounds(mth, ssaVar); } catch (JadxOverflowException e) { throw e; } catch (Exception e) { mth.addWarnComment("Failed to calculate best type for var: " + ssaVar, e); - return false; } } - private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) { + private void calculateFromBounds(MethodNode mth, SSAVar ssaVar) { TypeInfo typeInfo = ssaVar.getTypeInfo(); Set bounds = typeInfo.getBounds(); Optional bestTypeOpt = selectBestTypeFromBounds(bounds); @@ -208,21 +139,17 @@ private boolean calculateFromBounds(MethodNode mth, SSAVar ssaVar) { LOG.warn(" {}", bound); } } - return false; + return; } ArgType candidateType = bestTypeOpt.get(); TypeUpdateResult result = typeUpdate.apply(mth, ssaVar, candidateType); - if (result == TypeUpdateResult.REJECT) { - if (Consts.DEBUG_TYPE_INFERENCE) { - if (ssaVar.getTypeInfo().getType().equals(candidateType)) { - LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); - } else if (candidateType.isTypeKnown()) { - LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); - } + if (Consts.DEBUG_TYPE_INFERENCE && result == TypeUpdateResult.REJECT) { + if (ssaVar.getTypeInfo().getType().equals(candidateType)) { + LOG.info("Same type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); + } else if (candidateType.isTypeKnown()) { + LOG.debug("Type rejected: {} -> {}, bounds: {}", ssaVar, candidateType, bounds); } - return false; } - return result == TypeUpdateResult.CHANGED; } private Optional selectBestTypeFromBounds(Set bounds) { @@ -310,7 +237,11 @@ private void addAssignBound(TypeInfo typeInfo, RegisterArg assign) { break; case CHECK_CAST: - addBound(typeInfo, new TypeBoundCheckCastAssign(root, (IndexInsnNode) insn)); + if (insn.contains(AFlag.SOFT_CAST)) { + // ignore bound, will run checks on update + } else { + addBound(typeInfo, new TypeBoundCheckCastAssign(root, (IndexInsnNode) insn)); + } break; default: @@ -396,7 +327,7 @@ private ITypeBound makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) return new TypeBoundInvokeUse(root, invoke, regArg, argType); } - // for override methods use origin declared class as type + // for override methods use origin declared class as a type if (methodDetails instanceof MethodNode) { MethodNode callMth = (MethodNode) methodDetails; ClassInfo declCls = methodUtils.getMethodOriginDeclClass(callMth); @@ -405,621 +336,7 @@ private ITypeBound makeInvokeUseBound(RegisterArg regArg, BaseInvokeNode invoke) return null; } - private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) { - List types = makePossibleTypesList(type, var); - if (types.isEmpty()) { - return false; - } - for (ArgType candidateType : types) { - TypeUpdateResult result = typeUpdate.apply(mth, var, candidateType); - if (result == TypeUpdateResult.CHANGED) { - return true; - } - } - return false; - } - - private List makePossibleTypesList(ArgType type, @Nullable SSAVar var) { - if (type.isArray()) { - List list = new ArrayList<>(); - for (ArgType arrElemType : makePossibleTypesList(type.getArrayElement(), null)) { - list.add(ArgType.array(arrElemType)); - } - return list; - } - if (var != null) { - for (ITypeBound b : var.getTypeInfo().getBounds()) { - ArgType boundType = b.getType(); - if (boundType.isObject() || boundType.isArray()) { - // don't add primitive types - return Collections.emptyList(); - } - } - } - List list = new ArrayList<>(); - for (PrimitiveType possibleType : type.getPossibleTypes()) { - if (possibleType == PrimitiveType.VOID) { - continue; - } - list.add(ArgType.convertFromPrimitiveType(possibleType)); - } - return list; - } - - private boolean tryDeduceTypes(MethodNode mth) { - boolean fixed = false; - for (SSAVar ssaVar : mth.getSVars()) { - if (deduceType(mth, ssaVar)) { - fixed = true; - } - } - return fixed; - } - - @SuppressWarnings("RedundantIfStatement") - private boolean deduceType(MethodNode mth, SSAVar var) { - if (var.isTypeImmutable()) { - return false; - } - ArgType type = var.getTypeInfo().getType(); - if (type.isTypeKnown()) { - return false; - } - // try best type from bounds again - if (setBestType(mth, var)) { - return true; - } - // try all possible types (useful for primitives) - if (tryPossibleTypes(mth, var, type)) { - return true; - } - // for objects try super types - if (tryWiderObjects(mth, var)) { - return true; - } - return false; - } - - private boolean tryRemoveGenerics(MethodNode mth) { - boolean resolved = true; - for (SSAVar var : mth.getSVars()) { - ArgType type = var.getTypeInfo().getType(); - if (!type.isTypeKnown() - && !var.isTypeImmutable() - && !tryRawType(mth, var)) { - resolved = false; - } - } - return resolved; - } - - private boolean tryRawType(MethodNode mth, SSAVar var) { - Set objTypes = new LinkedHashSet<>(); - for (ITypeBound bound : var.getTypeInfo().getBounds()) { - ArgType boundType = bound.getType(); - if (boundType.isTypeKnown() && boundType.isObject()) { - objTypes.add(boundType); - } - } - if (objTypes.isEmpty()) { - return false; - } - for (ArgType objType : objTypes) { - if (checkRawType(mth, var, objType)) { - mth.addDebugComment("Type inference failed for " + var.toShortString() + "." - + " Raw type applied. Possible types: " + Utils.listToString(objTypes)); - return true; - } - } - return false; - } - - private boolean checkRawType(MethodNode mth, SSAVar var, ArgType objType) { - if (objType.isObject() && objType.containsGeneric()) { - ArgType rawType = objType.isGenericType() ? ArgType.OBJECT : ArgType.object(objType.getObject()); - TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, rawType); - return result == TypeUpdateResult.CHANGED; - } - return false; - } - - /** - * Fix check casts to type var extend type: - *
- * {@code T var = (Comparable) obj; => T var = (T) obj; } - */ - private boolean tryRestoreTypeVarCasts(MethodNode mth) { - int changed = 0; - List mthSVars = mth.getSVars(); - for (SSAVar var : mthSVars) { - changed += restoreTypeVarCasts(var); - } - if (changed == 0) { - return false; - } - if (Consts.DEBUG_TYPE_INFERENCE) { - mth.addDebugComment("Restore " + changed + " type vars casts"); - } - initTypeBounds(mth); - return runTypePropagation(mth); - } - - private int restoreTypeVarCasts(SSAVar var) { - TypeInfo typeInfo = var.getTypeInfo(); - Set bounds = typeInfo.getBounds(); - if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) { - return 0; - } - List casts = ListUtils.filter(bounds, TypeBoundCheckCastAssign.class::isInstance); - if (casts.isEmpty()) { - return 0; - } - ArgType bestType = selectBestTypeFromBounds(bounds).orElse(ArgType.UNKNOWN); - if (!bestType.isGenericType()) { - return 0; - } - List extendTypes = bestType.getExtendTypes(); - if (extendTypes.size() != 1) { - return 0; - } - int fixed = 0; - ArgType extendType = extendTypes.get(0); - for (ITypeBound bound : casts) { - TypeBoundCheckCastAssign cast = (TypeBoundCheckCastAssign) bound; - ArgType castType = cast.getType(); - TypeCompareEnum result = typeUpdate.getTypeCompare().compareTypes(extendType, castType); - if (result.isEqual() || result == TypeCompareEnum.NARROW_BY_GENERIC) { - cast.getInsn().updateIndex(bestType); - fixed++; - } - } - return fixed; - } - - @SuppressWarnings({ "ForLoopReplaceableByWhile", "ForLoopReplaceableByForEach" }) - private boolean tryInsertCasts(MethodNode mth) { - int added = 0; - List mthSVars = mth.getSVars(); - int varsCount = mthSVars.size(); - for (int i = 0; i < varsCount; i++) { - SSAVar var = mthSVars.get(i); - ArgType type = var.getTypeInfo().getType(); - if (!type.isTypeKnown() && !var.isTypeImmutable()) { - added += tryInsertVarCast(mth, var); - } - } - if (added != 0) { - if (Consts.DEBUG_TYPE_INFERENCE) { - mth.addDebugComment("Additional " + added + " cast instructions added to help type inference"); - } - InitCodeVariables.rerun(mth); - initTypeBounds(mth); - return runTypePropagation(mth); - } - return false; - } - - private int tryInsertVarCast(MethodNode mth, SSAVar var) { - for (ITypeBound bound : var.getTypeInfo().getBounds()) { - ArgType boundType = bound.getType(); - if (boundType.isTypeKnown() - && !boundType.equals(var.getTypeInfo().getType()) - && boundType.containsTypeVariable() - && !root.getTypeUtils().containsUnknownTypeVar(mth, boundType)) { - if (insertAssignCast(mth, var, boundType)) { - return 1; - } - return insertUseCasts(mth, var); - } - } - return 0; - } - - private int insertUseCasts(MethodNode mth, SSAVar var) { - List useList = var.getUseList(); - if (useList.isEmpty()) { - return 0; - } - int useCasts = 0; - for (RegisterArg useReg : new ArrayList<>(useList)) { - if (insertSoftUseCast(mth, useReg)) { - useCasts++; - } - } - return useCasts; - } - - private boolean insertAssignCast(MethodNode mth, SSAVar var, ArgType castType) { - RegisterArg assignArg = var.getAssign(); - InsnNode assignInsn = assignArg.getParentInsn(); - if (assignInsn == null || assignInsn.getType() == InsnType.PHI) { - return false; - } - BlockNode assignBlock = BlockUtils.getBlockByInsn(mth, assignInsn); - if (assignBlock == null) { - return false; - } - assignInsn.setResult(assignArg.duplicateWithNewSSAVar(mth)); - IndexInsnNode castInsn = makeSoftCastInsn(assignArg.duplicate(), assignInsn.getResult().duplicate(), castType); - return BlockUtils.insertAfterInsn(assignBlock, assignInsn, castInsn); - } - - private boolean insertSoftUseCast(MethodNode mth, RegisterArg useArg) { - InsnNode useInsn = useArg.getParentInsn(); - if (useInsn == null || useInsn.getType() == InsnType.PHI) { - return false; - } - if (useInsn.getType() == InsnType.IF && useInsn.getArg(1).isZeroLiteral()) { - // cast not needed if compare with null - return false; - } - BlockNode useBlock = BlockUtils.getBlockByInsn(mth, useInsn); - if (useBlock == null) { - return false; - } - IndexInsnNode castInsn = makeSoftCastInsn( - useArg.duplicateWithNewSSAVar(mth), - useArg.duplicate(), - useArg.getInitType()); - useInsn.replaceArg(useArg, castInsn.getResult().duplicate()); - return BlockUtils.insertBeforeInsn(useBlock, useInsn, castInsn); - } - - @NotNull - private IndexInsnNode makeSoftCastInsn(RegisterArg result, RegisterArg arg, ArgType castType) { - IndexInsnNode castInsn = new IndexInsnNode(InsnType.CHECK_CAST, castType, 1); - castInsn.setResult(result); - castInsn.addArg(arg); - castInsn.add(AFlag.SOFT_CAST); - castInsn.add(AFlag.SYNTHETIC); - return castInsn; - } - - private boolean trySplitConstInsns(MethodNode mth) { - boolean constSplit = false; - for (SSAVar var : new ArrayList<>(mth.getSVars())) { - if (checkAndSplitConstInsn(mth, var)) { - constSplit = true; - } - } - if (!constSplit) { - return false; - } - InitCodeVariables.rerun(mth); - initTypeBounds(mth); - return runTypePropagation(mth); - } - - private boolean checkAndSplitConstInsn(MethodNode mth, SSAVar var) { - ArgType type = var.getTypeInfo().getType(); - if (type.isTypeKnown() || var.isTypeImmutable()) { - return false; - } - if (var.getUsedInPhi().size() < 2) { - return false; - } - InsnNode assignInsn = var.getAssign().getAssignInsn(); - InsnNode constInsn = InsnUtils.checkInsnType(assignInsn, InsnType.CONST); - if (constInsn == null) { - return false; - } - BlockNode blockNode = BlockUtils.getBlockByInsn(mth, constInsn); - if (blockNode == null) { - return false; - } - // for every PHI make separate CONST insn - boolean first = true; - for (PhiInsn phiInsn : var.getUsedInPhi()) { - if (first) { - first = false; - continue; - } - InsnNode copyInsn = constInsn.copyWithNewSsaVar(mth); - copyInsn.add(AFlag.SYNTHETIC); - BlockUtils.insertAfterInsn(blockNode, constInsn, copyInsn); - - RegisterArg phiArg = phiInsn.getArgBySsaVar(var); - phiInsn.replaceArg(phiArg, copyInsn.getResult().duplicate()); - } - return true; - } - - private boolean tryInsertAdditionalMove(MethodNode mth) { - int insnsAdded = 0; - for (BlockNode block : mth.getBasicBlocks()) { - PhiListAttr phiListAttr = block.get(AType.PHI_LIST); - if (phiListAttr != null) { - for (PhiInsn phiInsn : phiListAttr.getList()) { - insnsAdded += tryInsertAdditionalInsn(mth, phiInsn); - } - } - } - if (insnsAdded == 0) { - return false; - } - if (Consts.DEBUG_TYPE_INFERENCE) { - mth.addDebugComment("Additional " + insnsAdded + " move instructions added to help type inference"); - } - InitCodeVariables.rerun(mth); - initTypeBounds(mth); - if (runTypePropagation(mth) && checkTypes(mth)) { - return true; - } - return tryDeduceTypes(mth); - } - - /** - * Add MOVE instruction before PHI in bound blocks to make 'soft' type link. - * This allows to use different types in blocks merged by PHI. - */ - private int tryInsertAdditionalInsn(MethodNode mth, PhiInsn phiInsn) { - ArgType phiType = getCommonTypeForPhiArgs(phiInsn); - if (phiType != null && phiType.isTypeKnown()) { - // all args have same known type => nothing to do here - return 0; - } - // check if instructions can be inserted - if (insertMovesForPhi(mth, phiInsn, false) == 0) { - return 0; - } - // check passed => apply - return insertMovesForPhi(mth, phiInsn, true); - } - - @Nullable - private ArgType getCommonTypeForPhiArgs(PhiInsn phiInsn) { - ArgType phiArgType = null; - for (InsnArg arg : phiInsn.getArguments()) { - ArgType type = arg.getType(); - if (phiArgType == null) { - phiArgType = type; - } else if (!phiArgType.equals(type)) { - return null; - } - } - return phiArgType; - } - - private int insertMovesForPhi(MethodNode mth, PhiInsn phiInsn, boolean apply) { - int argsCount = phiInsn.getArgsCount(); - int count = 0; - for (int argIndex = 0; argIndex < argsCount; argIndex++) { - RegisterArg reg = phiInsn.getArg(argIndex); - BlockNode startBlock = phiInsn.getBlockByArgIndex(argIndex); - BlockNode blockNode = checkBlockForInsnInsert(startBlock); - if (blockNode == null) { - mth.addDebugComment("Failed to insert an additional move for type inference into block " + startBlock); - return 0; - } - boolean add = true; - SSAVar var = reg.getSVar(); - InsnNode assignInsn = var.getAssign().getAssignInsn(); - if (assignInsn != null) { - InsnType assignType = assignInsn.getType(); - if (assignType == InsnType.CONST - || (assignType == InsnType.MOVE && var.getUseCount() == 1)) { - add = false; - } - } - if (add) { - count++; - if (apply) { - insertMove(mth, blockNode, phiInsn, reg); - } - } - } - return count; - } - - private void insertMove(MethodNode mth, BlockNode blockNode, PhiInsn phiInsn, RegisterArg reg) { - SSAVar var = reg.getSVar(); - int regNum = reg.getRegNum(); - RegisterArg resultArg = reg.duplicate(regNum, null); - SSAVar newSsaVar = mth.makeNewSVar(resultArg); - RegisterArg arg = reg.duplicate(regNum, var); - - InsnNode moveInsn = new InsnNode(InsnType.MOVE, 1); - moveInsn.setResult(resultArg); - moveInsn.addArg(arg); - moveInsn.add(AFlag.SYNTHETIC); - blockNode.getInstructions().add(moveInsn); - - phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar)); - } - - @Nullable - private BlockNode checkBlockForInsnInsert(BlockNode blockNode) { - if (blockNode.isSynthetic()) { - return null; - } - InsnNode lastInsn = BlockUtils.getLastInsn(blockNode); - if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) { - // can't insert move in a block with 'separate' instruction => try previous block by simple path - List preds = blockNode.getPredecessors(); - if (preds.size() == 1) { - return checkBlockForInsnInsert(preds.get(0)); - } - return null; - } - return blockNode; - } - - private boolean tryWiderObjects(MethodNode mth, SSAVar var) { - Set objTypes = new LinkedHashSet<>(); - for (ITypeBound bound : var.getTypeInfo().getBounds()) { - ArgType boundType = bound.getType(); - if (boundType.isTypeKnown() && boundType.isObject()) { - objTypes.add(boundType); - } - } - if (objTypes.isEmpty()) { - return false; - } - ClspGraph clsp = mth.root().getClsp(); - for (ArgType objType : objTypes) { - for (String ancestor : clsp.getSuperTypes(objType.getObject())) { - ArgType ancestorType = ArgType.object(ancestor); - TypeUpdateResult result = typeUpdate.applyWithWiderAllow(mth, var, ancestorType); - if (result == TypeUpdateResult.CHANGED) { - return true; - } - } - } - return false; - } - - @SuppressWarnings("ForLoopReplaceableByForEach") - private boolean tryToFixIncompatiblePrimitives(MethodNode mth) { - boolean fixed = false; - List ssaVars = mth.getSVars(); - int ssaVarsCount = ssaVars.size(); - // new vars will be added at list end if fix is applied (can't use for-each loop) - for (int i = 0; i < ssaVarsCount; i++) { - if (processIncompatiblePrimitives(mth, ssaVars.get(i))) { - fixed = true; - } - } - if (!fixed) { - return false; - } - InitCodeVariables.rerun(mth); - initTypeBounds(mth); - return runTypePropagation(mth); - } - - private boolean processIncompatiblePrimitives(MethodNode mth, SSAVar var) { - TypeInfo typeInfo = var.getTypeInfo(); - if (typeInfo.getType().isTypeKnown()) { - return false; - } - boolean assigned = false; - for (ITypeBound bound : typeInfo.getBounds()) { - ArgType boundType = bound.getType(); - switch (bound.getBound()) { - case ASSIGN: - if (!boundType.contains(PrimitiveType.BOOLEAN)) { - return false; - } - assigned = true; - break; - case USE: - if (!boundType.canBeAnyNumber()) { - return false; - } - break; - } - } - if (!assigned) { - return false; - } - - boolean fixed = false; - for (RegisterArg arg : new ArrayList<>(var.getUseList())) { - if (fixBooleanUsage(mth, arg)) { - fixed = true; - } - } - return fixed; - } - - private boolean fixBooleanUsage(MethodNode mth, RegisterArg boundArg) { - ArgType boundType = boundArg.getInitType(); - if (boundType == ArgType.BOOLEAN - || (boundType.isTypeKnown() && !boundType.isPrimitive())) { - return false; - } - InsnNode insn = boundArg.getParentInsn(); - if (insn == null || insn.getType() == InsnType.IF) { - return false; - } - BlockNode blockNode = BlockUtils.getBlockByInsn(mth, insn); - if (blockNode == null) { - return false; - } - List insnList = blockNode.getInstructions(); - int insnIndex = InsnList.getIndex(insnList, insn); - if (insnIndex == -1) { - return false; - } - InsnType insnType = insn.getType(); - if (insnType == InsnType.CAST) { - // replace cast - ArgType type = (ArgType) ((IndexInsnNode) insn).getIndex(); - TernaryInsn convertInsn = prepareBooleanConvertInsn(insn.getResult(), boundArg, type); - BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn); - return true; - } - if (insnType == InsnType.ARITH) { - ArithNode arithInsn = (ArithNode) insn; - if (arithInsn.getOp() == ArithOp.XOR && arithInsn.getArgsCount() == 2) { - // replace (boolean ^ 1) with (!boolean) - InsnArg secondArg = arithInsn.getArg(1); - if (secondArg.isLiteral() && ((LiteralArg) secondArg).getLiteral() == 1) { - InsnNode convertInsn = notBooleanToInt(arithInsn, boundArg); - BlockUtils.replaceInsn(mth, blockNode, insnIndex, convertInsn); - return true; - } - } - } - - // insert before insn - RegisterArg resultArg = boundArg.duplicateWithNewSSAVar(mth); - TernaryInsn convertInsn = prepareBooleanConvertInsn(resultArg, boundArg, boundType); - insnList.add(insnIndex, convertInsn); - insn.replaceArg(boundArg, convertInsn.getResult().duplicate()); - return true; - } - - private InsnNode notBooleanToInt(ArithNode insn, RegisterArg boundArg) { - InsnNode notInsn = new InsnNode(InsnType.NOT, 1); - notInsn.addArg(boundArg.duplicate()); - notInsn.add(AFlag.SYNTHETIC); - - InsnArg notArg = InsnArg.wrapArg(notInsn); - notArg.setType(ArgType.BOOLEAN); - TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(insn.getResult(), notArg, ArgType.INT); - convertInsn.add(AFlag.SYNTHETIC); - return convertInsn; - } - - private TernaryInsn prepareBooleanConvertInsn(RegisterArg resultArg, RegisterArg boundArg, ArgType useType) { - RegisterArg useArg = boundArg.getSVar().getAssign().duplicate(); - TernaryInsn convertInsn = ModVisitor.makeBooleanConvertInsn(resultArg, useArg, useType); - convertInsn.add(AFlag.SYNTHETIC); - return convertInsn; - } - - private boolean tryToForceImmutableTypes(MethodNode mth) { - boolean fixed = false; - for (SSAVar ssaVar : mth.getSVars()) { - ArgType type = ssaVar.getTypeInfo().getType(); - if (!type.isTypeKnown() && ssaVar.isTypeImmutable()) { - if (forceImmutableType(ssaVar)) { - fixed = true; - } - } - } - if (!fixed) { - return false; - } - return runTypePropagation(mth); - } - - private boolean forceImmutableType(SSAVar ssaVar) { - for (RegisterArg useArg : ssaVar.getUseList()) { - InsnNode parentInsn = useArg.getParentInsn(); - if (parentInsn != null) { - InsnType insnType = parentInsn.getType(); - if (insnType == InsnType.AGET || insnType == InsnType.APUT) { - ssaVar.setType(ssaVar.getImmutableType()); - return true; - } - } - } - return false; - } - - private static void assignImmutableTypes(MethodNode mth) { + private void assignImmutableTypes(MethodNode mth) { for (SSAVar ssaVar : mth.getSVars()) { ArgType immutableType = getSsaImmutableType(ssaVar); if (immutableType != null) { @@ -1040,4 +357,9 @@ private static ArgType getSsaImmutableType(SSAVar ssaVar) { } return null; } + + @Override + public String getName() { + return "TypeInferenceVisitor"; + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java index d2576e97bd8..39bdc82145a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearch.java @@ -67,10 +67,9 @@ public boolean run() { if (vars.isEmpty()) { searchSuccess = true; } else { - search(vars); - searchSuccess = fullCheck(vars); + searchSuccess = search(vars) && fullCheck(vars); if (Consts.DEBUG_TYPE_INFERENCE && !searchSuccess) { - LOG.debug("Multi-variable search failed in {}", mth); + LOG.debug("Multi-variable search failed"); } } if (searchSuccess) { @@ -110,7 +109,7 @@ private boolean applyResolvedVars() { private boolean search(List vars) { int len = vars.size(); if (Consts.DEBUG_TYPE_INFERENCE) { - LOG.debug("Run search for {} vars: ", len); + LOG.debug("Run multi-variable search for {} vars: ", len); StringBuilder sb = new StringBuilder(); long count = 1; for (TypeSearchVarInfo var : vars) { @@ -120,7 +119,7 @@ private boolean search(List vars) { count *= size; } sb.append(" = ").append(count); - LOG.debug(" > max iterations count = {}", sb); + LOG.debug(" max iterations count = {}", sb); } // prepare vars 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 6216d67fdee..9ebf6cc3f7b 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 @@ -8,12 +8,13 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.Consts; +import jadx.core.clsp.ClspClass; +import jadx.core.dex.attributes.AFlag; import jadx.core.dex.instructions.ArithNode; import jadx.core.dex.instructions.BaseInvokeNode; import jadx.core.dex.instructions.IndexInsnNode; @@ -83,14 +84,13 @@ private TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateT if (result == REJECT) { return result; } - List updates = updateInfo.getUpdates(); - if (updates.isEmpty()) { + if (updateInfo.isEmpty()) { return SAME; } if (Consts.DEBUG_TYPE_INFERENCE) { - LOG.debug("Applying type {} to {}", ssaVar.toShortString(), candidateType); - updates.forEach(updateEntry -> LOG.debug(" {} -> {} in {}", - updateEntry.getType(), updateEntry.getArg(), updateEntry.getArg().getParentInsn())); + LOG.debug("Applying type {} to {}:", candidateType, ssaVar.toShortString()); + updateInfo.getSortedUpdates().forEach(upd -> LOG.debug(" {} -> {} in {}", + upd.getType(), upd.getArg().toShortString(), upd.getArg().getParentInsn())); } updateInfo.applyUpdates(); return CHANGED; @@ -100,6 +100,21 @@ private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg ar if (candidateType == null) { throw new JadxRuntimeException("Null type update for arg: " + arg); } + if (updateInfo.isProcessed(arg)) { + return CHANGED; + } + TypeUpdateResult res = verifyType(updateInfo, arg, candidateType); + if (res != null) { + return res; + } + if (arg instanceof RegisterArg) { + RegisterArg reg = (RegisterArg) arg; + return updateTypeForSsaVar(updateInfo, reg.getSVar(), candidateType); + } + return requestUpdate(updateInfo, arg, candidateType); + } + + private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) { ArgType currentType = arg.getType(); if (Objects.equals(currentType, candidateType)) { if (!updateInfo.getFlags().isIgnoreSame()) { @@ -114,6 +129,12 @@ private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg ar } TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType); + if (compareResult.isConflict()) { + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.debug("Type rejected for {}: candidate={} in conflict with current={}", arg, candidateType, currentType); + } + return REJECT; + } if (compareResult == TypeCompareEnum.UNKNOWN && updateInfo.getFlags().isIgnoreUnknown()) { return REJECT; } @@ -144,11 +165,7 @@ private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg ar } } } - if (arg instanceof RegisterArg) { - RegisterArg reg = (RegisterArg) arg; - return updateTypeForSsaVar(updateInfo, reg.getSVar(), candidateType); - } - return requestUpdate(updateInfo, arg, candidateType); + return null; } private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) { @@ -163,26 +180,26 @@ private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar s if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) { return REJECT; } - return requestUpdateForSsaVar(updateInfo, ssaVar, candidateType); - } - - @NotNull - private TypeUpdateResult requestUpdateForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) { - boolean allSame = true; TypeUpdateResult result = requestUpdate(updateInfo, ssaVar.getAssign(), candidateType); - if (result == REJECT) { - return result; - } - List useList = ssaVar.getUseList(); - for (RegisterArg arg : useList) { - TypeUpdateResult useResult = requestUpdate(updateInfo, arg, candidateType); - if (useResult == REJECT) { - return REJECT; - } - if (useResult != SAME) { - allSame = false; + boolean allSame = result == SAME; + if (result != REJECT) { + List useList = ssaVar.getUseList(); + for (RegisterArg arg : useList) { + result = requestUpdate(updateInfo, arg, candidateType); + if (result == REJECT) { + break; + } + if (result != SAME) { + allSame = false; + } } } + if (result == REJECT) { + // rollback update for all registers in current SSA var + updateInfo.rollbackUpdate(ssaVar.getAssign()); + ssaVar.getUseList().forEach(updateInfo::rollbackUpdate); + return REJECT; + } return allSame ? SAME : CHANGED; } @@ -191,7 +208,6 @@ private TypeUpdateResult requestUpdate(TypeUpdateInfo updateInfo, InsnArg arg, A return CHANGED; } updateInfo.requestUpdate(arg, candidateType); - updateInfo.checkUpdatesCount(); try { TypeUpdateResult result = runListeners(updateInfo, arg, candidateType); if (result == REJECT) { @@ -199,7 +215,8 @@ private TypeUpdateResult requestUpdate(TypeUpdateInfo updateInfo, InsnArg arg, A } return result; } catch (StackOverflowError | BootstrapMethodError error) { - throw new JadxOverflowException("Type update terminated with stack overflow, arg: " + arg); + throw new JadxOverflowException("Type update terminated with stack overflow, arg: " + arg + + ", method size: " + updateInfo.getMth().getInsnsCount()); } } @@ -235,7 +252,7 @@ private boolean inBounds(TypeUpdateInfo updateInfo, SSAVar ssaVar, Set knownTypeVars, @Nullable ArgType type) { private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult(); + if (updateInfo.hasUpdateWithType(changeArg, candidateType)) { + return CHANGED; + } return updateTypeChecked(updateInfo, changeArg, candidateType); } @@ -500,15 +521,43 @@ private TypeUpdateResult checkCastListener(TypeUpdateInfo updateInfo, InsnNode i TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); return result == REJECT ? SAME : result; } - if (candidateType.containsGeneric()) { - ArgType castType = (ArgType) checkCast.getIndex(); - TypeCompareEnum compResult = comparator.compareTypes(candidateType, castType); - if (compResult == TypeCompareEnum.NARROW_BY_GENERIC) { - // propagate generic type to result - return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType); + ArgType castType = (ArgType) checkCast.getIndex(); + TypeCompareEnum res = comparator.compareTypes(candidateType, castType); + if (res == TypeCompareEnum.CONFLICT) { + // allow casting one interface to another + if (!isInterfaces(candidateType, castType)) { + return REJECT; } } - return SAME; + if (res == TypeCompareEnum.CONFLICT_BY_GENERIC) { + if (!insn.contains(AFlag.SOFT_CAST)) { + return REJECT; + } + } + if (res == TypeCompareEnum.NARROW_BY_GENERIC && candidateType.containsGeneric()) { + // propagate generic type to result + return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType); + } + ArgType currentType = checkCast.getArg(0).getType(); + return candidateType.equals(currentType) ? SAME : CHANGED; + } + + private boolean isInterfaces(ArgType firstType, ArgType secondType) { + if (!firstType.isObject() || !secondType.isObject()) { + return false; + } + ClspClass firstCls = root.getClsp().getClsDetails(firstType); + ClspClass secondCls = root.getClsp().getClsDetails(secondType); + if (firstCls != null && !firstCls.isInterface()) { + return false; + } + if (secondCls != null && !secondCls.isInterface()) { + return false; + } + if (firstCls == null || secondCls == null) { + return true; + } + return secondCls.isInterface() && firstCls.isInterface(); } private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateEntry.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateEntry.java index 76448f984ec..d6bd5bd40ac 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateEntry.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateEntry.java @@ -1,17 +1,25 @@ package jadx.core.dex.visitors.typeinference; +import org.jetbrains.annotations.NotNull; + import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; -public final class TypeUpdateEntry { +public final class TypeUpdateEntry implements Comparable { + private final int seq; private final InsnArg arg; private final ArgType type; - public TypeUpdateEntry(InsnArg arg, ArgType type) { + public TypeUpdateEntry(int seq, InsnArg arg, ArgType type) { + this.seq = seq; this.arg = arg; this.type = type; } + public int getSeq() { + return seq; + } + public InsnArg getArg() { return arg; } @@ -20,8 +28,13 @@ public ArgType getType() { return type; } + @Override + public int compareTo(@NotNull TypeUpdateEntry other) { + return Integer.compare(this.seq, other.seq); + } + @Override public String toString() { - return "TypeUpdateEntry{" + arg + " -> " + type + '}'; + return type + " -> " + arg.toShortString() + " in " + arg.getParentInsn(); } } 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 0036bf54a2f..e0dfc172a5e 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,7 +1,5 @@ package jadx.core.dex.visitors.typeinference; -import org.jetbrains.annotations.NotNull; - public class TypeUpdateFlags { private static final int ALLOW_WIDER = 1; private static final int IGNORE_SAME = 2; @@ -14,7 +12,6 @@ public class TypeUpdateFlags { private final int flags; - @NotNull private static TypeUpdateFlags build(int flags) { return new TypeUpdateFlags(flags); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java index 72b676cf074..0d8fcb70cef 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java @@ -1,73 +1,84 @@ package jadx.core.dex.visitors.typeinference; -import java.util.ArrayList; +import java.util.IdentityHashMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.exceptions.JadxOverflowException; +import jadx.core.utils.exceptions.JadxRuntimeException; public class TypeUpdateInfo { private final MethodNode mth; private final TypeUpdateFlags flags; - private final List updates = new ArrayList<>(); + private final Map updateMap = new IdentityHashMap<>(); private final int updatesLimitCount; + private int updateSeq = 0; public TypeUpdateInfo(MethodNode mth, TypeUpdateFlags flags) { this.mth = mth; this.flags = flags; - this.updatesLimitCount = mth.getInsnsCount() * 5; // maximum registers count to update at once + this.updatesLimitCount = mth.getInsnsCount() * 10; } public void requestUpdate(InsnArg arg, ArgType changeType) { - updates.add(new TypeUpdateEntry(arg, changeType)); + TypeUpdateEntry prev = updateMap.put(arg, new TypeUpdateEntry(updateSeq++, arg, changeType)); + if (prev != null) { + throw new JadxRuntimeException("Unexpected type update override for arg: " + arg + + " types: prev=" + prev.getType() + ", new=" + changeType + + ", insn: " + arg.getParentInsn()); + } + if (updateSeq > updatesLimitCount) { + throw new JadxOverflowException("Type inference error: updates count limit reached"); + } } - public void applyUpdates() { - for (TypeUpdateEntry updateEntry : updates) { - InsnArg arg = updateEntry.getArg(); - arg.setType(updateEntry.getType()); + public void rollbackUpdate(InsnArg arg) { + TypeUpdateEntry removed = updateMap.remove(arg); + if (removed != null) { + int seq = removed.getSeq(); + updateMap.values().removeIf(upd -> upd.getSeq() > seq); } } + public void applyUpdates() { + updateMap.values().stream().sorted() + .forEach(upd -> upd.getArg().setType(upd.getType())); + } + public boolean isProcessed(InsnArg arg) { - if (updates.isEmpty()) { - return false; - } - for (TypeUpdateEntry entry : updates) { - if (entry.getArg() == arg) { - return true; - } + return updateMap.containsKey(arg); + } + + public boolean hasUpdateWithType(InsnArg arg, ArgType type) { + TypeUpdateEntry updateEntry = updateMap.get(arg); + if (updateEntry != null) { + return updateEntry.getType().equals(type); } return false; } public ArgType getType(InsnArg arg) { - for (TypeUpdateEntry update : updates) { - if (update.getArg() == arg) { - return update.getType(); - } + TypeUpdateEntry updateEntry = updateMap.get(arg); + if (updateEntry != null) { + return updateEntry.getType(); } return arg.getType(); } - public void rollbackUpdate(InsnArg arg) { - updates.removeIf(updateEntry -> updateEntry.getArg() == arg); - } - - public void checkUpdatesCount() { - if (updates.size() > updatesLimitCount) { - throw new JadxOverflowException("Type inference error: update tree size limit reached"); - } - } - public MethodNode getMth() { return mth; } - public List getUpdates() { - return updates; + public boolean isEmpty() { + return updateMap.isEmpty(); + } + + public List getSortedUpdates() { + return updateMap.values().stream().sorted().collect(Collectors.toList()); } public TypeUpdateFlags getFlags() { @@ -76,6 +87,6 @@ public TypeUpdateFlags getFlags() { @Override public String toString() { - return "TypeUpdateInfo{" + flags + ", updates=" + updates + '}'; + return "TypeUpdateInfo{" + flags + ' ' + getSortedUpdates() + '}'; } } diff --git a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java index 64da254c16e..0de6b4ced3b 100644 --- a/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/InsnUtils.java @@ -227,6 +227,14 @@ public static InsnNode getWrappedInsn(InsnArg arg) { return null; } + public static boolean isWrapped(InsnArg arg, InsnType insnType) { + if (arg != null && arg.isInsnWrap()) { + InsnNode wrapInsn = ((InsnWrapArg) arg).getWrapInsn(); + return wrapInsn.getType() == insnType; + } + return false; + } + public static boolean dontGenerateIfNotUsed(InsnNode insn) { RegisterArg resArg = insn.getResult(); if (resArg != null) { diff --git a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java index be28e60ff96..cd6d71f4955 100644 --- a/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java +++ b/jadx-core/src/test/java/jadx/core/dex/visitors/typeinference/TypeCompareTest.java @@ -139,6 +139,16 @@ public void compareWildCards() { check(collSuperWildcard, listWildcard, TypeCompareEnum.CONFLICT); } + @Test + public void compareGenericWildCards() { + // 'java.util.List' and 'java.util.List' + ArgType listCls = object("java.util.List"); + ArgType genericType = genericType("T"); + ArgType genericList = generic(listCls, genericType); + ArgType genericExtendedList = generic(listCls, wildcard(genericType, WildcardBound.EXTENDS)); + check(genericList, genericExtendedList, TypeCompareEnum.CONFLICT_BY_GENERIC); + } + @Test public void compareGenericTypes() { ArgType vType = genericType("V"); diff --git a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java index a6d3b01047b..1c168195c5c 100644 --- a/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java +++ b/jadx-core/src/test/java/jadx/tests/api/IntegrationTest.java @@ -149,10 +149,10 @@ public void init() { @AfterEach public void after() throws IOException { - FileUtils.clearTempRootDir(); close(jadxDecompiler); close(sourceCompiler); close(decompiledCompiler); + FileUtils.clearTempRootDir(); } private void close(Closeable closeable) throws IOException { diff --git a/jadx-core/src/test/java/jadx/tests/integration/others/TestCastOfNull.java b/jadx-core/src/test/java/jadx/tests/integration/others/TestCastOfNull.java index 2039f1e70a0..a9665ecb7e5 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/others/TestCastOfNull.java +++ b/jadx-core/src/test/java/jadx/tests/integration/others/TestCastOfNull.java @@ -4,12 +4,11 @@ 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 org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; +@SuppressWarnings("unused") public class TestCastOfNull extends IntegrationTest { public static class TestCls { @@ -32,11 +31,10 @@ public void m(List list) { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("m((long[]) null);")); - assertThat(code, containsOne("m((String) null);")); - assertThat(code, containsOne("m((List) null);")); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("m((long[]) null);") + .containsOne("m((String) null);") + .containsOne("m((List) null);"); } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestInterfacesCast.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestInterfacesCast.java new file mode 100644 index 00000000000..53cdf0f7162 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestInterfacesCast.java @@ -0,0 +1,28 @@ +package jadx.tests.integration.types; + +import java.io.Closeable; +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestInterfacesCast extends IntegrationTest { + + public static class TestCls { + + public Runnable test(Closeable obj) throws IOException { + return (Runnable) obj; + } + } + + @Test + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("return (Runnable) closeable;"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver16.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver16.java index 63500ab66cd..46fe8a00556 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver16.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver16.java @@ -1,20 +1,28 @@ package jadx.tests.integration.types; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + import org.junit.jupiter.api.Test; import jadx.tests.api.SmaliTest; import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; +import static java.util.Collections.emptyList; /** * Issue 1002 * Insertion of additional cast (at use place) needed for successful type inference */ public class TestTypeResolver16 extends SmaliTest { - // @formatter:off - /* - public final List test(List list, Set set, Function function) { - checkParameterIsNotNull(function, "distinctBy"); + + @SuppressWarnings("unchecked") + public static class TestCls { + + public final List test(List list, + Set set, Function function) { if (set != null) { List union = list != null ? union(list, set, function) : null; if (union != null) { @@ -23,11 +31,24 @@ public final List test(List list, Set set, F } return list != null ? (List) list : emptyList(); } - */ - // @formatter:on + + public static List union( + Collection collection, + Iterable iterable, + Function function) { + return null; + } + } @Test public void test() { + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("(List) list"); + } + + @Test + public void testSmali() { assertThat(getClassNodeFromSmali()) .code() .containsOne("(List) list"); diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver24.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver24.java new file mode 100644 index 00000000000..aa4a9e7475c --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver24.java @@ -0,0 +1,49 @@ +package jadx.tests.integration.types; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; +import jadx.tests.api.extensions.profiles.TestProfile; +import jadx.tests.api.extensions.profiles.TestWithProfiles; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestTypeResolver24 extends SmaliTest { + + @SuppressWarnings("DataFlowIssue") + public static class TestCls { + public void test() { + ((T1) null).foo1(); + ((T2) null).foo2(); + } + + static class T1 { + public void foo1() { + } + } + + static class T2 { + public void foo2() { + } + } + } + + @TestWithProfiles({ TestProfile.DX_J8, TestProfile.JAVA8 }) + public void test() { + noDebugInfo(); + assertThat(getClassNode(TestCls.class)) + .code() + .containsOne("((T1) null).foo1();") + .containsOne("((T2) null).foo2();"); + } + + @Test + public void testSmali() { + assertThat(searchCls(loadFromSmaliFiles(), "Test1")) + .code() + .containsOne("((T1) null).foo1();") + .containsOne("((T2) null).foo2();") + .doesNotContain("T1 ") + .doesNotContain("T2 "); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver25.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver25.java new file mode 100644 index 00000000000..acd07802e11 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver25.java @@ -0,0 +1,22 @@ +package jadx.tests.integration.types; + +import org.junit.jupiter.api.Test; + +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestTypeResolver25 extends SmaliTest { + + @Test + public void testSmali() { + // TODO: type inference error not yet resolved + // Check that no stack overflow in type inference for now + allowWarnInCode(); + disableCompilation(); + assertThat(getClassNodeFromSmali()) + .code() + .oneOf(c -> c.containsOne("t = obj;"), + c -> c.containsOne("t = (T) obj;")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver7.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver7.java index 0d35db41351..37ed44591fe 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver7.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver7.java @@ -2,11 +2,9 @@ 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 org.hamcrest.MatcherAssert.assertThat; +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestTypeResolver7 extends IntegrationTest { @@ -37,10 +35,10 @@ private void use(Runnable r) { @Test public void test() { - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsOne("use(b ? (Exception) getObj() : null);")); + assertThat(getClassNode(TestCls.class)) + .code() + .oneOf(c -> c.containsOne("use(b ? (Exception) getObj() : null);"), + c -> c.containsOne("use(b ? (Exception) getObj() : (Exception) null);")); } @Test diff --git a/jadx-core/src/test/resources/logback.xml b/jadx-core/src/test/resources/logback.xml index 3fcc38c08bf..23bf38dca48 100644 --- a/jadx-core/src/test/resources/logback.xml +++ b/jadx-core/src/test/resources/logback.xml @@ -2,7 +2,7 @@ - %d{HH:mm:ss} %-5level - %msg%n + %-5level - %msg%n diff --git a/jadx-core/src/test/smali/types/TestTypeResolver16.smali b/jadx-core/src/test/smali/types/TestTypeResolver16.smali index 00e34e5d25a..dbbd1804f18 100644 --- a/jadx-core/src/test/smali/types/TestTypeResolver16.smali +++ b/jadx-core/src/test/smali/types/TestTypeResolver16.smali @@ -5,41 +5,23 @@ .locals 1 .annotation system Ldalvik/annotation/Signature; value = { - "(", - "Ljava/util/List<", - "+TT;>;", - "Ljava/util/Set<", - "+TT;>;", - "Ljava/util/function/Function<", - "-TT;+TK;>;)", - "Ljava/util/List<", - "TT;>;" + "(", + "Ljava/util/List<", "+TT;>;", + "Ljava/util/Set<", "+TT;>;", + "Ljava/util/function/Function<", "-TT;+TK;>;)", + "Ljava/util/List<", "TT;>;" } .end annotation - const-string v0, "distinctBy" - - invoke-static {p3, v0}, Ltypes/TestTypeResolver16;->checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V - if-eqz p2, :cond_1 - if-eqz p1, :cond_0 .line 85 move-object v0, p1 - check-cast v0, Ljava/util/Collection; - check-cast p2, Ljava/lang/Iterable; - invoke-static {v0, p2, p3}, Ltypes/TestTypeResolver16;->union(Ljava/util/Collection;Ljava/lang/Iterable;Ljava/util/function/Function;)Ljava/util/List; - move-result-object p2 - goto :goto_0 :cond_0 @@ -47,17 +29,14 @@ :goto_0 if-eqz p2, :cond_1 - move-object p1, p2 :cond_1 if-eqz p1, :cond_2 - goto :goto_1 :cond_2 invoke-static {}, Ltypes/TestTypeResolver16;->emptyList()Ljava/util/List; - move-result-object p1 :goto_1 @@ -89,11 +68,6 @@ return-object v0 .end method -.method public static checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V - .locals 0 - return-void -.end method - .method public static final emptyList()Ljava/util/List; .locals 1 .annotation system Ldalvik/annotation/Signature; diff --git a/jadx-core/src/test/smali/types/TestTypeResolver24/T1.smali b/jadx-core/src/test/smali/types/TestTypeResolver24/T1.smali new file mode 100644 index 00000000000..93d84b12356 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver24/T1.smali @@ -0,0 +1,7 @@ +.class LT1; +.super Ljava/lang/Object; + +.method public foo1()V + .registers 1 + return-void +.end method diff --git a/jadx-core/src/test/smali/types/TestTypeResolver24/T2.smali b/jadx-core/src/test/smali/types/TestTypeResolver24/T2.smali new file mode 100644 index 00000000000..ae7e83f4d0f --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver24/T2.smali @@ -0,0 +1,7 @@ +.class LT2; +.super Ljava/lang/Object; + +.method public foo2()V + .registers 1 + return-void +.end method diff --git a/jadx-core/src/test/smali/types/TestTypeResolver24/Test1.smali b/jadx-core/src/test/smali/types/TestTypeResolver24/Test1.smali new file mode 100644 index 00000000000..95ce1cd2c84 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver24/Test1.smali @@ -0,0 +1,14 @@ +.class public LTest1; +.super Ljava/lang/Object; + +.method public test()V + .registers 3 + const/4 v0, 0x0 + move-object v1, v0 + check-cast v1, LT1; + invoke-virtual {v0}, LT1;->foo1()V + move-object v1, v0 + check-cast v1, LT2; + invoke-virtual {v0}, LT2;->foo2()V + return-void +.end method diff --git a/jadx-core/src/test/smali/types/TestTypeResolver25.smali b/jadx-core/src/test/smali/types/TestTypeResolver25.smali new file mode 100644 index 00000000000..c8cf2648436 --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver25.smali @@ -0,0 +1,109 @@ +.class public abstract Ltypes/TestTypeResolver25; +.super Ljava/lang/Object; + +.field public final a:Ljava/util/Map; + .annotation system Ldalvik/annotation/Signature; + value = { + "Ljava/util/Map<", + "Ljava/lang/String;", + "Ljava/lang/Object;", + ">;" + } + .end annotation +.end field + +.field public volatile b:Z + +.method public k(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; + .registers 6 + .annotation system Ldalvik/annotation/Signature; + value = { + "(", + "Ljava/lang/String;", + "TT;)TT;" + } + .end annotation + + .line 1 + iget-object v0, p0, Ltypes/TestTypeResolver25;->a:Ljava/util/Map; + + monitor-enter v0 + + .line 2 + :try_start_3 + iget-object v1, p0, Ltypes/TestTypeResolver25;->a:Ljava/util/Map; + + invoke-interface {v1, p1}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object; + + move-result-object v1 + + if-nez v1, :cond_10 + + .line 3 + iget-object v2, p0, Ltypes/TestTypeResolver25;->a:Ljava/util/Map; + + invoke-interface {v2, p1, p2}, Ljava/util/Map;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; + + .line 4 + :cond_10 + monitor-exit v0 + :try_end_11 + .catchall {:try_start_3 .. :try_end_11} :catchall_2c + + if-nez v1, :cond_14 + + goto :goto_15 + + :cond_14 + move-object p2, v1 + + .line 5 + :goto_15 + iget-boolean p1, p0, Ltypes/TestTypeResolver25;->b:Z + + if-eqz p1, :cond_2b + + .line 6 + instance-of p1, p2, Ljava/io/Closeable; + + if-eqz p1, :cond_2b + + .line 7 + :try_start_1d + move-object p1, p2 + + check-cast p1, Ljava/io/Closeable; + + invoke-interface {p1}, Ljava/io/Closeable;->close()V + :try_end_23 + .catch Ljava/io/IOException; {:try_start_1d .. :try_end_23} :catch_24 + + goto :goto_2b + + :catch_24 + move-exception p1 + + .line 8 + new-instance p2, Ljava/lang/RuntimeException; + + invoke-direct {p2, p1}, Ljava/lang/RuntimeException;->(Ljava/lang/Throwable;)V + + throw p2 + + :cond_2b + :goto_2b + return-object p2 + + :catchall_2c + move-exception p1 + + .line 9 + :try_start_2d + monitor-exit v0 + :try_end_2e + .catchall {:try_start_2d .. :try_end_2e} :catchall_2c + + throw p1 +.end method