/
ReturnFromFinally.kt
108 lines (94 loc) · 4.14 KB
/
ReturnFromFinally.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package io.gitlab.arturbosch.detekt.rules.exceptions
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.config
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.Configuration
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtFinallySection
import org.jetbrains.kotlin.psi.KtReturnExpression
import org.jetbrains.kotlin.psi.KtTryExpression
import org.jetbrains.kotlin.psi.psiUtil.blockExpressionsOrSingle
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunction
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.types.KotlinType
/**
* Reports all `return` statements in `finally` blocks.
* Using `return` statements in `finally` blocks can discard and hide exceptions that are thrown in the `try` block.
* Furthermore, this rule reports values from `finally` blocks, if the corresponding `try` is used as an expression.
*
* <noncompliant>
* fun foo() {
* try {
* throw MyException()
* } finally {
* return // prevents MyException from being propagated
* }
* }
*
* val a: String = try { "s" } catch (e: Exception) { "e" } finally { "f" }
* </noncompliant>
*/
@RequiresTypeResolution
@ActiveByDefault(since = "1.16.0")
class ReturnFromFinally(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
"ReturnFromFinally",
Severity.Defect,
"Do not return within a finally statement. This can discard exceptions.",
Debt.TWENTY_MINS
)
@Configuration("ignores labeled return statements")
private val ignoreLabeled: Boolean by config(false)
override fun visitTryExpression(expression: KtTryExpression) {
super.visitTryExpression(expression)
val finallyBlock = expression.finallyBlock ?: return
if (expression.isUsedAsExpression(bindingContext) &&
finallyBlock.typeEqualsTo(expression.getType(bindingContext))
) {
report(
CodeSmell(
issue = issue,
entity = Entity.Companion.from(finallyBlock),
message = "Contents of the finally block do not affect " +
"the result of the expression."
)
)
}
finallyBlock.finalExpression
.collectDescendantsOfType<KtReturnExpression> { returnExpression ->
isReturnFromTargetFunction(finallyBlock.finalExpression, returnExpression) &&
canFilterLabeledExpression(returnExpression)
}
.forEach { report(CodeSmell(issue, Entity.from(it), issue.description)) }
}
private fun isReturnFromTargetFunction(
blockExpression: KtBlockExpression,
returnStmts: KtReturnExpression
): Boolean {
val targetFunction = returnStmts.getTargetFunction(bindingContext)
?: return false
val targetFunctionBodyExpressionStatements = targetFunction
.blockExpressionsOrSingle()
.asIterable()
return blockExpression.isInsideOf(targetFunctionBodyExpressionStatements)
}
private fun canFilterLabeledExpression(
returnStmt: KtReturnExpression
): Boolean = !ignoreLabeled || returnStmt.labeledExpression == null
private fun KtFinallySection.typeEqualsTo(type: KotlinType?): Boolean {
val finallyExpression = finalExpression
if (finallyExpression.statements.isEmpty()) return false
return finalExpression.getType(bindingContext) == type
}
}