Control-Flow Analysis: Compute STATEMENT slice using control-flow information

This commit is contained in:
Alexey Sedunov
2014-07-11 19:30:25 +04:00
parent ffd4af872c
commit 00dd1dbb0b
19 changed files with 99 additions and 70 deletions
@@ -1252,8 +1252,6 @@ public class JetControlFlowProcessor {
generateInstructions(subjectExpression);
}
boolean hasElse = false;
List<JetExpression> branches = new ArrayList<JetExpression>();
Label doneLabel = builder.createUnboundLabel();
@@ -1265,7 +1263,6 @@ public class JetControlFlowProcessor {
boolean isElse = whenEntry.isElse();
if (isElse) {
hasElse = true;
if (iterator.hasNext()) {
trace.report(ELSE_MISPLACED_IN_WHEN.on(whenEntry));
}
@@ -1300,9 +1297,6 @@ public class JetControlFlowProcessor {
}
}
builder.bindLabel(doneLabel);
if (!hasElse && WhenChecker.mustHaveElse(expression, trace)) {
trace.report(NO_ELSE_IN_WHEN.on(expression));
}
mergeValues(branches, expression);
}
@@ -1407,6 +1401,11 @@ public class JetControlFlowProcessor {
builder.magic(specifier, specifier, arguments, PseudocodePackage.expectedTypeFor(expectedTypePredicate, arguments), MagicKind.VALUE_CONSUMER);
}
@Override
public void visitDelegationSpecifierList(@NotNull JetDelegationSpecifierList list) {
list.acceptChildren(this);
}
@Override
public void visitJetFile(@NotNull JetFile file) {
for (JetDeclaration declaration : file.getDeclarations()) {
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableInitState;
import org.jetbrains.jet.lang.cfg.PseudocodeVariablesData.VariableUseState;
import org.jetbrains.jet.lang.cfg.pseudocode.PseudoValue;
import org.jetbrains.jet.lang.cfg.pseudocode.Pseudocode;
import org.jetbrains.jet.lang.cfg.pseudocode.PseudocodeUtil;
import org.jetbrains.jet.lang.cfg.pseudocode.instructions.Instruction;
@@ -121,6 +122,10 @@ public class JetFlowInformationProvider {
markUnusedVariables();
markUnusedLiteralsInBlock();
markStatements();
markWhenWithoutElse();
}
public void checkFunction(@Nullable JetType expectedReturnType) {
@@ -697,6 +702,48 @@ public class JetFlowInformationProvider {
);
}
////////////////////////////////////////////////////////////////////////////////
// Statements
public void markStatements() {
PseudocodeTraverserPackage.traverse(
pseudocode, FORWARD, new JetFlowInformationProvider.FunctionVoid1<Instruction>() {
@Override
public void execute(@NotNull Instruction instruction) {
PseudoValue value = instruction instanceof InstructionWithValue
? ((InstructionWithValue) instruction).getOutputValue()
: null;
Pseudocode pseudocode = instruction.getOwner();
boolean isUsedAsExpression = !pseudocode.getUsages(value).isEmpty();
for (JetElement element : pseudocode.getValueElements(value)) {
trace.record(BindingContext.USED_AS_EXPRESSION, element, isUsedAsExpression);
}
}
}
);
}
public void markWhenWithoutElse() {
PseudocodeTraverserPackage.traverse(
pseudocode, FORWARD, new JetFlowInformationProvider.FunctionVoid1<Instruction>() {
@Override
public void execute(@NotNull Instruction instruction) {
PseudoValue value = instruction instanceof InstructionWithValue
? ((InstructionWithValue) instruction).getOutputValue()
: null;
for (JetElement element : instruction.getOwner().getValueElements(value)) {
if (element instanceof JetWhenExpression) {
JetWhenExpression whenExpression = (JetWhenExpression) element;
if (whenExpression.getElseExpression() == null && WhenChecker.mustHaveElse(whenExpression, trace)) {
trace.report(NO_ELSE_IN_WHEN.on(whenExpression));
}
}
}
}
}
);
}
////////////////////////////////////////////////////////////////////////////////
// Tail calls
@@ -24,6 +24,7 @@ import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingTrace;
import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
@@ -37,7 +38,7 @@ public final class WhenChecker {
JetType expectedType = trace.get(BindingContext.EXPECTED_EXPRESSION_TYPE, expression);
boolean isUnit = expectedType != null && KotlinBuiltIns.getInstance().isUnit(expectedType);
// Some "statements" are actually expressions returned from lambdas, their expected types are non-null
boolean isStatement = Boolean.TRUE.equals(trace.get(BindingContext.STATEMENT, expression)) && expectedType == null;
boolean isStatement = BindingContextUtilPackage.isUsedAsStatement(expression, trace.getBindingContext()) && expectedType == null;
return !isUnit && !isStatement && !isWhenExhaustive(expression, trace);
}
@@ -33,9 +33,6 @@ import org.jetbrains.jet.lang.types.TypeUtils
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction
fun JetExpression.isStatement(pseudocode: Pseudocode): Boolean =
pseudocode.getUsages(pseudocode.getElementValue(this)).isEmpty()
fun getReceiverTypePredicate(resolvedCall: ResolvedCall<*>, receiverValue: ReceiverValue): TypePredicate? {
val callableDescriptor = resolvedCall.getResultingDescriptor()
if (callableDescriptor == null) return null
@@ -136,7 +136,7 @@ public interface BindingContext {
* Has type of current expression has been already resolved
*/
WritableSlice<JetExpression, Boolean> PROCESSED = Slices.createSimpleSetSlice();
WritableSlice<JetElement, Boolean> STATEMENT = Slices.createRemovableSetSlice();
WritableSlice<JetElement, Boolean> USED_AS_EXPRESSION = Slices.createRemovableSetSlice();
WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<VariableDescriptor, CaptureKind>(DO_NOTHING);
@@ -139,3 +139,6 @@ public fun JetReturnExpression.getTargetFunctionDescriptor(context: BindingConte
.dropWhile { it is AnonymousFunctionDescriptor }
.firstOrNull()
}
public fun JetExpression.isUsedAsExpression(context: BindingContext): Boolean = context[BindingContext.USED_AS_EXPRESSION, this]!!
public fun JetExpression.isUsedAsStatement(context: BindingContext): Boolean = !isUsedAsExpression(context)
@@ -49,7 +49,6 @@ import javax.inject.Inject;
import java.util.Iterator;
import java.util.List;
import static org.jetbrains.jet.lang.resolve.BindingContext.STATEMENT;
import static org.jetbrains.jet.lang.types.TypeUtils.*;
import static org.jetbrains.jet.lang.types.expressions.CoercionStrategy.COERCION_TO_UNIT;
@@ -253,7 +252,6 @@ public class ExpressionTypingServices {
);
JetTypeInfo typeInfo = expressionTypingFacade.getTypeInfo(bodyExpression, context, function.hasBlockBody());
trace.record(STATEMENT, bodyExpression, false);
JetType type = typeInfo.getType();
if (type != null) {
return type;
@@ -283,7 +281,6 @@ public class ExpressionTypingServices {
if (!(statement instanceof JetExpression)) {
continue;
}
trace.record(STATEMENT, statement);
JetExpression statementExpression = (JetExpression) statement;
if (!iterator.hasNext()) {
result = getTypeOfLastExpressionInBlock(
@@ -71,10 +71,6 @@ public class PatternMatchingTypingVisitor extends ExpressionTypingVisitor {
}
public JetTypeInfo visitWhenExpression(JetWhenExpression expression, ExpressionTypingContext contextWithExpectedType, boolean isStatement) {
if (isStatement) {
contextWithExpectedType.trace.record(BindingContext.STATEMENT, expression);
}
DataFlowUtils.recordExpectedType(contextWithExpectedType.trace, expression, contextWithExpectedType.expectedType);
ExpressionTypingContext context = contextWithExpectedType.replaceExpectedType(NO_EXPECTED_TYPE).replaceContextDependency(INDEPENDENT);
@@ -42,6 +42,7 @@ public class ConvertToForEachLoopIntention : JetSelfTargetingIntention<JetExpres
else -> null
}
}
else -> null
}
}
is JetBinaryExpression -> element.getRight()
@@ -28,7 +28,6 @@ import org.jetbrains.jet.plugin.refactoring.introduce.introduceVariable.KotlinIn
import org.jetbrains.jet.lang.psi.JetSafeQualifiedExpression
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import com.intellij.psi.PsiElement
import org.jetbrains.jet.plugin.refactoring.inline.KotlinInlineValHandler
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
@@ -43,6 +42,7 @@ import org.jetbrains.jet.lang.psi.JetPostfixExpression
import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.resolve.DescriptorUtils
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.lang.resolve.bindingContextUtil.isUsedAsStatement
val NULL_PTR_EXCEPTION = "NullPointerException"
val NULL_PTR_EXCEPTION_FQ = "java.lang.NullPointerException"
@@ -72,17 +72,7 @@ fun JetExpression.extractExpressionIfSingle(): JetExpression? {
return innerExpression
}
fun JetExpression.isStatement(): Boolean {
val context = AnalyzerFacadeWithCache.getContextForElement(this)
val expectedType = context.get(BindingContext.EXPECTED_EXPRESSION_TYPE, this);
val isUnit = expectedType != null && KotlinBuiltIns.getInstance().isUnit(expectedType);
// Some "statements" are actually expressions returned from lambdas, their expected types are non-null
val isStatement = context.get(BindingContext.STATEMENT, this) == true && expectedType == null;
return isStatement || isUnit
}
fun JetExpression.isStatement(): Boolean = isUsedAsStatement(AnalyzerFacadeWithCache.getContextForElement(this))
fun JetBinaryExpression.getNonNullExpression(): JetExpression? = when {
this.getLeft()?.isNullExpression() == false ->
@@ -35,9 +35,9 @@ import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.psi.PsiDocumentManager
import org.apache.commons.lang.StringEscapeUtils.escapeJava
import org.jetbrains.jet.plugin.intentions.branchedTransformations.convertToIfNullExpression
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStatement
import org.jetbrains.jet.plugin.intentions.branchedTransformations.NULL_PTR_EXCEPTION
import org.jetbrains.jet.plugin.intentions.branchedTransformations.KOTLIN_NULL_PTR_EXCEPTION
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStatement
public class DoubleBangToIfThenIntention : JetSelfTargetingIntention<JetPostfixExpression>("double.bang.to.if.then", javaClass()) {
@@ -32,9 +32,8 @@ import org.jetbrains.jet.plugin.intentions.branchedTransformations.extractExpres
import org.jetbrains.jet.lang.psi.JetThrowExpression
import org.jetbrains.jet.plugin.JetBundle
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isNullExpressionOrEmptyBlock
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStatement
import org.jetbrains.jet.plugin.intentions.branchedTransformations.throwsNullPointerExceptionWithNoArguments
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStatement
public class IfThenToDoubleBangIntention : JetSelfTargetingIntention<JetIfExpression>("if.then.to.double.bang", javaClass()) {
@@ -25,8 +25,8 @@ import org.jetbrains.jet.lang.psi.JetBinaryExpression
import org.jetbrains.jet.plugin.intentions.branchedTransformations.convertToIfNotNullExpression
import org.jetbrains.jet.plugin.intentions.branchedTransformations.introduceValueForCondition
import org.jetbrains.jet.lang.psi.JetDotQualifiedExpression
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStatement
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStableVariable
import org.jetbrains.jet.plugin.intentions.branchedTransformations.isStatement
public class SafeAccessToIfThenIntention : JetSelfTargetingIntention<JetSafeQualifiedExpression>("safe.access.to.if.then", javaClass()) {
override fun isApplicableTo(element: JetSafeQualifiedExpression): Boolean = true
@@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.analyzer.AnalyzerPackage;
import org.jetbrains.jet.di.InjectorForBodyResolve;
import org.jetbrains.jet.lang.cfg.JetFlowInformationProvider;
import org.jetbrains.jet.lang.descriptors.*;
import org.jetbrains.jet.lang.descriptors.annotations.Annotated;
import org.jetbrains.jet.lang.psi.*;
@@ -173,6 +174,8 @@ public class ResolveElementCache {
assert false : "Invalid type of the topmost parent";
}
new JetFlowInformationProvider(resolveElement, trace).checkDeclaration();
return trace.getBindingContext();
}
@@ -315,6 +318,10 @@ public class ResolveElementCache {
}
bodyResolver.resolvePropertyAccessors(bodyResolveContext, jetProperty, descriptor);
for (JetPropertyAccessor accessor : jetProperty.getAccessors()) {
new JetFlowInformationProvider(accessor, trace).checkDeclaration();
}
}
private static void functionAdditionalResolve(
@@ -64,6 +64,7 @@ import org.jetbrains.jet.lang.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.jet.lang.resolve.bindingContextUtil.getTargetFunctionDescriptor
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.jet.lang.resolve.OverridingUtil
import org.jetbrains.jet.lang.resolve.bindingContextUtil.isUsedAsStatement
import org.jetbrains.jet.lang.psi.psiUtil.isAncestor
import org.jetbrains.jet.plugin.intentions.declarations.DeclarationUtils
import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils
@@ -93,7 +94,6 @@ private fun List<Instruction>.getExitPoints(): List<Instruction> =
filter { localInstruction -> localInstruction.nextInstructions.any { it !in this } }
private fun List<Instruction>.getResultType(
pseudocode: Pseudocode,
bindingContext: BindingContext,
options: ExtractionOptions): JetType {
fun instructionToType(instruction: Instruction): JetType? {
@@ -106,7 +106,7 @@ private fun List<Instruction>.getResultType(
}
if (expression == null) return null
if (options.inferUnitTypeForUnusedValues && expression.isStatement(pseudocode)) return null
if (options.inferUnitTypeForUnusedValues && expression.isUsedAsStatement(bindingContext)) return null
return bindingContext[BindingContext.EXPRESSION_TYPE, expression]
}
@@ -206,8 +206,8 @@ private fun ExtractionData.analyzeControlFlow(
val nonLocallyUsedDeclarations = getLocalDeclarationsWithNonLocalUsages(pseudocode, localInstructions, bindingContext)
val (declarationsToCopy, declarationsToReport) = nonLocallyUsedDeclarations.partition { it is JetProperty && it.isLocal() }
val typeOfDefaultFlow = defaultExits.getResultType(pseudocode, bindingContext, options)
val returnValueType = valuedReturnExits.getResultType(pseudocode, bindingContext, options)
val typeOfDefaultFlow = defaultExits.getResultType(bindingContext, options)
val returnValueType = valuedReturnExits.getResultType(bindingContext, options)
val defaultReturnType = if (returnValueType.isMeaningful()) returnValueType else typeOfDefaultFlow
if (defaultReturnType.isError()) return Pair(DefaultControlFlow(DEFAULT_RETURN_TYPE, declarationsToCopy), ErrorMessage.ERROR_TYPES)
@@ -25,6 +25,7 @@ import org.jetbrains.jet.lang.descriptors.VariableDescriptor;
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.bindingContextUtil.BindingContextUtilPackage;
import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant;
import org.jetbrains.jet.lang.resolve.constants.NullValue;
import org.jetbrains.jet.lang.types.JetType;
@@ -42,7 +43,6 @@ import org.jetbrains.k2js.translate.reference.AccessTranslationUtils;
import org.jetbrains.k2js.translate.reference.CallExpressionTranslator;
import org.jetbrains.k2js.translate.reference.QualifiedExpressionTranslator;
import org.jetbrains.k2js.translate.reference.ReferenceTranslator;
import org.jetbrains.k2js.translate.utils.BindingUtils;
import org.jetbrains.k2js.translate.utils.JsAstUtils;
import org.jetbrains.k2js.translate.utils.TranslationUtils;
import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
@@ -185,7 +185,7 @@ public final class ExpressionVisitor extends TranslatorVisitor<JsNode> {
JsNode thenNode = thenExpression.accept(this, context);
JsNode elseNode = elseExpression == null ? null : elseExpression.accept(this, context);
boolean isKotlinStatement = BindingUtils.isStatement(context.bindingContext(), expression);
boolean isKotlinStatement = BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext());
boolean canBeJsExpression = thenNode instanceof JsExpression && elseNode instanceof JsExpression;
if (!isKotlinStatement && canBeJsExpression) {
return new JsConditional(testExpression, convertToExpression(thenNode), convertToExpression(elseNode)).source(expression);
@@ -21,10 +21,10 @@ import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
import org.jetbrains.k2js.translate.context.TranslationContext;
import org.jetbrains.k2js.translate.general.AbstractTranslator;
import org.jetbrains.k2js.translate.general.Translation;
import org.jetbrains.k2js.translate.utils.BindingUtils;
import org.jetbrains.k2js.translate.utils.TranslationUtils;
import org.jetbrains.k2js.translate.utils.mutator.AssignToExpressionMutator;
import org.jetbrains.k2js.translate.utils.mutator.LastExpressionMutator;
@@ -39,7 +39,7 @@ public final class WhenTranslator extends AbstractTranslator {
public static JsNode translate(@NotNull JetWhenExpression expression, @NotNull TranslationContext context) {
WhenTranslator translator = new WhenTranslator(expression, context);
if (BindingUtils.isStatement(context.bindingContext(), expression)) {
if (BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext())) {
JsBlock jsBlock = new JsBlock();
translator.translateAsStatement(jsBlock.getStatements());
return jsBlock;
@@ -83,25 +83,25 @@ public final class WhenTranslator extends AbstractTranslator {
JsIf prevIf = null;
for (JetWhenEntry entry : whenExpression.getEntries()) {
JsStatement statement = withReturnValueCaptured(translateEntryExpression(entry));
if (entry.isElse()) {
if (prevIf == null) {
statements.add(statement);
}
else {
prevIf.setElseStatement(statement);
}
break;
JsBlock entryBody = new JsBlock();
JsStatement statement = withReturnValueCaptured(translateEntryExpression(entry, context().innerBlock(entryBody)));
if (!entryBody.isEmpty()) {
entryBody.getStatements().add(statement);
statement = entryBody;
}
JsIf ifStatement = new JsIf(translateConditions(entry), statement);
JsStatement statementToAdd = entry.isElse() ? statement : new JsIf(translateConditions(entry), statement);
if (prevIf == null) {
statements.add(ifStatement);
statements.add(statementToAdd);
}
else {
prevIf.setElseStatement(ifStatement);
prevIf.setElseStatement(statementToAdd);
}
prevIf = ifStatement;
if (entry.isElse()) break;
assert statementToAdd instanceof JsIf : "Non-else entry expected: " + entry.getText();
prevIf = (JsIf) statementToAdd;
}
}
@@ -127,10 +127,10 @@ public final class WhenTranslator extends AbstractTranslator {
}
@NotNull
private JsNode translateEntryExpression(@NotNull JetWhenEntry entry) {
private JsNode translateEntryExpression(@NotNull JetWhenEntry entry, @NotNull TranslationContext context) {
JetExpression expressionToExecute = entry.getExpression();
assert expressionToExecute != null : "WhenEntry should have whenExpression to execute.";
return Translation.translateExpression(expressionToExecute, context());
return Translation.translateExpression(expressionToExecute, context);
}
@NotNull
@@ -95,10 +95,6 @@ public final class BindingUtils {
return (JsDescriptorUtils.findAncestorClass(superclassDescriptors) != null);
}
public static boolean isStatement(@NotNull BindingContext context, @NotNull JetExpression expression) {
return BindingContextUtils.getNotNull(context, BindingContext.STATEMENT, expression);
}
@NotNull
public static JetType getTypeByReference(@NotNull BindingContext context,
@NotNull JetTypeReference typeReference) {
@@ -18,11 +18,10 @@ package org.jetbrains.k2js.translate.utils.dangerous;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.bindingContextUtil.BindingContextUtilPackage;
import org.jetbrains.k2js.translate.context.TranslationContext;
import org.jetbrains.k2js.translate.reference.InlinedCallExpressionTranslator;
import static org.jetbrains.k2js.translate.utils.BindingUtils.isStatement;
public final class FindDangerousVisitor extends JetTreeVisitor<DangerousData> {
@NotNull
@@ -63,12 +62,9 @@ public final class FindDangerousVisitor extends JetTreeVisitor<DangerousData> {
@Override
public Void visitBlockExpression(@NotNull JetBlockExpression expression, DangerousData data) {
if (isStatement(context.bindingContext(), expression)) {
return null;
}
else {
return super.visitBlockExpression(expression, data);
}
return BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext())
? null
: super.visitBlockExpression(expression, data);
}
@Override
@@ -85,7 +81,7 @@ public final class FindDangerousVisitor extends JetTreeVisitor<DangerousData> {
if (data.exists()) {
return true;
}
if (!isStatement(context.bindingContext(), expression)) {
if (!BindingContextUtilPackage.isUsedAsStatement(expression, context.bindingContext())) {
data.setDangerousNode(expression);
return true;
}