Semantic

class Semantic
Companion
object
class Object
trait Matchable
class Any

Type members

Classlikes

sealed abstract class Addr extends Value
case object Cold extends Value

An object with unknown initialization status

An object with unknown initialization status

case class Config(thisV: Value, expr: Tree)

Interpreter configuration

Interpreter configuration

The (abstract) interpreter can be seen as a push-down automaton that transits between the configurations where the stack is the implicit call stack of the meta-language.

It's important that the configuration is finite for the analysis to terminate.

For soundness, we need to compute fixed point of the cache, which maps configuration to evaluation result.

Thanks to heap monotonicity, heap is not part of the configuration. Which also avoid computing fix-point on the cache, as the cache is immutable.

case class Fun(expr: Tree, thisV: Addr, klass: ClassSymbol) extends Value

A function value

A function value

object Heap

Abstract heap stores abstract objects

Abstract heap stores abstract objects

As in the OOPSLA paper, the abstract heap is monotonistic.

case object Hot extends Value

A transitively initialized object

A transitively initialized object

case class Objekt(klass: ClassSymbol, fields: Map[Symbol, Value], outers: Map[ClassSymbol, Value])

The abstract object which stores value about its fields and immediate outers.

The abstract object which stores value about its fields and immediate outers.

Semantically it suffices to store the outer for klass. We cache other outers for performance reasons.

Note: Object is NOT a value.

object Promoted
case class RefSet(refs: List[Fun | Addr]) extends Value

A value which represents a set of addresses

A value which represents a set of addresses

It comes from if expressions.

case class Result(value: Value, errors: Seq[Error])

Result of abstract interpretation

Result of abstract interpretation

case class ThisRef(klass: ClassSymbol) extends Addr

A reference to the object under initialization pointed by this

A reference to the object under initialization pointed by this

object Trace
sealed abstract class Value

Abstract values

Abstract values

Value = Hot | Cold | Warm | ThisRef | Fun | RefSet

           Cold
  ┌──────►  ▲   ◄──┐  ◄────┐
  │         │      │       │
  │         │      │       │

ThisRef(C) │ │ │ ▲ │ │ │ │ Warm(D) Fun RefSet │ ▲ ▲ ▲ │ │ │ │ Warm(C) │ │ │ ▲ │ │ │ │ │ │ │ └─────────┴──────┴───────┘ Hot

The most important ordering is the following:

 Hot ⊑ Warm(C) ⊑ ThisRef(C) ⊑ Cold

The diagram above does not reflect relationship between RefSet and other values. RefSet represents a set of values which could be ThisRef, Warm or Fun. The following ordering applies for RefSet:

   R_a ⊑ R_b if R_a ⊆ R_b

   V ⊑ R if V ∈ R
case class Warm(klass: ClassSymbol, outer: Value) extends Addr

An object with all fields initialized but reaches objects under initialization

An object with all fields initialized but reaches objects under initialization

We need to restrict nesting levels of outer to finitize the domain.

Types

Cache used to terminate the analysis

Cache used to terminate the analysis

A finitary configuration is not enough for the analysis to terminate. We need to use cache to let the interpreter "know" that it can terminate.

For performance reasons we use curried key.

Note: It's tempting to use location of trees as key. That should be avoided as a template may have the same location as its single statement body. Macros may also create incorrect locations.

type Contextual[T] = (Context, Trace, Promoted) => T

The state that threads through the interpreter

The state that threads through the interpreter

type Heap = Heap
type Trace = Trace

Value members

Concrete methods

def cases(expr: Tree, thisV: Addr, klass: ClassSymbol): () => Result

Handles the evaluation of different expressions

Handles the evaluation of different expressions

Note: Recursive call should go to eval instead of cases.

def cases(tp: Type, thisV: Addr, klass: ClassSymbol, source: Tree): () => Result

Handle semantics of leaf nodes

Handle semantics of leaf nodes

def checkTermUsage(tpt: Tree, thisV: Addr, klass: ClassSymbol): () => List[Error]

Check that path in path-dependent types are initialized

Check that path in path-dependent types are initialized

This is intended to avoid type soundness issues in Dotty.

def eval(expr: Tree, thisV: Addr, klass: ClassSymbol, cacheResult: Boolean): () => Result

Evaluate an expression with the given value for this in a given class klass

Evaluate an expression with the given value for this in a given class klass

Note that klass might be a super class of the object referred by thisV. The parameter klass is needed for this resolution. Consider the following code:

class A { A.this class B extends A { A.this } }

As can be seen above, the meaning of the expression A.this depends on where it is located.

This method only handles cache logic and delegates the work to cases.

def eval(exprs: List[Tree], thisV: Addr, klass: ClassSymbol): () => List[Result]

Evaluate a list of expressions

Evaluate a list of expressions

def evalArgs(args: List[Arg], thisV: Addr, klass: ClassSymbol): () => List[Error]

Evaluate arguments of methods

Evaluate arguments of methods

def init(tpl: Template, thisV: Addr, klass: ClassSymbol): () => Result

Initialize part of an abstract object in klass of the inheritance chain

Initialize part of an abstract object in klass of the inheritance chain

def outerValue(tref: TypeRef, thisV: Addr, klass: ClassSymbol, source: Tree): () => Result

Compute the outer value that correspond to tref.prefix

Compute the outer value that correspond to tref.prefix

def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, source: Tree): () => Value

Resolve C.this that appear in klass

Resolve C.this that appear in klass

def trace(using t: Trace): Trace

Concrete fields

val cache: Cache
val heap: Heap

Extensions

Extensions

extension (a: Value)
def join(b: Value): Value
extension (value: Addr)

Can the method call on value be ignored?

Can the method call on value be ignored?

Note: assume overriding resolution has been performed.

extension (value: Value)

Can we promote the value by checking the extrinsic values?

Can we promote the value by checking the extrinsic values?

The extrinsic values are environment values, e.g. outers for Warm and thisV captured in functions.

This is a fast track for early promotion of values.

def promote(msg: String, source: Tree): () => List[Error]

Promotion of values to hot

Promotion of values to hot

extension (value: Value)
def call(meth: Symbol, superType: Type, source: Tree, needResolve: Boolean): () => Result
def instantiate(klass: ClassSymbol, ctor: Symbol, source: Tree): () => Result

Handle a new expression new p.C where p is abstracted by value

Handle a new expression new p.C where p is abstracted by value

def select(field: Symbol, source: Tree, needResolve: Boolean): () => Result
extension (values: Seq[Value])
def join: Value
extension (warm: Warm)
def tryPromote(msg: String, source: Tree): () => List[Error]

Try early promotion of warm objects

Try early promotion of warm objects

Promotion is expensive and should only be performed for small classes.

  1. for each concrete method m of the warm object: call the method and promote the result

  2. for each concrete field f of the warm object: promote the field value

If the object contains nested classes as members, the checker simply reports a warning to avoid expensive checks.

TODO: we need to revisit whether this is needed once we make the system more flexible in other dimentions: e.g. leak to methods or constructors, or use ownership for creating cold data structures.