001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.cfg;
018
019import com.google.common.collect.Maps;
020import com.google.common.collect.Sets;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.annotations.Nullable;
023import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.*;
024import org.jetbrains.jet.lang.cfg.pseudocode.*;
025import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
026import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
027import org.jetbrains.jet.lang.psi.JetDeclaration;
028import org.jetbrains.jet.lang.psi.JetProperty;
029import org.jetbrains.jet.lang.resolve.BindingContext;
030
031import java.util.Collection;
032import java.util.Collections;
033import java.util.Map;
034import java.util.Set;
035
036import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.LookInsideStrategy.ANALYSE_LOCAL_DECLARATIONS;
037import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.LookInsideStrategy.SKIP_LOCAL_DECLARATIONS;
038import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.TraversalOrder.BACKWARD;
039import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.TraversalOrder.FORWARD;
040
041public class PseudocodeVariablesData {
042    private final Pseudocode pseudocode;
043    private final BindingContext bindingContext;
044
045    private final Map<Pseudocode, Set<VariableDescriptor>> declaredVariablesForDeclaration = Maps.newHashMap();
046    private final Map<Pseudocode, Set<VariableDescriptor>> usedVariablesForDeclaration = Maps.newHashMap();
047
048    private Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> variableInitializers;
049
050    public PseudocodeVariablesData(@NotNull Pseudocode pseudocode, @NotNull BindingContext bindingContext) {
051        this.pseudocode = pseudocode;
052        this.bindingContext = bindingContext;
053    }
054
055    @NotNull
056    public Pseudocode getPseudocode() {
057        return pseudocode;
058    }
059
060    @NotNull
061    public Set<VariableDescriptor> getUsedVariables(@NotNull Pseudocode pseudocode) {
062        Set<VariableDescriptor> usedVariables = usedVariablesForDeclaration.get(pseudocode);
063        if (usedVariables == null) {
064            final Set<VariableDescriptor> result = Sets.newHashSet();
065            PseudocodeTraverser.traverse(pseudocode, FORWARD, new InstructionAnalyzeStrategy() {
066                @Override
067                public void execute(@NotNull Instruction instruction) {
068                    VariableDescriptor variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, false,
069                                                                                                          bindingContext);
070                    if (variableDescriptor != null) {
071                        result.add(variableDescriptor);
072                    }
073                }
074            });
075            usedVariables = Collections.unmodifiableSet(result);
076            usedVariablesForDeclaration.put(pseudocode, usedVariables);
077        }
078        return usedVariables;
079    }
080
081    @NotNull
082    public Set<VariableDescriptor> getDeclaredVariables(@NotNull Pseudocode pseudocode, boolean includeInsideLocalDeclarations) {
083        if (!includeInsideLocalDeclarations) {
084            return getUpperLevelDeclaredVariables(pseudocode);
085        }
086        Set<VariableDescriptor> declaredVariables = Sets.newHashSet();
087        declaredVariables.addAll(getUpperLevelDeclaredVariables(pseudocode));
088
089        for (LocalDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) {
090            Pseudocode localPseudocode = localDeclarationInstruction.getBody();
091            declaredVariables.addAll(getUpperLevelDeclaredVariables(localPseudocode));
092        }
093        return declaredVariables;
094    }
095
096    @NotNull
097    private Set<VariableDescriptor> getUpperLevelDeclaredVariables(@NotNull Pseudocode pseudocode) {
098        Set<VariableDescriptor> declaredVariables = declaredVariablesForDeclaration.get(pseudocode);
099        if (declaredVariables == null) {
100            declaredVariables = computeDeclaredVariablesForPseudocode(pseudocode);
101            declaredVariablesForDeclaration.put(pseudocode, declaredVariables);
102        }
103        return declaredVariables;
104    }
105
106    @NotNull
107    private Set<VariableDescriptor> computeDeclaredVariablesForPseudocode(Pseudocode pseudocode) {
108        Set<VariableDescriptor> declaredVariables = Sets.newHashSet();
109        for (Instruction instruction : pseudocode.getInstructions()) {
110            if (instruction instanceof VariableDeclarationInstruction) {
111                JetDeclaration variableDeclarationElement = ((VariableDeclarationInstruction) instruction).getVariableDeclarationElement();
112                DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, variableDeclarationElement);
113                if (descriptor != null) {
114                    assert descriptor instanceof VariableDescriptor;
115                    declaredVariables.add((VariableDescriptor) descriptor);
116                }
117            }
118        }
119        return Collections.unmodifiableSet(declaredVariables);
120    }
121
122    // variable initializers
123
124    @NotNull
125    public Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> getVariableInitializers() {
126        if (variableInitializers == null) {
127            variableInitializers = getVariableInitializers(pseudocode);
128        }
129
130        return variableInitializers;
131    }
132
133    @NotNull
134    private Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> getVariableInitializers(@NotNull Pseudocode pseudocode) {
135
136        Set<VariableDescriptor> usedVariables = getUsedVariables(pseudocode);
137        Set<VariableDescriptor> declaredVariables = getDeclaredVariables(pseudocode, false);
138        Map<VariableDescriptor, VariableInitState> initialMap = Collections.emptyMap();
139        Map<VariableDescriptor, VariableInitState> initialMapForStartInstruction = prepareInitializersMapForStartInstruction(
140                usedVariables, declaredVariables);
141
142        Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> variableInitializersMap = PseudocodeTraverser.collectData(
143                pseudocode, FORWARD, SKIP_LOCAL_DECLARATIONS,
144                initialMap, initialMapForStartInstruction, new PseudocodeTraverser.InstructionDataMergeStrategy<Map<VariableDescriptor, VariableInitState>>() {
145            @Override
146            public Edges<Map<VariableDescriptor, VariableInitState>> execute(
147                    @NotNull Instruction instruction, @NotNull Collection<Map<VariableDescriptor, VariableInitState>> incomingEdgesData) {
148
149                Map<VariableDescriptor, VariableInitState> enterInstructionData = mergeIncomingEdgesDataForInitializers(incomingEdgesData);
150                Map<VariableDescriptor, VariableInitState> exitInstructionData =
151                        addVariableInitStateFromCurrentInstructionIfAny(instruction, enterInstructionData);
152                return Edges.create(enterInstructionData, exitInstructionData);
153            }
154        });
155
156
157        for (LocalDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) {
158            Pseudocode localPseudocode = localDeclarationInstruction.getBody();
159            Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> initializersForLocalDeclaration = getVariableInitializers(localPseudocode);
160
161            for (Instruction instruction : initializersForLocalDeclaration.keySet()) {
162                //todo
163                if (!variableInitializersMap.containsKey(instruction)) {
164                    variableInitializersMap.put(instruction, initializersForLocalDeclaration.get(instruction));
165                }
166            }
167            variableInitializersMap.putAll(initializersForLocalDeclaration);
168        }
169        return variableInitializersMap;
170    }
171
172    @NotNull
173    private static Map<VariableDescriptor, VariableInitState> prepareInitializersMapForStartInstruction(
174            @NotNull Collection<VariableDescriptor> usedVariables,
175            @NotNull Collection<VariableDescriptor> declaredVariables) {
176
177        Map<VariableDescriptor, VariableInitState> initialMapForStartInstruction = Maps.newHashMap();
178        VariableInitState initializedForExternalVariable = VariableInitState.create(true);
179        VariableInitState notInitializedForDeclaredVariable = VariableInitState.create(false);
180
181        for (VariableDescriptor variable : usedVariables) {
182            if (declaredVariables.contains(variable)) {
183                initialMapForStartInstruction.put(variable, notInitializedForDeclaredVariable);
184            }
185            else {
186                initialMapForStartInstruction.put(variable, initializedForExternalVariable);
187            }
188        }
189        return initialMapForStartInstruction;
190    }
191
192    @NotNull
193    private static Map<VariableDescriptor, VariableInitState> mergeIncomingEdgesDataForInitializers(
194            @NotNull Collection<Map<VariableDescriptor, VariableInitState>> incomingEdgesData) {
195
196        Set<VariableDescriptor> variablesInScope = Sets.newHashSet();
197        for (Map<VariableDescriptor, VariableInitState> edgeData : incomingEdgesData) {
198            variablesInScope.addAll(edgeData.keySet());
199        }
200
201        Map<VariableDescriptor, VariableInitState> enterInstructionData = Maps.newHashMap();
202        for (VariableDescriptor variable : variablesInScope) {
203            boolean isInitialized = true;
204            boolean isDeclared = true;
205            for (Map<VariableDescriptor, VariableInitState> edgeData : incomingEdgesData) {
206                VariableInitState initState = edgeData.get(variable);
207                if (initState != null) {
208                    if (!initState.isInitialized) {
209                        isInitialized = false;
210                    }
211                    if (!initState.isDeclared) {
212                        isDeclared = false;
213                    }
214                }
215            }
216            enterInstructionData.put(variable, VariableInitState.create(isInitialized, isDeclared));
217        }
218        return enterInstructionData;
219    }
220
221    @NotNull
222    private Map<VariableDescriptor, VariableInitState> addVariableInitStateFromCurrentInstructionIfAny(
223            @NotNull Instruction instruction, @NotNull Map<VariableDescriptor, VariableInitState> enterInstructionData) {
224
225        if (!(instruction instanceof WriteValueInstruction) && !(instruction instanceof VariableDeclarationInstruction)) {
226            return enterInstructionData;
227        }
228        VariableDescriptor variable = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, false, bindingContext);
229        if (variable == null) {
230            return enterInstructionData;
231        }
232        Map<VariableDescriptor, VariableInitState> exitInstructionData = Maps.newHashMap(enterInstructionData);
233        if (instruction instanceof WriteValueInstruction) {
234            VariableInitState enterInitState = enterInstructionData.get(variable);
235            VariableInitState initializationAtThisElement =
236                    VariableInitState.create(((WriteValueInstruction) instruction).getElement() instanceof JetProperty, enterInitState);
237            exitInstructionData.put(variable, initializationAtThisElement);
238        }
239        else { // instruction instanceof VariableDeclarationInstruction
240            VariableInitState enterInitState = enterInstructionData.get(variable);
241            if (enterInitState == null || !enterInitState.isInitialized || !enterInitState.isDeclared) {
242                boolean isInitialized = enterInitState != null && enterInitState.isInitialized;
243                VariableInitState variableDeclarationInfo = VariableInitState.create(isInitialized, true);
244                exitInstructionData.put(variable, variableDeclarationInfo);
245            }
246        }
247        return exitInstructionData;
248    }
249
250// variable use
251
252    @NotNull
253    public Map<Instruction, Edges<Map<VariableDescriptor, VariableUseState>>> getVariableUseStatusData() {
254        Map<VariableDescriptor, VariableUseState> sinkInstructionData = Maps.newHashMap();
255        for (VariableDescriptor usedVariable : getUsedVariables(pseudocode)) {
256            sinkInstructionData.put(usedVariable, VariableUseState.UNUSED);
257        }
258        InstructionDataMergeStrategy<Map<VariableDescriptor, VariableUseState>> collectVariableUseStatusStrategy =
259                new InstructionDataMergeStrategy<Map<VariableDescriptor, VariableUseState>>() {
260                    @Override
261                    public Edges<Map<VariableDescriptor, VariableUseState>> execute(
262                            @NotNull Instruction instruction,
263                            @NotNull Collection<Map<VariableDescriptor, VariableUseState>> incomingEdgesData
264                    ) {
265
266                        Map<VariableDescriptor, VariableUseState> enterResult = Maps.newHashMap();
267                        for (Map<VariableDescriptor, VariableUseState> edgeData : incomingEdgesData) {
268                            for (Map.Entry<VariableDescriptor, VariableUseState> entry : edgeData.entrySet()) {
269                                VariableDescriptor variableDescriptor = entry.getKey();
270                                VariableUseState variableUseState = entry.getValue();
271                                enterResult.put(variableDescriptor, variableUseState.merge(enterResult.get(variableDescriptor)));
272                            }
273                        }
274                        VariableDescriptor variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true,
275                                                                                                              bindingContext);
276                        if (variableDescriptor == null ||
277                            (!(instruction instanceof ReadValueInstruction) && !(instruction instanceof WriteValueInstruction))) {
278                            return Edges.create(enterResult, enterResult);
279                        }
280                        Map<VariableDescriptor, VariableUseState> exitResult = Maps.newHashMap(enterResult);
281                        if (instruction instanceof ReadValueInstruction) {
282                            exitResult.put(variableDescriptor, VariableUseState.LAST_READ);
283                        }
284                        else { //instruction instanceof WriteValueInstruction
285                            VariableUseState variableUseState = enterResult.get(variableDescriptor);
286                            if (variableUseState == null) {
287                                variableUseState = VariableUseState.UNUSED;
288                            }
289                            switch (variableUseState) {
290                                case UNUSED:
291                                case ONLY_WRITTEN_NEVER_READ:
292                                    exitResult.put(variableDescriptor, VariableUseState.ONLY_WRITTEN_NEVER_READ);
293                                    break;
294                                case LAST_WRITTEN:
295                                case LAST_READ:
296                                    exitResult.put(variableDescriptor, VariableUseState.LAST_WRITTEN);
297                            }
298                        }
299                        return Edges.create(enterResult, exitResult);
300                    }
301                };
302        return PseudocodeTraverser.collectData(pseudocode, BACKWARD, ANALYSE_LOCAL_DECLARATIONS,
303                                               Collections.<VariableDescriptor, VariableUseState>emptyMap(),
304                                               sinkInstructionData, collectVariableUseStatusStrategy);
305    }
306
307    public static class VariableInitState {
308        public final boolean isInitialized;
309        public final boolean isDeclared;
310
311        private VariableInitState(boolean isInitialized, boolean isDeclared) {
312            this.isInitialized = isInitialized;
313            this.isDeclared = isDeclared;
314        }
315
316        private static final VariableInitState VS_TT = new VariableInitState(true, true);
317        private static final VariableInitState VS_TF = new VariableInitState(true, false);
318        private static final VariableInitState VS_FT = new VariableInitState(false, true);
319        private static final VariableInitState VS_FF = new VariableInitState(false, false);
320
321
322        private static VariableInitState create(boolean isInitialized, boolean isDeclared) {
323            if (isInitialized) {
324                if (isDeclared) return VS_TT;
325                return VS_TF;
326            }
327            if (isDeclared) return VS_FT;
328            return VS_FF;
329        }
330
331        private static VariableInitState create(boolean isInitialized) {
332            return create(isInitialized, false);
333        }
334
335        private static VariableInitState create(boolean isDeclaredHere, @Nullable VariableInitState mergedEdgesData) {
336            return create(true, isDeclaredHere || (mergedEdgesData != null && mergedEdgesData.isDeclared));
337        }
338    }
339
340    public static enum VariableUseState {
341        LAST_READ(3),
342        LAST_WRITTEN(2),
343        ONLY_WRITTEN_NEVER_READ(1),
344        UNUSED(0);
345
346        private final int importance;
347
348        VariableUseState(int importance) {
349            this.importance = importance;
350        }
351
352        private VariableUseState merge(@Nullable VariableUseState variableUseState) {
353            if (variableUseState == null || importance > variableUseState.importance) return this;
354            return variableUseState;
355        }
356
357        public static boolean isUsed(@Nullable VariableUseState variableUseState) {
358            return variableUseState != null && variableUseState != UNUSED;
359        }
360    }
361}