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    
017    package org.jetbrains.jet.lang.cfg;
018    
019    import com.google.common.collect.Maps;
020    import com.google.common.collect.Sets;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    import org.jetbrains.jet.lang.cfg.PseudocodeTraverser.*;
024    import org.jetbrains.jet.lang.cfg.pseudocode.*;
025    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
026    import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
027    import org.jetbrains.jet.lang.psi.JetDeclaration;
028    import org.jetbrains.jet.lang.psi.JetProperty;
029    import org.jetbrains.jet.lang.resolve.BindingContext;
030    
031    import java.util.Collection;
032    import java.util.Collections;
033    import java.util.Map;
034    import java.util.Set;
035    
036    import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.LookInsideStrategy.ANALYSE_LOCAL_DECLARATIONS;
037    import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.LookInsideStrategy.SKIP_LOCAL_DECLARATIONS;
038    import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.TraversalOrder.BACKWARD;
039    import static org.jetbrains.jet.lang.cfg.PseudocodeTraverser.TraversalOrder.FORWARD;
040    
041    public 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    }