From 853f21d9b1c648773c6ad3eb02eb04f1ed679048 Mon Sep 17 00:00:00 2001 From: peter Date: Thu, 25 Sep 2014 16:00:01 +0200 Subject: [PATCH] dfa: flush variables immediately when they're not used anymore to reduce the number of different memory states --- .../dataFlow/ControlFlowAnalyzer.java | 10 +- .../dataFlow/LiveVariablesAnalyzer.java | 189 ++++++++++++++++++ .../dataFlow/StandardInstructionVisitor.java | 2 +- .../FinishElementInstruction.java | 57 ++++++ .../instructions/PushInstruction.java | 10 +- .../fixture/FlushFurtherUnusedVariables.java | 40 ++++ .../DataFlowInspectionTest.java | 1 + 7 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FinishElementInstruction.java create mode 100644 java/java-tests/testData/inspection/dataFlow/fixture/FlushFurtherUnusedVariables.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index fe02eb0758cf..36b195da9b0d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -92,6 +92,8 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(new ReturnInstruction(false, null)); + new LiveVariablesAnalyzer(myCurrentFlow, myFactory).flushDeadVariablesOnStatementFinish(); + return myCurrentFlow; } @@ -125,6 +127,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { if (element != popped) { throw new AssertionError("Expected " + element + ", popped " + popped); } + if (element instanceof PsiStatement && !(element instanceof PsiReturnStatement)) { + addInstruction(new FinishElementInstruction(element)); + } } @Override @@ -1611,8 +1616,9 @@ public class ControlFlowAnalyzer extends JavaElementVisitor { addInstruction(expression.resolve() instanceof PsiField ? new FieldReferenceInstruction(expression, null) : new PopInstruction()); } - boolean referenceRead = PsiUtil.isAccessedForReading(expression) && !PsiUtil.isAccessedForWriting(expression); - addInstruction(new PushInstruction(myFactory.createValue(expression), expression, referenceRead)); + // complex assignments (e.g. "|=") are both reading and writing + boolean writing = PsiUtil.isAccessedForWriting(expression) && !PsiUtil.isAccessedForReading(expression); + addInstruction(new PushInstruction(myFactory.createValue(expression), expression, writing)); finishElement(expression); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java new file mode 100644 index 000000000000..c09fb1e5fe6c --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/LiveVariablesAnalyzer.java @@ -0,0 +1,189 @@ +/* + * 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.dataFlow; + +import com.intellij.codeInspection.dataFlow.instructions.*; +import com.intellij.codeInspection.dataFlow.value.DfaValue; +import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.openapi.util.Pair; +import com.intellij.util.PairFunction; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.FilteringIterator; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.containers.Queue; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author peter + */ +public class LiveVariablesAnalyzer { + private final DfaValueFactory myFactory; + private final Instruction[] myInstructions; + private final MultiMap myBackwardMap; + + public LiveVariablesAnalyzer(ControlFlow flow, DfaValueFactory factory) { + myFactory = factory; + myInstructions = flow.getInstructions(); + myBackwardMap = calcBackwardMap(); + } + + private List getSuccessors(Instruction i) { + if (i instanceof GotoInstruction) { + return Arrays.asList(myInstructions[((GotoInstruction)i).getOffset()]); + } + + int index = i.getIndex(); + if (i instanceof ConditionalGotoInstruction) { + return Arrays.asList(myInstructions[((ConditionalGotoInstruction)i).getOffset()], myInstructions[index + 1]); + } + + if (i instanceof ReturnInstruction) { + return Collections.emptyList(); + } + + return Arrays.asList(myInstructions[index + 1]); + + } + + private MultiMap calcBackwardMap() { + MultiMap result = MultiMap.create(); + for (Instruction instruction : myInstructions) { + for (Instruction next : getSuccessors(instruction)) { + result.putValue(next, instruction); + } + } + return result; + } + + private Map findLiveVars() { + final Map result = ContainerUtil.newHashMap(); + + runDfa(false, new PairFunction() { + @Override + public BitSet fun(Instruction instruction, BitSet liveVars) { + if (instruction instanceof FinishElementInstruction) { + BitSet set = result.get(instruction); + if (set != null) { + set.or(liveVars); + return set; + } else { + result.put((FinishElementInstruction)instruction, liveVars); + } + } + + if (instruction instanceof PushInstruction) { + DfaValue value = ((PushInstruction)instruction).getValue(); + if (value instanceof DfaVariableValue) { + if (((PushInstruction)instruction).isReferenceWrite()) { + liveVars = (BitSet)liveVars.clone(); + liveVars.clear(value.getID()); + for (DfaVariableValue var : myFactory.getVarFactory().getAllQualifiedBy((DfaVariableValue)value)) { + liveVars.clear(var.getID()); + } + } else if (!liveVars.get(value.getID())) { + liveVars = (BitSet)liveVars.clone(); + liveVars.set(value.getID()); + } + } + } else if (instruction instanceof FlushVariableInstruction) { + DfaVariableValue variable = ((FlushVariableInstruction)instruction).getVariable(); + if (variable != null) { + liveVars = (BitSet)liveVars.clone(); + liveVars.clear(variable.getID()); + for (DfaVariableValue var : myFactory.getVarFactory().getAllQualifiedBy(variable)) { + liveVars.clear(var.getID()); + } + } + } + + return liveVars; + } + }); + return result; + } + + void flushDeadVariablesOnStatementFinish() { + final Map liveVars = findLiveVars(); + + runDfa(true, new PairFunction() { + @Override + @NotNull + public BitSet fun(Instruction instruction, @NotNull BitSet prevLiveVars) { + if (instruction instanceof FinishElementInstruction) { + BitSet currentlyLive = liveVars.get(instruction); + if (currentlyLive == null) { + return new BitSet(); // an instruction unreachable from the end? + } + int index = 0; + while (true) { + int setBit = prevLiveVars.nextSetBit(index); + if (setBit < 0) break; + if (!currentlyLive.get(setBit)) { + ((FinishElementInstruction)instruction).getVarsToFlush().add((DfaVariableValue)myFactory.getValue(setBit)); + } + index = setBit + 1; + } + return currentlyLive; + } + + return prevLiveVars; + } + }); + } + + private void runDfa(boolean forward, PairFunction handleState) { + Set entryPoints = ContainerUtil.newHashSet(); + if (forward) { + entryPoints.add(myInstructions[0]); + } else { + entryPoints.addAll(ContainerUtil.findAll(myInstructions, FilteringIterator.instanceOf(ReturnInstruction.class))); + } + + Queue queue = new Queue(10); + for (Instruction i : entryPoints) { + queue.addLast(new InstructionState(i, new BitSet())); + } + + int steps = 0; + Set processed = ContainerUtil.newHashSet(); + while (!queue.isEmpty()) { + steps++; + InstructionState state = queue.pullFirst(); + Instruction instruction = state.first; + Collection nextInstructions = forward ? getSuccessors(instruction) : myBackwardMap.get(instruction); + boolean branching = nextInstructions.size() > 1 || !forward && instruction.getIndex() == 0; + BitSet nextVars = handleState.fun(instruction, state.second); + for (Instruction next : nextInstructions) { + InstructionState nextState = new InstructionState(next, nextVars); + if (!branching || processed.add(nextState)) { + queue.addLast(nextState); + } + } + } + if (steps > 10000) { + int a = 1; + } + } + + private static class InstructionState extends Pair { + public InstructionState(Instruction first, BitSet second) { + super(first, second); + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index efb0f1af5418..39a6d28e62af 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -111,7 +111,7 @@ public class StandardInstructionVisitor extends InstructionVisitor { @Override public DfaInstructionState[] visitPush(PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { - if (instruction.isReferenceRead()) { + if (!instruction.isReferenceWrite() && instruction.getPlace() instanceof PsiReferenceExpression) { DfaValue dfaValue = instruction.getValue(); if (dfaValue instanceof DfaVariableValue) { DfaConstValue constValue = memState.getConstantValue((DfaVariableValue)dfaValue); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FinishElementInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FinishElementInstruction.java new file mode 100644 index 000000000000..a0aabe963480 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/FinishElementInstruction.java @@ -0,0 +1,57 @@ +/* + * 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.dataFlow.instructions; + +import com.intellij.codeInspection.dataFlow.DataFlowRunner; +import com.intellij.codeInspection.dataFlow.DfaInstructionState; +import com.intellij.codeInspection.dataFlow.DfaMemoryState; +import com.intellij.codeInspection.dataFlow.InstructionVisitor; +import com.intellij.codeInspection.dataFlow.value.DfaVariableValue; +import com.intellij.psi.PsiElement; +import com.intellij.util.containers.ContainerUtil; + +import java.util.Set; + +/** + * @author peter + */ +public class FinishElementInstruction extends Instruction { + private final Set myVarsToFlush = ContainerUtil.newHashSet(); + private final PsiElement myElement; + + public FinishElementInstruction(PsiElement element) { + myElement = element; + } + + @Override + public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState state, InstructionVisitor visitor) { + if (!myVarsToFlush.isEmpty()) { + for (DfaVariableValue value : myVarsToFlush) { + state.flushVariable(value); + } + } + return nextInstruction(runner, state); + } + + @Override + public String toString() { + return "Finish " + myElement + "; flushing " + myVarsToFlush; + } + + public Set getVarsToFlush() { + return myVarsToFlush; + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java index 417890a3b111..8e59c1582d18 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/PushInstruction.java @@ -37,20 +37,20 @@ import org.jetbrains.annotations.Nullable; public class PushInstruction extends Instruction { private final DfaValue myValue; private final PsiExpression myPlace; - private final boolean myReferenceRead; + private final boolean myReferenceWrite; public PushInstruction(@Nullable DfaValue value, PsiExpression place) { this(value, place, false); } - public PushInstruction(@Nullable DfaValue value, PsiExpression place, final boolean isReferenceRead) { + public PushInstruction(@Nullable DfaValue value, PsiExpression place, final boolean isReferenceWrite) { myValue = value != null ? value : DfaUnknownValue.getInstance(); myPlace = place; - myReferenceRead = isReferenceRead; + myReferenceWrite = isReferenceWrite; } - public boolean isReferenceRead() { - return myReferenceRead; + public boolean isReferenceWrite() { + return myReferenceWrite; } @NotNull diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/FlushFurtherUnusedVariables.java b/java/java-tests/testData/inspection/dataFlow/fixture/FlushFurtherUnusedVariables.java new file mode 100644 index 000000000000..b2d139d11dde --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/FlushFurtherUnusedVariables.java @@ -0,0 +1,40 @@ +/* + * 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. + */ +import org.jetbrains.annotations.Contract; + +class Foo { + + void foo() { + int a1 = random() ? 1 : 0; + int a2 = random() ? 1 : 0; + int a3 = random() ? 1 : 0; + int a4 = random() ? 1 : 0; + int a5 = random() ? 1 : 0; + int a6 = random() ? 1 : 0; + int a7 = random() ? 1 : 0; + int a8 = random() ? 1 : 0; + int a9 = random() ? 1 : 0; + int a10 = random() ? 1 : 0; + int a11 = random() ? 1 : 0; + int a12 = random() ? 1 : 0; + int a13 = random() ? 1 : 0; + int a14 = random() ? 1 : 0; + int a15 = random() ? 1 : 0; + } + + native boolean random(); + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java index 85478517138d..405478625937 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java @@ -240,6 +240,7 @@ public class DataFlowInspectionTest extends LightCodeInsightFixtureTestCase { public void testManyDisjunctiveFieldAssignmentsInLoopNotComplex() { doTest(); } public void testManyContinuesNotComplex() { doTest(); } public void testFinallyNotComplex() { doTest(); } + public void testFlushFurtherUnusedVariables() { doTest(); } public void testVariablesDiverge() { doTest(); } public void testMergeByNullability() { doTest(); }