Skip to content

Commit ba5d236

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 ba5d236

File tree

13 files changed

+1066
-1423
lines changed

13 files changed

+1066
-1423
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: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ import scala.quoted._
1919
}
2020

2121
def unseal(using qctx: QuoteContext): qctx.tasty.Term =
22-
if (qctx.tasty.internal.compilerId != scopeId)
23-
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
22+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
23+
if (qctx.tasty.compilerId != scopeId)
24+
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
25+
}
2426
tree.asInstanceOf[qctx.tasty.Term]
2527

2628
override def hashCode: Int = tree.hashCode
@@ -52,7 +54,7 @@ object Expr {
5254
*/
5355
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: scala.quoted.Expr[Any])(using patternExpr: scala.quoted.Expr[Any],
5456
hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = {
55-
new Matcher.QuoteMatcher[qctx.type].termMatch(scrutineeExpr.unseal, patternExpr.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
57+
new Matcher.QuoteMatcher[qctx.type](qctx).termMatch(scrutineeExpr.unseal, patternExpr.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
5658
}
5759

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

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

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ 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 = qctx0.asInstanceOf[qctx0.type {
127+
val tasty: qctx0.tasty.type & scala.internal.tasty.CompilerInterface
128+
}]
126129
// TODO improve performance
127130

128131
// TODO use flag from qctx.tasty.rootContext. Maybe -debug or add -debug-macros
@@ -147,14 +150,14 @@ object Matcher {
147150
def termMatch(scrutineeTerm: Term, patternTerm: Term, hasTypeSplices: Boolean): Option[Tuple] = {
148151
given Env = Map.empty
149152
if (hasTypeSplices) {
150-
val ctx: Context = internal.Constraints_init(rootContext)
153+
val ctx: Context = qctx.tasty.Constraints_init(rootContext)
151154
given Context = ctx
152155
val matchings = scrutineeTerm =?= patternTerm
153156
// After matching and doing all subtype checks, we have to approximate all the type bindings
154157
// that we have found and seal them in a quoted.Type
155158
matchings.asOptionOfTuple.map { tup =>
156159
Tuple.fromArray(tup.toArray.map { // TODO improve performance
157-
case x: SymBinding => internal.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
160+
case x: SymBinding => qctx.tasty.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
158161
case x => x
159162
})
160163
}
@@ -168,14 +171,14 @@ object Matcher {
168171
def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree, hasTypeSplices: Boolean): Option[Tuple] = {
169172
given Env = Map.empty
170173
if (hasTypeSplices) {
171-
val ctx: Context = internal.Constraints_init(rootContext)
174+
val ctx: Context = qctx.tasty.Constraints_init(rootContext)
172175
given Context = ctx
173176
val matchings = scrutineeTypeTree =?= patternTypeTree
174177
// After matching and doing all subtype checks, we have to approximate all the type bindings
175178
// that we have found and seal them in a quoted.Type
176179
matchings.asOptionOfTuple.map { tup =>
177180
Tuple.fromArray(tup.toArray.map { // TODO improve performance
178-
case x: SymBinding => internal.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
181+
case x: SymBinding => qctx.tasty.Constraints_approximation(summon[Context])(x.sym, !x.fromAbove).seal
179182
case x => x
180183
})
181184
}
@@ -190,13 +193,13 @@ object Matcher {
190193
private def hasFromAboveAnnotation(sym: Symbol) = sym.annots.exists(isFromAboveAnnotation)
191194

192195
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
196+
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
197+
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_patternTypeAnnot
195198
}
196199

197200
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
201+
case New(tpt) => tpt.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
202+
case annot => annot.symbol.owner == qctx.tasty.Definitions_InternalQuotedMatcher_fromAboveAnnot
200203
}
201204

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

258261
/* Term hole */
259262
// Match a scala.internal.Quoted.patternHole and return the scrutinee tree
260263
case (ClosedPatternTerm(scrutinee), TypeApply(patternHole, tpt :: Nil))
261-
if patternHole.symbol == internal.Definitions_InternalQuotedMatcher_patternHole &&
264+
if patternHole.symbol == qctx.tasty.Definitions_InternalQuotedMatcher_patternHole &&
262265
scrutinee.tpe <:< tpt.tpe =>
263266
matched(scrutinee.seal)
264267

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

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

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

329332
/* Match block */
@@ -340,7 +343,7 @@ object Matcher {
340343

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

346349
/* Match if */

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ 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)
18-
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
17+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
18+
if (qctx.tasty.compilerId != scopeId)
19+
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
20+
}
1921
typeTree.asInstanceOf[qctx.tasty.TypeTree]
2022

2123
override def hashCode: Int = typeTree.hashCode
@@ -39,7 +41,7 @@ object Type {
3941
*/
4042
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeType: scala.quoted.Type[_])(using patternType: scala.quoted.Type[_],
4143
hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = {
42-
new Matcher.QuoteMatcher[qctx.type].typeTreeMatch(scrutineeType.unseal, patternType.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
44+
new Matcher.QuoteMatcher[qctx.type](qctx).typeTreeMatch(scrutineeType.unseal, patternType.unseal, hasTypeSplices).asInstanceOf[Option[Tup]]
4345
}
4446

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

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,20 @@ 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 = summon[QuoteContext]
16+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
17+
val tree = qctx.tasty.unpickleExpr(repr, args)
18+
new scala.internal.quoted.Expr(tree, qctx.tasty.compilerId).asInstanceOf[Expr[T]]
19+
}
1820

1921
/** Unpickle `repr` which represents a pickled `Type` tree,
2022
* replacing splice nodes with `args`
2123
*/
2224
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]]
25+
val qctx = summon[QuoteContext]
26+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
27+
val tree = qctx.tasty.unpickleType(repr, args)
28+
new scala.internal.quoted.Type(tree, qctx.tasty.compilerId).asInstanceOf[Type[T]]
29+
}
2630

2731
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,11 @@ 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
79-
case Some(expr1) => expr1.seal.asInstanceOf[Expr[T]]
80-
case _ => expr
78+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
79+
qctx.tasty.betaReduce(expr.unseal) match
80+
case Some(expr1) => expr1.seal.asInstanceOf[Expr[T]]
81+
case _ => expr
82+
}
8183

8284
/** Returns an expression containing a block with the given statements and ending with the expresion
8385
* Given list of statements `s1 :: s2 :: ... :: Nil` and an expression `e` the resulting expression

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ 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 =>
23-
def f(args: Tuple.Map[Args, Expr]): Expr[Res] =
24-
fn(args.toArray.toList.map(_.asInstanceOf[Expr[Any]].unseal)).seal.asInstanceOf[Expr[Res]]
25-
tg.untupled(f)
22+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
23+
qctx.tasty.lambdaExtractor(expr.unseal, argTypes).map { fn =>
24+
def f(args: Tuple.Map[Args, Expr]): Expr[Res] =
25+
fn(args.toArray.toList.map(_.asInstanceOf[Expr[Any]].unseal)).seal.asInstanceOf[Expr[Res]]
26+
tg.untupled(f)
27+
}
2628
}
27-
2829
}
2930

3031
}

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

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

2121
def unseal(using qctx: QuoteContext): qctx.tasty.Term =
22-
if (qctx.tasty.internal.compilerId != scopeId)
23-
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
22+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
23+
if (qctx.tasty.compilerId != scopeId)
24+
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
25+
}
2426
tree.asInstanceOf[qctx.tasty.Term]
2527

2628
override def hashCode: Int = tree.hashCode

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ 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)
18-
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
17+
scala.internal.tasty.CompilerInterface.leak(qctx) { qctx ?=>
18+
if (qctx.tasty.compilerId != scopeId)
19+
throw new scala.quoted.ScopeException("Cannot call `scala.quoted.staging.run(...)` within a macro or another `run(...)`")
20+
}
1921
typeTree.asInstanceOf[qctx.tasty.TypeTree]
2022

2123
override def hashCode: Int = typeTree.hashCode

0 commit comments

Comments
 (0)