summaryrefslogtreecommitdiff
path: root/src/compiler
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 /src/compiler
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.
Diffstat (limited to 'src/compiler')
-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
4 files changed, 217 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)