diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ArgsListUpdateCallback.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ArgsListUpdateCallback.java new file mode 100644 index 000000000..f78f3c9d8 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ArgsListUpdateCallback.java @@ -0,0 +1,121 @@ +package jadx.core.dex.visitors.typeinference; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Predicate; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; + +import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.CHANGED; +import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.REJECT; +import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.SAME; + +/** + * Type update callback to set same type for args from list. + */ +public class ArgsListUpdateCallback implements ITypeUpdateCallback { + private final TypeUpdate typeUpdate; + private final TypeUpdateInfo updateInfo; + private final Iterator argsIterator; + private final ArgType candidateType; + private final boolean direct; + + private @Nullable Predicate argsFilter; + private @Nullable ITypeUpdateCallback finalResultCallback; + private boolean ignoreReject = false; + + private boolean allSame = true; + private boolean firstQueue = false; + + public ArgsListUpdateCallback(TypeUpdate typeUpdate, TypeUpdateInfo updateInfo, + List args, ArgType candidateType, boolean direct) { + this.typeUpdate = typeUpdate; + this.updateInfo = updateInfo; + this.argsIterator = args.iterator(); + this.candidateType = candidateType; + this.direct = direct; + } + + @Override + public @Nullable TypeUpdateResult updateCallback(TypeUpdateResult result) { + while (true) { + if (!ignoreReject) { + if (result == REJECT) { + return finalResult(result); + } + } + if (result != SAME) { + allSame = false; + } + T next = getNextArg(); + if (next == null) { + return finalResult(allSame ? SAME : CHANGED); + } + result = queueUpdate(next); + if (result == null) { + // keep this callback + return null; + } + } + } + + private @Nullable TypeUpdateResult queueUpdate(T next) { + ITypeUpdateCallback cb; + if (firstQueue) { + cb = this; + firstQueue = false; + } else { + cb = null; + } + if (direct) { + return typeUpdate.queueDirectTypeUpdate(updateInfo, next, candidateType, cb); + } + return typeUpdate.queueTypeUpdate(updateInfo, next, candidateType, cb); + } + + public @Nullable TypeUpdateResult runFirstQueue() { + firstQueue = true; + return updateCallback(SAME); + } + + public void setFinalResultCallback(@Nullable ITypeUpdateCallback finalResultCallback) { + this.finalResultCallback = finalResultCallback; + } + + public void setArgsFilter(@Nullable Predicate argsFilter) { + this.argsFilter = argsFilter; + } + + public void setIgnoreReject(boolean ignoreReject) { + this.ignoreReject = ignoreReject; + } + + private @Nullable TypeUpdateResult finalResult(TypeUpdateResult result) { + if (finalResultCallback != null) { + return finalResultCallback.updateCallback(result); + } + return result; + } + + private @Nullable T getNextArg() { + Iterator it = argsIterator; + Predicate filter = argsFilter; + while (true) { + if (!it.hasNext()) { + return null; + } + T next = it.next(); + if (filter == null || filter.test(next)) { + return next; + } + } + } + + @Override + public String toString() { + return "ArgsListUpdateCallback"; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java index 5a72e0bd1..f89a2b17b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeListener.java @@ -1,6 +1,7 @@ package jadx.core.dex.visitors.typeinference; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; @@ -16,5 +17,6 @@ public interface ITypeListener { * @param arg apply suggested type for this arg * @param candidateType suggest new type */ + @Nullable TypeUpdateResult update(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, @NotNull ArgType candidateType); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeUpdateCallback.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeUpdateCallback.java new file mode 100644 index 000000000..9abcac557 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/ITypeUpdateCallback.java @@ -0,0 +1,19 @@ +package jadx.core.dex.visitors.typeinference; + +import org.jetbrains.annotations.Nullable; + +/** + * Callback to process and modify type update result + */ +@FunctionalInterface +public interface ITypeUpdateCallback { + + /** + * Called on type update result being calculated + * + * @param result - type update result + * @return modified result, can be null - will keep callback and wait for another result + */ + @Nullable + TypeUpdateResult updateCallback(TypeUpdateResult result); +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/InvokeUpdateCallback.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/InvokeUpdateCallback.java new file mode 100644 index 000000000..00e0f0881 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/InvokeUpdateCallback.java @@ -0,0 +1,144 @@ +package jadx.core.dex.visitors.typeinference; + +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.instructions.BaseInvokeNode; +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; +import jadx.core.dex.instructions.args.RegisterArg; + +import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.CHANGED; +import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.REJECT; +import static jadx.core.dex.visitors.typeinference.TypeUpdateResult.SAME; + +public class InvokeUpdateCallback implements ITypeUpdateCallback { + private final TypeUpdate typeUpdate; + private final TypeUpdateInfo updateInfo; + private final BaseInvokeNode invoke; + private final int argsCount; + private final Set knownTypeVars; + private final Supplier getReturnType; + private final Function getArgType; + + private boolean isAssign; + private boolean allSame = true; + private int currentArg = -1; + + private InsnArg updateArg; + private ArgType updateType; + private boolean firstQueue = false; + + public InvokeUpdateCallback(TypeUpdate typeUpdate, TypeUpdateInfo updateInfo, BaseInvokeNode invoke, int argsCount, + Set knownTypeVars, Supplier getReturnType, Function getArgType) { + this.typeUpdate = typeUpdate; + this.updateInfo = updateInfo; + this.invoke = invoke; + this.argsCount = argsCount; + this.knownTypeVars = knownTypeVars; + this.getReturnType = getReturnType; + this.getArgType = getArgType; + } + + @Override + public @Nullable TypeUpdateResult updateCallback(TypeUpdateResult result) { + while (true) { + switch (result) { + case CHANGED: + allSame = false; + break; + + case REJECT: + TypeCompareEnum compare = typeUpdate.getTypeCompare().compareTypes(updateType, updateArg.getType()); + if (isAssign ? compare.isWider() : compare.isNarrow()) { + return REJECT; + } + break; + } + if (!getNextArg()) { + return allSame ? SAME : CHANGED; + } + ITypeUpdateCallback cb; + if (firstQueue) { + cb = this; + firstQueue = false; + } else { + cb = null; + } + result = typeUpdate.queueTypeUpdate(updateInfo, updateArg, updateType, cb); + if (result == null) { + return null; + } + } + } + + public TypeUpdateResult runQueue() { + firstQueue = true; + TypeUpdateResult result = SAME; + RegisterArg resultArg = invoke.getResult(); + if (resultArg != null && !resultArg.isTypeImmutable()) { + ArgType returnType = checkType(knownTypeVars, getReturnType.get()); + if (returnType != null) { + updateArg = resultArg; + updateType = returnType; + isAssign = true; + firstQueue = false; + result = typeUpdate.queueTypeUpdate(updateInfo, updateArg, updateType, this); + if (result == null) { + return null; + } + } + } + return updateCallback(result); + } + + private boolean getNextArg() { + while (true) { + currentArg++; + int i = currentArg; + if (i >= argsCount) { + return false; + } + int argOffset = invoke.getFirstArgOffset(); + InsnArg invokeArg = invoke.getArg(argOffset + i); + if (!invokeArg.isTypeImmutable()) { + ArgType argType = checkType(knownTypeVars, getArgType.apply(i)); + if (argType != null) { + updateArg = invokeArg; + updateType = argType; + isAssign = false; + return true; + } + } + } + } + + private @Nullable ArgType checkType(Set knownTypeVars, @Nullable ArgType type) { + if (type == null) { + return null; + } + if (type.isWildcard()) { + return null; + } + if (type.containsTypeVariable()) { + if (knownTypeVars.isEmpty()) { + return null; + } + Boolean hasUnknown = type.visitTypes(this::isUnknown); + if (hasUnknown != null) { + return null; + } + } + return type; + } + + private @Nullable Boolean isUnknown(ArgType t) { + if (t.isGenericType() && !knownTypeVars.contains(t)) { + return Boolean.TRUE; + } + return null; + } +} diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java index 896f60d88..3086195f2 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdate.java @@ -52,7 +52,7 @@ public final class TypeUpdate { } /** - * Perform recursive type checking and type propagation for all related variables + * Perform type checking and type propagation for all related variables */ public TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType) { return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_EMPTY); @@ -81,9 +81,14 @@ public final class TypeUpdate { if (candidateType == null || !candidateType.isTypeKnown()) { return REJECT; } - + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.debug("Start type update for {} to {}", ssaVar.toShortString(), candidateType); + } TypeUpdateInfo updateInfo = new TypeUpdateInfo(mth, flags, args); - TypeUpdateResult result = updateTypeChecked(updateInfo, ssaVar.getAssign(), candidateType); + TypeUpdateResult result = queueTypeUpdate(updateInfo, ssaVar.getAssign(), candidateType, null); + if (result == null) { + result = runUpdate(updateInfo); + } if (result == REJECT) { return result; } @@ -103,16 +108,88 @@ public final class TypeUpdate { } } - private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) { - if (candidateType == null) { - throw new JadxRuntimeException("Null type update for arg: " + arg); + private TypeUpdateResult runUpdate(TypeUpdateInfo updateInfo) { + TypeUpdateResult result = REJECT; + while (true) { + TypeUpdateRequest request = updateInfo.pollNextRequest(); + if (request == null) { + return result; + } + InsnArg updateArg = request.getArg(); + ArgType updateType = request.getCandidateType(); + TypeUpdateResult newResult; + if (request.isDirect()) { + newResult = requestUpdate(updateInfo, updateArg, updateType); + } else { + newResult = updateTypeForArg(updateInfo, updateArg, updateType); + } + updateInfo.saveCallback(request); + if (newResult == null) { + // no result: continue + } else { + // propagate result back through callbacks + result = processCallbacks(updateInfo, newResult); + } } - if (updateInfo.isProcessed(arg)) { - return CHANGED; + } + + private static TypeUpdateResult processCallbacks(TypeUpdateInfo updateInfo, TypeUpdateResult result) { + TypeUpdateResult current = result; + while (true) { + TypeUpdateRequest cbReq = updateInfo.pollNextCallback(); + if (cbReq == null) { + return current; + } + ITypeUpdateCallback callback = Objects.requireNonNull(cbReq.getCallback()); + current = callback.updateCallback(current); + if (current == null) { + // no result, put callback back into queue + // so it can be executed once result is calculated + updateInfo.saveCallback(cbReq); + return null; + } + if (current == REJECT) { + updateInfo.rollbackUpdate(cbReq.getArg()); + } + // proceed to next callback } + } + + /** + * Queue type update for InsnArg. + * + * @param callback - will be executed when result for this update is calculated, + * can be null - callback will pass result without change + * @return null if update added into queue, non-null result if not queued (verify failed) + */ + public @Nullable TypeUpdateResult queueTypeUpdate(TypeUpdateInfo updateInfo, + InsnArg arg, ArgType candidateType, @Nullable ITypeUpdateCallback callback) { + // verify can be done in queue processing before request run, but kept here for faster processing + // this might increase code complexity because result should be checked for null every time TypeUpdateResult res = verifyType(updateInfo, arg, candidateType); if (res != null) { - return res; + if (callback == null) { + return res; + } + TypeUpdateResult result = callback.updateCallback(res); + if (result == null) { + updateInfo.saveCallback(new TypeUpdateRequest(arg, candidateType, false, callback)); + } + return result; + } + updateInfo.queueRequest(new TypeUpdateRequest(arg, candidateType, false, callback)); + return null; + } + + public @Nullable TypeUpdateResult queueDirectTypeUpdate(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType, + @Nullable ITypeUpdateCallback callback) { + updateInfo.queueRequest(new TypeUpdateRequest(arg, candidateType, true, callback)); + return null; + } + + private TypeUpdateResult updateTypeForArg(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) { + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.debug("-> update type for: {} to {}", arg, candidateType); } if (arg instanceof RegisterArg) { RegisterArg reg = (RegisterArg) arg; @@ -122,6 +199,12 @@ public final class TypeUpdate { } private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) { + if (candidateType == null) { + throw new JadxRuntimeException("Null type update for arg: " + arg); + } + if (updateInfo.isProcessed(arg)) { + return CHANGED; + } ArgType currentType = arg.getType(); TypeUpdateFlags typeUpdateFlags = updateInfo.getFlags(); if (Objects.equals(currentType, candidateType)) { @@ -194,27 +277,16 @@ public final class TypeUpdate { if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) { return REJECT; } - TypeUpdateResult result = requestUpdate(updateInfo, ssaVar.getAssign(), candidateType); - boolean allSame = result == SAME; - if (result != REJECT) { - List useList = ssaVar.getUseList(); - for (RegisterArg arg : useList) { - result = requestUpdate(updateInfo, arg, candidateType); - if (result == REJECT) { - break; - } - if (result != SAME) { - allSame = false; - } + var updateCallback = new ArgsListUpdateCallback<>(this, updateInfo, ssaVar.getUseList(), candidateType, true); + updateCallback.setFinalResultCallback(result -> { + if (result == REJECT) { + // rollback update for all registers in current SSA var + updateInfo.rollbackUpdate(ssaVar.getAssign()); + ssaVar.getUseList().forEach(updateInfo::rollbackUpdate); } - } - 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; + return result; + }); + return queueDirectTypeUpdate(updateInfo, ssaVar.getAssign(), candidateType, updateCallback); } private TypeUpdateResult requestUpdate(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) { @@ -222,14 +294,6 @@ public final class TypeUpdate { return CHANGED; } updateInfo.requestUpdate(arg, candidateType); - TypeUpdateResult result = runListeners(updateInfo, arg, candidateType); - if (result == REJECT) { - updateInfo.rollbackUpdate(arg); - } - return result; - } - - private TypeUpdateResult runListeners(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) { InsnNode insn = arg.getParentInsn(); if (insn == null) { return SAME; @@ -238,6 +302,9 @@ public final class TypeUpdate { if (listener == null) { return CHANGED; } + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.debug("Run listener for insn: {}, arg: {}, type: {}", insn.getType(), arg, candidateType); + } return listener.update(updateInfo, insn, arg, candidateType); } @@ -359,85 +426,30 @@ public final class TypeUpdate { ArgType returnType = methodDetails.getReturnType(); List argTypes = methodDetails.getArgTypes(); int argsCount = argTypes.size(); + + Supplier getReturnType; + Function getArgType; if (typeVarsMap.isEmpty()) { // generics can't be resolved => use as is - return applyInvokeTypes(updateInfo, invoke, argsCount, knownTypeVars, () -> returnType, argTypes::get); + getReturnType = () -> returnType; + getArgType = argTypes::get; + } else { + // resolve types before apply + getReturnType = () -> typeUtils.replaceTypeVariablesUsingMap(returnType, typeVarsMap); + getArgType = argNum -> typeUtils.replaceClassGenerics(candidateType, argTypes.get(argNum)); } - // resolve types before apply - return applyInvokeTypes(updateInfo, invoke, argsCount, knownTypeVars, - () -> typeUtils.replaceTypeVariablesUsingMap(returnType, typeVarsMap), - argNum -> typeUtils.replaceClassGenerics(candidateType, argTypes.get(argNum))); + return new InvokeUpdateCallback(this, updateInfo, invoke, argsCount, knownTypeVars, getReturnType, getArgType) + .runQueue(); } return SAME; } - private TypeUpdateResult applyInvokeTypes(TypeUpdateInfo updateInfo, BaseInvokeNode invoke, int argsCount, - Set knownTypeVars, Supplier getReturnType, Function getArgType) { - boolean allSame = true; - RegisterArg resultArg = invoke.getResult(); - if (resultArg != null && !resultArg.isTypeImmutable()) { - ArgType returnType = checkType(knownTypeVars, getReturnType.get()); - if (returnType != null) { - TypeUpdateResult result = updateTypeChecked(updateInfo, resultArg, returnType); - if (result == REJECT) { - TypeCompareEnum compare = comparator.compareTypes(returnType, resultArg.getType()); - if (compare.isWider()) { - return REJECT; - } - } - if (result == CHANGED) { - allSame = false; - } - } - } - int argOffset = invoke.getFirstArgOffset(); - for (int i = 0; i < argsCount; i++) { - InsnArg invokeArg = invoke.getArg(argOffset + i); - if (!invokeArg.isTypeImmutable()) { - ArgType argType = checkType(knownTypeVars, getArgType.apply(i)); - if (argType != null) { - TypeUpdateResult result = updateTypeChecked(updateInfo, invokeArg, argType); - if (result == REJECT) { - TypeCompareEnum compare = comparator.compareTypes(argType, invokeArg.getType()); - if (compare.isNarrow()) { - return REJECT; - } - } - if (result == CHANGED) { - allSame = false; - } - } - } - } - return allSame ? SAME : CHANGED; - } - - @Nullable - private ArgType checkType(Set knownTypeVars, @Nullable ArgType type) { - if (type == null) { - return null; - } - if (type.isWildcard()) { - return null; - } - if (type.containsTypeVariable()) { - if (knownTypeVars.isEmpty()) { - return null; - } - Boolean hasUnknown = type.visitTypes(t -> t.isGenericType() && !knownTypeVars.contains(t) ? Boolean.TRUE : null); - if (hasUnknown != null) { - return null; - } - } - return type; - } - 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); + return queueTypeUpdate(updateInfo, changeArg, candidateType, null); } private TypeUpdateResult moveListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { @@ -451,18 +463,19 @@ public final class TypeUpdate { TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType()); boolean correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow()); - TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType); - if (result == SAME && !correctType) { - if (Consts.DEBUG_TYPE_INFERENCE) { - LOG.debug("Move insn types mismatch: {} -> {}, change arg: {}, insn: {}", - candidateType, changeArg.getType(), changeArg, insn); + return queueTypeUpdate(updateInfo, changeArg, candidateType, result -> { + if (result == SAME && !correctType) { + if (Consts.DEBUG_TYPE_INFERENCE) { + LOG.debug("Move insn types mismatch: {} -> {}, change arg: {}, insn: {}", + candidateType, changeArg.getType(), changeArg, insn); + } + return REJECT; } - return REJECT; - } - if (result == REJECT && correctType) { - return CHANGED; - } - return result; + if (result == REJECT && correctType) { + return CHANGED; + } + return result; + }); } /** @@ -470,22 +483,12 @@ public final class TypeUpdate { */ private TypeUpdateResult allSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { if (!isAssign(insn, arg)) { - return updateTypeChecked(updateInfo, insn.getResult(), candidateType); + return queueTypeUpdate(updateInfo, insn.getResult(), candidateType, null); } - boolean allSame = true; - for (InsnArg insnArg : insn.getArguments()) { - if (insnArg == arg) { - continue; - } - TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); - if (result == REJECT) { - return result; - } - if (result != SAME) { - allSame = false; - } - } - return allSame ? SAME : CHANGED; + // update args with same type + var updateCallback = new ArgsListUpdateCallback<>(this, updateInfo, insn.getArgList(), candidateType, false); + updateCallback.setArgsFilter(a -> a != arg); + return updateCallback.runFirstQueue(); } private TypeUpdateResult arithListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { @@ -501,32 +504,26 @@ public final class TypeUpdate { * Try to set candidate type to all args, don't fail on reject */ private TypeUpdateResult suggestAllSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { + var updateCallback = new ArgsListUpdateCallback<>(this, updateInfo, insn.getArgList(), candidateType, false); + updateCallback.setArgsFilter(a -> a != arg); + updateCallback.setIgnoreReject(true); if (!isAssign(insn, arg)) { RegisterArg resultArg = insn.getResult(); if (resultArg != null) { - updateTypeChecked(updateInfo, resultArg, candidateType); + // start with result + return queueTypeUpdate(updateInfo, resultArg, candidateType, updateCallback); } } - boolean allSame = true; - for (InsnArg insnArg : insn.getArguments()) { - if (insnArg != arg) { - TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); - if (result == REJECT) { - // ignore - } else if (result != SAME) { - allSame = false; - } - } - } - return allSame ? SAME : CHANGED; + // start with first arg + return updateCallback.runFirstQueue(); } private TypeUpdateResult checkCastListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { IndexInsnNode checkCast = (IndexInsnNode) insn; if (isAssign(insn, arg)) { InsnArg insnArg = insn.getArg(0); - TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType); - return result == REJECT ? SAME : result; + return queueTypeUpdate(updateInfo, insnArg, candidateType, + r -> r == REJECT ? SAME : r); } ArgType castType = (ArgType) checkCast.getIndex(); TypeCompareEnum res = comparator.compareTypes(candidateType, castType); @@ -543,7 +540,7 @@ public final class TypeUpdate { } if (res == TypeCompareEnum.NARROW_BY_GENERIC && candidateType.containsGeneric()) { // propagate generic type to result - return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType); + return queueTypeUpdate(updateInfo, checkCast.getResult(), candidateType, null); } ArgType currentType = checkCast.getArg(0).getType(); return candidateType.equals(currentType) ? SAME : CHANGED; @@ -569,18 +566,19 @@ public final class TypeUpdate { private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) { if (isAssign(insn, arg)) { - TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType)); - if (result == REJECT) { - ArgType arrType = insn.getArg(0).getType(); - if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) { - TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement()); - if (compResult == TypeCompareEnum.WIDER) { - // allow implicit upcast for primitive types (int a = byteArr[n]) - return CHANGED; + return queueTypeUpdate(updateInfo, insn.getArg(0), ArgType.array(candidateType), result -> { + if (result == REJECT) { + ArgType arrType = insn.getArg(0).getType(); + if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) { + TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement()); + if (compResult == TypeCompareEnum.WIDER) { + // allow implicit upcast for primitive types (int a = byteArr[n]) + return CHANGED; + } } } - } - return result; + return result; + }); } InsnArg arrArg = insn.getArg(0); if (arrArg == arg) { @@ -588,18 +586,19 @@ public final class TypeUpdate { if (arrayElement == null) { return REJECT; } - TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getResult(), arrayElement); - if (result == REJECT) { - ArgType resType = insn.getResult().getType(); - if (resType.isTypeKnown() && resType.isPrimitive()) { - TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement); - if (compResult == TypeCompareEnum.WIDER) { - // allow implicit upcast for primitive types (int a = byteArr[n]) - return CHANGED; + return queueTypeUpdate(updateInfo, insn.getResult(), arrayElement, result -> { + if (result == REJECT) { + ArgType resType = insn.getResult().getType(); + if (resType.isTypeKnown() && resType.isPrimitive()) { + TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement); + if (compResult == TypeCompareEnum.WIDER) { + // allow implicit upcast for primitive types (int a = byteArr[n]) + return CHANGED; + } } } - } - return result; + return result; + }); } // index argument return SAME; @@ -613,21 +612,22 @@ public final class TypeUpdate { if (arrayElement == null) { return REJECT; } - TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement); - if (result == REJECT) { - ArgType putType = putArg.getType(); - if (putType.isTypeKnown()) { - TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType); - if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) { - // allow wider result (i.e. allow put any objects in Object[] or byte in int[]) - return CHANGED; + return queueTypeUpdate(updateInfo, putArg, arrayElement, result -> { + if (result == REJECT) { + ArgType putType = putArg.getType(); + if (putType.isTypeKnown()) { + TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType); + if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) { + // allow wider result (i.e. allow put any objects in Object[] or byte in int[]) + return CHANGED; + } } } - } - return result; + return result; + }); } if (arrArg == putArg) { - return updateTypeChecked(updateInfo, arrArg, ArgType.array(candidateType)); + return queueTypeUpdate(updateInfo, arrArg, ArgType.array(candidateType), null); } // index return SAME; @@ -637,26 +637,27 @@ public final class TypeUpdate { InsnArg firstArg = insn.getArg(0); InsnArg secondArg = insn.getArg(1); InsnArg updateArg = firstArg == arg ? secondArg : firstArg; - TypeUpdateResult result = updateTypeChecked(updateInfo, updateArg, candidateType); - if (result == REJECT) { - // soft checks for objects and array - exact type not compared - ArgType updateArgType = updateArg.getType(); - if (candidateType.isObject() && updateArgType.canBeObject()) { - return SAME; - } - if (candidateType.isArray() && updateArgType.canBeArray()) { - return SAME; - } - if (candidateType.isPrimitive()) { - if (updateArgType.canBePrimitive(candidateType.getPrimitiveType())) { + return queueTypeUpdate(updateInfo, updateArg, candidateType, result -> { + if (result == REJECT) { + // soft checks for objects and array - exact type not compared + ArgType updateArgType = updateArg.getType(); + if (candidateType.isObject() && updateArgType.canBeObject()) { return SAME; } - if (updateArgType.isTypeKnown() && candidateType.getRegCount() == updateArgType.getRegCount()) { + if (candidateType.isArray() && updateArgType.canBeArray()) { return SAME; } + if (candidateType.isPrimitive()) { + if (updateArgType.canBePrimitive(candidateType.getPrimitiveType())) { + return SAME; + } + if (updateArgType.isTypeKnown() && candidateType.getRegCount() == updateArgType.getRegCount()) { + return SAME; + } + } } - } - return result; + return result; + }); } private static boolean isAssign(InsnNode insn, InsnArg arg) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java index 7cc23b57d..9d7958157 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateInfo.java @@ -1,14 +1,18 @@ 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 org.jetbrains.annotations.Nullable; + import jadx.api.JadxArgs; import jadx.core.dex.instructions.args.ArgType; import jadx.core.dex.instructions.args.InsnArg; import jadx.core.dex.nodes.MethodNode; +import jadx.core.utils.ListUtils; import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxOverflowException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -17,6 +21,8 @@ public class TypeUpdateInfo { private final MethodNode mth; private final TypeUpdateFlags flags; private final Map updateMap = new IdentityHashMap<>(); + private final List queue = new ArrayList<>(); + private final List callbackQueue = new ArrayList<>(); private final int updatesLimitCount; private int updateSeq = 0; @@ -26,6 +32,24 @@ public class TypeUpdateInfo { this.updatesLimitCount = mth.getInsnsCount() * args.getTypeUpdatesLimitCount(); } + public void queueRequest(TypeUpdateRequest request) { + queue.add(request); + } + + public void saveCallback(TypeUpdateRequest request) { + if (request.getCallback() != null) { + callbackQueue.add(request); + } + } + + public @Nullable TypeUpdateRequest pollNextRequest() { + return ListUtils.pollLast(queue); + } + + public @Nullable TypeUpdateRequest pollNextCallback() { + return ListUtils.pollLast(callbackQueue); + } + public void requestUpdate(InsnArg arg, ArgType changeType) { TypeUpdateEntry prev = updateMap.put(arg, new TypeUpdateEntry(updateSeq++, arg, changeType)); if (prev != null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateRequest.java b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateRequest.java new file mode 100644 index 000000000..fcdf76988 --- /dev/null +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/typeinference/TypeUpdateRequest.java @@ -0,0 +1,41 @@ +package jadx.core.dex.visitors.typeinference; + +import org.jetbrains.annotations.Nullable; + +import jadx.core.dex.instructions.args.ArgType; +import jadx.core.dex.instructions.args.InsnArg; + +public class TypeUpdateRequest { + private final InsnArg arg; + private final ArgType candidateType; + private final boolean direct; + private final @Nullable ITypeUpdateCallback callback; + + public TypeUpdateRequest(InsnArg arg, ArgType candidateType, boolean direct, @Nullable ITypeUpdateCallback callback) { + this.arg = arg; + this.candidateType = candidateType; + this.direct = direct; + this.callback = callback; + } + + public InsnArg getArg() { + return arg; + } + + public ArgType getCandidateType() { + return candidateType; + } + + public boolean isDirect() { + return direct; + } + + public @Nullable ITypeUpdateCallback getCallback() { + return callback; + } + + @Override + public String toString() { + return "TypeUpdateRequest{arg=" + arg + ", candidateType=" + candidateType + '}'; + } +} diff --git a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java index ddc82704b..2fbcf7e73 100644 --- a/jadx-core/src/main/java/jadx/core/utils/ListUtils.java +++ b/jadx-core/src/main/java/jadx/core/utils/ListUtils.java @@ -227,4 +227,15 @@ public class ListUtils { } return list; } + + public static @Nullable T pollLast(List list) { + if (list == null) { + return null; + } + int size = list.size(); + if (size == 0) { + return null; + } + return list.remove(size - 1); + } } diff --git a/jadx-core/src/test/java/jadx/tests/integration/types/TestArrayTypes.java b/jadx-core/src/test/java/jadx/tests/integration/types/TestArrayTypes.java index ac69cddeb..f088302e3 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/types/TestArrayTypes.java +++ b/jadx-core/src/test/java/jadx/tests/integration/types/TestArrayTypes.java @@ -3,10 +3,12 @@ package jadx.tests.integration.types; import org.junit.jupiter.api.Test; import jadx.tests.api.IntegrationTest; -import jadx.tests.api.utils.assertj.JadxAssertions; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; public class TestArrayTypes extends IntegrationTest { + @SuppressWarnings({ "ThrowablePrintedToSystemOut", "unused" }) public static class TestCls { public void test() { @@ -25,7 +27,7 @@ public class TestArrayTypes extends IntegrationTest { @Test public void test() { - JadxAssertions.assertThat(getClassNode(TestCls.class)) + assertThat(getClassNode(TestCls.class)) .code() .containsOne("use(new Object[]{e});"); } @@ -33,7 +35,7 @@ public class TestArrayTypes extends IntegrationTest { @Test public void testNoDebug() { noDebugInfo(); - JadxAssertions.assertThat(getClassNode(TestCls.class)) + assertThat(getClassNode(TestCls.class)) .code() .containsOne("use(new Object[]{exc});"); }