dotty.tools.dotc.transform.init
Members list
Type members
Classlikes
The co-inductive cache used for analysis
The co-inductive cache used for analysis
The cache contains two maps from (Config, Tree)
to Res
:
- input cache (
this.last
) - output cache (
this.current
)
The two caches are required because we want to make sure in a new iteration, an expression is evaluated exactly once. The monotonicity of the analysis ensures that the cache state goes up the lattice of the abstract domain, consequently the algorithm terminates.
The general skeleton for usage of the cache is as follows
def analysis(entryExp: Expr) = {
def iterate(entryExp: Expr)(using Cache) =
eval(entryExp, initConfig)
if cache.hasChanged && noErrors then
cache.last = cache.current
cache.current = Empty
cache.changed = false
iterate(entryExp)
else
reportErrors
def eval(expr: Expr, config: Config)(using Cache) =
cache.cachedEval(config, expr) {
// Actual recursive evaluation of expression.
//
// Only executed if the entry `(exp, config)` is not in the output cache.
}
iterate(entryExp)(using new Cache)
}
See the documentation for the method Cache.cachedEval
for more information.
What goes to the configuration (Config
) and what goes to the result (Res
) need to be decided by the specific analysis and justified by reasoning about soundness.
Type parameters
- Config
-
The analysis state that matters for evaluating an expression.
- Res
-
The result from the evaluation the given expression.
Attributes
- Companion
- object
- Supertypes
- Known subtypes
Check initialization safety of static 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:
-
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.
-
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.
-
It is inter-procedural and flow-sensitive.
-
It is object-sensitive by default and parameter-sensitive on-demand.
-
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
- Supertypes
- Self type
-
Objects.type
Logic related to evaluation trace for showing friendly error messages
Logic related to evaluation trace for showing friendly error messages
A trace is a sequence of program positions which tells the evaluation order that leads to an error. It is usually more informative than the stack trace by tracking the exact sub-expression in the trace instead of only methods.
Attributes
- Supertypes
- Self type
-
Trace.type