feat: use queue instead of recursion for type updates
This commit is contained in:
+121
@@ -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<T extends InsnArg> implements ITypeUpdateCallback {
|
||||||
|
private final TypeUpdate typeUpdate;
|
||||||
|
private final TypeUpdateInfo updateInfo;
|
||||||
|
private final Iterator<T> argsIterator;
|
||||||
|
private final ArgType candidateType;
|
||||||
|
private final boolean direct;
|
||||||
|
|
||||||
|
private @Nullable Predicate<T> argsFilter;
|
||||||
|
private @Nullable ITypeUpdateCallback finalResultCallback;
|
||||||
|
private boolean ignoreReject = false;
|
||||||
|
|
||||||
|
private boolean allSame = true;
|
||||||
|
private boolean firstQueue = false;
|
||||||
|
|
||||||
|
public ArgsListUpdateCallback(TypeUpdate typeUpdate, TypeUpdateInfo updateInfo,
|
||||||
|
List<T> 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<T> 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<T> it = argsIterator;
|
||||||
|
Predicate<T> 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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
@@ -16,5 +17,6 @@ public interface ITypeListener {
|
|||||||
* @param arg apply suggested type for this arg
|
* @param arg apply suggested type for this arg
|
||||||
* @param candidateType suggest new type
|
* @param candidateType suggest new type
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
TypeUpdateResult update(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, @NotNull ArgType candidateType);
|
TypeUpdateResult update(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, @NotNull ArgType candidateType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
+144
@@ -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<ArgType> knownTypeVars;
|
||||||
|
private final Supplier<ArgType> getReturnType;
|
||||||
|
private final Function<Integer, ArgType> 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<ArgType> knownTypeVars, Supplier<ArgType> getReturnType, Function<Integer, ArgType> 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<ArgType> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
public TypeUpdateResult apply(MethodNode mth, SSAVar ssaVar, ArgType candidateType) {
|
||||||
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_EMPTY);
|
return apply(mth, ssaVar, candidateType, TypeUpdateFlags.FLAGS_EMPTY);
|
||||||
@@ -81,9 +81,14 @@ public final class TypeUpdate {
|
|||||||
if (candidateType == null || !candidateType.isTypeKnown()) {
|
if (candidateType == null || !candidateType.isTypeKnown()) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
|
LOG.debug("Start type update for {} to {}", ssaVar.toShortString(), candidateType);
|
||||||
|
}
|
||||||
TypeUpdateInfo updateInfo = new TypeUpdateInfo(mth, flags, args);
|
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) {
|
if (result == REJECT) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -103,16 +108,88 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult updateTypeChecked(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult runUpdate(TypeUpdateInfo updateInfo) {
|
||||||
if (candidateType == null) {
|
TypeUpdateResult result = REJECT;
|
||||||
throw new JadxRuntimeException("Null type update for arg: " + arg);
|
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);
|
TypeUpdateResult res = verifyType(updateInfo, arg, candidateType);
|
||||||
if (res != null) {
|
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) {
|
if (arg instanceof RegisterArg) {
|
||||||
RegisterArg reg = (RegisterArg) arg;
|
RegisterArg reg = (RegisterArg) arg;
|
||||||
@@ -122,6 +199,12 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable TypeUpdateResult verifyType(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
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();
|
ArgType currentType = arg.getType();
|
||||||
TypeUpdateFlags typeUpdateFlags = updateInfo.getFlags();
|
TypeUpdateFlags typeUpdateFlags = updateInfo.getFlags();
|
||||||
if (Objects.equals(currentType, candidateType)) {
|
if (Objects.equals(currentType, candidateType)) {
|
||||||
@@ -194,27 +277,16 @@ public final class TypeUpdate {
|
|||||||
if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) {
|
if (!inBounds(updateInfo, ssaVar, typeInfo.getBounds(), candidateType)) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
TypeUpdateResult result = requestUpdate(updateInfo, ssaVar.getAssign(), candidateType);
|
var updateCallback = new ArgsListUpdateCallback<>(this, updateInfo, ssaVar.getUseList(), candidateType, true);
|
||||||
boolean allSame = result == SAME;
|
updateCallback.setFinalResultCallback(result -> {
|
||||||
if (result != REJECT) {
|
if (result == REJECT) {
|
||||||
List<RegisterArg> useList = ssaVar.getUseList();
|
// rollback update for all registers in current SSA var
|
||||||
for (RegisterArg arg : useList) {
|
updateInfo.rollbackUpdate(ssaVar.getAssign());
|
||||||
result = requestUpdate(updateInfo, arg, candidateType);
|
ssaVar.getUseList().forEach(updateInfo::rollbackUpdate);
|
||||||
if (result == REJECT) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (result != SAME) {
|
|
||||||
allSame = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
if (result == REJECT) {
|
});
|
||||||
// rollback update for all registers in current SSA var
|
return queueDirectTypeUpdate(updateInfo, ssaVar.getAssign(), candidateType, updateCallback);
|
||||||
updateInfo.rollbackUpdate(ssaVar.getAssign());
|
|
||||||
ssaVar.getUseList().forEach(updateInfo::rollbackUpdate);
|
|
||||||
return REJECT;
|
|
||||||
}
|
|
||||||
return allSame ? SAME : CHANGED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult requestUpdate(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult requestUpdate(TypeUpdateInfo updateInfo, InsnArg arg, ArgType candidateType) {
|
||||||
@@ -222,14 +294,6 @@ public final class TypeUpdate {
|
|||||||
return CHANGED;
|
return CHANGED;
|
||||||
}
|
}
|
||||||
updateInfo.requestUpdate(arg, candidateType);
|
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();
|
InsnNode insn = arg.getParentInsn();
|
||||||
if (insn == null) {
|
if (insn == null) {
|
||||||
return SAME;
|
return SAME;
|
||||||
@@ -238,6 +302,9 @@ public final class TypeUpdate {
|
|||||||
if (listener == null) {
|
if (listener == null) {
|
||||||
return CHANGED;
|
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);
|
return listener.update(updateInfo, insn, arg, candidateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,85 +426,30 @@ public final class TypeUpdate {
|
|||||||
ArgType returnType = methodDetails.getReturnType();
|
ArgType returnType = methodDetails.getReturnType();
|
||||||
List<ArgType> argTypes = methodDetails.getArgTypes();
|
List<ArgType> argTypes = methodDetails.getArgTypes();
|
||||||
int argsCount = argTypes.size();
|
int argsCount = argTypes.size();
|
||||||
|
|
||||||
|
Supplier<ArgType> getReturnType;
|
||||||
|
Function<Integer, ArgType> getArgType;
|
||||||
if (typeVarsMap.isEmpty()) {
|
if (typeVarsMap.isEmpty()) {
|
||||||
// generics can't be resolved => use as is
|
// 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 new InvokeUpdateCallback(this, updateInfo, invoke, argsCount, knownTypeVars, getReturnType, getArgType)
|
||||||
return applyInvokeTypes(updateInfo, invoke, argsCount, knownTypeVars,
|
.runQueue();
|
||||||
() -> typeUtils.replaceTypeVariablesUsingMap(returnType, typeVarsMap),
|
|
||||||
argNum -> typeUtils.replaceClassGenerics(candidateType, argTypes.get(argNum)));
|
|
||||||
}
|
}
|
||||||
return SAME;
|
return SAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult applyInvokeTypes(TypeUpdateInfo updateInfo, BaseInvokeNode invoke, int argsCount,
|
|
||||||
Set<ArgType> knownTypeVars, Supplier<ArgType> getReturnType, Function<Integer, ArgType> 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<ArgType> 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) {
|
private TypeUpdateResult sameFirstArgListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult();
|
InsnArg changeArg = isAssign(insn, arg) ? insn.getArg(0) : insn.getResult();
|
||||||
if (updateInfo.hasUpdateWithType(changeArg, candidateType)) {
|
if (updateInfo.hasUpdateWithType(changeArg, candidateType)) {
|
||||||
return CHANGED;
|
return CHANGED;
|
||||||
}
|
}
|
||||||
return updateTypeChecked(updateInfo, changeArg, candidateType);
|
return queueTypeUpdate(updateInfo, changeArg, candidateType, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult moveListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
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());
|
TypeCompareEnum cmp = comparator.compareTypes(candidateType, changeArg.getType());
|
||||||
boolean correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow());
|
boolean correctType = cmp.isEqual() || (assignChanged ? cmp.isWider() : cmp.isNarrow());
|
||||||
|
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, changeArg, candidateType);
|
return queueTypeUpdate(updateInfo, changeArg, candidateType, result -> {
|
||||||
if (result == SAME && !correctType) {
|
if (result == SAME && !correctType) {
|
||||||
if (Consts.DEBUG_TYPE_INFERENCE) {
|
if (Consts.DEBUG_TYPE_INFERENCE) {
|
||||||
LOG.debug("Move insn types mismatch: {} -> {}, change arg: {}, insn: {}",
|
LOG.debug("Move insn types mismatch: {} -> {}, change arg: {}, insn: {}",
|
||||||
candidateType, changeArg.getType(), changeArg, insn);
|
candidateType, changeArg.getType(), changeArg, insn);
|
||||||
|
}
|
||||||
|
return REJECT;
|
||||||
}
|
}
|
||||||
return REJECT;
|
if (result == REJECT && correctType) {
|
||||||
}
|
return CHANGED;
|
||||||
if (result == REJECT && correctType) {
|
}
|
||||||
return CHANGED;
|
return result;
|
||||||
}
|
});
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -470,22 +483,12 @@ public final class TypeUpdate {
|
|||||||
*/
|
*/
|
||||||
private TypeUpdateResult allSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult allSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
if (!isAssign(insn, arg)) {
|
if (!isAssign(insn, arg)) {
|
||||||
return updateTypeChecked(updateInfo, insn.getResult(), candidateType);
|
return queueTypeUpdate(updateInfo, insn.getResult(), candidateType, null);
|
||||||
}
|
}
|
||||||
boolean allSame = true;
|
// update args with same type
|
||||||
for (InsnArg insnArg : insn.getArguments()) {
|
var updateCallback = new ArgsListUpdateCallback<>(this, updateInfo, insn.getArgList(), candidateType, false);
|
||||||
if (insnArg == arg) {
|
updateCallback.setArgsFilter(a -> a != arg);
|
||||||
continue;
|
return updateCallback.runFirstQueue();
|
||||||
}
|
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
|
||||||
if (result == REJECT) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (result != SAME) {
|
|
||||||
allSame = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allSame ? SAME : CHANGED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult arithListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
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
|
* Try to set candidate type to all args, don't fail on reject
|
||||||
*/
|
*/
|
||||||
private TypeUpdateResult suggestAllSameListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
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)) {
|
if (!isAssign(insn, arg)) {
|
||||||
RegisterArg resultArg = insn.getResult();
|
RegisterArg resultArg = insn.getResult();
|
||||||
if (resultArg != null) {
|
if (resultArg != null) {
|
||||||
updateTypeChecked(updateInfo, resultArg, candidateType);
|
// start with result
|
||||||
|
return queueTypeUpdate(updateInfo, resultArg, candidateType, updateCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
boolean allSame = true;
|
// start with first arg
|
||||||
for (InsnArg insnArg : insn.getArguments()) {
|
return updateCallback.runFirstQueue();
|
||||||
if (insnArg != arg) {
|
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
|
||||||
if (result == REJECT) {
|
|
||||||
// ignore
|
|
||||||
} else if (result != SAME) {
|
|
||||||
allSame = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allSame ? SAME : CHANGED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TypeUpdateResult checkCastListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
private TypeUpdateResult checkCastListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
IndexInsnNode checkCast = (IndexInsnNode) insn;
|
IndexInsnNode checkCast = (IndexInsnNode) insn;
|
||||||
if (isAssign(insn, arg)) {
|
if (isAssign(insn, arg)) {
|
||||||
InsnArg insnArg = insn.getArg(0);
|
InsnArg insnArg = insn.getArg(0);
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, insnArg, candidateType);
|
return queueTypeUpdate(updateInfo, insnArg, candidateType,
|
||||||
return result == REJECT ? SAME : result;
|
r -> r == REJECT ? SAME : r);
|
||||||
}
|
}
|
||||||
ArgType castType = (ArgType) checkCast.getIndex();
|
ArgType castType = (ArgType) checkCast.getIndex();
|
||||||
TypeCompareEnum res = comparator.compareTypes(candidateType, castType);
|
TypeCompareEnum res = comparator.compareTypes(candidateType, castType);
|
||||||
@@ -543,7 +540,7 @@ public final class TypeUpdate {
|
|||||||
}
|
}
|
||||||
if (res == TypeCompareEnum.NARROW_BY_GENERIC && candidateType.containsGeneric()) {
|
if (res == TypeCompareEnum.NARROW_BY_GENERIC && candidateType.containsGeneric()) {
|
||||||
// propagate generic type to result
|
// propagate generic type to result
|
||||||
return updateTypeChecked(updateInfo, checkCast.getResult(), candidateType);
|
return queueTypeUpdate(updateInfo, checkCast.getResult(), candidateType, null);
|
||||||
}
|
}
|
||||||
ArgType currentType = checkCast.getArg(0).getType();
|
ArgType currentType = checkCast.getArg(0).getType();
|
||||||
return candidateType.equals(currentType) ? SAME : CHANGED;
|
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) {
|
private TypeUpdateResult arrayGetListener(TypeUpdateInfo updateInfo, InsnNode insn, InsnArg arg, ArgType candidateType) {
|
||||||
if (isAssign(insn, arg)) {
|
if (isAssign(insn, arg)) {
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getArg(0), ArgType.array(candidateType));
|
return queueTypeUpdate(updateInfo, insn.getArg(0), ArgType.array(candidateType), result -> {
|
||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
ArgType arrType = insn.getArg(0).getType();
|
ArgType arrType = insn.getArg(0).getType();
|
||||||
if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) {
|
if (arrType.isTypeKnown() && arrType.isArray() && arrType.getArrayElement().isPrimitive()) {
|
||||||
TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement());
|
TypeCompareEnum compResult = comparator.compareTypes(candidateType, arrType.getArrayElement());
|
||||||
if (compResult == TypeCompareEnum.WIDER) {
|
if (compResult == TypeCompareEnum.WIDER) {
|
||||||
// allow implicit upcast for primitive types (int a = byteArr[n])
|
// allow implicit upcast for primitive types (int a = byteArr[n])
|
||||||
return CHANGED;
|
return CHANGED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
});
|
||||||
}
|
}
|
||||||
InsnArg arrArg = insn.getArg(0);
|
InsnArg arrArg = insn.getArg(0);
|
||||||
if (arrArg == arg) {
|
if (arrArg == arg) {
|
||||||
@@ -588,18 +586,19 @@ public final class TypeUpdate {
|
|||||||
if (arrayElement == null) {
|
if (arrayElement == null) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, insn.getResult(), arrayElement);
|
return queueTypeUpdate(updateInfo, insn.getResult(), arrayElement, result -> {
|
||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
ArgType resType = insn.getResult().getType();
|
ArgType resType = insn.getResult().getType();
|
||||||
if (resType.isTypeKnown() && resType.isPrimitive()) {
|
if (resType.isTypeKnown() && resType.isPrimitive()) {
|
||||||
TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement);
|
TypeCompareEnum compResult = comparator.compareTypes(resType, arrayElement);
|
||||||
if (compResult == TypeCompareEnum.WIDER) {
|
if (compResult == TypeCompareEnum.WIDER) {
|
||||||
// allow implicit upcast for primitive types (int a = byteArr[n])
|
// allow implicit upcast for primitive types (int a = byteArr[n])
|
||||||
return CHANGED;
|
return CHANGED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
});
|
||||||
}
|
}
|
||||||
// index argument
|
// index argument
|
||||||
return SAME;
|
return SAME;
|
||||||
@@ -613,21 +612,22 @@ public final class TypeUpdate {
|
|||||||
if (arrayElement == null) {
|
if (arrayElement == null) {
|
||||||
return REJECT;
|
return REJECT;
|
||||||
}
|
}
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, putArg, arrayElement);
|
return queueTypeUpdate(updateInfo, putArg, arrayElement, result -> {
|
||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
ArgType putType = putArg.getType();
|
ArgType putType = putArg.getType();
|
||||||
if (putType.isTypeKnown()) {
|
if (putType.isTypeKnown()) {
|
||||||
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
|
TypeCompareEnum compResult = comparator.compareTypes(arrayElement, putType);
|
||||||
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
if (compResult == TypeCompareEnum.WIDER || compResult == TypeCompareEnum.WIDER_BY_GENERIC) {
|
||||||
// allow wider result (i.e. allow put any objects in Object[] or byte in int[])
|
// allow wider result (i.e. allow put any objects in Object[] or byte in int[])
|
||||||
return CHANGED;
|
return CHANGED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return result;
|
||||||
return result;
|
});
|
||||||
}
|
}
|
||||||
if (arrArg == putArg) {
|
if (arrArg == putArg) {
|
||||||
return updateTypeChecked(updateInfo, arrArg, ArgType.array(candidateType));
|
return queueTypeUpdate(updateInfo, arrArg, ArgType.array(candidateType), null);
|
||||||
}
|
}
|
||||||
// index
|
// index
|
||||||
return SAME;
|
return SAME;
|
||||||
@@ -637,26 +637,27 @@ public final class TypeUpdate {
|
|||||||
InsnArg firstArg = insn.getArg(0);
|
InsnArg firstArg = insn.getArg(0);
|
||||||
InsnArg secondArg = insn.getArg(1);
|
InsnArg secondArg = insn.getArg(1);
|
||||||
InsnArg updateArg = firstArg == arg ? secondArg : firstArg;
|
InsnArg updateArg = firstArg == arg ? secondArg : firstArg;
|
||||||
TypeUpdateResult result = updateTypeChecked(updateInfo, updateArg, candidateType);
|
return queueTypeUpdate(updateInfo, updateArg, candidateType, result -> {
|
||||||
if (result == REJECT) {
|
if (result == REJECT) {
|
||||||
// soft checks for objects and array - exact type not compared
|
// soft checks for objects and array - exact type not compared
|
||||||
ArgType updateArgType = updateArg.getType();
|
ArgType updateArgType = updateArg.getType();
|
||||||
if (candidateType.isObject() && updateArgType.canBeObject()) {
|
if (candidateType.isObject() && updateArgType.canBeObject()) {
|
||||||
return SAME;
|
|
||||||
}
|
|
||||||
if (candidateType.isArray() && updateArgType.canBeArray()) {
|
|
||||||
return SAME;
|
|
||||||
}
|
|
||||||
if (candidateType.isPrimitive()) {
|
|
||||||
if (updateArgType.canBePrimitive(candidateType.getPrimitiveType())) {
|
|
||||||
return SAME;
|
return SAME;
|
||||||
}
|
}
|
||||||
if (updateArgType.isTypeKnown() && candidateType.getRegCount() == updateArgType.getRegCount()) {
|
if (candidateType.isArray() && updateArgType.canBeArray()) {
|
||||||
return SAME;
|
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) {
|
private static boolean isAssign(InsnNode insn, InsnArg arg) {
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
package jadx.core.dex.visitors.typeinference;
|
package jadx.core.dex.visitors.typeinference;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import jadx.api.JadxArgs;
|
import jadx.api.JadxArgs;
|
||||||
import jadx.core.dex.instructions.args.ArgType;
|
import jadx.core.dex.instructions.args.ArgType;
|
||||||
import jadx.core.dex.instructions.args.InsnArg;
|
import jadx.core.dex.instructions.args.InsnArg;
|
||||||
import jadx.core.dex.nodes.MethodNode;
|
import jadx.core.dex.nodes.MethodNode;
|
||||||
|
import jadx.core.utils.ListUtils;
|
||||||
import jadx.core.utils.Utils;
|
import jadx.core.utils.Utils;
|
||||||
import jadx.core.utils.exceptions.JadxOverflowException;
|
import jadx.core.utils.exceptions.JadxOverflowException;
|
||||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||||
@@ -17,6 +21,8 @@ public class TypeUpdateInfo {
|
|||||||
private final MethodNode mth;
|
private final MethodNode mth;
|
||||||
private final TypeUpdateFlags flags;
|
private final TypeUpdateFlags flags;
|
||||||
private final Map<InsnArg, TypeUpdateEntry> updateMap = new IdentityHashMap<>();
|
private final Map<InsnArg, TypeUpdateEntry> updateMap = new IdentityHashMap<>();
|
||||||
|
private final List<TypeUpdateRequest> queue = new ArrayList<>();
|
||||||
|
private final List<TypeUpdateRequest> callbackQueue = new ArrayList<>();
|
||||||
private final int updatesLimitCount;
|
private final int updatesLimitCount;
|
||||||
private int updateSeq = 0;
|
private int updateSeq = 0;
|
||||||
|
|
||||||
@@ -26,6 +32,24 @@ public class TypeUpdateInfo {
|
|||||||
this.updatesLimitCount = mth.getInsnsCount() * args.getTypeUpdatesLimitCount();
|
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) {
|
public void requestUpdate(InsnArg arg, ArgType changeType) {
|
||||||
TypeUpdateEntry prev = updateMap.put(arg, new TypeUpdateEntry(updateSeq++, arg, changeType));
|
TypeUpdateEntry prev = updateMap.put(arg, new TypeUpdateEntry(updateSeq++, arg, changeType));
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
|
|||||||
@@ -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 + '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -227,4 +227,15 @@ public class ListUtils {
|
|||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> @Nullable T pollLast(List<T> list) {
|
||||||
|
if (list == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int size = list.size();
|
||||||
|
if (size == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return list.remove(size - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package jadx.tests.integration.types;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jadx.tests.api.IntegrationTest;
|
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 {
|
public class TestArrayTypes extends IntegrationTest {
|
||||||
|
|
||||||
|
@SuppressWarnings({ "ThrowablePrintedToSystemOut", "unused" })
|
||||||
public static class TestCls {
|
public static class TestCls {
|
||||||
|
|
||||||
public void test() {
|
public void test() {
|
||||||
@@ -25,7 +27,7 @@ public class TestArrayTypes extends IntegrationTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void test() {
|
||||||
JadxAssertions.assertThat(getClassNode(TestCls.class))
|
assertThat(getClassNode(TestCls.class))
|
||||||
.code()
|
.code()
|
||||||
.containsOne("use(new Object[]{e});");
|
.containsOne("use(new Object[]{e});");
|
||||||
}
|
}
|
||||||
@@ -33,7 +35,7 @@ public class TestArrayTypes extends IntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testNoDebug() {
|
public void testNoDebug() {
|
||||||
noDebugInfo();
|
noDebugInfo();
|
||||||
JadxAssertions.assertThat(getClassNode(TestCls.class))
|
assertThat(getClassNode(TestCls.class))
|
||||||
.code()
|
.code()
|
||||||
.containsOne("use(new Object[]{exc});");
|
.containsOne("use(new Object[]{exc});");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user