summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIulian Dragos <jaguarul@gmail.com>2008-07-28 18:05:50 +0000
committerIulian Dragos <jaguarul@gmail.com>2008-07-28 18:05:50 +0000
commit90200957ca0beb4db24555a2563d0a902de0078d (patch)
tree77ad1d2465b247d5df397e7e744d5a2bff459801
parent8bacd7cf469bd3097070e74e8bb72010403d21e6 (diff)
downloadscala-90200957ca0beb4db24555a2563d0a902de0078d.tar.gz
scala-90200957ca0beb4db24555a2563d0a902de0078d.tar.bz2
scala-90200957ca0beb4db24555a2563d0a902de0078d.zip
Added -Ycheckinit, which causes all getters to ...
Added -Ycheckinit, which causes all getters to check that fields are initialized before being used. It reuses the same machinery (and bitmaps) as lazy values. For now it requires the new initialization order (-Xexperimental) to work.
-rw-r--r--src/compiler/scala/tools/nsc/Settings.scala1
-rw-r--r--src/compiler/scala/tools/nsc/symtab/Definitions.scala2
-rw-r--r--src/compiler/scala/tools/nsc/transform/Constructors.scala8
-rw-r--r--src/compiler/scala/tools/nsc/transform/Mixin.scala262
-rw-r--r--src/library/scala/UninitializedFieldError.scala29
-rw-r--r--test/files/run/checked.check35
-rw-r--r--test/files/run/checked.flags1
-rw-r--r--test/files/run/checked.scala115
8 files changed, 397 insertions, 56 deletions
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala
index 10c038badb..5b42d47c69 100644
--- a/src/compiler/scala/tools/nsc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/Settings.scala
@@ -92,6 +92,7 @@ class Settings(error: String => Unit) {
val outdir = StringSetting ("-d", "directory", "Specify where to place generated class files", ".").hideToIDE
val encoding = StringSetting ("-encoding", "encoding", "Specify character encoding used by source files", Properties.encodingString).hideToIDE
val target = ChoiceSetting ("-target", "Specify for which target object files should be built", List("jvm-1.5", "jvm-1.4", "msil", "cldc"), "jvm-1.5")
+ val checkInit = BooleanSetting ("-Ycheckinit", "Add runtime checks on field accessors. Uninitialized accesses result in an exception being thrown.")
val printLate = BooleanSetting ("-print", "Print program with all Scala-specific features removed").hideToIDE
val XO = BooleanSetting ("-optimise", "Generates faster bytecode by applying optimisations to the program")
val explaintypes = BooleanSetting ("-explaintypes", "Explain type errors in more detail").hideToIDE
diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala
index 35434de9ed..450a03993f 100644
--- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala
+++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala
@@ -75,6 +75,7 @@ trait Definitions {
def Int_Or = definitions.getMember(definitions.IntClass, nme.OR)
def Int_And = definitions.getMember(definitions.IntClass, nme.AND)
def Int_== = definitions.getMember(definitions.IntClass, nme.EQ)
+ def Int_!= = definitions.getMember(definitions.IntClass, nme.NE)
var LongClass: Symbol = _
var FloatClass: Symbol = _
@@ -126,6 +127,7 @@ trait Definitions {
def Predef_error = getMember(PredefModule, nme.error)
lazy val ConsoleModule: Symbol = getModule("scala.Console")
lazy val MatchErrorClass: Symbol = getClass("scala.MatchError")
+ lazy val UninitializedErrorClass: Symbol = getClass("scala.UninitializedFieldError")
//var MatchErrorModule: Symbol = _
// def MatchError_fail = getMember(MatchErrorModule, nme.fail)
// def MatchError_report = getMember(MatchErrorModule, nme.report)
diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala
index 78ecd9a158..8753e83868 100644
--- a/src/compiler/scala/tools/nsc/transform/Constructors.scala
+++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala
@@ -187,11 +187,15 @@ abstract class Constructors extends Transform {
case ValDef(mods, name, tpt, rhs) =>
// val defs with constant right-hand sides are eliminated.
// for all other val defs, an empty valdef goes into the template and
- // the initializer goes as an assignment into th constructor
+ // the initializer goes as an assignment into the constructor
// if the val def is an early initialized or a parameter accessor, it goes
// before the superclass constructor call, otherwise it goes after.
+ // Lazy vals don't get the assignment in the constructor. Fields initialized
+ // to default values are not eliminated until the mixin phase, when
+ // checked initializers are added
if (!stat.symbol.tpe.isInstanceOf[ConstantType]) {
- if (rhs != EmptyTree && !stat.symbol.hasFlag(LAZY)) {
+ if ((rhs != EmptyTree || !stat.symbol.originalName.startsWith(nme.OUTER))
+ && !stat.symbol.hasFlag(LAZY)) {
val rhs1 = intoConstructor(stat.symbol, rhs);
(if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign(
stat.symbol, rhs1)
diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala
index 59dd57e8fd..4022c38dde 100644
--- a/src/compiler/scala/tools/nsc/transform/Mixin.scala
+++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala
@@ -355,6 +355,14 @@ abstract class Mixin extends InfoTransform {
/** The typer */
private var localTyper: erasure.Typer = _
+ import scala.collection._
+
+ /** Map a field symbol to a unique integer denoting its position in the class layout.
+ * For each class, fields defined by the class come after inherited fields. Mixed-in
+ * fields count as fields defined by the class itself.
+ */
+ private val fieldOffset: mutable.Map[Symbol, Int] = new mutable.HashMap[Symbol, Int]
+
/** The first transform; called in a pre-order traversal at phase mixin
* (that is, every node is processed before its children).
* What transform does:
@@ -450,7 +458,6 @@ abstract class Mixin extends InfoTransform {
*/
private def addNewDefs(clazz: Symbol, stats: List[Tree]): List[Tree] = {
val newDefs = new ListBuffer[Tree]
- var offset: Int = 0
/** Attribute given tree and anchor at given position */
def attributedDef(pos: Position, tree: Tree): Tree = {
@@ -522,6 +529,40 @@ abstract class Mixin extends InfoTransform {
import lazyVals._
+ /** Return the bitmap field for 'offset', create one if not inheriting it already. */
+ def bitmapFor(clazz: Symbol, offset: Int): Symbol = {
+ var sym = clazz.info.member(nme.bitmapName(offset / FLAGS_PER_WORD))
+ assert(!sym.hasFlag(OVERLOADED))
+ if (sym == NoSymbol) {
+ sym = clazz.newVariable(clazz.pos, nme.bitmapName(offset / FLAGS_PER_WORD))
+ .setInfo(definitions.IntClass.tpe)
+ .setFlag(PROTECTED)
+ atPhase(currentRun.typerPhase) {
+ sym.attributes = AnnotationInfo(definitions.VolatileAttr.tpe, List(), List()) :: sym.attributes
+ }
+ clazz.info.decls.enter(sym)
+ addDef(clazz.pos, ValDef(sym, Literal(Constant(0))))
+ }
+ sym
+ }
+
+ /** Return an (untyped) tree of the form 'Clazz.this.bmp = Clazz.this.bmp | mask'. */
+ def mkSetFlag(clazz: Symbol, offset: Int): Tree = {
+ val bmp = bitmapFor(clazz, offset)
+ val mask = Literal(Constant(1 << (offset % FLAGS_PER_WORD)))
+
+ Assign(Select(This(clazz), bmp),
+ Apply(Select(Select(This(clazz), bmp), Int_Or), List(mask)))
+ }
+
+ /** Return an (untyped) tree of the form 'clazz.this.bitmapSym & mask (==|!=) 0', the
+ * precise comparison operator depending on the value of 'equalToZero'.
+ */
+ def mkTest(clazz: Symbol, mask: Tree, bitmapSym: Symbol, equalToZero: Boolean): Tree =
+ Apply(Select(Apply(Select(Select(This(clazz), bitmapSym), Int_And), List(mask)),
+ if (equalToZero) Int_== else Int_!=),
+ List(Literal(Constant(0))))
+
/** return a 'lazified' version of rhs.
* @param clazz The class symbol
* @param init The tree which initializes the field ( f = <rhs> )
@@ -545,70 +586,175 @@ abstract class Mixin extends InfoTransform {
*/
def mkLazyDef(clazz: Symbol, init: Tree, retVal: Tree, offset: Int): Tree = {
- /** Return the bitmap field for 'offset', create one if not inheriting it already. */
- def bitmapFor(offset: Int): Symbol = {
- var sym = clazz.info.member(nme.bitmapName(offset / FLAGS_PER_WORD))
- assert(!sym.hasFlag(OVERLOADED))
- if (sym == NoSymbol) {
- sym = clazz.newVariable(clazz.pos, nme.bitmapName(offset / FLAGS_PER_WORD))
- .setInfo(definitions.IntClass.tpe)
- .setFlag(PROTECTED)
- atPhase(currentRun.typerPhase) {
- sym.attributes = AnnotationInfo(definitions.VolatileAttr.tpe, List(), List()) :: sym.attributes
- }
- clazz.info.decls.enter(sym)
- addDef(clazz.pos, ValDef(sym, Literal(Constant(0))))
- }
- sym
- }
- val bitmapSym = bitmapFor(offset)
-
- def mkSetFlag(bmp: Symbol, mask: Tree): Tree =
- Assign(Select(This(clazz), bmp),
- Apply(Select(Select(This(clazz), bmp), Int_Or), List(mask)))
-
- def mkTest(mask: Tree): Tree =
- Apply(Select(Apply(Select(Select(This(clazz), bitmapSym), Int_And), List(mask)),
- Int_==), List(Literal(Constant(0))))
+ val bitmapSym = bitmapFor(clazz, offset)
val mask = Literal(Constant(1 << (offset % FLAGS_PER_WORD)))
val result =
- If(mkTest(mask),
+ If(mkTest(clazz, mask, bitmapSym, true),
gen.mkSynchronized(gen.mkAttributedThis(clazz),
- If(mkTest(mask),
+ If(mkTest(clazz, mask, bitmapSym, true),
Block(List(init,
- mkSetFlag(bitmapSym, mask)),
+ mkSetFlag(clazz, offset)),
Literal(Constant(()))),
EmptyTree)),
EmptyTree)
localTyper.typed(atPos(init.pos)(Block(List(result), retVal)))
}
- /** Complete lazy field accessors. Applies only to classes, for it's own (non inherited) lazy fields. */
- def lazifyOwnFields(clazz: Symbol, stats: List[Tree]): List[Tree] = {
- var offset = clazz.info.findMember(nme.ANYNAME, 0, METHOD | LAZY, false)(NoSymbol).alternatives.filter(_.owner != clazz).length
+ def mkCheckedAccessor(clazz: Symbol, retVal: Tree, offset: Int, pos: Position): Tree = {
+ val bitmapSym = bitmapFor(clazz, offset)
+ val mask = Literal(Constant(1 << (offset % FLAGS_PER_WORD)))
+ val msg = "Uninitialized field: " + unit.source + ": " + pos.line.get
+ val result =
+ If(mkTest(clazz, mask, bitmapSym, false),
+ retVal,
+ Throw(New(TypeTree(definitions.UninitializedErrorClass.tpe), List(List(Literal(Constant(msg)))))))
+ localTyper.typed(atPos(pos)(Block(List(result), retVal)))
+ }
+
+ /** Fields that are initialized to their default value. They don't need a checked accessor. */
+ val initToDefault = new mutable.HashSet[Symbol]
+
+ /** Complete lazy field accessors. Applies only to classes, for it's own (non inherited) lazy fields.
+ * If 'checkinit' is enabled, getters that check for the initialized bit are generated, and
+ * the class constructor is changed to set the initialized bits.
+ */
+ def addCheckedGetters(clazz: Symbol, stats: List[Tree]): List[Tree] = {
val stats1 = for (stat <- stats; sym = stat.symbol) yield stat match {
case DefDef(mods, name, tp, vp, tpt, rhs)
if sym.hasFlag(LAZY) && rhs != EmptyTree && !clazz.isImplClass =>
+ assert(fieldOffset.isDefinedAt(sym))
val rhs1 = if (sym.tpe.resultType.typeSymbol == definitions.UnitClass)
- mkLazyDef(clazz, rhs, Literal(()), offset)
+ mkLazyDef(clazz, rhs, Literal(()), fieldOffset(sym))
else {
val Block(List(assignment), res) = rhs
- mkLazyDef(clazz, assignment, Select(This(clazz), res.symbol), offset)
+ mkLazyDef(clazz, assignment, Select(This(clazz), res.symbol), fieldOffset(sym))
+ }
+ copy.DefDef(stat, mods, name, tp, vp, tpt, rhs1)
+
+ case DefDef(mods, name, tp, vp, tpt, rhs)
+ if needsInitFlag(sym) && rhs != EmptyTree && !clazz.isImplClass && !clazz.isTrait =>
+ assert(fieldOffset.isDefinedAt(sym))
+
+ val rhs1 = if (sym.tpe.resultType.typeSymbol == definitions.UnitClass)
+ mkCheckedAccessor(clazz, Literal(()), fieldOffset(sym), stat.pos)
+ else {
+ mkCheckedAccessor(clazz, rhs, fieldOffset(sym), stat.pos)
}
- offset += 1
copy.DefDef(stat, mods, name, tp, vp, tpt, rhs1)
+
+ case DefDef(mods, name, tp, vp, tpt, rhs) if sym.isClassConstructor =>
+ copy.DefDef(stat, mods, name, tp, vp, tpt, addInitBits(clazz, rhs))
+
case _ => stat
}
- stats1
+ stats1
}
+ /** Does this field require an intialized bit? */
+ def needsInitFlag(sym: Symbol) = {
+ val res = (settings.checkInit.value
+ && sym.isGetter
+ && !initToDefault(sym)
+ && !sym.hasFlag(PARAMACCESSOR)
+ && !sym.accessed.hasFlag(PRESUPER)
+ && !sym.isOuterAccessor)
+
+ if (settings.debug.value) {
+ println("needsInitFlag(" + sym.fullNameString + "): " + res)
+ println("\tsym.isGetter: " + sym.isGetter)
+ println("\t!initToDefault(sym): " + !initToDefault(sym))
+ println("\t!sym.hasFlag(PARAMACCESSOR): " + !sym.hasFlag(PARAMACCESSOR))
+ //println("\t!sym.accessed.hasFlag(PRESUPER): " + !sym.accessed.hasFlag(PRESUPER))
+ println("\t!sym.isOuterAccessor: " + !sym.isOuterAccessor)
+ }
+
+ res
+ }
+
+ /** Adds statements to set the 'init' bit for each field initialized
+ * in the body of a constructor.
+ */
+ def addInitBits(clazz: Symbol, rhs: Tree): Tree = {
+ new Transformer {
+ override def transformStats(stats: List[Tree], exprOwner: Symbol) = {
+ val stats1 = stats flatMap { stat => stat match {
+ case Assign(lhs @ Select(This(_), _), rhs) =>
+ val sym = clazz.info.decl(nme.getterName(lhs.symbol.name))
+ .suchThat(_.isGetter)
+ if (rhs == EmptyTree)
+ List()
+ else if (sym != NoSymbol && needsInitFlag(sym) && fieldOffset.isDefinedAt(sym)) {
+ log("adding checked getter for: " + sym + " " + Flags.flagsToString(lhs.symbol.flags))
+ List(stat, localTyper.typed(mkSetFlag(clazz, fieldOffset(sym))))
+ } else {
+ List(stat)
+ }
+
+ case _ => List(stat)
+ }
+ }
+ super.transformStats(stats1, exprOwner)
+ }
+ }.transform(rhs)
+ }
+
+ /** Return the number of bits used by superclass fields.
+ * This number is a conservative approximation of what is actually used:
+ * - fields initialized to the default value don't get a checked initializer
+ * but there is no way to recover this information from types alone.
+ */
+ def usedBits(clazz: Symbol): Int = {
+ def isField(sym: Symbol) =
+ sym.hasFlag(PRIVATE) && sym.isTerm && !sym.isMethod
+
+ var fields =
+ for (sc <- clazz.info.parents;
+ field <- sc.decls.elements.toList
+ if !sc.typeSymbol.isTrait
+ && field.owner != clazz
+ && (settings.checkInit.value
+ && isField(field)
+ || field.hasFlag(LAZY))) yield field
+
+ if (settings.debug.value) log("Found inherited fields in " + clazz + " : " + fields)
+ fields.length
+ }
+
+
+ /** Fill the map from fields to offset numbers.
+ * Instead of field symbols, the map keeps their getter symbols. This makes
+ * code generation easier later.
+ */
+ def buildFieldPositions(clazz: Symbol) {
+ var fields = usedBits(clazz)
+ for (val f <- clazz.info.decls.elements if needsInitFlag(f) || f.hasFlag(LAZY)) {
+ if (settings.debug.value) log(f.fullNameString + " -> " + fields)
+ fieldOffset(f) = fields
+ fields += 1
+ }
+ }
+
+ // Look for fields that are initialized to the default value
+ // They won't get init fields.
+ new Traverser {
+ override def traverse(tree: Tree): Unit = tree match {
+ case DefDef(mods, name, tparams, vparamss, tpt, rhs) if tree.symbol.isClassConstructor =>
+ val Block(stats, _) = rhs
+ for (s <- stats) s match {
+ case Assign(f @ Select(This(_), _), EmptyTree) =>
+ val getter = clazz.info.decl(nme.getterName(f.symbol.name))
+ if (getter != NoSymbol)
+ initToDefault += getter
+ case _ => ()
+ }
+ case _ => super.traverse(tree)
+ }
+ }.traverseTrees(stats)
- // the number of inherited lazy fields that are not mixed in
- offset = (clazz.info.findMember(nme.ANYNAME, 0, METHOD | LAZY, false)(NoSymbol)
- .alternatives filter { f => f.owner != clazz || !f.hasFlag(MIXEDIN)}).length
+ buildFieldPositions(clazz)
// begin addNewDefs
- var stats1 = lazifyOwnFields(clazz, stats)
+ var stats1 = addCheckedGetters(clazz, stats)
// for all symbols `sym' in the class definition, which are mixed in:
for (val sym <- clazz.info.decls.toList) {
@@ -627,16 +773,16 @@ abstract class Mixin extends InfoTransform {
case _ =>
// if it is a mixed-in lazy value, complete the accessor
if (sym.hasFlag(LAZY) && sym.isGetter) {
- val rhs1 = if (sym.tpe.resultType.typeSymbol == definitions.UnitClass)
- mkLazyDef(clazz, Apply(staticRef(initializer(sym)), List(gen.mkAttributedThis(clazz))), Literal(()), offset)
- else {
- val assign = atPos(sym.pos) {
- Assign(Select(This(sym.accessed.owner), sym.accessed) /*gen.mkAttributedRef(sym.accessed)*/ ,
- Apply(staticRef(initializer(sym)), gen.mkAttributedThis(clazz) :: Nil))
+ val rhs1 =
+ if (sym.tpe.resultType.typeSymbol == definitions.UnitClass)
+ mkLazyDef(clazz, Apply(staticRef(initializer(sym)), List(gen.mkAttributedThis(clazz))), Literal(()), fieldOffset(sym))
+ else {
+ val assign = atPos(sym.pos) {
+ Assign(Select(This(sym.accessed.owner), sym.accessed) /*gen.mkAttributedRef(sym.accessed)*/ ,
+ Apply(staticRef(initializer(sym)), gen.mkAttributedThis(clazz) :: Nil))
+ }
+ mkLazyDef(clazz, assign, Select(This(clazz), sym.accessed), fieldOffset(sym))
}
- mkLazyDef(clazz, assign, Select(This(clazz), sym.accessed), offset)
- }
- offset += 1
rhs1
} else if (sym.getter(sym.owner).tpe.resultType.typeSymbol == definitions.UnitClass) {
Literal(())
@@ -644,12 +790,20 @@ abstract class Mixin extends InfoTransform {
Select(This(clazz), sym.accessed)
}
}
- if (sym.isSetter)
+ if (sym.isSetter) {
accessedRef match {
case Literal(_) => accessedRef
- case _ => Assign(accessedRef, Ident(vparams.head))
+ case _ =>
+ val init = Assign(accessedRef, Ident(vparams.head))
+ if (settings.checkInit.value && needsInitFlag(sym.getter(clazz))) {
+ Block(List(init, mkSetFlag(clazz, fieldOffset(sym.getter(clazz)))), Literal(()))
+ } else
+ init
}
- else gen.mkCheckInit(accessedRef)
+ } else if (!sym.hasFlag(LAZY) && settings.checkInit.value) {
+ mkCheckedAccessor(clazz, accessedRef, fieldOffset(sym), sym.pos)
+ } else
+ gen.mkCheckInit(accessedRef)
})
} else if (sym.isModule && !(sym hasFlag LIFTED | BRIDGE)) {
// add modules
@@ -810,13 +964,13 @@ abstract class Mixin extends InfoTransform {
*/
override def transform(tree: Tree): Tree = {
try { //debug
- val outerTyper = localTyper
+ val outerTyper = localTyper
val tree1 = super.transform(preTransform(tree))
val res = atPhase(phase.next)(postTransform(tree1))
// needed when not flattening inner classes. parts after an
// inner class will otherwise be typechecked with a wrong scope
localTyper = outerTyper
- res
+ res
} catch {
case ex: Throwable =>
if (settings.debug.value) Console.println("exception when traversing " + tree)
diff --git a/src/library/scala/UninitializedFieldError.scala b/src/library/scala/UninitializedFieldError.scala
new file mode 100644
index 0000000000..255b1eef5f
--- /dev/null
+++ b/src/library/scala/UninitializedFieldError.scala
@@ -0,0 +1,29 @@
+/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2002-2007, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+// $Id: $
+
+
+package scala
+
+import Predef._
+
+/** This class implements errors which are thrown whenever a
+ * field is used before it has been initialized.
+ *
+ * Such runtime checks are not emitted by default. See the
+ * compiler documentation for knowing how to turn them on.
+ *
+ * Note: This check requires the new initialization order,
+ * planned for 2.8.0 and available under -Xexperimental.
+ */
+final case class UninitializedFieldError(msg: String)
+ extends RuntimeException(msg) {
+ def this(obj: Any) =
+ this(if (null != obj) obj.toString() else "null")
+}
diff --git a/test/files/run/checked.check b/test/files/run/checked.check
new file mode 100644
index 0000000000..a1a43027f0
--- /dev/null
+++ b/test/files/run/checked.check
@@ -0,0 +1,35 @@
+checked.scala:42: warning: the semantics of this definition has changed;
+the initialization is no longer be executed before the superclass is called
+ val x = 1
+ ^
+checked.scala:19: warning: the semantics of this definition has changed;
+the initialization is no longer be executed before the superclass is called
+ val t1 = 1
+ ^
+checked.scala:20: warning: the semantics of this definition has changed;
+the initialization is no longer be executed before the superclass is called
+ var t2 = 2
+ ^
+checked.scala:53: warning: the semantics of this definition has changed;
+the initialization is no longer be executed before the superclass is called
+ val x = 1
+ ^
+checked.scala:54: warning: the semantics of this definition has changed;
+the initialization is no longer be executed before the superclass is called
+ val y = 2
+ ^
+5 warnings found
+sum = 12
+[OK] Cought UFE: Uninitialized field: checked.scala: 42
+2
+[OK] Cought UFE: Uninitialized field: checked.scala: 73
+x = 10
+y = 11
+lz1 = 1
+lz2 = 2
+[OK]: 24
+x = 10
+y = 11
+lz1 = 1
+lz2 = 2
+[OK]: 24
diff --git a/test/files/run/checked.flags b/test/files/run/checked.flags
new file mode 100644
index 0000000000..2182668259
--- /dev/null
+++ b/test/files/run/checked.flags
@@ -0,0 +1 @@
+-Xexperimental -Ycheckinit \ No newline at end of file
diff --git a/test/files/run/checked.scala b/test/files/run/checked.scala
new file mode 100644
index 0000000000..a20b819353
--- /dev/null
+++ b/test/files/run/checked.scala
@@ -0,0 +1,115 @@
+/* Test checked initializers. Needs to be run with -Xexperimental and -checkinit
+ */
+
+// 0 inherited fields
+class A {
+ val x = 1
+ val y = 2
+ var z = 3
+}
+
+// 3 inherited fields
+class B extends A {
+ val b1 = 1
+ var b2 = 2
+}
+
+
+trait T {
+ val t1 = 1
+ var t2 = 2
+}
+
+// Should not throw
+class D extends B with T {
+ val sum = x + y + z + b1 + b2 + t1 + t2
+ override def toString =
+ "sum = " + sum
+
+}
+
+abstract class NeedsXEarly {
+ val x: Int
+ val y = x + 1
+}
+
+// should pass
+class GoodX extends { val x = 1 } with NeedsXEarly {
+}
+
+// should throw
+class BadX extends NeedsXEarly {
+ val x = 1
+ println(y)
+}
+
+// should pass
+class UglyX extends NeedsXEarly {
+ lazy val x = 1
+ println(y)
+}
+
+trait XY {
+ val x = 1
+ val y = 2
+}
+
+// needs x and y early
+trait LazyFields {
+ lazy val lz1 = 1
+ lazy val lz2 = 2
+ val x: Int
+ val y: Int
+ val needsSomeEarly = {
+ println("x = " + x)
+ println("y = " + y)
+ println("lz1 = " + lz1)
+ println("lz2 = " + lz2)
+ x + y + lz1 + lz2
+ }
+}
+
+// will fail at init
+class BadMixin extends LazyFields with XY {
+ println("[OK]: " + needsSomeEarly)
+}
+
+// should print 24
+class GoodMixin extends {
+ override val x = 10
+ override val y = 11
+ } with LazyFields with XY {
+ println("[OK]: " + needsSomeEarly)
+}
+
+class TestInterference extends {
+ override val x = 10
+ override val y = 11
+} with A with T with LazyFields {
+ println("[OK]: " + needsSomeEarly)
+}
+
+
+object Test extends Application {
+
+ def shouldThrow(t: => Unit) = try {
+ t
+ println("[FAIL]: No UFE thrown")
+ } catch {
+ case UninitializedFieldError(msg) =>
+ println("[OK] Cought UFE: " + msg)
+ }
+
+
+ val d = new D()
+ println(d)
+
+ shouldThrow(new BadX)
+ (new GoodX)
+ (new UglyX)
+
+ shouldThrow(new BadMixin)
+ (new GoodMixin)
+
+ (new TestInterference)
+}