Skip to content

Commit

Permalink
Avoid leak of internal implementation in tasty.Reflection
Browse files Browse the repository at this point in the history
* Remove `Reflection.internal`: the source of the leak
* Add `CompilerInterface` as a self type
* Add common type abstraction for `Reflection` and `ComplerInterface`
* Add `ComplerInterface.leak` to allow access to internals within the library
  • Loading branch information
nicolasstucki committed Aug 21, 2020
1 parent f2a2dfc commit b6d0c5b
Show file tree
Hide file tree
Showing 13 changed files with 1,044 additions and 1,414 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import scala.internal.tasty.CompilerInterface

import scala.tasty.reflect.TypeTest

class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extends CompilerInterface {
// NOTE: `ReflectionCompilerInterface` should be a class to make sure that all functionality of
// `CompilerInterface` is implemented here.

/** Part of the reflection interface that is implemented by the compiler */
class ReflectionCompilerInterface(val rootContext: Context) extends CompilerInterface {
import tpd._

private given core.Contexts.Context = rootContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import scala.quoted.show.SyntaxHighlight
object ReflectionImpl {

def apply(rootContext: Contexts.Context): scala.tasty.Reflection =
new scala.tasty.Reflection(new ReflectionCompilerInterface(rootContext))
new ReflectionImpl(rootContext)

def showTree(tree: tpd.Tree)(using Contexts.Context): String = {
val refl = new scala.tasty.Reflection(new ReflectionCompilerInterface(MacroExpansion.context(tree)))
val refl = new ReflectionImpl(MacroExpansion.context(tree))
val reflCtx = ctx.asInstanceOf[refl.Context]
val reflTree = tree.asInstanceOf[refl.Tree]
val syntaxHighlight =
Expand All @@ -22,3 +22,6 @@ object ReflectionImpl {
}
}

// NOTE: This class should only mixin the compiler interface and the reflection interface.
// We should not implementt methods here, all should be implemented by `ReflectionCompilerInterface`
class ReflectionImpl(ctx: Context) extends ReflectionCompilerInterface(ctx) with scala.tasty.Reflection
4 changes: 2 additions & 2 deletions library/src-bootstrapped/scala/internal/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import scala.quoted._
}

def unseal(using qctx: QuoteContext): qctx.tasty.Term =
if (qctx.tasty.internal.compilerId != scopeId)
if (scala.internal.tasty.CompilerInterface.leaked(qctx).tasty.compilerId != scopeId)
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
tree.asInstanceOf[qctx.tasty.Term]

Expand Down Expand Up @@ -52,7 +52,7 @@ object Expr {
*/
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: scala.quoted.Expr[Any])(using patternExpr: scala.quoted.Expr[Any],
hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = {
new Matcher.QuoteMatcher[qctx.type].termMatch(scrutineeExpr.unseal, patternExpr.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
new Matcher.QuoteMatcher[qctx.type](qctx).termMatch(scrutineeExpr.unseal, patternExpr.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
}

/** Returns a null expresssion equivalent to `'{null}` */
Expand Down
30 changes: 16 additions & 14 deletions library/src-bootstrapped/scala/internal/quoted/Matcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ object Matcher {
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.fromAbove`")
class fromAbove extends Annotation

class QuoteMatcher[QCtx <: QuoteContext & Singleton](using val qctx: QCtx) {
class QuoteMatcher[QCtx <: QuoteContext & Singleton](val qctx0: QCtx) {
val qctx = scala.internal.tasty.CompilerInterface.leaked(qctx0)

// TODO improve performance

// TODO use flag from qctx.tasty.rootContext. Maybe -debug or add -debug-macros
Expand All @@ -147,14 +149,14 @@ object Matcher {
def termMatch(scrutineeTerm: Term, patternTerm: Term, hasTypeSplices: Boolean): Option[Tuple] = {
given Env = Map.empty
if (hasTypeSplices) {
val ctx: Context = internal.Constraints_init(rootContext)
val ctx: Context = qctx.tasty.Constraints_init(rootContext)
given Context = ctx
val matchings = scrutineeTerm =?= patternTerm
// After matching and doing all subtype checks, we have to approximate all the type bindings
// that we have found and seal them in a quoted.Type
matchings.asOptionOfTuple.map { tup =>
Tuple.fromArray(tup.toArray.map { // TODO improve performance
case x: SymBinding => internal.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
case x: SymBinding => qctx.tasty.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
case x => x
})
}
Expand All @@ -168,14 +170,14 @@ object Matcher {
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree, hasTypeSplices: Boolean): Option[Tuple] = {
given Env = Map.empty
if (hasTypeSplices) {
val ctx: Context = internal.Constraints_init(rootContext)
val ctx: Context = qctx.tasty.Constraints_init(rootContext)
given Context = ctx
val matchings = scrutineeTypeTree =?= patternTypeTree
// After matching and doing all subtype checks, we have to approximate all the type bindings
// that we have found and seal them in a quoted.Type
matchings.asOptionOfTuple.map { tup =>
Tuple.fromArray(tup.toArray.map { // TODO improve performance
case x: SymBinding => internal.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
case x: SymBinding => qctx.tasty.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
case x => x
})
}
Expand All @@ -190,13 +192,13 @@ object Matcher {
private def hasFromAboveAnnotation(sym: Symbol) = sym.annots.exists(isFromAboveAnnotation)

private def isPatternTypeAnnotation(tree: Tree): Boolean = tree match {
case New(tpt) => tpt.symbol == internal.Definitions_InternalQuotedMatcher_patternTypeAnnot
case annot => annot.symbol.owner == internal.Definitions_InternalQuotedMatcher_patternTypeAnnot
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
}

private def isFromAboveAnnotation(tree: Tree): Boolean = tree match {
case New(tpt) => tpt.symbol == internal.Definitions_InternalQuotedMatcher_fromAboveAnnot
case annot => annot.symbol.owner == internal.Definitions_InternalQuotedMatcher_fromAboveAnnot
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
}

/** Check that all trees match with `mtch` and concatenate the results with &&& */
Expand Down Expand Up @@ -250,22 +252,22 @@ object Matcher {
/* Term hole */
// Match a scala.internal.Quoted.patternHole typed as a repeated argument and return the scrutinee tree
case (scrutinee @ Typed(s, tpt1), Typed(TypeApply(patternHole, tpt :: Nil), tpt2))
if patternHole.symbol == internal.Definitions_InternalQuotedMatcher_patternHole &&
if patternHole.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternHole &&
s.tpe <:< tpt.tpe &&
tpt2.tpe.derivesFrom(defn.RepeatedParamClass) =>
matched(scrutinee.seal)

/* Term hole */
// Match a scala.internal.Quoted.patternHole and return the scrutinee tree
case (ClosedPatternTerm(scrutinee), TypeApply(patternHole, tpt :: Nil))
if patternHole.symbol == internal.Definitions_InternalQuotedMatcher_patternHole &&
if patternHole.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternHole &&
scrutinee.tpe <:< tpt.tpe =>
matched(scrutinee.seal)

/* Higher order term hole */
// Matches an open term and wraps it into a lambda that provides the free variables
case (scrutinee, pattern @ Apply(TypeApply(Ident("higherOrderHole"), List(Inferred())), Repeated(args, _) :: Nil))
if pattern.symbol == internal.Definitions_InternalQuotedMatcher_higherOrderHole =>
if pattern.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_higherOrderHole =>

def bodyFn(lambdaArgs: List[Tree]): Tree = {
val argsMap = args.map(_.symbol).zip(lambdaArgs.asInstanceOf[List[Term]]).toMap
Expand Down Expand Up @@ -323,7 +325,7 @@ object Matcher {
fn1 =?= fn2 &&& args1 =?= args2

case (Block(stats1, expr1), Block(binding :: stats2, expr2)) if isTypeBinding(binding) =>
qctx.tasty.internal.Constraints_add(summon[Context])(binding.symbol :: Nil)
qctx.tasty.Constraints_add(summon[Context])(binding.symbol :: Nil)
matched(new SymBinding(binding.symbol, hasFromAboveAnnotation(binding.symbol))) &&& Block(stats1, expr1) =?= Block(stats2, expr2)

/* Match block */
Expand All @@ -340,7 +342,7 @@ object Matcher {

case (scrutinee, Block(typeBindings, expr2)) if typeBindings.forall(isTypeBinding) =>
val bindingSymbols = typeBindings.map(_.symbol)
qctx.tasty.internal.Constraints_add(summon[Context])(bindingSymbols)
qctx.tasty.Constraints_add(summon[Context])(bindingSymbols)
bindingSymbols.foldRight(scrutinee =?= expr2)((x, acc) => matched(new SymBinding(x, hasFromAboveAnnotation(x))) &&& acc)

/* Match if */
Expand Down
4 changes: 2 additions & 2 deletions library/src-bootstrapped/scala/internal/quoted/Type.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class Type[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quote

/** View this expression `quoted.Type[T]` as a `TypeTree` */
def unseal(using qctx: QuoteContext): qctx.tasty.TypeTree =
if (qctx.tasty.internal.compilerId != scopeId)
if (scala.internal.tasty.CompilerInterface.leaked(qctx).tasty.compilerId != scopeId)
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
typeTree.asInstanceOf[qctx.tasty.TypeTree]

Expand All @@ -39,7 +39,7 @@ object Type {
*/
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeType: scala.quoted.Type[_])(using patternType: scala.quoted.Type[_],
hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = {
new Matcher.QuoteMatcher[qctx.type].typeTreeMatch(scrutineeType.unseal, patternType.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
new Matcher.QuoteMatcher[qctx.type](qctx).typeTreeMatch(scrutineeType.unseal, patternType.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
}

def Unit: QuoteContext ?=> quoted.Type[Unit] =
Expand Down
12 changes: 6 additions & 6 deletions library/src-bootstrapped/scala/internal/quoted/Unpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ object Unpickler {
* replacing splice nodes with `args`
*/
def unpickleExpr[T](repr: PickledQuote, args: PickledArgs): QuoteContext ?=> Expr[T] =
val ctx = summon[QuoteContext]
val tree = ctx.tasty.internal.unpickleExpr(repr, args)
new scala.internal.quoted.Expr(tree, ctx.tasty.internal.compilerId).asInstanceOf[Expr[T]]
val qctx = scala.internal.tasty.CompilerInterface.leaked(summon[QuoteContext])
val tree = qctx.tasty.unpickleExpr(repr, args)
new scala.internal.quoted.Expr(tree, qctx.tasty.compilerId).asInstanceOf[Expr[T]]

/** Unpickle `repr` which represents a pickled `Type` tree,
* replacing splice nodes with `args`
*/
def unpickleType[T](repr: PickledQuote, args: PickledArgs): QuoteContext ?=> Type[T] =
val ctx = summon[QuoteContext]
val tree = ctx.tasty.internal.unpickleType(repr, args)
new scala.internal.quoted.Type(tree, ctx.tasty.internal.compilerId).asInstanceOf[Type[T]]
val qctx = scala.internal.tasty.CompilerInterface.leaked(summon[QuoteContext])
val tree = qctx.tasty.unpickleType(repr, args)
new scala.internal.quoted.Type(tree, qctx.tasty.compilerId).asInstanceOf[Type[T]]

}
3 changes: 2 additions & 1 deletion library/src-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ object Expr {
* Otherwise returns `expr`.
*/
def betaReduce[T](expr: Expr[T])(using qctx: QuoteContext): Expr[T] =
qctx.tasty.internal.betaReduce(expr.unseal) match
val qctx2 = scala.internal.tasty.CompilerInterface.leaked(qctx)
qctx2.tasty.betaReduce(expr.unseal) match
case Some(expr1) => expr1.seal.asInstanceOf[Expr[T]]
case _ => expr

Expand Down
4 changes: 2 additions & 2 deletions library/src-bootstrapped/scala/quoted/Lambda.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ object Lambda {
import qctx.tasty._
val argTypes = functionType.unseal.tpe match
case AppliedType(_, functionArguments) => functionArguments.init.asInstanceOf[List[Type]]
qctx.tasty.internal.lambdaExtractor(expr.unseal, argTypes).map { fn =>
val qctx2 = scala.internal.tasty.CompilerInterface.leaked(qctx)
qctx2.tasty.lambdaExtractor(expr.unseal, argTypes).map { fn =>
def f(args: Tuple.Map[Args, Expr]): Expr[Res] =
fn(args.toArray.toList.map(_.asInstanceOf[Expr[Any]].unseal)).seal.asInstanceOf[Expr[Res]]
tg.untupled(f)
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import scala.quoted._
}

def unseal(using qctx: QuoteContext): qctx.tasty.Term =
if (qctx.tasty.internal.compilerId != scopeId)
if (scala.internal.tasty.CompilerInterface.leaked(qctx).tasty.compilerId != scopeId)
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
tree.asInstanceOf[qctx.tasty.Term]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ final class Type[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quote

/** View this expression `quoted.Type[T]` as a `TypeTree` */
def unseal(using qctx: QuoteContext): qctx.tasty.TypeTree =
if (qctx.tasty.internal.compilerId != scopeId)
if (scala.internal.tasty.CompilerInterface.leaked(qctx).tasty.compilerId != scopeId)
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
typeTree.asInstanceOf[qctx.tasty.TypeTree]

Expand Down
Loading

0 comments on commit b6d0c5b

Please sign in to comment.