mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-26 19:06:24 +07:00
Java control flow: Detect redundant assignment when the control flow includes a try-finally statement (IDEA-155836)
This commit is contained in:
@@ -23,13 +23,13 @@ import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.containers.IntArrayList;
|
||||
import com.intellij.util.containers.Queue;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import gnu.trove.THashMap;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
@@ -64,67 +64,71 @@ public class DefUseUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static class InstructionState {
|
||||
private Set<PsiVariable> myVariablesUseArmed;
|
||||
private final int myInstructionIdx;
|
||||
private final IntArrayList myBackwardTraces;
|
||||
private static class InstructionState implements Comparable<InstructionState> {
|
||||
private Set<PsiVariable> myUsed;
|
||||
private final InstructionKey myInstructionKey;
|
||||
private final List<InstructionKey> myBackwardTraces;
|
||||
private boolean myIsVisited;
|
||||
|
||||
public InstructionState(int instructionIdx) {
|
||||
myInstructionIdx = instructionIdx;
|
||||
myBackwardTraces = new IntArrayList();
|
||||
myVariablesUseArmed = null;
|
||||
public InstructionState(@NotNull InstructionKey instructionKey) {
|
||||
myInstructionKey = instructionKey;
|
||||
myBackwardTraces = new ArrayList<InstructionKey>();
|
||||
myUsed = null;
|
||||
}
|
||||
|
||||
public void addBackwardTrace(int i) {
|
||||
myBackwardTraces.add(i);
|
||||
public void addBackwardTrace(InstructionKey key) {
|
||||
myBackwardTraces.add(key);
|
||||
}
|
||||
|
||||
public IntArrayList getBackwardTraces() {
|
||||
public List<InstructionKey> getBackwardTraces() {
|
||||
return myBackwardTraces;
|
||||
}
|
||||
|
||||
public int getInstructionIdx() {
|
||||
return myInstructionIdx;
|
||||
public InstructionKey getInstructionKey() {
|
||||
return myInstructionKey;
|
||||
}
|
||||
|
||||
void mergeUseArmed(PsiVariable psiVariable) {
|
||||
void addUsed(PsiVariable psiVariable) {
|
||||
touch();
|
||||
myVariablesUseArmed.add(psiVariable);
|
||||
myUsed.add(psiVariable);
|
||||
}
|
||||
|
||||
boolean mergeUseDisarmed(PsiVariable psiVariable) {
|
||||
boolean removeUsed(PsiVariable psiVariable) {
|
||||
touch();
|
||||
|
||||
boolean result = myVariablesUseArmed.contains(psiVariable);
|
||||
myVariablesUseArmed.remove(psiVariable);
|
||||
|
||||
return result;
|
||||
return myUsed.remove(psiVariable);
|
||||
}
|
||||
|
||||
private void touch() {
|
||||
if (myVariablesUseArmed == null) myVariablesUseArmed = new THashSet<PsiVariable>();
|
||||
if (myUsed == null) myUsed = new THashSet<PsiVariable>();
|
||||
}
|
||||
|
||||
public void merge(InstructionState state) {
|
||||
public void addUsedFrom(InstructionState state) {
|
||||
touch();
|
||||
myVariablesUseArmed.addAll(state.myVariablesUseArmed);
|
||||
myUsed.addAll(state.myUsed);
|
||||
}
|
||||
|
||||
public boolean contains(InstructionState state) {
|
||||
return myVariablesUseArmed != null && state.myVariablesUseArmed != null &&
|
||||
myVariablesUseArmed.containsAll(state.myVariablesUseArmed);
|
||||
return myUsed != null && state.myUsed != null &&
|
||||
myUsed.containsAll(state.myUsed);
|
||||
}
|
||||
|
||||
public boolean markVisited() {
|
||||
boolean old = myIsVisited;
|
||||
public void markVisited() {
|
||||
myIsVisited = true;
|
||||
return old;
|
||||
}
|
||||
|
||||
public boolean isVisited() {
|
||||
return myIsVisited;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull InstructionState other) {
|
||||
return myInstructionKey.compareTo(other.myInstructionKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myInstructionKey + " " + myBackwardTraces + (myIsVisited ? "(v)" : "(n)") + " " + (myUsed != null ? myUsed : "-");
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -165,9 +169,11 @@ public class DefUseUtil {
|
||||
}
|
||||
}
|
||||
|
||||
InstructionState[] states = getStates(instructions);
|
||||
Map<InstructionKey, InstructionState> stateMap = getStates(instructions);
|
||||
InstructionState[] states = stateMap.values().toArray(new InstructionState[0]);
|
||||
Arrays.sort(states);
|
||||
|
||||
boolean[] defsArmed = new boolean[instructions.size()];
|
||||
BitSet usefulWrites = new BitSet(instructions.size());
|
||||
|
||||
Queue<InstructionState> queue = new Queue<InstructionState>(8);
|
||||
|
||||
@@ -178,7 +184,7 @@ public class DefUseUtil {
|
||||
|
||||
for (PsiVariable psiVariable : assignedVariables) {
|
||||
if (psiVariable instanceof PsiField) {
|
||||
outerState.mergeUseArmed(psiVariable);
|
||||
outerState.addUsed(psiVariable);
|
||||
}
|
||||
}
|
||||
queue.addLast(outerState);
|
||||
@@ -188,21 +194,21 @@ public class DefUseUtil {
|
||||
InstructionState state = queue.pullFirst();
|
||||
state.markVisited();
|
||||
|
||||
int idx = state.getInstructionIdx();
|
||||
if (idx < instructions.size()) {
|
||||
Instruction instruction = instructions.get(idx);
|
||||
InstructionKey key = state.getInstructionKey();
|
||||
if (key.getOffset() < instructions.size()) {
|
||||
Instruction instruction = instructions.get(key.getOffset());
|
||||
|
||||
if (instruction instanceof WriteVariableInstruction) {
|
||||
WriteVariableInstruction writeInstruction = (WriteVariableInstruction)instruction;
|
||||
PsiVariable psiVariable = writeInstruction.variable;
|
||||
outUsedVariables.add(psiVariable);
|
||||
if (state.mergeUseDisarmed(psiVariable)) {
|
||||
defsArmed[idx] = true;
|
||||
if (state.removeUsed(psiVariable)) {
|
||||
usefulWrites.set(key.getOffset());
|
||||
}
|
||||
}
|
||||
else if (instruction instanceof ReadVariableInstruction) {
|
||||
ReadVariableInstruction readInstruction = (ReadVariableInstruction)instruction;
|
||||
state.mergeUseArmed(readInstruction.variable);
|
||||
state.addUsed(readInstruction.variable);
|
||||
outUsedVariables.add(readInstruction.variable);
|
||||
}
|
||||
else {
|
||||
@@ -210,12 +216,11 @@ public class DefUseUtil {
|
||||
}
|
||||
}
|
||||
|
||||
IntArrayList backwardTraces = state.getBackwardTraces();
|
||||
for (int j = 0; j < backwardTraces.size(); j++) {
|
||||
int prevIdx = backwardTraces.get(j);
|
||||
InstructionState prevState = states[prevIdx];
|
||||
if (!prevState.contains(state)) {
|
||||
prevState.merge(state);
|
||||
List<InstructionKey> backwardTraces = state.getBackwardTraces();
|
||||
for (InstructionKey prevKeys : backwardTraces) {
|
||||
InstructionState prevState = stateMap.get(prevKeys);
|
||||
if (prevState != null && !prevState.contains(state)) {
|
||||
prevState.addUsedFrom(state);
|
||||
queue.addLast(prevState);
|
||||
}
|
||||
}
|
||||
@@ -228,7 +233,7 @@ public class DefUseUtil {
|
||||
Instruction instruction = instructions.get(i);
|
||||
if (instruction instanceof WriteVariableInstruction) {
|
||||
WriteVariableInstruction writeInstruction = (WriteVariableInstruction)instruction;
|
||||
if (!defsArmed[i]) {
|
||||
if (!usefulWrites.get(i)) {
|
||||
PsiElement context = PsiTreeUtil.getNonStrictParentOfType(flow.getElement(i),
|
||||
PsiStatement.class, PsiAssignmentExpression.class,
|
||||
PsiPostfixExpression.class, PsiPrefixExpression.class);
|
||||
@@ -254,16 +259,16 @@ public class DefUseUtil {
|
||||
public static PsiElement[] getDefs(PsiCodeBlock body, final PsiVariable def, PsiElement ref) {
|
||||
try {
|
||||
RefsDefs refsDefs = new RefsDefs(body) {
|
||||
private final InstructionState[] states = getStates(instructions);
|
||||
private final IntArrayList[] myBackwardTraces = getBackwardTraces(instructions);
|
||||
|
||||
@Override
|
||||
protected int nNext(int index) {
|
||||
return states[index].getBackwardTraces().size();
|
||||
return myBackwardTraces[index].size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getNext(int index, int no) {
|
||||
return states[index].getBackwardTraces().get(no);
|
||||
return myBackwardTraces[index].get(no);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -407,7 +412,7 @@ public class DefUseUtil {
|
||||
}
|
||||
}
|
||||
|
||||
// hack: ControlFlow doesnn't contains parameters initialization
|
||||
// hack: ControlFlow doesn't contains parameters initialization
|
||||
if (index == 0 && def instanceof PsiParameter) {
|
||||
res.add(def.getNameIdentifier());
|
||||
}
|
||||
@@ -442,10 +447,10 @@ public class DefUseUtil {
|
||||
}
|
||||
|
||||
|
||||
private static InstructionState[] getStates(final List<Instruction> instructions) {
|
||||
final InstructionState[] states = new InstructionState[instructions.size()];
|
||||
private static IntArrayList[] getBackwardTraces(final List<Instruction> instructions) {
|
||||
final IntArrayList[] states = new IntArrayList[instructions.size()];
|
||||
for (int i = 0; i < states.length; i++) {
|
||||
states[i] = new InstructionState(i);
|
||||
states[i] = new IntArrayList();
|
||||
}
|
||||
|
||||
for (int i = 0; i < instructions.size(); i++) {
|
||||
@@ -453,13 +458,105 @@ public class DefUseUtil {
|
||||
for (int j = 0; j != instruction.nNext(); ++ j) {
|
||||
final int next = instruction.getNext(i, j);
|
||||
if (next < states.length) {
|
||||
states[next].addBackwardTrace(i);
|
||||
states[next].add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return states;
|
||||
}
|
||||
|
||||
private static Map<InstructionKey, InstructionState> getStates(final List<Instruction> instructions) {
|
||||
class WalkThroughStack {
|
||||
private final com.intellij.util.containers.Stack<InstructionKey> myFrom;
|
||||
private final com.intellij.util.containers.Stack<InstructionKey> myNext;
|
||||
|
||||
WalkThroughStack(int size) {
|
||||
if (size < 2) size = 2;
|
||||
myFrom = new Stack<InstructionKey>(size);
|
||||
myNext = new Stack<InstructionKey>(size);
|
||||
}
|
||||
|
||||
void push(InstructionKey fromKey, InstructionKey nextKey) {
|
||||
myFrom.push(fromKey);
|
||||
myNext.push(nextKey);
|
||||
}
|
||||
|
||||
InstructionKey peekFrom() {
|
||||
return myFrom.peek();
|
||||
}
|
||||
|
||||
InstructionKey popNext() {
|
||||
myFrom.pop();
|
||||
return myNext.pop();
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return myFrom.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
class Walker {
|
||||
private final Map<InstructionKey, InstructionState> myStates;
|
||||
private final WalkThroughStack myWalkThroughStack;
|
||||
|
||||
Walker() {
|
||||
myStates = new THashMap<InstructionKey, InstructionState>(instructions.size());
|
||||
myWalkThroughStack = new WalkThroughStack(instructions.size() / 2);
|
||||
}
|
||||
|
||||
Map<InstructionKey, InstructionState> walk() {
|
||||
InstructionKey startKey = InstructionKey.create(0);
|
||||
myStates.put(startKey, new InstructionState(startKey));
|
||||
myWalkThroughStack.push(InstructionKey.create(-1), startKey);
|
||||
|
||||
Set<InstructionKey> visited = new THashSet<InstructionKey>(instructions.size());
|
||||
while (!myWalkThroughStack.isEmpty()) {
|
||||
InstructionKey fromKey = myWalkThroughStack.peekFrom();
|
||||
InstructionKey nextKey = myWalkThroughStack.popNext();
|
||||
addBackwardTrace(fromKey, nextKey);
|
||||
if (!visited.contains(nextKey)) {
|
||||
visit(nextKey);
|
||||
visited.add(nextKey);
|
||||
}
|
||||
}
|
||||
return myStates;
|
||||
}
|
||||
|
||||
private void visit(InstructionKey fromKey) {
|
||||
if (fromKey.getOffset() >= instructions.size()) return;
|
||||
final Instruction instruction = instructions.get(fromKey.getOffset());
|
||||
if (instruction instanceof CallInstruction) {
|
||||
int nextOffset = ((CallInstruction)instruction).procBegin;
|
||||
int returnOffset = fromKey.getOffset() + 1;
|
||||
InstructionKey nextKey = fromKey.push(nextOffset, returnOffset);
|
||||
myWalkThroughStack.push(fromKey, nextKey);
|
||||
}
|
||||
else if (instruction instanceof ReturnInstruction) {
|
||||
int overriddenOffset = ((ReturnInstruction)instruction).offset;
|
||||
InstructionKey nextKey = fromKey.pop(overriddenOffset);
|
||||
myWalkThroughStack.push(fromKey, nextKey);
|
||||
}
|
||||
else {
|
||||
for (int no = 0; no != instruction.nNext(); no++) {
|
||||
final int nextOffset = instruction.getNext(fromKey.getOffset(), no);
|
||||
InstructionKey nextKey = fromKey.next(nextOffset);
|
||||
myWalkThroughStack.push(fromKey, nextKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addBackwardTrace(InstructionKey fromKey, InstructionKey nextKey) {
|
||||
if (fromKey.getOffset() >= 0 && nextKey.getOffset() < instructions.size()) {
|
||||
InstructionState state = myStates.get(nextKey);
|
||||
if (state == null) myStates.put(nextKey, state = new InstructionState(nextKey));
|
||||
state.addBackwardTrace(fromKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Walker().walk();
|
||||
}
|
||||
|
||||
private static final ControlFlowPolicy ourPolicy = new ControlFlowPolicy() {
|
||||
@Override
|
||||
public PsiVariable getUsedVariable(@NotNull PsiReferenceExpression refExpr) {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2000-2016 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.psi.controlFlow;
|
||||
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
class InstructionKey implements Comparable<InstructionKey> {
|
||||
private final int myOffset;
|
||||
private final int[] myCallStack;
|
||||
|
||||
private InstructionKey(int offset, @NotNull int[] callStack) {
|
||||
myOffset = offset;
|
||||
myCallStack = callStack;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static InstructionKey create(int offset) {
|
||||
return new InstructionKey(offset, ArrayUtil.EMPTY_INT_ARRAY);
|
||||
}
|
||||
|
||||
InstructionKey next(int nextOffset) {
|
||||
return new InstructionKey(nextOffset, myCallStack);
|
||||
}
|
||||
|
||||
InstructionKey push(int nextOffset, int returnOffset) {
|
||||
int[] nextStack = ArrayUtil.realloc(myCallStack, myCallStack.length + 1);
|
||||
nextStack[myCallStack.length] = returnOffset;
|
||||
return new InstructionKey(nextOffset, nextStack);
|
||||
}
|
||||
|
||||
InstructionKey pop(int overriddenOffset) {
|
||||
int returnOffset = myCallStack[myCallStack.length - 1];
|
||||
int[] nextStack = ArrayUtil.realloc(myCallStack, myCallStack.length - 1);
|
||||
int nextOffset = overriddenOffset != 0 ? overriddenOffset : returnOffset;
|
||||
return new InstructionKey(nextOffset, nextStack);
|
||||
}
|
||||
|
||||
int getOffset() {
|
||||
return myOffset;
|
||||
}
|
||||
|
||||
int[] getCallStack() {
|
||||
return myCallStack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
InstructionKey key = (InstructionKey)o;
|
||||
|
||||
if (myOffset != key.myOffset) return false;
|
||||
if (!Arrays.equals(myCallStack, key.myCallStack)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = myOffset;
|
||||
result = 31 * result + Arrays.hashCode(myCallStack);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (myCallStack.length == 0) {
|
||||
return String.valueOf(myOffset);
|
||||
}
|
||||
StringBuilder s = new StringBuilder();
|
||||
for (int offset : myCallStack) {
|
||||
if (s.length() != 0) s.append(',');
|
||||
s.append(offset);
|
||||
}
|
||||
return myOffset + "(" + s + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull InstructionKey key) {
|
||||
int c = myOffset - key.myOffset;
|
||||
if (c != 0) return c;
|
||||
for (int i = 0, len = Math.min(myCallStack.length, key.myCallStack.length); i < len; i++) {
|
||||
c = myCallStack[i] - key.myCallStack[i];
|
||||
if (c != 0) return c;
|
||||
}
|
||||
c = myCallStack.length - key.myCallStack.length;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user