Objects

dotty.tools.dotc.transform.init.Objects
object Objects

Check initialization safety of static objects

The problem is illustrated by the example below:

class Foo(val opposite: Foo)
case object A extends Foo(B)     // A -> B
case object B extends Foo(A)     // B -> A

In the code above, the initialization of object A depends on B and vice versa. There is no correct way to initialize the code above. The current checker issues a warning for the code above.

At the high-level, the analysis has the following characteristics:

  1. The check enforces the principle of "initialization-time irrelevance", which means that the time when an object is initialized should not change program semantics. For that purpose, it enforces the following rule:

    The initialization of a static object should not directly or indirectly read or write
    mutable state of another static object.
    

    This principle not only put initialization of static objects on a solid foundation, but also avoids whole-program analysis.

  2. The design is based on the concept of "cold aliasing" --- a cold alias may not be actively used during initialization, i.e., it's forbidden to call methods or access fields of a cold alias. Method arguments are cold aliases by default unless specified to be sensitive. Method parameters captured in lambdas or inner classes are always cold aliases.

  3. It is inter-procedural and flow-sensitive.

  4. It is object-sensitive by default and parameter-sensitive on-demand.

  5. The check is modular in the sense that each object is checked separately and there is no whole-program analysis. However, the check is not modular in terms of project boundaries.

Attributes

Graph
Supertypes
class Object
trait Matchable
class Any
Self type
Objects.type

Members list

Type members

Classlikes

object Cache

Cache used to terminate the check

Cache used to terminate the check

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
Cache.type
case object Cold extends Value

A cold alias which should not be used during initialization.

A cold alias which should not be used during initialization.

Cold is not ValueElement since RefSet containing Cold is equivalent to Cold

Attributes

Supertypes
trait Singleton
trait Product
trait Mirror
trait Serializable
trait Product
trait Equals
class Value
class Object
trait Matchable
class Any
Show all
Self type
Cold.type
object Env

Environment for parameters

Environment for parameters

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
Env.type
case class Fun(code: Tree, thisV: ThisValue, klass: ClassSymbol, env: Data) extends ValueElement

Represents a lambda expression

Represents a lambda expression

Attributes

Supertypes
trait Serializable
trait Product
trait Equals
class ValueElement
class Value
class Object
trait Matchable
class Any
Show all
opaque object Heap

Abstract heap for mutable fields

Abstract heap for mutable fields

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
Heap.type
case class ObjectRef(klass: ClassSymbol) extends Ref

A reference to a static object

A reference to a static object

Attributes

Supertypes
trait Serializable
trait Product
trait Equals
class Ref
class ValueElement
class Value
class Object
trait Matchable
class Any
Show all
case class OfArray(owner: ClassSymbol, regions: Data)(using ctx: Context) extends ValueElement

Represents arrays.

Represents arrays.

Note that the 2nd parameter block does not take part in the definition of equality.

Different arrays are distinguished by the context. Currently the default context is the static object whose initialization triggers the creation of the array.

In the future, it is possible that we introduce a mechanism for end-users to mark the context.

Value parameters

owner

The static object whose initialization creates the array.

Attributes

Supertypes
trait Serializable
trait Product
trait Equals
class ValueElement
class Value
class Object
trait Matchable
class Any
Show all
case class OfClass extends Ref

Represents values that are instances of the specified class.

Represents values that are instances of the specified class.

Note that the 2nd parameter block does not take part in the definition of equality.

Attributes

Companion
object
Supertypes
trait Serializable
trait Product
trait Equals
class Ref
class ValueElement
class Value
class Object
trait Matchable
class Any
Show all
object OfClass

Attributes

Companion
class
Supertypes
class Object
trait Matchable
class Any
Self type
OfClass.type
sealed abstract class Ref(valsMap: Map[Symbol, Value], varsMap: Map[Symbol, Addr], outersMap: Map[ClassSymbol, Value]) extends ValueElement

A reference caches the values for outers and immutable fields.

A reference caches the values for outers and immutable fields.

Attributes

Supertypes
class ValueElement
class Value
class Object
trait Matchable
class Any
Known subtypes
class ObjectRef
class OfClass
opaque object Regions

Region context for mutable states

Region context for mutable states

By default, the region context is empty.

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
Regions.type
opaque object Returns

Handle return statements in methods and non-local returns in functions.

Handle return statements in methods and non-local returns in functions.

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
Returns.type
object State

Checking state

Checking state

Attributes

Supertypes
class Object
trait Matchable
class Any
Self type
State.type
sealed abstract class Value

Syntax for the data structure abstraction used in abstract domain:

Syntax for the data structure abstraction used in abstract domain:

ve ::= ObjectRef(class) // global object | OfClass(class, vs[outer], ctor, args, env) // instance of a class | OfArray(object[owner], regions) | Fun(..., env) // value elements that can be contained in ValueSet vs ::= ValueSet(ve) // set of abstract values Bottom ::= ValueSet(Empty) val ::= ve | Cold | vs // all possible abstract values in domain Ref ::= ObjectRef | OfClass // values that represent a reference to some (global or instance) object ThisValue ::= Ref | Cold // possible values for 'this'

refMap = Ref -> ( valsMap, varsMap, outersMap ) // refMap stores field informations of an object or instance valsMap = valsym -> val // maps immutable fields to their values varsMap = valsym -> addr // each mutable field has an abstract address outersMap = class -> val // maps outer objects to their values

arrayMap = OfArray -> addr // an array has one address that stores the join value of every element

heap = addr -> val // heap is mutable

env = (valsMap, Option[env]) // stores local variables in the residing method, and possibly outer environments

addr ::= localVarAddr(regions, valsym, owner) | fieldVarAddr(regions, valsym, owner) // independent of OfClass/ObjectRef | arrayAddr(regions, owner) // independent of array element type

regions ::= List(sourcePosition)

Attributes

Supertypes
class Object
trait Matchable
class Any
Known subtypes
object Cold
class ValueElement
class Fun
class OfArray
class Ref
class ObjectRef
class OfClass
class ValueSet
Show all
sealed abstract class ValueElement extends Value

ValueElement are elements that can be contained in a RefSet

ValueElement are elements that can be contained in a RefSet

Attributes

Supertypes
class Value
class Object
trait Matchable
class Any
Known subtypes
class Fun
class OfArray
class Ref
class ObjectRef
class OfClass
case class ValueSet(values: ListSet[ValueElement]) extends Value

Represents a set of values

Represents a set of values

It comes from if expressions.

Attributes

Supertypes
trait Serializable
trait Product
trait Equals
class Value
class Object
trait Matchable
class Any
Show all

Types

type ThisValue = Ref | Cold.type

Possible types for 'this'

Possible types for 'this'

Attributes

Value members

Concrete methods

def assign(lhs: Value, field: Symbol, rhs: Value, rhsTyp: Type): () ?=> Value

Handle assignment lhs.f = rhs.

Handle assignment lhs.f = rhs.

Value parameters

field

The symbol of the target field.

lhs

The value of the object to be mutated.

rhs

The value to be assigned.

rhsTyp

The type of the right-hand side.

Attributes

inline def cache(using c: Data): Data
def call(value: Value, meth: Symbol, args: List[ArgInfo], receiver: Type, superType: Type, needResolve: Boolean): () ?=> Value

Handle method calls e.m(args).

Handle method calls e.m(args).

Value parameters

args

Arguments of the method call (all parameter blocks flatten to a list).

meth

The symbol of the target method (could be virtual or abstract method).

needResolve

Whether the target of the call needs resolution?

receiver

The type of the receiver.

superType

The type of the super in a super call. NoType for non-super calls.

value

The value for the receiver.

Attributes

def callConstructor(value: Value, ctor: Symbol, args: List[ArgInfo]): () ?=> Value

Handle constructor calls <init>(args).

Handle constructor calls <init>(args).

Value parameters

args

Arguments of the constructor call (all parameter blocks flatten to a list).

ctor

The symbol of the target method.

value

The value for the receiver.

Attributes

def cases(expr: Tree, thisV: ThisValue, klass: ClassSymbol): () ?=> Value

Handles the evaluation of different expressions

Handles the evaluation of different expressions

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

Value parameters

expr

The expression to be evaluated.

klass

The enclosing class where the expression expr is located.

thisV

The value for C.this where C is represented by the parameter klass.

Attributes

def checkClasses(classes: List[ClassSymbol])(using Context): Unit
def errorMutateOtherStaticObject(currentObj: ClassSymbol, otherObj: ClassSymbol)(using Trace, Context): Unit
def errorReadOtherStaticObject(currentObj: ClassSymbol, otherObj: ClassSymbol)(using Trace, Context): Unit
def eval(expr: Tree, thisV: ThisValue, klass: ClassSymbol, cacheResult: Boolean): () ?=> Value

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.

Value parameters

cacheResult

It is used to reduce the size of the cache.

expr

The expression to be evaluated.

klass

The enclosing class where the expression is located.

thisV

The value for C.this where C is represented by the parameter klass.

Attributes

def evalArgs(args: List[Arg], thisV: ThisValue, klass: ClassSymbol): () ?=> List[ArgInfo]

Evaluate arguments of methods and constructors

Evaluate arguments of methods and constructors

Attributes

def evalExprs(exprs: List[Tree], thisV: ThisValue, klass: ClassSymbol): () ?=> List[Value]

Evaluate a list of expressions

Evaluate a list of expressions

Attributes

def evalType(tp: Type, thisV: ThisValue, klass: ClassSymbol, elideObjectAccess: Boolean): () ?=> Value

Handle semantics of leaf nodes

Handle semantics of leaf nodes

For leaf nodes, their semantics is determined by their types.

Value parameters

elideObjectAccess

Whether object access should be omitted. Object access elission happens when the object access is used as a prefix in new o.C and C does not need an outer.

klass

The enclosing class where the type tp is located.

thisV

The value for C.this where C is represented by klass.

tp

The type to be evaluated.

Attributes

def init(tpl: Template, thisV: Ref, klass: ClassSymbol): () ?=> Ref

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

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

Value parameters

klass

The class to which the template belongs.

thisV

The value of the current object to be initialized.

tpl

The class body to be evaluated.

Attributes

def initLocal(sym: Symbol, value: Value): () ?=> Unit

Handle local variable definition, val x = e or var x = e.

Handle local variable definition, val x = e or var x = e.

Value parameters

sym

The symbol of the variable.

value

The value of the initializer.

Attributes

def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): () ?=> Value

Handle new expression new p.C(args).

Handle new expression new p.C(args).

Value parameters

args

The arguments passsed to the constructor.

ctor

The symbol of the target constructor.

klass

The symbol of the class C.

outer

The value for p.

Attributes

def outerValue(tref: TypeRef, thisV: ThisValue, klass: ClassSymbol): () ?=> Value

Compute the outer value that corresponds to tref.prefix

Compute the outer value that corresponds to tref.prefix

Value parameters

klass

The enclosing class where the type tref is located.

thisV

The value for C.this where C is represented by the parameter klass.

tref

The type whose prefix is to be evaluated.

Attributes

def patternMatch(scrutinee: Value, cases: List[CaseDef], thisV: ThisValue, klass: ClassSymbol): () ?=> Value

Evaluate the cases against the scrutinee value.

Evaluate the cases against the scrutinee value.

It returns the scrutinee in most cases. The main effect of the function is for its side effects of adding bindings to the environment.

See https://docs.scala-lang.org/scala3/reference/changed-features/pattern-matching.html

Value parameters

cases

The cases to match.

klass

The enclosing class where the type tp is located.

scrutinee

The abstract value of the scrutinee.

thisV

The value for C.this where C is represented by klass.

Attributes

def readLocal(thisV: ThisValue, sym: Symbol): () ?=> Value

Read local variable x.

Read local variable x.

Value parameters

sym

The symbol of the variable.

thisV

The value for this where the variable is used.

Attributes

def resolveThis(target: ClassSymbol, thisV: Value, klass: ClassSymbol, elideObjectAccess: Boolean): () ?=> Value

Resolve C.this that appear in klass

Resolve C.this that appear in klass

Value parameters

elideObjectAccess

Whether object access should be omitted. Object access elision happens when the object access is used as a prefix in new o.C and C does not need an outer.

klass

The enclosing class where the type C.this is located.

target

The class symbol for C for which C.this is to be resolved.

thisV

The value for D.this where D is represented by the parameter klass.

Attributes

def select(value: Value, field: Symbol, receiver: Type, needResolve: Boolean): () ?=> Value

Handle selection e.f.

Handle selection e.f.

Value parameters

field

The symbol of the target field (could be virtual or abstract).

needResolve

Whether the target of the selection needs resolution?

receiver

The type of the receiver.

value

The value for the receiver.

Attributes

def writeLocal(thisV: ThisValue, sym: Symbol, value: Value): () ?=> Value

Handle local variable assignmenbt, x = e.

Handle local variable assignmenbt, x = e.

Value parameters

sym

The symbol of the variable.

thisV

The value for this where the assignment locates.

value

The value of the rhs of the assignment.

Attributes

Concrete fields

Extensions

Extensions

extension (a: Value)
def join(b: Value): Value
def widen(height: Int)(using Context): Value
extension (value: Ref | Cold.type)
def widenRefOrCold(height: Int)(using Context): Ref | Cold.type
extension (values: Iterable[Value])
def join: Value
def widen(height: Int): () ?=> List[Value]