Skip to content

Commit b6d0c5b

Browse files
committed
Avoid leak of internal implementation in tasty.Reflection
* 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
1 parent f2a2dfc commit b6d0c5b

File tree

13 files changed

+1044
-1414
lines changed

13 files changed

+1044
-1414
lines changed

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ import scala.internal.tasty.CompilerInterface
2121

2222
import scala.tasty.reflect.TypeTest
2323

24-
class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extends CompilerInterface {
24+
// NOTE: `ReflectionCompilerInterface` should be a class to make sure that all functionality of
25+
// `CompilerInterface` is implemented here.
26+
27+
/** Part of the reflection interface that is implemented by the compiler */
28+
class ReflectionCompilerInterface(val rootContext: Context) extends CompilerInterface {
2529
import tpd._
2630

2731
private given core.Contexts.Context = rootContext

compiler/src/dotty/tools/dotc/tastyreflect/ReflectionImpl.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import scala.quoted.show.SyntaxHighlight
99
object ReflectionImpl {
1010

1111
def apply(rootContext: Contexts.Context): scala.tasty.Reflection =
12-
new scala.tasty.Reflection(new ReflectionCompilerInterface(rootContext))
12+
new ReflectionImpl(rootContext)
1313

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

25+
// NOTE: This class should only mixin the compiler interface and the reflection interface.
26+
// We should not implementt methods here, all should be implemented by `ReflectionCompilerInterface`
27+
class ReflectionImpl(ctx: Context) extends ReflectionCompilerInterface(ctx) with scala.tasty.Reflection

library/src-bootstrapped/scala/internal/quoted/Expr.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import scala.quoted._
1919
}
2020

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

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

5858
/** Returns a null expresssion equivalent to `'{null}` */

library/src-bootstrapped/scala/internal/quoted/Matcher.scala

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ object Matcher {
122122
@compileTimeOnly("Illegal reference to `scala.internal.quoted.CompileTime.fromAbove`")
123123
class fromAbove extends Annotation
124124

125-
class QuoteMatcher[QCtx <: QuoteContext & Singleton](using val qctx: QCtx) {
125+
class QuoteMatcher[QCtx <: QuoteContext & Singleton](val qctx0: QCtx) {
126+
val qctx = scala.internal.tasty.CompilerInterface.leaked(qctx0)
127+
126128
// TODO improve performance
127129

128130
// TODO use flag from qctx.tasty.rootContext. Maybe -debug or add -debug-macros
@@ -147,14 +149,14 @@ object Matcher {
147149
def termMatch(scrutineeTerm: Term, patternTerm: Term, hasTypeSplices: Boolean): Option[Tuple] = {
148150
given Env = Map.empty
149151
if (hasTypeSplices) {
150-
val ctx: Context = internal.Constraints_init(rootContext)
152+
val ctx: Context = qctx.tasty.Constraints_init(rootContext)
151153
given Context = ctx
152154
val matchings = scrutineeTerm =?= patternTerm
153155
// After matching and doing all subtype checks, we have to approximate all the type bindings
154156
// that we have found and seal them in a quoted.Type
155157
matchings.asOptionOfTuple.map { tup =>
156158
Tuple.fromArray(tup.toArray.map { // TODO improve performance
157-
case x: SymBinding => internal.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
159+
case x: SymBinding => qctx.tasty.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
158160
case x => x
159161
})
160162
}
@@ -168,14 +170,14 @@ object Matcher {
168170
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree, hasTypeSplices: Boolean): Option[Tuple] = {
169171
given Env = Map.empty
170172
if (hasTypeSplices) {
171-
val ctx: Context = internal.Constraints_init(rootContext)
173+
val ctx: Context = qctx.tasty.Constraints_init(rootContext)
172174
given Context = ctx
173175
val matchings = scrutineeTypeTree =?= patternTypeTree
174176
// After matching and doing all subtype checks, we have to approximate all the type bindings
175177
// that we have found and seal them in a quoted.Type
176178
matchings.asOptionOfTuple.map { tup =>
177179
Tuple.fromArray(tup.toArray.map { // TODO improve performance
178-
case x: SymBinding => internal.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
180+
case x: SymBinding => qctx.tasty.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
179181
case x => x
180182
})
181183
}
@@ -190,13 +192,13 @@ object Matcher {
190192
private def hasFromAboveAnnotation(sym: Symbol) = sym.annots.exists(isFromAboveAnnotation)
191193

192194
private def isPatternTypeAnnotation(tree: Tree): Boolean = tree match {
193-
case New(tpt) => tpt.symbol == internal.Definitions_InternalQuotedMatcher_patternTypeAnnot
194-
case annot => annot.symbol.owner == internal.Definitions_InternalQuotedMatcher_patternTypeAnnot
195+
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
196+
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
195197
}
196198

197199
private def isFromAboveAnnotation(tree: Tree): Boolean = tree match {
198-
case New(tpt) => tpt.symbol == internal.Definitions_InternalQuotedMatcher_fromAboveAnnot
199-
case annot => annot.symbol.owner == internal.Definitions_InternalQuotedMatcher_fromAboveAnnot
200+
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
201+
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
200202
}
201203

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

258260
/* Term hole */
259261
// Match a scala.internal.Quoted.patternHole and return the scrutinee tree
260262
case (ClosedPatternTerm(scrutinee), TypeApply(patternHole, tpt :: Nil))
261-
if patternHole.symbol == internal.Definitions_InternalQuotedMatcher_patternHole &&
263+
if patternHole.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternHole &&
262264
scrutinee.tpe <:< tpt.tpe =>
263265
matched(scrutinee.seal)
264266

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

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

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

329331
/* Match block */
@@ -340,7 +342,7 @@ object Matcher {
340342

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

346348
/* Match if */

library/src-bootstrapped/scala/internal/quoted/Type.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class Type[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quote
1414

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

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

4545
def Unit: QuoteContext ?=> quoted.Type[Unit] =

library/src-bootstrapped/scala/internal/quoted/Unpickler.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ object Unpickler {
1212
* replacing splice nodes with `args`
1313
*/
1414
def unpickleExpr[T](repr: PickledQuote, args: PickledArgs): QuoteContext ?=> Expr[T] =
15-
val ctx = summon[QuoteContext]
16-
val tree = ctx.tasty.internal.unpickleExpr(repr, args)
17-
new scala.internal.quoted.Expr(tree, ctx.tasty.internal.compilerId).asInstanceOf[Expr[T]]
15+
val qctx = scala.internal.tasty.CompilerInterface.leaked(summon[QuoteContext])
16+
val tree = qctx.tasty.unpickleExpr(repr, args)
17+
new scala.internal.quoted.Expr(tree, qctx.tasty.compilerId).asInstanceOf[Expr[T]]
1818

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

2727
}

library/src-bootstrapped/scala/quoted/Expr.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ object Expr {
7575
* Otherwise returns `expr`.
7676
*/
7777
def betaReduce[T](expr: Expr[T])(using qctx: QuoteContext): Expr[T] =
78-
qctx.tasty.internal.betaReduce(expr.unseal) match
78+
val qctx2 = scala.internal.tasty.CompilerInterface.leaked(qctx)
79+
qctx2.tasty.betaReduce(expr.unseal) match
7980
case Some(expr1) => expr1.seal.asInstanceOf[Expr[T]]
8081
case _ => expr
8182

library/src-bootstrapped/scala/quoted/Lambda.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ object Lambda {
1919
import qctx.tasty._
2020
val argTypes = functionType.unseal.tpe match
2121
case AppliedType(_, functionArguments) => functionArguments.init.asInstanceOf[List[Type]]
22-
qctx.tasty.internal.lambdaExtractor(expr.unseal, argTypes).map { fn =>
22+
val qctx2 = scala.internal.tasty.CompilerInterface.leaked(qctx)
23+
qctx2.tasty.lambdaExtractor(expr.unseal, argTypes).map { fn =>
2324
def f(args: Tuple.Map[Args, Expr]): Expr[Res] =
2425
fn(args.toArray.toList.map(_.asInstanceOf[Expr[Any]].unseal)).seal.asInstanceOf[Expr[Res]]
2526
tg.untupled(f)
2627
}
27-
2828
}
2929

3030
}

library/src-non-bootstrapped/scala/internal/quoted/Expr.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import scala.quoted._
1919
}
2020

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

library/src-non-bootstrapped/scala/internal/quoted/Type.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ final class Type[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quote
1414

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

0 commit comments

Comments
 (0)