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 kotlin.Function1;
022    import kotlin.Unit;
023    import org.jetbrains.annotations.NotNull;
024    import org.jetbrains.annotations.Nullable;
025    import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.Edges;
026    import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.PseudocodeTraverserPackage;
027    import org.jetbrains.jet.lang.cfg.pseudocode.*;
028    import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
029    import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
030    import org.jetbrains.jet.lang.psi.JetDeclaration;
031    import org.jetbrains.jet.lang.psi.JetProperty;
032    import org.jetbrains.jet.lang.resolve.BindingContext;
033    
034    import java.util.Collection;
035    import java.util.Collections;
036    import java.util.Map;
037    import java.util.Set;
038    
039    import static org.jetbrains.jet.lang.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD;
040    import static org.jetbrains.jet.lang.cfg.pseudocodeTraverser.TraversalOrder.FORWARD;
041    
042    public class PseudocodeVariablesData {
043        private final Pseudocode pseudocode;
044        private final BindingContext bindingContext;
045        private final PseudocodeVariableDataCollector pseudocodeVariableDataCollector;
046    
047        private final Map<Pseudocode, Set<VariableDescriptor>> declaredVariablesForDeclaration = Maps.newHashMap();
048        private final Map<Pseudocode, Set<VariableDescriptor>> usedVariablesForDeclaration = Maps.newHashMap();
049    
050        private Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> variableInitializers;
051    
052        public PseudocodeVariablesData(@NotNull Pseudocode pseudocode, @NotNull BindingContext bindingContext) {
053            this.pseudocode = pseudocode;
054            this.bindingContext = bindingContext;
055            this.pseudocodeVariableDataCollector = new PseudocodeVariableDataCollector(bindingContext, pseudocode);
056        }
057    
058        @NotNull
059        public Pseudocode getPseudocode() {
060            return pseudocode;
061        }
062    
063        @NotNull
064        public LexicalScopeVariableInfo getLexicalScopeVariableInfo() {
065            return pseudocodeVariableDataCollector.getLexicalScopeVariableInfo();
066        }
067    
068        @NotNull
069        public Set<VariableDescriptor> getUsedVariables(@NotNull Pseudocode pseudocode) {
070            Set<VariableDescriptor> usedVariables = usedVariablesForDeclaration.get(pseudocode);
071            if (usedVariables == null) {
072                final Set<VariableDescriptor> result = Sets.newHashSet();
073                PseudocodeTraverserPackage.traverse(pseudocode, FORWARD, new Function1<Instruction, Unit>() {
074                    @Override
075                    public Unit invoke(@NotNull Instruction instruction) {
076                        VariableDescriptor variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny(
077                                instruction, false, bindingContext);
078                        if (variableDescriptor != null) {
079                            result.add(variableDescriptor);
080                        }
081                        return Unit.VALUE;
082                    }
083                });
084                usedVariables = Collections.unmodifiableSet(result);
085                usedVariablesForDeclaration.put(pseudocode, usedVariables);
086            }
087            return usedVariables;
088        }
089    
090        @NotNull
091        public Set<VariableDescriptor> getDeclaredVariables(@NotNull Pseudocode pseudocode, boolean includeInsideLocalDeclarations) {
092            if (!includeInsideLocalDeclarations) {
093                return getUpperLevelDeclaredVariables(pseudocode);
094            }
095            Set<VariableDescriptor> declaredVariables = Sets.newHashSet();
096            declaredVariables.addAll(getUpperLevelDeclaredVariables(pseudocode));
097    
098            for (LocalFunctionDeclarationInstruction localFunctionDeclarationInstruction : pseudocode.getLocalDeclarations()) {
099                Pseudocode localPseudocode = localFunctionDeclarationInstruction.getBody();
100                declaredVariables.addAll(getUpperLevelDeclaredVariables(localPseudocode));
101            }
102            return declaredVariables;
103        }
104    
105        @NotNull
106        private Set<VariableDescriptor> getUpperLevelDeclaredVariables(@NotNull Pseudocode pseudocode) {
107            Set<VariableDescriptor> declaredVariables = declaredVariablesForDeclaration.get(pseudocode);
108            if (declaredVariables == null) {
109                declaredVariables = computeDeclaredVariablesForPseudocode(pseudocode);
110                declaredVariablesForDeclaration.put(pseudocode, declaredVariables);
111            }
112            return declaredVariables;
113        }
114    
115        @NotNull
116        private Set<VariableDescriptor> computeDeclaredVariablesForPseudocode(Pseudocode pseudocode) {
117            Set<VariableDescriptor> declaredVariables = Sets.newHashSet();
118            for (Instruction instruction : pseudocode.getInstructions()) {
119                if (instruction instanceof VariableDeclarationInstruction) {
120                    JetDeclaration variableDeclarationElement = ((VariableDeclarationInstruction) instruction).getVariableDeclarationElement();
121                    DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, variableDeclarationElement);
122                    if (descriptor != null) {
123                        assert descriptor instanceof VariableDescriptor;
124                        declaredVariables.add((VariableDescriptor) descriptor);
125                    }
126                }
127            }
128            return Collections.unmodifiableSet(declaredVariables);
129        }
130    
131        // variable initializers
132    
133        @NotNull
134        public Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> getVariableInitializers() {
135            if (variableInitializers == null) {
136                variableInitializers = computeVariableInitializers();
137            }
138    
139            return variableInitializers;
140        }
141    
142        @NotNull
143        private Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> computeVariableInitializers() {
144    
145            final LexicalScopeVariableInfo lexicalScopeVariableInfo = pseudocodeVariableDataCollector.getLexicalScopeVariableInfo();
146    
147            return pseudocodeVariableDataCollector.collectData(
148                    FORWARD, /*mergeDataWithLocalDeclarations=*/ false,
149                    new InstructionDataMergeStrategy<VariableInitState>() {
150                        @NotNull
151                        @Override
152                        public Edges<Map<VariableDescriptor, VariableInitState>> invoke(
153                                @NotNull Instruction instruction,
154                                @NotNull Collection<? extends Map<VariableDescriptor, VariableInitState>> incomingEdgesData
155                        ) {
156    
157                            Map<VariableDescriptor, VariableInitState> enterInstructionData =
158                                    mergeIncomingEdgesDataForInitializers(incomingEdgesData);
159                            Map<VariableDescriptor, VariableInitState> exitInstructionData = addVariableInitStateFromCurrentInstructionIfAny(
160                                    instruction, enterInstructionData, lexicalScopeVariableInfo);
161                            return new Edges<Map<VariableDescriptor, VariableInitState>>(enterInstructionData, exitInstructionData);
162                        }
163                    }
164            );
165        }
166    
167        public static VariableInitState getDefaultValueForInitializers(
168                @NotNull VariableDescriptor variable,
169                @NotNull Instruction instruction,
170                @NotNull LexicalScopeVariableInfo lexicalScopeVariableInfo
171        ) {
172            //todo: think of replacing it with "MapWithDefaultValue"
173            LexicalScope declaredIn = lexicalScopeVariableInfo.getDeclaredIn().get(variable);
174            boolean declaredOutsideThisDeclaration =
175                    declaredIn == null //declared outside this pseudocode
176                    || declaredIn.getLexicalScopeForContainingDeclaration() != instruction.getLexicalScope().getLexicalScopeForContainingDeclaration();
177            return VariableInitState.create(/*isInitialized=*/declaredOutsideThisDeclaration);
178        }
179    
180        @NotNull
181        private static Map<VariableDescriptor, VariableInitState> mergeIncomingEdgesDataForInitializers(
182                @NotNull Collection<? extends Map<VariableDescriptor, VariableInitState>> incomingEdgesData
183        ) {
184            Set<VariableDescriptor> variablesInScope = Sets.newHashSet();
185            for (Map<VariableDescriptor, VariableInitState> edgeData : incomingEdgesData) {
186                variablesInScope.addAll(edgeData.keySet());
187            }
188    
189            Map<VariableDescriptor, VariableInitState> enterInstructionData = Maps.newHashMap();
190            for (VariableDescriptor variable : variablesInScope) {
191                boolean isInitialized = true;
192                boolean isDeclared = true;
193                for (Map<VariableDescriptor, VariableInitState> edgeData : incomingEdgesData) {
194                    VariableInitState initState = edgeData.get(variable);
195                    if (initState != null) {
196                        if (!initState.isInitialized) {
197                            isInitialized = false;
198                        }
199                        if (!initState.isDeclared) {
200                            isDeclared = false;
201                        }
202                    }
203                }
204                enterInstructionData.put(variable, VariableInitState.create(isInitialized, isDeclared));
205            }
206            return enterInstructionData;
207        }
208    
209        @NotNull
210        private Map<VariableDescriptor, VariableInitState> addVariableInitStateFromCurrentInstructionIfAny(
211                @NotNull Instruction instruction,
212                @NotNull Map<VariableDescriptor, VariableInitState> enterInstructionData,
213                @NotNull LexicalScopeVariableInfo lexicalScopeVariableInfo
214        ) {
215            if (!(instruction instanceof WriteValueInstruction) && !(instruction instanceof VariableDeclarationInstruction)) {
216                return enterInstructionData;
217            }
218            VariableDescriptor variable = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, false, bindingContext);
219            if (variable == null) {
220                return enterInstructionData;
221            }
222            Map<VariableDescriptor, VariableInitState> exitInstructionData = Maps.newHashMap(enterInstructionData);
223            if (instruction instanceof WriteValueInstruction) {
224                VariableInitState enterInitState = enterInstructionData.get(variable);
225                VariableInitState initializationAtThisElement =
226                        VariableInitState.create(((WriteValueInstruction) instruction).getElement() instanceof JetProperty, enterInitState);
227                exitInstructionData.put(variable, initializationAtThisElement);
228            }
229            else { // instruction instanceof VariableDeclarationInstruction
230                VariableInitState enterInitState = enterInstructionData.get(variable);
231                if (enterInitState == null) {
232                    enterInitState = getDefaultValueForInitializers(variable, instruction, lexicalScopeVariableInfo);
233                }
234                if (enterInitState == null || !enterInitState.isInitialized || !enterInitState.isDeclared) {
235                    boolean isInitialized = enterInitState != null && enterInitState.isInitialized;
236                    VariableInitState variableDeclarationInfo = VariableInitState.create(isInitialized, true);
237                    exitInstructionData.put(variable, variableDeclarationInfo);
238                }
239            }
240            return exitInstructionData;
241        }
242    
243    // variable use
244    
245        @NotNull
246        public Map<Instruction, Edges<Map<VariableDescriptor, VariableUseState>>> getVariableUseStatusData() {
247            return pseudocodeVariableDataCollector.collectData(
248                    BACKWARD, /*mergeDataWithLocalDeclarations=*/ true,
249                    new InstructionDataMergeStrategy<VariableUseState>() {
250                        @NotNull
251                        @Override
252                        public Edges<Map<VariableDescriptor, VariableUseState>> invoke(
253                                @NotNull Instruction instruction,
254                                @NotNull Collection<? extends Map<VariableDescriptor, VariableUseState>> incomingEdgesData
255                        ) {
256    
257                            Map<VariableDescriptor, VariableUseState> enterResult = Maps.newHashMap();
258                            for (Map<VariableDescriptor, VariableUseState> edgeData : incomingEdgesData) {
259                                for (Map.Entry<VariableDescriptor, VariableUseState> entry : edgeData.entrySet()) {
260                                    VariableDescriptor variableDescriptor = entry.getKey();
261                                    VariableUseState variableUseState = entry.getValue();
262                                    enterResult.put(variableDescriptor, variableUseState.merge(enterResult.get(variableDescriptor)));
263                                }
264                            }
265                            VariableDescriptor variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny(
266                                    instruction, true, bindingContext);
267                            if (variableDescriptor == null ||
268                                (!(instruction instanceof ReadValueInstruction) && !(instruction instanceof WriteValueInstruction))) {
269                                return new Edges<Map<VariableDescriptor, VariableUseState>>(enterResult, enterResult);
270                            }
271                            Map<VariableDescriptor, VariableUseState> exitResult = Maps.newHashMap(enterResult);
272                            if (instruction instanceof ReadValueInstruction) {
273                                exitResult.put(variableDescriptor, VariableUseState.READ);
274                            }
275                            else { //instruction instanceof WriteValueInstruction
276                                VariableUseState variableUseState = enterResult.get(variableDescriptor);
277                                if (variableUseState == null) {
278                                    variableUseState = VariableUseState.UNUSED;
279                                }
280                                switch (variableUseState) {
281                                    case UNUSED:
282                                    case ONLY_WRITTEN_NEVER_READ:
283                                        exitResult.put(variableDescriptor, VariableUseState.ONLY_WRITTEN_NEVER_READ);
284                                        break;
285                                    case WRITTEN_AFTER_READ:
286                                    case READ:
287                                        exitResult.put(variableDescriptor, VariableUseState.WRITTEN_AFTER_READ);
288                                }
289                            }
290                            return new Edges<Map<VariableDescriptor, VariableUseState>>(enterResult, exitResult);
291                        }
292                    }
293            );
294        }
295    
296        public static class VariableInitState {
297            public final boolean isInitialized;
298            public final boolean isDeclared;
299    
300            private VariableInitState(boolean isInitialized, boolean isDeclared) {
301                this.isInitialized = isInitialized;
302                this.isDeclared = isDeclared;
303            }
304    
305            private static final VariableInitState VS_TT = new VariableInitState(true, true);
306            private static final VariableInitState VS_TF = new VariableInitState(true, false);
307            private static final VariableInitState VS_FT = new VariableInitState(false, true);
308            private static final VariableInitState VS_FF = new VariableInitState(false, false);
309    
310    
311            private static VariableInitState create(boolean isInitialized, boolean isDeclared) {
312                if (isInitialized) {
313                    if (isDeclared) return VS_TT;
314                    return VS_TF;
315                }
316                if (isDeclared) return VS_FT;
317                return VS_FF;
318            }
319    
320            private static VariableInitState create(boolean isInitialized) {
321                return create(isInitialized, false);
322            }
323    
324            private static VariableInitState create(boolean isDeclaredHere, @Nullable VariableInitState mergedEdgesData) {
325                return create(true, isDeclaredHere || (mergedEdgesData != null && mergedEdgesData.isDeclared));
326            }
327    
328            @Override
329            public String toString() {
330                if (!isInitialized && !isDeclared) return "-";
331                return (isInitialized ? "I" : "") + (isDeclared ? "D" : "");
332            }
333        }
334    
335        public static enum VariableUseState {
336            READ(3),
337            WRITTEN_AFTER_READ(2),
338            ONLY_WRITTEN_NEVER_READ(1),
339            UNUSED(0);
340    
341            private final int priority;
342    
343            VariableUseState(int priority) {
344                this.priority = priority;
345            }
346    
347            private VariableUseState merge(@Nullable VariableUseState variableUseState) {
348                if (variableUseState == null || priority > variableUseState.priority) return this;
349                return variableUseState;
350            }
351    
352            public static boolean isUsed(@Nullable VariableUseState variableUseState) {
353                return variableUseState != null && variableUseState != UNUSED;
354            }
355        }
356    }