
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