Fix missed returned expression if the last expression was condition jump

See ticket comments for the detailed description

^KT-28061 Fixed
This commit is contained in:
Dmitry Savvinov
2018-11-12 15:19:32 +03:00
parent c9e87bf353
commit 500dc11514
2 changed files with 34 additions and 6 deletions
@@ -118,6 +118,29 @@ class ControlFlowInformationProvider private constructor(
markAndCheckTailCalls()
}
/**
* Collects returned expressions from current pseudocode.
*
* "Returned expression" here == "last expression" in *control-flow terms*. Intuitively,
* it considers all execution paths, takes last expression on each path and returns them.
*
* More specifically, this function starts from EXIT instruction, and performs DFS-search
* on reversed control-flow edges in a following manner:
* - if the current instruction is a Return-instruction, then add it's expression to result
* - if the current instruction is a Element-instruction, then add it's element to result
* - if the current instruction is a Jump-instruction, then process it's predecessors
* recursively
*
* NB. The second case (Element-instruction) means that notion of "returned expression"
* here differs from what the language treats as "returned expression" (notably in the
* presence of Unit-coercion). Example:
*
* fun foo() {
* val x = 42
* x.inc() // This call will be in a [returnedExpressions], even though this expression
* // isn't actually returned
* }
*/
private fun collectReturnExpressions(returnedExpressions: MutableCollection<KtElement>) {
val instructions = pseudocode.instructions.toHashSet()
val exitInstruction = pseudocode.exitInstruction
@@ -135,15 +158,17 @@ class ControlFlowInformationProvider private constructor(
}
}
override fun visitJump(instruction: AbstractJumpInstruction) {
// Nothing
}
override fun visitUnconditionalJump(instruction: UnconditionalJumpInstruction) {
redirectToPrevInstructions(instruction)
}
override fun visitConditionalJump(instruction: ConditionalJumpInstruction) {
redirectToPrevInstructions(instruction)
}
// Note that there's no need to overload `visitThrowException`, because
// it can never be a predecessor of EXIT (throwing always leads to ERROR)
private fun redirectToPrevInstructions(instruction: Instruction) {
for (redirectInstruction in instruction.previousInstructions) {
redirectInstruction.accept(this)
@@ -160,6 +185,9 @@ class ControlFlowInformationProvider private constructor(
override fun visitInstruction(instruction: Instruction) {
if (instruction is KtElementInstruction) {
// Caveats:
// - for empty block-bodies, read(Unit) is emitted and will be processed here
// - for Unit-coerced blocks, last expression will be processed here
returnedExpressions.add(instruction.element)
} else {
throw IllegalStateException("$instruction precedes the exit point")
@@ -14,4 +14,4 @@ fun test(): String {
val x: String? = null
x?.myRun { return "" }
}
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>