Fix verify error with 'return when/if/try' in suspend function
The problem appears for tail-optimized suspend functions, we erroneously assumed that when/if/try expressions in tail-call position have simple types like `I` while actually, they can return SUSPENDED object, i.e. must have `java/lang/Object` as return type. But this only concerns branching operations in tail-call position, so we have to make an additional analysis for remembering whether a given expression is in a tail-call position. Also, it's important here that we now assume the return type of the current function as `java/lang/Object` that is necessary to avoid wrong checkcasts. #KT-15364 Fixed
This commit is contained in:
@@ -30,10 +30,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.MarkInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.sideEffectFree
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.Edges
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverseFollowingInstructions
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.*
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.referencedProperty
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
@@ -58,6 +55,7 @@ import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils.*
|
||||
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import java.util.*
|
||||
|
||||
class ControlFlowInformationProvider private constructor(
|
||||
@@ -809,8 +807,8 @@ class ControlFlowInformationProvider private constructor(
|
||||
}
|
||||
|
||||
private fun checkSuspendCalls(currentFunction: FunctionDescriptor) {
|
||||
|
||||
if (!currentFunction.isSuspend) return
|
||||
var containsNonTailCalls = false
|
||||
|
||||
traverseCalls { instruction, resolvedCall ->
|
||||
val calleeDescriptor = resolvedCall.resultingDescriptor as? FunctionDescriptor ?: return@traverseCalls
|
||||
@@ -827,7 +825,28 @@ class ControlFlowInformationProvider private constructor(
|
||||
val isUsedAsExpression = instruction.owner.getUsages(instruction.outputValue).isNotEmpty()
|
||||
|
||||
if (!isUsedAsExpression || !instruction.isTailCall(enclosingSuspendFunction) || isInsideTry(element)) {
|
||||
trace.record(BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, currentFunction.original)
|
||||
containsNonTailCalls = true
|
||||
}
|
||||
}
|
||||
|
||||
if (containsNonTailCalls) {
|
||||
trace.record(BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, currentFunction.original)
|
||||
}
|
||||
else {
|
||||
val tailInstructionDetector = TailInstructionDetector(subroutine)
|
||||
traverseFollowingInstructions(
|
||||
pseudocode.sinkInstruction,
|
||||
order = TraversalOrder.BACKWARD
|
||||
) { instruction ->
|
||||
|
||||
instruction.safeAs<KtElementInstruction>()?.element?.safeAs<KtExpression>()?.let { expression ->
|
||||
trace.record(BindingContext.IS_TAIL_EXPRESSION_IN_SUSPEND_FUNCTION, expression)
|
||||
}
|
||||
|
||||
if (instruction.accept(tailInstructionDetector))
|
||||
TraverseInstructionResult.CONTINUE
|
||||
else
|
||||
TraverseInstructionResult.SKIP
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -895,13 +914,19 @@ class ControlFlowInformationProvider private constructor(
|
||||
KtTryExpression::class.java, KtFunction::class.java, KtAnonymousInitializer::class.java
|
||||
) is KtTryExpression
|
||||
|
||||
private fun CallInstruction.isTailCall(subroutine: KtElement = this@ControlFlowInformationProvider.subroutine) =
|
||||
traverseFollowingInstructions(
|
||||
this,
|
||||
HashSet<Instruction>(),
|
||||
TraversalOrder.FORWARD,
|
||||
TailCallDetector(subroutine, this)
|
||||
)
|
||||
private fun CallInstruction.isTailCall(subroutine: KtElement = this@ControlFlowInformationProvider.subroutine): Boolean {
|
||||
val tailInstructionDetector = TailInstructionDetector(subroutine)
|
||||
return traverseFollowingInstructions(
|
||||
this,
|
||||
HashSet<Instruction>(),
|
||||
TraversalOrder.FORWARD
|
||||
) {
|
||||
if (it == this@isTailCall || it.accept(tailInstructionDetector))
|
||||
TraverseInstructionResult.CONTINUE
|
||||
else
|
||||
TraverseInstructionResult.HALT
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun traverseCalls(crossinline onCall: (instruction: CallInstruction, resolvedCall: ResolvedCall<*>) -> Unit) {
|
||||
pseudocode.traverse(TraversalOrder.FORWARD) { instruction ->
|
||||
|
||||
+5
-11
@@ -16,7 +16,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.cfg;
|
||||
|
||||
import kotlin.jvm.functions.Function1;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult;
|
||||
@@ -27,21 +26,16 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ThrowExceptionInst
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.MarkInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction;
|
||||
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult;
|
||||
import org.jetbrains.kotlin.psi.KtElement;
|
||||
|
||||
public class TailCallDetector extends InstructionVisitorWithResult<Boolean> implements Function1<Instruction, TraverseInstructionResult> {
|
||||
/**
|
||||
* Returns true when visited instruction may lie on a path from a tail-call-like operation to the sink of the subroutine
|
||||
*/
|
||||
public class TailInstructionDetector extends InstructionVisitorWithResult<Boolean> {
|
||||
private final KtElement subroutine;
|
||||
private final Instruction start;
|
||||
|
||||
public TailCallDetector(@NotNull KtElement subroutine, @NotNull Instruction start) {
|
||||
public TailInstructionDetector(@NotNull KtElement subroutine) {
|
||||
this.subroutine = subroutine;
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TraverseInstructionResult invoke(@NotNull Instruction instruction) {
|
||||
return instruction == start || instruction.accept(this) ? TraverseInstructionResult.CONTINUE : TraverseInstructionResult.HALT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,6 +137,7 @@ public interface BindingContext {
|
||||
|
||||
WritableSlice<Call, FunctionDescriptor> ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL = Slices.createSimpleSlice();
|
||||
WritableSlice<FunctionDescriptor, Boolean> CONTAINS_NON_TAIL_SUSPEND_CALLS = Slices.createSimpleSetSlice();
|
||||
WritableSlice<KtExpression, Boolean> IS_TAIL_EXPRESSION_IN_SUSPEND_FUNCTION = Slices.createSimpleSetSlice();
|
||||
|
||||
WritableSlice<VariableAccessorDescriptor, ResolvedCall<FunctionDescriptor>> DELEGATED_PROPERTY_RESOLVED_CALL = Slices.createSimpleSlice();
|
||||
WritableSlice<VariableAccessorDescriptor, Call> DELEGATED_PROPERTY_CALL = Slices.createSimpleSlice();
|
||||
|
||||
Reference in New Issue
Block a user