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 }