mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Bytecode analysis: basic return value tracking
This commit is contained in:
+3
-3
@@ -332,12 +332,12 @@ public class BytecodeAnalysisConverter {
|
||||
return contractClauses;
|
||||
}
|
||||
|
||||
public static void addEffectAnnotations(Map<EKey, Set<EffectQuantum>> puritySolutions,
|
||||
public static void addEffectAnnotations(Map<EKey, Effects> puritySolutions,
|
||||
MethodAnnotations result,
|
||||
EKey methodKey,
|
||||
boolean constructor) {
|
||||
for (Map.Entry<EKey, Set<EffectQuantum>> entry : puritySolutions.entrySet()) {
|
||||
Set<EffectQuantum> effects = entry.getValue();
|
||||
for (Map.Entry<EKey, Effects> entry : puritySolutions.entrySet()) {
|
||||
Set<EffectQuantum> effects = entry.getValue().effects;
|
||||
EKey key = entry.getKey().mkStable();
|
||||
EKey baseKey = key.mkBase();
|
||||
if (!methodKey.equals(baseKey)) {
|
||||
|
||||
+117
-90
@@ -53,7 +53,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
private static final ID<HMethod, Void> NAME = ID.create("bytecodeAnalysis");
|
||||
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
|
||||
|
||||
private static final int VERSION = 4; // change when inference algorithm changes
|
||||
private static final int VERSION = 5; // change when inference algorithm changes
|
||||
private static final int VERSION_MODIFIER = HardCodedPurity.AGGRESSIVE_HARDCODED_PURITY ? 1 : 0;
|
||||
private static final int FINAL_VERSION = VERSION * 2 + VERSION_MODIFIER;
|
||||
|
||||
@@ -202,10 +202,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
EKey[] ids = component.ids;
|
||||
DataInputOutputUtil.writeINT(out, ids.length);
|
||||
for (EKey hKey : ids) {
|
||||
out.write(hKey.method.hashed(md).myBytes);
|
||||
int rawDirKey = hKey.negated ? -hKey.dirKey : hKey.dirKey;
|
||||
DataInputOutputUtil.writeINT(out, rawDirKey);
|
||||
out.writeBoolean(hKey.stable);
|
||||
writeKey(out, hKey, md);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,45 +210,9 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
Effects effects = (Effects)rhs;
|
||||
DataInputOutputUtil.writeINT(out, effects.effects.size());
|
||||
for (EffectQuantum effect : effects.effects) {
|
||||
if (effect == EffectQuantum.TopEffectQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -1);
|
||||
}
|
||||
else if (effect == EffectQuantum.ThisChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -2);
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -3);
|
||||
EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect;
|
||||
out.write(callQuantum.key.method.hashed(md).myBytes);
|
||||
DataInputOutputUtil.writeINT(out, callQuantum.key.dirKey);
|
||||
out.writeBoolean(callQuantum.key.stable);
|
||||
out.writeBoolean(callQuantum.isStatic);
|
||||
DataInputOutputUtil.writeINT(out, callQuantum.data.length);
|
||||
for (DataValue dataValue : callQuantum.data) {
|
||||
if (dataValue == DataValue.ThisDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -1);
|
||||
}
|
||||
else if (dataValue == DataValue.LocalDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -2);
|
||||
}
|
||||
else if (dataValue == DataValue.OwnedDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -3);
|
||||
}
|
||||
else if (dataValue == DataValue.UnknownDataValue1) {
|
||||
DataInputOutputUtil.writeINT(out, -4);
|
||||
}
|
||||
else if (dataValue == DataValue.UnknownDataValue2) {
|
||||
DataInputOutputUtil.writeINT(out, -5);
|
||||
}
|
||||
else if (dataValue instanceof DataValue.ParameterDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, ((DataValue.ParameterDataValue)dataValue).n);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, ((EffectQuantum.ParamChangeQuantum)effect).n);
|
||||
}
|
||||
writeEffect(out, effect, md);
|
||||
}
|
||||
writeDataValue(out, effects.returnValue, md);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,50 +228,10 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
Set<EffectQuantum> effects = new HashSet<>();
|
||||
int effectsSize = DataInputOutputUtil.readINT(in);
|
||||
for (int i = 0; i < effectsSize; i++) {
|
||||
int effectMask = DataInputOutputUtil.readINT(in);
|
||||
if (effectMask == -1) {
|
||||
effects.add(EffectQuantum.TopEffectQuantum);
|
||||
}
|
||||
else if (effectMask == -2) {
|
||||
effects.add(EffectQuantum.ThisChangeQuantum);
|
||||
}
|
||||
else if (effectMask == -3){
|
||||
byte[] bytes = new byte[HMethod.HASH_SIZE];
|
||||
in.readFully(bytes);
|
||||
int rawDirKey = DataInputOutputUtil.readINT(in);
|
||||
boolean isStable = in.readBoolean();
|
||||
EKey key = new EKey(new HMethod(bytes), Math.abs(rawDirKey), isStable, false);
|
||||
boolean isStatic = in.readBoolean();
|
||||
int dataLength = DataInputOutputUtil.readINT(in);
|
||||
DataValue[] data = new DataValue[dataLength];
|
||||
for (int di = 0; di < dataLength; di++) {
|
||||
int dataI = DataInputOutputUtil.readINT(in);
|
||||
if (dataI == -1) {
|
||||
data[di] = DataValue.ThisDataValue;
|
||||
}
|
||||
else if (dataI == -2) {
|
||||
data[di] = DataValue.LocalDataValue;
|
||||
}
|
||||
else if (dataI == -3) {
|
||||
data[di] = DataValue.OwnedDataValue;
|
||||
}
|
||||
else if (dataI == -4) {
|
||||
data[di] = DataValue.UnknownDataValue1;
|
||||
}
|
||||
else if (dataI == -5) {
|
||||
data[di] = DataValue.UnknownDataValue2;
|
||||
}
|
||||
else {
|
||||
data[di] = new DataValue.ParameterDataValue(dataI);
|
||||
}
|
||||
}
|
||||
effects.add(new EffectQuantum.CallQuantum(key, data, isStatic));
|
||||
}
|
||||
else {
|
||||
effects.add(new EffectQuantum.ParamChangeQuantum(effectMask));
|
||||
}
|
||||
effects.add(readEffect(in));
|
||||
}
|
||||
results.add(new DirectionResultPair(directionKey, new Effects(effects)));
|
||||
DataValue returnValue = readDataValue(in);
|
||||
results.add(new DirectionResultPair(directionKey, new Effects(returnValue, effects)));
|
||||
}
|
||||
else {
|
||||
boolean isFinal = in.readBoolean(); // flag
|
||||
@@ -329,10 +250,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
int componentSize = DataInputOutputUtil.readINT(in);
|
||||
EKey[] ids = new EKey[componentSize];
|
||||
for (int j = 0; j < componentSize; j++) {
|
||||
byte[] bytes = new byte[HMethod.HASH_SIZE];
|
||||
in.readFully(bytes);
|
||||
int rawDirKey = DataInputOutputUtil.readINT(in);
|
||||
ids[j] = new EKey(new HMethod(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0);
|
||||
ids[j] = readKey(in);
|
||||
}
|
||||
components[i] = new Component(value, ids);
|
||||
}
|
||||
@@ -342,5 +260,114 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
|
||||
}
|
||||
return new Equations(results, stable);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static EKey readKey(@NotNull DataInput in) throws IOException {
|
||||
byte[] bytes = new byte[HMethod.HASH_SIZE];
|
||||
in.readFully(bytes);
|
||||
int rawDirKey = DataInputOutputUtil.readINT(in);
|
||||
return new EKey(new HMethod(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0);
|
||||
}
|
||||
|
||||
private static void writeKey(@NotNull DataOutput out, EKey key, MessageDigest md) throws IOException {
|
||||
out.write(key.method.hashed(md).myBytes);
|
||||
int rawDirKey = key.negated ? -key.dirKey : key.dirKey;
|
||||
DataInputOutputUtil.writeINT(out, rawDirKey);
|
||||
out.writeBoolean(key.stable);
|
||||
}
|
||||
|
||||
private static void writeEffect(@NotNull DataOutput out, EffectQuantum effect, MessageDigest md) throws IOException {
|
||||
if (effect == EffectQuantum.TopEffectQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -1);
|
||||
}
|
||||
else if (effect == EffectQuantum.ThisChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -2);
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -3);
|
||||
EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect;
|
||||
writeKey(out, callQuantum.key, md);
|
||||
out.writeBoolean(callQuantum.isStatic);
|
||||
DataInputOutputUtil.writeINT(out, callQuantum.data.length);
|
||||
for (DataValue dataValue : callQuantum.data) {
|
||||
writeDataValue(out, dataValue, md);
|
||||
}
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.ReturnChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, -4);
|
||||
writeKey(out, ((EffectQuantum.ReturnChangeQuantum)effect).key, md);
|
||||
}
|
||||
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
|
||||
DataInputOutputUtil.writeINT(out, ((EffectQuantum.ParamChangeQuantum)effect).n);
|
||||
}
|
||||
}
|
||||
|
||||
private static EffectQuantum readEffect(@NotNull DataInput in) throws IOException {
|
||||
int effectMask = DataInputOutputUtil.readINT(in);
|
||||
switch (effectMask) {
|
||||
case -1:
|
||||
return EffectQuantum.TopEffectQuantum;
|
||||
case -2:
|
||||
return EffectQuantum.ThisChangeQuantum;
|
||||
case -3:
|
||||
EKey key = readKey(in);
|
||||
boolean isStatic = in.readBoolean();
|
||||
int dataLength = DataInputOutputUtil.readINT(in);
|
||||
DataValue[] data = new DataValue[dataLength];
|
||||
for (int di = 0; di < dataLength; di++) {
|
||||
data[di] = readDataValue(in);
|
||||
}
|
||||
return new EffectQuantum.CallQuantum(key, data, isStatic);
|
||||
case -4:
|
||||
return new EffectQuantum.ReturnChangeQuantum(readKey(in));
|
||||
default:
|
||||
return new EffectQuantum.ParamChangeQuantum(effectMask);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeDataValue(@NotNull DataOutput out, DataValue dataValue, MessageDigest md) throws IOException {
|
||||
if (dataValue == DataValue.ThisDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -1);
|
||||
}
|
||||
else if (dataValue == DataValue.LocalDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -2);
|
||||
}
|
||||
else if (dataValue == DataValue.OwnedDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -3);
|
||||
}
|
||||
else if (dataValue == DataValue.UnknownDataValue1) {
|
||||
DataInputOutputUtil.writeINT(out, -4);
|
||||
}
|
||||
else if (dataValue == DataValue.UnknownDataValue2) {
|
||||
DataInputOutputUtil.writeINT(out, -5);
|
||||
}
|
||||
else if (dataValue instanceof DataValue.ReturnDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, -6);
|
||||
writeKey(out, ((DataValue.ReturnDataValue)dataValue).key, md);
|
||||
}
|
||||
else if (dataValue instanceof DataValue.ParameterDataValue) {
|
||||
DataInputOutputUtil.writeINT(out, ((DataValue.ParameterDataValue)dataValue).n);
|
||||
}
|
||||
}
|
||||
|
||||
private static DataValue readDataValue(@NotNull DataInput in) throws IOException {
|
||||
int dataI = DataInputOutputUtil.readINT(in);
|
||||
switch (dataI) {
|
||||
case -1:
|
||||
return DataValue.ThisDataValue;
|
||||
case -2:
|
||||
return DataValue.LocalDataValue;
|
||||
case -3:
|
||||
return DataValue.OwnedDataValue;
|
||||
case -4:
|
||||
return DataValue.UnknownDataValue1;
|
||||
case -5:
|
||||
return DataValue.UnknownDataValue2;
|
||||
case -6:
|
||||
return new DataValue.ReturnDataValue(readKey(in));
|
||||
default:
|
||||
return new DataValue.ParameterDataValue(dataI);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -95,7 +95,8 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
|
||||
|
||||
private static Result hash(Result result, MessageDigest md) {
|
||||
if(result instanceof Effects) {
|
||||
return new Effects(StreamEx.of(((Effects)result).effects).map(effect -> hash(effect, md)).toSet());
|
||||
Effects effects = (Effects)result;
|
||||
return new Effects(effects.returnValue, StreamEx.of(effects.effects).map(effect -> hash(effect, md)).toSet());
|
||||
} else if(result instanceof Pending) {
|
||||
return new Pending(ContainerUtil.map(((Pending)result).delta, component -> hash(component, md)));
|
||||
}
|
||||
@@ -513,6 +514,9 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if(ourTotalCount.get() == 0) {
|
||||
return "";
|
||||
}
|
||||
return String.format(Locale.ENGLISH, "Classes: %d\nBytes: %d\nBytes per class: %.2f%n", ourTotalCount.get(), ourTotalSize.get(),
|
||||
((double)ourTotalSize.get()) / ourTotalCount.get());
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Represents a lattice product of a constant {@link #value} and all {@link #ids}.
|
||||
@@ -226,26 +227,33 @@ final class Pending implements Result {
|
||||
}
|
||||
|
||||
final class Effects implements Result {
|
||||
@NotNull final DataValue returnValue;
|
||||
@NotNull final Set<EffectQuantum> effects;
|
||||
|
||||
Effects(@NotNull Set<EffectQuantum> effects) {
|
||||
Effects(@NotNull DataValue returnValue, @NotNull Set<EffectQuantum> effects) {
|
||||
this.returnValue = returnValue;
|
||||
this.effects = effects;
|
||||
}
|
||||
|
||||
Stream<EKey> dependencies() {
|
||||
return Stream.concat(returnValue.dependencies(), effects.stream().flatMap(EffectQuantum::dependencies));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
return this.effects.equals(((Effects)o).effects);
|
||||
Effects that = (Effects)o;
|
||||
return this.returnValue.equals(that.returnValue) && this.effects.equals(that.effects);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return effects.hashCode();
|
||||
return effects.hashCode() * 31 + returnValue.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Effects["+effects.size()+"]";
|
||||
return "Effects[" + effects.size() + "|" + returnValue + "]";
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -66,8 +66,17 @@ class HardCodedPurity {
|
||||
return AGGRESSIVE_HARDCODED_PURITY ? new AggressiveHardCodedPurity() : new HardCodedPurity();
|
||||
}
|
||||
|
||||
Set<EffectQuantum> getHardCodedSolution(Method method) {
|
||||
return isThisChangingMethod(method) ? thisChange : isPureMethod(method) ? Collections.emptySet() : solutions.get(method);
|
||||
Effects getHardCodedSolution(Method method) {
|
||||
if (isThisChangingMethod(method)) {
|
||||
return new Effects(isBuilderChainCall(method) ? DataValue.ThisDataValue : DataValue.UnknownDataValue1, thisChange);
|
||||
}
|
||||
else if (isPureMethod(method)) {
|
||||
return new Effects(DataValue.LocalDataValue, Collections.emptySet());
|
||||
}
|
||||
else {
|
||||
Set<EffectQuantum> effects = solutions.get(method);
|
||||
return effects == null ? null : new Effects(DataValue.UnknownDataValue1, effects);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isThisChangingMethod(Method method) {
|
||||
|
||||
+21
-15
@@ -27,11 +27,10 @@ import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.compiled.ClsClassImpl;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.psi.util.PsiModificationTracker;
|
||||
import com.intellij.psi.util.*;
|
||||
import com.intellij.testFramework.LightVirtualFile;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap;
|
||||
@@ -303,7 +302,7 @@ public class ProjectBytecodeAnalysis {
|
||||
final PuritySolver puritySolver = new PuritySolver();
|
||||
collectPurityEquations(key.withDirection(Pure), puritySolver);
|
||||
|
||||
Map<EKey, Set<EffectQuantum>> puritySolutions = puritySolver.solve();
|
||||
Map<EKey, Effects> puritySolutions = puritySolver.solve();
|
||||
|
||||
int arity = owner.getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addEffectAnnotations(puritySolutions, result, key, owner.isConstructor());
|
||||
@@ -358,17 +357,14 @@ public class ProjectBytecodeAnalysis {
|
||||
for (DirectionResultPair pair : equations.results) {
|
||||
int dirKey = pair.directionKey;
|
||||
if (dirKey == hKey.dirKey) {
|
||||
Set<EffectQuantum> effects = ((Effects)pair.result).effects;
|
||||
Effects effects = (Effects)pair.result;
|
||||
puritySolver.addEquation(new EKey(hKey.method, dirKey, stable, false), effects);
|
||||
for (EffectQuantum effect : effects) {
|
||||
if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
EKey depKey = ((EffectQuantum.CallQuantum)effect).key;
|
||||
if (!queued.contains(depKey)) {
|
||||
queue.push(depKey);
|
||||
queued.add(depKey);
|
||||
}
|
||||
effects.dependencies().forEach(depKey -> {
|
||||
if (!queued.contains(depKey)) {
|
||||
queue.push(depKey);
|
||||
queued.add(depKey);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -480,7 +476,17 @@ public class ProjectBytecodeAnalysis {
|
||||
String packageName = StringUtil.getPackageName(internalClassName, '/').replace('/', '.');
|
||||
String className = StringUtil.getShortName(internalClassName, '/');
|
||||
PsiPackage aPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName);
|
||||
if (aPackage == null) return null;
|
||||
if (aPackage == null) {
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(myProject).findClass(StringUtil.getQualifiedName(packageName, className), GlobalSearchScope
|
||||
.allScope(myProject));
|
||||
if(psiClass != null) {
|
||||
PsiModifierListOwner compiledClass = PsiUtil.preferCompiledElement(psiClass);
|
||||
if(compiledClass instanceof ClsClassImpl) {
|
||||
return compiledClass.getContainingFile().getVirtualFile();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
String classFileName = className + ".class";
|
||||
for (PsiDirectory directory : aPackage.getDirectories()) {
|
||||
VirtualFile file = directory.getVirtualFile().findChild(classFileName);
|
||||
|
||||
+192
-82
@@ -26,6 +26,8 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Produces equations for inference of @Contract(pure=true) annotations.
|
||||
@@ -44,9 +46,9 @@ public class PurityAnalysis {
|
||||
@Nullable
|
||||
public static Equation analyze(Method method, MethodNode methodNode, boolean stable) {
|
||||
EKey key = new EKey(method, Direction.Pure, stable);
|
||||
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getInstance().getHardCodedSolution(method);
|
||||
Effects hardCodedSolution = HardCodedPurity.getInstance().getHardCodedSolution(method);
|
||||
if (hardCodedSolution != null) {
|
||||
return new Equation(key, new Effects(hardCodedSolution));
|
||||
return new Equation(key, hardCodedSolution);
|
||||
}
|
||||
|
||||
if ((methodNode.access & UN_ANALYZABLE_FLAG) != 0) return null;
|
||||
@@ -59,14 +61,17 @@ public class PurityAnalysis {
|
||||
return null;
|
||||
}
|
||||
EffectQuantum[] quanta = dataInterpreter.effects;
|
||||
DataValue returnValue = dataInterpreter.returnValue == null ? DataValue.UnknownDataValue1 : dataInterpreter.returnValue;
|
||||
Set<EffectQuantum> effects = new HashSet<>();
|
||||
for (EffectQuantum effectQuantum : quanta) {
|
||||
if (effectQuantum != null) {
|
||||
if (effectQuantum == EffectQuantum.TopEffectQuantum) return null;
|
||||
if (effectQuantum == EffectQuantum.TopEffectQuantum) {
|
||||
return returnValue == DataValue.UnknownDataValue1 ? null : new Equation(key, new Effects(returnValue, PuritySolver.TOP_EFFECTS));
|
||||
}
|
||||
effects.add(effectQuantum);
|
||||
}
|
||||
}
|
||||
return new Equation(key, new Effects(effects));
|
||||
return new Equation(key, new Effects(returnValue, effects));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +88,10 @@ abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysi
|
||||
return myHash;
|
||||
}
|
||||
|
||||
Stream<EKey> dependencies() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
static final DataValue ThisDataValue = new DataValue(-1) {
|
||||
@Override
|
||||
public int getSize() {
|
||||
@@ -123,8 +132,7 @@ abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysi
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ParameterDataValue that = (ParameterDataValue)o;
|
||||
if (n != that.n) return false;
|
||||
return true;
|
||||
return n == that.n;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,6 +140,37 @@ abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysi
|
||||
return "DataValue: arg#" + n;
|
||||
}
|
||||
}
|
||||
static class ReturnDataValue extends DataValue {
|
||||
final EKey key;
|
||||
|
||||
ReturnDataValue(EKey key) {
|
||||
super(key.hashCode());
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ReturnDataValue that = (ReturnDataValue)o;
|
||||
return key.equals(that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
Stream<EKey> dependencies() {
|
||||
return Stream.of(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Return of: " + key;
|
||||
}
|
||||
}
|
||||
static final DataValue OwnedDataValue = new DataValue(-3) {
|
||||
@Override
|
||||
public int getSize() {
|
||||
@@ -174,6 +213,10 @@ abstract class EffectQuantum {
|
||||
myHash = hash;
|
||||
}
|
||||
|
||||
Stream<EKey> dependencies() {
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return myHash;
|
||||
@@ -191,7 +234,32 @@ abstract class EffectQuantum {
|
||||
return "Changes this";
|
||||
}
|
||||
};
|
||||
static class ParamChangeQuantum extends EffectQuantum {
|
||||
|
||||
static final class ReturnChangeQuantum extends EffectQuantum {
|
||||
final EKey key;
|
||||
public ReturnChangeQuantum(EKey key) {
|
||||
super(key.hashCode());
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
Stream<EKey> dependencies() {
|
||||
return Stream.of(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
return o != null && getClass() == o.getClass() && key == ((ReturnChangeQuantum)o).key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Changes return value of " + key;
|
||||
}
|
||||
}
|
||||
|
||||
static final class ParamChangeQuantum extends EffectQuantum {
|
||||
final int n;
|
||||
public ParamChangeQuantum(int n) {
|
||||
super(n);
|
||||
@@ -201,13 +269,7 @@ abstract class EffectQuantum {
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ParamChangeQuantum that = (ParamChangeQuantum)o;
|
||||
|
||||
if (n != that.n) return false;
|
||||
|
||||
return true;
|
||||
return o != null && getClass() == o.getClass() && n == ((ParamChangeQuantum)o).n;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,7 +277,8 @@ abstract class EffectQuantum {
|
||||
return "Changes param#" + n;
|
||||
}
|
||||
}
|
||||
static class CallQuantum extends EffectQuantum {
|
||||
|
||||
static final class CallQuantum extends EffectQuantum {
|
||||
final EKey key;
|
||||
final DataValue[] data;
|
||||
final boolean isStatic;
|
||||
@@ -235,12 +298,15 @@ abstract class EffectQuantum {
|
||||
|
||||
if (isStatic != that.isStatic) return false;
|
||||
if (!key.equals(that.key)) return false;
|
||||
// Probably incorrect - comparing Object[] arrays with Arrays.equals
|
||||
if (!Arrays.equals(data, that.data)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
Stream<EKey> dependencies() {
|
||||
return Stream.concat(Stream.of(key), Stream.of(data).flatMap(DataValue::dependencies));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Calls " + key;
|
||||
@@ -256,6 +322,7 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
final int rangeEnd;
|
||||
final int arity;
|
||||
final EffectQuantum[] effects;
|
||||
DataValue returnValue = null;
|
||||
|
||||
protected DataInterpreter(MethodNode methodNode) {
|
||||
super(Opcodes.API_VERSION);
|
||||
@@ -336,16 +403,7 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
case Opcodes.LXOR:
|
||||
return DataValue.UnknownDataValue2;
|
||||
case Opcodes.PUTFIELD:
|
||||
final EffectQuantum effectQuantum;
|
||||
if (value1 == DataValue.ThisDataValue || value1 == DataValue.OwnedDataValue) {
|
||||
effectQuantum = EffectQuantum.ThisChangeQuantum;
|
||||
} else if (value1 == DataValue.LocalDataValue) {
|
||||
effectQuantum = null;
|
||||
} else if (value1 instanceof DataValue.ParameterDataValue) {
|
||||
effectQuantum = new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)value1).n);
|
||||
} else {
|
||||
effectQuantum = EffectQuantum.TopEffectQuantum;
|
||||
}
|
||||
final EffectQuantum effectQuantum = getChangeQuantum(value1);
|
||||
int insnIndex = methodNode.instructions.indexOf(insn);
|
||||
effects[insnIndex] = effectQuantum;
|
||||
return DataValue.UnknownDataValue1;
|
||||
@@ -354,6 +412,23 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static EffectQuantum getChangeQuantum(DataValue value) {
|
||||
if (value == DataValue.ThisDataValue || value == DataValue.OwnedDataValue) {
|
||||
return EffectQuantum.ThisChangeQuantum;
|
||||
}
|
||||
if (value == DataValue.LocalDataValue) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof DataValue.ParameterDataValue) {
|
||||
return new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)value).n);
|
||||
}
|
||||
if (value instanceof DataValue.ReturnDataValue) {
|
||||
return new EffectQuantum.ReturnChangeQuantum(((DataValue.ReturnDataValue)value).key);
|
||||
}
|
||||
return EffectQuantum.TopEffectQuantum;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataValue copyOperation(AbstractInsnNode insn, DataValue value) throws AnalyzerException {
|
||||
return value;
|
||||
@@ -382,7 +457,17 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
|
||||
EKey key = new EKey(method, Direction.Pure, stable);
|
||||
EffectQuantum quantum = new EffectQuantum.CallQuantum(key, data, opCode == Opcodes.INVOKESTATIC);
|
||||
DataValue result = (ASMUtils.getReturnSizeFast(mNode.desc) == 1) ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2;
|
||||
DataValue result;
|
||||
if (ASMUtils.getReturnSizeFast(mNode.desc) == 1) {
|
||||
if (ASMUtils.isReferenceReturnType(mNode.desc)) {
|
||||
result = new DataValue.ReturnDataValue(key);
|
||||
} else {
|
||||
result = DataValue.UnknownDataValue1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = DataValue.UnknownDataValue2;
|
||||
}
|
||||
if (HardCodedPurity.getInstance().isPureMethod(method)) {
|
||||
quantum = null;
|
||||
result = DataValue.LocalDataValue;
|
||||
@@ -446,21 +531,20 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
@Override
|
||||
public DataValue ternaryOperation(AbstractInsnNode insn, DataValue value1, DataValue value2, DataValue value3) throws AnalyzerException {
|
||||
int insnIndex = methodNode.instructions.indexOf(insn);
|
||||
if (value1 == DataValue.ThisDataValue || value1 == DataValue.OwnedDataValue) {
|
||||
effects[insnIndex] = EffectQuantum.ThisChangeQuantum;
|
||||
} else if (value1 instanceof DataValue.ParameterDataValue) {
|
||||
effects[insnIndex] = new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)value1).n);
|
||||
} else if (value1 == DataValue.LocalDataValue) {
|
||||
effects[insnIndex] = null;
|
||||
} else {
|
||||
effects[insnIndex] = EffectQuantum.TopEffectQuantum;
|
||||
}
|
||||
effects[insnIndex] = getChangeQuantum(value1);
|
||||
return DataValue.UnknownDataValue1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnOperation(AbstractInsnNode insn, DataValue value, DataValue expected) throws AnalyzerException {
|
||||
|
||||
if (insn.getOpcode() == Opcodes.ARETURN) {
|
||||
if (returnValue == null) {
|
||||
returnValue = value;
|
||||
}
|
||||
else if (!returnValue.equals(value)) {
|
||||
returnValue = DataValue.UnknownDataValue1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -475,95 +559,99 @@ class DataInterpreter extends Interpreter<DataValue> {
|
||||
}
|
||||
|
||||
final class PuritySolver {
|
||||
private static final Set<EffectQuantum> TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum);
|
||||
private HashMap<EKey, Set<EffectQuantum>> solved = new HashMap<>();
|
||||
static final Set<EffectQuantum> TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum);
|
||||
private HashMap<EKey, Effects> solved = new HashMap<>();
|
||||
private HashMap<EKey, Set<EKey>> dependencies = new HashMap<>();
|
||||
private final Stack<EKey> moving = new Stack<>();
|
||||
private HashMap<EKey, Set<EffectQuantum>> pending = new HashMap<>();
|
||||
private HashMap<EKey, Effects> pending = new HashMap<>();
|
||||
|
||||
void addEquation(EKey key, Set<EffectQuantum> effects) {
|
||||
Set<EKey> callKeys = new HashSet<>();
|
||||
for (EffectQuantum effect : effects) {
|
||||
if (effect instanceof EffectQuantum.CallQuantum) {
|
||||
callKeys.add(((EffectQuantum.CallQuantum)effect).key);
|
||||
}
|
||||
}
|
||||
void addEquation(EKey key, Effects effects) {
|
||||
Set<EKey> depKeys = effects.dependencies().collect(Collectors.toSet());
|
||||
|
||||
if (callKeys.isEmpty()) {
|
||||
if (depKeys.isEmpty()) {
|
||||
solved.put(key, effects);
|
||||
moving.add(key);
|
||||
} else {
|
||||
pending.put(key, effects);
|
||||
for (EKey callKey : callKeys) {
|
||||
Set<EKey> deps = dependencies.get(callKey);
|
||||
if (deps == null) {
|
||||
deps = new HashSet<>();
|
||||
dependencies.put(callKey, deps);
|
||||
}
|
||||
deps.add(key);
|
||||
for (EKey depKey : depKeys) {
|
||||
dependencies.computeIfAbsent(depKey, k -> new HashSet<>()).add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<EKey, Set<EffectQuantum>> solve() {
|
||||
public Map<EKey, Effects> solve() {
|
||||
while (!moving.isEmpty()) {
|
||||
EKey key = moving.pop();
|
||||
Set<EffectQuantum> effects = solved.get(key);
|
||||
Effects effects = solved.get(key);
|
||||
|
||||
EKey[] propagateKeys;
|
||||
Set[] propagateEffects;
|
||||
Effects[] propagateEffects;
|
||||
|
||||
if (key.stable) {
|
||||
propagateKeys = new EKey[]{key, key.mkUnstable()};
|
||||
propagateEffects = new Set[]{effects, effects};
|
||||
propagateEffects = new Effects[]{effects, effects};
|
||||
}
|
||||
else {
|
||||
propagateKeys = new EKey[]{key.mkStable(), key};
|
||||
propagateEffects = new Set[]{effects, TOP_EFFECTS};
|
||||
propagateEffects = new Effects[]{effects, new Effects(DataValue.UnknownDataValue1, TOP_EFFECTS)};
|
||||
}
|
||||
for (int i = 0; i < propagateKeys.length; i++) {
|
||||
EKey pKey = propagateKeys[i];
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<EffectQuantum> pEffects = propagateEffects[i];
|
||||
Effects pEffects = propagateEffects[i];
|
||||
Set<EKey> dKeys = dependencies.remove(pKey);
|
||||
if (dKeys != null) {
|
||||
for (EKey dKey : dKeys) {
|
||||
Set<EffectQuantum> dEffects = pending.remove(dKey);
|
||||
Effects dEffects = pending.remove(dKey);
|
||||
if (dEffects == null) {
|
||||
// already solved, for example, solution is top
|
||||
continue;
|
||||
}
|
||||
Set<EKey> callKeys = new HashSet<>();
|
||||
Set<EffectQuantum> newEffects = new HashSet<>();
|
||||
Set<EffectQuantum> delta = null;
|
||||
DataValue returnValue = substitute(dEffects.returnValue, pKey, pEffects);
|
||||
|
||||
for (EffectQuantum dEffect : dEffects) {
|
||||
for (EffectQuantum dEffect : dEffects.effects) {
|
||||
if (dEffect instanceof EffectQuantum.CallQuantum) {
|
||||
EffectQuantum.CallQuantum call = ((EffectQuantum.CallQuantum)dEffect);
|
||||
EffectQuantum.CallQuantum call = substitute((EffectQuantum.CallQuantum)dEffect, pKey, pEffects);
|
||||
if (call.key.equals(pKey)) {
|
||||
delta = substitute(pEffects, call.data, call.isStatic);
|
||||
if(delta.equals(TOP_EFFECTS)) {
|
||||
newEffects = delta;
|
||||
break;
|
||||
}
|
||||
newEffects.addAll(delta);
|
||||
}
|
||||
else {
|
||||
callKeys.add(call.key);
|
||||
newEffects.add(call);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
newEffects.add(dEffect);
|
||||
if (dEffect instanceof EffectQuantum.ReturnChangeQuantum) {
|
||||
EffectQuantum.ReturnChangeQuantum retChange = (EffectQuantum.ReturnChangeQuantum)dEffect;
|
||||
if (retChange.key.equals(pKey)) {
|
||||
if(pEffects.returnValue != DataValue.LocalDataValue) {
|
||||
newEffects = delta = TOP_EFFECTS;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newEffects.add(dEffect);
|
||||
}
|
||||
|
||||
if (TOP_EFFECTS.equals(delta)) {
|
||||
solved.put(dKey, TOP_EFFECTS);
|
||||
moving.push(dKey);
|
||||
}
|
||||
else if (callKeys.isEmpty()) {
|
||||
solved.put(dKey, newEffects);
|
||||
if (TOP_EFFECTS.equals(delta) && returnValue.equals(DataValue.UnknownDataValue1)) {
|
||||
solved.put(dKey, new Effects(returnValue, TOP_EFFECTS));
|
||||
moving.push(dKey);
|
||||
}
|
||||
else {
|
||||
pending.put(dKey, newEffects);
|
||||
Effects result = new Effects(returnValue, newEffects);
|
||||
if (result.dependencies().findFirst().isPresent()) {
|
||||
pending.put(dKey, result);
|
||||
}
|
||||
else {
|
||||
solved.put(dKey, result);
|
||||
moving.push(dKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,13 +663,31 @@ final class PuritySolver {
|
||||
return solved;
|
||||
}
|
||||
|
||||
private static Set<EffectQuantum> substitute(Set<EffectQuantum> effects, DataValue[] data, boolean isStatic) {
|
||||
if (effects.isEmpty() || TOP_EFFECTS.equals(effects)) {
|
||||
return effects;
|
||||
private static EffectQuantum.CallQuantum substitute(EffectQuantum.CallQuantum call, EKey pKey, Effects pEffects) {
|
||||
List<DataValue> list = new ArrayList<>();
|
||||
boolean same = true;
|
||||
for (DataValue value : call.data) {
|
||||
DataValue newValue = substitute(value, pKey, pEffects);
|
||||
same &= newValue.equals(value);
|
||||
list.add(newValue);
|
||||
}
|
||||
Set<EffectQuantum> newEffects = new HashSet<>(effects.size());
|
||||
return same ? call : new EffectQuantum.CallQuantum(call.key, list.toArray(new DataValue[0]), call.isStatic);
|
||||
}
|
||||
|
||||
private static DataValue substitute(DataValue value, EKey key, Effects effects) {
|
||||
if(value instanceof DataValue.ReturnDataValue && ((DataValue.ReturnDataValue)value).key.equals(key)) {
|
||||
return effects.returnValue == DataValue.LocalDataValue ? DataValue.LocalDataValue : DataValue.UnknownDataValue1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Set<EffectQuantum> substitute(Effects effects, DataValue[] data, boolean isStatic) {
|
||||
if (effects.effects.isEmpty() || TOP_EFFECTS.equals(effects.effects)) {
|
||||
return effects.effects;
|
||||
}
|
||||
Set<EffectQuantum> newEffects = new HashSet<>(effects.effects.size());
|
||||
int shift = isStatic ? 0 : 1;
|
||||
for (EffectQuantum effect : effects) {
|
||||
for (EffectQuantum effect : effects.effects) {
|
||||
DataValue arg = null;
|
||||
if (effect == EffectQuantum.ThisChangeQuantum) {
|
||||
arg = data[0];
|
||||
@@ -600,6 +706,10 @@ final class PuritySolver {
|
||||
newEffects.add(new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n));
|
||||
continue;
|
||||
}
|
||||
if (arg instanceof DataValue.ReturnDataValue) {
|
||||
newEffects.add(new EffectQuantum.ReturnChangeQuantum(((DataValue.ReturnDataValue)arg).key));
|
||||
continue;
|
||||
}
|
||||
return TOP_EFFECTS;
|
||||
}
|
||||
return newEffects;
|
||||
|
||||
+11
@@ -59,4 +59,15 @@ public class ASMUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public static boolean isReferenceReturnType(String methodDesc) {
|
||||
switch (methodDesc.charAt(methodDesc.indexOf(')') + 1)) {
|
||||
case 'L':
|
||||
case '[':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
@@ -163,6 +163,19 @@ public class Test01 {
|
||||
return copy;
|
||||
}
|
||||
|
||||
@ExpectNotNull
|
||||
@ExpectContract(pure = true)
|
||||
public static long[] copyAndModify(@ExpectNotNull long[] input) {
|
||||
long[] copy = copyOfRange(input, 0, input.length);
|
||||
copy[0] = 1;
|
||||
set(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
private static void set(@ExpectNotNull long[] copy) {
|
||||
copy[1] = 2;
|
||||
}
|
||||
|
||||
@ExpectContract(pure = true)
|
||||
public static <I, O> O[] copyOfRangeObject(@ExpectNotNull I[] arr, int from, int to, @ExpectNotNull Class<? extends O[]> newType) {
|
||||
int diff = to - from;
|
||||
|
||||
Reference in New Issue
Block a user