mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-144129: support for inferred negated contracts
This commit is contained in:
+2
-2
@@ -107,7 +107,7 @@ public class BytecodeAnalysisConverter {
|
||||
byte[] digest = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
return new HKey(digest, mkDirectionKey(key.direction), key.stable);
|
||||
return new HKey(digest, mkDirectionKey(key.direction), key.stable, key.negated);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,7 @@ public class BytecodeAnalysisConverter {
|
||||
byte[] digest = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
return new HKey(digest, mkDirectionKey(direction), true);
|
||||
return new HKey(digest, mkDirectionKey(direction), true, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
+5
-3
@@ -40,7 +40,7 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
|
||||
private static final ClassDataIndexer INDEXER = new ClassDataIndexer();
|
||||
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
|
||||
|
||||
private static final int ourInternalVersion = 4;
|
||||
private static final int ourInternalVersion = 5;
|
||||
private static final boolean ourEnabled = SystemProperties.getBooleanProperty("idea.enable.bytecode.contract.inference", true);
|
||||
|
||||
@NotNull
|
||||
@@ -145,7 +145,8 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
|
||||
DataInputOutputUtil.writeINT(out, ids.length);
|
||||
for (HKey hKey : ids) {
|
||||
out.write(hKey.key);
|
||||
DataInputOutputUtil.writeINT(out, hKey.dirKey);
|
||||
int rawDirKey = hKey.negated ? -hKey.dirKey : hKey.dirKey;
|
||||
DataInputOutputUtil.writeINT(out, rawDirKey);
|
||||
out.writeBoolean(hKey.stable);
|
||||
}
|
||||
}
|
||||
@@ -180,7 +181,8 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Bytes, HEquat
|
||||
for (int bi = 0; bi < bytes.length; bi++) {
|
||||
bytes[bi] = in.readByte();
|
||||
}
|
||||
ids[j] = new HKey(bytes, DataInputOutputUtil.readINT(in), in.readBoolean());
|
||||
int rawDirKey = DataInputOutputUtil.readINT(in);
|
||||
ids[j] = new HKey(bytes, Math.abs(rawDirKey), in.readBoolean(), rawDirKey < 0);
|
||||
}
|
||||
components[i] = new HComponent(value, ids);
|
||||
}
|
||||
|
||||
+108
-5
@@ -166,7 +166,8 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
if (branching) {
|
||||
RichControlFlow richControlFlow = new RichControlFlow(graph, dfs);
|
||||
if (richControlFlow.reducible()) {
|
||||
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isInterestingResult, stable, jsr, equations);
|
||||
NegationAnalysis negated = tryNegation(method, argumentTypes, graph, isBooleanResult, dfs, jsr);
|
||||
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isBooleanResult, stable, jsr, equations, negated);
|
||||
return Pair.create(primaryKey, equations);
|
||||
}
|
||||
LOG.debug(method + ": CFG is not reducible");
|
||||
@@ -190,15 +191,107 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
}
|
||||
|
||||
private NegationAnalysis tryNegation(final Method method,
|
||||
final Type[] argumentTypes,
|
||||
final ControlFlowGraph graph,
|
||||
final boolean isBooleanResult,
|
||||
final DFSTree dfs,
|
||||
final boolean jsr) throws AnalyzerException {
|
||||
|
||||
class Util {
|
||||
boolean isMethodCall(int opCode) {
|
||||
return opCode == Opcodes.INVOKESTATIC ||
|
||||
opCode == Opcodes.INVOKESPECIAL ||
|
||||
opCode == Opcodes.INVOKEVIRTUAL ||
|
||||
opCode == Opcodes.INVOKEINTERFACE;
|
||||
}
|
||||
|
||||
boolean singleIfBranch() {
|
||||
int branch = 0;
|
||||
|
||||
for (int i = 0; i < graph.transitions.length; i++) {
|
||||
int[] transition = graph.transitions[i];
|
||||
if (transition.length == 2) {
|
||||
branch++;
|
||||
int opCode = graph.methodNode.instructions.get(i).getOpcode();
|
||||
boolean isIfInsn = opCode == Opcodes.IFEQ || opCode == Opcodes.IFNE;
|
||||
if (!isIfInsn) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (branch > 1)
|
||||
return false;
|
||||
}
|
||||
return branch == 1;
|
||||
}
|
||||
|
||||
boolean singleMethodCall() {
|
||||
int callCount = 0;
|
||||
for (int i = 0; i < graph.transitions.length; i++) {
|
||||
if (isMethodCall(graph.methodNode.instructions.get(i).getOpcode())) {
|
||||
callCount++;
|
||||
if (callCount > 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return callCount == 1;
|
||||
}
|
||||
|
||||
public boolean booleanConstResult() {
|
||||
try {
|
||||
final boolean[] origins =
|
||||
OriginsAnalysis.resultOrigins(
|
||||
leakingParametersAndFrames(method, graph.methodNode, argumentTypes, jsr).frames,
|
||||
graph.methodNode.instructions,
|
||||
graph);
|
||||
|
||||
for (int i = 0; i < origins.length; i++) {
|
||||
if (origins[i]) {
|
||||
int opCode = graph.methodNode.instructions.get(i).getOpcode();
|
||||
boolean isBooleanConst = opCode == Opcodes.ICONST_0 || opCode == Opcodes.ICONST_1;
|
||||
if (!isBooleanConst) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (AnalyzerException ignore) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (graph.methodNode.instructions.size() < 20 && isBooleanResult && dfs.back.isEmpty() && !jsr) {
|
||||
Util util = new Util();
|
||||
if (util.singleIfBranch() && util.singleMethodCall() && util.booleanConstResult()) {
|
||||
NegationAnalysis analyzer = new NegationAnalysis(method, graph);
|
||||
try {
|
||||
analyzer.analyze();
|
||||
return analyzer;
|
||||
}
|
||||
catch (NegationAnalysisFailure ignore) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processBranchingMethod(final Method method,
|
||||
final MethodNode methodNode,
|
||||
final RichControlFlow richControlFlow,
|
||||
Type[] argumentTypes,
|
||||
boolean isReferenceResult,
|
||||
boolean isInterestingResult,
|
||||
boolean isBooleanResult,
|
||||
final boolean stable,
|
||||
boolean jsr,
|
||||
List<Equation<Key, Value>> result) throws AnalyzerException {
|
||||
List<Equation<Key, Value>> result,
|
||||
NegationAnalysis negatedAnalysis) throws AnalyzerException {
|
||||
boolean isInterestingResult = isBooleanResult || isReferenceResult;
|
||||
boolean maybeLeakingParameter = isInterestingResult;
|
||||
for (Type argType : argumentTypes) {
|
||||
if (ASMUtils.isReferenceType(argType)) {
|
||||
@@ -268,9 +361,19 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
}
|
||||
else {
|
||||
// may be null on some branch, running "null->..." analysis
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable, sharedPendingStates).analyze());
|
||||
if (isBooleanResult && negatedAnalysis != null) {
|
||||
result.add(negatedAnalysis.contractEquation(i, Value.Null, stable));
|
||||
}
|
||||
else {
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable, sharedPendingStates).analyze());
|
||||
}
|
||||
}
|
||||
if (isBooleanResult && negatedAnalysis != null) {
|
||||
result.add(negatedAnalysis.contractEquation(i, Value.NotNull, stable));
|
||||
}
|
||||
else {
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable, sharedPendingStates).analyze());
|
||||
}
|
||||
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable, sharedPendingStates).analyze());
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so a contract is the same as for the whole method
|
||||
|
||||
+194
-1
@@ -345,7 +345,7 @@ final class CombinedAnalysis {
|
||||
|
||||
Type[] args = Type.getArgumentTypes(methodNode.desc);
|
||||
int local = 0;
|
||||
if ((methodNode.access & Opcodes.ACC_STATIC) == 0) {
|
||||
if ((methodNode.access & ACC_STATIC) == 0) {
|
||||
frame.setLocal(local++, ThisValue);
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
@@ -582,3 +582,196 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
return track(origin, super.naryOperation(insn, values));
|
||||
}
|
||||
}
|
||||
|
||||
class NegationAnalysisFailure extends Exception {
|
||||
|
||||
}
|
||||
|
||||
final class NegationAnalysis {
|
||||
|
||||
private final ControlFlowGraph controlFlow;
|
||||
private final Method method;
|
||||
private final NegationInterpreter interpreter;
|
||||
private final MethodNode methodNode;
|
||||
|
||||
private TrackableCallValue conditionValue;
|
||||
private BasicValue trueBranchValue;
|
||||
private BasicValue falseBranchValue;
|
||||
|
||||
NegationAnalysis(Method 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 {
|
||||
if (!assertion) {
|
||||
throw new NegationAnalysisFailure();
|
||||
}
|
||||
}
|
||||
|
||||
final void analyze() throws AnalyzerException, NegationAnalysisFailure {
|
||||
Frame<BasicValue> frame = createStartFrame();
|
||||
int insnIndex = 0;
|
||||
|
||||
while (true) {
|
||||
AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex);
|
||||
switch (insnNode.getType()) {
|
||||
case AbstractInsnNode.LABEL:
|
||||
case AbstractInsnNode.LINE:
|
||||
case AbstractInsnNode.FRAME:
|
||||
insnIndex = controlFlow.transitions[insnIndex][0];
|
||||
break;
|
||||
default:
|
||||
switch (insnNode.getOpcode()) {
|
||||
case IFEQ:
|
||||
case IFNE:
|
||||
BasicValue conValue = popValue(frame);
|
||||
checkAssertion(conValue instanceof TrackableCallValue);
|
||||
frame.execute(insnNode, interpreter);
|
||||
conditionValue = (TrackableCallValue)conValue;
|
||||
int jumpIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
|
||||
int nextIndex = insnIndex + 1;
|
||||
proceedBranch(frame, jumpIndex, IFNE == insnNode.getOpcode());
|
||||
proceedBranch(frame, nextIndex, IFEQ == insnNode.getOpcode());
|
||||
checkAssertion(FalseValue == trueBranchValue);
|
||||
checkAssertion(TrueValue == falseBranchValue);
|
||||
return;
|
||||
default:
|
||||
frame.execute(insnNode, interpreter);
|
||||
insnIndex = controlFlow.transitions[insnIndex][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void proceedBranch(Frame<BasicValue> startFrame, int startIndex, boolean branchValue)
|
||||
throws NegationAnalysisFailure, AnalyzerException {
|
||||
|
||||
Frame<BasicValue> frame = new Frame<BasicValue>(startFrame);
|
||||
int insnIndex = startIndex;
|
||||
|
||||
while (true) {
|
||||
AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex);
|
||||
switch (insnNode.getType()) {
|
||||
case AbstractInsnNode.LABEL:
|
||||
case AbstractInsnNode.LINE:
|
||||
case AbstractInsnNode.FRAME:
|
||||
insnIndex = controlFlow.transitions[insnIndex][0];
|
||||
break;
|
||||
default:
|
||||
switch (insnNode.getOpcode()) {
|
||||
case IRETURN:
|
||||
BasicValue returnValue = frame.pop();
|
||||
if (branchValue) {
|
||||
trueBranchValue = returnValue;
|
||||
}
|
||||
else {
|
||||
falseBranchValue = returnValue;
|
||||
}
|
||||
return;
|
||||
default:
|
||||
checkAssertion(controlFlow.transitions[insnIndex].length == 1);
|
||||
frame.execute(insnNode, interpreter);
|
||||
insnIndex = controlFlow.transitions[insnIndex][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Equation<Key, Value> contractEquation(int i, Value inValue, boolean stable) {
|
||||
final Key key = new Key(method, new InOut(i, inValue), stable);
|
||||
final Result<Key, Value> result;
|
||||
HashSet<Key> keys = new HashSet<Key>();
|
||||
for (int argI = 0; argI < conditionValue.args.size(); argI++) {
|
||||
BasicValue arg = conditionValue.args.get(argI);
|
||||
if (arg instanceof NthParamValue) {
|
||||
NthParamValue npv = (NthParamValue)arg;
|
||||
if (npv.n == i) {
|
||||
keys.add(new Key(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keys.isEmpty()) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
} else {
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
|
||||
}
|
||||
return new Equation<Key, Value>(key, result);
|
||||
}
|
||||
|
||||
final Frame<BasicValue> createStartFrame() {
|
||||
Frame<BasicValue> frame = new Frame<BasicValue>(methodNode.maxLocals, methodNode.maxStack);
|
||||
Type returnType = Type.getReturnType(methodNode.desc);
|
||||
BasicValue returnValue = Type.VOID_TYPE.equals(returnType) ? null : new BasicValue(returnType);
|
||||
frame.setReturn(returnValue);
|
||||
|
||||
Type[] args = Type.getArgumentTypes(methodNode.desc);
|
||||
int local = 0;
|
||||
if ((methodNode.access & ACC_STATIC) == 0) {
|
||||
frame.setLocal(local++, ThisValue);
|
||||
}
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
BasicValue value = new NthParamValue(args[i], i);
|
||||
frame.setLocal(local++, value);
|
||||
if (args[i].getSize() == 2) {
|
||||
frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE);
|
||||
}
|
||||
}
|
||||
while (local < methodNode.maxLocals) {
|
||||
frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
private static BasicValue popValue(Frame<BasicValue> frame) {
|
||||
return frame.getStack(frame.getStackSize() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
final class NegationInterpreter extends BasicInterpreter {
|
||||
private final InsnList insns;
|
||||
|
||||
NegationInterpreter(InsnList insns) {
|
||||
this.insns = insns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException {
|
||||
switch (insn.getOpcode()) {
|
||||
case ICONST_0:
|
||||
return FalseValue;
|
||||
case ICONST_1:
|
||||
return TrueValue;
|
||||
default:
|
||||
return super.newOperation(insn);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> values) throws AnalyzerException {
|
||||
int opCode = insn.getOpcode();
|
||||
int shift = opCode == INVOKESTATIC ? 0 : 1;
|
||||
int origin = insns.indexOf(insn);
|
||||
|
||||
switch (opCode) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
case INVOKEVIRTUAL:
|
||||
case INVOKEINTERFACE:
|
||||
boolean stable = opCode == INVOKESTATIC || opCode == INVOKESPECIAL;
|
||||
MethodInsnNode mNode = (MethodInsnNode)insn;
|
||||
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
|
||||
Type retType = Type.getReturnType(mNode.desc);
|
||||
BasicValue receiver = null;
|
||||
if (shift == 1) {
|
||||
receiver = values.remove(0);
|
||||
}
|
||||
boolean thisCall = (opCode == INVOKEINTERFACE || opCode == INVOKEVIRTUAL) && receiver == ThisValue;
|
||||
return new TrackableCallValue(origin, retType, method, values, stable, thisCall);
|
||||
default:
|
||||
return super.naryOperation(insn, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,11 +198,20 @@ final class Key {
|
||||
final Method method;
|
||||
final Direction direction;
|
||||
final boolean stable;
|
||||
final boolean negated;
|
||||
|
||||
Key(Method method, Direction direction, boolean stable) {
|
||||
this.method = method;
|
||||
this.direction = direction;
|
||||
this.stable = stable;
|
||||
this.negated = false;
|
||||
}
|
||||
|
||||
Key(Method method, Direction direction, boolean stable, boolean negated) {
|
||||
this.method = method;
|
||||
this.direction = direction;
|
||||
this.stable = stable;
|
||||
this.negated = negated;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
-7
@@ -29,11 +29,13 @@ final class HKey {
|
||||
final byte[] key;
|
||||
final int dirKey;
|
||||
final boolean stable;
|
||||
final boolean negated;
|
||||
|
||||
HKey(@NotNull byte[] key, int dirKey, boolean stable) {
|
||||
HKey(@NotNull byte[] key, int dirKey, boolean stable, boolean negated) {
|
||||
this.key = key;
|
||||
this.dirKey = dirKey;
|
||||
this.stable = stable;
|
||||
this.negated = negated;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,6 +45,7 @@ final class HKey {
|
||||
HKey hKey = (HKey)o;
|
||||
if (dirKey != hKey.dirKey) return false;
|
||||
if (stable != hKey.stable) return false;
|
||||
if (negated != hKey.negated) return false;
|
||||
if (!Arrays.equals(key, hKey.key)) return false;
|
||||
return true;
|
||||
}
|
||||
@@ -52,27 +55,32 @@ final class HKey {
|
||||
int result = Arrays.hashCode(key);
|
||||
result = 31 * result + dirKey;
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
result = 31 * result + (negated ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
HKey negate() {
|
||||
return new HKey(key, dirKey, !stable);
|
||||
HKey invertStability() {
|
||||
return new HKey(key, dirKey, !stable, negated);
|
||||
}
|
||||
|
||||
HKey mkStable() {
|
||||
return stable ? this : new HKey(key, dirKey, true);
|
||||
return stable ? this : new HKey(key, dirKey, true, negated);
|
||||
}
|
||||
|
||||
HKey mkUnstable() {
|
||||
return stable ? new HKey(key, dirKey, false) : this;
|
||||
return stable ? new HKey(key, dirKey, false, negated) : this;
|
||||
}
|
||||
|
||||
public HKey mkBase() {
|
||||
return dirKey == 0 ? this : new HKey(key, 0, stable);
|
||||
return dirKey == 0 ? this : new HKey(key, 0, stable, false);
|
||||
}
|
||||
|
||||
HKey updateDirection(int newDirKey) {
|
||||
return new HKey(key, newDirKey, stable);
|
||||
return new HKey(key, newDirKey, stable, false);
|
||||
}
|
||||
|
||||
HKey negate() {
|
||||
return new HKey(key, dirKey, stable, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -283,7 +283,7 @@ public class ProjectBytecodeAnalysis {
|
||||
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
|
||||
|
||||
final Solver nullableSolver = new Solver(new ELattice<Value>(Value.Null, Value.Top), Value.Top);
|
||||
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true);
|
||||
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true, false);
|
||||
collectEquations(Collections.singletonList(nullableKey), nullableSolver, equationsCache);
|
||||
HashMap<HKey, Value> nullableSolutions = nullableSolver.solve();
|
||||
// subtle point
|
||||
@@ -313,7 +313,7 @@ public class ProjectBytecodeAnalysis {
|
||||
collectSingleEquation(nullableKey, nullableMethodSolver, equationsCache);
|
||||
}
|
||||
HashMap<HKey, Value> nullableSolutions = nullableMethodSolver.solve();
|
||||
if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.negate()) == Value.Null) {
|
||||
if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.invertStability()) == Value.Null) {
|
||||
result.nullables.add(key);
|
||||
}
|
||||
}
|
||||
@@ -354,7 +354,7 @@ public class ProjectBytecodeAnalysis {
|
||||
if (dirKey == hKey.dirKey) {
|
||||
HResult result = pair.hResult;
|
||||
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable), result));
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result));
|
||||
if (result instanceof HPending) {
|
||||
HPending pending = (HPending)result;
|
||||
for (HComponent component : pending.delta) {
|
||||
@@ -392,7 +392,7 @@ public class ProjectBytecodeAnalysis {
|
||||
int dirKey = pair.directionKey;
|
||||
if (dirKey == hKey.dirKey) {
|
||||
HResult result = pair.hResult;
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable), result));
|
||||
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-3
@@ -281,7 +281,7 @@ final class Solver {
|
||||
if (previousEquation == null) {
|
||||
equations.put(coreKey, equation);
|
||||
} else {
|
||||
HKey joinKey = new HKey(coreKey.key, coreKey.dirKey, equation.key.stable && previousEquation.key.stable);
|
||||
HKey joinKey = new HKey(coreKey.key, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true);
|
||||
HResult joinResult = resultUtil.join(equation.result, previousEquation.result);
|
||||
HEquation joinEquation = new HEquation(joinKey, joinResult);
|
||||
equations.put(coreKey, joinEquation);
|
||||
@@ -317,6 +317,17 @@ final class Solver {
|
||||
}
|
||||
}
|
||||
|
||||
Value negate(Value value) {
|
||||
switch (value) {
|
||||
case True:
|
||||
return Value.False;
|
||||
case False:
|
||||
return Value.True;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<HKey, Value> solve() {
|
||||
for (HEquation hEquation : equations.values()) {
|
||||
queueEquation(hEquation);
|
||||
@@ -325,8 +336,11 @@ final class Solver {
|
||||
HKey id = moving.pop();
|
||||
Value value = solved.get(id);
|
||||
|
||||
HKey[] pIds = id.stable ? new HKey[]{id, id.negate()} : new HKey[]{id.negate(), id};
|
||||
Value[] pVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue};
|
||||
HKey[] initialPIds = id.stable ? new HKey[]{id, id.invertStability()} : new HKey[]{id.invertStability(), id};
|
||||
Value[] initialPVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue};
|
||||
|
||||
HKey[] pIds = new HKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()};
|
||||
Value[] pVals = new Value[]{initialPVals[0], initialPVals[1], negate(initialPVals[0]), negate(initialPVals[1])};
|
||||
|
||||
for (int i = 0; i < pIds.length; i++) {
|
||||
HKey pId = pIds[i];
|
||||
|
||||
+8
-3
@@ -1153,7 +1153,7 @@
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.BooleanUtils boolean isNotFalse(java.lang.Boolean)">
|
||||
<annotation name="org.jetbrains.annotations.Contract">
|
||||
<val val="pure=true"/>
|
||||
<val val="value="null->true",pure=true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.BooleanUtils boolean isNotTrue(java.lang.Boolean) 0">
|
||||
@@ -1161,7 +1161,7 @@
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.BooleanUtils boolean isNotTrue(java.lang.Boolean)">
|
||||
<annotation name="org.jetbrains.annotations.Contract">
|
||||
<val val="pure=true"/>
|
||||
<val val="value="null->true",pure=true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.BooleanUtils boolean isTrue(java.lang.Boolean) 0">
|
||||
@@ -2612,12 +2612,17 @@
|
||||
<item name="org.apache.commons.lang.StringUtils boolean isNotBlank(java.lang.String) 0">
|
||||
<annotation name="org.jetbrains.annotations.Nullable"/>
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.StringUtils boolean isNotBlank(java.lang.String)">
|
||||
<annotation name="org.jetbrains.annotations.Contract">
|
||||
<val val=""null->false""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.StringUtils boolean isNotEmpty(java.lang.String) 0">
|
||||
<annotation name="org.jetbrains.annotations.Nullable"/>
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.StringUtils boolean isNotEmpty(java.lang.String)">
|
||||
<annotation name="org.jetbrains.annotations.Contract">
|
||||
<val val="pure=true"/>
|
||||
<val val="value="null->false",pure=true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name="org.apache.commons.lang.StringUtils boolean isNumeric(java.lang.String) 0">
|
||||
|
||||
@@ -79,4 +79,14 @@ public class Test01 {
|
||||
return new Test01();
|
||||
}
|
||||
|
||||
@ExpectContract(value = "!null->false;null->true", pure = true)
|
||||
static boolean isNull(Object o) {
|
||||
return o == null;
|
||||
}
|
||||
|
||||
@ExpectContract(value = "!null->true;null->false", pure = true)
|
||||
static boolean isNotNull(Object o) {
|
||||
return !isNull(o);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user