Safe(r) Kotlin Code Static Analysis Tools for Kotlin Marvin Ramin - - PowerPoint PPT Presentation

safe r kotlin code
SMART_READER_LITE
LIVE PREVIEW

Safe(r) Kotlin Code Static Analysis Tools for Kotlin Marvin Ramin - - PowerPoint PPT Presentation

Safe(r) Kotlin Code Static Analysis Tools for Kotlin Marvin Ramin Static Analysis public class kotlinconf { var YEAR = 2018 fun Welcome(year: Int?) { println("Welcome to KotlinConf ${year!!}!") } } public class kotlinconf {


slide-1
SLIDE 1

Safe(r) Kotlin Code

Static Analysis Tools for Kotlin

Marvin Ramin

slide-2
SLIDE 2

Static Analysis

slide-3
SLIDE 3

public class kotlinconf { var YEAR = 2018 fun Welcome(year: Int?) { println("Welcome to KotlinConf ${year!!}!") } }

slide-4
SLIDE 4

public class kotlinconf { var YEAR = 2018 fun Welcome(year: Int?) { println("Welcome to KotlinConf ${year!!}!") } } public class kotlinconf { var YEAR = 2018 fun Welcome(year: Int?) { println("Welcome to KotlinConf ${year!!}!") } }

slide-5
SLIDE 5

https://developer.android.com/kotlin/

  • Modern. Expressive. Safe.
slide-6
SLIDE 6

"The introduction of Kotlin code in Android applications initially written in Java increases the quality of [...] 50% of the apps."

Mateus, B. G., & Martinez, M. (2018). An Empirical Study on Quality of Android Applications written in Kotlin language.

slide-7
SLIDE 7

Kotlin Compiler

slide-8
SLIDE 8

IntelliJ

slide-9
SLIDE 9

Settings → Editor → Inspections

slide-10
SLIDE 10

Hints during development

slide-11
SLIDE 11

Hints during development Support automatic fixing

slide-12
SLIDE 12

Hints during development Support automatic fixing Can be run from the CLI

slide-13
SLIDE 13

Bytecode Analysis

slide-14
SLIDE 14

Android Lint

slide-15
SLIDE 15

Most checks Android specific

slide-16
SLIDE 16

Most checks Android specific Checks run both on Java & Kotlin

slide-17
SLIDE 17

Most checks Android specific Checks run both on Java & Kotlin Support for non-Android projects

slide-18
SLIDE 18

Android Project

./gradlew lint

slide-19
SLIDE 19

Android Project Kotlin

./gradlew lint apply plugin com.android.lint ./gradlew lint

slide-20
SLIDE 20

lintOptions { abortOnError true ignoreWarnings false lintConfig file("config.xml") ... }

slide-21
SLIDE 21

ktlint detekt

slide-22
SLIDE 22

ktlint

slide-23
SLIDE 23

No config required ktlint

slide-24
SLIDE 24

No config required Focussed on code style ktlint

slide-25
SLIDE 25

No config required Focussed on code style Built in formatter ktlint

slide-26
SLIDE 26

ktlint

ktlint

slide-27
SLIDE 27

ktlint

ktlint "**/src/*.kt"

slide-28
SLIDE 28

ktlint

ktlint "**/src/*.kt" --format

slide-29
SLIDE 29

detekt

slide-30
SLIDE 30

Rules can be configured in detail detekt

slide-31
SLIDE 31

Rules can be configured in detail Many different rule sets detekt

slide-32
SLIDE 32

Rules can be configured in detail Many different rule sets Wraps ktlint detekt

slide-33
SLIDE 33

detekt

java -jar detekt.jar --input src/

slide-34
SLIDE 34

detekt

java -jar detekt.jar --input src/

  • -config config.yml
slide-35
SLIDE 35

detekt

plugins { id "io.gitlab.arturbosch.detekt" version "[version]" } detekt { input = file("src/main/kotlin") }

slide-36
SLIDE 36

Gradle Plugins

slide-37
SLIDE 37

> ./gradlew check

slide-38
SLIDE 38

> ./gradlew check ...

slide-39
SLIDE 39

> ./gradlew check ... ...

slide-40
SLIDE 40

> ./gradlew check ... ... Static Analysis suite finished Found 8572 issues

slide-41
SLIDE 41

Using Static Analysis results

slide-42
SLIDE 42

Run Static Analysis on every (CI) build

slide-43
SLIDE 43

<issues format="5" by="lint 3.3.0-alpha12"> <issue id="LambdaLast" severity="Error" message="Functional interface parameters should be last to improve Kotlin interoperability" category="Interoperability:Kotlin Interoperability" priority="6" summary="Lambda Parameters Last" explanation="To improve calling this code from Kotlin parameter types eligible for SAM conversion should be last." url="https://android.github.io/kotlin-guides/interop.html#lambda-parameters-last" errorLine1=" public int doSomething(Action action, int count) {" errorLine2=" ~~~~~~~~~"> <location file="/.../src/main/java/com/example/Test.java" line="4" column="43"/> </issue> </issues>

slide-44
SLIDE 44

<issues format="5" by="lint 3.3.0-alpha12"> <issue id="LambdaLast" severity="Error" message="Functional interface parameters should be last to improve Kotlin interoperability" category="Interoperability:Kotlin Interoperability" priority="6" summary="Lambda Parameters Last" explanation="To improve calling this code from Kotlin parameter types eligible for SAM conversion should be last." url="https://android.github.io/kotlin-guides/interop.html#lambda-parameters-last" errorLine1=" public int doSomething(Action action, int count) {" errorLine2=" ~~~~~~~~~"> <location file="/.../src/main/java/com/example/Test.java" line="4" column="43"/> </issue> </issues>

slide-45
SLIDE 45

<issues format="5" by="lint 3.3.0-alpha12"> <issue id="LambdaLast" severity="Error" message="Functional interface parameters should be last to improve Kotlin interoperability" category="Interoperability:Kotlin Interoperability" priority="6" summary="Lambda Parameters Last" explanation="To improve calling this code from Kotlin parameter types eligible for SAM conversion should be last." url="https://android.github.io/kotlin-guides/interop.html#lambda-parameters-last" errorLine1=" public int doSomething(Action action, int count) {" errorLine2=" ~~~~~~~~~"> <location file="/.../src/main/java/com/example/Test.java" line="4" column="43"/> </issue> </issues>

slide-46
SLIDE 46

Issues

slide-47
SLIDE 47
slide-48
SLIDE 48
slide-49
SLIDE 49
slide-50
SLIDE 50

Warning vs. Error

slide-51
SLIDE 51

Warning vs. Error

slide-52
SLIDE 52

Baseline

slide-53
SLIDE 53

Adapt tools to your best-practices

slide-54
SLIDE 54

Enable/Disable Rules

slide-55
SLIDE 55

Enable/Disable Rules Configure rules

slide-56
SLIDE 56

Enable/Disable Rules Configure rules Baseline

slide-57
SLIDE 57

@Suppress("RuleToSuppress")

slide-58
SLIDE 58

@SuppressLint("KotlinPropertyAccess")

slide-59
SLIDE 59

@SuppressLint("KotlinPropertyAccess") // Ignore because of reason

slide-60
SLIDE 60

Inner workings of a Static Analysis tool

slide-61
SLIDE 61

Tool

slide-62
SLIDE 62

AST

slide-63
SLIDE 63

UAST

slide-64
SLIDE 64

class KotlinConf { const val YEAR = 2018 fun welcome(year: Int) { println("Welcome to KotlinConf $year!") } }

slide-65
SLIDE 65

class KotlinConf { const val YEAR = 2018 fun welcome(year: Int) { println("Welcome to KotlinConf $year!") } }

UFile (package = )

slide-66
SLIDE 66

class KotlinConf { const val YEAR = 2018 fun welcome(year: Int) { println("Welcome to KotlinConf $year!") } }

UFile (package = ) UClass (name = KotlinConf)

slide-67
SLIDE 67

class KotlinConf { const val YEAR = 2018 fun welcome(year: Int) { println("Welcome to KotlinConf $year!") } }

UFile (package = ) UClass (name = KotlinConf) UField (name = YEAR) UAnnotation ULiteralExpression (value = 2018)

slide-68
SLIDE 68

class KotlinConf { const val YEAR = 2018 fun welcome(year: Int) { println("Welcome to KotlinConf $year!") } }

UFile (package = ) UClass (name = KotlinConf) UField (name = YEAR) UAnnotation ULiteralExpression (value = 2018) UAnnotationMethod (name = welcome) UParameter (name = year) UAnnotation

slide-69
SLIDE 69

class KotlinConf { const val YEAR = 2018 fun welcome(year: Int) { println("Welcome to KotlinConf $year!") } }

UFile (package = ) UClass (name = KotlinConf) UField (name = YEAR) UAnnotation ULiteralExpression (value = 2018) UAnnotationMethod (name = welcome) UParameter (name = year) UAnnotation UBlockExpression UCallExpression UIdentifier (Identifier (println)) USimpleNameReferenceExpression ULiteralExpression

slide-70
SLIDE 70

IssueRegistry Lint

slide-71
SLIDE 71

List<Issue> IssueRegistry

slide-72
SLIDE 72

public final class Issue ... { private final String mId; private final String mBriefDescription; private final String mExplanation; private final Category mCategory; private final int mPriority; private final Severity mSeverity; private Object mMoreInfoUrls; private boolean mEnabledByDefault = true; private Implementation mImplementation; ... }

slide-73
SLIDE 73

<issues format="5" by="lint 3.3.0-alpha12"> <issue id="LambdaLast" summary="Lambda Parameters Last" message="..." explanation="..." category="Kotlin Interoperability" priority="6" severity="Error" url="..." errorLine1="..." <location file="Test.java" line="4" column="43"/> </issue> </issues>

public final class Issue ... { private final String mId; private final String mBriefDescription; private final String mExplanation; private final Category mCategory; private final int mPriority; private final Severity mSeverity; private Object mMoreInfoUrls; private boolean mEnabledByDefault = true; private Implementation mImplementation; ... }

slide-74
SLIDE 74

Detector AST

slide-75
SLIDE 75

Detector AST

slide-76
SLIDE 76

Detector AST

slide-77
SLIDE 77

import android.util.Log import com.something.Else class Something {

slide-78
SLIDE 78

import android.util.Log import com.something.Else class Something {

slide-79
SLIDE 79

0: UFile 1: UImportStatement 2: UImportStatement 4: UClass import android.util.Log import com.something.Else class Something {

slide-80
SLIDE 80

0: UFile 1: UImportStatement 2: UImportStatement 4: UClass import android.util.Log import com.something.Else class Something {

slide-81
SLIDE 81

0: UFile 1: UImportStatement 2: UImportStatement 4: UClass import android.util.Log import com.something.Else class Something {

slide-82
SLIDE 82

class ImportVisitor(private val context: JavaContext) : UElementHandler() {

  • verride fun visitImportStatement(import: UImportStatement) {

val resolved = import.resolve() if (resolved !is PsiClass) { return } if (resolved.qualifiedName == "android.util.Log") { context.report(ISSUE, import, context.getLocation(import), "...") } } }

slide-83
SLIDE 83

class ImportVisitor(private val context: JavaContext) : UElementHandler() {

  • verride fun visitImportStatement(import: UImportStatement) {

val resolved = import.resolve() if (resolved !is PsiClass) { return } if (resolved.qualifiedName == "android.util.Log") { context.report(ISSUE, import, context.getLocation(import), "...") } } }

slide-84
SLIDE 84

class ImportVisitor(private val context: JavaContext) : UElementHandler() {

  • verride fun visitImportStatement(import: UImportStatement) {

val resolved = import.resolve() if (resolved !is PsiClass) { return } if (resolved.qualifiedName == "android.util.Log") { context.report(ISSUE, import, context.getLocation(import), "...") } } }

slide-85
SLIDE 85

class ImportVisitor(private val context: JavaContext) : UElementHandler() {

  • verride fun visitImportStatement(import: UImportStatement) {

val resolved = import.resolve() if (resolved !is PsiClass) { return } if (resolved.qualifiedName == "android.util.Log") { context.report(ISSUE, import, context.getLocation(import), "...") } } }

slide-86
SLIDE 86

class ImportVisitor(private val context: JavaContext) : UElementHandler() {

  • verride fun visitImportStatement(import: UImportStatement) {

val resolved = import.resolve() if (resolved !is PsiClass) { return } if (resolved.qualifiedName == "android.util.Log") { context.report(ISSUE, import, context.getLocation(import), "...") } } }

slide-87
SLIDE 87

android.util.Log

slide-88
SLIDE 88

android.util.Log "android.util.Log"

slide-89
SLIDE 89

class AndroidLogDetector : Detector(), Detector.UastScanner {

  • verride fun getApplicableUastTypes(): List<Class<out UElement>>? {

return listOf(UImportStatement::class.java) } }

slide-90
SLIDE 90

class AndroidLogDetector : Detector(), Detector.UastScanner {

  • verride fun getApplicableUastTypes(): List<Class<out UElement>>? {

return listOf(UImportStatement::class.java) } }

slide-91
SLIDE 91

class AndroidLogDetector : Detector(), Detector.UastScanner {

  • verride fun getApplicableUastTypes(): List<Class<out UElement>>? {

return listOf(UImportStatement::class.java) } }

slide-92
SLIDE 92

Reports Findings

slide-93
SLIDE 93

HTML XML Reports

slide-94
SLIDE 94

Extensibility

slide-95
SLIDE 95
slide-96
SLIDE 96

Detector AST

slide-97
SLIDE 97

Detector AST

slide-98
SLIDE 98

package com.example class CustomIssueRegistry : IssueRegistry() {

  • verride fun getIssues() = listOf(ISSUE_ANDROID_LOG)

}

slide-99
SLIDE 99

jar { manifest { attributes("Lint-Registry-v2": "com.example.CustomIssueRegistry") } }

slide-100
SLIDE 100

Write your custom Issue & Detector

slide-101
SLIDE 101

val kotlinFile = kt(""" |import android.util.Log |import java.util.List | |class KotlinConf { |} """.trimMargin()) @Test fun testKotlinFile() { lint() .files(kotlinFile) .issues(ISSUE_ANDROID_LOG) .run() .expectCount(1) }

slide-102
SLIDE 102

val javaFile = java(""" |import android.util.Log; |import java.util.List; | |public class KotlinConf { |} """.trimMargin()) @Test fun testJavaFile() { lint() .files(javaFile) .issues(ISSUE_ANDROID_LOG) .run() .expectCount(1) }

slide-103
SLIDE 103

dependencies { lintChecks project(":lint") }

slide-104
SLIDE 104

Create IssueRegistry

slide-105
SLIDE 105

Create IssueRegistry Manifest file

slide-106
SLIDE 106

Create IssueRegistry Manifest file Implement Issue & Detector

slide-107
SLIDE 107

Create IssueRegistry Manifest file Implement Issue & Detector Test your Rule

slide-108
SLIDE 108

Create IssueRegistry Manifest file Implement Issue & Detector Test your Rule Tell Lint about your custom Lint JAR

slide-109
SLIDE 109

Libraries publishing Lint rules

slide-110
SLIDE 110

Libraries publishing Lint rules IntelliJ support

slide-111
SLIDE 111

Libraries publishing Lint rules IntelliJ support Quick fixes

slide-112
SLIDE 112

groups.google.com/forum/#!forum/lint-dev

slide-113
SLIDE 113

Make your Kotlin code safer

slide-114
SLIDE 114

Make your Kotlin code safer Coherent codebase

slide-115
SLIDE 115

Make your Kotlin code safer Coherent codebase Encode your own best-practices

slide-116
SLIDE 116

Thank you!

twitter.com/@Mauin github.com/Mauin