blob: dd10492785e728cff7df03101d599d1af53b3c18 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.codeInspection.bytecodeAnalysis.asm.ASMUtils;
import com.intellij.codeInspection.bytecodeAnalysis.asm.ControlFlowGraph;
import com.intellij.util.SingletonSet;
import com.intellij.util.containers.HashSet;
import org.jetbrains.org.objectweb.asm.Handle;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.tree.*;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
import java.util.Collections;
import java.util.List;
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.*;
// 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;
}
@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;
}
}
// value knowing at which instruction it was created
interface Trackable {
int getOriginInsnIndex();
}
final class TrackableCallValue extends BasicValue implements Trackable {
private final int originInsnIndex;
final Method 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) {
super(tp);
this.originInsnIndex = originInsnIndex;
this.method = method;
this.args = args;
this.stableCall = stableCall;
this.thisCall = thisCall;
}
@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"));
}
// specialized class for analyzing methods without branching in single pass
final class CombinedAnalysis {
private final ControlFlowGraph controlFlow;
private final Method method;
private final CombinedInterpreter interpreter;
private BasicValue returnValue;
private boolean exception;
private final MethodNode methodNode;
CombinedAnalysis(Method method, ControlFlowGraph controlFlow) {
this.method = method;
this.controlFlow = controlFlow;
methodNode = controlFlow.methodNode;
interpreter = new CombinedInterpreter(methodNode.instructions, Type.getArgumentTypes(methodNode.desc).length);
}
final void analyze() throws AnalyzerException {
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 ATHROW:
exception = true;
return;
case ARETURN:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
returnValue = frame.pop();
return;
case RETURN:
// nothing to return
return;
default:
frame.execute(insnNode, interpreter);
insnIndex = controlFlow.transitions[insnIndex][0];
}
}
}
}
final Equation<Key, Value> notNullParamEquation(int i, boolean stable) {
final Key key = new Key(method, new In(i, In.NOT_NULL), stable);
final Result<Key, Value> result;
if (interpreter.dereferencedParams[i]) {
result = new Final<Key, Value>(Value.NotNull);
}
else {
Set<ParamKey> calls = interpreter.parameterFlow[i];
if (calls == null || calls.isEmpty()) {
result = new Final<Key, Value>(Value.Top);
}
else {
Set<Key> keys = new HashSet<Key>();
for (ParamKey pk: calls) {
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL), pk.stable));
}
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 Equation<Key, Value> nullableParamEquation(int i, boolean stable) {
final Key key = new Key(method, new In(i, In.NULLABLE), stable);
final Result<Key, Value> result;
if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
result = new Final<Key, Value>(Value.Top);
}
else {
Set<ParamKey> calls = interpreter.parameterFlow[i];
if (calls == null || calls.isEmpty()) {
result = new Final<Key, Value>(Value.Null);
}
else {
Set<Product<Key, Value>> sum = new HashSet<Product<Key, Value>>();
for (ParamKey pk: calls) {
sum.add(new Product<Key, Value>(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE), pk.stable))));
}
result = new Pending<Key, Value>(sum);
}
}
return new Equation<Key, Value>(key, result);
}
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;
if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) {
result = new Final<Key, Value>(Value.Bot);
}
else if (FalseValue == returnValue) {
result = new Final<Key, Value>(Value.False);
}
else if (TrueValue == returnValue) {
result = new Final<Key, Value>(Value.True);
}
else if (returnValue instanceof TrackableNullValue) {
result = new Final<Key, Value>(Value.Null);
}
else if (returnValue instanceof NotNullValue || ThisValue == returnValue) {
result = new Final<Key, Value>(Value.NotNull);
}
else if (returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
result = new Final<Key, Value>(inValue);
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
HashSet<Key> keys = new HashSet<Key>();
for (int argI = 0; argI < call.args.size(); argI++) {
BasicValue arg = call.args.get(argI);
if (arg instanceof NthParamValue) {
NthParamValue npv = (NthParamValue)arg;
if (npv.n == i) {
keys.add(new Key(call.method, new InOut(argI, inValue), call.stableCall));
}
}
}
if (ASMUtils.isReferenceType(call.getType())) {
keys.add(new Key(call.method, Out, call.stableCall));
}
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)));
}
}
else {
result = new Final<Key, Value>(Value.Top);
}
return new Equation<Key, Value>(key, result);
}
final Equation<Key, Value> outContractEquation(boolean stable) {
final Key key = new Key(method, Out, stable);
final Result<Key, Value> result;
if (exception) {
result = new Final<Key, Value>(Value.Bot);
}
else if (FalseValue == returnValue) {
result = new Final<Key, Value>(Value.False);
}
else if (TrueValue == returnValue) {
result = new Final<Key, Value>(Value.True);
}
else if (returnValue instanceof TrackableNullValue) {
result = new Final<Key, Value>(Value.Null);
}
else if (returnValue instanceof NotNullValue || returnValue == ThisValue) {
result = new Final<Key, Value>(Value.NotNull);
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
Key callKey = new Key(call.method, Out, call.stableCall);
Set<Key> keys = new SingletonSet<Key>(callKey);
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
}
else {
result = new Final<Key, Value>(Value.Top);
}
return new Equation<Key, Value>(key, result);
}
final Equation<Key, Value> nullableResultEquation(boolean stable) {
final Key key = new Key(method, NullableOut, stable);
final Result<Key, Value> result;
if (exception ||
returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) {
result = new Final<Key, Value>(Value.Bot);
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall);
Set<Key> keys = new SingletonSet<Key>(callKey);
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Null, keys)));
}
else if (returnValue instanceof TrackableNullValue) {
result = new Final<Key, Value>(Value.Null);
}
else {
result = new Final<Key, Value>(Value.Bot);
}
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 & Opcodes.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;
}
}
final class CombinedInterpreter extends BasicInterpreter {
// 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<ParamKey>[] parameterFlow;
// 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 new TrackableNullValue(origin);
case LDC:
Object cst = ((LdcInsnNode)insn).cst;
if (cst instanceof Type) {
Type type = (Type)cst;
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return CLASS_VALUE;
}
if (type.getSort() == Type.METHOD) {
return METHOD_VALUE;
}
}
else if (cst instanceof String) {
return STRING_VALUE;
}
else if (cst instanceof Handle) {
return METHOD_HANDLE_VALUE;
}
break;
case NEW:
return new NotNullValue(Type.getObjectType(((TypeInsnNode)insn).desc));
default:
}
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 NthParamValue) {
dereferencedParams[((NthParamValue)value).n] = true;
}
if (value instanceof Trackable) {
dereferencedValues[((Trackable)value).getOriginInsnIndex()] = true;
}
return track(origin, super.unaryOperation(insn, value));
case CHECKCAST:
if (value instanceof NthParamValue) {
return new NthParamValue(Type.getObjectType(((TypeInsnNode)insn).desc), ((NthParamValue)value).n);
}
break;
case NEWARRAY:
case ANEWARRAY:
return new NotNullValue(super.unaryOperation(insn, value).getType());
default:
}
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:
case DALOAD:
case AALOAD:
case BALOAD:
case CALOAD:
case SALOAD:
if (value1 instanceof NthParamValue) {
dereferencedParams[((NthParamValue)value1).n] = true;
}
if (value1 instanceof Trackable) {
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
}
break;
default:
}
return track(insnIndex(insn), super.binaryOperation(insn, value1, value2));
}
@Override
public BasicValue ternaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2, BasicValue value3)
throws AnalyzerException {
switch (insn.getOpcode()) {
case IASTORE:
case LASTORE:
case FASTORE:
case DASTORE:
case BASTORE:
case CASTORE:
case SASTORE:
if (value1 instanceof NthParamValue) {
dereferencedParams[((NthParamValue)value1).n] = true;
}
if (value1 instanceof Trackable) {
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
}
break;
case AASTORE:
if (value1 instanceof NthParamValue) {
dereferencedParams[((NthParamValue)value1).n] = true;
}
if (value1 instanceof Trackable) {
dereferencedValues[((Trackable)value1).getOriginInsnIndex()] = true;
}
if (value3 instanceof NthParamValue) {
notNullableParams[((NthParamValue)value3).n] = true;
}
break;
default:
}
return null;
}
@Override
public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> 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:
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) {
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);
for (int i = shift; i < values.size(); i++) {
if (values.get(i) instanceof NthParamValue) {
int n = ((NthParamValue)values.get(i)).n;
if (opCode == INVOKEINTERFACE) {
notNullableParams[n] = true;
}
else {
Set<ParamKey> npKeys = parameterFlow[n];
if (npKeys == null) {
npKeys = new HashSet<ParamKey>();
parameterFlow[n] = npKeys;
}
npKeys.add(new ParamKey(method, i - shift, stable));
}
}
}
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);
case MULTIANEWARRAY:
return new NotNullValue(super.naryOperation(insn, values).getType());
default:
}
return track(origin, super.naryOperation(insn, values));
}
}