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