fix: correct rollback if type update failed (#2090)
This commit is contained in:
@@ -67,6 +67,7 @@ import jadx.core.dex.visitors.rename.SourceFileRename;
|
||||
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 class Jadx {
|
||||
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 class Jadx {
|
||||
if (args.isDebugInfo()) {
|
||||
passes.add(new DebugInfoApplyVisitor());
|
||||
}
|
||||
passes.add(new FixTypesVisitor());
|
||||
passes.add(new FinishTypeInference());
|
||||
passes.add(new CodeRenameVisitor());
|
||||
passes.add(new DeboxingVisitor());
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.dex.instructions;
|
||||
|
||||
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 class IndexInsnNode extends InsnNode {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public ArgType getIndexAsType() {
|
||||
return (ArgType) index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexInsnNode copy() {
|
||||
return copyCommonParams(new IndexInsnNode(insnType, index, getArgsCount()));
|
||||
|
||||
@@ -103,6 +103,6 @@ public class InvokeNode extends BaseInvokeNode {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return baseString() + " type: " + type + " call: " + mth + attributesString();
|
||||
return baseString() + " " + type + " call: " + mth + attributesString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import jadx.core.utils.InsnRemover;
|
||||
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 abstract class InsnArg extends Typed {
|
||||
}
|
||||
|
||||
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 abstract class InsnArg extends Typed {
|
||||
}
|
||||
|
||||
public boolean isSameVar(RegisterArg arg) {
|
||||
if (arg == null) {
|
||||
return false;
|
||||
}
|
||||
if (isRegister()) {
|
||||
return ((RegisterArg) this).sameRegAndSVar(arg);
|
||||
}
|
||||
@@ -280,4 +296,8 @@ public abstract class InsnArg extends Typed {
|
||||
public InsnArg duplicate() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toShortString() {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@ public final class LiteralArg extends InsnArg {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isZeroLiteral() {
|
||||
return literal == 0;
|
||||
}
|
||||
|
||||
public boolean isInteger() {
|
||||
switch (type.getPrimitiveType()) {
|
||||
case INT:
|
||||
@@ -125,6 +130,11 @@ public final class LiteralArg extends InsnArg {
|
||||
return literal == that.literal && getType().equals(that.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return Long.toString(literal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
|
||||
@@ -48,6 +48,11 @@ public final class NamedArg extends InsnArg implements Named {
|
||||
return name.equals(((NamedArg) o).name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return '(' + name + ' ' + type + ')';
|
||||
|
||||
@@ -215,6 +215,16 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
&& 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();
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.dex.instructions.args;
|
||||
|
||||
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.dex.visitors.typeinference.TypeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class SSAVar {
|
||||
public class SSAVar implements Comparable<SSAVar> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SSAVar.class);
|
||||
|
||||
private static final Comparator<SSAVar> SSA_VAR_COMPARATOR =
|
||||
Comparator.comparingInt(SSAVar::getRegNum).thenComparingInt(SSAVar::getVersion);
|
||||
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
|
||||
@@ -256,34 +260,6 @@ public class SSAVar {
|
||||
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<ArgType> types = new HashSet<>();
|
||||
Set<String> names = Collections.emptySet();
|
||||
@@ -323,4 +299,37 @@ public class SSAVar {
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import jadx.core.dex.visitors.ssa.SSATransform;
|
||||
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 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
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 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
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 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
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 @@ public class ConstInlineVisitor extends AbstractVisitor {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
||||
&& 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 @@ public class MarkMethodsForInline extends AbstractVisitor {
|
||||
&& 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:
|
||||
|
||||
@@ -5,6 +5,9 @@ import java.util.List;
|
||||
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.methods.MutableMethodDetails;
|
||||
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 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
}
|
||||
)
|
||||
public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MethodInvokeVisitor.class);
|
||||
|
||||
private RootNode root;
|
||||
|
||||
@Override
|
||||
@@ -116,7 +122,8 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
int argsOffset = invokeInsn.getFirstArgOffset();
|
||||
List<ArgType> compilerVarTypes = collectCompilerVarTypes(invokeInsn, argsOffset);
|
||||
List<ArgType> castTypes = searchCastTypes(parentMth, effectiveMthDetails, effectiveOverloadMethods, compilerVarTypes);
|
||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, castTypes);
|
||||
List<ArgType> resultCastTypes = expandTypes(parentMth, effectiveMthDetails, castTypes);
|
||||
applyArgsCast(invokeInsn, argsOffset, compilerVarTypes, resultCastTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -180,7 +187,13 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
if (arg.isLiteral() && compilerType.isPrimitive() && castType.isPrimitive()) {
|
||||
arg.setType(castType);
|
||||
arg.add(AFlag.EXPLICIT_PRIMITIVE_TYPE);
|
||||
} else if (InsnUtils.isWrapped(arg, InsnType.CHECK_CAST)) {
|
||||
IndexInsnNode wrapInsn = ((IndexInsnNode) ((InsnWrapArg) arg).getWrapInsn());
|
||||
wrapInsn.updateIndex(castType);
|
||||
} else {
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
LOG.info("Insert cast for invoke insn arg: {}, insn: {}", arg, invokeInsn);
|
||||
}
|
||||
InsnNode castInsn = new IndexInsnNode(InsnType.CAST, castType, 1);
|
||||
castInsn.addArg(arg);
|
||||
castInsn.add(AFlag.EXPLICIT_CAST);
|
||||
@@ -243,7 +256,7 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
|
||||
private List<ArgType> searchCastTypes(MethodNode parentMth, IMethodDetails mthDetails, List<IMethodDetails> overloadedMethods,
|
||||
List<ArgType> compilerVarTypes) {
|
||||
// try compile types
|
||||
// try compiler types
|
||||
if (isOverloadResolved(mthDetails, overloadedMethods, compilerVarTypes)) {
|
||||
return compilerVarTypes;
|
||||
}
|
||||
@@ -300,6 +313,27 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
return changed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use generified types if available
|
||||
*/
|
||||
private List<ArgType> expandTypes(MethodNode parentMth, IMethodDetails methodDetails, List<ArgType> castTypes) {
|
||||
TypeCompare typeCompare = parentMth.root().getTypeCompare();
|
||||
List<ArgType> mthArgTypes = methodDetails.getArgTypes();
|
||||
int argsCount = castTypes.size();
|
||||
List<ArgType> 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<IMethodDetails> overloadedMethods, List<ArgType> castTypes) {
|
||||
if (overloadedMethods.isEmpty()) {
|
||||
return false;
|
||||
@@ -387,12 +421,22 @@ public class MethodInvokeVisitor extends AbstractVisitor {
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,11 @@ public class ModVisitor extends AbstractVisitor {
|
||||
|
||||
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();
|
||||
|
||||
@@ -25,7 +25,9 @@ import jadx.core.dex.attributes.nodes.LineAttrNode;
|
||||
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 class PrepareForCodeGen extends AbstractVisitor {
|
||||
removeParenthesis(block);
|
||||
modifyArith(block);
|
||||
checkConstUsage(block);
|
||||
addNullCasts(mth, block);
|
||||
}
|
||||
moveConstructorInConstructor(mth);
|
||||
collectFieldsUsageInAnnotations(mth, mth);
|
||||
@@ -379,4 +382,27 @@ public class PrepareForCodeGen extends AbstractVisitor {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class DebugInfoApplyVisitor extends AbstractVisitor {
|
||||
return;
|
||||
}
|
||||
OptionalInt max = ssaVar.getUseList().stream().mapToInt(DebugInfoApplyVisitor::getInsnOffsetByArg).max();
|
||||
if (!max.isPresent()) {
|
||||
if (max.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int startOffset = getInsnOffsetByArg(ssaVar.getAssign());
|
||||
|
||||
+17
-1
@@ -84,7 +84,7 @@ public class ProcessVariables extends AbstractVisitor {
|
||||
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 class ProcessVariables extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -30,4 +30,9 @@ public final class FinishTypeInference extends AbstractVisitor {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "FinishTypeInference";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Function<MethodNode, Boolean>> 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<MethodNode, Boolean> 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<ITypeBound> bounds = typeInfo.getBounds();
|
||||
Optional<ArgType> 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<ArgType> selectBestTypeFromBounds(Set<ITypeBound> bounds) {
|
||||
return bounds.stream()
|
||||
.map(ITypeBound::getType)
|
||||
.filter(Objects::nonNull)
|
||||
.max(typeUpdate.getTypeCompare().getComparator());
|
||||
}
|
||||
|
||||
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
|
||||
List<ArgType> 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<ArgType> makePossibleTypesList(ArgType type, @Nullable SSAVar var) {
|
||||
if (type.isArray()) {
|
||||
List<ArgType> 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<ArgType> 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<ArgType> 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:
|
||||
* <br>
|
||||
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
|
||||
*/
|
||||
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
|
||||
int changed = 0;
|
||||
List<SSAVar> 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<ITypeBound> bounds = typeInfo.getBounds();
|
||||
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
|
||||
return 0;
|
||||
}
|
||||
List<ITypeBound> 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<ArgType> 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<SSAVar> 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<RegisterArg> 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<RegisterArg> 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<BlockNode> preds = blockNode.getPredecessors();
|
||||
if (preds.size() == 1) {
|
||||
return checkBlockForInsnInsert(preds.get(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return blockNode;
|
||||
}
|
||||
|
||||
private boolean tryWiderObjects(MethodNode mth, SSAVar var) {
|
||||
Set<ArgType> 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<SSAVar> 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<InsnNode> 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";
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -8,7 +8,7 @@ import jadx.core.dex.instructions.args.RegisterArg;
|
||||
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 final class TypeBoundCheckCastAssign implements ITypeBoundDynamic {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import jadx.core.dex.nodes.RootNode;
|
||||
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 @@ public class TypeCompare {
|
||||
if (objType.isGenericType()) {
|
||||
return compareTypeVariables(genericType, objType);
|
||||
}
|
||||
if (objType.isWildcard()) {
|
||||
return CONFLICT_BY_GENERIC;
|
||||
}
|
||||
boolean rootObject = objType.equals(ArgType.OBJECT);
|
||||
List<ArgType> extendTypes = genericType.getExtendTypes();
|
||||
if (extendTypes.isEmpty()) {
|
||||
|
||||
@@ -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 enum TypeCompareEnum {
|
||||
return NARROW_BY_GENERIC;
|
||||
|
||||
case CONFLICT:
|
||||
case CONFLICT_BY_GENERIC:
|
||||
case EQUAL:
|
||||
case UNKNOWN:
|
||||
default:
|
||||
@@ -51,7 +53,7 @@ public enum TypeCompareEnum {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+29
-707
@@ -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.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.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.trycatch.ExcHandlerAttr;
|
||||
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<Function<MethodNode, Boolean>> 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 final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
LOG.info("Start type inference in method: {}", mth);
|
||||
}
|
||||
assignImmutableTypes(mth);
|
||||
try {
|
||||
for (Function<MethodNode, Boolean> 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);
|
||||
mth.addError("Type inference 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect initial type bounds from assign and usages
|
||||
*/
|
||||
private boolean initTypeBounds(MethodNode mth) {
|
||||
void initTypeBounds(MethodNode mth) {
|
||||
List<SSAVar> 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<SSAVar> 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 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
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<ITypeBound> bounds = typeInfo.getBounds();
|
||||
Optional<ArgType> bestTypeOpt = selectBestTypeFromBounds(bounds);
|
||||
@@ -208,21 +139,17 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
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<ArgType> selectBestTypeFromBounds(Set<ITypeBound> bounds) {
|
||||
@@ -310,7 +237,11 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
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 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
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 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean tryPossibleTypes(MethodNode mth, SSAVar var, ArgType type) {
|
||||
List<ArgType> 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<ArgType> makePossibleTypesList(ArgType type, @Nullable SSAVar var) {
|
||||
if (type.isArray()) {
|
||||
List<ArgType> 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<ArgType> 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<ArgType> 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:
|
||||
* <br>
|
||||
* {@code <T extends Comparable> T var = (Comparable) obj; => T var = (T) obj; }
|
||||
*/
|
||||
private boolean tryRestoreTypeVarCasts(MethodNode mth) {
|
||||
int changed = 0;
|
||||
List<SSAVar> 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<ITypeBound> bounds = typeInfo.getBounds();
|
||||
if (!ListUtils.anyMatch(bounds, t -> t.getType().isGenericType())) {
|
||||
return 0;
|
||||
}
|
||||
List<ITypeBound> 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<ArgType> 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<SSAVar> 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<RegisterArg> 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<BlockNode> preds = blockNode.getPredecessors();
|
||||
if (preds.size() == 1) {
|
||||
return checkBlockForInsnInsert(preds.get(0));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return blockNode;
|
||||
}
|
||||
|
||||
private boolean tryWiderObjects(MethodNode mth, SSAVar var) {
|
||||
Set<ArgType> 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<SSAVar> 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<InsnNode> 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 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "TypeInferenceVisitor";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +67,9 @@ public class TypeSearch {
|
||||
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 @@ public class TypeSearch {
|
||||
private boolean search(List<TypeSearchVarInfo> 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 @@ public class TypeSearch {
|
||||
count *= size;
|
||||
}
|
||||
sb.append(" = ").append(count);
|
||||
LOG.debug(" > max iterations count = {}", sb);
|
||||
LOG.debug(" max iterations count = {}", sb);
|
||||
}
|
||||
|
||||
// prepare vars
|
||||
|
||||
@@ -8,12 +8,13 @@ import java.util.Set;
|
||||
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 @@ public final class TypeUpdate {
|
||||
if (result == REJECT) {
|
||||
return result;
|
||||
}
|
||||
List<TypeUpdateEntry> 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 @@ public final class TypeUpdate {
|
||||
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 @@ public final class TypeUpdate {
|
||||
}
|
||||
|
||||
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 @@ public final class TypeUpdate {
|
||||
}
|
||||
}
|
||||
}
|
||||
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,25 +180,25 @@ public final class TypeUpdate {
|
||||
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;
|
||||
boolean allSame = result == SAME;
|
||||
if (result != REJECT) {
|
||||
List<RegisterArg> useList = ssaVar.getUseList();
|
||||
for (RegisterArg arg : useList) {
|
||||
result = requestUpdate(updateInfo, arg, candidateType);
|
||||
if (result == REJECT) {
|
||||
break;
|
||||
}
|
||||
if (result != SAME) {
|
||||
allSame = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
List<RegisterArg> useList = ssaVar.getUseList();
|
||||
for (RegisterArg arg : useList) {
|
||||
TypeUpdateResult useResult = requestUpdate(updateInfo, arg, candidateType);
|
||||
if (useResult == REJECT) {
|
||||
return REJECT;
|
||||
}
|
||||
if (useResult != 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 @@ public final class TypeUpdate {
|
||||
return CHANGED;
|
||||
}
|
||||
updateInfo.requestUpdate(arg, candidateType);
|
||||
updateInfo.checkUpdatesCount();
|
||||
try {
|
||||
TypeUpdateResult result = runListeners(updateInfo, arg, candidateType);
|
||||
if (result == REJECT) {
|
||||
@@ -199,7 +215,8 @@ public final class TypeUpdate {
|
||||
}
|
||||
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 @@ public final class TypeUpdate {
|
||||
}
|
||||
if (boundType != null && !checkBound(candidateType, bound, boundType)) {
|
||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||
LOG.debug("Reject type '{}' for {} by bound: {}", candidateType, ssaVar, bound);
|
||||
LOG.debug("Reject type '{}' for {} by bound: {} from {}", candidateType, ssaVar, boundType, bound);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -265,6 +282,7 @@ public final class TypeUpdate {
|
||||
return true;
|
||||
|
||||
case CONFLICT:
|
||||
case CONFLICT_BY_GENERIC:
|
||||
return false;
|
||||
|
||||
case UNKNOWN:
|
||||
@@ -407,6 +425,9 @@ public final class TypeUpdate {
|
||||
|
||||
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 @@ public final class TypeUpdate {
|
||||
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) {
|
||||
|
||||
@@ -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<TypeUpdateEntry> {
|
||||
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 final class TypeUpdateEntry {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<TypeUpdateEntry> updates = new ArrayList<>();
|
||||
private final Map<InsnArg, TypeUpdateEntry> 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 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() {
|
||||
for (TypeUpdateEntry updateEntry : updates) {
|
||||
InsnArg arg = updateEntry.getArg();
|
||||
arg.setType(updateEntry.getType());
|
||||
}
|
||||
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<TypeUpdateEntry> getUpdates() {
|
||||
return updates;
|
||||
public boolean isEmpty() {
|
||||
return updateMap.isEmpty();
|
||||
}
|
||||
|
||||
public List<TypeUpdateEntry> getSortedUpdates() {
|
||||
return updateMap.values().stream().sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public TypeUpdateFlags getFlags() {
|
||||
@@ -76,6 +87,6 @@ public class TypeUpdateInfo {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TypeUpdateInfo{" + flags + ", updates=" + updates + '}';
|
||||
return "TypeUpdateInfo{" + flags + ' ' + getSortedUpdates() + '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,14 @@ public class InsnUtils {
|
||||
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) {
|
||||
|
||||
@@ -139,6 +139,16 @@ public class TypeCompareTest {
|
||||
check(collSuperWildcard, listWildcard, TypeCompareEnum.CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compareGenericWildCards() {
|
||||
// 'java.util.List<T>' and 'java.util.List<? extends T>'
|
||||
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");
|
||||
|
||||
@@ -149,10 +149,10 @@ public abstract class IntegrationTest extends TestUtils {
|
||||
|
||||
@AfterEach
|
||||
public void after() throws IOException {
|
||||
FileUtils.clearTempRootDir();
|
||||
close(jadxDecompiler);
|
||||
close(sourceCompiler);
|
||||
close(decompiledCompiler);
|
||||
FileUtils.clearTempRootDir();
|
||||
}
|
||||
|
||||
private void close(Closeable closeable) throws IOException {
|
||||
|
||||
@@ -4,12 +4,11 @@ import java.util.List;
|
||||
|
||||
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 class TestCastOfNull extends IntegrationTest {
|
||||
|
||||
@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<String>) null);"));
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("m((long[]) null);")
|
||||
.containsOne("m((String) null);")
|
||||
.containsOne("m((List<String>) null);");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;");
|
||||
}
|
||||
}
|
||||
@@ -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 <T, K> List<T> test(List<? extends T> list, Set<? extends T> set, Function<? super T, ? extends K> function) {
|
||||
checkParameterIsNotNull(function, "distinctBy");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static class TestCls {
|
||||
|
||||
public final <T, K> List<T> test(List<? extends T> list,
|
||||
Set<? extends T> set, Function<? super T, ? extends K> function) {
|
||||
if (set != null) {
|
||||
List<? extends T> union = list != null ? union(list, set, function) : null;
|
||||
if (union != null) {
|
||||
@@ -23,11 +31,24 @@ public class TestTypeResolver16 extends SmaliTest {
|
||||
}
|
||||
return list != null ? (List<T>) list : emptyList();
|
||||
}
|
||||
*/
|
||||
// @formatter:on
|
||||
|
||||
public static <T, K> List<T> union(
|
||||
Collection<? extends T> collection,
|
||||
Iterable<? extends T> iterable,
|
||||
Function<? super T, ? extends K> function) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
assertThat(getClassNode(TestCls.class))
|
||||
.code()
|
||||
.containsOne("(List<T>) list");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmali() {
|
||||
assertThat(getClassNodeFromSmali())
|
||||
.code()
|
||||
.containsOne("(List<T>) list");
|
||||
|
||||
@@ -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 ");
|
||||
}
|
||||
}
|
||||
@@ -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;"));
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,9 @@ package jadx.tests.integration.types;
|
||||
|
||||
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 @@ public class TestTypeResolver7 extends IntegrationTest {
|
||||
|
||||
@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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
|
||||
<pattern>%-5level - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
|
||||
@@ -5,41 +5,23 @@
|
||||
.locals 1
|
||||
.annotation system Ldalvik/annotation/Signature;
|
||||
value = {
|
||||
"<T:",
|
||||
"Ljava/lang/Object;",
|
||||
"K:",
|
||||
"Ljava/lang/Object;",
|
||||
">(",
|
||||
"Ljava/util/List<",
|
||||
"+TT;>;",
|
||||
"Ljava/util/Set<",
|
||||
"+TT;>;",
|
||||
"Ljava/util/function/Function<",
|
||||
"-TT;+TK;>;)",
|
||||
"Ljava/util/List<",
|
||||
"TT;>;"
|
||||
"<T:", "Ljava/lang/Object;", "K:", "Ljava/lang/Object;", ">(",
|
||||
"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;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.class LT1;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.method public foo1()V
|
||||
.registers 1
|
||||
return-void
|
||||
.end method
|
||||
@@ -0,0 +1,7 @@
|
||||
.class LT2;
|
||||
.super Ljava/lang/Object;
|
||||
|
||||
.method public foo2()V
|
||||
.registers 1
|
||||
return-void
|
||||
.end method
|
||||
@@ -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
|
||||
@@ -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 = {
|
||||
"<T:",
|
||||
"Ljava/lang/Object;",
|
||||
">(",
|
||||
"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;-><init>(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
|
||||
Reference in New Issue
Block a user