All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.jet.lang.cfg.JetControlFlowProcessor Maven / Gradle / Ivy

/*
 * Copyright 2010-2013 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.jet.lang.cfg;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.cfg.pseudocode.JetControlFlowInstructionsGenerator;
import org.jetbrains.jet.lang.cfg.pseudocode.Pseudocode;
import org.jetbrains.jet.lang.cfg.pseudocode.PseudocodeImpl;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.CompileTimeConstantUtils;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall;
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedValueArgument;
import org.jetbrains.jet.lang.resolve.calls.model.VariableAsFunctionResolvedCall;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import org.jetbrains.jet.lang.resolve.name.Name;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.jet.lang.resolve.scopes.receivers.ThisReceiver;
import org.jetbrains.jet.lang.resolve.scopes.receivers.TransientReceiver;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.expressions.OperatorConventions;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.lexer.JetToken;
import org.jetbrains.jet.lexer.JetTokens;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import static org.jetbrains.jet.lang.cfg.JetControlFlowBuilder.PredefinedOperation.*;
import static org.jetbrains.jet.lang.cfg.JetControlFlowProcessor.CFPContext.IN_CONDITION;
import static org.jetbrains.jet.lang.cfg.JetControlFlowProcessor.CFPContext.NOT_IN_CONDITION;
import static org.jetbrains.jet.lang.diagnostics.Errors.*;
import static org.jetbrains.jet.lexer.JetTokens.*;

public class JetControlFlowProcessor {

    private final JetControlFlowBuilder builder;
    private final BindingTrace trace;

    public JetControlFlowProcessor(BindingTrace trace) {
        this.builder = new JetControlFlowInstructionsGenerator();
        this.trace = trace;
    }

    @NotNull
    public Pseudocode generatePseudocode(@NotNull JetElement subroutine) {
        Pseudocode pseudocode = generate(subroutine);
        ((PseudocodeImpl) pseudocode).postProcess();
        return pseudocode;
    }

    @NotNull
    private Pseudocode generate(@NotNull JetElement subroutine) {
        builder.enterSubroutine(subroutine);
        CFPVisitor cfpVisitor = new CFPVisitor(builder);
        if (subroutine instanceof JetDeclarationWithBody) {
            JetDeclarationWithBody declarationWithBody = (JetDeclarationWithBody) subroutine;
            List valueParameters = declarationWithBody.getValueParameters();
            for (JetParameter valueParameter : valueParameters) {
                cfpVisitor.generateInstructions(valueParameter, NOT_IN_CONDITION);
            }
            JetExpression bodyExpression = declarationWithBody.getBodyExpression();
            if (bodyExpression != null) {
                cfpVisitor.generateInstructions(bodyExpression, NOT_IN_CONDITION);
            }
        } else {
            cfpVisitor.generateInstructions(subroutine, NOT_IN_CONDITION);
        }
        return builder.exitSubroutine(subroutine);
    }

    private void processLocalDeclaration(@NotNull JetDeclaration subroutine) {
        JetElement parent = PsiTreeUtil.getParentOfType(subroutine, JetElement.class);
        assert parent != null;

        Label afterDeclaration = builder.createUnboundLabel();

        builder.nondeterministicJump(afterDeclaration, parent);
        generate(subroutine);
        builder.bindLabel(afterDeclaration);
    }

    /*package*/ enum CFPContext {
        IN_CONDITION(true),
        NOT_IN_CONDITION(false);

        private final boolean inCondition;

        private CFPContext(boolean inCondition) {
            this.inCondition = inCondition;
        }

        public boolean inCondition() {
            return inCondition;
        }
    }
    
    private class CFPVisitor extends JetVisitorVoidWithParameter {
        private final JetControlFlowBuilder builder;

        private final JetVisitorVoidWithParameter conditionVisitor = new JetVisitorVoidWithParameter() {

            @Override
            public void visitWhenConditionInRangeVoid(@NotNull JetWhenConditionInRange condition, CFPContext context) {
                generateInstructions(condition.getRangeExpression(), context);
                generateInstructions(condition.getOperationReference(), context);
                // TODO : read the call to contains()...
            }

            @Override
            public void visitWhenConditionIsPatternVoid(@NotNull JetWhenConditionIsPattern condition, CFPContext context) {
                // TODO: types in CF?
            }

            @Override
            public void visitWhenConditionWithExpressionVoid(@NotNull JetWhenConditionWithExpression condition, CFPContext context) {
                generateInstructions(condition.getExpression(), context);
            }

            @Override
            public void visitJetElementVoid(@NotNull JetElement element, CFPContext context) {
                throw new UnsupportedOperationException("[JetControlFlowProcessor] " + element.toString());
            }
        };

        private CFPVisitor(@NotNull JetControlFlowBuilder builder) {
            this.builder = builder;
        }

        private void mark(JetElement element) {
            builder.mark(element);
        }

        public void generateInstructions(@Nullable JetElement element, CFPContext context) {
            if (element == null) return;
            element.accept(this, context);
            checkNothingType(element);
        }

        private void checkNothingType(JetElement element) {
            if (!(element instanceof JetExpression)) return;

            JetExpression expression = JetPsiUtil.deparenthesize((JetExpression) element);
            if (expression == null) return;

            if (expression instanceof JetStatementExpression || expression instanceof JetTryExpression
                    || expression instanceof JetIfExpression || expression instanceof JetWhenExpression) {
                return;
            }

            JetType type = trace.getBindingContext().get(BindingContext.EXPRESSION_TYPE, expression);
            if (type != null && KotlinBuiltIns.getInstance().isNothing(type)) {
                builder.jumpToError(expression);
            }
        }

        @Override
        public void visitParenthesizedExpressionVoid(@NotNull JetParenthesizedExpression expression, CFPContext context) {
            mark(expression);
            JetExpression innerExpression = expression.getExpression();
            if (innerExpression != null) {
                generateInstructions(innerExpression, context);
            }
        }

        @Override
        public void visitAnnotatedExpressionVoid(@NotNull JetAnnotatedExpression expression, CFPContext context) {
            JetExpression baseExpression = expression.getBaseExpression();
            if (baseExpression != null) {
                generateInstructions(baseExpression, context);
            }
        }

        @Override
        public void visitThisExpressionVoid(@NotNull JetThisExpression expression, CFPContext context) {
            ResolvedCall resolvedCall = getResolvedCall(expression);
            if (resolvedCall == null) {
                builder.readThis(expression, null);
                return;
            }

            CallableDescriptor resultingDescriptor = resolvedCall.getResultingDescriptor();
            if (resultingDescriptor instanceof ReceiverParameterDescriptor) {
                builder.readThis(expression, (ReceiverParameterDescriptor) resultingDescriptor);
            }
        }

        @Override
        public void visitConstantExpressionVoid(@NotNull JetConstantExpression expression, CFPContext context) {
            CompileTimeConstant constant = trace.get(BindingContext.COMPILE_TIME_VALUE, expression);
            builder.loadConstant(expression, constant);
        }

        @Override
        public void visitSimpleNameExpressionVoid(@NotNull JetSimpleNameExpression expression, CFPContext context) {
            ResolvedCall resolvedCall = getResolvedCall(expression);
            if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
                VariableAsFunctionResolvedCall variableAsFunctionResolvedCall = (VariableAsFunctionResolvedCall) resolvedCall;
                generateCall(expression, variableAsFunctionResolvedCall.getVariableCall());
            }
            else {
                generateCall(expression);
            }
        }

        @Override
        public void visitLabeledExpressionVoid(@NotNull JetLabeledExpression expression, CFPContext context) {
            mark(expression);
            JetExpression baseExpression = expression.getBaseExpression();
            if (baseExpression != null) {
                generateInstructions(baseExpression, context);
            }
        }

        @SuppressWarnings("SuspiciousMethodCalls") @Override
        public void visitBinaryExpressionVoid(@NotNull JetBinaryExpression expression, CFPContext context) {
            JetSimpleNameExpression operationReference = expression.getOperationReference();
            IElementType operationType = operationReference.getReferencedNameElementType();
            if (!ImmutableSet.of(ANDAND, OROR, EQ, ELVIS).contains(operationType)) {
                mark(expression);
            }
            JetExpression right = expression.getRight();
            if (operationType == ANDAND) {
                generateInstructions(expression.getLeft(), IN_CONDITION);
                Label resultLabel = builder.createUnboundLabel();
                builder.jumpOnFalse(resultLabel, expression);
                if (right != null) {
                    generateInstructions(right, IN_CONDITION);
                }
                builder.bindLabel(resultLabel);
                if (!context.inCondition()) {
                    builder.predefinedOperation(expression, AND);
                }
            }
            else if (operationType == OROR) {
                generateInstructions(expression.getLeft(), IN_CONDITION);
                Label resultLabel = builder.createUnboundLabel();
                builder.jumpOnTrue(resultLabel, expression);
                if (right != null) {
                    generateInstructions(right, IN_CONDITION);
                }
                builder.bindLabel(resultLabel);
                if (!context.inCondition()) {
                    builder.predefinedOperation(expression, OR);
                }
            }
            else if (operationType == EQ) {
                visitAssignment(expression.getLeft(), right, expression);
            }
            else if (OperatorConventions.ASSIGNMENT_OPERATIONS.containsKey(operationType)) {
                if (generateCall(operationReference)) {
                    ResolvedCall resolvedCall = getResolvedCall(operationReference);
                    assert resolvedCall != null : "Generation succeeded, but no call is found: " + expression.getText();
                    CallableDescriptor descriptor = resolvedCall.getResultingDescriptor();
                    Name assignMethodName = OperatorConventions.getNameForOperationSymbol((JetToken) expression.getOperationToken());
                    if (!descriptor.getName().equals(assignMethodName)) {
                        // plus() called, assignment needed
                        visitAssignment(expression.getLeft(), null, expression);
                    }
                }
                else {
                    generateBothArguments(expression);
                }
            }
            else if (operationType == ELVIS) {
                generateInstructions(expression.getLeft(), NOT_IN_CONDITION);
                Label afterElvis = builder.createUnboundLabel();
                builder.jumpOnTrue(afterElvis, expression);
                if (right != null) {
                    generateInstructions(right, NOT_IN_CONDITION);
                }
                builder.bindLabel(afterElvis);
            }
            else {
                if (!generateCall(operationReference)) {
                    generateBothArguments(expression);
                }
            }
        }

        private void generateBothArguments(JetBinaryExpression expression) {
            JetExpression left = JetPsiUtil.deparenthesize(expression.getLeft());
            if (left != null) {
                generateInstructions(left, NOT_IN_CONDITION);
            }
            JetExpression right = expression.getRight();
            if (right != null) {
                generateInstructions(right, NOT_IN_CONDITION);
            }
        }

        private void visitAssignment(JetExpression lhs, @Nullable JetExpression rhs, JetExpression parentExpression) {
            JetExpression left = JetPsiUtil.deparenthesize(lhs);
            if (left == null) {
                builder.compilationError(lhs, "No lValue in assignment");
                return;
            }

            if (left instanceof JetArrayAccessExpression) {
                ResolvedCall setResolvedCall = trace.get(BindingContext.INDEXED_LVALUE_SET, left);
                generateArrayAccess((JetArrayAccessExpression) left, setResolvedCall);
                recordWrite(left, parentExpression);
                return;
            }

            generateInstructions(rhs, NOT_IN_CONDITION);
            if (left instanceof JetSimpleNameExpression || left instanceof JetProperty) {
                // Do nothing, just record write below
            }
            else if (left instanceof JetQualifiedExpression) {
                generateInstructions(((JetQualifiedExpression) left).getReceiverExpression(), NOT_IN_CONDITION);
            }
            else {
                builder.unsupported(parentExpression); // TODO
            }

            recordWrite(left, parentExpression);
        }

        private void recordWrite(JetExpression left, JetExpression parentExpression) {
            VariableDescriptor descriptor = BindingContextUtils.extractVariableDescriptorIfAny(trace.getBindingContext(), left, false);
            if (descriptor != null) {
                builder.write(parentExpression, left);
            }
        }

        private void generateArrayAccess(JetArrayAccessExpression arrayAccessExpression, @Nullable ResolvedCall resolvedCall) {
            mark(arrayAccessExpression);
            if (!checkAndGenerateCall(arrayAccessExpression, resolvedCall)) {
                for (JetExpression index : arrayAccessExpression.getIndexExpressions()) {
                    generateInstructions(index, NOT_IN_CONDITION);
                }

                generateInstructions(arrayAccessExpression.getArrayExpression(), NOT_IN_CONDITION);
            }
        }

        @Override
        public void visitUnaryExpressionVoid(@NotNull JetUnaryExpression expression, CFPContext context) {
            mark(expression);
            JetSimpleNameExpression operationSign = expression.getOperationReference();
            IElementType operationType = operationSign.getReferencedNameElementType();
            JetExpression baseExpression = expression.getBaseExpression();
            if (baseExpression == null) return;
            if (JetTokens.EXCLEXCL == operationType) {
                generateInstructions(baseExpression, NOT_IN_CONDITION);
                builder.predefinedOperation(expression, NOT_NULL_ASSERTION);
            }
            else {
                if (!generateCall(expression.getOperationReference())) {
                    generateInstructions(baseExpression, NOT_IN_CONDITION);
                }

                boolean incrementOrDecrement = isIncrementOrDecrement(operationType);
                if (incrementOrDecrement) {
                    // We skip dup's and other subtleties here
                    visitAssignment(baseExpression, null, expression);
                }
            }
        }

        private boolean isIncrementOrDecrement(IElementType operationType) {
            return operationType == JetTokens.PLUSPLUS || operationType == JetTokens.MINUSMINUS;
        }


        @Override
        public void visitIfExpressionVoid(@NotNull JetIfExpression expression, CFPContext context) {
            mark(expression);
            JetExpression condition = expression.getCondition();
            if (condition != null) {
                generateInstructions(condition, IN_CONDITION);
            }
            Label elseLabel = builder.createUnboundLabel();
            builder.jumpOnFalse(elseLabel, expression);
            JetExpression thenBranch = expression.getThen();
            if (thenBranch != null) {
                generateInstructions(thenBranch, context);
            }
            else {
                builder.loadUnit(expression);
            }
            Label resultLabel = builder.createUnboundLabel();
            builder.jump(resultLabel, expression);
            builder.bindLabel(elseLabel);
            JetExpression elseBranch = expression.getElse();
            if (elseBranch != null) {
                generateInstructions(elseBranch, context);
            }
            else {
                builder.loadUnit(expression);
            }
            builder.bindLabel(resultLabel);
        }

        private class FinallyBlockGenerator {
            private final JetFinallySection finallyBlock;
            private final CFPContext context;
            private Label startFinally = null;
            private Label finishFinally = null;

            private FinallyBlockGenerator(JetFinallySection block, CFPContext context) {
                finallyBlock = block;
                this.context = context;
            }

            public void generate() {
                JetBlockExpression finalExpression = finallyBlock.getFinalExpression();
                if (finalExpression == null) return;
                if (startFinally != null) {
                    assert finishFinally != null;
                    builder.repeatPseudocode(startFinally, finishFinally);
                    return;
                }
                startFinally = builder.createUnboundLabel("start finally");
                builder.bindLabel(startFinally);
                generateInstructions(finalExpression, context);
                finishFinally = builder.createUnboundLabel("finish finally");
                builder.bindLabel(finishFinally);
            }
        }
       

        @Override
        public void visitTryExpressionVoid(@NotNull JetTryExpression expression, CFPContext context) {
            mark(expression);
            JetFinallySection finallyBlock = expression.getFinallyBlock();
            final FinallyBlockGenerator finallyBlockGenerator = new FinallyBlockGenerator(finallyBlock, context);
            if (finallyBlock != null) {
                builder.enterTryFinally(new GenerationTrigger() {
                    private boolean working = false;

                    @Override
                    public void generate() {
                        // This checks are needed for the case of having e.g. return inside finally: 'try {return} finally{return}'
                        if (working) return;
                        working = true;
                        finallyBlockGenerator.generate();
                        working = false;
                    }
                });
            }

            List catchClauses = expression.getCatchClauses();
            boolean hasCatches = !catchClauses.isEmpty();
            Label onException = null;
            if (hasCatches) {
                onException = builder.createUnboundLabel("onException");
                builder.nondeterministicJump(onException, expression);
            }
            Label onExceptionToFinallyBlock = null;
            if (finallyBlock != null) {
                onExceptionToFinallyBlock = builder.createUnboundLabel("onExceptionToFinallyBlock");
                builder.nondeterministicJump(onExceptionToFinallyBlock, expression);
            }
            generateInstructions(expression.getTryBlock(), context);

            if (hasCatches) {
                Label afterCatches = builder.createUnboundLabel("afterCatches");
                builder.jump(afterCatches, expression);

                builder.bindLabel(onException);
                LinkedList




© 2015 - 2025 Weber Informatics LLC | Privacy Policy