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, VariableControlFlowState>>> 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, VariableControlFlowState>>> getVariableInitializers() {
116            if (variableInitializers == null) {
117                variableInitializers = computeVariableInitializers();
118            }
119    
120            return variableInitializers;
121        }
122    
123        @NotNull
124        private Map<Instruction, Edges<Map<VariableDescriptor, VariableControlFlowState>>> computeVariableInitializers() {
125    
126            final LexicalScopeVariableInfo lexicalScopeVariableInfo = pseudocodeVariableDataCollector.getLexicalScopeVariableInfo();
127    
128            return pseudocodeVariableDataCollector.collectData(
129                    FORWARD, /*mergeDataWithLocalDeclarations=*/ false,
130                    new InstructionDataMergeStrategy<VariableControlFlowState>() {
131                        @NotNull
132                        @Override
133                        public Edges<Map<VariableDescriptor, VariableControlFlowState>> invoke(
134                                @NotNull Instruction instruction,
135                                @NotNull Collection<? extends Map<VariableDescriptor, VariableControlFlowState>> incomingEdgesData
136                        ) {
137    
138                            Map<VariableDescriptor, VariableControlFlowState> enterInstructionData =
139                                    mergeIncomingEdgesDataForInitializers(incomingEdgesData);
140                            Map<VariableDescriptor, VariableControlFlowState> exitInstructionData = addVariableInitStateFromCurrentInstructionIfAny(
141                                    instruction, enterInstructionData, lexicalScopeVariableInfo);
142                            return new Edges<Map<VariableDescriptor, VariableControlFlowState>>(enterInstructionData, exitInstructionData);
143                        }
144                    }
145            );
146        }
147    
148        public static VariableControlFlowState 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 VariableControlFlowState.create(/*initState=*/declaredOutsideThisDeclaration);
159        }
160    
161        @NotNull
162        private static Map<VariableDescriptor, VariableControlFlowState> mergeIncomingEdgesDataForInitializers(
163                @NotNull Collection<? extends Map<VariableDescriptor, VariableControlFlowState>> incomingEdgesData
164        ) {
165            Set<VariableDescriptor> variablesInScope = Sets.newHashSet();
166            for (Map<VariableDescriptor, VariableControlFlowState> edgeData : incomingEdgesData) {
167                variablesInScope.addAll(edgeData.keySet());
168            }
169    
170            Map<VariableDescriptor, VariableControlFlowState> enterInstructionData = Maps.newHashMap();
171            for (VariableDescriptor variable : variablesInScope) {
172                TriInitState initState = null;
173                boolean isDeclared = true;
174                for (Map<VariableDescriptor, VariableControlFlowState> edgeData : incomingEdgesData) {
175                    VariableControlFlowState varControlFlowState = edgeData.get(variable);
176                    if (varControlFlowState != null) {
177                        initState = initState != null ? initState.merge(varControlFlowState.initState) : varControlFlowState.initState;
178                        if (!varControlFlowState.isDeclared) {
179                            isDeclared = false;
180                        }
181                    }
182                }
183                if (initState == null) {
184                    throw new AssertionError("An empty set of incoming edges data");
185                }
186                enterInstructionData.put(variable, VariableControlFlowState.create(initState, isDeclared));
187            }
188            return enterInstructionData;
189        }
190    
191        @NotNull
192        private Map<VariableDescriptor, VariableControlFlowState> addVariableInitStateFromCurrentInstructionIfAny(
193                @NotNull Instruction instruction,
194                @NotNull Map<VariableDescriptor, VariableControlFlowState> enterInstructionData,
195                @NotNull LexicalScopeVariableInfo lexicalScopeVariableInfo
196        ) {
197            if (!(instruction instanceof WriteValueInstruction) && !(instruction instanceof VariableDeclarationInstruction)) {
198                return enterInstructionData;
199            }
200            VariableDescriptor variable = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, false, bindingContext);
201            if (variable == null) {
202                return enterInstructionData;
203            }
204            Map<VariableDescriptor, VariableControlFlowState> exitInstructionData = Maps.newHashMap(enterInstructionData);
205            if (instruction instanceof WriteValueInstruction) {
206                // if writing to already initialized object
207                if (!PseudocodeUtil.isThisOrNoDispatchReceiver((WriteValueInstruction) instruction, bindingContext)) {
208                    return enterInstructionData;
209                }
210    
211                VariableControlFlowState enterInitState = enterInstructionData.get(variable);
212                VariableControlFlowState initializationAtThisElement =
213                        VariableControlFlowState
214                                .create(((WriteValueInstruction) instruction).getElement() instanceof JetProperty, enterInitState);
215                exitInstructionData.put(variable, initializationAtThisElement);
216            }
217            else { // instruction instanceof VariableDeclarationInstruction
218                VariableControlFlowState enterInitState = enterInstructionData.get(variable);
219                if (enterInitState == null) {
220                    enterInitState = getDefaultValueForInitializers(variable, instruction, lexicalScopeVariableInfo);
221                }
222                if (enterInitState == null || !enterInitState.mayBeInitialized() || !enterInitState.isDeclared) {
223                    boolean isInitialized = enterInitState != null && enterInitState.mayBeInitialized();
224                    VariableControlFlowState variableDeclarationInfo = VariableControlFlowState.create(isInitialized, true);
225                    exitInstructionData.put(variable, variableDeclarationInfo);
226                }
227            }
228            return exitInstructionData;
229        }
230    
231    // variable use
232    
233        @NotNull
234        public Map<Instruction, Edges<Map<VariableDescriptor, VariableUseState>>> getVariableUseStatusData() {
235            return pseudocodeVariableDataCollector.collectData(
236                    BACKWARD, /*mergeDataWithLocalDeclarations=*/ true,
237                    new InstructionDataMergeStrategy<VariableUseState>() {
238                        @NotNull
239                        @Override
240                        public Edges<Map<VariableDescriptor, VariableUseState>> invoke(
241                                @NotNull Instruction instruction,
242                                @NotNull Collection<? extends Map<VariableDescriptor, VariableUseState>> incomingEdgesData
243                        ) {
244    
245                            Map<VariableDescriptor, VariableUseState> enterResult = Maps.newHashMap();
246                            for (Map<VariableDescriptor, VariableUseState> edgeData : incomingEdgesData) {
247                                for (Map.Entry<VariableDescriptor, VariableUseState> entry : edgeData.entrySet()) {
248                                    VariableDescriptor variableDescriptor = entry.getKey();
249                                    VariableUseState variableUseState = entry.getValue();
250                                    enterResult.put(variableDescriptor, variableUseState.merge(enterResult.get(variableDescriptor)));
251                                }
252                            }
253                            VariableDescriptor variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny(
254                                    instruction, true, bindingContext);
255                            if (variableDescriptor == null ||
256                                (!(instruction instanceof ReadValueInstruction) && !(instruction instanceof WriteValueInstruction))) {
257                                return new Edges<Map<VariableDescriptor, VariableUseState>>(enterResult, enterResult);
258                            }
259                            Map<VariableDescriptor, VariableUseState> exitResult = Maps.newHashMap(enterResult);
260                            if (instruction instanceof ReadValueInstruction) {
261                                exitResult.put(variableDescriptor, VariableUseState.READ);
262                            }
263                            else { //instruction instanceof WriteValueInstruction
264                                VariableUseState variableUseState = enterResult.get(variableDescriptor);
265                                if (variableUseState == null) {
266                                    variableUseState = VariableUseState.UNUSED;
267                                }
268                                switch (variableUseState) {
269                                    case UNUSED:
270                                    case ONLY_WRITTEN_NEVER_READ:
271                                        exitResult.put(variableDescriptor, VariableUseState.ONLY_WRITTEN_NEVER_READ);
272                                        break;
273                                    case WRITTEN_AFTER_READ:
274                                    case READ:
275                                        exitResult.put(variableDescriptor, VariableUseState.WRITTEN_AFTER_READ);
276                                }
277                            }
278                            return new Edges<Map<VariableDescriptor, VariableUseState>>(enterResult, exitResult);
279                        }
280                    }
281            );
282        }
283    
284        private enum TriInitState {
285            INITIALIZED("I"), UNKNOWN("I?"), NOT_INITIALIZED("");
286    
287            private final String s;
288    
289            TriInitState(String s) {
290                this.s = s;
291            }
292    
293            private TriInitState merge(@NotNull TriInitState other) {
294                if (this == other) return this;
295                return UNKNOWN;
296            }
297    
298            @Override
299            public String toString() {
300                return s;
301            }
302        }
303    
304        public static class VariableControlFlowState {
305    
306            public final TriInitState initState;
307            public final boolean isDeclared;
308    
309            private VariableControlFlowState(TriInitState initState, boolean isDeclared) {
310                this.initState = initState;
311                this.isDeclared = isDeclared;
312            }
313    
314            private static final VariableControlFlowState VS_IT = new VariableControlFlowState(TriInitState.INITIALIZED, true);
315            private static final VariableControlFlowState VS_IF = new VariableControlFlowState(TriInitState.INITIALIZED, false);
316            private static final VariableControlFlowState VS_UT = new VariableControlFlowState(TriInitState.UNKNOWN, true);
317            private static final VariableControlFlowState VS_UF = new VariableControlFlowState(TriInitState.UNKNOWN, false);
318            private static final VariableControlFlowState VS_NT = new VariableControlFlowState(TriInitState.NOT_INITIALIZED, true);
319            private static final VariableControlFlowState VS_NF = new VariableControlFlowState(TriInitState.NOT_INITIALIZED, false);
320    
321    
322            private static VariableControlFlowState create(TriInitState initState, boolean isDeclared) {
323                switch (initState) {
324                    case INITIALIZED: return isDeclared ? VS_IT : VS_IF;
325                    case UNKNOWN: return isDeclared ? VS_UT : VS_UF;
326                    default: return isDeclared ? VS_NT : VS_NF;
327                }
328            }
329    
330            private static VariableControlFlowState create(boolean isInitialized, boolean isDeclared) {
331                return create(isInitialized ? TriInitState.INITIALIZED : TriInitState.NOT_INITIALIZED, isDeclared);
332            }
333    
334            private static VariableControlFlowState create(boolean isInitialized) {
335                return create(isInitialized, false);
336            }
337    
338            private static VariableControlFlowState create(boolean isDeclaredHere, @Nullable VariableControlFlowState mergedEdgesData) {
339                return create(true, isDeclaredHere || (mergedEdgesData != null && mergedEdgesData.isDeclared));
340            }
341    
342            public boolean definitelyInitialized() {
343                return initState == TriInitState.INITIALIZED;
344            }
345    
346            public boolean mayBeInitialized() {
347                return initState != TriInitState.NOT_INITIALIZED;
348            }
349    
350            @Override
351            public String toString() {
352                if (initState == TriInitState.NOT_INITIALIZED && !isDeclared) return "-";
353                return initState + (isDeclared ? "D" : "");
354            }
355        }
356    
357        public enum VariableUseState {
358            READ(3),
359            WRITTEN_AFTER_READ(2),
360            ONLY_WRITTEN_NEVER_READ(1),
361            UNUSED(0);
362    
363            private final int priority;
364    
365            VariableUseState(int priority) {
366                this.priority = priority;
367            }
368    
369            private VariableUseState merge(@Nullable VariableUseState variableUseState) {
370                if (variableUseState == null || priority > variableUseState.priority) return this;
371                return variableUseState;
372            }
373    
374            public static boolean isUsed(@Nullable VariableUseState variableUseState) {
375                return variableUseState != null && variableUseState != UNUSED;
376            }
377        }
378    }