mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
IDEA-185138 Bytecode purity inference: do not infer method as pure if it reads the volatile field
This commit is contained in:
@@ -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];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) + "]";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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<java.lang.Throwable> iterator()'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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<java.lang.Thread> 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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user