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 }