From 488c7f97eed9083842c5386da49e76a5d5bb3c9b Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 7 Aug 2019 14:09:39 +0200 Subject: [PATCH 1/2] Fix #4730: Detect nested calls to run on the same toolbox --- .../dotty/tools/dotc/quoted/ToolboxImpl.scala | 11 +++++++++- library/src/scala/quoted/Toolbox.scala | 3 +++ library/src/scala/quoted/package.scala | 2 -- tests/run-with-compiler/i4730.scala | 20 +++++++++++++++++++ tests/run-with-compiler/i6754.check | 1 - tests/run-with-compiler/i6754.scala | 8 +++++++- 6 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 tests/run-with-compiler/i4730.scala diff --git a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala index 9e2f934040ea..71bb62302696 100644 --- a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala @@ -15,8 +15,17 @@ object ToolboxImpl { private[this] val driver: QuoteDriver = new QuoteDriver(appClassloader) + private[this] var running = false + def run[T](exprBuilder: QuoteContext => Expr[T]): T = synchronized { - driver.run(exprBuilder, settings) + try { + if (running) // detected nested run + throw new scala.quoted.Toolbox.ToolboxAlreadyRunning() + running = true + driver.run(exprBuilder, settings) + } finally { + running = false + } } } diff --git a/library/src/scala/quoted/Toolbox.scala b/library/src/scala/quoted/Toolbox.scala index 42fdb2d895fa..fe20bbc2bb50 100644 --- a/library/src/scala/quoted/Toolbox.scala +++ b/library/src/scala/quoted/Toolbox.scala @@ -56,4 +56,7 @@ object Toolbox { } class ToolboxNotFoundException(msg: String, cause: ClassNotFoundException) extends Exception(msg, cause) + + class ToolboxAlreadyRunning extends Exception("Cannot call `scala.quoted.run(...)` within another `run(...)`") + } diff --git a/library/src/scala/quoted/package.scala b/library/src/scala/quoted/package.scala index 0868eee2188e..a601f069016d 100644 --- a/library/src/scala/quoted/package.scala +++ b/library/src/scala/quoted/package.scala @@ -15,8 +15,6 @@ package object quoted { * * This method should not be called in a context where there is already has a `QuoteContext` * such as within a `run` or a `withQuoteContext`. - * - * May throw a FreeVariableError on expressions that came from a macro. */ def run[T](expr: given QuoteContext => Expr[T]) given (toolbox: Toolbox): T = toolbox.run(expr given _) diff --git a/tests/run-with-compiler/i4730.scala b/tests/run-with-compiler/i4730.scala new file mode 100644 index 000000000000..9ea4a9a3ccd1 --- /dev/null +++ b/tests/run-with-compiler/i4730.scala @@ -0,0 +1,20 @@ +import scala.quoted._ + +object Test { + implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader) + def ret given QuoteContext: Expr[Int => Int] = '{ (x: Int) => + ${ + val z = run('{x + 1}) // throws a ToolboxAlreadyRunning + z.toExpr + } + } + def main(args: Array[String]): Unit = { + try { + run(ret).apply(10) + throw new Exception + } catch { + case ex: scala.quoted.Toolbox.ToolboxAlreadyRunning => + // ok + } + } +} diff --git a/tests/run-with-compiler/i6754.check b/tests/run-with-compiler/i6754.check index 3bd1f0e29744..257cc5642cb1 100644 --- a/tests/run-with-compiler/i6754.check +++ b/tests/run-with-compiler/i6754.check @@ -1,2 +1 @@ foo -bar diff --git a/tests/run-with-compiler/i6754.scala b/tests/run-with-compiler/i6754.scala index da60fc80dc0f..e30c2097a818 100644 --- a/tests/run-with-compiler/i6754.scala +++ b/tests/run-with-compiler/i6754.scala @@ -10,6 +10,12 @@ object Test { println("foo") run(x) } - run(y) + try { + run(y) + throw new Exception + } catch { + case ex: java.lang.reflect.InvocationTargetException => + assert(ex.getTargetException.isInstanceOf[scala.quoted.Toolbox.ToolboxAlreadyRunning]) + } } } From b35936d80b8b9ea683d415ed47f8cacd88265a24 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 7 Aug 2019 16:48:04 +0200 Subject: [PATCH 2/2] Fix #6992: Add back heuristic to detect scope extrusion This heristic can detects and fails-fast when an Expr or Type is used with a different compiler instance. --- .../dotc/core/quoted/PickledQuotes.scala | 15 ++++++++---- .../tools/dotc/core/tasty/TreeUnpickler.scala | 5 ++-- .../dotty/tools/dotc/quoted/ToolboxImpl.scala | 15 +++++++++++- .../ReflectionCompilerInterface.scala | 10 ++++---- .../dotty/tools/dotc/transform/Splicer.scala | 6 ++--- library/src/scala/quoted/Expr.scala | 4 ++-- library/src/scala/quoted/Toolbox.scala | 2 +- library/src/scala/quoted/Type.scala | 2 +- tests/run-macros/i6992/Macro_1.scala | 23 +++++++++++++++++++ tests/run-macros/i6992/Test_2.scala | 9 ++++++++ tests/run-with-compiler/i4730.scala | 4 ++-- tests/run-with-compiler/i6754.scala | 2 +- 12 files changed, 76 insertions(+), 21 deletions(-) create mode 100644 tests/run-macros/i6992/Macro_1.scala create mode 100644 tests/run-macros/i6992/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index fbac02bebd8c..91f26147452d 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -14,6 +14,7 @@ import dotty.tools.dotc.core.Types._ import dotty.tools.dotc.core.tasty.TreePickler.Hole import dotty.tools.dotc.core.tasty.{PositionPickler, TastyPickler, TastyPrinter, TastyString} import dotty.tools.dotc.core.tasty.TreeUnpickler.UnpickleMode +import dotty.tools.dotc.quoted.ToolboxImpl import dotty.tools.dotc.tastyreflect.ReflectionImpl import scala.internal.quoted._ @@ -35,12 +36,18 @@ object PickledQuotes { } /** Transform the expression into its fully spliced Tree */ - def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = - healOwner(expr.asInstanceOf[TastyTreeExpr[Tree]].tree) + def quotedExprToTree[T](expr: quoted.Expr[T])(implicit ctx: Context): Tree = { + val expr1 = expr.asInstanceOf[TastyTreeExpr[Tree]] + ToolboxImpl.checkScopeId(expr1.scopeId) + healOwner(expr1.tree) + } /** Transform the expression into its fully spliced TypeTree */ - def quotedTypeToTree(expr: quoted.Type[_])(implicit ctx: Context): Tree = - healOwner(expr.asInstanceOf[TreeType[Tree]].typeTree) + def quotedTypeToTree(tpe: quoted.Type[_])(implicit ctx: Context): Tree = { + val tpe1 = tpe.asInstanceOf[TreeType[Tree]] + ToolboxImpl.checkScopeId(tpe1.scopeId) + healOwner(tpe1.typeTree) + } private def dealiasTypeTags(tp: Type)(implicit ctx: Context): Type = new TypeMap() { override def apply(tp: Type): Type = { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f729c48ec9c0..259ac00b8cce 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -31,6 +31,7 @@ import scala.collection.mutable.ListBuffer import scala.collection.mutable import config.Printers.pickling import core.quoted.PickledQuotes +import dotty.tools.dotc.quoted.ToolboxImpl import scala.quoted import scala.internal.quoted.{TastyTreeExpr, TreeType} @@ -1273,8 +1274,8 @@ class TreeUnpickler(reader: TastyReader, val args = until(end)(readTerm()) val splice = splices(idx) def wrap(arg: Tree) = - if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg) - else new TreeType(arg) + if (arg.isTerm) given (qctx: scala.quoted.QuoteContext) => new TastyTreeExpr(arg, ToolboxImpl.scopeId) + else new TreeType(arg, ToolboxImpl.scopeId) val reifiedArgs = args.map(wrap) val filled = if (isType) { val quotedType = splice.asInstanceOf[Seq[Any] => quoted.Type[_]](reifiedArgs) diff --git a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala index 71bb62302696..5443aeba3f15 100644 --- a/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala +++ b/compiler/src/dotty/tools/dotc/quoted/ToolboxImpl.scala @@ -1,5 +1,7 @@ package dotty.tools.dotc.quoted +import dotty.tools.dotc.core.Contexts.Context + import scala.quoted._ /** Default runners for quoted expressions */ @@ -20,14 +22,25 @@ object ToolboxImpl { def run[T](exprBuilder: QuoteContext => Expr[T]): T = synchronized { try { if (running) // detected nested run - throw new scala.quoted.Toolbox.ToolboxAlreadyRunning() + throw new scala.quoted.Toolbox.RunScopeException() running = true driver.run(exprBuilder, settings) } finally { running = false } } + } + type ScopeId = Int + + private[dotty] def checkScopeId(id: ScopeId) given Context: Unit = { + if (id != scopeId) + throw new Toolbox.RunScopeException } + // TODO Explore more fine grained scope ids. + // This id can only differentiate scope extrusion from one compiler instance to another. + private[dotty] def scopeId given Context: ScopeId = + the[Context].outersIterator.toList.last.hashCode() + } diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index b04fcc45bf04..8eb0bf628cce 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -34,10 +34,10 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend // def unpickleExpr(repr: Unpickler.PickledQuote, args: Unpickler.PickledExprArgs): scala.quoted.Expr[_] = - new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args)) + new scala.internal.quoted.TastyTreeExpr(PickledQuotes.unpickleExpr(repr, args), compilerId) def unpickleType(repr: Unpickler.PickledQuote, args: Unpickler.PickledTypeArgs): scala.quoted.Type[_] = - new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args)) + new scala.internal.quoted.TreeType(PickledQuotes.unpickleType(repr, args), compilerId) // // CONTEXT @@ -1752,7 +1752,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend tpd.Closure(closureMethod, tss => etaExpand(new tpd.TreeOps(term).appliedToArgs(tss.head))) case _ => term } - new scala.internal.quoted.TastyTreeExpr(etaExpand(self)) + new scala.internal.quoted.TastyTreeExpr(etaExpand(self), compilerId) } /** Checked cast to a `quoted.Expr[U]` */ @@ -1773,7 +1773,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend /** Convert `Type` to an `quoted.Type[_]` */ def QuotedType_seal(self: Type) given (ctx: Context): scala.quoted.Type[_] = { val dummySpan = ctx.owner.span // FIXME - new scala.internal.quoted.TreeType(tpd.TypeTree(self).withSpan(dummySpan)) + new scala.internal.quoted.TreeType(tpd.TypeTree(self).withSpan(dummySpan), compilerId) } // @@ -1934,4 +1934,6 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend private def withDefaultPos[T <: Tree](fn: given Context => T) given (ctx: Context): T = (fn given ctx.withSource(rootPosition.source)).withSpan(rootPosition.span) + private def compilerId: Int = rootContext.outersIterator.toList.last.hashCode() + } diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index f213fa0bbd98..8ce3420db72b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -25,7 +25,7 @@ import dotty.tools.repl.AbstractFileClassLoader import scala.reflect.ClassTag -import dotty.tools.dotc.quoted.QuoteContext +import dotty.tools.dotc.quoted.{QuoteContext, ToolboxImpl} /** Utility class to splice quoted expressions */ object Splicer { @@ -251,10 +251,10 @@ object Splicer { } private def interpretQuote(tree: Tree)(implicit env: Env): Object = - new scala.internal.quoted.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span)) + new scala.internal.quoted.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span), ToolboxImpl.scopeId) private def interpretTypeQuote(tree: Tree)(implicit env: Env): Object = - new scala.internal.quoted.TreeType(tree) + new scala.internal.quoted.TreeType(tree, ToolboxImpl.scopeId) private def interpretLiteral(value: Any)(implicit env: Env): Object = value.asInstanceOf[Object] diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index 786065499432..9cc2f624647d 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -79,7 +79,7 @@ package quoted { package internal { package quoted { - import scala.quoted.{Expr, QuoteContext} + import scala.quoted.Expr /** An Expr backed by a tree. Only the current compiler trees are allowed. * @@ -88,7 +88,7 @@ package internal { * * May contain references to code defined outside this TastyTreeExpr instance. */ - final class TastyTreeExpr[Tree](val tree: Tree) extends Expr[Any] { + final class TastyTreeExpr[Tree](val tree: Tree, val scopeId: Int) extends Expr[Any] { override def toString: String = s"Expr()" } diff --git a/library/src/scala/quoted/Toolbox.scala b/library/src/scala/quoted/Toolbox.scala index fe20bbc2bb50..e0e24c82f856 100644 --- a/library/src/scala/quoted/Toolbox.scala +++ b/library/src/scala/quoted/Toolbox.scala @@ -57,6 +57,6 @@ object Toolbox { class ToolboxNotFoundException(msg: String, cause: ClassNotFoundException) extends Exception(msg, cause) - class ToolboxAlreadyRunning extends Exception("Cannot call `scala.quoted.run(...)` within another `run(...)`") + class RunScopeException extends Exception("Cannot call `scala.quoted.run(...)` within a macro or another `run(...)`") } diff --git a/library/src/scala/quoted/Type.scala b/library/src/scala/quoted/Type.scala index 25a6ed515c7a..d9a578721b5c 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src/scala/quoted/Type.scala @@ -70,7 +70,7 @@ package internal { package quoted { /** An Type backed by a tree */ - final class TreeType[Tree](val typeTree: Tree) extends scala.quoted.Type[Any] { + final class TreeType[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quoted.Type[Any] { override def toString: String = s"Type()" } diff --git a/tests/run-macros/i6992/Macro_1.scala b/tests/run-macros/i6992/Macro_1.scala new file mode 100644 index 000000000000..f0e76e6b07a2 --- /dev/null +++ b/tests/run-macros/i6992/Macro_1.scala @@ -0,0 +1,23 @@ + +import scala.quoted._, scala.quoted.matching._ +import delegate scala.quoted._ + +delegate for Toolbox = Toolbox.make(getClass.getClassLoader) + +object macros { + inline def mcr(x: => Any): Any = ${mcrImpl('x)} + + class Foo { val x = 10 } + + def mcrImpl(body: Expr[Any]) given (ctx: QuoteContext): Expr[Any] = { + import ctx.tasty._ + try { + body match { + case '{$x: Foo} => run(x).x.toExpr + } + } catch { + case _: scala.quoted.Toolbox.RunScopeException => + '{"OK"} + } + } +} diff --git a/tests/run-macros/i6992/Test_2.scala b/tests/run-macros/i6992/Test_2.scala new file mode 100644 index 000000000000..13ff747a80c5 --- /dev/null +++ b/tests/run-macros/i6992/Test_2.scala @@ -0,0 +1,9 @@ +import macros._ + +object Test { + val foo = new Foo + + def main(args: Array[String]) = { + println(mcr {foo}) + } +} diff --git a/tests/run-with-compiler/i4730.scala b/tests/run-with-compiler/i4730.scala index 9ea4a9a3ccd1..f6007b18d279 100644 --- a/tests/run-with-compiler/i4730.scala +++ b/tests/run-with-compiler/i4730.scala @@ -4,7 +4,7 @@ object Test { implicit val toolbox: scala.quoted.Toolbox = scala.quoted.Toolbox.make(getClass.getClassLoader) def ret given QuoteContext: Expr[Int => Int] = '{ (x: Int) => ${ - val z = run('{x + 1}) // throws a ToolboxAlreadyRunning + val z = run('{x + 1}) // throws a RunScopeException z.toExpr } } @@ -13,7 +13,7 @@ object Test { run(ret).apply(10) throw new Exception } catch { - case ex: scala.quoted.Toolbox.ToolboxAlreadyRunning => + case ex: scala.quoted.Toolbox.RunScopeException => // ok } } diff --git a/tests/run-with-compiler/i6754.scala b/tests/run-with-compiler/i6754.scala index e30c2097a818..596dc0900caa 100644 --- a/tests/run-with-compiler/i6754.scala +++ b/tests/run-with-compiler/i6754.scala @@ -15,7 +15,7 @@ object Test { throw new Exception } catch { case ex: java.lang.reflect.InvocationTargetException => - assert(ex.getTargetException.isInstanceOf[scala.quoted.Toolbox.ToolboxAlreadyRunning]) + assert(ex.getTargetException.isInstanceOf[scala.quoted.Toolbox.RunScopeException]) } } }