summaryrefslogtreecommitdiff
path: root/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java')
-rw-r--r--java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java398
1 files changed, 398 insertions, 0 deletions
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java
new file mode 100644
index 000000000000..44e493c63f0a
--- /dev/null
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/bytecodeAnalysis/Analysis.java
@@ -0,0 +1,398 @@
+/*
+ * 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 gnu.trove.TIntObjectHashMap;
+import org.jetbrains.org.objectweb.asm.Opcodes;
+import org.jetbrains.org.objectweb.asm.Type;
+import org.jetbrains.org.objectweb.asm.tree.MethodNode;
+import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
+import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
+import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
+
+import java.util.*;
+
+class AbstractValues {
+ static final class ParamValue extends BasicValue {
+ ParamValue(Type tp) {
+ super(tp);
+ }
+ }
+ static final BasicValue InstanceOfCheckValue = new BasicValue(Type.INT_TYPE) {
+ @Override
+ public boolean equals(Object value) {
+ return this == value;
+ }
+ };
+
+ static final BasicValue TrueValue = new BasicValue(Type.INT_TYPE) {
+ @Override
+ public boolean equals(Object value) {
+ return this == value;
+ }
+ };
+
+ static final BasicValue FalseValue = new BasicValue(Type.INT_TYPE) {
+ @Override
+ public boolean equals(Object value) {
+ return this == value;
+ }
+ };
+
+ static final BasicValue NullValue = new BasicValue(Type.getObjectType("null")) {
+ @Override
+ public boolean equals(Object value) {
+ return this == value;
+ }
+ };
+ static final class NotNullValue extends BasicValue {
+ NotNullValue(Type tp) {
+ super(tp);
+ }
+ }
+ static final class CallResultValue extends BasicValue {
+ final Set<Key> inters;
+ CallResultValue(Type tp, Set<Key> inters) {
+ super(tp);
+ this.inters = inters;
+ }
+ }
+
+ static boolean isInstance(Conf curr, Conf prev) {
+ if (curr.insnIndex != prev.insnIndex) {
+ return false;
+ }
+ Frame<BasicValue> currFr = curr.frame;
+ Frame<BasicValue> prevFr = prev.frame;
+ for (int i = 0; i < currFr.getLocals(); i++) {
+ if (!isInstance(currFr.getLocal(i), prevFr.getLocal(i))) {
+ return false;
+ }
+ }
+ for (int i = 0; i < currFr.getStackSize(); i++) {
+ if (!isInstance(currFr.getStack(i), prevFr.getStack(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean isInstance(BasicValue curr, BasicValue prev) {
+ if (prev instanceof ParamValue) {
+ return curr instanceof ParamValue;
+ }
+ if (InstanceOfCheckValue == prev) {
+ return InstanceOfCheckValue == curr;
+ }
+ if (TrueValue == prev) {
+ return TrueValue == curr;
+ }
+ if (FalseValue == prev) {
+ return FalseValue == curr;
+ }
+ if (NullValue == prev) {
+ return NullValue == curr;
+ }
+ if (prev instanceof NotNullValue) {
+ return curr instanceof NotNullValue;
+ }
+ if (prev instanceof CallResultValue) {
+ if (curr instanceof CallResultValue) {
+ CallResultValue prevCall = (CallResultValue) prev;
+ CallResultValue currCall = (CallResultValue) curr;
+ return prevCall.inters.equals(currCall.inters);
+ }
+ else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean equiv(Conf curr, Conf prev) {
+ Frame<BasicValue> currFr = curr.frame;
+ Frame<BasicValue> prevFr = prev.frame;
+ for (int i = currFr.getStackSize() - 1; i >= 0; i--) {
+ if (!equiv(currFr.getStack(i), prevFr.getStack(i))) {
+ return false;
+ }
+ }
+ for (int i = currFr.getLocals() - 1; i >= 0; i--) {
+ if (!equiv(currFr.getLocal(i), prevFr.getLocal(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static boolean equiv(BasicValue curr, BasicValue prev) {
+ if (curr.getClass() == prev.getClass()) {
+ if (curr instanceof CallResultValue && prev instanceof CallResultValue) {
+ Set<Key> keys1 = ((CallResultValue)prev).inters;
+ Set<Key> keys2 = ((CallResultValue)curr).inters;
+ return keys1.equals(keys2);
+ }
+ else return true;
+ }
+ else return false;
+ }
+}
+
+final class Conf {
+ final int insnIndex;
+ final Frame<BasicValue> frame;
+ final int fastHashCode;
+
+ Conf(int insnIndex, Frame<BasicValue> frame) {
+ this.insnIndex = insnIndex;
+ this.frame = frame;
+
+ int hash = 0;
+ for (int i = 0; i < frame.getLocals(); i++) {
+ hash = hash * 31 + frame.getLocal(i).getClass().hashCode();
+ }
+ for (int i = 0; i < frame.getStackSize(); i++) {
+ hash = hash * 31 + frame.getStack(i).getClass().hashCode();
+ }
+ fastHashCode = hash;
+ }
+}
+
+final class State {
+ final int index;
+ final Conf conf;
+ final List<Conf> history;
+ final boolean taken;
+ final boolean hasCompanions;
+
+ State(int index, Conf conf, List<Conf> history, boolean taken, boolean hasCompanions) {
+ this.index = index;
+ this.conf = conf;
+ this.history = history;
+ this.taken = taken;
+ this.hasCompanions = hasCompanions;
+ }
+}
+
+interface PendingAction<Res> {}
+class ProceedState<Res> implements PendingAction<Res> {
+ final State state;
+
+ ProceedState(State state) {
+ this.state = state;
+ }
+}
+class MakeResult<Res> implements PendingAction<Res> {
+ final State state;
+ final Res subResult;
+ final int[] indices;
+
+ MakeResult(State state, Res subResult, int[] indices) {
+ this.state = state;
+ this.subResult = subResult;
+ this.indices = indices;
+ }
+}
+
+abstract class Analysis<Res> {
+ private static final int STEPS_LIMIT = 30000;
+ final RichControlFlow richControlFlow;
+ final Direction direction;
+ final ControlFlowGraph controlFlow;
+ final MethodNode methodNode;
+ final Method method;
+ final DFSTree dfsTree;
+ final Res myIdentity;
+
+ final Deque<PendingAction<Res>> pending = new LinkedList<PendingAction<Res>>();
+ final TIntObjectHashMap<List<State>> computed = new TIntObjectHashMap<List<State>>();
+ final TIntObjectHashMap<Res> results = new TIntObjectHashMap<Res>();
+ final Key aKey;
+
+ Res earlyResult = null;
+
+ abstract Res identity();
+ abstract Res combineResults(Res delta, List<Res> subResults);
+ abstract boolean isEarlyResult(Res res);
+ abstract Equation<Key, Value> mkEquation(Res result);
+ abstract void processState(State state) throws AnalyzerException;
+
+ protected Analysis(RichControlFlow richControlFlow, Direction direction, boolean stable) {
+ this.richControlFlow = richControlFlow;
+ this.direction = direction;
+ controlFlow = richControlFlow.controlFlow;
+ methodNode = controlFlow.methodNode;
+ method = new Method(controlFlow.className, methodNode.name, methodNode.desc);
+ dfsTree = richControlFlow.dfsTree;
+ aKey = new Key(method, direction, stable);
+ myIdentity = identity();
+ }
+
+ final State createStartState() {
+ return new State(0, new Conf(0, createStartFrame()), new ArrayList<Conf>(), false, false);
+ }
+
+ static boolean stateEquiv(State curr, State prev) {
+ if (curr.taken != prev.taken) {
+ return false;
+ }
+ if (curr.conf.fastHashCode != prev.conf.fastHashCode) {
+ return false;
+ }
+ if (!AbstractValues.equiv(curr.conf, prev.conf)) {
+ return false;
+ }
+ if (curr.history.size() != prev.history.size()) {
+ return false;
+ }
+ for (int i = 0; i < curr.history.size(); i++) {
+ Conf curr1 = curr.history.get(i);
+ Conf prev1 = prev.history.get(i);
+ if (curr1.fastHashCode != prev1.fastHashCode || !AbstractValues.equiv(curr1, prev1)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ final Equation<Key, Value> analyze() throws AnalyzerException {
+ pending.push(new ProceedState<Res>(createStartState()));
+ int steps = 0;
+ while (!pending.isEmpty() && earlyResult == null) {
+ steps ++;
+ if (steps >= STEPS_LIMIT) {
+ throw new AnalyzerException(null, "limit is reached, steps: " + steps + " in method " + method);
+ }
+ PendingAction<Res> action = pending.pop();
+ if (action instanceof MakeResult) {
+ MakeResult<Res> makeResult = (MakeResult<Res>) action;
+ ArrayList<Res> subResults = new ArrayList<Res>();
+ for (int index : makeResult.indices) {
+ subResults.add(results.get(index));
+ }
+ Res result = combineResults(makeResult.subResult, subResults);
+ if (isEarlyResult(result)) {
+ earlyResult = result;
+ } else {
+ State state = makeResult.state;
+ int insnIndex = state.conf.insnIndex;
+ results.put(state.index, result);
+ List<State> thisComputed = computed.get(insnIndex);
+ if (thisComputed == null) {
+ thisComputed = new ArrayList<State>();
+ computed.put(insnIndex, thisComputed);
+ }
+ thisComputed.add(state);
+ }
+ }
+ else if (action instanceof ProceedState) {
+ ProceedState<Res> proceedState = (ProceedState<Res>) action;
+ State state = proceedState.state;
+ int insnIndex = state.conf.insnIndex;
+ Conf conf = state.conf;
+ List<Conf> history = state.history;
+
+ boolean fold = false;
+ if (dfsTree.loopEnters.contains(insnIndex)) {
+ for (Conf prev : history) {
+ if (AbstractValues.isInstance(conf, prev)) {
+ fold = true;
+ }
+ }
+ }
+ if (fold) {
+ results.put(state.index, myIdentity);
+ List<State> thisComputed = computed.get(insnIndex);
+ if (thisComputed == null) {
+ thisComputed = new ArrayList<State>();
+ computed.put(insnIndex, thisComputed);
+ }
+ thisComputed.add(state);
+ }
+ else {
+ State baseState = null;
+ List<State> thisComputed = computed.get(insnIndex);
+ if (thisComputed != null) {
+ for (State prevState : thisComputed) {
+ if (stateEquiv(state, prevState)) {
+ baseState = prevState;
+ break;
+ }
+ }
+ }
+ if (baseState != null) {
+ results.put(state.index, results.get(baseState.index));
+ } else {
+ // the main call
+ processState(state);
+ }
+
+ }
+ }
+ }
+ if (earlyResult != null) {
+ return mkEquation(earlyResult);
+ } else {
+ return mkEquation(results.get(0));
+ }
+ }
+
+ 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++, new AbstractValues.NotNullValue(Type.getObjectType(controlFlow.className)));
+ }
+ for (int i = 0; i < args.length; i++) {
+ BasicValue value;
+ if (direction instanceof InOut && ((InOut)direction).paramIndex == i) {
+ value = new AbstractValues.ParamValue(args[i]);
+ }
+ else if (direction instanceof In && ((In)direction).paramIndex == i) {
+ value = new AbstractValues.ParamValue(args[i]);
+ }
+ else {
+ value = new BasicValue(args[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;
+ }
+
+ static BasicValue popValue(Frame<BasicValue> frame) {
+ return frame.getStack(frame.getStackSize() - 1);
+ }
+
+ static <A> List<A> append(List<A> xs, A x) {
+ ArrayList<A> result = new ArrayList<A>();
+ if (xs != null) {
+ result.addAll(xs);
+ }
+ result.add(x);
+ return result;
+ }
+}