From b7b5dce917c70e1d5eddc4071a6739ff690e94ed Mon Sep 17 00:00:00 2001 From: Ilya Klyuchnikov Date: Fri, 29 Aug 2014 18:46:05 +0400 Subject: [PATCH] preparations for @Nullable method analysis --- .../bytecodeAnalysis/ClassDataIndexer.java | 2 +- .../bytecodeAnalysis/Combined.java | 308 ++++++++++++------ .../codeInspection/bytecodeAnalysis/Data.java | 14 +- 3 files changed, 214 insertions(+), 110 deletions(-) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java index a6f64da6194d..a3c813e9d07c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/ClassDataIndexer.java @@ -314,7 +314,7 @@ public class ClassDataIndexer implements DataIndexer> result = new ArrayList>(argumentTypes.length * 4 + 1); - CombinedSingleAnalysis analyzer = new CombinedSingleAnalysis(method, graph); + CombinedAnalysis analyzer = new CombinedAnalysis(method, graph); analyzer.analyze(); if (isReferenceResult) { result.add(analyzer.outContractEquation(stable)); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java index a17c1e5f9dee..c02d12831c16 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Combined.java @@ -35,64 +35,113 @@ import java.util.Set; import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*; import static org.jetbrains.org.objectweb.asm.Opcodes.*; import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*; +import static com.intellij.codeInspection.bytecodeAnalysis.CombinedData.*; -final class ParamKey { - final Method method; - final int i; - final boolean stable; +// additional data structures for combined analysis +interface CombinedData { + final class ParamKey { + final Method method; + final int i; + final boolean stable; - ParamKey(Method method, int i, boolean stable) { - this.method = method; - this.i = i; - this.stable = stable; + ParamKey(Method method, int i, boolean stable) { + this.method = method; + this.i = i; + this.stable = stable; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ParamKey paramKey = (ParamKey)o; + + if (i != paramKey.i) return false; + if (stable != paramKey.stable) return false; + if (!method.equals(paramKey.method)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = method.hashCode(); + result = 31 * result + i; + result = 31 * result + (stable ? 1 : 0); + return result; + } } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ParamKey paramKey = (ParamKey)o; - - if (i != paramKey.i) return false; - if (stable != paramKey.stable) return false; - if (!method.equals(paramKey.method)) return false; - - return true; + // value knowing at which instruction it was created + interface Trackable { + int getOriginInsnIndex(); } - @Override - public int hashCode() { - int result = method.hashCode(); - result = 31 * result + i; - result = 31 * result + (stable ? 1 : 0); - return result; + final class TrackableCallValue extends BasicValue implements Trackable { + final Method method; + final boolean stableCall; + final List args; + private final int originInsnIndex; + + TrackableCallValue(int originInsnIndex, Type tp, Method method, boolean stableCall, List args) { + super(tp); + this.originInsnIndex = originInsnIndex; + this.method = method; + this.stableCall = stableCall; + this.args = args; + } + + @Override + public int getOriginInsnIndex() { + return originInsnIndex; + } } + + final class NthParamValue extends BasicValue { + final int n; + + public NthParamValue(Type type, int n) { + super(type); + this.n = n; + } + } + + final class TrackableNullValue extends BasicValue implements Trackable { + static final Type NullType = Type.getObjectType("null"); + private final int originInsnIndex; + public TrackableNullValue(int originInsnIndex) { + super(NullType); + this.originInsnIndex = originInsnIndex; + } + + @Override + public int getOriginInsnIndex() { + return originInsnIndex; + } + } + + final class TrackableValue extends BasicValue implements Trackable { + private final int originInsnIndex; + + public TrackableValue(int originInsnIndex, Type type) { + super(type); + this.originInsnIndex = originInsnIndex; + } + + @Override + public int getOriginInsnIndex() { + return originInsnIndex; + } + } + + BasicValue ThisValue = new BasicValue(Type.getObjectType("java/lang/Object")); } -final class CombinedCall extends BasicValue { - final Method method; - final boolean stableCall; - final List args; +// specialized class for analyzing methods without branching in single pass +final class CombinedAnalysis { - CombinedCall(Type tp, Method method, boolean stableCall, List args) { - super(tp); - this.method = method; - this.stableCall = stableCall; - this.args = args; - } -} - -final class NParamValue extends BasicValue { - final int n; - public NParamValue(Type type, int n) { - super(type); - this.n = n; - } -} - -final class CombinedSingleAnalysis { private final ControlFlowGraph controlFlow; private final Method method; private final CombinedInterpreter interpreter; @@ -100,11 +149,11 @@ final class CombinedSingleAnalysis { private boolean exception; private final MethodNode methodNode; - CombinedSingleAnalysis(Method method, ControlFlowGraph controlFlow) { + CombinedAnalysis(Method method, ControlFlowGraph controlFlow) { this.method = method; this.controlFlow = controlFlow; methodNode = controlFlow.methodNode; - interpreter = new CombinedInterpreter(Type.getArgumentTypes(methodNode.desc).length); + interpreter = new CombinedInterpreter(methodNode.instructions, Type.getArgumentTypes(methodNode.desc).length); } final void analyze() throws AnalyzerException { @@ -145,11 +194,11 @@ final class CombinedSingleAnalysis { final Equation notNullParamEquation(int i, boolean stable) { final Key key = new Key(method, new In(i, In.NOT_NULL), stable); final Result result; - if (interpreter.dereferenced[i]) { + if (interpreter.dereferencedParams[i]) { result = new Final(Value.NotNull); } else { - Set calls = interpreter.callDerefs[i]; + Set calls = interpreter.parameterFlow[i]; if (calls == null || calls.isEmpty()) { result = new Final(Value.Top); } @@ -167,11 +216,11 @@ final class CombinedSingleAnalysis { final Equation nullableParamEquation(int i, boolean stable) { final Key key = new Key(method, new In(i, In.NULLABLE), stable); final Result result; - if (interpreter.dereferenced[i] || interpreter.notNullable[i] || returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) { + if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) { result = new Final(Value.Top); } else { - Set calls = interpreter.callDerefs[i]; + Set calls = interpreter.parameterFlow[i]; if (calls == null || calls.isEmpty()) { result = new Final(Value.Null); } @@ -189,7 +238,7 @@ final class CombinedSingleAnalysis { final Equation contractEquation(int i, Value inValue, boolean stable) { final Key key = new Key(method, new InOut(i, inValue), stable); final Result result; - if (exception || (inValue == Value.Null && interpreter.dereferenced[i])) { + if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) { result = new Final(Value.Bot); } else if (FalseValue == returnValue) { @@ -198,22 +247,22 @@ final class CombinedSingleAnalysis { else if (TrueValue == returnValue) { result = new Final(Value.True); } - else if (NullValue == returnValue) { + else if (returnValue instanceof TrackableNullValue) { result = new Final(Value.Null); } - else if (returnValue instanceof NotNullValue) { + else if (returnValue instanceof NotNullValue || ThisValue == returnValue) { result = new Final(Value.NotNull); } - else if (returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) { + else if (returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) { result = new Final(inValue); } - else if (returnValue instanceof CombinedCall) { - CombinedCall call = (CombinedCall)returnValue; + else if (returnValue instanceof TrackableCallValue) { + TrackableCallValue call = (TrackableCallValue)returnValue; HashSet keys = new HashSet(); for (int argI = 0; argI < call.args.size(); argI++) { BasicValue arg = call.args.get(argI); - if (arg instanceof NParamValue) { - NParamValue npv = (NParamValue)arg; + if (arg instanceof NthParamValue) { + NthParamValue npv = (NthParamValue)arg; if (npv.n == i) { keys.add(new Key(call.method, new InOut(argI, inValue), call.stableCall)); } @@ -246,14 +295,14 @@ final class CombinedSingleAnalysis { else if (TrueValue == returnValue) { result = new Final(Value.True); } - else if (NullValue == returnValue) { + else if (returnValue instanceof TrackableNullValue) { result = new Final(Value.Null); } - else if (returnValue instanceof NotNullValue) { + else if (returnValue instanceof NotNullValue || returnValue == ThisValue) { result = new Final(Value.NotNull); } - else if (returnValue instanceof CombinedCall) { - CombinedCall call = (CombinedCall)returnValue; + else if (returnValue instanceof TrackableCallValue) { + TrackableCallValue call = (TrackableCallValue)returnValue; Key callKey = new Key(call.method, Out, call.stableCall); Set keys = new SingletonSet(callKey); result = new Pending(new SingletonSet>(new Product(Value.Top, keys))); @@ -273,10 +322,10 @@ final class CombinedSingleAnalysis { Type[] args = Type.getArgumentTypes(methodNode.desc); int local = 0; if ((methodNode.access & Opcodes.ACC_STATIC) == 0) { - frame.setLocal(local++, new AbstractValues.NotNullValue(Type.getObjectType(controlFlow.className))); + frame.setLocal(local++, ThisValue); } for (int i = 0; i < args.length; i++) { - BasicValue value = new NParamValue(args[i], i); + BasicValue value = new NthParamValue(args[i], i); frame.setLocal(local++, value); if (args[i].getSize() == 2) { frame.setLocal(local++, BasicValue.UNINITIALIZED_VALUE); @@ -290,25 +339,47 @@ final class CombinedSingleAnalysis { } final class CombinedInterpreter extends BasicInterpreter { - final boolean[] dereferenced; - final boolean[] notNullable; - final Set[] callDerefs; + // Parameters dereferenced during execution of a method, tracked by parameter's indices. + // Dereferenced parameters are @NotNull. + final boolean[] dereferencedParams; + // Parameters, that are written to something or passed to an interface methods. + // This parameters cannot be @Nullable. + final boolean[] notNullableParams; + // parameterFlow(i) for i-th parameter stores a set parameter positions it is passed to + // parameter is @NotNull if any of its usages are @NotNull + final Set[] parameterFlow; - CombinedInterpreter(int arity) { - dereferenced = new boolean[arity]; - notNullable = new boolean[arity]; - callDerefs = new Set[arity]; + // Trackable values that were dereferenced during execution of a method + // Values are are identified by `origin` index + final boolean[] dereferencedValues; + private final InsnList insns; + + CombinedInterpreter(InsnList insns, int arity) { + dereferencedParams = new boolean[arity]; + notNullableParams = new boolean[arity]; + parameterFlow = new Set[arity]; + this.insns = insns; + dereferencedValues = new boolean[insns.size()]; + } + + private int insnIndex(AbstractInsnNode insn) { + return insns.indexOf(insn); + } + + private static BasicValue track(int origin, BasicValue basicValue) { + return basicValue == null ? null : new TrackableValue(origin, basicValue.getType()); } @Override public BasicValue newOperation(AbstractInsnNode insn) throws AnalyzerException { + int origin = insnIndex(insn); switch (insn.getOpcode()) { case ICONST_0: return FalseValue; case ICONST_1: return TrueValue; case ACONST_NULL: - return NullValue; + return new TrackableNullValue(origin); case LDC: Object cst = ((LdcInsnNode)insn).cst; if (cst instanceof Type) { @@ -331,22 +402,26 @@ final class CombinedInterpreter extends BasicInterpreter { return new NotNullValue(Type.getObjectType(((TypeInsnNode)insn).desc)); default: } - return super.newOperation(insn); + return track(origin, super.newOperation(insn)); } @Override public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException { + int origin = insnIndex(insn); switch (insn.getOpcode()) { case GETFIELD: case ARRAYLENGTH: case MONITORENTER: - if (value instanceof NParamValue) { - dereferenced[((NParamValue)value).n] = true; + if (value instanceof NthParamValue) { + dereferencedParams[((NthParamValue)value).n] = true; } - return super.unaryOperation(insn, value); + if (value instanceof Trackable) { + dereferencedValues[((Trackable)value).getOriginInsnIndex()] = true; + } + return track(origin, super.unaryOperation(insn, value)); case CHECKCAST: - if (value instanceof NParamValue) { - return new NParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NParamValue)value).n); + if (value instanceof NthParamValue) { + return new NthParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NthParamValue)value).n); } break; case NEWARRAY: @@ -354,12 +429,23 @@ final class CombinedInterpreter extends BasicInterpreter { return new NotNullValue(super.unaryOperation(insn, value).getType()); default: } - return super.unaryOperation(insn, value); + return track(origin, super.unaryOperation(insn, value)); } @Override public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException { switch (insn.getOpcode()) { + case PUTFIELD: + if (value1 instanceof NthParamValue) { + dereferencedParams[((NthParamValue)value1).n] = true; + } + if (value1 instanceof Trackable) { + dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true; + } + if (value2 instanceof NthParamValue) { + notNullableParams[((NthParamValue)value2).n] = true; + } + break; case IALOAD: case LALOAD: case FALOAD: @@ -368,21 +454,16 @@ final class CombinedInterpreter extends BasicInterpreter { case BALOAD: case CALOAD: case SALOAD: - if (value1 instanceof NParamValue) { - dereferenced[((NParamValue)value1).n] = true; + if (value1 instanceof NthParamValue) { + dereferencedParams[((NthParamValue)value1).n] = true; } - break; - case PUTFIELD: - if (value1 instanceof NParamValue) { - dereferenced[((NParamValue)value1).n] = true; - } - if (value2 instanceof NParamValue) { - notNullable[((NParamValue)value2).n] = true; + if (value1 instanceof Trackable) { + dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true; } break; default: } - return super.binaryOperation(insn, value1, value2); + return track(insnIndex(insn), super.binaryOperation(insn, value1, value2)); } @Override @@ -396,16 +477,22 @@ final class CombinedInterpreter extends BasicInterpreter { case BASTORE: case CASTORE: case SASTORE: - if (value1 instanceof NParamValue) { - dereferenced[((NParamValue)value1).n] = true; + if (value1 instanceof NthParamValue) { + dereferencedParams[((NthParamValue)value1).n] = true; + } + if (value1 instanceof Trackable) { + dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true; } break; case AASTORE: - if (value1 instanceof NParamValue) { - dereferenced[((NParamValue)value1).n] = true; + if (value1 instanceof NthParamValue) { + dereferencedParams[((NthParamValue)value1).n] = true; } - if (value3 instanceof NParamValue) { - notNullable[((NParamValue)value3).n] = true; + if (value1 instanceof Trackable) { + dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true; + } + if (value3 instanceof NthParamValue) { + notNullableParams[((NthParamValue)value3).n] = true; } break; default: @@ -417,14 +504,19 @@ final class CombinedInterpreter extends BasicInterpreter { public BasicValue naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException { int opCode = insn.getOpcode(); int shift = opCode == INVOKESTATIC ? 0 : 1; - + int origin = insnIndex(insn); switch (opCode) { case INVOKESPECIAL: case INVOKEINTERFACE: case INVOKEVIRTUAL: - if (values.get(0) instanceof NParamValue) { - dereferenced[((NParamValue)values.get(0)).n] = true; + BasicValue receiver = values.get(0); + if (receiver instanceof NthParamValue) { + dereferencedParams[((NthParamValue)receiver).n] = true; } + if (receiver instanceof Trackable) { + dereferencedValues[((Trackable)receiver).getOriginInsnIndex()] = true; + } + default: } switch (opCode) { @@ -438,16 +530,16 @@ final class CombinedInterpreter extends BasicInterpreter { Type retType = Type.getReturnType(mNode.desc); for (int i = shift; i < values.size(); i++) { - if (values.get(i) instanceof NParamValue) { - int n = ((NParamValue)values.get(i)).n; + if (values.get(i) instanceof NthParamValue) { + int n = ((NthParamValue)values.get(i)).n; if (opCode == INVOKEINTERFACE) { - notNullable[n] = true; + notNullableParams[n] = true; } else { - Set npKeys = callDerefs[n]; + Set npKeys = parameterFlow[n]; if (npKeys == null) { npKeys = new HashSet(); - callDerefs[n] = npKeys; + parameterFlow[n] = npKeys; } npKeys.add(new ParamKey(method, i - shift, stable)); } @@ -456,11 +548,11 @@ final class CombinedInterpreter extends BasicInterpreter { if (shift == 1) { values.remove(0); } - return new CombinedCall(retType, method, stable, values); + return new TrackableCallValue(origin, retType, method, stable, values); case MULTIANEWARRAY: return new NotNullValue(super.naryOperation(insn, values).getType()); default: } - return super.naryOperation(insn, values); + return track(origin, super.naryOperation(insn, values)); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java index 74e805a31492..b76e31197482 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Data.java @@ -140,7 +140,19 @@ interface Direction { @Override public int hashCode() { - return 1; + return -1; + } + }; + + Direction NullableOut = new Direction() { + @Override + public String toString() { + return "NullableOut"; + } + + @Override + public int hashCode() { + return -2; } }; }