forked from thewisenerd/detekt-sidekt
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from udaan-com/main_thread_blocking_resource
[feat] added new plugin to check for jersey main thread blocking call
- Loading branch information
Showing
5 changed files
with
194 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
src/main/kotlin/io/github/thewisenerd/linters/sidekt/rules/JerseyMainThreadBlockingCall.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package io.github.thewisenerd.linters.sidekt.rules | ||
|
||
import io.github.thewisenerd.linters.sidekt.helpers.Debugger | ||
import io.gitlab.arturbosch.detekt.api.* | ||
import io.gitlab.arturbosch.detekt.rules.hasAnnotation | ||
import org.jetbrains.kotlin.psi.* | ||
|
||
class JerseyMainThreadBlockingCall(config: Config) : Rule(config) { | ||
|
||
companion object { | ||
const val RUN_BLOCKING_EXP_ID = "runBlocking" | ||
val httpMethodList = arrayOf("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS") | ||
} | ||
|
||
private val debugStream by lazy { | ||
valueOrNull<String>("debug")?.let { | ||
Debugger.getOutputStreamForDebugger(it) | ||
} | ||
} | ||
|
||
override val issue: Issue = Issue( | ||
id = JerseyMainThreadBlockingCall::class.java.simpleName, | ||
severity = Severity.Performance, | ||
description = "main thread blocking call in resource method", | ||
debt = Debt.TEN_MINS | ||
) | ||
|
||
|
||
override fun visitNamedFunction(resourceMethod: KtNamedFunction) { | ||
super.visitNamedFunction(resourceMethod) | ||
val dbg = Debugger.make(JerseyMainThreadBlockingCall::class.java.simpleName, debugStream) | ||
|
||
val hasPathAnnotation = resourceMethod.hasAnnotation("Path") | ||
val hasAnyOfHttpMethodAnnotation = resourceMethod.hasAnnotation(*httpMethodList) | ||
|
||
if (!hasPathAnnotation && !hasAnyOfHttpMethodAnnotation) { | ||
dbg.i(" hasPathAnnotation=false or none of http method annotation available") | ||
return | ||
} | ||
|
||
var methodBlockingMainThread = false | ||
resourceMethod.children.forEach { | ||
if(it is KtCallExpression) { | ||
val isUsingRunBlockingExpression = (it.calleeExpression as KtNameReferenceExpression).getReferencedNameAsName().identifier == RUN_BLOCKING_EXP_ID | ||
if(isUsingRunBlockingExpression) { | ||
methodBlockingMainThread = true | ||
} | ||
} | ||
|
||
if(it is KtBlockExpression) { | ||
it.children.map { blkExp -> | ||
if(blkExp is KtCallExpression) { | ||
val identifiedBlockingExp = (blkExp.calleeExpression as KtNameReferenceExpression).getReferencedNameAsName().identifier == RUN_BLOCKING_EXP_ID | ||
if(identifiedBlockingExp){ | ||
methodBlockingMainThread = true | ||
} | ||
} | ||
|
||
if(blkExp is KtReturnExpression) { | ||
blkExp.children.filterIsInstance<KtCallExpression>().map { retExp -> | ||
val identifiedBlockingExp = (retExp.calleeExpression as KtNameReferenceExpression).getReferencedNameAsName().identifier == RUN_BLOCKING_EXP_ID | ||
if(identifiedBlockingExp){ | ||
methodBlockingMainThread = true | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
if(methodBlockingMainThread) { | ||
dbg.i("blocks main application thread") | ||
report( | ||
CodeSmell( | ||
issue = issue, | ||
entity = Entity.from(resourceMethod), | ||
message = "jersey resource method (${resourceMethod.name}) have main thread blocking call)" | ||
) | ||
) | ||
} | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
src/test/kotlin/io/github/thewisenerd/linters/sidekt/JerseyMainThreadBlockingCallTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package io.github.thewisenerd.linters.sidekt | ||
|
||
import io.github.thewisenerd.linters.sidekt.rules.JerseyMainThreadBlockingCall | ||
import io.gitlab.arturbosch.detekt.api.Config | ||
import io.gitlab.arturbosch.detekt.api.Finding | ||
import io.gitlab.arturbosch.detekt.api.SourceLocation | ||
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext | ||
import org.junit.Test | ||
|
||
class JerseyMainThreadBlockingCallTest { | ||
companion object { | ||
private val blockingJerseyMethod = JerseyMainThreadBlockingCall::class.java.simpleName | ||
|
||
private fun ensureJerseyMethodParameterDefaultValueFindings(findings: List<Finding>, requiredFindings: List<SourceLocation>) = | ||
TestUtils.ensureFindings(blockingJerseyMethod, findings, requiredFindings) | ||
} | ||
|
||
private val testConfig = object : Config { | ||
override fun subConfig(key: String): Config = this | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
override fun <T : Any> valueOrNull(key: String): T? { | ||
return when (key) { | ||
"active" -> true as? T | ||
"debug" -> "stderr" as? T | ||
else -> null | ||
} | ||
} | ||
} | ||
private val subject = JerseyMainThreadBlockingCall(testConfig) | ||
|
||
|
||
@Test | ||
fun simple06() { | ||
val code = TestUtils.readFile("simple06.kt") | ||
val findings = subject.compileAndLintWithContext(TestUtils.env, code) | ||
ensureJerseyMethodParameterDefaultValueFindings(findings, listOf( | ||
SourceLocation(3, 5), | ||
SourceLocation(8, 5), | ||
SourceLocation(13, 5), | ||
SourceLocation(18, 5), | ||
SourceLocation(25, 5), | ||
SourceLocation(42, 5) | ||
)) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import kotlinx.coroutines.runBlocking | ||
|
||
@Path("/variant01") | ||
fun testRunBlockingVariant01() = runBlocking { | ||
"This is variation 01" | ||
} | ||
|
||
@Path("/variant02") | ||
fun testRunBlockingVariant02() : String = runBlocking{ | ||
"This is variation 02" | ||
} | ||
|
||
@Path("/variant03") | ||
fun testRunBlockingVariant03() = runBlocking{ | ||
return@runBlocking "This is variation 03" | ||
} | ||
|
||
@Path("/variant04") | ||
fun testRunBlockingVariant04() : String { | ||
return runBlocking{ | ||
return@runBlocking "This is variation 04" | ||
} | ||
} | ||
|
||
@Path("/variant05") | ||
fun testRunBlockingVariant05() : String { | ||
println("This is variation 05") | ||
|
||
runBlocking { | ||
"This is variation 05" | ||
} | ||
|
||
return "This is variation 05" | ||
} | ||
|
||
@Path("/variant06") | ||
fun testRunBlockingVariant06() : String { | ||
println("This is variation 06") | ||
return "This is variation 06" | ||
} | ||
|
||
@GET | ||
fun testRunBlockingVariant07() = runBlocking { | ||
println("This is variation 07") | ||
} | ||
|
||
@GET | ||
fun testRunBlockingVariant08() : String { | ||
println("This is variation 08") | ||
return "This is variation 08" | ||
} |