| /* |
| * 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.Edge; |
| import com.intellij.codeInspection.bytecodeAnalysis.asm.RichControlFlow; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.org.objectweb.asm.Type; |
| import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode; |
| import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode; |
| import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode; |
| import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode; |
| 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.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.InstanceOfCheckValue; |
| import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.ParamValue; |
| import static com.intellij.codeInspection.bytecodeAnalysis.PResults.*; |
| import static org.jetbrains.org.objectweb.asm.Opcodes.*; |
| import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*; |
| |
| abstract class PResults { |
| // SoP = sum of products |
| static Set<Set<Key>> join(Set<Set<Key>> sop1, Set<Set<Key>> sop2) { |
| Set<Set<Key>> sop = new HashSet<Set<Key>>(); |
| sop.addAll(sop1); |
| sop.addAll(sop2); |
| return sop; |
| } |
| |
| static Set<Set<Key>> meet(Set<Set<Key>> sop1, Set<Set<Key>> sop2) { |
| Set<Set<Key>> sop = new HashSet<Set<Key>>(); |
| for (Set<Key> prod1 : sop1) { |
| for (Set<Key> prod2 : sop2) { |
| Set<Key> prod = new HashSet<Key>(); |
| prod.addAll(prod1); |
| prod.addAll(prod2); |
| sop.add(prod); |
| } |
| } |
| return sop; |
| } |
| |
| // Results |
| interface PResult {} |
| static final PResult Identity = new PResult() { |
| @Override |
| public String toString() { |
| return "Identity"; |
| } |
| }; |
| // similar to top, maximal element |
| static final PResult Return = new PResult() { |
| @Override |
| public String toString() { |
| return "Return"; |
| } |
| }; |
| // minimal element |
| static final PResult NPE = new PResult() { |
| @Override |
| public String toString() { |
| return "NPE"; |
| } |
| }; |
| static final class ConditionalNPE implements PResult { |
| final Set<Set<Key>> sop; |
| public ConditionalNPE(Set<Set<Key>> sop) throws AnalyzerException { |
| this.sop = sop; |
| checkLimit(sop); |
| } |
| |
| public ConditionalNPE(Key key) { |
| sop = new HashSet<Set<Key>>(); |
| Set<Key> prod = new HashSet<Key>(); |
| prod.add(key); |
| sop.add(prod); |
| } |
| |
| static void checkLimit(Set<Set<Key>> sop) throws AnalyzerException { |
| int size = 0; |
| for (Set<Key> keys : sop) { |
| size += keys.size(); |
| } |
| if (size > Analysis.EQUATION_SIZE_LIMIT) { |
| throw new AnalyzerException(null, "Equation size is too big"); |
| } |
| } |
| } |
| |
| static PResult combineNullable(PResult r1, PResult r2) throws AnalyzerException { |
| if (Identity == r1) return r2; |
| if (Identity == r2) return r1; |
| if (Return == r1) return r2; |
| if (Return == r2) return r1; |
| if (NPE == r1) return NPE; |
| if (NPE == r2) return NPE; |
| ConditionalNPE cnpe1 = (ConditionalNPE) r1; |
| ConditionalNPE cnpe2 = (ConditionalNPE) r2; |
| return new ConditionalNPE(join(cnpe1.sop, cnpe2.sop)); |
| } |
| |
| static PResult join(PResult r1, PResult r2) throws AnalyzerException { |
| if (Identity == r1) return r2; |
| if (Identity == r2) return r1; |
| if (Return == r1) return Return; |
| if (Return == r2) return Return; |
| if (NPE == r1) return r2; |
| if (NPE == r2) return r1; |
| ConditionalNPE cnpe1 = (ConditionalNPE) r1; |
| ConditionalNPE cnpe2 = (ConditionalNPE) r2; |
| return new ConditionalNPE(join(cnpe1.sop, cnpe2.sop)); |
| } |
| |
| static PResult meet(PResult r1, PResult r2) throws AnalyzerException { |
| if (Identity == r1) return r2; |
| if (Return == r1) return r2; |
| if (Return == r2) return r1; |
| if (NPE == r1) return NPE; |
| if (NPE == r2) return NPE; |
| if (Identity == r2) return Identity; |
| ConditionalNPE cnpe1 = (ConditionalNPE) r1; |
| ConditionalNPE cnpe2 = (ConditionalNPE) r2; |
| return new ConditionalNPE(meet(cnpe1.sop, cnpe2.sop)); |
| } |
| |
| } |
| |
| interface PendingAction {} |
| class ProceedState implements PendingAction { |
| final State state; |
| |
| ProceedState(State state) { |
| this.state = state; |
| } |
| } |
| class MakeResult implements PendingAction { |
| final State state; |
| final PResult subResult; |
| final int[] indices; |
| |
| MakeResult(State state, PResult subResult, int[] indices) { |
| this.state = state; |
| this.subResult = subResult; |
| this.indices = indices; |
| } |
| } |
| |
| class NonNullInAnalysis extends Analysis<PResult> { |
| private static final ThreadLocal<PendingAction[]> ourPending = new ThreadLocal<PendingAction[]>() { |
| @Override |
| protected PendingAction[] initialValue() { |
| return new PendingAction[Analysis.STEPS_LIMIT]; |
| } |
| }; |
| private static final ThreadLocal<PResult[]> ourResults = new ThreadLocal<PResult[]>() { |
| @Override |
| protected PResult[] initialValue() { |
| return new PResult[Analysis.STEPS_LIMIT]; |
| } |
| }; |
| |
| final private PendingAction[] pending = ourPending.get(); |
| |
| private final NotNullInterpreter interpreter = new NotNullInterpreter(); |
| private PResult[] results; |
| |
| // Flag saying that at some branch NPE was found. Used later as an evidence that this param is *NOT* @Nullable (optimization). |
| boolean possibleNPE; |
| |
| protected NonNullInAnalysis(RichControlFlow richControlFlow, Direction direction, boolean stable) { |
| super(richControlFlow, direction, stable); |
| results = ourResults.get(); |
| } |
| |
| PResult combineResults(PResult delta, int[] subResults) throws AnalyzerException { |
| PResult result = Identity; |
| for (int subResult : subResults) { |
| result = join(result, results[subResult]); |
| } |
| return meet(delta, result); |
| } |
| |
| @NotNull |
| Equation<Key, Value> mkEquation(PResult result) { |
| if (Identity == result || Return == result) { |
| return new Equation<Key, Value>(aKey, new Final<Key, Value>(Value.Top)); |
| } |
| else if (NPE == result) { |
| return new Equation<Key, Value>(aKey, new Final<Key, Value>(Value.NotNull)); |
| } |
| else { |
| ConditionalNPE condNpe = (ConditionalNPE) result; |
| Set<Product<Key, Value>> components = new HashSet<Product<Key, Value>>(); |
| for (Set<Key> prod : condNpe.sop) { |
| components.add(new Product<Key, Value>(Value.Top, prod)); |
| } |
| return new Equation<Key, Value>(aKey, new Pending<Key, Value>(components)); |
| } |
| } |
| |
| private int id = 0; |
| private Frame<BasicValue> nextFrame = null; |
| private PResult subResult = null; |
| |
| @NotNull |
| protected Equation<Key, Value> analyze() throws AnalyzerException { |
| pendingPush(new ProceedState(createStartState())); |
| int steps = 0; |
| while (pendingTop > 0 && earlyResult == null) { |
| steps ++; |
| if (steps >= STEPS_LIMIT) { |
| throw new AnalyzerException(null, "limit is reached, steps: " + steps + " in method " + method); |
| } |
| PendingAction action = pending[--pendingTop]; |
| if (action instanceof MakeResult) { |
| MakeResult makeResult = (MakeResult) action; |
| PResult result = combineResults(makeResult.subResult, makeResult.indices); |
| State state = makeResult.state; |
| int insnIndex = state.conf.insnIndex; |
| results[state.index] = result; |
| addComputed(insnIndex, state); |
| } |
| else if (action instanceof ProceedState) { |
| ProceedState proceedState = (ProceedState) 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[insnIndex]) { |
| for (Conf prev : history) { |
| if (AbstractValues.isInstance(conf, prev)) { |
| fold = true; |
| break; |
| } |
| } |
| } |
| if (fold) { |
| results[state.index] = Identity; |
| addComputed(insnIndex, state); |
| } |
| else { |
| State baseState = null; |
| List<State> thisComputed = computed[insnIndex]; |
| if (thisComputed != null) { |
| for (State prevState : thisComputed) { |
| if (stateEquiv(state, prevState)) { |
| baseState = prevState; |
| break; |
| } |
| } |
| } |
| if (baseState != null) { |
| results[state.index] = results[baseState.index]; |
| } else { |
| // the main call |
| processState(state); |
| } |
| |
| } |
| } |
| } |
| if (earlyResult != null) { |
| return mkEquation(earlyResult); |
| } else { |
| return mkEquation(results[0]); |
| } |
| } |
| |
| private void processState(State state) throws AnalyzerException { |
| int stateIndex = state.index; |
| Conf conf = state.conf; |
| int insnIndex = conf.insnIndex; |
| List<Conf> history = state.history; |
| boolean taken = state.taken; |
| Frame<BasicValue> frame = conf.frame; |
| AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex); |
| List<Conf> nextHistory = dfsTree.loopEnters[insnIndex] ? append(history, conf) : history; |
| boolean hasCompanions = state.hasCompanions; |
| execute(frame, insnNode); |
| |
| boolean notEmptySubResult = subResult != Identity; |
| |
| if (subResult == NPE) { |
| results[stateIndex] = NPE; |
| possibleNPE = true; |
| addComputed(insnIndex, state); |
| return; |
| } |
| |
| int opcode = insnNode.getOpcode(); |
| switch (opcode) { |
| case ARETURN: |
| case IRETURN: |
| case LRETURN: |
| case FRETURN: |
| case DRETURN: |
| case RETURN: |
| if (!hasCompanions) { |
| earlyResult = Return; |
| } else { |
| results[stateIndex] = Return; |
| addComputed(insnIndex, state); |
| } |
| return; |
| default: |
| } |
| |
| if (opcode == ATHROW) { |
| if (taken) { |
| results[stateIndex] = NPE; |
| possibleNPE = true; |
| } else { |
| results[stateIndex] = Identity; |
| } |
| addComputed(insnIndex, state); |
| return; |
| } |
| |
| if (opcode == IFNONNULL && popValue(frame) instanceof ParamValue) { |
| int nextInsnIndex = insnIndex + 1; |
| State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); |
| pendingPush(new MakeResult(state, subResult, new int[]{nextState.index})); |
| pendingPush(new ProceedState(nextState)); |
| return; |
| } |
| |
| if (opcode == IFNULL && popValue(frame) instanceof ParamValue) { |
| int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); |
| State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); |
| pendingPush(new MakeResult(state, subResult, new int[]{nextState.index})); |
| pendingPush(new ProceedState(nextState)); |
| return; |
| } |
| |
| if (opcode == IFEQ && popValue(frame) == InstanceOfCheckValue) { |
| int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); |
| State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); |
| pendingPush(new MakeResult(state, subResult, new int[]{nextState.index})); |
| pendingPush(new ProceedState(nextState)); |
| return; |
| } |
| |
| if (opcode == IFNE && popValue(frame) == InstanceOfCheckValue) { |
| int nextInsnIndex = insnIndex + 1; |
| State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, hasCompanions || notEmptySubResult); |
| pendingPush(new MakeResult(state, subResult, new int[]{nextState.index})); |
| pendingPush(new ProceedState(nextState)); |
| return; |
| } |
| |
| // general case |
| int[] nextInsnIndices = controlFlow.transitions[insnIndex]; |
| int[] subIndices = new int[nextInsnIndices.length]; |
| for (int i = 0; i < nextInsnIndices.length; i++) { |
| subIndices[i] = ++id; |
| } |
| pendingPush(new MakeResult(state, subResult, subIndices)); |
| for (int i = 0; i < nextInsnIndices.length; i++) { |
| int nextInsnIndex = nextInsnIndices[i]; |
| Frame<BasicValue> nextFrame1 = nextFrame; |
| if (controlFlow.errors[nextInsnIndex] && controlFlow.errorTransitions.contains(new Edge(insnIndex, nextInsnIndex))) { |
| nextFrame1 = new Frame<BasicValue>(frame); |
| nextFrame1.clearStack(); |
| nextFrame1.push(ASMUtils.THROWABLE_VALUE); |
| } |
| pendingPush(new ProceedState(new State(subIndices[i], new Conf(nextInsnIndex, nextFrame1), nextHistory, taken, hasCompanions || notEmptySubResult))); |
| } |
| |
| } |
| |
| private int pendingTop = 0; |
| |
| private void pendingPush(PendingAction action) throws AnalyzerException { |
| if (pendingTop >= STEPS_LIMIT) { |
| throw new AnalyzerException(null, "limit is reached in method " + method); |
| } |
| pending[pendingTop++] = action; |
| } |
| |
| private void execute(Frame<BasicValue> frame, AbstractInsnNode insnNode) throws AnalyzerException { |
| switch (insnNode.getType()) { |
| case AbstractInsnNode.LABEL: |
| case AbstractInsnNode.LINE: |
| case AbstractInsnNode.FRAME: |
| nextFrame = frame; |
| subResult = Identity; |
| break; |
| default: |
| nextFrame = new Frame<BasicValue>(frame); |
| interpreter.reset(); |
| nextFrame.execute(insnNode, interpreter); |
| subResult = interpreter.getSubResult(); |
| } |
| } |
| } |
| |
| class NullableInAnalysis extends Analysis<PResult> { |
| final private State[] pending = ourPendingStates.get(); |
| |
| private final NullableInterpreter interpreter = new NullableInterpreter(); |
| |
| protected NullableInAnalysis(RichControlFlow richControlFlow, Direction direction, boolean stable) { |
| super(richControlFlow, direction, stable); |
| } |
| |
| @NotNull |
| Equation<Key, Value> mkEquation(PResult result) { |
| if (NPE == result) { |
| return new Equation<Key, Value>(aKey, new Final<Key, Value>(Value.Top)); |
| } |
| if (Identity == result || Return == result) { |
| return new Equation<Key, Value>(aKey, new Final<Key, Value>(Value.Null)); |
| } |
| else { |
| ConditionalNPE condNpe = (ConditionalNPE) result; |
| Set<Product<Key, Value>> components = new HashSet<Product<Key, Value>>(); |
| for (Set<Key> prod : condNpe.sop) { |
| components.add(new Product<Key, Value>(Value.Top, prod)); |
| } |
| return new Equation<Key, Value>(aKey, new Pending<Key, Value>(components)); |
| } |
| } |
| |
| private int id = 0; |
| private Frame<BasicValue> nextFrame = null; |
| private PResult myResult = Identity; |
| private PResult subResult = Identity; |
| private boolean top = false; |
| |
| @NotNull |
| protected Equation<Key, Value> analyze() throws AnalyzerException { |
| pendingPush(createStartState()); |
| int steps = 0; |
| while (pendingTop > 0 && earlyResult == null) { |
| steps ++; |
| if (steps >= STEPS_LIMIT) { |
| throw new AnalyzerException(null, "limit is reached, steps: " + steps + " in method " + method); |
| } |
| State state = pending[--pendingTop]; |
| int insnIndex = state.conf.insnIndex; |
| Conf conf = state.conf; |
| List<Conf> history = state.history; |
| |
| boolean fold = false; |
| if (dfsTree.loopEnters[insnIndex]) { |
| for (Conf prev : history) { |
| if (AbstractValues.isInstance(conf, prev)) { |
| fold = true; |
| break; |
| } |
| } |
| } |
| if (fold) { |
| addComputed(insnIndex, state); |
| } |
| else { |
| State baseState = null; |
| List<State> thisComputed = computed[insnIndex]; |
| if (thisComputed != null) { |
| for (State prevState : thisComputed) { |
| if (stateEquiv(state, prevState)) { |
| baseState = prevState; |
| break; |
| } |
| } |
| } |
| if (baseState == null) { |
| processState(state); |
| } |
| } |
| } |
| if (earlyResult != null) { |
| return mkEquation(earlyResult); |
| } else { |
| return mkEquation(myResult); |
| } |
| } |
| |
| private void processState(State state) throws AnalyzerException { |
| Conf conf = state.conf; |
| int insnIndex = conf.insnIndex; |
| List<Conf> history = state.history; |
| boolean taken = state.taken; |
| Frame<BasicValue> frame = conf.frame; |
| AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex); |
| List<Conf> nextHistory = dfsTree.loopEnters[insnIndex] ? append(history, conf) : history; |
| |
| addComputed(insnIndex, state); |
| execute(frame, insnNode); |
| |
| if (subResult == NPE || top) { |
| earlyResult = NPE; |
| return; |
| } |
| |
| if (subResult instanceof ConditionalNPE) { |
| myResult = combineNullable(myResult, subResult); |
| } |
| |
| int opcode = insnNode.getOpcode(); |
| switch (opcode) { |
| case ARETURN: |
| if (popValue(frame) instanceof ParamValue) { |
| earlyResult = NPE; |
| } |
| return; |
| case IRETURN: |
| case LRETURN: |
| case FRETURN: |
| case DRETURN: |
| case RETURN: |
| return; |
| default: |
| } |
| |
| if (opcode == ATHROW) { |
| if (taken) { |
| earlyResult = NPE; |
| } |
| return; |
| } |
| |
| if (opcode == IFNONNULL && popValue(frame) instanceof ParamValue) { |
| int nextInsnIndex = insnIndex + 1; |
| pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false)); |
| return; |
| } |
| |
| if (opcode == IFNULL && popValue(frame) instanceof ParamValue) { |
| int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); |
| pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false)); |
| return; |
| } |
| |
| if (opcode == IFEQ && popValue(frame) == InstanceOfCheckValue) { |
| int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label); |
| pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false)); |
| return; |
| } |
| |
| if (opcode == IFNE && popValue(frame) == InstanceOfCheckValue) { |
| int nextInsnIndex = insnIndex + 1; |
| pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false)); |
| return; |
| } |
| |
| // general case |
| for (int nextInsnIndex : controlFlow.transitions[insnIndex]) { |
| Frame<BasicValue> nextFrame1 = nextFrame; |
| if (controlFlow.errors[nextInsnIndex] && controlFlow.errorTransitions.contains(new Edge(insnIndex, nextInsnIndex))) { |
| nextFrame1 = new Frame<BasicValue>(frame); |
| nextFrame1.clearStack(); |
| nextFrame1.push(ASMUtils.THROWABLE_VALUE); |
| } |
| pendingPush(new State(++id, new Conf(nextInsnIndex, nextFrame1), nextHistory, taken, false)); |
| } |
| |
| } |
| |
| private int pendingTop = 0; |
| |
| private void pendingPush(State state) throws AnalyzerException { |
| if (pendingTop >= STEPS_LIMIT) { |
| throw new AnalyzerException(null, "limit is reached in method " + method); |
| } |
| pending[pendingTop++] = state; |
| } |
| |
| private void execute(Frame<BasicValue> frame, AbstractInsnNode insnNode) throws AnalyzerException { |
| switch (insnNode.getType()) { |
| case AbstractInsnNode.LABEL: |
| case AbstractInsnNode.LINE: |
| case AbstractInsnNode.FRAME: |
| nextFrame = frame; |
| subResult = Identity; |
| top = false; |
| break; |
| default: |
| nextFrame = new Frame<BasicValue>(frame); |
| interpreter.reset(); |
| nextFrame.execute(insnNode, interpreter); |
| subResult = interpreter.getSubResult(); |
| top = interpreter.top; |
| } |
| } |
| } |
| |
| abstract class NullityInterpreter extends BasicInterpreter { |
| boolean top = false; |
| final boolean nullableAnalysis; |
| final int nullityMask; |
| private PResult subResult = Identity; |
| |
| NullityInterpreter(boolean nullableAnalysis, int nullityMask) { |
| this.nullableAnalysis = nullableAnalysis; |
| this.nullityMask = nullityMask; |
| } |
| |
| abstract PResult combine(PResult res1, PResult res2) throws AnalyzerException; |
| |
| public PResult getSubResult() { |
| return subResult; |
| } |
| void reset() { |
| subResult = Identity; |
| top = false; |
| } |
| |
| @Override |
| public BasicValue unaryOperation(AbstractInsnNode insn, BasicValue value) throws AnalyzerException { |
| switch (insn.getOpcode()) { |
| case GETFIELD: |
| case ARRAYLENGTH: |
| case MONITORENTER: |
| if (value instanceof ParamValue) { |
| subResult = NPE; |
| } |
| break; |
| case CHECKCAST: |
| if (value instanceof ParamValue) { |
| return new ParamValue(Type.getObjectType(((TypeInsnNode)insn).desc)); |
| } |
| break; |
| case INSTANCEOF: |
| if (value instanceof ParamValue) { |
| return InstanceOfCheckValue; |
| } |
| break; |
| default: |
| |
| } |
| return super.unaryOperation(insn, value); |
| } |
| |
| @Override |
| public BasicValue binaryOperation(AbstractInsnNode insn, BasicValue value1, BasicValue value2) throws AnalyzerException { |
| switch (insn.getOpcode()) { |
| case IALOAD: |
| case LALOAD: |
| case FALOAD: |
| case DALOAD: |
| case AALOAD: |
| case BALOAD: |
| case CALOAD: |
| case SALOAD: |
| if (value1 instanceof ParamValue) { |
| subResult = NPE; |
| } |
| break; |
| case PUTFIELD: |
| if (value1 instanceof ParamValue) { |
| subResult = NPE; |
| } |
| if (nullableAnalysis && value2 instanceof ParamValue) { |
| subResult = NPE; |
| } |
| break; |
| default: |
| } |
| return 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 ParamValue) { |
| subResult = NPE; |
| } |
| break; |
| case AASTORE: |
| if (value1 instanceof ParamValue) { |
| subResult = NPE; |
| } |
| if (nullableAnalysis && value3 instanceof ParamValue) { |
| subResult = NPE; |
| } |
| break; |
| default: |
| } |
| return null; |
| } |
| |
| @Override |
| public BasicValue naryOperation(AbstractInsnNode insn, List<? extends BasicValue> values) throws AnalyzerException { |
| int opcode = insn.getOpcode(); |
| boolean isStaticInvoke = opcode == INVOKESTATIC; |
| int shift = isStaticInvoke ? 0 : 1; |
| if ((opcode == INVOKESPECIAL || opcode ==INVOKEINTERFACE || opcode == INVOKEVIRTUAL) && values.get(0) instanceof ParamValue) { |
| subResult = NPE; |
| } |
| switch (opcode) { |
| case INVOKEINTERFACE: |
| if (nullableAnalysis) { |
| for (int i = shift; i < values.size(); i++) { |
| if (values.get(i) instanceof ParamValue) { |
| top = true; |
| return super.naryOperation(insn, values); |
| } |
| } |
| } |
| break; |
| case INVOKESTATIC: |
| case INVOKESPECIAL: |
| case INVOKEVIRTUAL: |
| boolean stable = opcode == INVOKESTATIC || opcode == INVOKESPECIAL; |
| MethodInsnNode methodNode = (MethodInsnNode) insn; |
| Method method = new Method(methodNode.owner, methodNode.name, methodNode.desc); |
| for (int i = shift; i < values.size(); i++) { |
| if (values.get(i) instanceof ParamValue) { |
| subResult = combine(subResult, new ConditionalNPE(new Key(method, new In(i - shift, nullityMask), stable))); |
| } |
| } |
| break; |
| default: |
| } |
| return super.naryOperation(insn, values); |
| } |
| } |
| |
| class NotNullInterpreter extends NullityInterpreter { |
| |
| NotNullInterpreter() { |
| super(false, In.NOT_NULL); |
| } |
| |
| @Override |
| PResult combine(PResult res1, PResult res2) throws AnalyzerException { |
| return meet(res1, res2); |
| } |
| } |
| |
| class NullableInterpreter extends NullityInterpreter { |
| |
| NullableInterpreter() { |
| super(true, In.NULLABLE); |
| } |
| |
| @Override |
| PResult combine(PResult res1, PResult res2) throws AnalyzerException { |
| return join(res1, res2); |
| } |
| } |