diff options
Diffstat (limited to 'src/dotty')
29 files changed, 251 insertions, 114 deletions
diff --git a/src/dotty/tools/dotc/Bench.scala b/src/dotty/tools/dotc/Bench.scala index 417054dce..47b5fd6dd 100644 --- a/src/dotty/tools/dotc/Bench.scala +++ b/src/dotty/tools/dotc/Bench.scala @@ -10,7 +10,7 @@ import reporting.Reporter object Bench extends Driver { - private var numRuns = 1 + @sharable private var numRuns = 1 def newCompiler(): Compiler = new Compiler diff --git a/src/dotty/tools/dotc/Compiler.scala b/src/dotty/tools/dotc/Compiler.scala index a14aa3655..827134e84 100644 --- a/src/dotty/tools/dotc/Compiler.scala +++ b/src/dotty/tools/dotc/Compiler.scala @@ -40,7 +40,8 @@ class Compiler { List(new FrontEnd), List(new PostTyper), List(new Pickler), - List(new FirstTransform), + List(new FirstTransform, + new CheckReentrant), List(new RefChecks, new ElimRepeated, new NormalizeFlags, diff --git a/src/dotty/tools/dotc/Resident.scala b/src/dotty/tools/dotc/Resident.scala index d671ea259..9578e7d2c 100644 --- a/src/dotty/tools/dotc/Resident.scala +++ b/src/dotty/tools/dotc/Resident.scala @@ -21,7 +21,7 @@ import scala.annotation.tailrec * * dotc> :q // quit */ -object Resident extends Driver { +class Resident extends Driver { object residentCompiler extends Compiler diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 660a8dbcb..30467f185 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -209,7 +209,7 @@ object desugar { else tdef } - private val synthetic = Modifiers(Synthetic) + @sharable private val synthetic = Modifiers(Synthetic) private def toDefParam(tparam: TypeDef): TypeDef = tparam.withFlags(Param) diff --git a/src/dotty/tools/dotc/ast/Trees.scala b/src/dotty/tools/dotc/ast/Trees.scala index 97facaa5d..f63d32b14 100644 --- a/src/dotty/tools/dotc/ast/Trees.scala +++ b/src/dotty/tools/dotc/ast/Trees.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package ast import core._ @@ -26,7 +27,7 @@ object Trees { type Untyped = Null /** The total number of created tree nodes, maintained if Stats.enabled */ - var ntrees = 0 + @sharable var ntrees = 0 /** Modifiers and annotations for definitions * @param flags The set flags @@ -68,7 +69,7 @@ object Trees { def tokenPos: Seq[(Token, Position)] = ??? } - private var nextId = 0 // for debugging + @sharable private var nextId = 0 // for debugging type LazyTree = AnyRef /* really: Tree | Lazy[Tree] */ type LazyTreeList = AnyRef /* really: List[Tree] | Lazy[List[Tree]] */ @@ -723,9 +724,9 @@ object Trees { setMods(Modifiers[T](PrivateLocal)) } - val theEmptyTree: Thicket[Type] = Thicket(Nil) - val theEmptyValDef = new EmptyValDef[Type] - val theEmptyModifiers = new Modifiers() + @sharable val theEmptyTree: Thicket[Type] = Thicket(Nil) + @sharable val theEmptyValDef = new EmptyValDef[Type] + @sharable val theEmptyModifiers = new Modifiers() def genericEmptyValDef[T >: Untyped]: ValDef[T] = theEmptyValDef.asInstanceOf[ValDef[T]] def genericEmptyTree[T >: Untyped]: Thicket[T] = theEmptyTree.asInstanceOf[Thicket[T]] @@ -845,9 +846,9 @@ object Trees { type Annotated = Trees.Annotated[T] type Thicket = Trees.Thicket[T] - val EmptyTree: Thicket = genericEmptyTree - val EmptyValDef: ValDef = genericEmptyValDef - val EmptyModifiers: Modifiers = genericEmptyModifiers + @sharable val EmptyTree: Thicket = genericEmptyTree + @sharable val EmptyValDef: ValDef = genericEmptyValDef + @sharable val EmptyModifiers: Modifiers = genericEmptyModifiers // ----- Auxiliary creation methods ------------------ diff --git a/src/dotty/tools/dotc/config/Properties.scala b/src/dotty/tools/dotc/config/Properties.scala index d592a7bb1..ec1f24d06 100644 --- a/src/dotty/tools/dotc/config/Properties.scala +++ b/src/dotty/tools/dotc/config/Properties.scala @@ -12,7 +12,7 @@ object Properties extends PropertiesTrait { /** Scala manifest attributes. */ - val ScalaCompilerVersion = new AttributeName("Scala-Compiler-Version") + @sharable val ScalaCompilerVersion = new AttributeName("Scala-Compiler-Version") } trait PropertiesTrait { @@ -23,7 +23,7 @@ trait PropertiesTrait { protected val propFilename = "/" + propCategory + ".properties" /** The loaded properties */ - protected lazy val scalaProps: java.util.Properties = { + @sharable protected lazy val scalaProps: java.util.Properties = { val props = new java.util.Properties val stream = pickJarBasedOn getResourceAsStream propFilename if (stream ne null) diff --git a/src/dotty/tools/dotc/config/ScalaSettings.scala b/src/dotty/tools/dotc/config/ScalaSettings.scala index f8c155cad..05fefc8b4 100644 --- a/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -151,6 +151,7 @@ class ScalaSettings extends Settings.SettingGroup { val YnoDeepSubtypes = BooleanSetting("-Yno-deep-subtypes", "throw an exception on deep subtyping call stacks.") val YprintSyms = BooleanSetting("-Yprint-syms", "when printing trees print info in symbols instead of corresponding info in trees.") val YtestPickler = BooleanSetting("-Ytest-pickler", "self-test for pickling functionality; should be used with -Ystop-after:pickler") + val YcheckReentrant = BooleanSetting("-Ycheck-reentrant", "check that compiled program does not contain vars that can be accessed from a global root.") def stop = YstopAfter /** Area-specific debug output. diff --git a/src/dotty/tools/dotc/config/ScalaVersion.scala b/src/dotty/tools/dotc/config/ScalaVersion.scala index 7d4585441..02ba74af9 100644 --- a/src/dotty/tools/dotc/config/ScalaVersion.scala +++ b/src/dotty/tools/dotc/config/ScalaVersion.scala @@ -1,6 +1,7 @@ /* @author James Iry */ -package dotty.tools.dotc.config +package dotty.tools +package dotc.config import scala.util.{Try, Success, Failure} @@ -15,7 +16,7 @@ sealed abstract class ScalaVersion extends Ordered[ScalaVersion] { /** * A scala version that sorts higher than all actual versions */ -case object NoScalaVersion extends ScalaVersion { +@sharable case object NoScalaVersion extends ScalaVersion { def unparse = "none" def compare(that: ScalaVersion): Int = that match { @@ -52,7 +53,7 @@ case class SpecificScalaVersion(major: Int, minor: Int, rev: Int, build: ScalaBu /** * A Scala version that sorts lower than all actual versions */ -case object AnyScalaVersion extends ScalaVersion { +@sharable case object AnyScalaVersion extends ScalaVersion { def unparse = "any" def compare(that: ScalaVersion): Int = that match { @@ -64,7 +65,7 @@ case object AnyScalaVersion extends ScalaVersion { /** * Methods for parsing ScalaVersions */ -object ScalaVersion { +@sharable object ScalaVersion { private val dot = "\\." private val dash = "\\-" private def not(s:String) = s"[^${s}]" diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index c9deaab10..206ef9d8b 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -28,6 +28,7 @@ import printing._ import config.{Settings, ScalaSettings, Platform, JavaPlatform} import language.implicitConversions import DenotTransformers.DenotTransformer + object Contexts { /** A context is passed basically everywhere in dotc. @@ -473,7 +474,7 @@ object Contexts { gadt = new GADTMap(SimpleMap.Empty) } - object NoContext extends Context { + @sharable object NoContext extends Context { lazy val base = unsupported("base") override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this) } @@ -620,7 +621,7 @@ object Contexts { /** implicit conversion that injects all ContextBase members into a context */ implicit def toBase(ctx: Context): ContextBase = ctx.base - val theBase = new ContextBase // !!! DEBUG, so that we can use a minimal context for reporting even in code that normally cannot access a context + // @sharable val theBase = new ContextBase // !!! DEBUG, so that we can use a minimal context for reporting even in code that normally cannot access a context } /** Info that changes on each compiler run */ diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 70ca88702..fc97fb32b 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -556,7 +556,7 @@ object Denotations { */ private def bringForward()(implicit ctx: Context): SingleDenotation = this match { case denot: SymDenotation if ctx.stillValid(denot) => - if (denot.exists) assert(ctx.runId > validFor.runId, s"denotation $denot invalid in run ${ctx.runId}. ValidFor: $validFor") + assert(ctx.runId > validFor.runId, s"denotation $denot invalid in run ${ctx.runId}. ValidFor: $validFor") var d: SingleDenotation = denot do { d.validFor = Period(ctx.period.runId, d.validFor.firstPhaseId, d.validFor.lastPhaseId) @@ -592,7 +592,9 @@ object Denotations { assert(false) } - if (valid.runId != currentPeriod.runId) initial.bringForward.current + if (valid.runId != currentPeriod.runId) + if (exists) initial.bringForward.current + else this else { var cur = this if (currentPeriod.code > valid.code) { diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index f39f2bac6..161c8fde3 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -136,7 +136,7 @@ object Flags { private final val FirstNotPickledFlag = 48 private final val MaxFlag = 63 - private var flagName = Array.fill(64, 2)("") + private val flagName = Array.fill(64, 2)("") private def isDefinedAsFlag(idx: Int) = flagName(idx) exists (_.nonEmpty) diff --git a/src/dotty/tools/dotc/core/Names.scala b/src/dotty/tools/dotc/core/Names.scala index ea30b1c3b..1ee56fe1c 100644 --- a/src/dotty/tools/dotc/core/Names.scala +++ b/src/dotty/tools/dotc/core/Names.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package core import scala.io.Codec @@ -151,11 +152,13 @@ object Names { override def seq = toCollection(this) } - class TermName(val start: Int, val length: Int, private[Names] var next: TermName) extends Name { + class TermName(val start: Int, val length: Int, @sharable private[Names] var next: TermName) extends Name { + // `next` is @sharable because it is only modified in the synchronized block of termName. type ThisName = TermName def isTypeName = false def isTermName = true + @sharable // because it is only modified in the synchronized block of toTypeName. @volatile private[this] var _typeName: TypeName = null def toTypeName: TypeName = { @@ -200,44 +203,21 @@ object Names { private final val fillFactor = 0.7 /** Memory to store all names sequentially. */ + @sharable // because it's only mutated in synchronized block of termName private[dotty] var chrs: Array[Char] = new Array[Char](InitialNameSize) /** The number of characters filled. */ + @sharable // because it's only mutated in synchronized block of termName private var nc = 0 /** Hashtable for finding term names quickly. */ + @sharable // because it's only mutated in synchronized block of termName private var table = new Array[TermName](InitialHashSize) /** The number of defined names. */ + @sharable // because it's only mutated in synchronized block of termName private var size = 1 - /** Make sure the capacity of the character array is at least `n` */ - private def ensureCapacity(n: Int) = - if (n > chrs.length) { - val newchrs = new Array[Char](chrs.length * 2) - chrs.copyToArray(newchrs) - chrs = newchrs - } - - /** Make sure the hash table is large enough for the given load factor */ - private def incTableSize() = { - size += 1 - if (size.toDouble / table.size > fillFactor) { - val oldTable = table - table = new Array[TermName](table.size * 2) - for (i <- 0 until oldTable.size) rehash(oldTable(i)) - } - } - - /** Rehash chain of names */ - private def rehash(name: TermName): Unit = - if (name != null) { - rehash(name.next) - val h = hashValue(chrs, name.start, name.length) & (table.size - 1) - name.next = table(h) - table(h) = name - } - /** The hash of a name made of from characters cs[offset..offset+len-1]. */ private def hashValue(cs: Array[Char], offset: Int, len: Int): Int = if (len > 0) @@ -257,24 +237,53 @@ object Names { i == len } - /** Enter characters into chrs array. */ - private def enterChars(cs: Array[Char], offset: Int, len: Int): Unit = { - ensureCapacity(nc + len) - var i = 0 - while (i < len) { - chrs(nc + i) = cs(offset + i) - i += 1 - } - nc += len - } - /** Create a term name from the characters in cs[offset..offset+len-1]. * Assume they are already encoded. */ def termName(cs: Array[Char], offset: Int, len: Int): TermName = { util.Stats.record("termName") val h = hashValue(cs, offset, len) & (table.size - 1) + synchronized { + + /** Make sure the capacity of the character array is at least `n` */ + def ensureCapacity(n: Int) = + if (n > chrs.length) { + val newchrs = new Array[Char](chrs.length * 2) + chrs.copyToArray(newchrs) + chrs = newchrs + } + + /** Enter characters into chrs array. */ + def enterChars(): Unit = { + ensureCapacity(nc + len) + var i = 0 + while (i < len) { + chrs(nc + i) = cs(offset + i) + i += 1 + } + nc += len + } + + /** Rehash chain of names */ + def rehash(name: TermName): Unit = + if (name != null) { + rehash(name.next) + val h = hashValue(chrs, name.start, name.length) & (table.size - 1) + name.next = table(h) + table(h) = name + } + + /** Make sure the hash table is large enough for the given load factor */ + def incTableSize() = { + size += 1 + if (size.toDouble / table.size > fillFactor) { + val oldTable = table + table = new Array[TermName](table.size * 2) + for (i <- 0 until oldTable.size) rehash(oldTable(i)) + } + } + val next = table(h) var name = next while (name ne null) { @@ -283,7 +292,7 @@ object Names { name = name.next } name = new TermName(nc, len, next) - enterChars(cs, offset, len) + enterChars() table(h) = name incTableSize() name diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 164b0b8f3..53973b587 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package core import Periods._, Contexts._, Symbols._, Denotations._, Names._, NameOps._, Annotations._ @@ -1713,8 +1714,8 @@ object SymDenotations { validFor = Period.allInRun(NoRunId) // will be brought forward automatically } - val NoDenotation = new NoDenotation - val NotDefinedHereDenotation = new NoDenotation + @sharable val NoDenotation = new NoDenotation + @sharable val NotDefinedHereDenotation = new NoDenotation // ---- Completion -------------------------------------------------------- @@ -1757,7 +1758,7 @@ object SymDenotations { val NoSymbolFn = (ctx: Context) => NoSymbol /** A missing completer */ - class NoCompleter extends LazyType { + @sharable class NoCompleter extends LazyType { def complete(denot: SymDenotation)(implicit ctx: Context): Unit = unsupported("complete") } diff --git a/src/dotty/tools/dotc/core/Symbols.scala b/src/dotty/tools/dotc/core/Symbols.scala index 602bdba80..ce308459a 100644 --- a/src/dotty/tools/dotc/core/Symbols.scala +++ b/src/dotty/tools/dotc/core/Symbols.scala @@ -38,12 +38,12 @@ trait Symbols { this: Context => * Note this uses a cast instead of a direct type refinement because * it's debug-friendlier not to create an anonymous class here. */ - def newNakedSymbol[N <: Name](coord: Coord = NoCoord): Symbol { type ThisName = N } = - new Symbol(coord).asInstanceOf[Symbol { type ThisName = N }] + def newNakedSymbol[N <: Name](coord: Coord = NoCoord)(implicit ctx: Context): Symbol { type ThisName = N } = + new Symbol(coord, ctx.nextId).asInstanceOf[Symbol { type ThisName = N }] /** Create a class symbol without a denotation. */ - def newNakedClassSymbol(coord: Coord = NoCoord, assocFile: AbstractFile = null) = - new ClassSymbol(coord, assocFile) + def newNakedClassSymbol(coord: Coord = NoCoord, assocFile: AbstractFile = null)(implicit ctx: Context) = + new ClassSymbol(coord, assocFile, ctx.nextId) // ---- Symbol creation methods ---------------------------------- @@ -364,22 +364,16 @@ trait Symbols { this: Context => object Symbols { - var _nextId = 0 // !!! DEBUG, use global counter instead - def nextId = { _nextId += 1; _nextId } - - /** A Symbol represents a Scala definition/declaration or a package. + * @param coord The coordinates of the symbol (a position or an index) + * @param id A unique identifier of the symbol (unique per ContextBase) */ - class Symbol private[Symbols] (val coord: Coord) extends DotClass with printing.Showable { + class Symbol private[Symbols] (val coord: Coord, val id: Int) extends DotClass with printing.Showable { type ThisName <: Name - private[this] var _id: Int = nextId //assert(_id != 30214) - /** The unique id of this symbol */ - def id = _id - /** The last denotation of this symbol */ private[this] var lastDenot: SymDenotation = _ @@ -514,8 +508,8 @@ object Symbols { type TermSymbol = Symbol { type ThisName = TermName } type TypeSymbol = Symbol { type ThisName = TypeName } - class ClassSymbol private[Symbols] (coord: Coord, val assocFile: AbstractFile) - extends Symbol(coord) { + class ClassSymbol private[Symbols] (coord: Coord, val assocFile: AbstractFile, id: Int) + extends Symbol(coord, id) { type ThisName = TypeName @@ -551,12 +545,12 @@ object Symbols { override protected def prefixString = "ClassSymbol" } - class ErrorSymbol(val underlying: Symbol, msg: => String)(implicit ctx: Context) extends Symbol(NoCoord) { + class ErrorSymbol(val underlying: Symbol, msg: => String)(implicit ctx: Context) extends Symbol(NoCoord, ctx.nextId) { type ThisName = underlying.ThisName denot = underlying.denot } - object NoSymbol extends Symbol(NoCoord) { + @sharable object NoSymbol extends Symbol(NoCoord, 0) { denot = NoDenotation override def associatedFile(implicit ctx: Context): AbstractFile = NoSource.file @@ -590,5 +584,5 @@ object Symbols { /** The current class */ def currentClass(implicit ctx: Context): ClassSymbol = ctx.owner.enclosingClass.asClass - var stubs: List[Symbol] = Nil // diagnostic + @sharable var stubs: List[Symbol] = Nil // diagnostic only } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 2b2ef83a2..b61d39749 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package core import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ @@ -572,5 +573,5 @@ trait TypeOps { this: Context => // TODO: Make standalone object. object TypeOps { val emptyDNF = (Nil, Set[Name]()) :: Nil - var track = false // !!!DEBUG + @sharable var track = false // !!!DEBUG } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index f4dbfb0e3..61ab1133b 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package core import util.common._ @@ -35,7 +36,7 @@ import language.implicitConversions object Types { - private var nextId = 0 + @sharable private var nextId = 0 /** The class of types. * The principal subclasses and sub-objects are as follows: @@ -73,6 +74,7 @@ object Types { // ----- Tests ----------------------------------------------------- + // debug only: a unique identifier for a type val uniqId = { nextId = nextId + 1 // if (nextId == 19555) @@ -2753,13 +2755,13 @@ object Types { case class ImportType(expr: Tree) extends UncachedGroundType /** Sentinel for "missing type" */ - case object NoType extends CachedGroundType { + @sharable case object NoType extends CachedGroundType { override def exists = false override def computeHash = hashSeed } /** Missing prefix */ - case object NoPrefix extends CachedGroundType { + @sharable case object NoPrefix extends CachedGroundType { override def computeHash = hashSeed } @@ -2776,7 +2778,7 @@ object Types { final class CachedWildcardType(optBounds: Type) extends WildcardType(optBounds) - object WildcardType extends WildcardType(NoType) { + @sharable object WildcardType extends WildcardType(NoType) { def apply(bounds: TypeBounds)(implicit ctx: Context) = unique(new CachedWildcardType(bounds)) } @@ -2984,7 +2986,7 @@ object Types { } } - object IdentityTypeMap extends TypeMap()(NoContext) { + @sharable object IdentityTypeMap extends TypeMap()(NoContext) { override def stopAtStatic = true def apply(tp: Type) = tp } @@ -3218,7 +3220,7 @@ object Types { // ----- Debug --------------------------------------------------------- - var debugTrace = false + @sharable var debugTrace = false val watchList = List[String]( ) map (_.toTypeName) diff --git a/src/dotty/tools/dotc/parsing/Parsers.scala b/src/dotty/tools/dotc/parsing/Parsers.scala index 2a42a7fa9..d6125f236 100644 --- a/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/src/dotty/tools/dotc/parsing/Parsers.scala @@ -36,11 +36,11 @@ object Parsers { def nonePositive: Boolean = parCounts forall (_ <= 0) } - object Location extends Enumeration { + @sharable object Location extends Enumeration { val InParens, InBlock, InPattern, ElseWhere = Value } - object ParamOwner extends Enumeration { + @sharable object ParamOwner extends Enumeration { val Class, Type, TypeParam, Def = Value } diff --git a/src/dotty/tools/dotc/transform/CheckReentrant.scala b/src/dotty/tools/dotc/transform/CheckReentrant.scala new file mode 100644 index 000000000..2569b3aae --- /dev/null +++ b/src/dotty/tools/dotc/transform/CheckReentrant.scala @@ -0,0 +1,95 @@ +package dotty.tools.dotc +package transform + +import core._ +import Names._ +import dotty.tools.dotc.transform.TreeTransforms.{AnnotationTransformer, TransformerInfo, MiniPhaseTransform, TreeTransformer} +import ast.Trees._ +import Flags._ +import Types._ +import Constants.Constant +import Contexts.Context +import Symbols._ +import SymDenotations._ +import Decorators._ +import dotty.tools.dotc.core.Annotations.ConcreteAnnotation +import dotty.tools.dotc.core.Denotations.SingleDenotation +import scala.collection.mutable +import DenotTransformers._ +import typer.Checking +import Names.Name +import NameOps._ +import StdNames._ + + +/** A no-op transform that checks whether the compiled sources are re-entrant. + * If -Ycheck:reentrant is set, the phase makes sure that there are no variables + * that are accessible from a global object. It excludes from checking paths that + * are labeled with one of the annotations + * + * @sharable Indicating a class or val can be safely shared + * @unshared Indicating an object will not be accessed from multiple threads + * + * Currently the analysis is only intended to check the dotty compiler itself. To make + * it generally useful we'd need to add at least the following: + * + * - Handle polymorphic instantiation: We might instantiate a generic class + * with a type that contains vars. If the class contains fields of the generic + * type, this may constitute a path to a shared var, which currently goes undetected. + * - Handle arrays: Array elements are currently ignored because they are often used + * in an immutable way anyway. To do better, it would be helpful to have a type + * for immutable array. + */ +class CheckReentrant extends MiniPhaseTransform { thisTransformer => + import ast.tpd._ + + override def phaseName = "checkReentrant" + + private var shared: Set[Symbol] = Set() + private var seen: Set[ClassSymbol] = Set() + private var indent: Int = 0 + + private val sharableAnnot = new CtxLazy(implicit ctx => + ctx.requiredClass("dotty.tools.sharable")) + private val unsharedAnnot = new CtxLazy(implicit ctx => + ctx.requiredClass("dotty.tools.unshared")) + + def isIgnored(sym: Symbol)(implicit ctx: Context) = + sym.hasAnnotation(sharableAnnot()) || + sym.hasAnnotation(unsharedAnnot()) + + def scanning(sym: Symbol)(op: => Unit)(implicit ctx: Context): Unit = { + ctx.log(i"${" " * indent}scanning $sym") + indent += 1 + try op + finally indent -= 1 + } + + def addVars(cls: ClassSymbol)(implicit ctx: Context): Unit = { + if (!seen.contains(cls) && !isIgnored(cls)) { + seen += cls + scanning(cls) { + for (sym <- cls.classInfo.decls) + if (sym.isTerm && !sym.isSetter && !isIgnored(sym)) + if (sym.is(Mutable)) { + ctx.error( + i"""possible data race involving globally reachable ${sym.showLocated}: ${sym.info} + | use -Ylog:checkReentrant+ to find out more about why the variable is reachable.""".stripMargin) + shared += sym + } else if (!sym.is(Method) || sym.is(Accessor | ParamAccessor)) { + scanning(sym) { + sym.info.widenExpr.classSymbols.foreach(addVars) + } + } + for (parent <- cls.classInfo.classParents) + addVars(parent.symbol.asClass) + } + } + } + + override def transformTemplate(tree: Template)(implicit ctx: Context, info: TransformerInfo): Tree = { + if (ctx.settings.YcheckReentrant.value && tree.symbol.owner.isStaticOwner) + addVars(tree.symbol.owner.asClass) + tree + } +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/CtxLazy.scala b/src/dotty/tools/dotc/transform/CtxLazy.scala new file mode 100644 index 000000000..7b317abef --- /dev/null +++ b/src/dotty/tools/dotc/transform/CtxLazy.scala @@ -0,0 +1,23 @@ +package dotty.tools.dotc +package transform +import core.Contexts.Context + +/** Utility class for lazy values whose evaluation depends on a context. + * This should be used whenever the evaluation of a lazy expression + * depends on some context, but the value can be re-used afterwards + * with a different context. + * + * A typical use case is a lazy val in a phase object which exists once per root context where + * the expression intiializing the lazy val depends only on the root context, but not any changes afterwards. + */ +class CtxLazy[T](expr: Context => T) { + private var myValue: T = _ + private var forced = false + def apply()(implicit ctx: Context): T = { + if (!forced) { + myValue = expr(ctx) + forced = true + } + myValue + } +}
\ No newline at end of file diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 347eb8c01..0e69b9d1f 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -308,5 +308,3 @@ class TreeChecker extends Phase with SymTransformer { } } } - -object TreeChecker extends TreeChecker diff --git a/src/dotty/tools/dotc/transform/TreeTransform.scala b/src/dotty/tools/dotc/transform/TreeTransform.scala index 43d77d766..62cd5dbe9 100644 --- a/src/dotty/tools/dotc/transform/TreeTransform.scala +++ b/src/dotty/tools/dotc/transform/TreeTransform.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import dotty.tools.dotc.ast.tpd @@ -194,7 +195,7 @@ object TreeTransforms { } } - val NoTransform = new TreeTransform { + @sharable val NoTransform = new TreeTransform { def phase = unsupported("phase") idx = -1 } diff --git a/src/dotty/tools/dotc/typer/Implicits.scala b/src/dotty/tools/dotc/typer/Implicits.scala index f79288e74..5444dddb0 100644 --- a/src/dotty/tools/dotc/typer/Implicits.scala +++ b/src/dotty/tools/dotc/typer/Implicits.scala @@ -681,4 +681,4 @@ class TermRefSet(implicit ctx: Context) extends mutable.Traversable[TermRef] { f(TermRef(pre, sym)) } -object EmptyTermRefSet extends TermRefSet()(NoContext) +@sharable object EmptyTermRefSet extends TermRefSet()(NoContext) diff --git a/src/dotty/tools/dotc/typer/Inferencing.scala b/src/dotty/tools/dotc/typer/Inferencing.scala index 79e00db6b..8df544dd6 100644 --- a/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/src/dotty/tools/dotc/typer/Inferencing.scala @@ -257,7 +257,7 @@ trait Inferencing { this: Checking => } /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ -object ForceDegree extends Enumeration { +@sharable object ForceDegree extends Enumeration { val none, // don't force type variables noBottom, // force type variables, fail if forced to Nothing or Null all = Value // force type variables, don't fail diff --git a/src/dotty/tools/dotc/typer/Mode.scala b/src/dotty/tools/dotc/typer/Mode.scala index b903049d2..8dd4c5b68 100644 --- a/src/dotty/tools/dotc/typer/Mode.scala +++ b/src/dotty/tools/dotc/typer/Mode.scala @@ -18,7 +18,7 @@ case class Mode(val bits: Int) extends AnyVal { object Mode { val None = Mode(0) - private var modeName = new Array[String](32) + private val modeName = new Array[String](32) def newMode(bit: Int, name: String): Mode = { modeName(bit) = name diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 13011cb85..40e919ee5 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -151,7 +151,7 @@ object ProtoTypes { * operation is further selection. In this case, the expression need not be a value. * @see checkValue */ - object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed) + @sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed) /** A prototype for selections in pattern constructors */ class UnapplySelectionProto(name: Name) extends SelectionProto(name, WildcardType, NoViewsAllowed) @@ -308,7 +308,7 @@ object ProtoTypes { * * [] _ */ - object AnyFunctionProto extends UncachedGroundType with MatchAlways + @sharable object AnyFunctionProto extends UncachedGroundType with MatchAlways /** Add all parameters in given polytype `pt` to the constraint's domain. * If the constraint contains already some of these parameters in its domain, @@ -429,7 +429,7 @@ object ProtoTypes { def apply(tp: Type) = wildApprox(tp, this) } - private lazy val dummyTree = untpd.Literal(Constant(null)) + @sharable private lazy val dummyTree = untpd.Literal(Constant(null)) /** Dummy tree to be used as an argument of a FunProto or ViewProto type */ object dummyTreeOfType { diff --git a/src/dotty/tools/dotc/util/SourceFile.scala b/src/dotty/tools/dotc/util/SourceFile.scala index 5e8f85e28..da2e54132 100644 --- a/src/dotty/tools/dotc/util/SourceFile.scala +++ b/src/dotty/tools/dotc/util/SourceFile.scala @@ -12,7 +12,7 @@ import ScriptSourceFile._ import Positions._ object ScriptSourceFile { - private val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE) + @sharable private val headerPattern = Pattern.compile("""^(::)?!#.*(\r|\n|\r\n)""", Pattern.MULTILINE) private val headerStarts = List("#!", "::#!") def apply(file: AbstractFile, content: Array[Char]) = { @@ -131,7 +131,7 @@ case class SourceFile(file: AbstractFile, content: Array[Char]) { override def toString = file.toString } -object NoSource extends SourceFile("<no source>", Nil) { +@sharable object NoSource extends SourceFile("<no source>", Nil) { override def exists = false } diff --git a/src/dotty/tools/dotc/util/SourcePosition.scala b/src/dotty/tools/dotc/util/SourcePosition.scala index c88cbc78b..9e02841f2 100644 --- a/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/src/dotty/tools/dotc/util/SourcePosition.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc.util +package dotty.tools +package dotc.util import Positions.{Position, NoPosition} @@ -23,7 +24,7 @@ case class SourcePosition(source: SourceFile, pos: Position) { } /** A sentinel for a non-existing source position */ -object NoSourcePosition extends SourcePosition(NoSource, NoPosition) { +@sharable object NoSourcePosition extends SourcePosition(NoSource, NoPosition) { override def toString = "?" } diff --git a/src/dotty/tools/dotc/util/Stats.scala b/src/dotty/tools/dotc/util/Stats.scala index d899e9546..fdd3602c9 100644 --- a/src/dotty/tools/dotc/util/Stats.scala +++ b/src/dotty/tools/dotc/util/Stats.scala @@ -1,10 +1,11 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package util import core.Contexts._ import collection.mutable -object Stats { +@sharable object Stats { final val enabled = true diff --git a/src/dotty/tools/package.scala b/src/dotty/tools/package.scala index f23b62862..5dae82b71 100644 --- a/src/dotty/tools/package.scala +++ b/src/dotty/tools/package.scala @@ -1,9 +1,13 @@ package dotty +import scala.annotation.Annotation package object tools { type FatalError = scala.reflect.internal.FatalError val FatalError = scala.reflect.internal.FatalError + class sharable extends Annotation + class unshared extends Annotation + val ListOfNil = Nil :: Nil /** True if two lists have the same length. Since calling length on linear sequences |