fix: several improvements for multi-variable type search (#720)
This commit is contained in:
@@ -132,6 +132,9 @@ public class NameGen {
|
||||
if (!NameMapper.isValidAndPrintable(varName)) {
|
||||
varName = getFallbackName(var);
|
||||
}
|
||||
if (Consts.DEBUG) {
|
||||
varName += '_' + getFallbackName(var);
|
||||
}
|
||||
return varName;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 + ')';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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<ArgType> comparator;
|
||||
private final Comparator<ArgType> 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<ArgType> getComparator() {
|
||||
return comparator;
|
||||
}
|
||||
|
||||
public Comparator<ArgType> getReversedComparator() {
|
||||
return reversedComparator;
|
||||
}
|
||||
|
||||
private final class ArgTypeComparator implements Comparator<ArgType> {
|
||||
@Override
|
||||
public int compare(ArgType a, ArgType b) {
|
||||
|
||||
@@ -31,6 +31,10 @@ public enum TypeCompareEnum {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEqual() {
|
||||
return this == EQUAL;
|
||||
}
|
||||
|
||||
public boolean isWider() {
|
||||
return this == WIDER || this == WIDER_BY_GENERIC;
|
||||
}
|
||||
|
||||
+48
-29
@@ -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<BlockNode> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + '}';
|
||||
|
||||
@@ -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<ArgType> types = new ArrayList<>(candidateTypes);
|
||||
types.sort(typeCompare.getComparator());
|
||||
types.sort(typeCompare.getReversedComparator());
|
||||
varInfo.setCandidateTypes(Collections.unmodifiableList(types));
|
||||
}
|
||||
}
|
||||
|
||||
private void addUsageTypeCandidates(SSAVar ssaVar, Set<ITypeBound> bounds, Set<ArgType> 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<ITypeBound> bounds, Set<ArgType> collectedTypes, Collection<ArgType> 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<ITypeBound> bounds, Set<ArgType> 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<ArgType> 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;
|
||||
}
|
||||
|
||||
@@ -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<TypeSearchVarInfo> getAllVars() {
|
||||
return new ArrayList<>(varInfoMap.values());
|
||||
}
|
||||
|
||||
@@ -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<InsnType, ITypeListener> 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<ArgType> getArgTypeComparator() {
|
||||
return comparator.getComparator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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;")));
|
||||
}
|
||||
}
|
||||
@@ -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;")));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user