tmpa-2015: kotlin: from null dereference to smart casts

32
Mikhail Glukhikh mailto: [email protected] JetBrains, Senior Software Developer

Upload: iosif-itkin

Post on 17-Feb-2017

1.880 views

Category:

Science


2 download

TRANSCRIPT

Mikhail Glukhikh mailto: [email protected]

JetBrains, Senior Software Developer

Developed by JetBrains since 2011

Open-Source since 2012

Targets JVM and JavaScript ◦ Java 6 / 7 / 8 / Android

Statically Typed

Object-Oriented ◦ + Functional features

◦ + Procedural features

Java- and Scala-Compatible

Plugins for IDEA and Eclipse

Kotlin: from null dereference to

smart casts 2

14 minor releases passed in 2012 – 2015

1.0 beta released (Nov 2015)

1.0 coming soon

Lines of Code ◦ ~250 KLOC in Kotlin project itself

◦ ~250 KLOC in other JetBrains projects

◦ ~1000 KLOC in non-JetBrains projects on GitHub

Kotlin: from null dereference to smart casts 3

Working in Kotlin project since March 2015

Before: author of various research static analysis tools

Also: associate professor in SPbPU

Kotlin: from null dereference to smart casts 4

Full Java interoperability Safer than Java ◦ Null safety ◦ No raw types ◦ Invariant arrays ◦ Read-only collections

More concise and expressive than Java ◦ Type inference ◦ Higher-order functions (closures) ◦ Extension functions ◦ Class delegation

Compiler is at least as fast as Java Simpler than Scala

Kotlin: from null dereference to smart casts 5

fun main(args: Array<String>) {

val name = if (args.isNotEmpty()) args[0]

else "Kotlin"

println("Hello, $name")

}

val / var name : Type or = …

fun name(a: TypeA, b: TypeB): Type { … } or = …

Array<>, String

if … else … expressions

"Hello, $name" or even "Hello, ${my.name}"

Kotlin: from null dereference to smart casts 6

No primitive types, everything is an object ◦ Standard classes Int, Long, Char, Boolean, String, etc. ◦ Unit as the single-value type

fun main(args: Array<String>): Unit { … }

◦ Nothing as the type that never exists fun todo(): Nothing = throw AssertionError()

◦ null has the type of Nothing? ◦ Any as the very base type for everything (not-null)

fun equals(other: Any?): Boolean = …

Interfaces and classes Nullable and not-null types ◦ E.g. String? and String, Any? and Any, etc.

Kotlin: from null dereference to smart casts 7

interface Food interface Animal : Food { enum class Kind { HERBIVORE,OMNIVORE,CARNIVORE } val kind: Kind fun eat(food: Food): Boolean fun die() = println("It dies") } class Lion : Animal { override val kind = Animal.Kind.CARNIVORE override fun eat(food: Food) = if (food is Animal) { food.die() true } else false } }

Kotlin: from null dereference to smart casts 8

fun String.greet() = println("Hello, $this")

fun greet(args: List<String>) = args.filter { it.isNotEmpty() } .sortBy { it } .forEach { it.greet() }

Kotlin: from null dereference to smart casts 9

sealed class Tree { object Empty: Tree() class Leaf(val x: Int): Tree() class Node(val left: Tree, val right: Tree): Tree() fun max(): Int = when (this) { Empty -> Int.MIN_VALUE is Leaf -> this.x is Node -> Math.max(this.left.max(), this.right.max()) } }

Kotlin: from null dereference to smart casts 10

Distinct nullable types Type? and not-null types Type

Null checks and smart casts fun foo(s: String?) {

// Error: Only safe or not-null asserted // calls allowed

println(s.length)

// Ok, smart cast to String

if (s != null) println(s.length)

if (s == null) return

println(s.length) // Smart cast also here

}

Kotlin: from null dereference to

smart casts 11

Distinct nullable types Type? and not-null types Type

Safe calls, not-null asserted calls fun foo(s: String?) {

// Error: Only safe or not-null asserted // calls allowed

println(s.length)

// Ok, safe call (s.length or null)

println(s?.length)

// Ok, not-null assertion (unsafe!)

println(s!!.length)

}

Kotlin: from null dereference to

smart casts 12

Distinct nullable types Type? and not-null types Type

Safe call with Elvis operator fun foo(s: String?) {

// Error: Only safe or // not-null asserted calls allowed

println(s.length)

// Ok, safe call + Elvis (s.length or NOTHING)

println(s?.length ?: "NOTHING")

}

Kotlin: from null dereference to smart casts 13

Flexible Types like Type! ◦ Type in Java Type! in Kotlin

◦ Type! is not a syntax, just notation

◦ Assignable to both Type and Type?

◦ Dot is applicable to a variable of Type!

◦ Annotations are taken into account

@NotNull Type in Java Type in Kotlin

Kotlin: from null dereference to smart casts 14

is or !is to check, as or as? to convert class StringHolder(val s: String) {

override fun equals(o: Any?): Boolean {

// Error: unresolved reference (o.s)

return s == o.s

if (o !is StringHolder) return false

// Ok, smart cast to StringHolder

return s == o.s

}

}

Kotlin: from null dereference to smart casts 15

is or !is to check, as or as? to convert class StringHolder(val s: String) {

override fun equals(o: Any?): Boolean {

// Ok, unsafe (ClassCastException)

return s == (o as StringHolder).s

// Ok, safe

return s == (o as? StringHolder)?.s

}

}

Kotlin: from null dereference to smart casts 16

Top-down analysis on AST

For each relevant expression E, possible types of E: T(E) are determined at each AST node

T(null) = Nothing?

Example fun foo(s: String?) { // T(s) = {String?} = {String,Nothing?} if (s != null) { // T(s) = {Nothing?} println(s.length) } }

Kotlin: from null dereference to smart casts 17

For local values, function parameters, special this variable

For member or top-level values if: ◦ They are not overridable and have no custom getter AND

They are private or internal (not a part of public API) OR They are protected or public, and used in the same

compilation module when declared class My { private val a: String? // safe public val b: String? // same module only internal open val c: String? // unsafe private val d: String? // unsafe get() = null }

Kotlin: from null dereference to smart casts 18

For local variables if ◦ a smart cast is performed not in the loop which changes the

variable after the smart cast

fun foo() { var s: String? // T(s) = String U Nothing? s = "abc" // T(s) = String s.length // smart cast while (s != "x") { // Unsafe: changed later in the loop if (s.length > 0) s = s.substring(1) else s = null } }

Kotlin: from null dereference to smart casts 19

For local variables if ◦ a smart cast is performed in the same function when the

variable is declared, not inside some closure ◦ no closure that changes the variable exists before the

location of the smart cast

fun indexOfMax(a: IntArray): Int? { var maxI: Int? = null a.forEachIndexed { i, value -> if (maxI == null || value >= a[maxI]) { maxI = i } } return maxI }

Kotlin: from null dereference to smart casts 20

val / var x: Type : T(x) = Type

val / var x: Type? : T(x) = Type U Nothing?

Statement: Tin(x) Tout(x)

If: Tin(x) Ttrue(x), Tfalse(x)

Merge: Tin1(x), T in2(x) Tout(x)

Kotlin: from null dereference to

smart casts 21

if (x != null) or while (x != null) Ttrue (x) = T in(x) \ {Nothing?} Tfalse(x) = T in(x) & {Nothing?}

if (x is Something) Ttrue (x) = T in(x) & {Something} Tfalse(x) = T in(x) \ {Something}

if (x == y) Ttrue (x,y) = T in(x) & T in(y) Tfalse(x) = T in(x) \ T in(y) Tfalse(y) = T in(y) \ T in(x)

Kotlin: from null dereference to smart casts 22

if (A && B) True: TrueA & TrueB False: FalseA U FalseB

if (A || B) True: TrueA U TrueB False: FalseA & FalseB

Example: if (x == null && y != null) Ttrue (x) = T in(x) & {Nothing?} Ttrue (y) = T in(y) \ {Nothing?} Tfalse(x) = T in(x) Tfalse(y) = T in(y)

Kotlin: from null dereference to smart casts 23

x!! Tout(x) = T in(x) \ {Nothing?}

x as Something Tout(x) = T in(x) & {Something}

x?.foo(...) Tfoo(x) = T in(x) \ {Nothing?} Tout(x) = T in(x)

Kotlin: from null dereference to smart casts 24

if (…) { … } else { … } Out = In1 U In2

when (…) { … -> …; … -> …; else -> …;} Out = In1 U In2 U … U In(else)

Kotlin: from null dereference to smart casts 25

Assignment: x = y or initialization: var x = y ◦ Tout(x) = Tin(y)

Initialization with given type: var x: Type = y ◦ Tout(x) = { Type }

Before entering a loop ◦ For all X changed inside: T out(X) = Type(X) data class Node(val s: String, val next: Node?) fun foo(node: Node) { var current: Node? // T = {Node,Nothing?} current = node // T = {Node} while (true) { // T = {Node,Nothing?} println(current.s) // Error if (current == null) break println(current.s) // Ok: T = {Node} current = current.next // T = {Node,Nothing?} } // T = {Nothing?} }

Kotlin: from null dereference to smart casts 26

Classic data flow analysis on CFG ◦ Variable initialization analysis

◦ Variable usage analysis

◦ Code reachability analysis

◦ …

Kotlin: from null dereference to smart casts 27

Total smart casts: 8500 Unsafe operations ◦ !! : 1500 (mostly provoked by implicit conventions) ◦ as : 1500

Checks: ◦ != null : 2000 ◦ == null : 1000

Safe operations: ◦ ?. : 2500 ◦ as? : 1000 ◦ ?: : 2500

Kotlin: from null dereference to smart casts 28

More precise loop analysis

More precise closure analysis

Extra helper annotations like @Pure or @Consistent for mutable properties

Kotlin: from null dereference to smart casts 29

http://kotlinlang.org – language information, API reference, tutorials, koans, installation instructions, community, etc.

http://github.com/JetBrains/Kotlin – main compiler & plugin repository

http://blog.jetbrains.com/kotlin/ – Kotlin blog

Questions?

Kotlin: from null dereference to smart casts 30

Java \ Kotlin ◦ Checked exceptions ◦ Primitive types that are not classes ◦ Static members ◦ Non-private fields

Kotlin \ Java ◦ Extension functions ◦ Null-safety, smart casts ◦ String templates ◦ Properties ◦ Primary constructors ◦ Class delegation ◦ Type inference ◦ Singletons ◦ Operator overloading, infix functions ◦ …

Kotlin: from null dereference to smart casts 31

Scala \ Kotlin ◦ Implicit conversions ◦ Overridable type members ◦ Existential types ◦ Structural types ◦ Value types ◦ Yield operator ◦ Actors ◦ …

Kotlin \ Scala ◦ Zero overhead null safety ◦ Smart casts ◦ Class delegation

Kotlin: from null dereference to smart casts 32