Safe Initialization of Objects Fengyun Liu LAMP, EPFL June 26, - - PowerPoint PPT Presentation

safe initialization of objects
SMART_READER_LITE
LIVE PREVIEW

Safe Initialization of Objects Fengyun Liu LAMP, EPFL June 26, - - PowerPoint PPT Presentation

Safe Initialization of Objects Fengyun Liu LAMP, EPFL June 26, 2020 Challenges Reasoning Problem Evaluation Inference Model A 50-year old problem class Hello { 1 val message = "Hello, " + name 2 val name = "world"


slide-1
SLIDE 1

Safe Initialization of Objects

Fengyun Liu

LAMP, EPFL June 26, 2020

slide-2
SLIDE 2

Problem Challenges Reasoning Evaluation Inference Model

slide-3
SLIDE 3

A 50-year old problem

1

class Hello {

2

val message = "Hello, " + name

3

val name = "world"

4

}

5 6

println(new Hello().message))

1 / 63

slide-4
SLIDE 4

A 50-year old problem

1

class Hello {

2

val message = "Hello, " + name

3

val name = "world"

4

}

5 6

println(new Hello().message))

At runtime: Hello, null

1 / 63

slide-5
SLIDE 5

A 50-year old problem

1

class Hello {

2

val message = "Hello, " + name

3

val name = "world"

4

}

5 6

println(new Hello().message))

At runtime: Hello, null

1 / 63

slide-6
SLIDE 6

Status quo

All mainstream object-oriented languages still suffer from the problem, including C++, Java, Scala, C#, Kotlin, ...

2 / 63

slide-7
SLIDE 7

Why the problem is important?

Not only are partially-constructed objects a source

  • f consternation for everyday programmers, they are

also a challenge for language designers wanting to provide guarantees around invariants, immutability and concurrency-safety, and non-nullability. — Joe Duffy, “On partially-constructed objects”

http://joeduffyblog.com/2010/06/27/on-partiallyconstructed-objects/ 3 / 63

slide-8
SLIDE 8

Is it only a problem of OOP?

1

let rec even n = n = 0 || odd (x - 1)

2

and odd n = n = 1 || even (x - 1)

3

and flag = odd 3

The latest OCaml compiler rejects the code!

4 / 63

slide-9
SLIDE 9

Is it only a problem of strict languages?

The following Haskell code loops forever:

1

a = if b then 10 else 20

2

b = a >= 10

3 4

main = putStrLn (show a)

Initialization errors are disguised as non-termination!

5 / 63

slide-10
SLIDE 10

Problem Challenges Reasoning Evaluation Inference Model

slide-11
SLIDE 11

Can we disallow usage of this before initialization?

6 / 63

slide-12
SLIDE 12

Can we disallow usage of this before initialization?

Requirement 1. Call methods and access fields on this.

Gil et al. (2009) report that over 8% constructors include method calls on this.

6 / 63

slide-13
SLIDE 13

Can we disallow usage of this before initialization?

Requirement 1. Call methods and access fields on this.

Gil et al. (2009) report that over 8% constructors include method calls on this.

Requirement 2. Creation of cyclic data structures.

1

class Parent { val child: Child = new Child(this) }

2

class Child(parent: Parent)

6 / 63

slide-14
SLIDE 14

Challenge 1: virtual method calls

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

10 11

new RemoteFile("...")

7 / 63

slide-15
SLIDE 15

Challenge 1: virtual method calls

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

10 11

new RemoteFile("...") null pointer exception at runtime

7 / 63

slide-16
SLIDE 16

Challenge 1: virtual method calls

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

10 11

new RemoteFile("...")

virtual call this.name

7 / 63

slide-17
SLIDE 17

Challenge 1: virtual method calls

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

10 11

new RemoteFile("...")

access uninitialized this.localFile

7 / 63

slide-18
SLIDE 18

Challenge 2: aliasing

1

class Knot {

2

val a = this

3

val b = a.c + 5

4

val c = 10

5

}

aliasing of this

8 / 63

slide-19
SLIDE 19

Challenge 2: aliasing

1

class Knot {

2

val a = this

3

val b = a.c + 5

4

val c = 10

5

}

aliasing of this

a.c is not initialized

8 / 63

slide-20
SLIDE 20

Challenge 3: traits

1

trait TA { val x = "EPFL" }

2

trait TB { def x: String; val n = x.length }

3 4

class Foo extends TA with TB

5

class Bar extends TB with TA

6 7

new Foo // ok

8

new Bar // error null pointer exception

9 / 63

slide-21
SLIDE 21

Challenge 3: traits

1

trait TA { val x = "EPFL" }

2

trait TB { def x: String; val n = x.length }

3 4

class Foo extends TA with TB

5

class Bar extends TB with TA

6 7

new Foo // ok

8

new Bar // error null pointer exception

x is initialized after n

9 / 63

slide-22
SLIDE 22

Challenge 4: first-class functions

1

class Rec {

2

val even = (n: Int) => n == 0 || odd(n - 1)

3

even(6)

4

val odd = (n: Int) => n == 1 || even(n - 1)

5

even(6)

6

}

10 / 63

slide-23
SLIDE 23

Challenge 4: first-class functions

1

class Rec {

2

val even = (n: Int) => n == 0 || odd(n - 1)

3

even(6)

4

val odd = (n: Int) => n == 1 || even(n - 1)

5

even(6)

6

} null pointer exception

10 / 63

slide-24
SLIDE 24

Challenge 4: first-class functions

1

class Rec {

2

val even = (n: Int) => n == 0 || odd(n - 1)

3

even(6)

4

val odd = (n: Int) => n == 1 || even(n - 1)

5

even(6)

6

}

it is OK to call even here

null pointer exception

10 / 63

slide-25
SLIDE 25

Challenge 5: inner classes

1

class Trees {

2

class ValDef { counter += 1 }

3

class EmptyValDef extends ValDef

4 5

val theEmptyValDef = new EmptyValDef

6

private var counter = 0

7

}

11 / 63

slide-26
SLIDE 26

Challenge 5: inner classes

1

class Trees {

2

class ValDef { counter += 1 }

3

class EmptyValDef extends ValDef

4 5

val theEmptyValDef = new EmptyValDef

6

private var counter = 0

7

}

11 / 63

slide-27
SLIDE 27

Challenge 5: inner classes

1

class Trees {

2

class ValDef { counter += 1 }

3

class EmptyValDef extends ValDef

4 5

val theEmptyValDef = new EmptyValDef

6

private var counter = 0

7

} counter is not yet initialized

11 / 63

slide-28
SLIDE 28

Challenge 6: properties

1

class A {

2

val a = "Bonjour"

3

val b = a.size

4

}

5 6

class B extends A {

7

  • verride val a = "Hi"

8

}

9 10

new B null pointer exception

12 / 63

slide-29
SLIDE 29

Challenge 6: properties

1

class A {

2

val a = "Bonjour"

3

val b = a.size

4

}

5 6

class B extends A {

7

  • verride val a = "Hi"

8

}

9 10

new B null pointer exception

this.a is a dynamic method call!

12 / 63

slide-30
SLIDE 30

Challenge 6: properties

1

class A {

2

val a = "Bonjour"

3

val b = a.size

4

}

5 6

class B extends A {

7

  • verride val a = "Hi"

8

}

9 10

new B null pointer exception

this.a is a dynamic method call!

12 / 63

slide-31
SLIDE 31

Engineering Challenges

Initialization

Safety Simplicity Performance Usability Expressivity

13 / 63

slide-32
SLIDE 32

Problem Challenges Reasoning Evaluation Inference Model

slide-33
SLIDE 33

A golden idea: local reasoning

If a constructor is called with only transitively initialized arguments, so is the resulting object. If the receiver and arguments of a method call are transitively initialized, so is the result.

Summers, Alexander J. and Peter M¨

  • uller. “Freedom before commitment.” OOPSLA ’11

14 / 63

slide-34
SLIDE 34

Three pillars of local reasoning

local reasoning

weak monotonicity stackability scopability

15 / 63

slide-35
SLIDE 35

A small language

P ∈ Program ::= (C, D) C ∈ Class ::= class C(ˆ f :T) { F M } F ∈ Field ::= var f :T = e e ∈ Exp ::= x | this | e.f | e.m(e) | new C(e) | e.f = e; e M ∈ Method ::= def m(x:T) : T = e S, T, U ∈ Type ::= C

1

class Parent { var child: Child = new Child(this) }

2

class Child(parent: Parent)

16 / 63

slide-36
SLIDE 36

Semantics

e (σ, ρ, ψ) = (l, σ′)

17 / 63

slide-37
SLIDE 37

Semantics

e (σ, ρ, ψ) = (l, σ′)

σ: the heap before evaluation ρ: the environment ψ: value for this σ′: the heap after evaluation l: the resulting value

17 / 63

slide-38
SLIDE 38

Semantics - Examples

x (σ, ρ, ψ) = (ρ(x), σ) this (σ, ρ, ψ) = (ψ, σ) e.f (σ, ρ, ψ) = (ω(f ), σ1) where (l0, σ1) = e (σ, ρ, ψ) and ( , ω) = σ1(l0)

18 / 63

slide-39
SLIDE 39

Reachability

An object l′ is reachable from l in the heap σ, written σ l l′, is defined below: l ∈ dom(σ) σ l l σ l0 l1 ( , ω) = σ(l1) ∃ f . ω(f ) = l2 l2 ∈ dom(σ) σ l0 l2

19 / 63

slide-40
SLIDE 40

Abstractions: cold, warm and hot

Cold A cold object may have uninitialized fields Warm A warm object has all its fields initialized Hot A hot object only reaches warm objects

20 / 63

slide-41
SLIDE 41

Formal definition

Definition (Cold) σ l : cold

  • l ∈ dom(σ)

Definition (Warm) σ l : warm

  • ∃(C, ω) = σ(l) fields(C) ⊆ dom(ω)

Definition (Hot) σ l : hot

  • l ∈ dom(σ)
  • ∀l′.σ l l′ =

⇒ σ l′ : warm

21 / 63

slide-42
SLIDE 42

Weak Monotonicity

Definition (Weak Monotonicity) σ σ′

  • ∀l ∈ dom(σ).

(C, ω) = σ(l) = ⇒ (C, ω′) = σ′(l) dom(ω) ⊆ dom(ω′) Theorem (Weak Monotonicity) e (σ, ρ, ψ) = (l, σ′) = ⇒ σ σ′

22 / 63

slide-43
SLIDE 43

Stackability, formally

Definition (Stacking) σ ≪ σ′

  • ∀l ∈ dom(σ′). σ′ l : warm
  • l ∈ dom(σ)

Theorem (Stackability) e (σ, ρ, ψ) = (l, σ′) = ⇒ σ ≪ σ′

23 / 63

slide-44
SLIDE 44

Stackability, visually

1 2 4 3 time 5 6 1 2 4 3 5 6 stacked non-stacked

slide-45
SLIDE 45

Scopability, formally

Definition (Scoping) (σ, L) ⋖ (σ′, L′)

  • ∀l ∈ dom(σ).

σ′ L′ l = ⇒ σ L l Theorem (Scopability) e (σ, ρ, ψ) = (l, σ′) = ⇒ (σ, codom(ρ) ∪ { ψ }) ⋖ (σ′, { l })

25 / 63

slide-46
SLIDE 46

Scopability, visually

1 7 2 4 3 5 6 1 7 2 4 3 5 6 8 9

˟

Heap σ Heap σ’ ⟦e⟧(σ, ø, 2)

the result value

26 / 63

slide-47
SLIDE 47

Scopability, visually

1 7 2 4 3 5 6 1 7 2 4 3 5 6 8 9

˟

Heap σ Heap σ’ ⟦e⟧(σ, ø, 2)

the result value σ′ 8 7 7 ∈ dom(σ) σ 2 7 (σ, 2) ⋖ (σ′, 8)

26 / 63

slide-48
SLIDE 48

Local reasoning theorem

e (σ, ρ, ψ) = (l, σ′) σ { ψ } ∪ codom(ρ) : hot σ′ l : hot

27 / 63

slide-49
SLIDE 49

Problem Challenges Reasoning Evaluation Inference Model

slide-50
SLIDE 50

The basic model

µ ::= cold | warm | hot T ::= C µ hot ⊑ warm ⊑ cold

28 / 63

slide-51
SLIDE 51

Typestate polymorphism

1

class C {

2

// ...

3

def g(): Int = 100

4

}

g can be called for any initialization state of this

29 / 63

slide-52
SLIDE 52

Typestate polymorphism

1

class C {

2

// ...

3

def g(): Int = 100

4

}

g can be called for any initialization state of this

  • how to express the polymorphism in the system?
  • how to avoid syntactic overhead for polymorphism?

29 / 63

slide-53
SLIDE 53

Flow-sensitivity and typestate polymorphism

In a flow-sensitive system, we need to resort to parametric polymorphic because a method has to represent the typestates of this both before and after the method call.

1

class C {

2

def g(): Int = 100 // g: ∀M.M M

3

}

Qi, Xin and Andrew C. Myers. “Masked types for sound object initialization.” POPL ’09 30 / 63

slide-54
SLIDE 54

Flow-insensitivity and typestate polymorphism

In a flow-insensitive system, we may resort to subtyping polymorphism to support typestate polymorphism.

1

class C {

2

@cold def g(): Int = 100

3

}

Summers, Alexander J. and Peter M¨

  • uller. “Freedom before commitment.” OOPSLA ’11

31 / 63

slide-55
SLIDE 55

Strong monotonicity

Hot/warm objects continue to be hot/warm: σ σ′

  • ∀l ∈ dom(σ). σ l : µ =

⇒ σ′ l : µ

F¨ ahndrich, Manuel and K. Rustan M. Leino. “Heap Monotonic Typestate.” (2003). 32 / 63

slide-56
SLIDE 56

Perfect monotonicity

Hot/warm fields continue to be hot/warm: σ σ′

  • ∀l ∈ dom(σ).

(C, ω) = σ(l) = ⇒ (C, ω′) = σ′(l)

  • ∀f ∈ dom(ω).σ ω(f ) : µ =

⇒ σ′ ω′(f ) : µ

33 / 63

slide-57
SLIDE 57

Typing rules - T-New

Ti = constrType(C) Γ; T ⊢ ei : C µi

i

C µi

i

<: Ti µ = (⊔ µi) ⊓ warm Γ; T ⊢ new C(e) : C µ (T-New) local reasoning and stackability

34 / 63

slide-58
SLIDE 58

Typing rules - T-Invoke

Γ; T ⊢ e : C µ0 (µm, Ti, Dµr ) = methodType(C, m) µ0 ⊑ µm Γ; T ⊢ ei : Dµi

i

Dµi

i

<: Ti µ = (⊔ µi = hot)?hot : µr Γ; T ⊢ e.m(e) : Dµ (T-Invoke) local reasoning typestate polymorphism

35 / 63

slide-59
SLIDE 59

Typing rules - T-Block

Γ; T ⊢ e1.f : C µ Γ; T ⊢ e2 : C hot Γ; T ⊢ e : T1 Γ; T ⊢ e1.f = e2; e : T1 (T-Block) perfect monotonicity

36 / 63

slide-60
SLIDE 60

Typing rules - selection

Γ; T ⊢ e : Dhot C µ = fieldType(D, f ) Γ; T ⊢ e.f : C hot (T-SelHot) Γ; T ⊢ e : Dwarm U = fieldType(D, f ) Γ; T ⊢ e.f : U (T-SelWarm)

37 / 63

slide-61
SLIDE 61

Authority, flow-insensitivity and strong updates

The meta-theory depends on authority: ∀l ∈ dom(Σ).Σ(l) = C µ = ⇒ Σ′(l) = C µ Σ Σ′

38 / 63

slide-62
SLIDE 62

Authority, flow-insensitivity and strong updates

The meta-theory depends on authority: ∀l ∈ dom(Σ).Σ(l) = C µ = ⇒ Σ′(l) = C µ Σ Σ′ In a flow-insensitive system without aliasing tracking, it is

  • nly safe to perform strong updates of initialization states

via the outstanding alias this in a local flow inside the constructor.

38 / 63

slide-63
SLIDE 63

Design principles of initialization

Authority Each field should have a unique location in the constructor where it is officially initialized. Stack- ability All fields of an object should be initial- ized at the end of the class constructor. Mono- tonicity Initialization states cannot be reversed. Scop- ability Access to uninitialized objects should be controlled by static scoping.

39 / 63

slide-64
SLIDE 64

Principled design of constructors

To align with the principles, we advocate

  • class parameters
  • mandatory field initializers

40 / 63

slide-65
SLIDE 65

Principled design of constructors

To align with the principles, we advocate

  • class parameters
  • mandatory field initializers

1

class RemoteDoc(url: String) {

2

val localFile: String = url.hashCode

3

def name: String = localFile

4

}

40 / 63

slide-66
SLIDE 66

Languages in industry

language

class C(f:Int) var f:Int = e

year Java

  • 1995

C#

  • 2000

D

  • 2001

Scala

  • 2003

Ceylon

  • 2011

Dart

  • 2011

Kotlin

  • 2011

TypeScript

  • 2012

Crystal

  • 2014

Swift

  • 2014

41 / 63

slide-67
SLIDE 67

Problem Challenges Reasoning Evaluation Inference Model

slide-68
SLIDE 68

What is wrong with type-based approach?

To be honest, the reason this approach has likely not yet seen widespread use is that the cost is not commensurate with the benefit. ... For systems programmers, this makes sense. For many other programmers, it would be useless ceremony with no perceived value. — Joe Duffy, “On partially-constructed objects”

http://joeduffyblog.com/2010/06/27/on-partiallyconstructed-objects/ 42 / 63

slide-69
SLIDE 69

Drawbacks of type-based approach

  • Annotation overhead
  • Inadequate to handle traits, inner classes, properties
  • Does not handle inheritance well
  • Difficult to integrate in compilers

43 / 63

slide-70
SLIDE 70

Type-and-effect systems

Γ, x : S ⊢ t : T ! ǫ Γ ⊢ λx:S.t : S

ǫ

− → T ! ∅ (T-Abs) Γ ⊢ t1 : S

ǫ3

− → T ! ǫ1 Γ ⊢ t2 : S ! ǫ2 Γ ⊢ t1 t2 : T ! ǫ1 ∪ ǫ2 ∪ ǫ3 (T-App)

Lucassen, J.M., Gifford, D.K. (1988). Polymorphic effect systems. POPL ’88. 44 / 63

slide-71
SLIDE 71

Potentials and effects

Potentials represent aliasing information of objects possibly under initialization. β ::= this | warm[C] | cold π ::= β | π.f | π.m

45 / 63

slide-72
SLIDE 72

Potentials and effects

Potentials represent aliasing information of objects possibly under initialization. β ::= this | warm[C] | cold π ::= β | π.f | π.m Effects represent operations on objects possibly under initialization. φ ::= π.f ! | π.m♦ | π↑

45 / 63

slide-73
SLIDE 73

Potentials and effects

Potentials represent aliasing information of objects possibly under initialization. β ::= this | warm[C] | cold π ::= β | π.f | π.m Effects represent operations on objects possibly under initialization. φ ::= π.f ! | π.m♦ | π↑ Γ; C ⊢ e : D ! (Φ, Π) If Π = ∅, the value of e must be hot.

45 / 63

slide-74
SLIDE 74

Expression Typing - Block

Γ; C ⊢ e0 : E0 ! (Φ0, Π0) E1 = fieldType(E0, f ) Γ; C ⊢ e1 : E1 ! (Φ1, Π1) Γ; C ⊢ e2 : E2 ! (Φ2, Π2) Φ = Φ0 ∪ Φ1 ∪ Φ2 ∪ Π1↑ Γ; C ⊢ e0.f = e1; e2 : E2 ! (Φ, Π2) (T-Block) ensures that e1 is hot

46 / 63

slide-75
SLIDE 75

Example – field access effect

1

class C {

2

var x: Int = this.y

3

var y: Int = 10

4

}

effects: { this.y! }

47 / 63

slide-76
SLIDE 76

Example – potentials

1

class C {

2

var a = this

3

var b = this.a

4

}

48 / 63

slide-77
SLIDE 77

Example – potentials

1

class C {

2

var a = this

3

var b = this.a

4

}

potentials: { this } effects: ∅

48 / 63

slide-78
SLIDE 78

Example – potentials

1

class C {

2

var a = this

3

var b = this.a

4

}

potentials: { this } effects: ∅ potentials: { this.a } effects: { this.a! }

48 / 63

slide-79
SLIDE 79

Example – potentials

1

class C {

2

var a = this

3

var b = this.a

4

}

potentials: { this } effects: ∅ potentials: { this.a } effects: { this.a! }

48 / 63

slide-80
SLIDE 80

Example – method call effects

1

class C {

2

var a = this.m()

3

var b = this

4

def m() = this.b

5

}

49 / 63

slide-81
SLIDE 81

Example – method call effects

1

class C {

2

var a = this.m()

3

var b = this

4

def m() = this.b

5

}

potentials: { this.m } effects: { this.m♦ }

49 / 63

slide-82
SLIDE 82

Example – method call effects

1

class C {

2

var a = this.m()

3

var b = this

4

def m() = this.b

5

}

potentials: { this.m } effects: { this.m♦ } potentials: { this.b } effects: { this.b! }

49 / 63

slide-83
SLIDE 83

Example – method call effects

1

class C {

2

var a = this.m()

3

var b = this

4

def m() = this.b

5

}

potentials: { this.m } effects: { this.m♦ } potentials: { this.b } effects: { this.b! }

49 / 63

slide-84
SLIDE 84

Example – promotion effects

1

class C(fun: C => Int) {

2

var x: Int = fun(this)

3

}

50 / 63

slide-85
SLIDE 85

Example – promotion effects

1

class C(fun: C => Int) {

2

var x: Int = fun(this)

3

}

potentials: ∅ effects: { this↑ }

50 / 63

slide-86
SLIDE 86

Example – promotion effects

1

class C(fun: C => Int) {

2

var x: Int = fun(this)

3

}

potentials: ∅ effects: { this↑ } Promotion is the same as requiring a value to be hot.

50 / 63

slide-87
SLIDE 87

Two-phase checking

1

class C {

2

var a: Int = h()

3

def h(): Int = g()

4

def g(): Int = h()

5

}

51 / 63

slide-88
SLIDE 88

Two-phase checking

1

class C {

2

var a: Int = h()

3

def h(): Int = g()

4

def g(): Int = h()

5

}

First phase method effects potentials h { this.g♦ } { this.g } g { this.h♦ } { this.h }

51 / 63

slide-89
SLIDE 89

Two-phase checking

1

class C {

2

var a: Int = h()

3

def h(): Int = g()

4

def g(): Int = h()

5

}

First phase method effects potentials h { this.g♦ } { this.g } g { this.h♦ } { this.h } Second phase fixpoint({ this.h♦ }) = { this.g♦, this.h♦ }

51 / 63

slide-90
SLIDE 90

Two-phase checking

1

class C {

2

var a: Int = h()

3

def h(): Int = g()

4

def g(): Int = h()

5

}

First phase method effects potentials h { this.g♦ } { this.g } g { this.h♦ } { this.h } Second phase fixpoint({ this.h♦ }) = { this.g♦, this.h♦ }

51 / 63

slide-91
SLIDE 91

Cyclic data structures

1

class Parent {

2

val child: Child = new Child(this)

3

child.name // OK

4

}

5 6

class Child( parent: Parent @cold ) {

7

val name = "Jack"

8

}

52 / 63

slide-92
SLIDE 92

Cyclic data structures

1

class Parent {

2

val child: Child = new Child(this)

3

child.name // OK

4

}

5 6

class Child( parent: Parent @cold ) {

7

val name = "Jack"

8

}

potentials: { warm[Child] }

52 / 63

slide-93
SLIDE 93

Full-construction analysis

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

53 / 63

slide-94
SLIDE 94

Full-construction analysis

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

Analysis entry point: primary constructor of concrete classes

53 / 63

slide-95
SLIDE 95

Full-construction analysis

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

follows super constructor calls

53 / 63

slide-96
SLIDE 96

Full-construction analysis

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

}

resolve virtual call this.name

53 / 63

slide-97
SLIDE 97

Full-construction analysis

1

abstract class AbstractFile {

2

def name: String

3

val extension: String = name.substring(4)

4

}

5 6

class RemoteFile(url:String) extends AbstractFile {

7

val localFile: String = url.hashCode

8

def name: String = localFile

9

} access uninitialized this.localFile

53 / 63

slide-98
SLIDE 98

Functions

Function potential: π ::= . . . | Fun(Φ, Π)

54 / 63

slide-99
SLIDE 99

Functions

Function potential: π ::= . . . | Fun(Φ, Π) effects when called potentials of return

54 / 63

slide-100
SLIDE 100

Functions

Function potential: π ::= . . . | Fun(Φ, Π) effects when called potentials of return

1

class Rec {

2

val even = (n: Int) => n == 0 || odd(n - 1)

3

val odd = (n: Int) => n == 1 || even(n - 1)

4

val flag: Boolean = odd(6)

5

}

54 / 63

slide-101
SLIDE 101

Functions

Function potential: π ::= . . . | Fun(Φ, Π) effects when called potentials of return

1

class Rec {

2

val even = (n: Int) => n == 0 || odd(n - 1)

3

val odd = (n: Int) => n == 1 || even(n - 1)

4

val flag: Boolean = odd(6)

5

}

54 / 63

slide-102
SLIDE 102

Inner classes

β ::= this | cold | warm(C, π) π ::= β | π.f | π.m | π.outer(C) φ ::= π↑ | π.f ! | π.m♦ | π.init(C)

55 / 63

slide-103
SLIDE 103

Inner classes

β ::= this | cold | warm(C, π) π ::= β | π.f | π.m | π.outer(C) φ ::= π↑ | π.f ! | π.m♦ | π.init(C)

  • uter potential π

potential of outer constructor effect

55 / 63

slide-104
SLIDE 104

Inner classes

β ::= this | cold | warm(C, π) π ::= β | π.f | π.m | π.outer(C) φ ::= π↑ | π.f ! | π.m♦ | π.init(C)

  • uter potential π

potential of outer constructor effect

1

class C {

2

private var counter = 0

3

class D { counter += 1 }

4

val d = new D

5

}

55 / 63

slide-105
SLIDE 105

Inner classes

β ::= this | cold | warm(C, π) π ::= β | π.f | π.m | π.outer(C) φ ::= π↑ | π.f ! | π.m♦ | π.init(C)

  • uter potential π

potential of outer constructor effect

1

class C {

2

private var counter = 0

3

class D { counter += 1 }

4

val d = new D

5

}

warm(D, this).init(D) warm(D, this).outer(D).counter! this.counter!

this.outer(D).counter!

55 / 63

slide-106
SLIDE 106

Termination - limit length

1

class C {

2

var a = this.g

3

def g = this.g.g

4

}

56 / 63

slide-107
SLIDE 107

Termination - limit length

1

class C {

2

var a = this.g

3

def g = this.g.g

4

}

Infinite Loop this.g♦ = ⇒ this.g.g♦ = ⇒ this.g.g.g♦ = ⇒ this.g.g.g.g♦ = ⇒ . . .

56 / 63

slide-108
SLIDE 108

Termination - limit length

1

class C {

2

var a = this.g

3

def g = this.g.g

4

}

Infinite Loop this.g♦ = ⇒ this.g.g♦ = ⇒ this.g.g.g♦ = ⇒ this.g.g.g.g♦ = ⇒ . . . Limit length this.g♦ = ⇒ this.g.g♦ = ⇒ this.g.g↑ = ⇒ . . . = ⇒

56 / 63

slide-109
SLIDE 109

Termination - widening (1)

1

class B {

2

class C extends B

3

val c: C = new C

4

}

57 / 63

slide-110
SLIDE 110

Termination - widening (1)

1

class B {

2

class C extends B

3

val c: C = new C

4

}

warm(C, this).init(C) = ⇒ warm(C, warm(C, this)).init(C) = ⇒ warm(C, warm(C, warm(C, this))).init(C) = ⇒ . . .

57 / 63

slide-111
SLIDE 111

Termination - widening (2)

1

class B {

2

class C extends B

3

val c: C = new C

4

}

It is safe to widen warm(C, π) to cold: warm(C, this).init(C) = ⇒ warm(C, warm(C, cold)).init(C) = ⇒ warm(C, warm(C, cold)).init(C) = ⇒ ✔

58 / 63

slide-112
SLIDE 112

Problem Challenges Reasoning Evaluation Inference Model

slide-113
SLIDE 113

Implementation

Implemented in Scala 3 compiler, under the option

  • Ycheck-init.
  • Lazy summarization of methods
  • Takes advantage of TASTy for separate compilation
  • Zero changes to core type system

59 / 63

slide-114
SLIDE 114

Debuggability

1

class Greeting {

2

val message: String = this.welcome()

3

val name: String = "Jack"

4

def welcome() = "Hello, " + name // error

5

}

60 / 63

slide-115
SLIDE 115

Debuggability

1

class Greeting {

2

val message: String = this.welcome()

3

val name: String = "Jack"

4

def welcome() = "Hello, " + name // error

5

}

1

  • - Error: code/greeting.scala:3:6 ------------------------------

2

3 | val name: String = "Jack"

3

| ^

4

|Access non-initialized field name. Calling trace:

5

| -> val message: String = this.welcome() [ greeting.scala:2 ]

6

|

  • > def welcome()

= "Hello, " + name [ greeting.scala:4 ]

60 / 63

slide-116
SLIDE 116

Experiment

We run the checker for 0.6 million real-world Scala code.

  • It finds bugs in Dotty, ScalaTest and Scala stdlib
  • It reports 0.6 warnings/KLOC

2 4 6 8 10 d

  • t

t y i n t e n t a l g e b r a s t d L i b 2 1 3 s c a l a c h e c k s c a l a t e s t s c a l a X m l s c

  • p

t s c a l a p s q u a n t s b e t t e r f i l e s S c a l a P B s h a p e l e s s e f f p i s c

  • n

f i g m u n i t Warnings/KLOC 0.7 39.5 4.7 0.6 1.1 0.4 0.1 0.0 5.4 0.0 0.0 0.3 0.8 0.5 0.6 1.1

61 / 63

slide-117
SLIDE 117

Performance

20 40 60 80 100 120 d

  • t

t y i n t e n t a l g e b r a s t d L i b 2 1 3 s c a l a c h e c k s c a l a t e s t s c a l a X m l s c

  • p

t s c a l a p s q u a n t s b e t t e r f i l e s S c a l a P B s h a p e l e s s e f f p i s c

  • n

f i g m u n i t Percent (%)

The percentage of time for initialization check relative to the whole compilation time.

62 / 63

slide-118
SLIDE 118

Conclusion

  • modular understanding of local reasoning
  • monotonicity, stackability, scopability, authority
  • abstractions of cold, warm, hot
  • type-and-effect inference system with potentials
  • implementation in Scala 3 compiler

63 / 63

slide-119
SLIDE 119

Problem Challenges Reasoning Evaluation Inference Model

slide-120
SLIDE 120

Proof of scopability (1)

Definition (Scoping Preservation) σ1 σ2 ⋖ L

  • ∀σ0, L0, L1.

(σ0, L0) ⋖ (σ1, L)

  • (σ0, L0) ⋖ (σ1, L1)

= ⇒ (σ0, L0) ⋖ (σ2, L1) Without scoping preservation, in an evaluation e (σ, ρ, ψ) = (l, σ′), we cannot even conclude that (σ, L) ⋖ (σ′, L), where L = codom(ρ) ∪ { ψ }.

slide-121
SLIDE 121

Proof of scopability (2)

1 7 2 4 3 5 6 1 7 2 4 3 5 6 8 9

˟

Heap σ’ Heap σ’’

1 7 2 4 3 5

⟦e⟧(σ, ø, 4) Heap σ ⟦e.m()⟧(σ, ø, 4)

˟

⟦e’⟧(σ’, ø, 2)

˟

  • (σ, 1) ⋖ (σ′, 6) is not preserved in σ′ σ′′
  • (σ, 4) ⋖ (σ′, 6) is preserved in σ′ σ′′
slide-122
SLIDE 122

Proof of scopability (3)

Theorem (Scopability) If e (σ, ρ, ψ) = (l, σ′), then we have

  • (σ, codom(ρ) ∪ { ψ }) ⋖ (σ′, l)
  • σ σ′ ⋖ codom(ρ) ∪ { ψ }
slide-123
SLIDE 123

Local reasoning lemma

Lemma (Local Reasoning) (σ, L) ⋖ (σ′, L′) σ ≪ σ′ σ σ′ σ L : hot σ′ L′ : hot Proof. Let’s consider a reachable object l from L′, i.e. σ′ L′ l. Depending on whether l ∈ dom(σ), there are two cases.

  • Case l /

∈ dom(σ). Use the fact that σ ≪ σ′, we know σ′ l : warm.

  • Case l ∈ dom(σ).

Use the fact that (σ, L) ⋖ (σ′, L′), we have σ L l. From the premise σ L : hot, we have σ l : warm. From σ σ′, we have σ′ l : warm. In both cases, we have σ′ l : warm, by definition we have σ′ L′ : hot.

slide-124
SLIDE 124

Meta-theory

1

class C {

2

// ...

3

var x: D @warm = e

4 5

var y: Int = 10

6

}

slide-125
SLIDE 125

Meta-theory

1

class C {

2

// ...

3

var x: D @warm = e

4 5

var y: Int = 10

6

}

Σ0(ψ) = C µ

slide-126
SLIDE 126

Meta-theory

1

class C {

2

// ...

3

var x: D @warm = e

4 5

var y: Int = 10

6

}

Σ0(ψ) = C µ Σ1(ψ) = C hot

slide-127
SLIDE 127

Meta-theory

1

class C {

2

// ...

3

var x: D @warm = e

4 5

var y: Int = 10

6

}

Σ0(ψ) = C µ Σ1(ψ) = C hot Σ2(ψ) = C hot breaks typing, as by I.H. we

  • nly know the value of e

is warm.

slide-128
SLIDE 128

Challenging examples (1)

1

class RecType(parentExp: RecType => Type) {

2

val parent = parentExp(this)

3

}

slide-129
SLIDE 129

Challenging examples (2)

1

  • bject Foo {

2

case class Student(name: String, age: Int)

3

call(Student("Jack", 30) // currently a warning

4

}

slide-130
SLIDE 130

Challenging examples (3)

1

def foo(x: => Int) = new A(x)

2

class A(init: => Int)

3

class Foo {

4

val a: A = foo(b) // warning!

5

val b: Int = 100

6

}

slide-131
SLIDE 131

Fragile base class

1

class Base {

2

def g(): String = "hello"

3

}

4 5

class Foo extends Base {

6

val a = this.g()

7

}

8 9

class Bar extends Base {

10

val b: String = "b"

11

  • verride def g(): String = this.b

12

}

slide-132
SLIDE 132

Strong and weak updates

Suppose that Σ maps addresses to a lattice L: Σ : Loc ⇀ L Weak update Σ(l) ⊑ Σ′(l) Strong update Σ(l) ⊑ Σ′(l)

slide-133
SLIDE 133

Scala Puzzle

What does the following Scala code prints at runtime?

1

class A(n: Int) { println(n) }

2 3

class B(val m: Int) extends A(m) {

4

println(m)

5

}

6 7

new B(10) { override val m = 10 }

slide-134
SLIDE 134

Scala Puzzle

What does the following Scala code prints at runtime?

1

class A(n: Int) { println(n) }

2 3

class B(val m: Int) extends A(m) {

4

println(m)

5

}

6 7

new B(10) { override val m = 10 }

Answer: 10, 0

slide-135
SLIDE 135

Swift

The Swift compiler rejects the following code:

1

class Position {

2

var x, y: Int

3

var f: () -> Int

4

init() {

5

x = 4

6

y = x * x // OK

7

f = { () -> Int in self.y } // error

8

}

9

}

Overly restrictive and inconsistent!