mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Construct equations for '@Contract(pure=true)' analysis
This commit is contained in:
+50
-15
@@ -53,10 +53,11 @@ public class BytecodeAnalysisConverter {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
Result<Key, Value> rhs = equation.rhs;
|
||||
HResult result;
|
||||
HResult hResult;
|
||||
if (rhs instanceof Final) {
|
||||
result = new HFinal(((Final<Key, Value>)rhs).value);
|
||||
} else {
|
||||
hResult = new HFinal(((Final<Key, Value>)rhs).value);
|
||||
}
|
||||
else {
|
||||
Pending<Key, Value> pending = (Pending<Key, Value>)rhs;
|
||||
Set<Product<Key, Value>> sumOrigin = pending.sum;
|
||||
HComponent[] components = new HComponent[sumOrigin.size()];
|
||||
@@ -64,17 +65,17 @@ public class BytecodeAnalysisConverter {
|
||||
for (Product<Key, Value> prod : sumOrigin) {
|
||||
HKey[] intProd = new HKey[prod.ids.size()];
|
||||
int idI = 0;
|
||||
for (Key id : prod.ids) {
|
||||
intProd[idI] = asmKey(id, md);
|
||||
for (Key key : prod.ids) {
|
||||
intProd[idI] = asmKey(key, md);
|
||||
idI++;
|
||||
}
|
||||
HComponent intIdComponent = new HComponent(prod.value, intProd);
|
||||
components[componentI] = intIdComponent;
|
||||
componentI++;
|
||||
}
|
||||
result = new HPending(components);
|
||||
hResult = new HPending(components);
|
||||
}
|
||||
return new DirectionResultPair(mkDirectionKey(equation.id.direction), result);
|
||||
return new DirectionResultPair(mkDirectionKey(equation.id.direction), hResult);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,6 +275,23 @@ public class BytecodeAnalysisConverter {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts Direction object to int.
|
||||
*
|
||||
* 0 - Out
|
||||
* 1 - NullableOut
|
||||
* 2 - Pure
|
||||
*
|
||||
* 3 - 0-th NOT_NULL
|
||||
* 4 - 0-th NULLABLE
|
||||
* ...
|
||||
*
|
||||
* 11 - 1-st NOT_NULL
|
||||
* 12 - 1-st NULLABLE
|
||||
*
|
||||
* @param dir direction of analysis
|
||||
* @return unique int for direction
|
||||
*/
|
||||
static int mkDirectionKey(Direction dir) {
|
||||
if (dir == Out) {
|
||||
return 0;
|
||||
@@ -281,17 +299,28 @@ public class BytecodeAnalysisConverter {
|
||||
else if (dir == NullableOut) {
|
||||
return 1;
|
||||
}
|
||||
else if (dir == Pure) {
|
||||
return 2;
|
||||
}
|
||||
else if (dir instanceof In) {
|
||||
In in = (In)dir;
|
||||
// nullity mask is 0/1
|
||||
return 2 + 8 * in.paramId() + in.nullityMask;
|
||||
return 3 + 8 * in.paramId() + in.nullityMask;
|
||||
}
|
||||
else {
|
||||
// valueId is [1-5]
|
||||
InOut inOut = (InOut)dir;
|
||||
return 4 + 8 * inOut.paramId() + inOut.valueId();
|
||||
return 3 + 8 * inOut.paramId() + 2 + inOut.valueId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts int to Direction object.
|
||||
*
|
||||
* @param directionKey int representation of direction
|
||||
* @return Direction object
|
||||
* @see #mkDirectionKey(Direction)
|
||||
*/
|
||||
@NotNull
|
||||
private static Direction extractDirection(int directionKey) {
|
||||
if (directionKey == 0) {
|
||||
@@ -300,15 +329,21 @@ public class BytecodeAnalysisConverter {
|
||||
else if (directionKey == 1) {
|
||||
return NullableOut;
|
||||
}
|
||||
else if (directionKey == 2) {
|
||||
return Pure;
|
||||
}
|
||||
else {
|
||||
directionKey--;
|
||||
int paramId = directionKey / 8;
|
||||
int subDirection = directionKey % 8;
|
||||
if (subDirection <= 2) {
|
||||
return new In(paramId, subDirection - 1);
|
||||
int paramKey = directionKey - 3;
|
||||
int paramId = paramKey / 8;
|
||||
// shifting first 3 values - now we have key [0 - 7]
|
||||
int subDirectionId = paramKey % 8;
|
||||
// 0 - 1 - @NotNull, @Nullable, parameter
|
||||
if (subDirectionId <= 1) {
|
||||
return new In(paramId, subDirectionId);
|
||||
}
|
||||
else {
|
||||
return new InOut(paramId, Value.values()[subDirection - 3]);
|
||||
int valueId = subDirectionId - 2;
|
||||
return new InOut(paramId, Value.values()[valueId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -88,6 +88,9 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
|
||||
return ourInternalVersion + (ourEnabled ? 0xFF : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Externalizer for primary method keys.
|
||||
*/
|
||||
private static class HKeyDescriptor implements KeyDescriptor<Bytes>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
|
||||
@Override
|
||||
@@ -115,6 +118,9 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Externalizer for compressed equations.
|
||||
*/
|
||||
public static class HEquationsExternalizer implements DataExternalizer<HEquations>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, HEquations eqs) throws IOException {
|
||||
|
||||
+53
-45
@@ -42,7 +42,6 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
public static final Final<Key, Value> FINAL_BOT = new Final<Key, Value>(Value.Bot);
|
||||
public static final Final<Key, Value> FINAL_NOT_NULL = new Final<Key, Value>(Value.NotNull);
|
||||
public static final Final<Key, Value> FINAL_NULL = new Final<Key, Value>(Value.Null);
|
||||
private static final List<Equation<Key, Value>> EMPTY_EQUATIONS = Collections.EMPTY_LIST;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -50,17 +49,19 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
HashMap<Bytes, HEquations> map = new HashMap<Bytes, HEquations>();
|
||||
try {
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
Map<Key, List<Equation<Key, Value>>> rawEquations = processClass(new ClassReader(inputData.getContent()), inputData.getFile().getPresentableUrl());
|
||||
for (Map.Entry<Key, List<Equation<Key, Value>>> entry: rawEquations.entrySet()) {
|
||||
Key primaryKey = entry.getKey();
|
||||
Key serKey = new Key(primaryKey.method, primaryKey.direction, true);
|
||||
Map<Key, List<Equation<Key, Value>>> allEquations = processClass(new ClassReader(inputData.getContent()), inputData.getFile().getPresentableUrl());
|
||||
for (Map.Entry<Key, List<Equation<Key, Value>>> entry: allEquations.entrySet()) {
|
||||
|
||||
List<Equation<Key, Value>> equations = entry.getValue();
|
||||
List<DirectionResultPair> result = new ArrayList<DirectionResultPair>(equations.size());
|
||||
for (Equation<Key, Value> equation : equations) {
|
||||
result.add(BytecodeAnalysisConverter.convert(equation, md));
|
||||
Key methodKey = entry.getKey();
|
||||
// method equations with raw (not-compressed keys)
|
||||
List<Equation<Key, Value>> rawMethodEquations = entry.getValue();
|
||||
//
|
||||
List<DirectionResultPair> compressedMethodEquations =
|
||||
new ArrayList<DirectionResultPair>(rawMethodEquations.size());
|
||||
for (Equation<Key, Value> equation : rawMethodEquations) {
|
||||
compressedMethodEquations.add(BytecodeAnalysisConverter.convert(equation, md));
|
||||
}
|
||||
map.put(new Bytes(BytecodeAnalysisConverter.asmKey(serKey, md).key), new HEquations(result, primaryKey.stable));
|
||||
map.put(new Bytes(BytecodeAnalysisConverter.asmKey(methodKey, md).key), new HEquations(compressedMethodEquations, methodKey.stable));
|
||||
}
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
@@ -71,7 +72,6 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
// so here we suppose that exception is due to incorrect bytecode
|
||||
LOG.debug("Unexpected Error during indexing of bytecode", e);
|
||||
}
|
||||
//return map;
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -113,6 +113,13 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Facade for analysis, it invokes specialized analyses for branching/non-branching methods.
|
||||
*
|
||||
* @param methodNode asm node for method
|
||||
* @param jsr whether a method has jsr instruction
|
||||
* @return pair of (primaryKey, equations)
|
||||
*/
|
||||
private Pair<Key, List<Equation<Key, Value>>> processMethod(final MethodNode methodNode, boolean jsr) {
|
||||
ProgressManager.checkCanceled();
|
||||
final Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
|
||||
@@ -125,8 +132,15 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
final boolean stable = stableClass || (methodNode.access & STABLE_FLAGS) != 0 || "<init>".equals(methodNode.name);
|
||||
|
||||
Key primaryKey = new Key(method, Out, stable);
|
||||
|
||||
// 4*n: for each reference parameter: @NotNull IN, @Nullable, null -> ... contract, !null -> contract
|
||||
// 3: @NotNull OUT, @Nullable OUT, purity analysis
|
||||
List<Equation<Key, Value>> equations = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 3);
|
||||
equations.add(PurityAnalysis.analyze(method, methodNode, stable));
|
||||
|
||||
if (argumentTypes.length == 0 && !isInterestingResult) {
|
||||
return Pair.create(primaryKey, EMPTY_EQUATIONS);
|
||||
// no need to continue analysis
|
||||
return Pair.create(primaryKey, equations);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -142,19 +156,18 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (branching) {
|
||||
RichControlFlow richControlFlow = new RichControlFlow(graph, dfs);
|
||||
if (richControlFlow.reducible()) {
|
||||
return Pair.create(primaryKey,
|
||||
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable, jsr));
|
||||
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable, jsr, equations);
|
||||
return Pair.create(primaryKey, equations);
|
||||
}
|
||||
LOG.debug(method + ": CFG is not reducible");
|
||||
}
|
||||
// simple
|
||||
else {
|
||||
return Pair.create(primaryKey,
|
||||
processNonBranchingMethod(method, argumentTypes, graph, isReferenceResult, isBooleanResult, stable));
|
||||
processNonBranchingMethod(method, argumentTypes, graph, isReferenceResult, isBooleanResult, stable, equations);
|
||||
return Pair.create(primaryKey, equations);
|
||||
}
|
||||
}
|
||||
return Pair.create(primaryKey, topEquations(method, argumentTypes, isReferenceResult, isInterestingResult, stable));
|
||||
@@ -170,17 +183,15 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
}
|
||||
|
||||
private List<Equation<Key, Value>> processBranchingMethod(final Method method,
|
||||
final MethodNode methodNode,
|
||||
final RichControlFlow richControlFlow,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
final boolean stable,
|
||||
boolean jsr) throws AnalyzerException {
|
||||
|
||||
// 4 = @NotNull parameter, @Nullable parameter, null -> ..., !null -> ...
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
|
||||
private void processBranchingMethod(final Method method,
|
||||
final MethodNode methodNode,
|
||||
final RichControlFlow richControlFlow,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
final boolean stable,
|
||||
boolean jsr,
|
||||
List<Equation<Key, Value>> result) throws AnalyzerException {
|
||||
boolean maybeLeakingParameter = isInterestingResult;
|
||||
for (Type argType : argumentTypes) {
|
||||
if (ASMUtils.isReferenceType(argType)) {
|
||||
@@ -218,7 +229,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
if (ASMUtils.isReferenceType(argumentTypes[i])) {
|
||||
boolean possibleNPE = false;
|
||||
if (leakingParameters[i]) {
|
||||
NonNullInAnalysis notNullInAnalysis = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable);
|
||||
NonNullInAnalysis notNullInAnalysis = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL_MASK), stable);
|
||||
Equation<Key, Value> notNullParamEquation = notNullInAnalysis.analyze();
|
||||
possibleNPE = notNullInAnalysis.possibleNPE;
|
||||
notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL);
|
||||
@@ -226,19 +237,19 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so it is definitely NOT @NotNull
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
|
||||
}
|
||||
|
||||
if (leakingNullableParameters[i]) {
|
||||
if (notNullParam || possibleNPE) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
|
||||
}
|
||||
else {
|
||||
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE), stable).analyze());
|
||||
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE_MASK), stable).analyze());
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_NULL));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_NULL));
|
||||
}
|
||||
|
||||
if (isInterestingResult) {
|
||||
@@ -261,17 +272,15 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Equation<Key, Value>> processNonBranchingMethod(Method method,
|
||||
Type[] argumentTypes,
|
||||
ControlFlowGraph graph,
|
||||
boolean isReferenceResult,
|
||||
boolean isBooleanResult,
|
||||
boolean stable) throws AnalyzerException {
|
||||
// 4 = @NotNull parameter, @Nullable parameter, null -> ..., !null -> ...
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 2);
|
||||
private void processNonBranchingMethod(Method method,
|
||||
Type[] argumentTypes,
|
||||
ControlFlowGraph graph,
|
||||
boolean isReferenceResult,
|
||||
boolean isBooleanResult,
|
||||
boolean stable,
|
||||
List<Equation<Key, Value>> result) throws AnalyzerException {
|
||||
CombinedAnalysis analyzer = new CombinedAnalysis(method, graph);
|
||||
analyzer.analyze();
|
||||
if (isReferenceResult) {
|
||||
@@ -289,7 +298,6 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Equation<Key, Value>> topEquations(Method method,
|
||||
@@ -305,8 +313,8 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
if (ASMUtils.isReferenceType(argumentTypes[i])) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
|
||||
if (isInterestingResult) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
|
||||
|
||||
+4
-4
@@ -194,7 +194,7 @@ final class CombinedAnalysis {
|
||||
}
|
||||
|
||||
final Equation<Key, Value> notNullParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i, In.NOT_NULL), stable);
|
||||
final Key key = new Key(method, new In(i, In.NOT_NULL_MASK), stable);
|
||||
final Result<Key, Value> result;
|
||||
if (interpreter.dereferencedParams[i]) {
|
||||
result = new Final<Key, Value>(Value.NotNull);
|
||||
@@ -207,7 +207,7 @@ final class CombinedAnalysis {
|
||||
else {
|
||||
Set<Key> keys = new HashSet<Key>();
|
||||
for (ParamKey pk: calls) {
|
||||
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL), pk.stable));
|
||||
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL_MASK), pk.stable));
|
||||
}
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
|
||||
}
|
||||
@@ -216,7 +216,7 @@ final class CombinedAnalysis {
|
||||
}
|
||||
|
||||
final Equation<Key, Value> nullableParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i, In.NULLABLE), stable);
|
||||
final Key key = new Key(method, new In(i, In.NULLABLE_MASK), stable);
|
||||
final Result<Key, Value> result;
|
||||
if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
@@ -229,7 +229,7 @@ final class CombinedAnalysis {
|
||||
else {
|
||||
Set<Product<Key, Value>> sum = new HashSet<Product<Key, Value>>();
|
||||
for (ParamKey pk: calls) {
|
||||
sum.add(new Product<Key, Value>(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE), pk.stable))));
|
||||
sum.add(new Product<Key, Value>(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE_MASK), pk.stable))));
|
||||
}
|
||||
result = new Pending<Key, Value>(sum);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode;
|
||||
|
||||
final class Method {
|
||||
final String internalClassName;
|
||||
final String methodName;
|
||||
@@ -36,12 +38,30 @@ final class Method {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary constructor
|
||||
*
|
||||
* @param internalClassName class name in asm format
|
||||
* @param methodName method name
|
||||
* @param methodDesc method descriptor in asm format
|
||||
*/
|
||||
Method(String internalClassName, String methodName, String methodDesc) {
|
||||
this.internalClassName = internalClassName;
|
||||
this.methodName = methodName;
|
||||
this.methodDesc = methodDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient constructor to convert asm instruction into method key
|
||||
*
|
||||
* @param mNode asm node from which method key is extracted
|
||||
*/
|
||||
Method(MethodInsnNode mNode) {
|
||||
this.internalClassName = mNode.owner;
|
||||
this.methodName = mNode.name;
|
||||
this.methodDesc = mNode.desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return internalClassName + ' ' + methodName + ' ' + methodDesc;
|
||||
@@ -49,14 +69,19 @@ final class Method {
|
||||
}
|
||||
|
||||
enum Value {
|
||||
Bot, NotNull, Null, True, False, Top
|
||||
Bot, NotNull, Null, True, False, Pure, Top
|
||||
}
|
||||
|
||||
interface Direction {
|
||||
final class In implements Direction {
|
||||
static final int NOT_NULL = 0;
|
||||
static final int NULLABLE = 1;
|
||||
final int paramIndex;
|
||||
|
||||
static final int NOT_NULL_MASK = 0;
|
||||
static final int NULLABLE_MASK = 1;
|
||||
/**
|
||||
* @see #NOT_NULL_MASK
|
||||
* @see #NULLABLE_MASK
|
||||
*/
|
||||
final int nullityMask;
|
||||
|
||||
In(int paramIndex, int nullityMask) {
|
||||
@@ -155,6 +180,18 @@ interface Direction {
|
||||
return -2;
|
||||
}
|
||||
};
|
||||
|
||||
Direction Pure = new Direction() {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return -3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Pure";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final class Key {
|
||||
|
||||
@@ -166,7 +166,11 @@ final class HEquation {
|
||||
return result1;
|
||||
}
|
||||
}
|
||||
class Bytes {
|
||||
|
||||
/**
|
||||
* Bytes of primary HKey of a method.
|
||||
*/
|
||||
final class Bytes {
|
||||
@NotNull
|
||||
final byte[] bytes;
|
||||
Bytes(@NotNull byte[] bytes) {
|
||||
@@ -177,12 +181,7 @@ class Bytes {
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Bytes bytes1 = (Bytes)o;
|
||||
|
||||
if (!Arrays.equals(bytes, bytes1.bytes)) return false;
|
||||
|
||||
return true;
|
||||
return Arrays.equals(bytes, ((Bytes)o).bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -750,7 +750,7 @@ abstract class NullityInterpreter extends BasicInterpreter {
|
||||
class NotNullInterpreter extends NullityInterpreter {
|
||||
|
||||
NotNullInterpreter() {
|
||||
super(false, In.NOT_NULL);
|
||||
super(false, In.NOT_NULL_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -762,7 +762,7 @@ class NotNullInterpreter extends NullityInterpreter {
|
||||
class NullableInterpreter extends NullityInterpreter {
|
||||
|
||||
NullableInterpreter() {
|
||||
super(true, In.NULLABLE);
|
||||
super(true, In.NULLABLE_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -201,7 +201,7 @@ public class ProjectBytecodeAnalysis {
|
||||
PsiElement gParent = parent.getParent();
|
||||
if (gParent instanceof PsiMethod) {
|
||||
final int index = ((PsiParameterList)parent).getParameterIndex((PsiParameter)owner);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, In.NOT_NULL), md);
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, In.NOT_NULL_MASK), md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes;
|
||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.InsnList;
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Produces equations for inference of @Contract(pure=true) annotations.
|
||||
*
|
||||
* This simple analysis infers @Contract(pure=true) only if the method doesn't have following instructions.
|
||||
* <ul>
|
||||
* <li>PUTFIELD</li>
|
||||
* <li>PUTSTATIC</li>
|
||||
* <li>IASTORE</li>
|
||||
* <li>LASTORE</li>
|
||||
* <li>FASTORE</li>
|
||||
* <li>DASTORE</li>
|
||||
* <li>AASTORE</li>
|
||||
* <li>BASTORE</li>
|
||||
* <li>CASTORE</li>
|
||||
* <li>SASTORE</li>
|
||||
* <li>INVOKEDYNAMIC</li>
|
||||
* <li>INVOKEINTERFACE</li>
|
||||
* </ul>
|
||||
*
|
||||
* - Nested calls (via INVOKESPECIAL, INVOKESTATIC, INVOKEVIRTUAL) are processed by transitivity.
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class PurityAnalysis {
|
||||
private static final Result<Key, Value> FINAL_TOP = new Final<Key, Value>(Value.Top);
|
||||
private static final Result<Key, Value> FINAL_PURE = new Final<Key, Value>(Value.Pure);
|
||||
|
||||
static final int UN_ANALYZABLE_FLAG = Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_INTERFACE;
|
||||
|
||||
@NotNull
|
||||
public static Equation<Key, Value> analyze(Method method, MethodNode methodNode, boolean stable) {
|
||||
Key key = new Key(method, Direction.Pure, stable);
|
||||
|
||||
if ((methodNode.access & UN_ANALYZABLE_FLAG) != 0) {
|
||||
return new Equation<Key, Value>(key, FINAL_TOP);
|
||||
}
|
||||
|
||||
InsnList insns = methodNode.instructions;
|
||||
// primary keys of invoked methods
|
||||
Set<Key> invokedKeys = new HashSet<Key>();
|
||||
for (int i = 0; i < insns.size(); i++) {
|
||||
AbstractInsnNode insn = insns.get(i);
|
||||
switch (insn.getOpcode()) {
|
||||
case Opcodes.PUTFIELD:
|
||||
case Opcodes.PUTSTATIC:
|
||||
case Opcodes.IASTORE:
|
||||
case Opcodes.LASTORE:
|
||||
case Opcodes.FASTORE:
|
||||
case Opcodes.DASTORE:
|
||||
case Opcodes.AASTORE:
|
||||
case Opcodes.BASTORE:
|
||||
case Opcodes.CASTORE:
|
||||
case Opcodes.SASTORE:
|
||||
case Opcodes.INVOKEDYNAMIC:
|
||||
case Opcodes.INVOKEINTERFACE:
|
||||
return new Equation<Key, Value>(key, FINAL_TOP);
|
||||
case Opcodes.INVOKESPECIAL:
|
||||
case Opcodes.INVOKESTATIC:
|
||||
invokedKeys.add(new Key(new Method((MethodInsnNode)insn), Direction.Pure, true));
|
||||
break;
|
||||
case Opcodes.INVOKEVIRTUAL:
|
||||
invokedKeys.add(new Key(new Method((MethodInsnNode)insn), Direction.Pure, false));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (invokedKeys.isEmpty()) {
|
||||
return new Equation<Key, Value>(key, FINAL_PURE);
|
||||
}
|
||||
else {
|
||||
HashSet<Product<Key, Value>> sumOfProducts = new HashSet<Product<Key, Value>>();
|
||||
for (Key call : invokedKeys) {
|
||||
sumOfProducts.add(new Product<Key, Value>(Value.Top, Collections.singleton(call)));
|
||||
}
|
||||
return new Equation<Key, Value>(key, new Pending<Key, Value>(sumOfProducts));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user