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 }