mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
384 lines
16 KiB
Java
384 lines
16 KiB
Java
/*
|
|
* Copyright 2000-2015 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.
|
|
*/
|
|
|
|
/*
|
|
* Created by IntelliJ IDEA.
|
|
* User: max
|
|
* Date: Jan 28, 2002
|
|
* Time: 10:16:39 PM
|
|
* To change template for new class use
|
|
* Code Style | Class Templates options (Tools | IDE Options).
|
|
*/
|
|
package com.intellij.codeInspection.dataFlow;
|
|
|
|
import com.intellij.codeInspection.dataFlow.instructions.*;
|
|
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
|
|
import com.intellij.codeInspection.dataFlow.value.DfaVariableValue;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.progress.ProgressManager;
|
|
import com.intellij.openapi.util.Key;
|
|
import com.intellij.openapi.util.Pair;
|
|
import com.intellij.openapi.util.registry.Registry;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.templateLanguages.OuterLanguageElement;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.psi.util.PsiUtil;
|
|
import com.intellij.util.Processor;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.containers.MultiMap;
|
|
import gnu.trove.THashSet;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.*;
|
|
|
|
public class DataFlowRunner {
|
|
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.DataFlowRunner");
|
|
private static final Key<Integer> TOO_EXPENSIVE_HASH = Key.create("TOO_EXPENSIVE_HASH");
|
|
|
|
private Instruction[] myInstructions;
|
|
private final MultiMap<PsiElement, DfaMemoryState> myNestedClosures = new MultiMap<PsiElement, DfaMemoryState>();
|
|
@NotNull
|
|
private final DfaValueFactory myValueFactory;
|
|
|
|
// Maximum allowed attempts to process instruction. Fail as too complex to process if certain instruction
|
|
// is executed more than this limit times.
|
|
static final int MAX_STATES_PER_BRANCH = 300;
|
|
|
|
protected DataFlowRunner() {
|
|
this(false, true);
|
|
}
|
|
|
|
protected DataFlowRunner(boolean unknownMembersAreNullable, boolean honorFieldInitializers) {
|
|
myValueFactory = new DfaValueFactory(honorFieldInitializers, unknownMembersAreNullable);
|
|
}
|
|
|
|
@NotNull
|
|
public DfaValueFactory getFactory() {
|
|
return myValueFactory;
|
|
}
|
|
|
|
@Nullable
|
|
private Collection<DfaMemoryState> createInitialStates(@NotNull PsiElement psiBlock, @NotNull InstructionVisitor visitor) {
|
|
PsiElement container = PsiTreeUtil.getParentOfType(psiBlock, PsiClass.class, PsiLambdaExpression.class);
|
|
if (container != null && (!(container instanceof PsiClass) || PsiUtil.isLocalOrAnonymousClass((PsiClass)container))) {
|
|
final PsiElement parent = container.getParent();
|
|
final PsiCodeBlock block = DfaPsiUtil.getTopmostBlockInSameClass(parent);
|
|
if (block != null) {
|
|
final RunnerResult result = analyzeMethod(block, visitor);
|
|
if (result == RunnerResult.OK) {
|
|
final Collection<DfaMemoryState> closureStates = myNestedClosures.get(DfaPsiUtil.getTopmostBlockInSameClass(psiBlock));
|
|
if (!closureStates.isEmpty()) {
|
|
return closureStates;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return Collections.singletonList(createMemoryState());
|
|
}
|
|
|
|
@NotNull
|
|
public final RunnerResult analyzeMethod(@NotNull PsiElement psiBlock, @NotNull InstructionVisitor visitor) {
|
|
Collection<DfaMemoryState> initialStates = createInitialStates(psiBlock, visitor);
|
|
return initialStates == null ? RunnerResult.NOT_APPLICABLE : analyzeMethod(psiBlock, visitor, false, initialStates);
|
|
}
|
|
|
|
@NotNull
|
|
final RunnerResult analyzeMethod(@NotNull PsiElement psiBlock,
|
|
@NotNull InstructionVisitor visitor,
|
|
boolean ignoreAssertions,
|
|
@NotNull Collection<DfaMemoryState> initialStates) {
|
|
if (PsiTreeUtil.findChildOfType(psiBlock, OuterLanguageElement.class) != null) return RunnerResult.NOT_APPLICABLE;
|
|
|
|
try {
|
|
final ControlFlow flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, ignoreAssertions).buildControlFlow();
|
|
if (flow == null) return RunnerResult.NOT_APPLICABLE;
|
|
int[] loopNumber = LoopAnalyzer.calcInLoop(flow);
|
|
|
|
int endOffset = flow.getInstructionCount();
|
|
myInstructions = flow.getInstructions();
|
|
myNestedClosures.clear();
|
|
|
|
Set<Instruction> joinInstructions = ContainerUtil.newHashSet();
|
|
for (int index = 0; index < myInstructions.length; index++) {
|
|
Instruction instruction = myInstructions[index];
|
|
if (instruction instanceof GotoInstruction) {
|
|
joinInstructions.add(myInstructions[((GotoInstruction)instruction).getOffset()]);
|
|
} else if (instruction instanceof ConditionalGotoInstruction) {
|
|
joinInstructions.add(myInstructions[((ConditionalGotoInstruction)instruction).getOffset()]);
|
|
} else if (instruction instanceof MethodCallInstruction && !((MethodCallInstruction)instruction).getContracts().isEmpty()) {
|
|
joinInstructions.add(myInstructions[index + 1]);
|
|
}
|
|
}
|
|
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("Analyzing code block: " + psiBlock.getText());
|
|
for (int i = 0; i < myInstructions.length; i++) {
|
|
LOG.debug(i + ": " + myInstructions[i]);
|
|
}
|
|
}
|
|
//for (int i = 0; i < myInstructions.length; i++) System.out.println(i + ": " + myInstructions[i].toString());
|
|
|
|
Integer tooExpensiveHash = psiBlock.getUserData(TOO_EXPENSIVE_HASH);
|
|
if (tooExpensiveHash != null && tooExpensiveHash == psiBlock.getText().hashCode()) {
|
|
LOG.debug("Too complex because hasn't changed since being too complex already");
|
|
return RunnerResult.TOO_COMPLEX;
|
|
}
|
|
|
|
final StateQueue queue = new StateQueue();
|
|
for (final DfaMemoryState initialState : initialStates) {
|
|
queue.offer(new DfaInstructionState(myInstructions[0], initialState));
|
|
}
|
|
|
|
MultiMap<BranchingInstruction, DfaMemoryState> processedStates = MultiMap.createSet();
|
|
MultiMap<BranchingInstruction, DfaMemoryState> incomingStates = MultiMap.createSet();
|
|
|
|
long msLimit = shouldCheckTimeLimit() ? Registry.intValue("ide.dfa.time.limit.online") : Registry.intValue("ide.dfa.time.limit.offline");
|
|
WorkingTimeMeasurer measurer = new WorkingTimeMeasurer(msLimit * 1000 * 1000);
|
|
int count = 0;
|
|
while (!queue.isEmpty()) {
|
|
List<DfaInstructionState> states = queue.getNextInstructionStates(joinInstructions);
|
|
for (DfaInstructionState instructionState : states) {
|
|
if (count++ % 1024 == 0 && measurer.isTimeOver()) {
|
|
LOG.debug("Too complex because the analysis took too long");
|
|
psiBlock.putUserData(TOO_EXPENSIVE_HASH, psiBlock.getText().hashCode());
|
|
return RunnerResult.TOO_COMPLEX;
|
|
}
|
|
ProgressManager.checkCanceled();
|
|
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug(instructionState.toString());
|
|
}
|
|
//System.out.println(instructionState.toString());
|
|
|
|
Instruction instruction = instructionState.getInstruction();
|
|
|
|
if (instruction instanceof BranchingInstruction) {
|
|
BranchingInstruction branching = (BranchingInstruction)instruction;
|
|
Collection<DfaMemoryState> processed = processedStates.get(branching);
|
|
if (processed.contains(instructionState.getMemoryState())) {
|
|
continue;
|
|
}
|
|
if (processed.size() > MAX_STATES_PER_BRANCH) {
|
|
LOG.debug("Too complex because too many different possible states");
|
|
return RunnerResult.TOO_COMPLEX; // Too complex :(
|
|
}
|
|
if (loopNumber[branching.getIndex()] != 0) {
|
|
processedStates.putValue(branching, instructionState.getMemoryState().createCopy());
|
|
}
|
|
}
|
|
|
|
DfaInstructionState[] after = acceptInstruction(visitor, instructionState);
|
|
for (DfaInstructionState state : after) {
|
|
Instruction nextInstruction = state.getInstruction();
|
|
if (nextInstruction.getIndex() >= endOffset) {
|
|
continue;
|
|
}
|
|
handleStepOutOfLoop(instruction, nextInstruction, loopNumber, processedStates, incomingStates, states, after, queue);
|
|
if (nextInstruction instanceof BranchingInstruction) {
|
|
BranchingInstruction branching = (BranchingInstruction)nextInstruction;
|
|
if (processedStates.get(branching).contains(state.getMemoryState()) ||
|
|
incomingStates.get(branching).contains(state.getMemoryState())) {
|
|
continue;
|
|
}
|
|
if (loopNumber[branching.getIndex()] != 0) {
|
|
incomingStates.putValue(branching, state.getMemoryState().createCopy());
|
|
}
|
|
}
|
|
queue.offer(state);
|
|
}
|
|
}
|
|
}
|
|
|
|
psiBlock.putUserData(TOO_EXPENSIVE_HASH, null);
|
|
LOG.debug("Analysis ok");
|
|
return RunnerResult.OK;
|
|
}
|
|
catch (ArrayIndexOutOfBoundsException e) {
|
|
LOG.error(psiBlock.getText(), e);
|
|
return RunnerResult.ABORTED;
|
|
}
|
|
catch (EmptyStackException e) {
|
|
LOG.error(psiBlock.getText(), e);
|
|
return RunnerResult.ABORTED;
|
|
}
|
|
}
|
|
|
|
private void handleStepOutOfLoop(@NotNull final Instruction prevInstruction,
|
|
@NotNull Instruction nextInstruction,
|
|
@NotNull final int[] loopNumber,
|
|
@NotNull MultiMap<BranchingInstruction, DfaMemoryState> processedStates,
|
|
@NotNull MultiMap<BranchingInstruction, DfaMemoryState> incomingStates,
|
|
@NotNull List<DfaInstructionState> inFlightStates,
|
|
@NotNull DfaInstructionState[] afterStates,
|
|
@NotNull StateQueue queue) {
|
|
if (loopNumber[prevInstruction.getIndex()] == 0 || inSameLoop(prevInstruction, nextInstruction, loopNumber)) {
|
|
return;
|
|
}
|
|
// stepped out of loop. destroy all memory states from the loop, we don't need them anymore
|
|
|
|
// but do not touch yet states being handled right now
|
|
for (DfaInstructionState state : inFlightStates) {
|
|
Instruction instruction = state.getInstruction();
|
|
if (inSameLoop(prevInstruction, instruction, loopNumber)) {
|
|
return;
|
|
}
|
|
}
|
|
for (DfaInstructionState state : afterStates) {
|
|
Instruction instruction = state.getInstruction();
|
|
if (inSameLoop(prevInstruction, instruction, loopNumber)) {
|
|
return;
|
|
}
|
|
}
|
|
// and still in queue
|
|
if (!queue.processAll(new Processor<DfaInstructionState>() {
|
|
@Override
|
|
public boolean process(DfaInstructionState state) {
|
|
Instruction instruction = state.getInstruction();
|
|
return !inSameLoop(prevInstruction, instruction, loopNumber);
|
|
}
|
|
})) return;
|
|
|
|
// now remove obsolete memory states
|
|
final Set<BranchingInstruction> mayRemoveStatesFor = new THashSet<BranchingInstruction>();
|
|
for (Instruction instruction : myInstructions) {
|
|
if (inSameLoop(prevInstruction, instruction, loopNumber) && instruction instanceof BranchingInstruction) {
|
|
mayRemoveStatesFor.add((BranchingInstruction)instruction);
|
|
}
|
|
}
|
|
|
|
for (Instruction instruction : mayRemoveStatesFor) {
|
|
processedStates.remove((BranchingInstruction)instruction);
|
|
incomingStates.remove((BranchingInstruction)instruction);
|
|
}
|
|
}
|
|
|
|
private static boolean inSameLoop(@NotNull Instruction prevInstruction, @NotNull Instruction nextInstruction, @NotNull int[] loopNumber) {
|
|
return loopNumber[nextInstruction.getIndex()] == loopNumber[prevInstruction.getIndex()];
|
|
}
|
|
|
|
protected boolean shouldCheckTimeLimit() {
|
|
return !ApplicationManager.getApplication().isUnitTestMode();
|
|
}
|
|
|
|
@NotNull
|
|
protected DfaInstructionState[] acceptInstruction(@NotNull InstructionVisitor visitor, @NotNull DfaInstructionState instructionState) {
|
|
Instruction instruction = instructionState.getInstruction();
|
|
PsiElement closure = DfaUtil.getClosureInside(instruction);
|
|
if (closure instanceof PsiClass) {
|
|
registerNestedClosures(instructionState, (PsiClass)closure);
|
|
} else if (closure instanceof PsiLambdaExpression) {
|
|
registerNestedClosures(instructionState, (PsiLambdaExpression)closure);
|
|
}
|
|
|
|
return instruction.accept(this, instructionState.getMemoryState(), visitor);
|
|
}
|
|
|
|
private void registerNestedClosures(@NotNull DfaInstructionState instructionState, @NotNull PsiClass nestedClass) {
|
|
DfaMemoryState state = instructionState.getMemoryState();
|
|
for (PsiMethod method : nestedClass.getMethods()) {
|
|
PsiCodeBlock body = method.getBody();
|
|
if (body != null) {
|
|
myNestedClosures.putValue(body, createClosureState(state));
|
|
}
|
|
}
|
|
for (PsiClassInitializer initializer : nestedClass.getInitializers()) {
|
|
myNestedClosures.putValue(initializer.getBody(), createClosureState(state));
|
|
}
|
|
for (PsiField field : nestedClass.getFields()) {
|
|
myNestedClosures.putValue(field, createClosureState(state));
|
|
}
|
|
}
|
|
|
|
private void registerNestedClosures(@NotNull DfaInstructionState instructionState, @NotNull PsiLambdaExpression expr) {
|
|
DfaMemoryState state = instructionState.getMemoryState();
|
|
PsiElement body = expr.getBody();
|
|
if (body != null) {
|
|
myNestedClosures.putValue(body, createClosureState(state));
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
protected DfaMemoryState createMemoryState() {
|
|
return new DfaMemoryStateImpl(myValueFactory);
|
|
}
|
|
|
|
@NotNull
|
|
public Instruction[] getInstructions() {
|
|
return myInstructions;
|
|
}
|
|
|
|
@NotNull
|
|
public Instruction getInstruction(int index) {
|
|
return myInstructions[index];
|
|
}
|
|
|
|
@NotNull
|
|
MultiMap<PsiElement, DfaMemoryState> getNestedClosures() {
|
|
return new MultiMap<PsiElement, DfaMemoryState>(myNestedClosures);
|
|
}
|
|
|
|
@NotNull
|
|
public Pair<Set<Instruction>,Set<Instruction>> getConstConditionalExpressions() {
|
|
Set<Instruction> trueSet = new HashSet<Instruction>();
|
|
Set<Instruction> falseSet = new HashSet<Instruction>();
|
|
|
|
for (Instruction instruction : myInstructions) {
|
|
if (instruction instanceof BranchingInstruction) {
|
|
BranchingInstruction branchingInstruction = (BranchingInstruction)instruction;
|
|
if (branchingInstruction.getPsiAnchor() != null && branchingInstruction.isConditionConst()) {
|
|
if (!branchingInstruction.isTrueReachable()) {
|
|
falseSet.add(branchingInstruction);
|
|
}
|
|
|
|
if (!branchingInstruction.isFalseReachable()) {
|
|
trueSet.add(branchingInstruction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Instruction instruction : myInstructions) {
|
|
if (instruction instanceof BranchingInstruction) {
|
|
BranchingInstruction branchingInstruction = (BranchingInstruction)instruction;
|
|
if (branchingInstruction.isTrueReachable()) {
|
|
falseSet.remove(branchingInstruction);
|
|
}
|
|
if (branchingInstruction.isFalseReachable()) {
|
|
trueSet.remove(branchingInstruction);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Pair.create(trueSet, falseSet);
|
|
}
|
|
|
|
@NotNull
|
|
private static DfaMemoryStateImpl createClosureState(@NotNull DfaMemoryState memState) {
|
|
DfaMemoryStateImpl copy = (DfaMemoryStateImpl)memState.createCopy();
|
|
copy.flushFields();
|
|
Set<DfaVariableValue> vars = new HashSet<DfaVariableValue>(copy.getVariableStates().keySet());
|
|
for (DfaVariableValue value : vars) {
|
|
copy.flushDependencies(value);
|
|
}
|
|
copy.emptyStack();
|
|
return copy;
|
|
}
|
|
}
|