From 64f65182f6e4f80b03d45923e02441dafe0755b4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 1 Jul 2015 15:28:11 +0200 Subject: Add reentrancy checking New miniphase CheckRentrant verifies that compiled program is without vars accessible through global roots if -Ycheck-reentrant option is set. Known shortcoming: Array elements are currently not considered as vars. This is because in many programs arrays are used as an efficient container for immutable fields. --- src/dotty/tools/dotc/Compiler.scala | 3 +- src/dotty/tools/dotc/config/ScalaSettings.scala | 1 + .../tools/dotc/transform/CheckReentrant.scala | 85 ++++++++++++++++++++++ src/dotty/tools/dotc/transform/CtxLazy.scala | 12 +++ src/dotty/tools/package.scala | 4 + 5 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/dotty/tools/dotc/transform/CheckReentrant.scala create mode 100644 src/dotty/tools/dotc/transform/CtxLazy.scala (limited to 'src/dotty/tools') 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/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/transform/CheckReentrant.scala b/src/dotty/tools/dotc/transform/CheckReentrant.scala new file mode 100644 index 000000000..aff8a1e7b --- /dev/null +++ b/src/dotty/tools/dotc/transform/CheckReentrant.scala @@ -0,0 +1,85 @@ +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._ +import util.CtxLazy + + +/** The first tree transform + * - ensures there are companion objects for all classes except module classes + * - eliminates some kinds of trees: Imports, NamedArgs + * - stubs out native methods + */ +class CheckReentrant extends MiniPhaseTransform { thisTransformer => + import ast.tpd._ + + override def phaseName = "checkReentrant" + + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp + + 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 = { + println(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)) { + println(i"GLOBAL ${sym.showLocated}: ${sym.info} <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + 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..c0bac4e11 --- /dev/null +++ b/src/dotty/tools/dotc/transform/CtxLazy.scala @@ -0,0 +1,12 @@ +package dotty.tools.dotc +package util +import core.Contexts.Context + +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) + myValue + } +} \ No newline at end of file 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 -- cgit v1.2.3