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