More Related Content Similar to Static Kotlin bug hunting Devoxx Morocco.pdf (20) Static Kotlin bug hunting Devoxx Morocco.pdf3. ©2023, SonarSource S.A, Switzerland.
https://www.freepik.com/vectors/ukraine-war
Ukraine war vector created by starline - www.freepik.com
5. ©2023, SonarSource S.A, Switzerland.
Agenda
● History
● Why Kotlin?
● sonar-kotlin 2.0
● Feedback
● Achievements
● Takeaways
5
20. PROs
● 1 rule - many languages
● Fast new languages
adoption
● Maintenance
● Rules precision
● Language specific rules
● Testing
● Semantic
● …
CONs
22. ©2023, SonarSource S.A, Switzerland.
class HelloWorld {
fun f() {
println("Hello, World!")
}
}
fun g(x: HelloWorld) {
x.f()
}
23. ©2023, SonarSource S.A, Switzerland.
class HelloWorld {
fun f() {
println("Hello, World!")
}
}
fun g(x: HelloWorld) {
x.f()
}
24. ©2023, SonarSource S.A, Switzerland.
class HelloWorld {
fun f() {
println("Hello, World!")
}
}
fun g(x: HelloWorld) {
x.f()
}
27. ©2023, SonarSource S.A, Switzerland.
Why
● Growing popularity of Kotlin
● User feedback
● We wanted to use Kotlin
● Android and Security
● SLang is limited
27
36. ©2023, SonarSource S.A, Switzerland.
36
override fun visitBinaryExpression(expression: KtBinaryExpression, ...) {
val rightExpression = expression. right // Nullable
val leftExpression = expression. left // Nullable
...
}
37. ©2023, SonarSource S.A, Switzerland.
37
override fun visitBinaryExpression(expression: KtBinaryExpression,...) {
val rightExpression = expression. right?.let {
val leftExpression = expression. left?.let {
...
}
}
...
}
38. ©2023, SonarSource S.A, Switzerland.
38
override fun visitBinaryExpression(expression: KtBinaryExpression,...) {
val rightExpression = expression. right ?: return
val leftExpression = expression. left ?: return
...
}
39. ©2023, SonarSource S.A, Switzerland.
39
override fun visitBinaryExpression(expression: KtBinaryExpression,...) {
val rightExpression = expression. right!!
val leftExpression = expression. left!!
...
}
40. ©2023, SonarSource S.A, Switzerland.
40
override fun visitBinaryExpression(expression: KtBinaryExpression,...) {
// Valid KtBinaryExpression should always have right & left
val rightExpression = expression. right!!
val leftExpression = expression. left!!
...
}
41. ©2023, SonarSource S.A, Switzerland.
What’s wrong with Kotlin AST
● Nullability hell
● Lack of Documentation & naming
41
43. ©2023, SonarSource S.A, Switzerland.
Bad Function Name rule
43
fun visitNamedFunction
(f: KtNamedFunction, …){
// Named functions always have name
val name = f.name!!
if (!name.matches(formatRegex)) {
reportIssue(
f.nameIdentifier!!,
"Message"
)
}
}
44. ©2023, SonarSource S.A, Switzerland.
Bad Function Name rule
fun visitNamedFunction
(f: KtNamedFunction, …){
// Named functions always have name
val name = f.name!!
if (!name.matches(formatRegex)) {
reportIssue(
f.nameIdentifier!!,
"Message"
)
}
}
44
val function = fun() {}
45. ©2023, SonarSource S.A, Switzerland.
Bad Function Name rule
45
val function = fun() {}
java.lang.NullPointerException
fun visitNamedFunction
(f: KtNamedFunction, …){
// Named functions always have name
val name = f.name!!
if (!name.matches(formatRegex)) {
reportIssue(
f.nameIdentifier!!,
"Message"
)
}
}
46. ©2023, SonarSource S.A, Switzerland.
Bad Function Name rule
46
val function = fun() {}
java.lang.NullPointerException
fun visitNamedFunction(f: KtNamedFunction
, …){
// Named functions always have name
val name = f.name!!
if (!name.matches(formatRegex)) {
reportIssue(
f.nameIdentifier!!,
"Message"
)
}
}
47. ©2023, SonarSource S.A, Switzerland.
Bad Function Name rule
47
val function = fun() {}
java.lang.NullPointerException
fun visitNamedFunction(f: KtNamedFunction
, …){
// Named functions always have name
val name = f.name!!
if (!name.matches(formatRegex)) {
reportIssue(
f.nameIdentifier!!,
"Message"
)
}
}
KtNamedFunction
is
declared with
“fun”
48. ©2023, SonarSource S.A, Switzerland.
What’s wrong with Kotlin AST
● Nullability hell
● Lack of Documentation & naming
● Compiler diagnostics
48
50. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
50
fun visitKtFile(file: KtFile, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.DEPRECATION
}.forEach {
ctx.reportIssue(
it.psiElement,"Message"
)
}
}
51. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
51
fun visitKtFile(file: KtFile, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.DEPRECATION
}.forEach {
ctx.reportIssue(
it.psiElement,"Message"
)
}
}
@Deprecated("Some text")
enum class
MyEnum(val s:String) {
ENTRY1(""),
}
52. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
52
fun visitKtFile(file: KtFile, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.DEPRECATION
}.forEach {
ctx.reportIssue(
it.psiElement,"Message"
)
}
}
@Deprecated("Some text")
enum class
MyEnum(val s:String) {
ENTRY1(""),
}
java.lang.IndexOutOfBoundsException: (5:11,5:10)
53. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
53
fun visitKtFile(file: KtFile, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.DEPRECATION
}.forEach {
ctx.reportIssue(
it.psiElement,"Message"
)
}
}
@Deprecated("Some text")
enum class
MyEnum(val s:String) {
ENTRY1(""),
}
java.lang.IndexOutOfBoundsException: (5:11,5:10)
54. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
54
fun visitKtFile(file: KtFile
, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.
DEPRECATION
}.forEach {
ctx.reportIssue(
it.psiElement,"Message"
)
}
}
@Deprecated("Some text")
enum class
MyEnum(val s:String) {
ENTRY1[MyEnum](""),
}
java.lang.IndexOutOfBoundsException: (5:11,5:10)
55. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
55
fun visitKtFile(file: KtFile
, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.
DEPRECATION
}.forEach {
ctx.reportIssue(
it.psiElement,"Message"
)
}
}
@Deprecated("Some text")
enum class
MyEnum(val s:String) {
ENTRY1[MyEnum](""),
}
java.lang.IndexOutOfBoundsException: (5:11,5:10)
Let’s fix it
56. ©2023, SonarSource S.A, Switzerland.
Deprecated Code Used Rule
56
fun visitKtFile(file: KtFile
, ctx: Context) {
ctx.diagnostics
.filter {
it.factory == Errors.
DEPRECATION
}.forEach {
ctx.reportIssue(
adjust(it.psiElement),"Message"
)
}
}
@Deprecated("Some text")
enum class
MyEnum(val s:String) {
ENTRY1(""),
}
57. ©2023, SonarSource S.A, Switzerland.
sonar-kotlin 2.0 released (24.06.2021)
● Improved 42 existing rules
● 10 new rules
● Semantic
● Detekt, AndroidLint, Ktlint rules sets
57
64. ©2023, SonarSource S.A, Switzerland.
64
import java.util.Random
import kotlin.random.Random as KotlinRandom
fun f(x: Long) {
val random = Random()
// val javaRandom = java.util.Random()
val kotlinRandom = kotlin.random. Random(0)
// Named import is used here
val kotlinRandom1 = KotlinRandom(1000L)
// x - parameter, no constraints
val kotlinRandomLong = kotlin.random. Random(x)
/*
val xx = 0L
val kotlinRandomLongZero = kotlin.random.Random(xx)
*/
}
65. ©2023, SonarSource S.A, Switzerland.
65
import java.util.Random
import kotlin.random.Random as KotlinRandom
fun f(x: Long) {
val random = Random()
// val javaRandom = java.util.Random()
val kotlinRandom = kotlin.random. Random(0)
// Named import is used here
val kotlinRandom1 = KotlinRandom(1000L)
// x - parameter, no constraints
val kotlinRandomLong = kotlin.random. Random(x)
/*
val xx = 0L
val kotlinRandomLongZero = kotlin.random.Random(xx)
*/
}
67. ©2023, SonarSource S.A, Switzerland.
foreach { comment ->
when (comment.parse()) {
is Success -> reportIssue()
is Error -> doNothing()
}
}
Commented out code rule
67
68. ©2023, SonarSource S.A, Switzerland.
// Hello World here
Commented out code rule
68
foreach { comment ->
when (comment.parse()) {
is Success -> reportIssue()
is Error -> doNothing()
}
}
69. ©2023, SonarSource S.A, Switzerland.
“World” could be an infix function
Commented out code rule
69
foreach { comment ->
when (comment.parse()) {
is Success -> reportIssue()
is Error -> doNothing()
}
}
// Hello World here
71. ©2023, SonarSource S.A, Switzerland.
foreach { comment ->
when (comment.parseAndSemantic()) {
is Success -> reportIssue()
is Error -> doNothing()
}
}
Commented out code rule
71
72. ©2023, SonarSource S.A, Switzerland.
foreach { comment ->
when (comment.parseAndSemantic()) {
is Success -> reportIssue()
is Error -> doNothing()
}
}
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣦⠀⢀⠀⠀
⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣿⣿⣷⣶⣦⣄⠀⠀⠀⠀⠀⠀⢰⡟⠈⠁⣰⣿⡗⠀
⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⢸⡇⠀⢠⡟⠀⠀⠀
⠀⠀⠀⣰⣿⣿⣿⣿⠟⠋⣉⣉⣉⠙⠻⣿⣿⣿⣷⡀⠀⠀⢸⡇⠀⢸⠃⠀⠀⠀
⠀⠀⢀⣿⣿⣿⣿⡇⢠⣿⣿⣿⢿⣿⣆⠘⣿⣿⣿⣷⡀⠀⢸⣧⠀⢸⡇⠀⠀⠀
⠀⠀⢸⣿⣿⣿⣿⡇⠸⣿⣿⣿⠆⣿⣿⡄⢸⣿⣿⣿⣧⠀⣼⣿⣿⣿⣇⠀⠀⠀
⠀⠀⠀⣿⣿⣿⣿⣿⣦⣈⣉⣁⣴⣿⣿⠁⣼⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⠀⠀⠀
⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⣰⣿⣿⣿⣿⡿⠀⣿⣿⣿⣿⡿⠀⠀⠀
⠀⠀⠀⠀⠈⠻⠿⣿⣿⣿⠿⠟⠋⠀⠾⢿⣿⣿⠿⠟⢁⣼⣿⣿⣿⣿⡇⠀⠀⠀
⠀⠀⠀⠀⣀⣤⣤⣤⣤⣤⣤⣶⣿⣷⣦⣤⣤⣤⣴⣾⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀
⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Commented out code rule
72
73. ©2023, SonarSource S.A, Switzerland.
Problems
● Compiler bugs
● Code with mistakes
● Code not in Kotlin
● Semantic - VERY SLOW
73
78. ©2023, SonarSource S.A, Switzerland.
1. Probably not a KDoc?
2. Heuristics: does it look like code?
- Keywords
- if(, for(, catch(, when(, apply{, …
- ++, ||, &&, +=, …
- Contains lambdas, var/val declarations, …
- camelCase, ends with } or {, …
3. Try to parse
Commented out code rule
78
80. ©2023, SonarSource S.A, Switzerland.
1. Collect potential refs
2. Group by importable name
3. Filter out referenced in KDocs
4. Filter out referenced in code
5. Filter out array access imports
6. Remaining imports are unused
Unnecessary Imports Rule
80
81. ©2023, SonarSource S.A, Switzerland.
1. Collect potential refs
2. Group by importable name
3. Filter out referenced in KDocs
4. Filter out referenced in code
5. FIlter out array access imports
6. Remaining imports are unused
Unnecessary Imports Rule
81
Companion
object
82. ©2023, SonarSource S.A, Switzerland.
1. Collect potential refs
2. Group by importable name
3. Filter out referenced in KDocs
4. Filter out referenced in code
5. FIlter out array access imports
6. Remaining imports are unused
import pkg.MyClass
fun foo() {
val c = MyClass.MY_CONSTANT
}
Unnecessary Imports Rule
82
83. ©2023, SonarSource S.A, Switzerland.
1. Collect potential refs
2. Group by importable name
3. Filter out referenced in KDocs
4. Filter out referenced in code
5. FIlter out array access imports
6. Remaining imports are unused
import pkg.MyClass
fun foo() {
val c = MyClass.MY_CONSTANT
}
Unnecessary Imports Rule
83
False positive!
84. ©2023, SonarSource S.A, Switzerland.
relevantImports.filter {
it.importedFqName != refName && !(
refName.shortName().asString() == "Companion"
&& it.importedFqName == refName.parent()
)
}
import pkg.MyClass
fun foo() {
val c = MyClass.MY_CONSTANT
}
Unnecessary Imports Rule
84
True negative
85. ©2023, SonarSource S.A, Switzerland.
relevantImports.filter {
it.importedFqName != refName && !(
refName.shortName().asString() == "Companion"
&& it.importedFqName == refName.parent()
)
}
import pkg.MyClass
fun foo() {
val c = MyClass.MY_CONSTANT
}
Unnecessary Imports Rule
85
True negative
Named
Companion
object
86. ©2023, SonarSource S.A, Switzerland.
relevantImports.filter {
it.importedFqName != refName && !(
refName.shortName().asString() == "Companion"
&& it.importedFqName == refName.parent()
)
}
class MyClass {
companion object Named {
fun myFun(): String {}
}
}
Unnecessary Imports Rule
86
87. ©2023, SonarSource S.A, Switzerland.
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
False positive!
relevantImports.filter {
it.importedFqName != refName && !(
refName.shortName().asString() == "Companion"
&& it.importedFqName == refName.parent()
)
}
Unnecessary Imports Rule
87
88. ©2023, SonarSource S.A, Switzerland.
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
True negative
relevantImports.filter {
it.importedFqName != refName && !(
refDescriptor.isCompanionObject()
&& it.importedFqName == refName.parent()
)
}
Unnecessary Imports Rule
88
89. ©2023, SonarSource S.A, Switzerland.
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
True negative
relevantImports.filter {
it.importedFqName != refName && !(
refDescriptor.isCompanionObject()
&& it.importedFqName == refName.parent()
)
}
Unnecessary Imports Rule
89
Unresolved
imports
90. ©2023, SonarSource S.A, Switzerland.
1. Collect potential refs
2. Group by importable name
3. Filter out referenced in KDocs
4. Filter out referenced in code
5. FIlter out array access imports
6. Remaining imports are unused
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
Unnecessary Imports Rule
90
91. ©2023, SonarSource S.A, Switzerland.
1. Collect potential refs
2. Group by importable name
3. Filter out referenced in KDocs
4. Filter out referenced in code
5. FIlter out array access imports
6. Remaining imports are unused
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
Unnecessary Imports Rule
91
pkg.MyClass not found
92. ©2023, SonarSource S.A, Switzerland.
1. Filter out unresolved imports
2. Collect potential refs
3. Group by importable name
4. Filter out referenced in KDocs
5. Filter out referenced in code
6. FIlter out array access imports
7. Remaining imports are unused
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
Unnecessary Imports Rule
92
True negative
93. ©2023, SonarSource S.A, Switzerland.
1. Filter out unresolved imports
2. Collect potential refs
3. Group by importable name
4. Filter out referenced in KDocs
5. Filter out referenced in code
6. FIlter out array access imports
7. Remaining imports are unused
import pkg.MyClass
fun foo() {
val c = MyClass.myFun()
}
Unnecessary Imports Rule
93
True negative
Delegates
94. ©2023, SonarSource S.A, Switzerland.
import operators.getValue
class My (delegate: D) {
val p by delegate
}
Unnecessary Imports Rule
94
95. ©2023, SonarSource S.A, Switzerland.
import operators.getValue
class My (delegate: D) {
val p by delegate
}
False positive!
Unnecessary Imports Rule
95
96. ©2023, SonarSource S.A, Switzerland.
if (noSemantic)
skip "getValue", "setValue", "provideDelegate"
else
val delegateRefs =
delegates.flatMap(::importableNames)
if (import in delegateRefs) skip
import operators.getValue
class My (delegate: D) {
val p by delegate
}
False positive!
Unnecessary Imports Rule
96
97. ©2023, SonarSource S.A, Switzerland.
if (noSemantic)
skip "getValue", "setValue", "provideDelegate"
else
val delegateImports =
delegates.flatMap(::importableNames)
if (import in delegateImports) skip
import operators.getValue
class My (delegate: D) {
val p by delegate
}
False positive!
Unnecessary Imports Rule
97
Overloaded
operators
98. ©2023, SonarSource S.A, Switzerland.
import operators.contains
import operators.invoke
class My (list: MyList) {
1 in list
2 !in list
list(0)
}
Unnecessary Imports Rule
98
99. ©2023, SonarSource S.A, Switzerland.
package operators
operator fun MyList.contains(i:
Int) = true
operator fun MyList.invoke(i:
Int) = this
Unnecessary Imports Rule
99
100. ©2023, SonarSource S.A, Switzerland.
import operators.contains
import operators.invoke
class My (list: MyList) {
1 in list
2 !in list
list(0)
}
Unnecessary Imports Rule
100
False positive!
101. ©2023, SonarSource S.A, Switzerland.
import operators.contains
import operators.invoke
class My (list: MyList) {
1 in list
2 !in list
list(0)
}
Unnecessary Imports Rule
101
False positive!
val opRefs = map { operation ->
operation.getReferencedName()
}
imports.filter { import !in opRefs }
102. ©2023, SonarSource S.A, Switzerland.
import operators.contains
import operators.invoke
class My (list: MyList) {
1 in list
2 !in list
list(0)
}
Unnecessary Imports Rule
102
True negative!
val calls by lazy { collectAllCalls() }
imports.filter { import ->
import.simpleName == “invoke”
}.foreach { import ->
if ( import in calls) skip
}
103. ©2023, SonarSource S.A, Switzerland.
import operators.contains
import operators.invoke
class My (list: MyList) {
1 in list
2 !in list
list(0)
}
Unnecessary Imports Rule
103
True negative!
val calls by lazy { collectAllCalls() }
imports.filter { import ->
import.simpleName == “invoke”
}.foreach { import ->
if ( import in calls) skip
}
Java 16+
104. ©2023, SonarSource S.A, Switzerland.
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.streams.toList
fun f(x: String): List<Path> {
return Files.list(x).toList()
}
Unnecessary Imports Rule
104
False positive!
105. ©2023, SonarSource S.A, Switzerland.
@SinceKotlin ("1.2")
public fun <T> Stream<T>.toList(): List<T> = ...
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.streams.toList
fun f(x: String): List<Path> {
return Files.list(x).toList()
}
Unnecessary Imports Rule
105
False positive!
106. ©2023, SonarSource S.A, Switzerland.
@SinceKotlin ("1.2")
public fun <T> Stream<T>.toList(): List<T> = ...
// Since Java 16
public interface Stream<T> {
default List<T> toList() {...}
}
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.streams.toList
fun f(x: String): List<Path> {
return Files.list(x).toList()
}
Unnecessary Imports Rule
106
False positive!
107. ©2023, SonarSource S.A, Switzerland.
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.streams.toList
fun f(x: String): List<Path> {
return Files.list(x).toList()
}
Unnecessary Imports Rule
107
False positive!
109. ©2023, SonarSource S.A, Switzerland.
Achievements
● Improved precision
● 133 rules
● 18 releases
● Rules for regex, security, coroutines
● Rules for Kotlin Gradle DSL
● Up-to-date external linters’ mappings
● Fast community handling
109
110. ©2023, SonarSource S.A, Switzerland.
Takeaways
● Sonar supports Kotlin
● Static analysis for Kotlin is fun
● Be active community member
● Rewrite if needed
110