refactor: fix misuse of immutable type flag
This commit is contained in:
@@ -4,15 +4,12 @@ import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.InsnNode;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class RegisterArg extends InsnArg implements Named {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RegisterArg.class);
|
||||
public static final String THIS_ARG_NAME = "this";
|
||||
|
||||
protected final int regNum;
|
||||
@@ -38,15 +35,6 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
if (sVar == null) {
|
||||
throw new JadxRuntimeException("Can't change type for register without SSA variable: " + this);
|
||||
}
|
||||
if (contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
if (!type.isTypeKnown()) {
|
||||
throw new JadxRuntimeException("Unknown immutable type '" + type + "' in " + this);
|
||||
}
|
||||
if (!type.equals(newType)) {
|
||||
LOG.warn("JADX WARNING: Can't change immutable type from '{}' to '{}' for {}", type, newType, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sVar.setType(newType);
|
||||
}
|
||||
|
||||
@@ -62,9 +50,23 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ArgType getImmutableType() {
|
||||
if (contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
return type;
|
||||
}
|
||||
if (sVar != null) {
|
||||
return sVar.getImmutableType();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTypeImmutable() {
|
||||
return contains(AFlag.IMMUTABLE_TYPE) || (sVar != null && sVar.contains(AFlag.IMMUTABLE_TYPE));
|
||||
if (contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
return true;
|
||||
}
|
||||
return sVar != null && sVar.isTypeImmutable();
|
||||
}
|
||||
|
||||
public SSAVar getSVar() {
|
||||
@@ -73,9 +75,14 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
|
||||
void setSVar(@NotNull SSAVar sVar) {
|
||||
this.sVar = sVar;
|
||||
if (contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
sVar.add(AFlag.IMMUTABLE_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(AFlag flag) {
|
||||
if (flag == AFlag.IMMUTABLE_TYPE && !type.isTypeKnown()) {
|
||||
throw new JadxRuntimeException("Can't mark unknown type as immutable, type: " + type + ", reg: " + this);
|
||||
}
|
||||
super.add(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,8 +176,7 @@ public class RegisterArg extends InsnArg implements Named {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("(r");
|
||||
sb.append(regNum);
|
||||
sb.append("(r").append(regNum);
|
||||
if (sVar != null) {
|
||||
sb.append('v').append(sVar.getVersion());
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import java.util.Set;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.attributes.AType;
|
||||
import jadx.core.dex.attributes.AttrNode;
|
||||
import jadx.core.dex.attributes.nodes.RegDebugInfoAttr;
|
||||
import jadx.core.dex.instructions.InsnType;
|
||||
import jadx.core.dex.instructions.PhiInsn;
|
||||
@@ -20,7 +20,7 @@ import jadx.core.dex.visitors.typeinference.TypeInfo;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class SSAVar extends AttrNode {
|
||||
public class SSAVar {
|
||||
private final int regNum;
|
||||
private final int version;
|
||||
|
||||
@@ -66,8 +66,29 @@ public class SSAVar extends AttrNode {
|
||||
return useList.size();
|
||||
}
|
||||
|
||||
// must be used only from RegisterArg#setType()
|
||||
void setType(ArgType type) {
|
||||
@Nullable
|
||||
public ArgType getImmutableType() {
|
||||
if (assign.contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
return assign.getInitType();
|
||||
}
|
||||
for (RegisterArg useArg : useList) {
|
||||
if (useArg.contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
return useArg.getInitType();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isTypeImmutable() {
|
||||
return getImmutableType() != null;
|
||||
}
|
||||
|
||||
public void setType(ArgType type) {
|
||||
ArgType imType = getImmutableType();
|
||||
if (imType != null && !imType.equals(type)) {
|
||||
throw new JadxRuntimeException("Can't change immutable type " + imType + " to " + type + " for " + this);
|
||||
}
|
||||
|
||||
typeInfo.setType(type);
|
||||
if (codeVar != null) {
|
||||
codeVar.setType(type);
|
||||
|
||||
@@ -244,12 +244,12 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode {
|
||||
return;
|
||||
}
|
||||
argsList = new ArrayList<>(args.size());
|
||||
for (ArgType arg : args) {
|
||||
RegisterArg regArg = InsnArg.reg(pos, arg);
|
||||
for (ArgType argType : args) {
|
||||
RegisterArg regArg = InsnArg.reg(pos, argType);
|
||||
regArg.add(AFlag.METHOD_ARGUMENT);
|
||||
regArg.add(AFlag.IMMUTABLE_TYPE);
|
||||
argsList.add(regArg);
|
||||
pos += arg.getRegCount();
|
||||
pos += argType.getRegCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -134,8 +134,7 @@ public class DeboxingVisitor extends AbstractVisitor {
|
||||
|
||||
private boolean canChangeTypeToPrimitive(RegisterArg arg) {
|
||||
for (SSAVar ssaVar : arg.getSVar().getCodeVar().getSsaVars()) {
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
if (assignArg.contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
if (ssaVar.isTypeImmutable()) {
|
||||
return false;
|
||||
}
|
||||
for (RegisterArg useArg : ssaVar.getUseList()) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package jadx.core.dex.visitors;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -89,8 +90,8 @@ public class InitCodeVariables extends AbstractVisitor {
|
||||
private static void setCodeVarType(CodeVar codeVar, Set<SSAVar> vars) {
|
||||
if (vars.size() > 1) {
|
||||
List<ArgType> imTypes = vars.stream()
|
||||
.filter(var -> var.contains(AFlag.IMMUTABLE_TYPE))
|
||||
.map(var -> var.getTypeInfo().getType())
|
||||
.map(SSAVar::getImmutableType)
|
||||
.filter(Objects::nonNull)
|
||||
.filter(ArgType::isTypeKnown)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
@@ -294,7 +294,7 @@ public class ModVisitor extends AbstractVisitor {
|
||||
SSAVar sVar = reg.getSVar();
|
||||
if (sVar != null) {
|
||||
sVar.getCodeVar().setFinal(true);
|
||||
sVar.add(AFlag.DONT_INLINE);
|
||||
sVar.getAssign().add(AFlag.DONT_INLINE);
|
||||
}
|
||||
reg.add(AFlag.SKIP_ARG);
|
||||
}
|
||||
|
||||
@@ -337,9 +337,16 @@ public class LoopRegionVisitor extends AbstractVisitor implements IRegionVisitor
|
||||
if (!iterableArg.isRegister() || !iterableType.isObject()) {
|
||||
return true;
|
||||
}
|
||||
// TODO: add checks
|
||||
iterableType = ArgType.generic(iterableType.getObject(), varType);
|
||||
iterableArg.setType(iterableType);
|
||||
ArgType genericType = ArgType.generic(iterableType.getObject(), varType);
|
||||
if (iterableArg.isRegister()) {
|
||||
ArgType immutableType = ((RegisterArg) iterableArg).getImmutableType();
|
||||
if (immutableType != null && !immutableType.equals(genericType)) {
|
||||
// can't change type
|
||||
// allow to iterate over not generified collection only for Object vars
|
||||
return varType.equals(ArgType.OBJECT);
|
||||
}
|
||||
}
|
||||
iterableArg.setType(genericType);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -793,8 +793,7 @@ public class RegionMaker {
|
||||
LOG.debug("Fixing incorrect switch cases order, method: {}", mth);
|
||||
blocksMap = reOrderSwitchCases(blocksMap, fallThroughCases);
|
||||
if (isBadCasesOrder(blocksMap, fallThroughCases)) {
|
||||
LOG.error("Can't fix incorrect switch cases order, method: {}", mth);
|
||||
mth.add(AFlag.INCONSISTENT_CODE);
|
||||
mth.addWarn("Can't fix incorrect switch cases order");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -72,11 +72,10 @@ public class ProcessVariables extends AbstractVisitor {
|
||||
codeVar.setType(ArgType.UNKNOWN);
|
||||
unknownTypesCount++;
|
||||
} else {
|
||||
codeVar.getSsaVars().stream()
|
||||
.filter(ssaVar -> ssaVar.contains(AFlag.IMMUTABLE_TYPE))
|
||||
codeVar.getSsaVars()
|
||||
.forEach(ssaVar -> {
|
||||
ArgType ssaType = ssaVar.getAssign().getInitType();
|
||||
if (ssaType.isTypeKnown()) {
|
||||
ArgType ssaType = ssaVar.getImmutableType();
|
||||
if (ssaType != null && ssaType.isTypeKnown()) {
|
||||
TypeCompare comparator = mth.root().getTypeUpdate().getTypeCompare();
|
||||
TypeCompareEnum result = comparator.compareTypes(ssaType, codeVarType);
|
||||
if (result == TypeCompareEnum.CONFLICT || result.isNarrow()) {
|
||||
|
||||
@@ -75,7 +75,7 @@ public class CodeShrinkVisitor extends AbstractVisitor {
|
||||
private static void checkInline(MethodNode mth, BlockNode block, InsnList insnList,
|
||||
List<WrapInfo> wrapList, ArgsInfo argsInfo, RegisterArg arg) {
|
||||
SSAVar sVar = arg.getSVar();
|
||||
if (sVar == null || sVar.contains(AFlag.DONT_INLINE)) {
|
||||
if (sVar == null || sVar.getAssign().contains(AFlag.DONT_INLINE)) {
|
||||
return;
|
||||
}
|
||||
// allow inline only one use arg
|
||||
|
||||
@@ -2,7 +2,6 @@ package jadx.core.dex.visitors.ssa;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.instructions.args.RegisterArg;
|
||||
import jadx.core.dex.instructions.args.SSAVar;
|
||||
import jadx.core.dex.nodes.BlockNode;
|
||||
@@ -23,13 +22,10 @@ final class RenameState {
|
||||
new int[regsCount]);
|
||||
RegisterArg thisArg = mth.getThisArg();
|
||||
if (thisArg != null) {
|
||||
SSAVar ssaVar = state.startVar(thisArg);
|
||||
ssaVar.add(AFlag.THIS);
|
||||
ssaVar.add(AFlag.METHOD_ARGUMENT);
|
||||
state.startVar(thisArg);
|
||||
}
|
||||
for (RegisterArg arg : mth.getArgRegs()) {
|
||||
SSAVar ssaVar = state.startVar(arg);
|
||||
ssaVar.add(AFlag.METHOD_ARGUMENT);
|
||||
state.startVar(arg);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
+13
-19
@@ -107,7 +107,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
for (SSAVar var : mth.getSVars()) {
|
||||
ArgType type = var.getTypeInfo().getType();
|
||||
if (!type.isTypeKnown()
|
||||
&& !var.getAssign().isTypeImmutable()
|
||||
&& !var.isTypeImmutable()
|
||||
&& !tryDeduceType(mth, var, type)) {
|
||||
resolved = false;
|
||||
}
|
||||
@@ -131,20 +131,9 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
|
||||
private boolean setImmutableType(SSAVar ssaVar) {
|
||||
try {
|
||||
ArgType codeVarType = ssaVar.getCodeVar().getType();
|
||||
if (codeVarType != null) {
|
||||
return applyImmutableType(ssaVar, codeVarType);
|
||||
}
|
||||
RegisterArg assignArg = ssaVar.getAssign();
|
||||
if (assignArg.isTypeImmutable()) {
|
||||
return applyImmutableType(ssaVar, assignArg.getInitType());
|
||||
}
|
||||
if (ssaVar.contains(AFlag.IMMUTABLE_TYPE)) {
|
||||
for (RegisterArg arg : ssaVar.getUseList()) {
|
||||
if (arg.isTypeImmutable()) {
|
||||
return applyImmutableType(ssaVar, arg.getInitType());
|
||||
}
|
||||
}
|
||||
ArgType immutableType = ssaVar.getImmutableType();
|
||||
if (immutableType != null) {
|
||||
return applyImmutableType(ssaVar, immutableType);
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
@@ -166,7 +155,7 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
TypeUpdateResult result = typeUpdate.apply(ssaVar, initType);
|
||||
if (result == TypeUpdateResult.REJECT) {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.warn("Initial immutable type set rejected: {} -> {}", ssaVar, initType);
|
||||
LOG.info("Reject initial immutable type {} for {}", initType, ssaVar);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -236,8 +225,13 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private void addAssignBound(TypeInfo typeInfo, RegisterArg assign) {
|
||||
ArgType immutableType = assign.getImmutableType();
|
||||
if (immutableType != null) {
|
||||
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, immutableType));
|
||||
return;
|
||||
}
|
||||
InsnNode insn = assign.getParentInsn();
|
||||
if (insn == null || assign.isTypeImmutable()) {
|
||||
if (insn == null || insn.getResult() == null) {
|
||||
addBound(typeInfo, new TypeBoundConst(BoundEnum.ASSIGN, assign.getInitType()));
|
||||
return;
|
||||
}
|
||||
@@ -436,12 +430,12 @@ public final class TypeInferenceVisitor extends AbstractVisitor {
|
||||
}
|
||||
|
||||
private void processIncompatiblePrimitives(MethodNode mth, SSAVar var) {
|
||||
if (var.getAssign().getType() == ArgType.BOOLEAN) {
|
||||
if (var.getTypeInfo().getType() == ArgType.BOOLEAN) {
|
||||
for (ITypeBound bound : var.getTypeInfo().getBounds()) {
|
||||
if (bound.getBound() == BoundEnum.USE
|
||||
&& bound.getType().isPrimitive() && bound.getType() != ArgType.BOOLEAN) {
|
||||
InsnNode insn = bound.getArg().getParentInsn();
|
||||
if (insn.getType() == InsnType.CAST) {
|
||||
if (insn == null || insn.getType() == InsnType.CAST) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -77,10 +77,7 @@ public class TypeSearch {
|
||||
for (TypeSearchVarInfo var : resolvedVars) {
|
||||
SSAVar ssaVar = var.getVar();
|
||||
ArgType resolvedType = var.getCurrentType();
|
||||
ssaVar.getAssign().setType(resolvedType);
|
||||
for (RegisterArg arg : ssaVar.getUseList()) {
|
||||
arg.setType(resolvedType);
|
||||
}
|
||||
ssaVar.setType(resolvedType);
|
||||
}
|
||||
boolean applySuccess = true;
|
||||
for (TypeSearchVarInfo var : resolvedVars) {
|
||||
@@ -194,18 +191,14 @@ public class TypeSearch {
|
||||
|
||||
private void fillTypeCandidates(SSAVar ssaVar) {
|
||||
TypeSearchVarInfo varInfo = state.getVarInfo(ssaVar);
|
||||
ArgType currentType = ssaVar.getTypeInfo().getType();
|
||||
if (currentType.isTypeKnown()) {
|
||||
varInfo.setTypeResolved(true);
|
||||
varInfo.setCurrentType(currentType);
|
||||
varInfo.setCandidateTypes(Collections.emptyList());
|
||||
ArgType immutableType = ssaVar.getImmutableType();
|
||||
if (immutableType != null) {
|
||||
varInfo.markResolved(immutableType);
|
||||
return;
|
||||
}
|
||||
if (ssaVar.getAssign().isTypeImmutable()) {
|
||||
ArgType initType = ssaVar.getAssign().getInitType();
|
||||
varInfo.setTypeResolved(true);
|
||||
varInfo.setCurrentType(initType);
|
||||
varInfo.setCandidateTypes(Collections.emptyList());
|
||||
ArgType currentType = ssaVar.getTypeInfo().getType();
|
||||
if (currentType.isTypeKnown()) {
|
||||
varInfo.markResolved(currentType);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package jadx.core.dex.visitors.typeinference;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -17,6 +18,12 @@ public class TypeSearchVarInfo {
|
||||
this.var = var;
|
||||
}
|
||||
|
||||
public void markResolved(ArgType type) {
|
||||
this.currentType = type;
|
||||
this.typeResolved = true;
|
||||
this.candidateTypes = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
if (typeResolved) {
|
||||
return;
|
||||
|
||||
@@ -122,6 +122,13 @@ public final class TypeUpdate {
|
||||
|
||||
private TypeUpdateResult updateTypeForSsaVar(TypeUpdateInfo updateInfo, SSAVar ssaVar, ArgType candidateType) {
|
||||
TypeInfo typeInfo = ssaVar.getTypeInfo();
|
||||
ArgType immutableType = ssaVar.getImmutableType();
|
||||
if (immutableType != null && !Objects.equals(immutableType, candidateType)) {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.info("Reject change immutable type {} to {} for {}", immutableType, candidateType, ssaVar);
|
||||
}
|
||||
return REJECT;
|
||||
}
|
||||
if (!inBounds(updateInfo, typeInfo.getBounds(), candidateType)) {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Reject type '{}' for {} by bounds: {}", candidateType, ssaVar, typeInfo.getBounds());
|
||||
@@ -363,15 +370,20 @@ public final class TypeUpdate {
|
||||
boolean assignChanged = isAssign(insn, arg);
|
||||
InsnArg changeArg = assignChanged ? insn.getArg(0) : insn.getResult();
|
||||
|
||||
// allow result to be wider
|
||||
TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType());
|
||||
boolean correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow());
|
||||
boolean correctType;
|
||||
if (changeArg.getType().isTypeKnown()) {
|
||||
// allow result to be wider
|
||||
TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType());
|
||||
correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow());
|
||||
} else {
|
||||
correctType = true;
|
||||
}
|
||||
|
||||
TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType);
|
||||
if (result == SAME && !correctType) {
|
||||
if (Consts.DEBUG) {
|
||||
LOG.debug("Move insn types mismatch: {} -> {}, insn: {}",
|
||||
candidateType, changeArg.getType(), insn);
|
||||
LOG.debug("Move insn types mismatch: {} -> {}, change arg: {}, insn: {}",
|
||||
candidateType, changeArg.getType(), changeArg, insn);
|
||||
}
|
||||
return REJECT;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package jadx.tests.integration.inline;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
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;
|
||||
|
||||
public class TestTernaryCast extends IntegrationTest {
|
||||
|
||||
public static class TestCls {
|
||||
public String test(boolean b, Object obj, CharSequence cs) {
|
||||
return (String) (b ? obj : cs);
|
||||
}
|
||||
|
||||
public void check() {
|
||||
assertThat(test(true, "a", "b"), Matchers.is("a"));
|
||||
assertThat(test(false, "a", "b"), Matchers.is("b"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
ClassNode cls = getClassNode(TestCls.class);
|
||||
String code = cls.getCode().toString();
|
||||
|
||||
assertThat(code, containsOne("return (String) (b ? obj : cs);"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoDebug() {
|
||||
noDebugInfo();
|
||||
getClassNode(TestCls.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user