fix: several improvements for multi-variable type search (#720)

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