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