001 /* 002 * Copyright 2010-2013 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.jet.lang.cfg; 018 019 import com.google.common.collect.Lists; 020 import com.google.common.collect.Maps; 021 import com.google.common.collect.Sets; 022 import com.intellij.psi.PsiElement; 023 import com.intellij.psi.tree.IElementType; 024 import com.intellij.psi.util.PsiTreeUtil; 025 import kotlin.Function1; 026 import kotlin.Function3; 027 import kotlin.Unit; 028 import org.jetbrains.annotations.NotNull; 029 import org.jetbrains.annotations.Nullable; 030 import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.Edges; 031 import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.PseudocodeTraverserPackage; 032 import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableInitState; 033 import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseState; 034 import org.jetbrains.jet.lang.cfg.pseudocode.*; 035 import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.TraversalOrder; 036 import org.jetbrains.jet.lang.descriptors.*; 037 import org.jetbrains.jet.lang.diagnostics.Diagnostic; 038 import org.jetbrains.jet.lang.diagnostics.DiagnosticFactory; 039 import org.jetbrains.jet.lang.diagnostics.Errors; 040 import org.jetbrains.jet.lang.psi.*; 041 import org.jetbrains.jet.lang.resolve.BindingContext; 042 import org.jetbrains.jet.lang.resolve.BindingContextUtils; 043 import org.jetbrains.jet.lang.resolve.BindingTrace; 044 import org.jetbrains.jet.lang.resolve.DescriptorUtils; 045 import org.jetbrains.jet.lang.resolve.calls.TailRecursionKind; 046 import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall; 047 import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver; 048 import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue; 049 import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver; 050 import org.jetbrains.jet.lang.types.JetType; 051 import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; 052 import org.jetbrains.jet.lexer.JetTokens; 053 import org.jetbrains.jet.plugin.MainFunctionDetector; 054 055 import java.util.*; 056 057 import static org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseState.*; 058 import static org.jetbrains.jet.lang.cfg.pseudocodeTraverser.TraversalOrder.FORWARD; 059 import static org.jetbrains.jet.lang.diagnostics.Errors.*; 060 import static org.jetbrains.jet.lang.resolve.BindingContext.*; 061 import static org.jetbrains.jet.lang.resolve.calls.TailRecursionKind.*; 062 import static org.jetbrains.jet.lang.types.TypeUtils.NO_EXPECTED_TYPE; 063 import static org.jetbrains.jet.lang.types.TypeUtils.noExpectedType; 064 065 public class JetFlowInformationProvider { 066 067 private final JetElement subroutine; 068 private final Pseudocode pseudocode; 069 private final BindingTrace trace; 070 private PseudocodeVariablesData pseudocodeVariablesData; 071 072 private JetFlowInformationProvider( 073 @NotNull JetElement declaration, 074 @NotNull BindingTrace trace, 075 @NotNull Pseudocode pseudocode 076 ) { 077 this.subroutine = declaration; 078 this.trace = trace; 079 this.pseudocode = pseudocode; 080 } 081 082 public JetFlowInformationProvider( 083 @NotNull JetElement declaration, 084 @NotNull BindingTrace trace 085 ) { 086 this(declaration, trace, new JetControlFlowProcessor(trace).generatePseudocode(declaration)); 087 } 088 089 public PseudocodeVariablesData getPseudocodeVariablesData() { 090 if (pseudocodeVariablesData == null) { 091 pseudocodeVariablesData = new PseudocodeVariablesData(pseudocode, trace.getBindingContext()); 092 } 093 return pseudocodeVariablesData; 094 } 095 096 public void checkForLocalClassOrObjectMode() { 097 // Local classes and objects are analyzed twice: when TopDownAnalyzer processes it and as a part of its container. 098 // Almost all checks can be done when the container is analyzed 099 // except recording initialized variables (this information is needed for DeclarationChecker). 100 recordInitializedVariables(); 101 } 102 103 public void checkDeclaration() { 104 105 recordInitializedVariables(); 106 107 checkLocalFunctions(); 108 109 markUninitializedVariables(); 110 111 markUnusedVariables(); 112 113 markUnusedLiteralsInBlock(); 114 } 115 116 public void checkFunction(@Nullable JetType expectedReturnType) { 117 118 checkDefiniteReturn(expectedReturnType != null ? expectedReturnType : NO_EXPECTED_TYPE); 119 120 markTailCalls(); 121 } 122 123 private void collectReturnExpressions(@NotNull final Collection<JetElement> returnedExpressions) { 124 final Set<Instruction> instructions = Sets.newHashSet(pseudocode.getInstructions()); 125 SubroutineExitInstruction exitInstruction = pseudocode.getExitInstruction(); 126 for (Instruction previousInstruction : exitInstruction.getPreviousInstructions()) { 127 previousInstruction.accept(new InstructionVisitor() { 128 @Override 129 public void visitReturnValue(ReturnValueInstruction instruction) { 130 if (instructions.contains(instruction)) { //exclude non-local return expressions 131 returnedExpressions.add(instruction.getElement()); 132 } 133 } 134 135 @Override 136 public void visitReturnNoValue(ReturnNoValueInstruction instruction) { 137 if (instructions.contains(instruction)) { 138 returnedExpressions.add(instruction.getElement()); 139 } 140 } 141 142 143 @Override 144 public void visitJump(AbstractJumpInstruction instruction) { 145 // Nothing 146 } 147 148 @Override 149 public void visitUnconditionalJump(UnconditionalJumpInstruction instruction) { 150 redirectToPrevInstructions(instruction); 151 } 152 153 private void redirectToPrevInstructions(Instruction instruction) { 154 for (Instruction previousInstruction : instruction.getPreviousInstructions()) { 155 previousInstruction.accept(this); 156 } 157 } 158 159 @Override 160 public void visitNondeterministicJump(NondeterministicJumpInstruction instruction) { 161 redirectToPrevInstructions(instruction); 162 } 163 164 @Override 165 public void visitMarkInstruction(MarkInstruction instruction) { 166 redirectToPrevInstructions(instruction); 167 } 168 169 @Override 170 public void visitInstruction(Instruction instruction) { 171 if (instruction instanceof JetElementInstruction) { 172 JetElementInstruction elementInstruction = (JetElementInstruction) instruction; 173 returnedExpressions.add(elementInstruction.getElement()); 174 } 175 else { 176 throw new IllegalStateException(instruction + " precedes the exit point"); 177 } 178 } 179 }); 180 } 181 } 182 183 private void checkLocalFunctions() { 184 for (LocalFunctionDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) { 185 JetElement element = localDeclarationInstruction.getElement(); 186 if (element instanceof JetDeclarationWithBody) { 187 JetDeclarationWithBody localDeclaration = (JetDeclarationWithBody) element; 188 if (localDeclaration instanceof JetFunctionLiteral) continue; 189 CallableDescriptor functionDescriptor = 190 (CallableDescriptor) trace.getBindingContext().get(BindingContext.DECLARATION_TO_DESCRIPTOR, localDeclaration); 191 JetType expectedType = functionDescriptor != null ? functionDescriptor.getReturnType() : null; 192 193 JetFlowInformationProvider providerForLocalDeclaration = 194 new JetFlowInformationProvider(localDeclaration, trace, localDeclarationInstruction.getBody()); 195 196 providerForLocalDeclaration.checkFunction(expectedType); 197 } 198 } 199 } 200 201 public void checkDefiniteReturn(final @NotNull JetType expectedReturnType) { 202 assert subroutine instanceof JetDeclarationWithBody; 203 JetDeclarationWithBody function = (JetDeclarationWithBody) subroutine; 204 205 JetExpression bodyExpression = function.getBodyExpression(); 206 if (bodyExpression == null) return; 207 208 List<JetElement> returnedExpressions = Lists.newArrayList(); 209 collectReturnExpressions(returnedExpressions); 210 211 final boolean blockBody = function.hasBlockBody(); 212 213 final Set<JetElement> rootUnreachableElements = collectUnreachableCode(); 214 for (JetElement element : rootUnreachableElements) { 215 trace.report(UNREACHABLE_CODE.on(element)); 216 } 217 218 final boolean[] noReturnError = new boolean[] { false }; 219 for (JetElement returnedExpression : returnedExpressions) { 220 returnedExpression.accept(new JetVisitorVoid() { 221 @Override 222 public void visitReturnExpression(@NotNull JetReturnExpression expression) { 223 if (!blockBody) { 224 trace.report(RETURN_IN_FUNCTION_WITH_EXPRESSION_BODY.on(expression)); 225 } 226 } 227 228 @Override 229 public void visitExpression(@NotNull JetExpression expression) { 230 if (blockBody && !noExpectedType(expectedReturnType) 231 && !KotlinBuiltIns.getInstance().isUnit(expectedReturnType) 232 && !rootUnreachableElements.contains(expression)) { 233 noReturnError[0] = true; 234 } 235 } 236 }); 237 } 238 if (noReturnError[0]) { 239 trace.report(NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY.on(function)); 240 } 241 } 242 243 private Set<JetElement> collectUnreachableCode() { 244 Collection<JetElement> unreachableElements = Lists.newArrayList(); 245 for (Instruction deadInstruction : pseudocode.getDeadInstructions()) { 246 if (deadInstruction instanceof JetElementInstruction && 247 !(deadInstruction instanceof LoadUnitValueInstruction)) { 248 unreachableElements.add(((JetElementInstruction) deadInstruction).getElement()); 249 } 250 } 251 // This is needed in order to highlight only '1 < 2' and not '1', '<' and '2' as well 252 return JetPsiUtil.findRootExpressions(unreachableElements); 253 } 254 255 //////////////////////////////////////////////////////////////////////////////// 256 // Uninitialized variables analysis 257 258 public void markUninitializedVariables() { 259 final Collection<VariableDescriptor> varWithUninitializedErrorGenerated = Sets.newHashSet(); 260 final Collection<VariableDescriptor> varWithValReassignErrorGenerated = Sets.newHashSet(); 261 final boolean processClassOrObject = subroutine instanceof JetClassOrObject; 262 263 PseudocodeVariablesData pseudocodeVariablesData = getPseudocodeVariablesData(); 264 Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> initializers = 265 pseudocodeVariablesData.getVariableInitializers(); 266 final Set<VariableDescriptor> declaredVariables = pseudocodeVariablesData.getDeclaredVariables(pseudocode, true); 267 final LexicalScopeVariableInfo lexicalScopeVariableInfo = pseudocodeVariablesData.getLexicalScopeVariableInfo(); 268 269 final Map<Instruction, DiagnosticFactory> reportedDiagnosticMap = Maps.newHashMap(); 270 271 PseudocodeTraverserPackage.traverse( 272 pseudocode, FORWARD, initializers, 273 new InstructionDataAnalyzeStrategy<Map<VariableDescriptor, VariableInitState>>() { 274 @Override 275 public void execute( 276 @NotNull Instruction instruction, 277 @Nullable Map<VariableDescriptor, VariableInitState> in, 278 @Nullable Map<VariableDescriptor, VariableInitState> out 279 ) { 280 assert in != null && out != null; 281 VariableInitContext ctxt = 282 new VariableInitContext(instruction, reportedDiagnosticMap, in, out, lexicalScopeVariableInfo); 283 if (ctxt.variableDescriptor == null) return; 284 if (instruction instanceof ReadValueInstruction) { 285 JetElement element = ((ReadValueInstruction) instruction).getElement(); 286 boolean error = checkBackingField(ctxt, element); 287 if (!error && declaredVariables.contains(ctxt.variableDescriptor)) { 288 checkIsInitialized(ctxt, element, varWithUninitializedErrorGenerated); 289 } 290 return; 291 } 292 if (!(instruction instanceof WriteValueInstruction)) return; 293 JetElement element = ((WriteValueInstruction) instruction).getlValue(); 294 boolean error = checkBackingField(ctxt, element); 295 if (!(element instanceof JetExpression)) return; 296 if (!error) { 297 error = checkValReassignment(ctxt, (JetExpression) element, varWithValReassignErrorGenerated); 298 } 299 if (!error && processClassOrObject) { 300 error = checkAssignmentBeforeDeclaration(ctxt, (JetExpression) element); 301 } 302 if (!error && processClassOrObject) { 303 checkInitializationUsingBackingField(ctxt, (JetExpression) element); 304 } 305 } 306 } 307 ); 308 } 309 310 public void recordInitializedVariables() { 311 PseudocodeVariablesData pseudocodeVariablesData = getPseudocodeVariablesData(); 312 Pseudocode pseudocode = pseudocodeVariablesData.getPseudocode(); 313 Map<Instruction, Edges<Map<VariableDescriptor, VariableInitState>>> initializers = 314 pseudocodeVariablesData.getVariableInitializers(); 315 recordInitializedVariables(pseudocode, initializers); 316 for (LocalFunctionDeclarationInstruction instruction : pseudocode.getLocalDeclarations()) { 317 recordInitializedVariables(instruction.getBody(), initializers); 318 } 319 } 320 321 private void checkIsInitialized( 322 @NotNull VariableInitContext ctxt, 323 @NotNull JetElement element, 324 @NotNull Collection<VariableDescriptor> varWithUninitializedErrorGenerated 325 ) { 326 if (!(element instanceof JetSimpleNameExpression)) return; 327 328 boolean isInitialized = ctxt.exitInitState.isInitialized; 329 VariableDescriptor variableDescriptor = ctxt.variableDescriptor; 330 if (variableDescriptor instanceof PropertyDescriptor) { 331 if (!trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor)) { 332 isInitialized = true; 333 } 334 } 335 if (!isInitialized && !varWithUninitializedErrorGenerated.contains(variableDescriptor)) { 336 if (!(variableDescriptor instanceof PropertyDescriptor)) { 337 varWithUninitializedErrorGenerated.add(variableDescriptor); 338 } 339 if (variableDescriptor instanceof ValueParameterDescriptor) { 340 report(Errors.UNINITIALIZED_PARAMETER.on((JetSimpleNameExpression) element, 341 (ValueParameterDescriptor) variableDescriptor), ctxt); 342 } 343 else { 344 report(Errors.UNINITIALIZED_VARIABLE.on((JetSimpleNameExpression) element, variableDescriptor), ctxt); 345 } 346 } 347 } 348 349 private boolean checkValReassignment( 350 @NotNull VariableInitContext ctxt, 351 @NotNull JetExpression expression, 352 @NotNull Collection<VariableDescriptor> varWithValReassignErrorGenerated 353 ) { 354 VariableDescriptor variableDescriptor = ctxt.variableDescriptor; 355 if (JetPsiUtil.isBackingFieldReference(expression) && variableDescriptor instanceof PropertyDescriptor) { 356 PropertyDescriptor propertyDescriptor = (PropertyDescriptor) variableDescriptor; 357 JetPropertyAccessor accessor = PsiTreeUtil.getParentOfType(expression, JetPropertyAccessor.class); 358 if (accessor != null) { 359 DeclarationDescriptor accessorDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, accessor); 360 if (propertyDescriptor.getGetter() == accessorDescriptor) { 361 //val can be reassigned through backing field inside its own getter 362 return false; 363 } 364 } 365 } 366 367 boolean isInitializedNotHere = ctxt.enterInitState.isInitialized; 368 boolean hasBackingField = true; 369 if (variableDescriptor instanceof PropertyDescriptor) { 370 hasBackingField = trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor); 371 } 372 if (variableDescriptor.isVar() && variableDescriptor instanceof PropertyDescriptor) { 373 DeclarationDescriptor descriptor = BindingContextUtils.getEnclosingDescriptor(trace.getBindingContext(), expression); 374 PropertySetterDescriptor setterDescriptor = ((PropertyDescriptor) variableDescriptor).getSetter(); 375 if (Visibilities.isVisible(variableDescriptor, descriptor) && setterDescriptor != null 376 && !Visibilities.isVisible(setterDescriptor, descriptor)) { 377 report(Errors.INVISIBLE_SETTER.on(expression, variableDescriptor, setterDescriptor.getVisibility(), 378 variableDescriptor.getContainingDeclaration()), ctxt); 379 return true; 380 } 381 } 382 if ((isInitializedNotHere || !hasBackingField) && !variableDescriptor.isVar() 383 && !varWithValReassignErrorGenerated.contains(variableDescriptor)) { 384 boolean hasReassignMethodReturningUnit = false; 385 JetSimpleNameExpression operationReference = null; 386 PsiElement parent = expression.getParent(); 387 if (parent instanceof JetBinaryExpression) { 388 operationReference = ((JetBinaryExpression) parent).getOperationReference(); 389 } 390 else if (parent instanceof JetUnaryExpression) { 391 operationReference = ((JetUnaryExpression) parent).getOperationReference(); 392 } 393 if (operationReference != null) { 394 DeclarationDescriptor descriptor = trace.get(BindingContext.REFERENCE_TARGET, operationReference); 395 if (descriptor instanceof FunctionDescriptor) { 396 if (KotlinBuiltIns.getInstance().isUnit(((FunctionDescriptor) descriptor).getReturnType())) { 397 hasReassignMethodReturningUnit = true; 398 } 399 } 400 if (descriptor == null) { 401 Collection<? extends DeclarationDescriptor> descriptors = 402 trace.get(BindingContext.AMBIGUOUS_REFERENCE_TARGET, operationReference); 403 if (descriptors != null) { 404 for (DeclarationDescriptor referenceDescriptor : descriptors) { 405 if (KotlinBuiltIns.getInstance().isUnit(((FunctionDescriptor) referenceDescriptor).getReturnType())) { 406 hasReassignMethodReturningUnit = true; 407 } 408 } 409 } 410 } 411 } 412 if (!hasReassignMethodReturningUnit) { 413 varWithValReassignErrorGenerated.add(variableDescriptor); 414 report(Errors.VAL_REASSIGNMENT.on(expression, variableDescriptor), ctxt); 415 return true; 416 } 417 } 418 return false; 419 } 420 421 private boolean checkAssignmentBeforeDeclaration(@NotNull VariableInitContext ctxt, @NotNull JetExpression expression) { 422 if (!ctxt.enterInitState.isDeclared && !ctxt.exitInitState.isDeclared 423 && !ctxt.enterInitState.isInitialized && ctxt.exitInitState.isInitialized) { 424 report(Errors.INITIALIZATION_BEFORE_DECLARATION.on(expression, ctxt.variableDescriptor), ctxt); 425 return true; 426 } 427 return false; 428 } 429 430 private boolean checkInitializationUsingBackingField(@NotNull VariableInitContext ctxt, @NotNull JetExpression expression) { 431 VariableDescriptor variableDescriptor = ctxt.variableDescriptor; 432 if (variableDescriptor instanceof PropertyDescriptor && !ctxt.enterInitState.isInitialized && ctxt.exitInitState.isInitialized) { 433 if (!variableDescriptor.isVar()) return false; 434 if (!trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor)) return false; 435 PsiElement property = BindingContextUtils.descriptorToDeclaration(trace.getBindingContext(), variableDescriptor); 436 assert property instanceof JetProperty; 437 if (((PropertyDescriptor) variableDescriptor).getModality() == Modality.FINAL && ((JetProperty) property).getSetter() == null) { 438 return false; 439 } 440 JetExpression variable = expression; 441 if (expression instanceof JetDotQualifiedExpression) { 442 if (((JetDotQualifiedExpression) expression).getReceiverExpression() instanceof JetThisExpression) { 443 variable = ((JetDotQualifiedExpression) expression).getSelectorExpression(); 444 } 445 } 446 if (variable instanceof JetSimpleNameExpression) { 447 JetSimpleNameExpression simpleNameExpression = (JetSimpleNameExpression) variable; 448 if (simpleNameExpression.getReferencedNameElementType() != JetTokens.FIELD_IDENTIFIER) { 449 if (((PropertyDescriptor) variableDescriptor).getModality() != Modality.FINAL) { 450 report(Errors.INITIALIZATION_USING_BACKING_FIELD_OPEN_SETTER.on(expression, variableDescriptor), ctxt); 451 } 452 else { 453 report(Errors.INITIALIZATION_USING_BACKING_FIELD_CUSTOM_SETTER.on(expression, variableDescriptor), ctxt); 454 } 455 return true; 456 } 457 } 458 } 459 return false; 460 } 461 462 private boolean checkBackingField(@NotNull VariableContext cxtx, @NotNull JetElement element) { 463 VariableDescriptor variableDescriptor = cxtx.variableDescriptor; 464 boolean[] error = new boolean[1]; 465 if (!isCorrectBackingFieldReference(element, cxtx, error, true)) return false; 466 if (error[0]) return true; 467 if (!(variableDescriptor instanceof PropertyDescriptor)) { 468 report(Errors.NOT_PROPERTY_BACKING_FIELD.on(element), cxtx); 469 return true; 470 } 471 PsiElement property = BindingContextUtils.descriptorToDeclaration(trace.getBindingContext(), variableDescriptor); 472 boolean insideSelfAccessors = PsiTreeUtil.isAncestor(property, element, false); 473 if (!trace.get(BindingContext.BACKING_FIELD_REQUIRED, (PropertyDescriptor) variableDescriptor) && 474 // not to generate error in accessors of abstract properties, there is one: declared accessor of abstract property 475 !insideSelfAccessors) { 476 477 if (((PropertyDescriptor) variableDescriptor).getModality() == Modality.ABSTRACT) { 478 report(NO_BACKING_FIELD_ABSTRACT_PROPERTY.on(element), cxtx); 479 } 480 else { 481 report(NO_BACKING_FIELD_CUSTOM_ACCESSORS.on(element), cxtx); 482 } 483 return true; 484 } 485 if (insideSelfAccessors) return false; 486 487 DeclarationDescriptor declarationDescriptor = BindingContextUtils.getEnclosingDescriptor(trace.getBindingContext(), element); 488 489 DeclarationDescriptor containingDeclaration = variableDescriptor.getContainingDeclaration(); 490 if ((containingDeclaration instanceof ClassDescriptor) 491 && DescriptorUtils.isAncestor(containingDeclaration, declarationDescriptor, false)) { 492 return false; 493 } 494 report(Errors.INACCESSIBLE_BACKING_FIELD.on(element), cxtx); 495 return true; 496 } 497 498 private boolean isCorrectBackingFieldReference( 499 @Nullable JetElement element, 500 VariableContext ctxt, 501 boolean[] error, 502 boolean reportError 503 ) { 504 error[0] = false; 505 if (JetPsiUtil.isBackingFieldReference(element)) { 506 return true; 507 } 508 if (element instanceof JetDotQualifiedExpression && isCorrectBackingFieldReference( 509 ((JetDotQualifiedExpression) element).getSelectorExpression(), ctxt, error, false)) { 510 if (((JetDotQualifiedExpression) element).getReceiverExpression() instanceof JetThisExpression) { 511 return true; 512 } 513 error[0] = true; 514 if (reportError) { 515 report(Errors.INACCESSIBLE_BACKING_FIELD.on(element), ctxt); 516 } 517 } 518 return false; 519 } 520 521 private void recordInitializedVariables( 522 @NotNull Pseudocode pseudocode, 523 @NotNull Map<Instruction, Edges<Map<VariableDescriptor, PseudocodeVariablesData.VariableInitState>>> initializersMap 524 ) { 525 Edges<Map<VariableDescriptor, VariableInitState>> initializers = initializersMap.get(pseudocode.getExitInstruction()); 526 Set<VariableDescriptor> declaredVariables = getPseudocodeVariablesData().getDeclaredVariables(pseudocode, false); 527 for (VariableDescriptor variable : declaredVariables) { 528 if (variable instanceof PropertyDescriptor) { 529 PseudocodeVariablesData.VariableInitState variableInitState = initializers.getIncoming().get(variable); 530 if (variableInitState == null) return; 531 trace.record(BindingContext.IS_INITIALIZED, (PropertyDescriptor) variable, variableInitState.isInitialized); 532 } 533 } 534 } 535 536 //////////////////////////////////////////////////////////////////////////////// 537 // "Unused variable" & "unused value" analyses 538 539 public void markUnusedVariables() { 540 final PseudocodeVariablesData pseudocodeVariablesData = getPseudocodeVariablesData(); 541 Map<Instruction, Edges<Map<VariableDescriptor, VariableUseState>>> variableStatusData = 542 pseudocodeVariablesData.getVariableUseStatusData(); 543 final Map<Instruction, DiagnosticFactory> reportedDiagnosticMap = Maps.newHashMap(); 544 InstructionDataAnalyzeStrategy<Map<VariableDescriptor, VariableUseState>> variableStatusAnalyzeStrategy = 545 new InstructionDataAnalyzeStrategy<Map<VariableDescriptor, VariableUseState>>() { 546 @Override 547 public void execute( 548 @NotNull Instruction instruction, 549 @Nullable Map<VariableDescriptor, VariableUseState> in, 550 @Nullable Map<VariableDescriptor, VariableUseState> out 551 ) { 552 553 assert in != null && out != null; 554 VariableContext ctxt = new VariableUseContext(instruction, reportedDiagnosticMap, in, out); 555 Set<VariableDescriptor> declaredVariables = 556 pseudocodeVariablesData.getDeclaredVariables(instruction.getOwner(), false); 557 VariableDescriptor variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny( 558 instruction, false, trace.getBindingContext()); 559 if (variableDescriptor == null || !declaredVariables.contains(variableDescriptor) 560 || !DescriptorUtils.isLocal(variableDescriptor.getContainingDeclaration(), variableDescriptor)) { 561 return; 562 } 563 PseudocodeVariablesData.VariableUseState variableUseState = in.get(variableDescriptor); 564 if (instruction instanceof WriteValueInstruction) { 565 if (trace.get(CAPTURED_IN_CLOSURE, variableDescriptor) != null) return; 566 JetElement element = ((WriteValueInstruction) instruction).getElement(); 567 if (variableUseState != READ) { 568 if (element instanceof JetBinaryExpression && 569 ((JetBinaryExpression) element).getOperationToken() == JetTokens.EQ) { 570 JetExpression right = ((JetBinaryExpression) element).getRight(); 571 if (right != null) { 572 report(Errors.UNUSED_VALUE.on(right, right, variableDescriptor), ctxt); 573 } 574 } 575 else if (element instanceof JetPostfixExpression) { 576 IElementType operationToken = 577 ((JetPostfixExpression) element).getOperationReference().getReferencedNameElementType(); 578 if (operationToken == JetTokens.PLUSPLUS || operationToken == JetTokens.MINUSMINUS) { 579 report(Errors.UNUSED_CHANGED_VALUE.on(element, element), ctxt); 580 } 581 } 582 } 583 } 584 else if (instruction instanceof VariableDeclarationInstruction) { 585 JetDeclaration element = ((VariableDeclarationInstruction) instruction).getVariableDeclarationElement(); 586 if (!(element instanceof JetNamedDeclaration)) return; 587 PsiElement nameIdentifier = ((JetNamedDeclaration) element).getNameIdentifier(); 588 if (nameIdentifier == null) return; 589 if (!VariableUseState.isUsed(variableUseState)) { 590 if (JetPsiUtil.isVariableNotParameterDeclaration(element)) { 591 report(Errors.UNUSED_VARIABLE.on((JetNamedDeclaration) element, variableDescriptor), ctxt); 592 } 593 else if (element instanceof JetParameter) { 594 PsiElement psiElement = element.getParent().getParent(); 595 if (psiElement instanceof JetFunction) { 596 MainFunctionDetector mainFunctionDetector = new MainFunctionDetector(trace.getBindingContext()); 597 boolean isMain = (psiElement instanceof JetNamedFunction) && mainFunctionDetector.isMain((JetNamedFunction) psiElement); 598 if (psiElement instanceof JetFunctionLiteral) return; 599 DeclarationDescriptor descriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, psiElement); 600 assert descriptor instanceof FunctionDescriptor : psiElement.getText(); 601 FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; 602 if (!isMain && !functionDescriptor.getModality().isOverridable() 603 && functionDescriptor.getOverriddenDescriptors().isEmpty()) { 604 report(Errors.UNUSED_PARAMETER.on((JetParameter) element, variableDescriptor), ctxt); 605 } 606 } 607 } 608 } 609 else if (variableUseState == ONLY_WRITTEN_NEVER_READ && JetPsiUtil.isVariableNotParameterDeclaration(element)) { 610 report(Errors.ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE.on((JetNamedDeclaration) element, variableDescriptor), ctxt); 611 } 612 else if (variableUseState == WRITTEN_AFTER_READ && element instanceof JetVariableDeclaration) { 613 if (element instanceof JetProperty) { 614 JetExpression initializer = ((JetProperty) element).getInitializer(); 615 if (initializer != null) { 616 report(Errors.VARIABLE_WITH_REDUNDANT_INITIALIZER.on(initializer, variableDescriptor), ctxt); 617 } 618 } 619 else if (element instanceof JetMultiDeclarationEntry) { 620 report(VARIABLE_WITH_REDUNDANT_INITIALIZER.on(element, variableDescriptor), ctxt); 621 } 622 } 623 } 624 } 625 }; 626 PseudocodeTraverserPackage.traverse(pseudocode, TraversalOrder.BACKWARD, variableStatusData, variableStatusAnalyzeStrategy); 627 } 628 629 //////////////////////////////////////////////////////////////////////////////// 630 // "Unused literals" in block 631 632 public void markUnusedLiteralsInBlock() { 633 final Map<Instruction, DiagnosticFactory> reportedDiagnosticMap = Maps.newHashMap(); 634 PseudocodeTraverserPackage.traverse( 635 pseudocode, FORWARD, new FunctionVoid1<Instruction>() { 636 @Override 637 public void execute(@NotNull Instruction instruction) { 638 if (!(instruction instanceof ReadValueInstruction)) return; 639 VariableContext ctxt = new VariableContext(instruction, reportedDiagnosticMap); 640 JetElement element = 641 ((ReadValueInstruction) instruction).getElement(); 642 if (!(element instanceof JetFunctionLiteralExpression 643 || element instanceof JetConstantExpression 644 || element instanceof JetStringTemplateExpression 645 || element instanceof JetSimpleNameExpression)) { 646 return; 647 } 648 PsiElement parent = element.getParent(); 649 if (parent instanceof JetBlockExpression) { 650 if (!JetPsiUtil.isImplicitlyUsed(element)) { 651 if (element instanceof JetFunctionLiteralExpression) { 652 report(Errors.UNUSED_FUNCTION_LITERAL.on((JetFunctionLiteralExpression) element), ctxt); 653 } 654 else { 655 report(Errors.UNUSED_EXPRESSION.on(element), ctxt); 656 } 657 } 658 } 659 } 660 } 661 ); 662 } 663 664 //////////////////////////////////////////////////////////////////////////////// 665 // Tail calls 666 667 public void markTailCalls() { 668 final DeclarationDescriptor subroutineDescriptor = trace.get(BindingContext.DECLARATION_TO_DESCRIPTOR, subroutine); 669 if (!(subroutineDescriptor instanceof FunctionDescriptor)) return; 670 if (!KotlinBuiltIns.getInstance().isTailRecursive(subroutineDescriptor)) return; 671 672 // finally blocks are copied which leads to multiple diagnostics reported on one instruction 673 class KindAndCall { 674 TailRecursionKind kind; 675 ResolvedCall<?> call; 676 677 KindAndCall(TailRecursionKind kind, ResolvedCall<?> call) { 678 this.kind = kind; 679 this.call = call; 680 } 681 } 682 final Map<JetElement, KindAndCall> calls = new HashMap<JetElement, KindAndCall>(); 683 PseudocodeTraverserPackage.traverse( 684 pseudocode, 685 FORWARD, 686 new FunctionVoid1<Instruction>() { 687 public void execute(@NotNull Instruction instruction) { 688 if (!(instruction instanceof CallInstruction)) return; 689 CallInstruction callInstruction = (CallInstruction) instruction; 690 691 ResolvedCall<?> resolvedCall = trace.get(RESOLVED_CALL, callInstruction.getElement()); 692 if (resolvedCall == null) return; 693 694 // is this a recursive call? 695 CallableDescriptor functionDescriptor = resolvedCall.getResultingDescriptor(); 696 if (!functionDescriptor.getOriginal().equals(subroutineDescriptor)) return; 697 698 JetElement element = callInstruction.getElement(); 699 //noinspection unchecked 700 JetExpression parent = PsiTreeUtil.getParentOfType( 701 element, 702 JetTryExpression.class, JetFunction.class, JetClassInitializer.class 703 ); 704 705 if (parent instanceof JetTryExpression) { 706 // We do not support tail calls Collections.singletonMap() try-catch-finally, for simplicity of the mental model 707 // very few cases there would be real tail-calls, and it's often not so easy for the user to see why 708 calls.put(element, new KindAndCall(IN_TRY, resolvedCall)); 709 return; 710 } 711 712 boolean isTail = PseudocodeTraverserPackage.traverseFollowingInstructions( 713 callInstruction, 714 new HashSet<Instruction>(), 715 FORWARD, 716 new TailRecursionDetector(subroutine, callInstruction) 717 ); 718 719 boolean sameThisObject = sameThisObject(resolvedCall); 720 721 TailRecursionKind kind = isTail && sameThisObject ? TAIL_CALL : NON_TAIL; 722 723 KindAndCall kindAndCall = calls.get(element); 724 calls.put(element, 725 new KindAndCall( 726 combineKinds(kind, kindAndCall == null ? null : kindAndCall.kind), 727 resolvedCall 728 ) 729 ); 730 } 731 } 732 ); 733 boolean hasTailCalls = false; 734 for (Map.Entry<JetElement, KindAndCall> entry : calls.entrySet()) { 735 JetElement element = entry.getKey(); 736 KindAndCall kindAndCall = entry.getValue(); 737 switch (kindAndCall.kind) { 738 case TAIL_CALL: 739 trace.record(TAIL_RECURSION_CALL, kindAndCall.call, TailRecursionKind.TAIL_CALL); 740 hasTailCalls = true; 741 break; 742 case IN_TRY: 743 trace.report(Errors.TAIL_RECURSION_IN_TRY_IS_NOT_SUPPORTED.on(element)); 744 break; 745 case NON_TAIL: 746 trace.report(Errors.NON_TAIL_RECURSIVE_CALL.on(element)); 747 break; 748 } 749 } 750 751 if (!hasTailCalls) { 752 trace.report(Errors.NO_TAIL_CALLS_FOUND.on((JetNamedFunction) subroutine)); 753 } 754 } 755 756 private boolean sameThisObject(ResolvedCall<?> resolvedCall) { 757 // A tail call is not allowed to change dispatch receiver 758 // class C { 759 // fun foo(other: C) { 760 // other.foo(this) // not a tail call 761 // } 762 // } 763 ReceiverParameterDescriptor thisObject = resolvedCall.getResultingDescriptor().getExpectedThisObject(); 764 ReceiverValue thisObjectValue = resolvedCall.getThisObject(); 765 if (thisObject == null || !thisObjectValue.exists()) return true; 766 767 DeclarationDescriptor classDescriptor = null; 768 if (thisObjectValue instanceof ThisReceiver) { 769 // foo() -- implicit receiver 770 classDescriptor = ((ThisReceiver) thisObjectValue).getDeclarationDescriptor(); 771 } 772 else if (thisObjectValue instanceof ExpressionReceiver) { 773 JetExpression expression = JetPsiUtil.deparenthesize(((ExpressionReceiver) thisObjectValue).getExpression()); 774 if (expression instanceof JetThisExpression) { 775 // this.foo() -- explicit receiver 776 JetThisExpression thisExpression = (JetThisExpression) expression; 777 classDescriptor = trace.get(BindingContext.REFERENCE_TARGET, thisExpression.getInstanceReference()); 778 } 779 } 780 return thisObject.getContainingDeclaration() == classDescriptor; 781 } 782 783 private static TailRecursionKind combineKinds(TailRecursionKind kind, @Nullable TailRecursionKind existingKind) { 784 TailRecursionKind resultingKind; 785 if (existingKind == null || existingKind == kind) { 786 resultingKind = kind; 787 } 788 else { 789 if (check(kind, existingKind, IN_TRY, TAIL_CALL)) { 790 resultingKind = IN_TRY; 791 } 792 else if (check(kind, existingKind, IN_TRY, NON_TAIL)) { 793 resultingKind = IN_TRY; 794 } 795 else { 796 // TAIL_CALL, NON_TAIL 797 resultingKind = NON_TAIL; 798 } 799 } 800 return resultingKind; 801 } 802 803 private static boolean check(Object a, Object b, Object x, Object y) { 804 return (a == x && b == y) || (a == y && b == x); 805 } 806 807 //////////////////////////////////////////////////////////////////////////////// 808 // Utility classes and methods 809 810 /** 811 * The method provides reporting of the same diagnostic only once for copied instructions 812 * (depends on whether it should be reported for all or only for one of the copies) 813 */ 814 private void report( 815 @NotNull Diagnostic diagnostic, 816 @NotNull VariableContext ctxt 817 ) { 818 Instruction instruction = ctxt.instruction; 819 if (instruction.getCopies().isEmpty()) { 820 trace.report(diagnostic); 821 return; 822 } 823 Map<Instruction, DiagnosticFactory> previouslyReported = ctxt.reportedDiagnosticMap; 824 previouslyReported.put(instruction, diagnostic.getFactory()); 825 826 boolean alreadyReported = false; 827 boolean sameErrorForAllCopies = true; 828 for (Instruction copy : instruction.getCopies()) { 829 DiagnosticFactory previouslyReportedErrorFactory = previouslyReported.get(copy); 830 if (previouslyReportedErrorFactory != null) { 831 alreadyReported = true; 832 } 833 834 if (previouslyReportedErrorFactory != diagnostic.getFactory()) { 835 sameErrorForAllCopies = false; 836 } 837 } 838 839 if (mustBeReportedOnAllCopies(diagnostic.getFactory())) { 840 if (sameErrorForAllCopies) { 841 trace.report(diagnostic); 842 } 843 } 844 else { 845 //only one reporting required 846 if (!alreadyReported) { 847 trace.report(diagnostic); 848 } 849 } 850 } 851 852 private static boolean mustBeReportedOnAllCopies(@NotNull DiagnosticFactory diagnosticFactory) { 853 return diagnosticFactory == UNUSED_VARIABLE 854 || diagnosticFactory == UNUSED_PARAMETER 855 || diagnosticFactory == UNUSED_CHANGED_VALUE; 856 } 857 858 859 private class VariableContext { 860 final Map<Instruction, DiagnosticFactory> reportedDiagnosticMap; 861 final Instruction instruction; 862 final VariableDescriptor variableDescriptor; 863 864 private VariableContext( 865 @NotNull Instruction instruction, 866 @NotNull Map<Instruction, DiagnosticFactory> map 867 ) { 868 this.instruction = instruction; 869 reportedDiagnosticMap = map; 870 variableDescriptor = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, true, trace.getBindingContext()); 871 } 872 } 873 874 private class VariableInitContext extends VariableContext { 875 final VariableInitState enterInitState; 876 final VariableInitState exitInitState; 877 878 private VariableInitContext( 879 @NotNull Instruction instruction, 880 @NotNull Map<Instruction, DiagnosticFactory> map, 881 @NotNull Map<VariableDescriptor, VariableInitState> in, 882 @NotNull Map<VariableDescriptor, VariableInitState> out, 883 @NotNull LexicalScopeVariableInfo lexicalScopeVariableInfo 884 ) { 885 super(instruction, map); 886 enterInitState = initialize(variableDescriptor, lexicalScopeVariableInfo, in); 887 exitInitState = initialize(variableDescriptor, lexicalScopeVariableInfo, out); 888 } 889 890 private VariableInitState initialize( 891 VariableDescriptor variableDescriptor, 892 LexicalScopeVariableInfo lexicalScopeVariableInfo, 893 Map<VariableDescriptor, VariableInitState> map 894 ) { 895 if (variableDescriptor == null) return null; 896 VariableInitState state = map.get(variableDescriptor); 897 if (state != null) return state; 898 return PseudocodeVariablesData.getDefaultValueForInitializers(variableDescriptor, instruction, lexicalScopeVariableInfo); 899 } 900 } 901 902 private class VariableUseContext extends VariableContext { 903 final VariableUseState enterUseState; 904 final VariableUseState exitUseState; 905 906 907 private VariableUseContext( 908 @NotNull Instruction instruction, 909 @NotNull Map<Instruction, DiagnosticFactory> map, 910 @NotNull Map<VariableDescriptor, VariableUseState> in, 911 @NotNull Map<VariableDescriptor, VariableUseState> out 912 ) { 913 super(instruction, map); 914 enterUseState = variableDescriptor != null ? in.get(variableDescriptor) : null; 915 exitUseState = variableDescriptor != null ? out.get(variableDescriptor) : null; 916 } 917 } 918 919 //TODO after KT-4621 rewrite to Kotlin 920 public abstract static class InstructionDataAnalyzeStrategy<D> implements Function3<Instruction, D, D, Unit> { 921 @Override 922 public Unit invoke(Instruction instruction, D enterData, D exitData) { 923 execute(instruction, enterData, exitData); 924 return Unit.VALUE; 925 } 926 927 public abstract void execute(Instruction instruction, D enterData, D exitData); 928 } 929 930 public abstract static class FunctionVoid1<P> implements Function1<P, Unit> { 931 @Override 932 public Unit invoke(P p) { 933 execute(p); 934 return Unit.VALUE; 935 } 936 937 public abstract void execute(P p); 938 } 939 }