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