| /* |
| * 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 org.jetbrains.plugins.groovy.lang.psi.dataFlow.reachingDefs; |
| |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import gnu.trove.TIntHashSet; |
| import gnu.trove.TIntObjectHashMap; |
| import gnu.trove.TIntObjectProcedure; |
| import gnu.trove.TIntProcedure; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ControlFlowBuilderUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; |
| import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction; |
| import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAEngine; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.ClosureSyntheticParameter; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; |
| import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; |
| |
| import java.util.*; |
| |
| /** |
| * @author ven |
| */ |
| public class ReachingDefinitionsCollector { |
| private ReachingDefinitionsCollector() { |
| } |
| |
| @NotNull |
| public static FragmentVariableInfos obtainVariableFlowInformation(@NotNull final GrStatement first, |
| @NotNull final GrStatement last, |
| @NotNull final GrControlFlowOwner flowOwner, |
| @NotNull final Instruction[] flow) { |
| |
| final DefinitionMap dfaResult = inferDfaResult(flow); |
| |
| final LinkedHashSet<Integer> fragmentInstructions = getFragmentInstructions(first, last, flow); |
| final int[] postorder = ControlFlowBuilderUtil.postorder(flow); |
| LinkedHashSet<Integer> reachableFromFragmentReads = getReachable(fragmentInstructions, flow, dfaResult, postorder); |
| LinkedHashSet<Integer> fragmentReads = filterReads(fragmentInstructions, flow); |
| |
| final Map<String, VariableInfo> imap = new LinkedHashMap<String, VariableInfo>(); |
| final Map<String, VariableInfo> omap = new LinkedHashMap<String, VariableInfo>(); |
| |
| final PsiManager manager = first.getManager(); |
| |
| for (final Integer ref : fragmentReads) { |
| ReadWriteVariableInstruction rwInstruction = (ReadWriteVariableInstruction)flow[ref]; |
| String name = rwInstruction.getVariableName(); |
| final int[] defs = dfaResult.getDefinitions(ref); |
| if (!allDefsInFragment(defs, fragmentInstructions)) { |
| addVariable(name, imap, manager, getType(rwInstruction.getElement())); |
| } |
| } |
| |
| for (final Integer ref : reachableFromFragmentReads) { |
| ReadWriteVariableInstruction rwInstruction = (ReadWriteVariableInstruction)flow[ref]; |
| String name = rwInstruction.getVariableName(); |
| final int[] defs = dfaResult.getDefinitions(ref); |
| if (anyDefInFragment(defs, fragmentInstructions)) { |
| for (int def : defs) { |
| if (fragmentInstructions.contains(def)) { |
| PsiType outputType = getType(flow[def].getElement()); |
| addVariable(name, omap, manager, outputType); |
| } |
| } |
| |
| if (!allProperDefsInFragment(defs, ref, fragmentInstructions, postorder)) { |
| PsiType inputType = getType(rwInstruction.getElement()); |
| addVariable(name, imap, manager, inputType); |
| } |
| } |
| } |
| |
| addClosureUsages(imap, omap, first, last, flowOwner); |
| |
| final VariableInfo[] iarr = filterNonlocals(imap, first); |
| final VariableInfo[] oarr = filterNonlocals(omap, first); |
| |
| return new FragmentVariableInfos() { |
| @Override |
| public VariableInfo[] getInputVariableNames() { |
| return iarr; |
| } |
| |
| @Override |
| public VariableInfo[] getOutputVariableNames() { |
| return oarr; |
| } |
| }; |
| } |
| |
| private static DefinitionMap inferDfaResult(Instruction[] flow) { |
| final ReachingDefinitionsDfaInstance dfaInstance = new ReachingDefinitionsDfaInstance(flow); |
| final ReachingDefinitionsSemilattice lattice = new ReachingDefinitionsSemilattice(); |
| final DFAEngine<DefinitionMap> engine = new DFAEngine<DefinitionMap>(flow, dfaInstance, lattice); |
| return postprocess(engine.performForceDFA(), flow, dfaInstance); |
| } |
| |
| private static void addClosureUsages(final Map<String, VariableInfo> imap, |
| final Map<String, VariableInfo> omap, |
| final GrStatement first, |
| final GrStatement last, |
| GrControlFlowOwner flowOwner) { |
| flowOwner.accept(new GroovyRecursiveElementVisitor() { |
| @Override |
| public void visitClosure(GrClosableBlock closure) { |
| addUsagesInClosure(imap, omap, closure, first, last); |
| super.visitClosure(closure); |
| } |
| |
| private void addUsagesInClosure(final Map<String, VariableInfo> imap, |
| final Map<String, VariableInfo> omap, |
| final GrClosableBlock closure, |
| final GrStatement first, |
| final GrStatement last) { |
| closure.accept(new GroovyRecursiveElementVisitor() { |
| @Override |
| public void visitReferenceExpression(GrReferenceExpression refExpr) { |
| if (refExpr.isQualified()) { |
| return; |
| } |
| PsiElement resolved = refExpr.resolve(); |
| if (!(resolved instanceof GrVariable)) { |
| return; |
| } |
| GrVariable variable = (GrVariable)resolved; |
| if (PsiTreeUtil.isAncestor(closure, variable, true)) { |
| return; |
| } |
| if (variable instanceof ClosureSyntheticParameter && |
| PsiTreeUtil.isAncestor(closure, ((ClosureSyntheticParameter)variable).getClosure(), false)) { |
| return; |
| } |
| |
| String name = variable.getName(); |
| if (!(variable instanceof GrField)) { |
| if (!isInFragment(first, last, resolved)) { |
| if (isInFragment(first, last, closure)) { |
| addVariable(name, imap, variable.getManager(), variable.getType()); |
| } |
| } |
| else { |
| if (!isInFragment(first, last, closure)) { |
| addVariable(name, omap, variable.getManager(), variable.getType()); |
| } |
| } |
| } |
| } |
| }); |
| } |
| }); |
| } |
| |
| private static void addVariable(String name, Map<String, VariableInfo> map, PsiManager manager, PsiType type) { |
| VariableInfoImpl info = (VariableInfoImpl)map.get(name); |
| if (info == null) { |
| info = new VariableInfoImpl(name, manager); |
| map.put(name, info); |
| } |
| info.addSubtype(type); |
| } |
| |
| private static LinkedHashSet<Integer> filterReads(final LinkedHashSet<Integer> instructions, final Instruction[] flow) { |
| final LinkedHashSet<Integer> result = new LinkedHashSet<Integer>(); |
| for (final Integer i : instructions) { |
| final Instruction instruction = flow[i]; |
| if (isReadInsn(instruction)) { |
| result.add(i); |
| } |
| } |
| return result; |
| } |
| |
| private static boolean allDefsInFragment(int[] defs, LinkedHashSet<Integer> fragmentInstructions) { |
| for (int def : defs) { |
| if (!fragmentInstructions.contains(def)) return false; |
| } |
| |
| return true; |
| } |
| |
| private static boolean allProperDefsInFragment(int[] defs, int ref, LinkedHashSet<Integer> fragmentInstructions, int[] postorder) { |
| for (int def : defs) { |
| if (!fragmentInstructions.contains(def) && postorder[def] < postorder[ref]) return false; |
| } |
| |
| return true; |
| } |
| |
| |
| private static boolean anyDefInFragment(int[] defs, LinkedHashSet<Integer> fragmentInstructions) { |
| for (int def : defs) { |
| if (fragmentInstructions.contains(def)) return true; |
| } |
| |
| return false; |
| } |
| |
| @Nullable |
| private static PsiType getType(PsiElement element) { |
| if (element instanceof GrVariable) { |
| return ((GrVariable)element).getTypeGroovy(); |
| } |
| else if (element instanceof GrReferenceExpression) return ((GrReferenceExpression)element).getType(); |
| return null; |
| } |
| |
| private static VariableInfo[] filterNonlocals(Map<String, VariableInfo> infos, GrStatement place) { |
| List<VariableInfo> result = new ArrayList<VariableInfo>(); |
| for (Iterator<VariableInfo> iterator = infos.values().iterator(); iterator.hasNext(); ) { |
| VariableInfo info = iterator.next(); |
| String name = info.getName(); |
| GroovyPsiElement property = ResolveUtil.resolveProperty(place, name); |
| if (property instanceof GrVariable) { |
| iterator.remove(); |
| } |
| else if (property instanceof GrReferenceExpression) { |
| GrMember member = PsiTreeUtil.getParentOfType(property, GrMember.class); |
| if (member == null) { |
| continue; |
| } |
| else if (!member.hasModifierProperty(PsiModifier.STATIC)) { |
| if (member.getContainingClass() instanceof GroovyScriptClass) { |
| //binding variable |
| continue; |
| } |
| } |
| } |
| if (ResolveUtil.resolveClass(place, name) == null) { |
| result.add(info); |
| } |
| } |
| return result.toArray(new VariableInfo[result.size()]); |
| } |
| |
| private static LinkedHashSet<Integer> getFragmentInstructions(GrStatement first, GrStatement last, Instruction[] flow) { |
| LinkedHashSet<Integer> result = new LinkedHashSet<Integer>(); |
| for (Instruction instruction : flow) { |
| if (isInFragment(instruction, first, last)) { |
| result.add(instruction.num()); |
| } |
| } |
| return result; |
| } |
| |
| private static boolean isInFragment(Instruction instruction, GrStatement first, GrStatement last) { |
| final PsiElement element = instruction.getElement(); |
| if (element == null) return false; |
| return isInFragment(first, last, element); |
| } |
| |
| private static boolean isInFragment(GrStatement first, GrStatement last, PsiElement element) { |
| final PsiElement parent = first.getParent(); |
| if (!PsiTreeUtil.isAncestor(parent, element, true)) return false; |
| PsiElement run = element; |
| while (run.getParent() != parent) run = run.getParent(); |
| return isBetween(first, last, run); |
| } |
| |
| private static boolean isBetween(PsiElement first, PsiElement last, PsiElement run) { |
| while (first != null && first != run) first = first.getNextSibling(); |
| if (first == null) return false; |
| while (last != null && last != run) last = last.getPrevSibling(); |
| if (last == null) return false; |
| |
| return true; |
| } |
| |
| private static LinkedHashSet<Integer> getReachable(final LinkedHashSet<Integer> fragmentInsns, |
| final Instruction[] flow, |
| final DefinitionMap dfaResult, |
| final int[] postorder) { |
| final LinkedHashSet<Integer> result = new LinkedHashSet<Integer>(); |
| for (final Instruction insn : flow) { |
| if (isReadInsn(insn)) { |
| final int ref = insn.num(); |
| int[] definitions = dfaResult.getDefinitions(ref); |
| if (definitions != null) { |
| for (final int def : definitions) { |
| if (fragmentInsns.contains(def) && |
| (!fragmentInsns.contains(ref) || postorder[ref] < postorder[def] && checkPathIsOutsideOfFragment(def, ref, flow, fragmentInsns))) { |
| result.add(ref); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| private static boolean checkPathIsOutsideOfFragment(int def, int ref, Instruction[] flow, LinkedHashSet<Integer> fragmentInsns) { |
| Boolean path = findPath(flow[def], ref, fragmentInsns, false, new HashMap<Instruction, Boolean>()); |
| assert path != null : "def=" + def + ", ref=" + ref; |
| return path.booleanValue(); |
| } |
| |
| /** |
| * return true if path is outside of fragment, null if there is no pathand false if path is inside fragment |
| */ |
| @Nullable |
| private static Boolean findPath(Instruction cur, |
| int destination, |
| LinkedHashSet<Integer> fragmentInsns, |
| boolean wasOutside, |
| HashMap<Instruction, Boolean> visited) { |
| wasOutside = wasOutside || !fragmentInsns.contains(cur.num()); |
| visited.put(cur, null); |
| Iterable<? extends Instruction> instructions = cur.allSuccessors(); |
| |
| boolean pathExists = false; |
| for (Instruction i : instructions) { |
| if (i.num() == destination) return wasOutside; |
| |
| Boolean result; |
| if (visited.containsKey(i)) { |
| result = visited.get(i); |
| } |
| else { |
| result = findPath(i, destination, fragmentInsns, wasOutside, visited); |
| visited.put(i, result); |
| } |
| if (result != null) { |
| if (result.booleanValue()) { |
| visited.put(cur, true); |
| return true; |
| } |
| pathExists = true; |
| } |
| } |
| if (pathExists) { |
| visited.put(cur, false); |
| return false; |
| } |
| else { |
| visited.put(cur, null); |
| return null; |
| } |
| } |
| |
| |
| private static boolean isReadInsn(Instruction insn) { |
| return insn instanceof ReadWriteVariableInstruction && !((ReadWriteVariableInstruction)insn).isWrite(); |
| } |
| |
| @SuppressWarnings({"UnusedDeclaration"}) |
| private static String dumpDfaResult(ArrayList<TIntObjectHashMap<TIntHashSet>> dfaResult, ReachingDefinitionsDfaInstance dfa) { |
| final StringBuffer buffer = new StringBuffer(); |
| for (int i = 0; i < dfaResult.size(); i++) { |
| TIntObjectHashMap<TIntHashSet> map = dfaResult.get(i); |
| buffer.append("At ").append(i).append(":\n"); |
| map.forEachEntry(new TIntObjectProcedure<TIntHashSet>() { |
| @Override |
| public boolean execute(int i, TIntHashSet defs) { |
| buffer.append(i).append(" -> "); |
| defs.forEach(new TIntProcedure() { |
| @Override |
| public boolean execute(int i) { |
| buffer.append(i).append(" "); |
| return true; |
| } |
| }); |
| return true; |
| } |
| }); |
| buffer.append("\n"); |
| } |
| |
| return buffer.toString(); |
| } |
| |
| private static class VariableInfoImpl implements VariableInfo { |
| @NotNull private final String myName; |
| private final PsiManager myManager; |
| |
| @Nullable private |
| PsiType myType; |
| |
| VariableInfoImpl(@NotNull String name, PsiManager manager) { |
| myName = name; |
| myManager = manager; |
| } |
| |
| @Override |
| @NotNull |
| public String getName() { |
| return myName; |
| } |
| |
| @Override |
| @Nullable |
| public PsiType getType() { |
| if (myType instanceof PsiIntersectionType) return ((PsiIntersectionType)myType).getConjuncts()[0]; |
| return myType; |
| } |
| |
| void addSubtype(PsiType t) { |
| if (t != null) { |
| if (myType == null) { |
| myType = t; |
| } |
| else { |
| if (!myType.isAssignableFrom(t)) { |
| if (t.isAssignableFrom(myType)) { |
| myType = t; |
| } |
| else { |
| myType = TypesUtil.getLeastUpperBound(myType, t, myManager); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @NotNull |
| private static DefinitionMap postprocess(@NotNull final ArrayList<DefinitionMap> dfaResult, |
| @NotNull Instruction[] flow, |
| @NotNull ReachingDefinitionsDfaInstance dfaInstance) { |
| DefinitionMap result = new DefinitionMap(); |
| for (int i = 0; i < flow.length; i++) { |
| Instruction insn = flow[i]; |
| if (insn instanceof ReadWriteVariableInstruction) { |
| ReadWriteVariableInstruction rwInsn = (ReadWriteVariableInstruction)insn; |
| if (!rwInsn.isWrite()) { |
| int idx = dfaInstance.getVarIndex(rwInsn.getVariableName()); |
| result.copyFrom(dfaResult.get(i), idx, i); |
| } |
| } |
| } |
| return result; |
| } |
| } |