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    }