IDEA-185138 Bytecode purity inference: do not infer method as pure if it reads the volatile field

This commit is contained in:
Tagir Valeev
2018-01-18 17:55:57 +07:00
parent 95bd3fbd93
commit 1aa64e3da2
31 changed files with 282 additions and 361 deletions

View File

@@ -224,7 +224,7 @@ abstract class Analysis<Res> {
final Direction direction;
final ControlFlowGraph controlFlow;
final MethodNode methodNode;
final Method method;
final Member method;
final DFSTree dfsTree;
final protected List<State>[] computed;
@@ -237,7 +237,7 @@ abstract class Analysis<Res> {
this.direction = direction;
controlFlow = richControlFlow.controlFlow;
methodNode = controlFlow.methodNode;
method = new Method(controlFlow.className, methodNode.name, methodNode.desc);
method = new Member(controlFlow.className, methodNode.name, methodNode.desc);
dfsTree = richControlFlow.dfsTree;
aKey = new EKey(method, direction, stable);
computed = (List<State>[]) new List[controlFlow.transitions.length];

View File

@@ -73,7 +73,7 @@ public class BytecodeAnalysisConverter {
return null;
}
String methodName = psiMethod.getReturnType() == null ? "<init>" : psiMethod.getName();
return new EKey(new Method(className, methodName, methodSig), direction, true, false);
return new EKey(new Member(className, methodName, methodSig), direction, true, false);
}
@Nullable

View File

@@ -32,10 +32,7 @@ import com.intellij.util.io.DifferentSerializableBytesImplyNonEqualityPolicy;
import com.intellij.util.io.KeyDescriptor;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.*;
import java.io.DataInput;
import java.io.DataOutput;
@@ -49,15 +46,15 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
/**
* @author lambdamix
*/
public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
private static final ID<HMethod, Void> NAME = ID.create("bytecodeAnalysis");
public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMember> {
private static final ID<HMember, Void> NAME = ID.create("bytecodeAnalysis");
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
private static final int VERSION = 8; // change when inference algorithm changes
private static final int VERSION = 9; // 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;
private static final VirtualFileGist<Map<HMethod, Equations>> ourGist = GistManager.getInstance().newVirtualFileGist(
private static final VirtualFileGist<Map<HMember, Equations>> ourGist = GistManager.getInstance().newVirtualFileGist(
"BytecodeAnalysisIndex", FINAL_VERSION, new EquationsExternalizer(), new ClassDataIndexer());
// Hash collision is possible: resolve it just flushing all the equations for colliding methods (unless equations are the same)
static final BinaryOperator<Equations> MERGER =
@@ -65,13 +62,13 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
@NotNull
@Override
public ID<HMethod, Void> getName() {
public ID<HMember, Void> getName() {
return NAME;
}
@NotNull
@Override
public DataIndexer<HMethod, Void, FileContent> getIndexer() {
public DataIndexer<HMember, Void, FileContent> getIndexer() {
return inputData -> {
try {
return collectKeys(inputData.getContent());
@@ -89,23 +86,32 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
}
@NotNull
private static Map<HMethod, Void> collectKeys(byte[] content) {
HashMap<HMethod, Void> map = new HashMap<>();
private static Map<HMember, Void> collectKeys(byte[] content) {
HashMap<HMember, Void> map = new HashMap<>();
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
new ClassReader(content).accept(new KeyedMethodVisitor() {
@Nullable
ClassReader reader = new ClassReader(content);
String className = reader.getClassName();
reader.accept(new ClassVisitor(Opcodes.API_VERSION) {
@Override
MethodVisitor visitMethod(MethodNode node, Method method, EKey key) {
map.put(method.hashed(md), null);
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if((access & Opcodes.ACC_PRIVATE) == 0) {
map.put(new Member(className, name, desc).hashed(md), null);
}
return null;
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
map.put(new Member(className, name, desc).hashed(md), null);
return null;
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE);
return map;
}
@NotNull
@Override
public KeyDescriptor<HMethod> getKeyDescriptor() {
public KeyDescriptor<HMember> getKeyDescriptor() {
return KEY_DESCRIPTOR;
}
@@ -131,7 +137,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
}
@NotNull
static List<Equations> getEquations(GlobalSearchScope scope, HMethod key) {
static List<Equations> getEquations(GlobalSearchScope scope, HMember key) {
Project project = ProjectManager.getInstance().getDefaultProject(); // the data is project-independent
return ContainerUtil.mapNotNull(FileBasedIndex.getInstance().getContainingFiles(NAME, key, scope),
file -> ourGist.getFileData(project, file).get(key));
@@ -140,27 +146,27 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
/**
* Externalizer for primary method keys.
*/
private static class HKeyDescriptor implements KeyDescriptor<HMethod>, DifferentSerializableBytesImplyNonEqualityPolicy {
private static class HKeyDescriptor implements KeyDescriptor<HMember>, DifferentSerializableBytesImplyNonEqualityPolicy {
@Override
public void save(@NotNull DataOutput out, HMethod value) throws IOException {
public void save(@NotNull DataOutput out, HMember value) throws IOException {
out.write(value.myBytes);
}
@Override
public HMethod read(@NotNull DataInput in) throws IOException {
byte[] bytes = new byte[HMethod.HASH_SIZE];
public HMember read(@NotNull DataInput in) throws IOException {
byte[] bytes = new byte[HMember.HASH_SIZE];
in.readFully(bytes);
return new HMethod(bytes);
return new HMember(bytes);
}
@Override
public int getHashCode(HMethod value) {
public int getHashCode(HMember value) {
return value.hashCode();
}
@Override
public boolean isEqual(HMethod val1, HMethod val2) {
public boolean isEqual(HMember val1, HMember val2) {
return val1.equals(val2);
}
}
@@ -168,9 +174,9 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
/**
* Externalizer for compressed equations.
*/
public static class EquationsExternalizer implements DataExternalizer<Map<HMethod, Equations>> {
public static class EquationsExternalizer implements DataExternalizer<Map<HMember, Equations>> {
@Override
public void save(@NotNull DataOutput out, Map<HMethod, Equations> value) throws IOException {
public void save(@NotNull DataOutput out, Map<HMember, Equations> value) throws IOException {
DataInputOutputUtilRt.writeSeq(out, value.entrySet(), entry -> {
KEY_DESCRIPTOR.save(out, entry.getKey());
saveEquations(out, entry.getValue());
@@ -178,7 +184,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
}
@Override
public Map<HMethod, Equations> read(@NotNull DataInput in) throws IOException {
public Map<HMember, Equations> read(@NotNull DataInput in) throws IOException {
return StreamEx.of(DataInputOutputUtilRt.readSeq(in, () -> Pair.create(KEY_DESCRIPTOR.read(in), readEquations(in)))).
toMap(p -> p.getFirst(), p -> p.getSecond(), MERGER);
}
@@ -227,7 +233,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
for (int k = 0; k < size; k++) {
int directionKey = DataInputOutputUtil.readINT(in);
Direction direction = Direction.fromInt(directionKey);
if (direction == Direction.Pure) {
if (direction == Direction.Pure || direction == Direction.Volatile) {
Set<EffectQuantum> effects = new HashSet<>();
int effectsSize = DataInputOutputUtil.readINT(in);
for (int i = 0; i < effectsSize; i++) {
@@ -266,14 +272,14 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
@NotNull
private static EKey readKey(@NotNull DataInput in) throws IOException {
byte[] bytes = new byte[HMethod.HASH_SIZE];
byte[] bytes = new byte[HMember.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);
return new EKey(new HMember(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);
out.write(key.member.hashed(md).myBytes);
int rawDirKey = key.negated ? -key.dirKey : key.dirKey;
DataInputOutputUtil.writeINT(out, rawDirKey);
out.writeBoolean(key.stable);
@@ -300,6 +306,10 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
DataInputOutputUtil.writeINT(out, -4);
writeKey(out, ((EffectQuantum.ReturnChangeQuantum)effect).key, md);
}
else if (effect instanceof EffectQuantum.FieldReadQuantum) {
DataInputOutputUtil.writeINT(out, -5);
writeKey(out, ((EffectQuantum.FieldReadQuantum)effect).key, md);
}
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
DataInputOutputUtil.writeINT(out, ((EffectQuantum.ParamChangeQuantum)effect).n);
}
@@ -323,6 +333,8 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
return new EffectQuantum.CallQuantum(key, data, isStatic);
case -4:
return new EffectQuantum.ReturnChangeQuantum(readKey(in));
case -5:
return new EffectQuantum.FieldReadQuantum(readKey(in));
default:
return new EffectQuantum.ParamChangeQuantum(effectMask);
}

View File

@@ -44,6 +44,7 @@ import java.util.function.IntFunction;
import java.util.stream.Stream;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
import static com.intellij.codeInspection.bytecodeAnalysis.Effects.VOLATILE_EFFECTS;
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
/**
@@ -54,7 +55,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
*
* @author lambdamix
*/
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMethod, Equations>> {
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMember, Equations>> {
public static final Final FINAL_TOP = new Final(Value.Top);
public static final Final FINAL_FAIL = new Final(Value.Fail);
@@ -62,21 +63,22 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
public static final Final FINAL_NOT_NULL = new Final(Value.NotNull);
public static final Final FINAL_NULL = new Final(Value.Null);
public static final Consumer<Map<HMethod, Equations>> ourIndexSizeStatistics =
public static final Consumer<Map<HMember, Equations>> ourIndexSizeStatistics =
ApplicationManager.getApplication().isUnitTestMode() ? new ClassDataIndexerStatistics() : map -> {};
@Nullable
@Override
public Map<HMethod, Equations> calcData(@NotNull Project project, @NotNull VirtualFile file) {
HashMap<HMethod, Equations> map = new HashMap<>();
public Map<HMember, Equations> calcData(@NotNull Project project, @NotNull VirtualFile file) {
HashMap<HMember, Equations> map = new HashMap<>();
if (isFileExcluded(file)) {
return map;
}
try {
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
Map<EKey, Equations> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
allEquations = solvePartially(allEquations);
allEquations.forEach((methodKey, equations) -> map.merge(methodKey.method.hashed(md), hash(equations, md), BytecodeAnalysisIndex.MERGER));
ClassReader reader = new ClassReader(file.contentsToByteArray(false));
Map<EKey, Equations> allEquations = processClass(reader, file.getPresentableUrl());
allEquations = solvePartially(reader.getClassName(), allEquations);
allEquations.forEach((methodKey, equations) -> map.merge(methodKey.member.hashed(md), hash(equations, md), BytecodeAnalysisIndex.MERGER));
}
catch (ProcessCanceledException e) {
throw e;
@@ -111,13 +113,15 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
return index > 0 && path.lastIndexOf("platforms/android-", index) > 0;
}
private static Map<EKey, Equations> solvePartially(Map<EKey, Equations> map) {
private static Map<EKey, Equations> solvePartially(String className, Map<EKey, Equations> map) {
PuritySolver solver = new PuritySolver();
BiFunction<EKey, Equations, EKey> keyCreator = (key, eqs) -> new EKey(key.method, Pure, eqs.stable, false);
BiFunction<EKey, Equations, EKey> keyCreator =
(key, eqs) -> new EKey(key.member, eqs.find(Volatile).isPresent() ? Volatile : Pure, eqs.stable, false);
EntryStream.of(map).mapToKey(keyCreator)
.flatMapValues(eqs -> eqs.results.stream().map(drp -> drp.result))
.selectValues(Effects.class)
.forKeyValue(solver::addEquation);
solver.addPlainFieldEquations(md -> md instanceof Member && ((Member)md).internalClassName.equals(className));
Map<EKey, Effects> solved = solver.solve();
Map<EKey, Effects> partiallySolvedPurity =
StreamEx.of(solved, solver.pending).flatMapToEntry(Function.identity()).removeValues(Effects::isTop).toMap();
@@ -175,10 +179,12 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
final PResults.PResult[] sharedResults = new PResults.PResult[Analysis.STEPS_LIMIT];
final Map<EKey, Equations> equations = new HashMap<>();
registerVolatileFields(equations, classReader);
if ((classReader.getAccess() & Opcodes.ACC_ENUM) != 0) {
// ordinal() method is final in java.lang.Enum, but for some reason referred on call sites using specific enum class
// it's used on every enum switch statement, so forcing its purity is important
EKey ordinalKey = new EKey(new Method(classReader.getClassName(), "ordinal", "()I"), Out, true);
EKey ordinalKey = new EKey(new Member(classReader.getClassName(), "ordinal", "()I"), Out, true);
equations.put(ordinalKey, new Equations(
Collections.singletonList(new DirectionResultPair(Pure.asInt(), new Effects(DataValue.LocalDataValue, Collections.emptySet()))),
true));
@@ -186,7 +192,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
classReader.accept(new KeyedMethodVisitor() {
protected MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key) {
protected MethodVisitor visitMethod(final MethodNode node, Member method, final EKey key) {
return new MethodVisitor(Opcodes.API_VERSION, node) {
private boolean jsr;
@@ -214,7 +220,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
* @param method a method descriptor
* @param stable whether a method is stable (final or declared in a final class)
*/
private List<Equation> processMethod(final MethodNode methodNode, boolean jsr, Method method, boolean stable) {
private List<Equation> processMethod(final MethodNode methodNode, boolean jsr, Member method, boolean stable) {
ProgressManager.checkCanceled();
final Type[] argumentTypes = Type.getArgumentTypes(methodNode.desc);
final Type resultType = Type.getReturnType(methodNode.desc);
@@ -273,7 +279,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
}
}
private NegationAnalysis tryNegation(final Method method,
private NegationAnalysis tryNegation(final Member method,
final Type[] argumentTypes,
final ControlFlowGraph graph,
final boolean isBooleanResult,
@@ -354,7 +360,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
analyzer.analyze();
return analyzer;
}
catch (NegationAnalysisFailure ignore) {
catch (NegationAnalysisFailedException ignore) {
return null;
}
}
@@ -363,7 +369,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
return null;
}
private void processBranchingMethod(final Method method,
private void processBranchingMethod(final Member method,
final MethodNode methodNode,
final RichControlFlow richControlFlow,
Type[] argumentTypes,
@@ -489,7 +495,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
}
}
private void processNonBranchingMethod(Method method,
private void processNonBranchingMethod(Member method,
Type[] argumentTypes,
ControlFlowGraph graph,
Type returnType,
@@ -514,7 +520,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
});
}
private List<Equation> topEquations(Method method,
private List<Equation> topEquations(Member method,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
@@ -539,7 +545,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
}
@NotNull
private LeakingParameters leakingParametersAndFrames(Method method, MethodNode methodNode, Type[] argumentTypes, boolean jsr)
private LeakingParameters leakingParametersAndFrames(Member method, MethodNode methodNode, Type[] argumentTypes, boolean jsr)
throws AnalyzerException {
return argumentTypes.length < 32 ?
LeakingParameters.buildFast(method.internalClassName, methodNode, jsr) :
@@ -550,12 +556,25 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMet
return equations;
}
private static class ClassDataIndexerStatistics implements Consumer<Map<HMethod, Equations>> {
private static void registerVolatileFields(Map<EKey, Equations> equations, ClassReader classReader) {
classReader.accept(new ClassVisitor(Opcodes.API_VERSION) {
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if ((access & Opcodes.ACC_VOLATILE) != 0) {
EKey fieldKey = new EKey(new Member(classReader.getClassName(), name, desc), Out, true);
equations.put(fieldKey, new Equations(Collections.singletonList(new DirectionResultPair(Volatile.asInt(), VOLATILE_EFFECTS)), true));
}
return null;
}
}, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE);
}
private static class ClassDataIndexerStatistics implements Consumer<Map<HMember, Equations>> {
private static final AtomicLong ourTotalSize = new AtomicLong(0);
private static final AtomicLong ourTotalCount = new AtomicLong(0);
@Override
public void consume(Map<HMethod, Equations> map) {
public void consume(Map<HMember, Equations> map) {
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
new BytecodeAnalysisIndex.EquationsExternalizer().save(new DataOutputStream(stream), map);

View File

@@ -44,11 +44,11 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*;
interface CombinedData {
final class ParamKey {
final Method method;
final Member method;
final int i;
final boolean stable;
ParamKey(Method method, int i, boolean stable) {
ParamKey(Member method, int i, boolean stable) {
this.method = method;
this.i = i;
this.stable = stable;
@@ -84,12 +84,12 @@ interface CombinedData {
final class TrackableCallValue extends BasicValue implements Trackable {
private final int originInsnIndex;
final Method method;
final Member method;
final List<? extends BasicValue> args;
final boolean stableCall;
final boolean thisCall;
TrackableCallValue(int originInsnIndex, Type tp, Method method, List<? extends BasicValue> args, boolean stableCall, boolean thisCall) {
TrackableCallValue(int originInsnIndex, Type tp, Member method, List<? extends BasicValue> args, boolean stableCall, boolean thisCall) {
super(tp);
this.originInsnIndex = originInsnIndex;
this.method = method;
@@ -154,13 +154,13 @@ interface CombinedData {
final class CombinedAnalysis {
private final ControlFlowGraph controlFlow;
private final Method method;
private final Member method;
private final CombinedInterpreter interpreter;
private BasicValue returnValue;
private boolean exception;
private final MethodNode methodNode;
CombinedAnalysis(Method method, ControlFlowGraph controlFlow) {
CombinedAnalysis(Member method, ControlFlowGraph controlFlow) {
this.method = method;
this.controlFlow = controlFlow;
methodNode = controlFlow.methodNode;
@@ -537,8 +537,7 @@ final class CombinedInterpreter extends BasicInterpreter {
}
@Override
public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3)
throws AnalyzerException {
public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3) {
switch (insn.getOpcode()) {
case IASTORE:
case LASTORE:
@@ -581,7 +580,7 @@ final class CombinedInterpreter extends BasicInterpreter {
case INVOKEVIRTUAL:
case INVOKEINTERFACE: {
MethodInsnNode mNode = (MethodInsnNode)insn;
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
Member method = new Member(mNode.owner, mNode.name, mNode.desc);
TrackableCallValue value = methodCall(opCode, origin, method, values);
calls.add(value);
return value;
@@ -602,7 +601,7 @@ final class CombinedInterpreter extends BasicInterpreter {
}
@NotNull
private TrackableCallValue methodCall(int opCode, int origin, Method method, List<? extends BasicValue> values) {
private TrackableCallValue methodCall(int opCode, int origin, Member method, List<? extends BasicValue> values) {
Type retType = Type.getReturnType(method.methodDesc);
boolean stable = opCode == INVOKESTATIC || opCode == INVOKESPECIAL;
boolean thisCall = false;
@@ -637,14 +636,14 @@ final class CombinedInterpreter extends BasicInterpreter {
}
}
class NegationAnalysisFailure extends Exception {
class NegationAnalysisFailedException extends Exception {
}
final class NegationAnalysis {
private final ControlFlowGraph controlFlow;
private final Method method;
private final Member method;
private final NegationInterpreter interpreter;
private final MethodNode methodNode;
@@ -652,20 +651,20 @@ final class NegationAnalysis {
private BasicValue trueBranchValue;
private BasicValue falseBranchValue;
NegationAnalysis(Method method, ControlFlowGraph controlFlow) {
NegationAnalysis(Member method, ControlFlowGraph controlFlow) {
this.method = method;
this.controlFlow = controlFlow;
methodNode = controlFlow.methodNode;
interpreter = new NegationInterpreter(methodNode.instructions);
}
private static void checkAssertion(boolean assertion) throws NegationAnalysisFailure {
private static void checkAssertion(boolean assertion) throws NegationAnalysisFailedException {
if (!assertion) {
throw new NegationAnalysisFailure();
throw new NegationAnalysisFailedException();
}
}
final void analyze() throws AnalyzerException, NegationAnalysisFailure {
final void analyze() throws AnalyzerException, NegationAnalysisFailedException {
Frame<BasicValue> frame = createStartFrame();
int insnIndex = 0;
@@ -701,7 +700,7 @@ final class NegationAnalysis {
}
private void proceedBranch(Frame<BasicValue> startFrame, int startIndex, boolean branchValue)
throws NegationAnalysisFailure, AnalyzerException {
throws NegationAnalysisFailedException, AnalyzerException {
Frame<BasicValue> frame = new Frame<>(startFrame);
int insnIndex = startIndex;
@@ -816,7 +815,7 @@ final class NegationInterpreter extends BasicInterpreter {
case INVOKEINTERFACE:
boolean stable = opCode == INVOKESTATIC || opCode == INVOKESPECIAL;
MethodInsnNode mNode = (MethodInsnNode)insn;
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
Member method = new Member(mNode.owner, mNode.name, mNode.desc);
Type retType = Type.getReturnType(mNode.desc);
BasicValue receiver = null;
if (shift == 1) {

View File

@@ -527,7 +527,7 @@ class InOutInterpreter extends BasicInterpreter {
case INVOKEINTERFACE:
boolean stable = opCode == INVOKESTATIC || opCode == INVOKESPECIAL;
MethodInsnNode mNode = (MethodInsnNode)insn;
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
Member method = new Member(mNode.owner, mNode.name, mNode.desc);
Type retType = Type.getReturnType(mNode.desc);
boolean isRefRetType = retType.getSort() == Type.OBJECT || retType.getSort() == Type.ARRAY;
if (!Type.VOID_TYPE.equals(retType)) {

View File

@@ -263,6 +263,7 @@ final class Pending implements Result {
final class Effects implements Result {
static final Set<EffectQuantum> TOP_EFFECTS = Collections.singleton(EffectQuantum.TopEffectQuantum);
static final Effects VOLATILE_EFFECTS = new Effects(DataValue.UnknownDataValue2, Collections.singleton(EffectQuantum.TopEffectQuantum));
@NotNull final DataValue returnValue;
@NotNull final Set<EffectQuantum> effects;

View File

@@ -25,8 +25,9 @@ public abstract class Direction {
public static final Direction NullableOut = explicitDirection("NullableOut");
public static final Direction Pure = explicitDirection("Pure");
public static final Direction Throw = explicitDirection("Throw");
public static final Direction Volatile = explicitDirection("Volatile");
private static final List<Direction> ourConcreteDirections = Arrays.asList(Out, NullableOut, Pure, Throw);
private static final List<Direction> ourConcreteDirections = Arrays.asList(Out, NullableOut, Pure, Throw, Volatile);
private static final int CONCRETE_DIRECTIONS_OFFSET = ourConcreteDirections.size();
private static final int IN_OUT_OFFSET = 2; // nullity mask is 0/1
private static final int IN_THROW_OFFSET = 2 + Value.values().length;

View File

@@ -24,21 +24,21 @@ import java.security.MessageDigest;
*/
public final class EKey {
@NotNull
final MethodDescriptor method;
final MemberDescriptor member;
final int dirKey;
final boolean stable;
final boolean negated;
public EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable) {
this(method, direction, stable, false);
public EKey(@NotNull MemberDescriptor member, Direction direction, boolean stable) {
this(member, direction, stable, false);
}
EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable, boolean negated) {
this(method, direction.asInt(), stable, negated);
EKey(@NotNull MemberDescriptor member, Direction direction, boolean stable, boolean negated) {
this(member, direction.asInt(), stable, negated);
}
EKey(@NotNull MethodDescriptor method, int dirKey, boolean stable, boolean negated) {
this.method = method;
EKey(@NotNull MemberDescriptor member, int dirKey, boolean stable, boolean negated) {
this.member = member;
this.dirKey = dirKey;
this.stable = stable;
this.negated = negated;
@@ -54,13 +54,13 @@ public final class EKey {
if (stable != key.stable) return false;
if (negated != key.negated) return false;
if (dirKey != key.dirKey) return false;
if (!method.equals(key.method)) return false;
if (!member.equals(key.member)) return false;
return true;
}
@Override
public int hashCode() {
int result = method.hashCode();
int result = member.hashCode();
result = 31 * result + dirKey;
result = 31 * result + (stable ? 1 : 0);
result = 31 * result + (negated ? 1 : 0);
@@ -68,15 +68,15 @@ public final class EKey {
}
EKey invertStability() {
return new EKey(method, dirKey, !stable, negated);
return new EKey(member, dirKey, !stable, negated);
}
EKey mkStable() {
return stable ? this : new EKey(method, dirKey, true, negated);
return stable ? this : new EKey(member, dirKey, true, negated);
}
EKey mkUnstable() {
return stable ? new EKey(method, dirKey, false, negated) : this;
return stable ? new EKey(member, dirKey, false, negated) : this;
}
public EKey mkBase() {
@@ -84,16 +84,16 @@ public final class EKey {
}
EKey withDirection(Direction dir) {
return dirKey == dir.asInt() ? this : new EKey(method, dir, stable, false);
return dirKey == dir.asInt() ? this : new EKey(member, dir, stable, false);
}
EKey negate() {
return new EKey(method, dirKey, stable, true);
return new EKey(member, dirKey, stable, true);
}
public EKey hashed(MessageDigest md) {
HMethod hmethod = method.hashed(md);
return hmethod == method ? this : new EKey(hmethod, dirKey, stable, negated);
HMember hMember = member.hashed(md);
return hMember == member ? this : new EKey(hMember, dirKey, stable, negated);
}
public Direction getDirection() {
@@ -102,6 +102,6 @@ public final class EKey {
@Override
public String toString() {
return "Key [" + method + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + Direction.fromInt(dirKey) + "]";
return "Key [" + member + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + Direction.fromInt(dirKey) + "]";
}
}

View File

@@ -27,7 +27,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConve
/**
* Hashed representation of method.
*/
public final class HMethod implements MethodDescriptor {
public final class HMember implements MemberDescriptor {
// how many bytes are taken from class fqn digest
private static final int CLASS_HASH_SIZE = 10;
// how many bytes are taken from signature digest
@@ -37,7 +37,7 @@ public final class HMethod implements MethodDescriptor {
@NotNull
final byte[] myBytes;
HMethod(Method method, MessageDigest md) {
HMember(Member method, MessageDigest md) {
if (md == null) {
md = getMessageDigest();
}
@@ -50,7 +50,7 @@ public final class HMethod implements MethodDescriptor {
System.arraycopy(sigDigest, 0, myBytes, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
}
public HMethod(@NotNull byte[] bytes) {
public HMember(@NotNull byte[] bytes) {
myBytes = bytes;
}
@@ -58,7 +58,7 @@ public final class HMethod implements MethodDescriptor {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return Arrays.equals(myBytes, ((HMethod)o).myBytes);
return Arrays.equals(myBytes, ((HMember)o).myBytes);
}
@Override
@@ -68,7 +68,7 @@ public final class HMethod implements MethodDescriptor {
@NotNull
@Override
public HMethod hashed(MessageDigest md) {
public HMember hashed(MessageDigest md) {
return this;
}

View File

@@ -31,42 +31,42 @@ class HardCodedPurity {
private static final Set<Couple<String>> ownedFields = ContainerUtil.set(
new Couple<>("java/lang/AbstractStringBuilder", "value")
);
private static final Set<Method> thisChangingMethods = ContainerUtil.set(
new Method("java/lang/Throwable", "fillInStackTrace", "()Ljava/lang/Throwable;")
private static final Set<Member> thisChangingMethods = ContainerUtil.set(
new Member("java/lang/Throwable", "fillInStackTrace", "()Ljava/lang/Throwable;")
);
// Assumed that all these methods are not only pure, but return object which could be safely modified
private static final Set<Method> pureMethods = ContainerUtil.set(
private static final Set<Member> pureMethods = ContainerUtil.set(
// Maybe overloaded and be not pure, but this would be definitely bad code style
// Used in Throwable(Throwable) ctor, so this helps to infer purity of many exception constructors
new Method("java/lang/Throwable", "toString", "()Ljava/lang/String;"),
new Member("java/lang/Throwable", "toString", "()Ljava/lang/String;"),
// Declared in final class StringBuilder
new Method("java/lang/StringBuilder", "toString", "()Ljava/lang/String;"),
new Method("java/lang/StringBuffer", "toString", "()Ljava/lang/String;"),
new Member("java/lang/StringBuilder", "toString", "()Ljava/lang/String;"),
new Member("java/lang/StringBuffer", "toString", "()Ljava/lang/String;"),
// Native
new Method("java/lang/Object", "getClass", "()Ljava/lang/Class;"),
new Method("java/lang/Class", "getComponentType", "()Ljava/lang/Class;"),
new Method("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;"),
new Method("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;"),
new Method("java/lang/Float", "floatToRawIntBits", "(F)I"),
new Method("java/lang/Float", "intBitsToFloat", "(I)F"),
new Method("java/lang/Double", "doubleToRawLongBits", "(D)J"),
new Method("java/lang/Double", "longBitsToDouble", "(J)D")
new Member("java/lang/Object", "getClass", "()Ljava/lang/Class;"),
new Member("java/lang/Class", "getComponentType", "()Ljava/lang/Class;"),
new Member("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;"),
new Member("java/lang/reflect/Array", "newInstance", "(Ljava/lang/Class;[I)Ljava/lang/Object;"),
new Member("java/lang/Float", "floatToRawIntBits", "(F)I"),
new Member("java/lang/Float", "intBitsToFloat", "(I)F"),
new Member("java/lang/Double", "doubleToRawLongBits", "(D)J"),
new Member("java/lang/Double", "longBitsToDouble", "(J)D")
);
private static final Map<Method, Set<EffectQuantum>> solutions = new HashMap<>();
private static final Map<Member, Set<EffectQuantum>> solutions = new HashMap<>();
private static final Set<EffectQuantum> thisChange = Collections.singleton(EffectQuantum.ThisChangeQuantum);
static {
// Native
solutions.put(new Method("java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
solutions.put(new Member("java/lang/System", "arraycopy", "(Ljava/lang/Object;ILjava/lang/Object;II)V"),
Collections.singleton(new EffectQuantum.ParamChangeQuantum(2)));
solutions.put(new Method("java/lang/Object", "hashCode", "()I"), Collections.emptySet());
solutions.put(new Member("java/lang/Object", "hashCode", "()I"), Collections.emptySet());
}
static HardCodedPurity getInstance() {
return AGGRESSIVE_HARDCODED_PURITY ? new AggressiveHardCodedPurity() : new HardCodedPurity();
}
Effects getHardCodedSolution(Method method) {
Effects getHardCodedSolution(Member method) {
if (isThisChangingMethod(method)) {
return new Effects(isBuilderChainCall(method) ? DataValue.ThisDataValue : DataValue.UnknownDataValue1, thisChange);
}
@@ -79,11 +79,11 @@ class HardCodedPurity {
}
}
boolean isThisChangingMethod(Method method) {
boolean isThisChangingMethod(Member method) {
return isBuilderChainCall(method) || thisChangingMethods.contains(method);
}
boolean isBuilderChainCall(Method method) {
boolean isBuilderChainCall(Member method) {
// Those methods are virtual, thus contracts cannot be inferred automatically,
// but all possible implementations are controlled
// (only final classes j.l.StringBuilder and j.l.StringBuffer extend package-private j.l.AbstractStringBuilder)
@@ -91,7 +91,7 @@ class HardCodedPurity {
method.methodName.startsWith("append");
}
boolean isPureMethod(Method method) {
boolean isPureMethod(Member method) {
if(pureMethods.contains(method)) {
return true;
}
@@ -113,7 +113,7 @@ class HardCodedPurity {
"java/util/AbstractSet", "java/util/TreeSet");
@Override
boolean isThisChangingMethod(Method method) {
boolean isThisChangingMethod(Member method) {
if (method.methodName.equals("next") && method.methodDesc.startsWith("()") && method.internalClassName.equals("java/util/Iterator")) {
return true;
}
@@ -121,7 +121,7 @@ class HardCodedPurity {
}
@Override
boolean isPureMethod(Method method) {
boolean isPureMethod(Member method) {
if (method.methodName.equals("toString") && method.methodDesc.equals("()Ljava/lang/String;")) return true;
if (method.methodName.equals("iterator") && method.methodDesc.equals("()Ljava/util/Iterator;") &&
ITERABLES.contains(method.internalClassName)) {

View File

@@ -46,12 +46,12 @@ abstract class KeyedMethodVisitor extends ClassVisitor {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodNode node = new MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions);
Method method = new Method(className, node.name, node.desc);
Member method = new Member(className, node.name, node.desc);
boolean stable = stableClass || (node.access & STABLE_FLAGS) != 0 || "<init>".equals(node.name);
return visitMethod(node, method, new EKey(method, Out, stable));
}
@Nullable
abstract MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key);
abstract MethodVisitor visitMethod(final MethodNode node, Member method, final EKey key);
}

View File

@@ -34,10 +34,10 @@ final class LambdaIndy {
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
private final int myTag;
private final Type myFunctionalMethodType;
private final Method myMethod;
private final Member myMethod;
private final Type myFunctionalInterfaceType;
private LambdaIndy(int tag, Type functionalMethodType, Method lambdaMethod, Type functionalInterfaceType) {
private LambdaIndy(int tag, Type functionalMethodType, Member lambdaMethod, Type functionalInterfaceType) {
myTag = tag;
myFunctionalMethodType = functionalMethodType;
myMethod = lambdaMethod;
@@ -69,7 +69,7 @@ final class LambdaIndy {
return myFunctionalMethodType;
}
public Method getMethod() {
public Member getMethod() {
return myMethod;
}
@@ -105,7 +105,7 @@ final class LambdaIndy {
indyNode.bsmArgs.length >= 3 && indyNode.bsmArgs[1] instanceof Handle && indyNode.bsmArgs[2] instanceof Type) {
Handle lambdaBody = (Handle)indyNode.bsmArgs[1];
Type targetType = (Type)indyNode.bsmArgs[2];
Method targetMethod = new Method(lambdaBody.getOwner(), lambdaBody.getName(), lambdaBody.getDesc());
Member targetMethod = new Member(lambdaBody.getOwner(), lambdaBody.getName(), lambdaBody.getDesc());
Type retType = Type.getReturnType(indyNode.desc);
return new LambdaIndy(lambdaBody.getTag(), targetType, targetMethod, retType);
}

View File

@@ -21,7 +21,7 @@ import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode;
import java.security.MessageDigest;
public final class Method implements MethodDescriptor {
public final class Member implements MemberDescriptor {
final String internalClassName;
final String methodName;
final String methodDesc;
@@ -30,7 +30,7 @@ public final class Method implements MethodDescriptor {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Method method = (Method) o;
Member method = (Member) o;
return internalClassName.equals(method.internalClassName) && methodDesc.equals(method.methodDesc) && methodName.equals(method.methodName);
}
@@ -49,7 +49,7 @@ public final class Method implements MethodDescriptor {
* @param methodName method name
* @param methodDesc method descriptor in asm format
*/
public Method(String internalClassName, String methodName, String methodDesc) {
public Member(String internalClassName, String methodName, String methodDesc) {
this.internalClassName = internalClassName;
this.methodName = methodName;
this.methodDesc = methodDesc;
@@ -60,7 +60,7 @@ public final class Method implements MethodDescriptor {
*
* @param mNode asm node from which method key is extracted
*/
public Method(MethodInsnNode mNode) {
public Member(MethodInsnNode mNode) {
this.internalClassName = mNode.owner;
this.methodName = mNode.name;
this.methodDesc = mNode.desc;
@@ -68,8 +68,8 @@ public final class Method implements MethodDescriptor {
@NotNull
@Override
public HMethod hashed(@Nullable MessageDigest md) {
return new HMethod(this, md);
public HMember hashed(@Nullable MessageDigest md) {
return new HMember(this, md);
}
@Override

View File

@@ -21,9 +21,9 @@ import org.jetbrains.annotations.Nullable;
import java.security.MessageDigest;
/**
* An uniquely method (class name+method name+signature) identifier: either {@link Method} or {@link HMethod}.
* An uniquely method (class name+method name+signature) identifier: either {@link Member} or {@link HMember}.
*/
public interface MethodDescriptor {
public interface MemberDescriptor {
/**
* Creates and returns the hashed representation of this method descriptor.
* May return itself if already hashed. Note that hashed descriptor is not equal to
@@ -32,5 +32,6 @@ public interface MethodDescriptor {
* @param md message digest to use for hashing (could be null to use the default one)
* @return a corresponding HMethod.
*/
@NotNull HMethod hashed(@Nullable MessageDigest md);
@NotNull
HMember hashed(@Nullable MessageDigest md);
}

View File

@@ -304,8 +304,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
}
@Override
public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3)
throws AnalyzerException {
public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3) {
if (value1 instanceof Calls) {
delta = ((Calls)value1).mergedLabels;
}
@@ -341,7 +340,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
if (origins[insnIndex]) {
boolean stable = opCode == INVOKESTATIC || opCode == INVOKESPECIAL;
MethodInsnNode mNode = ((MethodInsnNode)insn);
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
Member method = new Member(mNode.owner, mNode.name, mNode.desc);
int label = 1 << originsMapping[insnIndex];
if (keys[insnIndex] == null) {
keys[insnIndex] = new EKey(method, Direction.NullableOut, stable);

View File

@@ -693,7 +693,7 @@ abstract class NullityInterpreter extends BasicInterpreter {
case INVOKESTATIC:
case INVOKEVIRTUAL:
MethodInsnNode methodNode = (MethodInsnNode)insn;
methodCall(opcode, new Method(methodNode.owner, methodNode.name, methodNode.desc), values);
methodCall(opcode, new Member(methodNode.owner, methodNode.name, methodNode.desc), values);
break;
case INVOKEDYNAMIC:
LambdaIndy lambda = LambdaIndy.from((InvokeDynamicInsnNode)insn);
@@ -708,7 +708,7 @@ abstract class NullityInterpreter extends BasicInterpreter {
return super.naryOperation(insn, values);
}
private void methodCall(int opcode, Method method, List<? extends BasicValue> values) throws AnalyzerException {
private void methodCall(int opcode, Member method, List<? extends BasicValue> values) throws AnalyzerException {
if (opcode != INVOKESTATIC && values.remove(0) instanceof ParamValue) {
subResult = NPE;
}

View File

@@ -92,11 +92,8 @@ public class ProjectBytecodeAnalysis {
return annotation;
}
}
return null;
}
else {
return null;
}
return null;
}
@NotNull
@@ -275,7 +272,7 @@ public class ProjectBytecodeAnalysis {
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
final Solver nullableSolver = new Solver(new ELattice<>(Value.Null, Value.Top), Value.Top);
final EKey nullableKey = new EKey(notNullKey.method, notNullKey.dirKey + 1, true, false);
final EKey nullableKey = new EKey(notNullKey.member, notNullKey.dirKey + 1, true, false);
collectEquations(Collections.singletonList(nullableKey), nullableSolver);
Map<EKey, Value> nullableSolutions = nullableSolver.solve();
// subtle point
@@ -327,18 +324,18 @@ public class ProjectBytecodeAnalysis {
}
private static EKey withStability(EKey key, boolean stability) {
return new EKey(key.method, key.dirKey, stability, false);
return new EKey(key.member, key.dirKey, stability, false);
}
private void collectPurityEquations(EKey key, PuritySolver puritySolver)
throws EquationsLimitException {
HashSet<EKey> queued = new HashSet<>();
Stack<EKey> queue = new Stack<>();
ArrayDeque<EKey> queue = new ArrayDeque<>();
queue.push(key);
queued.add(key);
while (!queue.empty()) {
while (!queue.isEmpty()) {
if (queued.size() > EQUATIONS_LIMIT) {
throw new EquationsLimitException();
}
@@ -347,7 +344,7 @@ public class ProjectBytecodeAnalysis {
boolean stable = true;
Effects combined = null;
for (Equations equations : myEquationProvider.getEquations(curKey.method)) {
for (Equations equations : myEquationProvider.getEquations(curKey.member)) {
stable &= equations.stable;
Effects effects = (Effects)equations.find(curKey.getDirection())
.orElseGet(() -> new Effects(DataValue.UnknownDataValue1, Effects.TOP_EFFECTS));
@@ -358,6 +355,7 @@ public class ProjectBytecodeAnalysis {
puritySolver.addEquation(withStability(curKey, stable), combined);
}
}
puritySolver.addPlainFieldEquations(md -> true);
}
private void collectEquations(List<EKey> keys, Solver solver) throws EquationsLimitException {
@@ -376,7 +374,7 @@ public class ProjectBytecodeAnalysis {
ProgressManager.checkCanceled();
EKey curKey = queue.pop();
for (Equations equations : myEquationProvider.getEquations(curKey.method)) {
for (Equations equations : myEquationProvider.getEquations(curKey.member)) {
Result result = equations.find(curKey.getDirection()).orElseGet(solver::getUnknownResult);
solver.addEquation(new Equation(withStability(curKey, equations.stable), result));
result.dependencies().filter(queued::add).forEach(queue::push);
@@ -387,7 +385,7 @@ public class ProjectBytecodeAnalysis {
private void collectSingleEquation(EKey curKey, Solver solver) {
ProgressManager.checkCanceled();
for (Equations equations : myEquationProvider.getEquations(curKey.method)) {
for (Equations equations : myEquationProvider.getEquations(curKey.member)) {
Result result = equations.find(curKey.getDirection()).orElseGet(solver::getUnknownResult);
solver.addEquation(new Equation(withStability(curKey, equations.stable), result));
}
@@ -401,7 +399,7 @@ public class ProjectBytecodeAnalysis {
return annotation;
}
static abstract class EquationProvider<T extends MethodDescriptor> {
static abstract class EquationProvider<T extends MemberDescriptor> {
final Map<T, List<Equations>> myEquationCache = ContainerUtil.createConcurrentSoftValueMap();
final Project myProject;
@@ -412,28 +410,28 @@ public class ProjectBytecodeAnalysis {
abstract EKey adaptKey(@NotNull EKey key, MessageDigest messageDigest);
abstract List<Equations> getEquations(MethodDescriptor method);
abstract List<Equations> getEquations(MemberDescriptor method);
}
/**
* PlainEquationProvider (used for debug purposes)
* All EKey's are not hashed; persistent index is not used to store equations
*/
static class PlainEquationProvider extends EquationProvider<Method> {
static class PlainEquationProvider extends EquationProvider<Member> {
PlainEquationProvider(Project project) {
super(project);
}
@Override
public EKey adaptKey(@NotNull EKey key, MessageDigest messageDigest) {
assert key.method instanceof Method;
assert key.member instanceof Member;
return key;
}
@Override
public List<Equations> getEquations(MethodDescriptor methodDescriptor) {
assert methodDescriptor instanceof Method;
Method method = (Method)methodDescriptor;
public List<Equations> getEquations(MemberDescriptor memberDescriptor) {
assert memberDescriptor instanceof Member;
Member method = (Member)memberDescriptor;
List<Equations> equations = myEquationCache.get(method);
return equations == null ? loadEquations(method) : equations;
}
@@ -463,13 +461,13 @@ public class ProjectBytecodeAnalysis {
return null;
}
private List<Equations> loadEquations(Method method) {
private List<Equations> loadEquations(Member method) {
VirtualFile file = findClassFile(method.internalClassName);
if (file == null) return Collections.emptyList();
try {
Map<EKey, Equations> map =
ClassDataIndexer.processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
Map<Method, List<Equations>> groups = EntryStream.of(map).mapKeys(key -> (Method)key.method).grouping();
Map<Member, List<Equations>> groups = EntryStream.of(map).mapKeys(key -> (Member)key.member).grouping();
myEquationCache.putAll(groups);
return groups.getOrDefault(method, Collections.emptyList());
}
@@ -483,7 +481,7 @@ public class ProjectBytecodeAnalysis {
* IndexedEquationProvider (used normally)
* All EKey's are hashed after processing in ClassDataIndexer; persistent index is used to store equations
*/
static class IndexedEquationProvider extends EquationProvider<HMethod> {
static class IndexedEquationProvider extends EquationProvider<HMember> {
IndexedEquationProvider(Project project) {
super(project);
}
@@ -494,8 +492,8 @@ public class ProjectBytecodeAnalysis {
}
@Override
public List<Equations> getEquations(MethodDescriptor method) {
HMethod key = method.hashed(null);
public List<Equations> getEquations(MemberDescriptor method) {
HMember key = method.hashed(null);
return myEquationCache.computeIfAbsent(key, m -> BytecodeAnalysisIndex.getEquations(ProjectScope.getLibrariesScope(myProject), m));
}
}

View File

@@ -26,6 +26,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -44,7 +45,7 @@ public class PurityAnalysis {
* @return a purity equation or null for top result (either impure or unknown, impurity assumed)
*/
@Nullable
public static Equation analyze(Method method, MethodNode methodNode, boolean stable) {
public static Equation analyze(Member method, MethodNode methodNode, boolean stable) {
EKey key = new EKey(method, Direction.Pure, stable);
Effects hardCodedSolution = HardCodedPurity.getInstance().getHardCodedSolution(method);
if (hardCodedSolution != null) {
@@ -77,6 +78,8 @@ public class PurityAnalysis {
// data for data analysis
abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysis.Value {
public static final DataValue[] EMPTY = new DataValue[0];
private final int myHash;
DataValue(int hash) {
@@ -235,6 +238,30 @@ abstract class EffectQuantum {
}
};
static final class FieldReadQuantum extends EffectQuantum {
final EKey key;
public FieldReadQuantum(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 == ((FieldReadQuantum)o).key;
}
@Override
public String toString() {
return "Reads field " + key;
}
}
static final class ReturnChangeQuantum extends EffectQuantum {
final EKey key;
public ReturnChangeQuantum(EKey key) {
@@ -360,7 +387,7 @@ class DataInterpreter extends Interpreter<DataValue> {
}
@Override
public DataValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
public DataValue newOperation(AbstractInsnNode insn) {
switch (insn.getOpcode()) {
case Opcodes.NEW:
return DataValue.LocalDataValue;
@@ -374,6 +401,10 @@ class DataInterpreter extends Interpreter<DataValue> {
int size = (cst instanceof Long || cst instanceof Double) ? 2 : 1;
return size == 1 ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2;
case Opcodes.GETSTATIC:
FieldInsnNode fieldInsn = (FieldInsnNode)insn;
Member method = new Member(fieldInsn.owner, fieldInsn.name, fieldInsn.desc);
EKey key = new EKey(method, Direction.Volatile, true);
effects[methodNode.instructions.indexOf(insn)] = new EffectQuantum.FieldReadQuantum(key);
size = Type.getType(((FieldInsnNode)insn).desc).getSize();
return size == 1 ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2;
default:
@@ -382,7 +413,7 @@ class DataInterpreter extends Interpreter<DataValue> {
}
@Override
public DataValue binaryOperation(AbstractInsnNode insn, DataValue value1, DataValue value2) throws AnalyzerException {
public DataValue binaryOperation(AbstractInsnNode insn, DataValue value1, DataValue value2) {
switch (insn.getOpcode()) {
case Opcodes.LALOAD:
case Opcodes.DALOAD:
@@ -430,12 +461,12 @@ class DataInterpreter extends Interpreter<DataValue> {
}
@Override
public DataValue copyOperation(AbstractInsnNode insn, DataValue value) throws AnalyzerException {
public DataValue copyOperation(AbstractInsnNode insn, DataValue value) {
return value;
}
@Override
public DataValue naryOperation(AbstractInsnNode insn, List<? extends DataValue> values) throws AnalyzerException {
public DataValue naryOperation(AbstractInsnNode insn, List<? extends DataValue> values) {
int insnIndex = methodNode.instructions.indexOf(insn);
int opCode = insn.getOpcode();
switch (opCode) {
@@ -453,8 +484,8 @@ class DataInterpreter extends Interpreter<DataValue> {
case Opcodes.INVOKEINTERFACE:
boolean stable = opCode == Opcodes.INVOKESPECIAL || opCode == Opcodes.INVOKESTATIC;
MethodInsnNode mNode = ((MethodInsnNode)insn);
DataValue[] data = values.toArray(new DataValue[0]);
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
DataValue[] data = values.toArray(DataValue.EMPTY);
Member method = new Member(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;
@@ -495,7 +526,7 @@ class DataInterpreter extends Interpreter<DataValue> {
}
@Override
public DataValue unaryOperation(AbstractInsnNode insn, DataValue value) throws AnalyzerException {
public DataValue unaryOperation(AbstractInsnNode insn, DataValue value) {
switch (insn.getOpcode()) {
case Opcodes.LNEG:
@@ -509,6 +540,9 @@ class DataInterpreter extends Interpreter<DataValue> {
return DataValue.UnknownDataValue2;
case Opcodes.GETFIELD:
FieldInsnNode fieldInsn = ((FieldInsnNode)insn);
Member method = new Member(fieldInsn.owner, fieldInsn.name, fieldInsn.desc);
EKey key = new EKey(method, Direction.Volatile, true);
effects[methodNode.instructions.indexOf(insn)] = new EffectQuantum.FieldReadQuantum(key);
if (value == DataValue.ThisDataValue && HardCodedPurity.getInstance().isOwnedField(fieldInsn)) {
return DataValue.OwnedDataValue;
} else {
@@ -529,14 +563,14 @@ class DataInterpreter extends Interpreter<DataValue> {
}
@Override
public DataValue ternaryOperation(AbstractInsnNode insn, DataValue value1, DataValue value2, DataValue value3) throws AnalyzerException {
public DataValue ternaryOperation(AbstractInsnNode insn, DataValue value1, DataValue value2, DataValue value3) {
int insnIndex = methodNode.instructions.indexOf(insn);
effects[insnIndex] = getChangeQuantum(value1);
return DataValue.UnknownDataValue1;
}
@Override
public void returnOperation(AbstractInsnNode insn, DataValue value, DataValue expected) throws AnalyzerException {
public void returnOperation(AbstractInsnNode insn, DataValue value, DataValue expected) {
if (insn.getOpcode() == Opcodes.ARETURN) {
if (returnValue == null) {
returnValue = value;
@@ -561,7 +595,7 @@ class DataInterpreter extends Interpreter<DataValue> {
final class PuritySolver {
private HashMap<EKey, Effects> solved = new HashMap<>();
private HashMap<EKey, Set<EKey>> dependencies = new HashMap<>();
private final Stack<EKey> moving = new Stack<>();
private final ArrayDeque<EKey> moving = new ArrayDeque<>();
HashMap<EKey, Effects> pending = new HashMap<>();
void addEquation(EKey key, Effects effects) {
@@ -635,6 +669,10 @@ final class PuritySolver {
continue;
}
}
if (dEffect instanceof EffectQuantum.FieldReadQuantum && ((EffectQuantum.FieldReadQuantum)dEffect).key.equals(pKey)) {
newEffects.addAll(pEffects.effects);
continue;
}
newEffects.add(dEffect);
}
@@ -662,6 +700,16 @@ final class PuritySolver {
return solved;
}
public void addPlainFieldEquations(Predicate<MemberDescriptor> plainByDefault) {
for (EKey key : dependencies.keySet()) {
if(key.getDirection() == Direction.Volatile && plainByDefault.test(key.member)) {
// Absent fields are considered non-volatile
solved.putIfAbsent(key, new Effects(DataValue.UnknownDataValue1, Collections.emptySet()));
moving.add(key);
}
}
}
private static EffectQuantum.CallQuantum substitute(EffectQuantum.CallQuantum call, EKey pKey, Effects pEffects) {
List<DataValue> list = new ArrayList<>();
boolean same = true;
@@ -670,7 +718,7 @@ final class PuritySolver {
same &= newValue.equals(value);
list.add(newValue);
}
return same ? call : new EffectQuantum.CallQuantum(call.key, list.toArray(new DataValue[0]), call.isStatic);
return same ? call : new EffectQuantum.CallQuantum(call.key, list.toArray(DataValue.EMPTY), call.isStatic);
}
private static DataValue substitute(DataValue value, EKey key, Effects effects) {

View File

@@ -116,10 +116,10 @@ class ResultUtil {
final class CoreHKey {
@NotNull
final MethodDescriptor myMethod;
final MemberDescriptor myMethod;
final int dirKey;
CoreHKey(@NotNull MethodDescriptor method, int dirKey) {
CoreHKey(@NotNull MemberDescriptor method, int dirKey) {
this.myMethod = method;
this.dirKey = dirKey;
}
@@ -168,7 +168,7 @@ final class Solver {
void addEquation(Equation equation) {
EKey key = equation.key;
CoreHKey coreKey = new CoreHKey(key.method, key.dirKey);
CoreHKey coreKey = new CoreHKey(key.member, key.dirKey);
Equation previousEquation = equations.get(coreKey);
if (previousEquation == null) {

View File

@@ -2,11 +2,11 @@
package com.intellij.codeInspection.bytecodeAnalysis;
class TooComplexException extends RuntimeException {
TooComplexException(Method method, int steps) {
TooComplexException(Member method, int steps) {
super("limit is reached, steps: " + steps + " in method " + method);
}
static void check(Method method, int steps) {
static void check(Member method, int steps) {
if(steps >= Analysis.STEPS_LIMIT) {
throw new TooComplexException(method, steps);
}

View File

@@ -4,11 +4,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.io.BufferedInputStream byte[] getBufIfOpen()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.io.BufferedInputStream int read(byte[], int, int) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -2987,16 +2987,6 @@
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.lang.Thread java.lang.Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.lang.Thread java.lang.Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.lang.Thread long getId()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -86,11 +86,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.security.Policy boolean isSet()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.security.Policy java.lang.String getType()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -142,16 +142,6 @@
<item name='java.sql.DriverManager boolean isDriverAllowed(java.sql.Driver, java.lang.ClassLoader) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.sql.DriverManager int getLoginTimeout()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.sql.DriverManager java.io.PrintStream getLogStream()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.sql.DriverManager java.sql.Connection getConnection(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -204,11 +194,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.sql.SQLException java.sql.SQLException getNextException()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.sql.SQLException java.util.Iterator&lt;java.lang.Throwable&gt; iterator()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -9,11 +9,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicBoolean boolean get()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicBoolean java.lang.String toString()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -30,37 +25,12 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicInteger double doubleValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicInteger float floatValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicInteger int get()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicInteger int intValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicInteger java.lang.String toString()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.atomic.AtomicInteger long longValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicIntegerArray AtomicIntegerArray(int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -148,37 +118,12 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicLong double doubleValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicLong float floatValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicLong int intValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicLong java.lang.String toString()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.atomic.AtomicLong long get()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicLong long longValue()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicLongArray AtomicLongArray(int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -273,19 +218,9 @@
<item name='java.util.concurrent.atomic.AtomicMarkableReference V get(boolean[]) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.atomic.AtomicMarkableReference V getReference()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicMarkableReference boolean compareAndSet(V, V, boolean, boolean) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.concurrent.atomic.AtomicMarkableReference boolean isMarked()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicMarkableReference.Pair Pair(T, boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -307,11 +242,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicReference V get()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicReference java.lang.String toString()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -420,19 +350,9 @@
<item name='java.util.concurrent.atomic.AtomicStampedReference V get(int[]) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.atomic.AtomicStampedReference V getReference()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicStampedReference boolean compareAndSet(V, V, int, int) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.concurrent.atomic.AtomicStampedReference int getStamp()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.atomic.AtomicStampedReference.Pair Pair(T, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -2,29 +2,9 @@
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean acquireQueued(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node, int) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean apparentlyFirstQueuedIsExclusive()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean findNodeFromTail(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean findNodeFromTail(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean hasContended()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean hasQueuedThreads()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean hasWaiters(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -33,19 +13,9 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean isOnSyncQueue(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean isOnSyncQueue(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean isQueued(java.lang.Thread)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer boolean isQueued(java.lang.Thread) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -78,16 +48,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer int getQueueLength()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer int getState()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer int getWaitQueueLength(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -103,15 +63,9 @@
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer java.lang.Thread fullGetFirstQueuedThread()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer java.lang.Thread getFirstQueuedThread()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer java.util.Collection&lt;java.lang.Thread&gt; getWaitingThreads(java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject) 0'>
@@ -183,11 +137,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.AbstractQueuedSynchronizer.Node java.util.concurrent.locks.AbstractQueuedSynchronizer.Node predecessor()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.concurrent.locks.LockSupport LockSupport()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -33,11 +33,6 @@
<item name='org.apache.velocity.runtime.RuntimeInstance boolean invokeVelocimacro(java.lang.String, java.lang.String, java.lang.String[], org.apache.velocity.context.Context, java.io.Writer) 4'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.velocity.runtime.RuntimeInstance boolean isInitialized()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.RuntimeInstance boolean isVelocimacro(java.lang.String, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -99,7 +99,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
}
private static void checkLeakingParameters(Class<?> jClass) throws IOException {
final HashMap<Method, boolean[]> map = new HashMap<>();
final HashMap<Member, boolean[]> map = new HashMap<>();
// collecting leakedParameters
final ClassReader classReader = new ClassReader(jClass.getResourceAsStream("/" + jClass.getName().replace('.', '/') + ".class"));
@@ -107,7 +107,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
final MethodNode node = new MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions);
final Method method = new Method(classReader.getClassName(), name, desc);
final Member method = new Member(classReader.getClassName(), name, desc);
return new MethodVisitor(Opcodes.API_VERSION, node) {
@Override
public void visitEnd() {
@@ -122,7 +122,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
for (java.lang.reflect.Method jMethod : jClass.getDeclaredMethods()) {
Method method = new Method(Type.getType(jClass).getInternalName(), jMethod.getName(), Type.getMethodDescriptor(jMethod));
Member method = new Member(Type.getType(jClass).getInternalName(), jMethod.getName(), Type.getMethodDescriptor(jMethod));
Annotation[][] annotations = jMethod.getParameterAnnotations();
for (int i = 0; i < annotations.length; i++) {
boolean isLeaking = false;
@@ -211,16 +211,16 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
for (java.lang.reflect.Method javaMethod : javaClass.getDeclaredMethods()) {
if(javaMethod.isSynthetic()) continue; // skip lambda runtime representation
Method method = new Method(Type.getType(javaClass).getInternalName(), javaMethod.getName(), Type.getMethodDescriptor(javaMethod));
Member method = new Member(Type.getType(javaClass).getInternalName(), javaMethod.getName(), Type.getMethodDescriptor(javaMethod));
boolean noKey = javaMethod.getAnnotation(ExpectNoPsiKey.class) != null;
PsiMethod[] methods = psiClass.findMethodsByName(javaMethod.getName(), false);
assertTrue("Must be single method: "+javaMethod, methods.length == 1);
assertEquals("Must be single method: " + javaMethod, 1, methods.length);
PsiMethod psiMethod = methods[0];
checkCompoundId(method, psiMethod, noKey);
}
for (Constructor<?> constructor : javaClass.getDeclaredConstructors()) {
Method method = new Method(Type.getType(javaClass).getInternalName(), "<init>", Type.getConstructorDescriptor(constructor));
Member method = new Member(Type.getType(javaClass).getInternalName(), "<init>", Type.getConstructorDescriptor(constructor));
boolean noKey = constructor.getAnnotation(ExpectNoPsiKey.class) != null;
PsiMethod[] constructors = psiClass.getConstructors();
PsiMethod psiMethod = constructors[0];
@@ -228,7 +228,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
}
}
private void checkCompoundId(Method method, PsiMethod psiMethod, boolean noKey) {
private void checkCompoundId(Member method, PsiMethod psiMethod, boolean noKey) {
/*
System.out.println();
System.out.println(method.internalClassName);
@@ -239,11 +239,11 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
EKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out);
if (noKey) {
assertTrue(null == psiKey);
assertNull(psiKey);
return;
}
else {
assertFalse(null == psiKey);
assertNotNull(psiKey);
}
EKey asmKey = new EKey(method, Direction.Out, true);
Assert.assertEquals(asmKey, psiKey);

View File

@@ -27,7 +27,11 @@ import java.nio.file.Files;
/**
* @author lambdamix
*/
@SuppressWarnings({"unused", "IOResourceOpenedButNotSafelyClosed"})
public class Test01 {
boolean plainFlag;
volatile boolean volatileFlag;
static void f(@ExpectNotNull Object o1, @ExpectNotNull Object o2) {
if (o1 == null) throw new NullPointerException();
else s(o2, o2);

View File

@@ -21,6 +21,7 @@ import com.intellij.java.codeInspection.bytecodeAnalysis.ExpectNotNull;
/**
* @author lambdamix
*/
@SuppressWarnings("unused")
public final class Test02 {
@ExpectContract(pure = true)
@ExpectNotNull
@@ -33,4 +34,13 @@ public final class Test02 {
public String notNullStringDelegate() {
return notNullString();
}
public boolean getFlagVolatile(@ExpectNotNull Test01 test01) {
return test01.volatileFlag;
}
@ExpectContract(pure = true)
public boolean getFlagPlain(@ExpectNotNull Test01 test01) {
return test01.plainFlag;
}
}