diff --git a/jadx-core/src/main/java/jadx/core/codegen/NameGen.java b/jadx-core/src/main/java/jadx/core/codegen/NameGen.java index b666d3f04..f18ac2a87 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/NameGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/NameGen.java @@ -132,6 +132,9 @@ public class NameGen { if (!NameMapper.isValidAndPrintable(varName)) { varName = getFallbackName(var); } + if (Consts.DEBUG) { + varName += '_' + getFallbackName(var); + } return varName; } diff --git a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java index d19f78c53..660372430 100644 --- a/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java +++ b/jadx-core/src/main/java/jadx/core/dex/instructions/args/InsnWrapArg.java @@ -1,7 +1,11 @@ package jadx.core.dex.instructions.args; +import java.util.Objects; + import org.jetbrains.annotations.NotNull; +import jadx.core.dex.instructions.ConstStringNode; +import jadx.core.dex.instructions.InsnType; import jadx.core.dex.nodes.InsnNode; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -9,9 +13,12 @@ public final class InsnWrapArg extends InsnArg { private final InsnNode wrappedInsn; - public InsnWrapArg(@NotNull InsnNode insn) { + /** + * Use {@link InsnArg#wrapInsnIntoArg(InsnNode)} instead this constructor + */ + InsnWrapArg(@NotNull InsnNode insn) { RegisterArg result = insn.getResult(); - this.type = result != null ? result.getType() : ArgType.VOID; + this.type = result != null ? result.getType() : ArgType.UNKNOWN; this.wrappedInsn = insn; } @@ -29,7 +36,9 @@ public final class InsnWrapArg extends InsnArg { @Override public InsnArg duplicate() { - return copyCommonParams(new InsnWrapArg(wrappedInsn.copy())); + InsnWrapArg copy = new InsnWrapArg(wrappedInsn.copy()); + copy.setType(type); + return copyCommonParams(copy); } @Override @@ -67,6 +76,10 @@ public final class InsnWrapArg extends InsnArg { @Override public String toString() { + if (wrappedInsn.getType() == InsnType.CONST_STR + && Objects.equals(type, ArgType.STRING)) { + return "(\"" + ((ConstStringNode) wrappedInsn).getString() + "\")"; + } return "(wrap: " + type + "\n " + wrappedInsn + ')'; } } 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 b2ee73dc6..2f2fed6a0 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 @@ -175,6 +175,11 @@ public class SSAVar extends AttrNode { codeVar.addSsaVar(this); } + public void resetTypeAndCodeVar() { + this.typeInfo.reset(); + this.codeVar = null; + } + public boolean isCodeVarSet() { return codeVar != null; } 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 02df79678..366b489e8 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 @@ -87,11 +87,13 @@ public class ConstInlineVisitor extends AbstractVisitor { String s = ((ConstStringNode) insn).getString(); FieldNode f = mth.getParentClass().getConstField(s); if (f == null) { - constArg = InsnArg.wrapArg(insn.copy()); + InsnNode copy = insn.copy(); + copy.setResult(null); + constArg = InsnArg.wrapArg(copy); } else { InsnNode constGet = new IndexInsnNode(InsnType.SGET, f.getFieldInfo(), 0); - constGet.setResult(insn.getResult().duplicate()); constArg = InsnArg.wrapArg(constGet); + constArg.setType(ArgType.STRING); } } else { return; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java b/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java index 10a185e21..896e1b44e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/InitCodeVariables.java @@ -31,6 +31,13 @@ public class InitCodeVariables extends AbstractVisitor { initCodeVars(mth); } + public static void rerun(MethodNode mth) { + for (SSAVar sVar : mth.getSVars()) { + sVar.resetTypeAndCodeVar(); + } + initCodeVars(mth); + } + private static void initCodeVars(MethodNode mth) { for (RegisterArg mthArg : mth.getArguments(true)) { initCodeVar(mthArg.getSVar()); @@ -40,7 +47,7 @@ public class InitCodeVariables extends AbstractVisitor { } } - public static void initCodeVar(SSAVar ssaVar) { + private static void initCodeVar(SSAVar ssaVar) { if (ssaVar.isCodeVarSet()) { return; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java index d11385e9a..bb1a6c5a3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/SimplifyVisitor.java @@ -165,7 +165,7 @@ public class SimplifyVisitor extends AbstractVisitor { } } if (printable >= arr.length - printable) { - InsnWrapArg wa = new InsnWrapArg(new ConstStringNode(new String(arr))); + InsnArg wa = InsnArg.wrapArg(new ConstStringNode(new String(arr))); if (insn.getArgsCount() == 1) { insn.setArg(0, wa); } else { @@ -176,7 +176,7 @@ public class SimplifyVisitor extends AbstractVisitor { ArgType.array(ArgType.BYTE)); InvokeNode in = new InvokeNode(mi, InvokeType.VIRTUAL, 1); in.addArg(wa); - insn.setArg(0, new InsnWrapArg(in)); + insn.setArg(0, InsnArg.wrapArg(in)); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java index 56f8eb56e..a94edf586 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/blocksmaker/BlockSplitter.java @@ -37,7 +37,7 @@ public class BlockSplitter extends AbstractVisitor { InsnType.MONITOR_EXIT, InsnType.THROW); - public static boolean makeSeparate(InsnType insnType) { + public static boolean isSeparate(InsnType insnType) { return SEPARATE_INSNS.contains(insnType); } @@ -89,7 +89,7 @@ public class BlockSplitter extends AbstractVisitor { InsnType type = prevInsn.getType(); if (type == InsnType.GOTO || type == InsnType.THROW - || makeSeparate(type)) { + || isSeparate(type)) { if (type == InsnType.RETURN || type == InsnType.THROW) { mth.addExitBlock(curBlock); @@ -102,7 +102,7 @@ public class BlockSplitter extends AbstractVisitor { startNew = true; } else { startNew = isSplitByJump(prevInsn, insn) - || makeSeparate(insn.getType()) + || isSeparate(insn.getType()) || isDoWhile(blocksMap, curBlock, insn) || insn.contains(AType.EXC_HANDLER) || prevInsn.contains(AFlag.TRY_LEAVE) 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 a6deb1822..702eda545 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 @@ -77,7 +77,7 @@ public class ProcessVariables extends AbstractVisitor { .forEach(ssaVar -> { ArgType ssaType = ssaVar.getAssign().getInitType(); if (ssaType.isTypeKnown()) { - TypeCompare comparator = mth.root().getTypeUpdate().getComparator(); + TypeCompare comparator = mth.root().getTypeUpdate().getTypeCompare(); TypeCompareEnum result = comparator.compareTypes(ssaType, codeVarType); if (result == TypeCompareEnum.CONFLICT || result.isNarrow()) { mth.addWarn("Incorrect type for immutable var: ssa=" + ssaType 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 79e2445db..ad1239fe3 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 @@ -23,11 +23,13 @@ public class TypeCompare { private static final Logger LOG = LoggerFactory.getLogger(TypeCompare.class); private final RootNode root; - private final ArgTypeComparator comparator; + private final Comparator comparator; + private final Comparator reversedComparator; public TypeCompare(RootNode root) { this.root = root; this.comparator = new ArgTypeComparator(); + this.reversedComparator = comparator.reversed(); } /** @@ -192,10 +194,14 @@ public class TypeCompare { return NARROW_BY_GENERIC; } - public ArgTypeComparator getComparator() { + public Comparator getComparator() { return comparator; } + public Comparator getReversedComparator() { + return reversedComparator; + } + private final class ArgTypeComparator implements Comparator { @Override public int compare(ArgType a, ArgType b) { 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 0bb40333f..1a42c8a32 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 @@ -31,6 +31,10 @@ public enum TypeCompareEnum { } } + public boolean isEqual() { + return this == EQUAL; + } + public boolean isWider() { return this == WIDER || this == WIDER_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 5e2340a64..b72b307a0 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 @@ -68,7 +68,33 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (mth.isNoCode()) { return; } - // collect initial type bounds from assign and usages + boolean resolved = runTypePropagation(mth); + if (!resolved) { + boolean moveAdded = false; + for (SSAVar var : new ArrayList<>(mth.getSVars())) { + moveAdded |= tryInsertAdditionalInsn(mth, var); + } + if (moveAdded) { + InitCodeVariables.rerun(mth); + resolved = runTypePropagation(mth); + } + if (!resolved) { + resolved = runMultiVariableSearch(mth); + } + } + if (resolved) { + for (SSAVar var : new ArrayList<>(mth.getSVars())) { + processIncompatiblePrimitives(mth, var); + } + } + } + + /** + * Guess type from usage and try to set it to current variable + * and all connected instructions with {@link TypeUpdate#apply(SSAVar, ArgType)} + */ + private boolean runTypePropagation(MethodNode mth) { + // collect initial type bounds from assign and usages` mth.getSVars().forEach(this::attachBounds); mth.getSVars().forEach(this::mergePhiBounds); @@ -86,27 +112,20 @@ public final class TypeInferenceVisitor extends AbstractVisitor { resolved = false; } } - if (resolved) { - for (SSAVar var : new ArrayList<>(mth.getSVars())) { - processIncompatiblePrimitives(mth, var); - } - } else { - for (SSAVar var : new ArrayList<>(mth.getSVars())) { - tryInsertAdditionalInsn(mth, var); - } - runMultiVariableSearch(mth); - } + return resolved; } - private void runMultiVariableSearch(MethodNode mth) { + private boolean runMultiVariableSearch(MethodNode mth) { TypeSearch typeSearch = new TypeSearch(mth); try { boolean success = typeSearch.run(); if (!success) { mth.addWarn("Multi-variable type inference failed"); } + return success; } catch (Exception e) { mth.addWarn("Multi-variable type inference failed. Error: " + Utils.getStackTrace(e)); + return false; } } @@ -186,7 +205,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor { return bounds.stream() .map(ITypeBound::getType) .filter(Objects::nonNull) - .max(typeUpdate.getArgTypeComparator()); + .max(typeUpdate.getTypeCompare().getComparator()); } private void attachBounds(SSAVar var) { @@ -337,9 +356,13 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (usedInPhiList.isEmpty()) { return false; } - if (var.getUseCount() == 1) { - InsnNode assignInsn = var.getAssign().getAssignInsn(); - if (assignInsn != null && assignInsn.getType() == InsnType.MOVE) { + InsnNode assignInsn = var.getAssign().getAssignInsn(); + if (assignInsn != null) { + InsnType assignType = assignInsn.getType(); + if (assignType == InsnType.CONST) { + return false; + } + if (assignType == InsnType.MOVE && var.getUseCount() == 1) { return false; } } @@ -358,11 +381,16 @@ public final class TypeInferenceVisitor extends AbstractVisitor { if (reg.getSVar() == var) { BlockNode blockNode = phiInsn.getBlockByArgIndex(argIndex); InsnNode lastInsn = BlockUtils.getLastInsn(blockNode); - if (lastInsn != null && BlockSplitter.makeSeparate(lastInsn.getType())) { - if (Consts.DEBUG) { - LOG.warn("Can't insert move for PHI in block with separate insn: {}", lastInsn); + if (lastInsn != null && BlockSplitter.isSeparate(lastInsn.getType())) { + // can't insert move in block with separate instruction + // trying previous block + List preds = blockNode.getPredecessors(); + if (preds.size() == 1) { + blockNode = preds.get(0); + } else { + mth.addWarn("Failed to insert additional move for type inference"); + return false; } - return false; } int regNum = reg.getRegNum(); @@ -377,15 +405,6 @@ public final class TypeInferenceVisitor extends AbstractVisitor { blockNode.getInstructions().add(moveInsn); phiInsn.replaceArg(reg, reg.duplicate(regNum, newSsaVar)); - - attachBounds(var); - for (InsnArg phiArg : phiInsn.getArguments()) { - attachBounds(((RegisterArg) phiArg).getSVar()); - } - for (InsnArg phiArg : phiInsn.getArguments()) { - mergePhiBounds(((RegisterArg) phiArg).getSVar()); - } - InitCodeVariables.initCodeVar(newSsaVar); return true; } } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java index 09b94f2e9..636aebd24 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeInfo.java @@ -25,6 +25,11 @@ public class TypeInfo { return bounds; } + public void reset() { + this.type = ArgType.UNKNOWN; + this.bounds.clear(); + } + @Override public String toString() { return "TypeInfo{type=" + type + ", bounds=" + bounds + '}'; 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 f0c39d29c..a6d600531 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 @@ -13,6 +13,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.core.Consts; +import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.instructions.args.PrimitiveType; @@ -20,7 +21,6 @@ import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.InsnNode; import jadx.core.dex.nodes.MethodNode; -import jadx.core.utils.exceptions.JadxRuntimeException; /** * Slow and memory consuming multi-variable type search algorithm. @@ -46,7 +46,7 @@ public class TypeSearch { this.mth = mth; this.state = new TypeSearchState(mth); this.typeUpdate = mth.root().getTypeUpdate(); - this.typeCompare = typeUpdate.getComparator(); + this.typeCompare = typeUpdate.getTypeCompare(); } public boolean run() { @@ -84,14 +84,16 @@ public class TypeSearch { } boolean applySuccess = true; for (TypeSearchVarInfo var : resolvedVars) { - TypeUpdateResult res = typeUpdate.applyWithWiderAllow(var.getVar(), var.getCurrentType()); + if (!var.getCurrentType().isTypeKnown()) { + // exclude unknown variables + continue; + } + TypeUpdateResult res = typeUpdate.applyWithWiderIgnSame(var.getVar(), var.getCurrentType()); if (res == TypeUpdateResult.REJECT) { + mth.addComment("JADX DEBUG: Multi-variable search result rejected for " + var); applySuccess = false; } } - if (!applySuccess) { - LOG.warn("Multi-variable search result apply rejected in {}", mth); - } return applySuccess; } @@ -229,14 +231,14 @@ public class TypeSearch { addCandidateTypes(bounds, candidateTypes, getNarrowTypes(useType)); } + addUsageTypeCandidates(ssaVar, bounds, candidateTypes); + int size = candidateTypes.size(); if (size == 0) { - throw new JadxRuntimeException("No candidate types for var: " + ssaVar.getDetailedVarInfo(mth) - + "\n assigns: " + assigns - + "\n uses: " + uses - + "\n mth insns count: " + mth.countInsns()); - } - if (size == 1) { + varInfo.setTypeResolved(true); + varInfo.setCurrentType(ArgType.UNKNOWN); + varInfo.setCandidateTypes(Collections.emptyList()); + } else if (size == 1) { varInfo.setTypeResolved(true); varInfo.setCurrentType(candidateTypes.iterator().next()); varInfo.setCandidateTypes(Collections.emptyList()); @@ -244,22 +246,44 @@ public class TypeSearch { varInfo.setTypeResolved(false); varInfo.setCurrentType(ArgType.UNKNOWN); ArrayList types = new ArrayList<>(candidateTypes); - types.sort(typeCompare.getComparator()); + types.sort(typeCompare.getReversedComparator()); varInfo.setCandidateTypes(Collections.unmodifiableList(types)); } } + private void addUsageTypeCandidates(SSAVar ssaVar, Set bounds, Set candidateTypes) { + for (RegisterArg useArg : ssaVar.getUseList()) { + InsnNode parentInsn = useArg.getParentInsn(); + if (parentInsn != null) { + InsnType insnType = parentInsn.getType(); + if (insnType == InsnType.APUT) { + ArgType aputType = parentInsn.getArg(2).getType(); + if (aputType.isTypeKnown()) { + addCandidateType(bounds, candidateTypes, ArgType.array(aputType)); + } + } + } + } + } + private void addCandidateTypes(Set bounds, Set collectedTypes, Collection candidateTypes) { for (ArgType candidateType : candidateTypes) { - if (candidateType.isTypeKnown() && typeUpdate.inBounds(bounds, candidateType)) { - collectedTypes.add(candidateType); - if (collectedTypes.size() > CANDIDATES_COUNT_LIMIT) { - return; - } + if (addCandidateType(bounds, collectedTypes, candidateType)) { + return; } } } + private boolean addCandidateType(Set bounds, Set collectedTypes, ArgType candidateType) { + if (candidateType.isTypeKnown() && typeUpdate.inBounds(bounds, candidateType)) { + collectedTypes.add(candidateType); + if (collectedTypes.size() > CANDIDATES_COUNT_LIMIT) { + return true; + } + } + return false; + } + private List getWiderTypes(ArgType type) { if (type.isTypeKnown()) { if (type.isObject()) { @@ -309,14 +333,6 @@ public class TypeSearch { } } - public static ArgType getArgType(TypeSearchState state, InsnArg arg) { - if (arg.isRegister()) { - RegisterArg reg = (RegisterArg) arg; - return state.getVarInfo(reg.getSVar()).getCurrentType(); - } - return arg.getType(); - } - private void addConstraint(TypeSearchVarInfo varInfo, ITypeConstraint constraint) { if (constraint != null) { varInfo.getConstraints().add(constraint); @@ -349,10 +365,10 @@ public class TypeSearch { return new AbstractTypeConstraint(insn, arg) { @Override public boolean check(TypeSearchState state) { - ArgType resType = getArgType(state, insn.getResult()); - ArgType argType = getArgType(state, insn.getArg(0)); + ArgType resType = state.getArgType(insn.getResult()); + ArgType argType = state.getArgType(insn.getArg(0)); TypeCompareEnum res = typeCompare.compareTypes(resType, argType); - return res == TypeCompareEnum.EQUAL || res.isWider(); + return res.isEqual() || res.isWider(); } }; } @@ -361,9 +377,9 @@ public class TypeSearch { return new AbstractTypeConstraint(insn, arg) { @Override public boolean check(TypeSearchState state) { - ArgType resType = getArgType(state, insn.getResult()); + ArgType resType = state.getArgType(insn.getResult()); for (InsnArg insnArg : insn.getArguments()) { - ArgType argType = getArgType(state, insnArg); + ArgType argType = state.getArgType(insnArg); if (!argType.equals(resType)) { return false; } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearchState.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearchState.java index b37a73f84..7f1fa6255 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearchState.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeSearchState.java @@ -8,6 +8,9 @@ import java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.RegisterArg; import jadx.core.dex.instructions.args.SSAVar; import jadx.core.dex.nodes.MethodNode; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -33,6 +36,14 @@ public class TypeSearchState { return varInfo; } + public ArgType getArgType(InsnArg arg) { + if (arg.isRegister()) { + RegisterArg reg = (RegisterArg) arg; + return getVarInfo(reg.getSVar()).getCurrentType(); + } + return arg.getType(); + } + public List getAllVars() { return new ArrayList<>(varInfoMap.values()); } 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 3abeb8400..9d8de7ae0 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 @@ -1,6 +1,5 @@ package jadx.core.dex.visitors.typeinference; -import java.util.Comparator; import java.util.EnumMap; import java.util.LinkedHashMap; import java.util.List; @@ -15,7 +14,6 @@ import org.slf4j.LoggerFactory; import jadx.core.Consts; import jadx.core.clsp.NClass; -import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.MethodInfo; import jadx.core.dex.instructions.InsnType; import jadx.core.dex.instructions.InvokeNode; @@ -37,9 +35,6 @@ import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.SAME; public final class TypeUpdate { private static final Logger LOG = LoggerFactory.getLogger(TypeUpdate.class); - private static final TypeUpdateFlags FLAGS_EMPTY = new TypeUpdateFlags(); - private static final TypeUpdateFlags FLAGS_WIDER = new TypeUpdateFlags().allowWider(); - private final RootNode root; private final Map listenerRegistry; private final TypeCompare comparator; @@ -54,14 +49,21 @@ public final class TypeUpdate { * Perform recursive type checking and type propagation for all related variables */ public TypeUpdateResult apply(SSAVar ssaVar, ArgType candidateType) { - return apply(ssaVar, candidateType, FLAGS_EMPTY); + return apply(ssaVar, candidateType, TypeUpdateFlags.FLAGS_EMPTY); } /** * Allow wider types for apply from debug info and some special cases */ public TypeUpdateResult applyWithWiderAllow(SSAVar ssaVar, ArgType candidateType) { - return apply(ssaVar, candidateType, FLAGS_WIDER); + return apply(ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER); + } + + /** + * Force type setting + */ + public TypeUpdateResult applyWithWiderIgnSame(SSAVar ssaVar, ArgType candidateType) { + return apply(ssaVar, candidateType, TypeUpdateFlags.FLAGS_WIDER_IGNSAME); } private TypeUpdateResult apply(SSAVar ssaVar, ArgType candidateType, TypeUpdateFlags flags) { @@ -91,7 +93,7 @@ public final class TypeUpdate { throw new JadxRuntimeException("Null type update for arg: " + arg); } ArgType currentType = arg.getType(); - if (Objects.equals(currentType, candidateType)) { + if (Objects.equals(currentType, candidateType) && !updateInfo.getFlags().isIgnoreSame()) { return SAME; } TypeCompareEnum compareResult = comparator.compareTypes(candidateType, currentType); @@ -360,23 +362,20 @@ public final class TypeUpdate { private TypeUpdateResult moveListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { boolean assignChanged = isAssign(insn, arg); InsnArg changeArg = assignChanged ? insn.getArg(0) : insn.getResult(); - boolean allowReject; - if (changeArg.getType().isTypeKnown()) { - // allow result to be wider - TypeCompareEnum compareTypes = comparator.compareTypes(candidateType, changeArg.getType()); - boolean correctType = compareTypes == TypeCompareEnum.EQUAL - || (assignChanged ? compareTypes.isWider() : compareTypes.isNarrow()); - if (correctType && inBounds(updateInfo, changeArg, candidateType)) { - allowReject = true; - } else { - return REJECT; - } - } else { - allowReject = arg.isThis() || arg.contains(AFlag.IMMUTABLE_TYPE); - } + + // allow result to be wider + TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType()); + boolean correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow()); TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType); - if (result == REJECT && allowReject) { + if (result == SAME && !correctType) { + if (Consts.DEBUG) { + LOG.debug("Move insn types mismatch: {} -> {}, insn: {}", + candidateType, changeArg.getType(), insn); + } + return REJECT; + } + if (result == REJECT && correctType) { return CHANGED; } return result; @@ -508,11 +507,7 @@ public final class TypeUpdate { return insn.getResult() == arg; } - public TypeCompare getComparator() { + public TypeCompare getTypeCompare() { return comparator; } - - public Comparator getArgTypeComparator() { - return comparator.getComparator(); - } } 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 cfc8b24aa..6c3437bd4 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 @@ -2,14 +2,23 @@ package jadx.core.dex.visitors.typeinference; public class TypeUpdateFlags { - private boolean allowWider; + public static final TypeUpdateFlags FLAGS_EMPTY = new TypeUpdateFlags(false, false); + public static final TypeUpdateFlags FLAGS_WIDER = new TypeUpdateFlags(true, false); + public static final TypeUpdateFlags FLAGS_WIDER_IGNSAME = new TypeUpdateFlags(true, true); - public TypeUpdateFlags allowWider() { - this.allowWider = true; - return this; + private final boolean allowWider; + private final boolean ignoreSame; + + private TypeUpdateFlags(boolean allowWider, boolean ignoreSame) { + this.allowWider = allowWider; + this.ignoreSame = ignoreSame; } public boolean isAllowWider() { return allowWider; } + + public boolean isIgnoreSame() { + return ignoreSame; + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestVarArg.java b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestVarArg.java index 5ca48e23f..2636fc12e 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/invoke/TestVarArg.java +++ b/jadx-core/src/test/java/jadx/tests/integration/invoke/TestVarArg.java @@ -12,16 +12,16 @@ public class TestVarArg extends IntegrationTest { public static class TestCls { - void test1(int... a) { + public void test1(int... a) { } - void test2(int i, Object... a) { + public void test2(int i, Object... a) { } - void test3(int[] a) { + public void test3(int[] a) { } - void call() { + public void call() { test1(1, 2); test2(3, "1", 7); test3(new int[] { 5, 8 }); diff --git a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java b/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java deleted file mode 100644 index a81f01426..000000000 --- a/jadx-core/src/test/java/jadx/tests/integration/trycatch/TestTryCatch4.java +++ /dev/null @@ -1,46 +0,0 @@ -package jadx.tests.integration.trycatch; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; - -import org.junit.jupiter.api.Test; - -import jadx.core.dex.nodes.ClassNode; -import jadx.tests.api.IntegrationTest; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.MatcherAssert.assertThat; - -public class TestTryCatch4 extends IntegrationTest { - - public static class TestCls { - @SuppressWarnings({ "resource", "unused" }) - public Object test(Object obj) { - FileOutputStream output = null; - try { - output = new FileOutputStream(new File("f")); - return new Object(); - } catch (FileNotFoundException e) { - System.out.println("Exception"); - return null; - } - } - } - - @Test - public void test() { - disableCompilation(); - ClassNode cls = getClassNode(TestCls.class); - String code = cls.getCode().toString(); - - assertThat(code, containsString("try {")); - assertThat(code, containsString("output = new FileOutputStream(new File(\"f\"));")); - assertThat(code, containsString("return new Object();")); - assertThat(code, containsString("} catch (FileNotFoundException e) {")); - assertThat(code, containsString("System.out.println(\"Exception\");")); - assertThat(code, containsString("return null;")); - assertThat(code, not(containsString("output = output;"))); - } -} diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver10.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver10.java new file mode 100644 index 000000000..feb797334 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestTypeResolver10.java @@ -0,0 +1,27 @@ +package jadx.tests.integration.types; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.SmaliTest; + +import static jadx.tests.api.utils.JadxMatchers.containsOne; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; + +public class TestTypeResolver10 extends SmaliTest { + + /** + * Method argument assigned with different types in separate branches + */ + + @Test + public void test() { + ClassNode cls = getClassNodeFromSmali(); + String code = cls.getCode().toString(); + + assertThat(code, containsOne("Object test(String str, String str2)")); + assertThat(code, not(containsString("Object obj2 = 0;"))); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables2.java b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables2.java index bdd8eaf1f..c12fb8d5c 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables2.java +++ b/jadx-core/src/test/java/jadx/tests/integration/variables/TestVariables2.java @@ -11,7 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; public class TestVariables2 extends IntegrationTest { public static class TestCls { - Object test(Object s) { + public Object test(Object s) { Object store = s != null ? s : null; if (store == null) { store = new Object(); diff --git a/jadx-core/src/test/smali/types/TestTypeResolver10.smali b/jadx-core/src/test/smali/types/TestTypeResolver10.smali new file mode 100644 index 000000000..9c0f4344c --- /dev/null +++ b/jadx-core/src/test/smali/types/TestTypeResolver10.smali @@ -0,0 +1,80 @@ +.class public Ltypes/TestTypeResolver10; +.super Ljava/lang/Object; +.source "SourceFile" + + +.method private test(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; + .locals 2 + + invoke-virtual {p1}, Ljava/lang/String;->isEmpty()Z + + move-result v0 + + const/4 v1, 0x0 + + if-eqz v0, :cond_0 + + return-object v1 + + :cond_0 + invoke-virtual {p2}, Ljava/lang/String;->isEmpty()Z + + move-result v0 + + if-eqz v0, :cond_1 + + return-object v1 + + :cond_1 + :try_start_0 + invoke-static {p2}, Ljava/lang/Integer;->parseInt(Ljava/lang/String;)I + + move-result p2 + + if-eqz p2, :cond_4 + + const/4 v0, 0x1 + + if-eq p2, v0, :cond_3 + + const/4 v0, 0x3 + + if-eq p2, v0, :cond_2 + + goto :goto_1 + + .line 4 + :cond_2 + invoke-static {p1}, Ljava/lang/Boolean;->parseBoolean(Ljava/lang/String;)Z + + move-result p1 + + invoke-static {p1}, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean; + + move-result-object p1 + + :cond_3 + :goto_0 + move-object v1, p1 + + goto :goto_1 + + .line 5 + :cond_4 + invoke-static {p1}, Ljava/lang/Integer;->valueOf(Ljava/lang/String;)Ljava/lang/Integer; + + move-result-object p1 + :try_end_0 + .catch Ljava/lang/NumberFormatException; {:try_start_0 .. :try_end_0} :catch_0 + + goto :goto_0 + + :catch_0 + move-exception p1 + + .line 6 + invoke-virtual {p1}, Ljava/lang/NumberFormatException;->printStackTrace()V + + :goto_1 + return-object v1 +.end method