Skip to content

Commit a4afa21

Browse files
committed
Restrict allowed trees in annotations
1 parent a5a9fc8 commit a4afa21

24 files changed

+264
-73
lines changed

compiler/src/dotty/tools/dotc/transform/TreeChecker.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,17 @@ object TreeChecker {
827827
|${mismatch.message}${mismatch.explanation}
828828
|tree = $tree ${tree.className}""".stripMargin
829829
})
830+
checkWellFormedType(tp1)
831+
checkWellFormedType(tp2)
832+
833+
/** Check that the type `tp` is well-formed. Currently this only means
834+
* checking that annotated types have valid annotation arguments.
835+
*/
836+
private def checkWellFormedType(tp: Type)(using Context): Unit =
837+
tp.foreachPart:
838+
case AnnotatedType(underlying, annot) => checkAnnot(annot.tree)
839+
case _ => ()
840+
830841
}
831842

832843
/** Tree checker that can be applied to a local tree. */

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,6 @@ object Checking {
921921
annot
922922
case _ => annot
923923
end checkNamedArgumentForJavaAnnotation
924-
925924
}
926925

927926
trait Checking {
@@ -1392,12 +1391,21 @@ trait Checking {
13921391
if !Inlines.inInlineMethod && !ctx.isInlineContext then
13931392
report.error(em"$what can only be used in an inline method", pos)
13941393

1394+
def checkAnnot(tree: Tree)(using Context): Tree =
1395+
tree match
1396+
case Ident(tpnme.BOUNDTYPE_ANNOT) =>
1397+
// `FirstTransform.toTypeTree` creates `Annotated` nodes whose `annot` are
1398+
// `Ident`s, not annotation instances. See `tests/pos/annot-boundtype.scala`.
1399+
tree
1400+
case _ =>
1401+
checkAnnotTree(checkAnnotClass(tree))
1402+
13951403
/** Check that the class corresponding to this tree is either a Scala or Java annotation.
13961404
*
13971405
* @return The original tree or an error tree in case `tree` isn't a valid
13981406
* annotation or already an error tree.
13991407
*/
1400-
def checkAnnotClass(tree: Tree)(using Context): Tree =
1408+
private def checkAnnotClass(tree: Tree)(using Context): Tree =
14011409
if tree.tpe.isError then
14021410
return tree
14031411
val cls = Annotations.annotClass(tree)
@@ -1409,8 +1417,8 @@ trait Checking {
14091417
errorTree(tree, em"$cls is not a valid Scala annotation: it does not extend `scala.annotation.Annotation`")
14101418
else tree
14111419

1412-
/** Check arguments of compiler-defined annotations */
1413-
def checkAnnotArgs(tree: Tree)(using Context): tree.type =
1420+
/** Check arguments of annotations */
1421+
private def checkAnnotTree(tree: Tree)(using Context): Tree =
14141422
val cls = Annotations.annotClass(tree)
14151423
tree match
14161424
case Apply(tycon, arg :: Nil) if cls == defn.TargetNameAnnot =>
@@ -1420,8 +1428,40 @@ trait Checking {
14201428
case Literal(_) => // ok
14211429
case _ =>
14221430
report.error(em"@${cls.name} needs a string literal as argument", arg.srcPos)
1431+
tree
14231432
case _ =>
1424-
tree
1433+
tree.find(!isValidAnnotSubtree(_)) match
1434+
case None => tree
1435+
case Some(invalidSubTree) =>
1436+
errorTree(
1437+
EmptyTree,
1438+
em"""Implementation restriction: expression cannot be used inside an annotation argument.
1439+
|Tree: ${invalidSubTree}
1440+
|Type: ${invalidSubTree.tpe}""",
1441+
invalidSubTree.srcPos
1442+
)
1443+
1444+
/** Returns `true` if this tree can appear inside an annotation argument. */
1445+
private def isValidAnnotSubtree(subTree: Tree) =
1446+
subTree.isType || subTree.isInstanceOf[
1447+
EmptyTree.type
1448+
| Ident
1449+
| Select
1450+
| This
1451+
| Super
1452+
| Apply
1453+
| TypeApply
1454+
| Literal
1455+
| New
1456+
| Typed
1457+
| NamedArg
1458+
| Assign
1459+
| Block
1460+
| SeqLiteral
1461+
| Inlined
1462+
| Hole
1463+
| Annotated
1464+
]
14251465

14261466
/** 1. Check that all case classes that extend `scala.reflect.Enum` are `enum` cases
14271467
* 2. Check that parameterised `enum` cases do not extend java.lang.Enum.
@@ -1670,7 +1710,7 @@ trait NoChecking extends ReChecking {
16701710
override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = ()
16711711
override def checkImplicitConversionUseOK(tree: Tree, expected: Type)(using Context): Unit = ()
16721712
override def checkFeasibleParent(tp: Type, pos: SrcPos, where: => String = "")(using Context): Type = tp
1673-
override def checkAnnotArgs(tree: Tree)(using Context): tree.type = tree
1713+
override def checkAnnot(tree: Tree)(using Context): tree.type = tree
16741714
override def checkNoTargetNameConflict(stats: List[Tree])(using Context): Unit = ()
16751715
override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = ()
16761716
override def checkSimpleKinded(tpt: Tree)(using Context): Tree = tpt

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2799,7 +2799,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
27992799
}
28002800

28012801
def typedAnnotation(annot: untpd.Tree)(using Context): Tree =
2802-
checkAnnotClass(checkAnnotArgs(typed(annot)))
2802+
checkAnnot(typed(annot))
28032803

28042804
def registerNowarn(tree: Tree, mdef: untpd.Tree)(using Context): Unit =
28052805
val annot = Annotations.Annotation(tree)
@@ -3330,7 +3330,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
33303330
end typedPackageDef
33313331

33323332
def typedAnnotated(tree: untpd.Annotated, pt: Type)(using Context): Tree = {
3333-
val annot1 = checkAnnotClass(typedExpr(tree.annot))
3333+
val annot1 = checkAnnot(typedExpr(tree.annot))
33343334
val annotCls = Annotations.annotClass(annot1)
33353335
if annotCls == defn.NowarnAnnot then
33363336
registerNowarn(annot1, tree)

tests/neg-macros/i7052.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import scala.quoted.*
22
class Test {
33
def foo(str: String)(using Quotes) = '{
4-
@deprecated(str, "") // error
4+
@deprecated(str, "") // error: expression cannot be used inside an annotation argument
55
def bar = ???
66
}
77
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import scala.quoted.*
22
class Test {
33
def foo(str: Expr[String])(using Quotes) = '{
4-
@deprecated($str, "")
4+
@deprecated($str, "") // error: expression cannot be used inside an annotation argument
55
def bar = ???
66
}
77
}

tests/neg-macros/i7121.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ class annot1[T](x: Expr[T]) extends scala.annotation.Annotation
44
class annot2[T: Type](x: T) extends scala.annotation.Annotation
55

66
class Test()(implicit qtx: Quotes) {
7-
@annot1('{4}) // error
8-
def foo(str: String) = ()
9-
107
@annot2(4)(using Type.of[Int]) // error
118
def foo2(str: String) = ()
129

tests/neg-macros/i7121b.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import scala.quoted.*
2+
3+
class annot1[T](x: Expr[T]) extends scala.annotation.Annotation
4+
class annot2[T: Type](x: T) extends scala.annotation.Annotation
5+
6+
class Test()(implicit qtx: Quotes) {
7+
@annot1('{4}) // error: expression cannot be used inside an annotation argument
8+
def foo(str: String) = ()
9+
10+
}

tests/pos-macros/i7519b.scala renamed to tests/neg-macros/i7519b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ inline def quote[T]: Quoted[T] = ${ quoteImpl[T] }
99

1010
def quoteImpl[T: Type](using Quotes): Expr[Quoted[T]] = {
1111
val value: Expr[Int] = '{ 42 }
12-
'{ new Quoted[T @Annot($value)] }
12+
'{ new Quoted[T @Annot($value)] } // error: expression cannot be used inside an annotation argument
1313
}

tests/neg/annot-invalid.check

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
-- Error: tests/neg/annot-invalid.scala:9:21 ---------------------------------------------------------------------------
2+
9 | val x1: Int @annot(new Object {}) = 0 // error
3+
| ^^^^^^^^^^^^^
4+
| Implementation restriction: expression cannot be used inside an annotation argument.
5+
| Tree: final class $anon() extends Object() {}
6+
| Type: Object {...}
7+
-- Error: tests/neg/annot-invalid.scala:10:28 --------------------------------------------------------------------------
8+
10 | val x2: Int @annot({class C}) = 0 // error
9+
| ^^^^^^^
10+
| Implementation restriction: expression cannot be used inside an annotation argument.
11+
| Tree: class C() extends Object() {}
12+
| Type: C
13+
-- Error: tests/neg/annot-invalid.scala:11:26 --------------------------------------------------------------------------
14+
11 | val x3: Int @annot({val y: Int = 2}) = 0 // error
15+
| ^^^^^^^^^^^^^^
16+
| Implementation restriction: expression cannot be used inside an annotation argument.
17+
| Tree: val y: Int = 2
18+
| Type: (y : Int)
19+
-- Error: tests/neg/annot-invalid.scala:12:26 --------------------------------------------------------------------------
20+
12 | val x4: Int @annot({def f = 2}) = 0 // error
21+
| ^^^^^^^^^
22+
| Implementation restriction: expression cannot be used inside an annotation argument.
23+
| Tree: def f: Int = 2
24+
| Type: (f : => Int)
25+
-- Error: tests/neg/annot-invalid.scala:13:30 --------------------------------------------------------------------------
26+
13 | val x5: Int @annot((x: Int) => x) = 0 // error
27+
| ^^^^^^^^^^^^^
28+
| Implementation restriction: expression cannot be used inside an annotation argument.
29+
| Tree: closure($anonfun)
30+
| Type: Int => Int
31+
-- Error: tests/neg/annot-invalid.scala:14:21 --------------------------------------------------------------------------
32+
14 | val x6: Int @annot(O.g) = 0 // error
33+
| ^^^
34+
| Implementation restriction: expression cannot be used inside an annotation argument.
35+
| Tree: closure($anonfun)
36+
| Type: Int => Int
37+
-- Error: tests/neg/annot-invalid.scala:16:25 --------------------------------------------------------------------------
38+
16 | val x7: Int @annot('{4}) = 0 // error
39+
| ^
40+
| Implementation restriction: expression cannot be used inside an annotation argument.
41+
| Tree: '{4}
42+
| Type: (scala.quoted.Quotes) ?=> scala.quoted.Expr[Int]
43+
-- Error: tests/neg/annot-invalid.scala:18:9 ---------------------------------------------------------------------------
44+
18 | @annot(new Object {}) val y1: Int = 0 // error
45+
| ^^^^^^^^^^^^^
46+
| Implementation restriction: expression cannot be used inside an annotation argument.
47+
| Tree: final class $anon() extends Object() {}
48+
| Type: Object {...}
49+
-- Error: tests/neg/annot-invalid.scala:19:16 --------------------------------------------------------------------------
50+
19 | @annot({class C}) val y2: Int = 0 // error
51+
| ^^^^^^^
52+
| Implementation restriction: expression cannot be used inside an annotation argument.
53+
| Tree: class C() extends Object() {}
54+
| Type: C
55+
-- Error: tests/neg/annot-invalid.scala:20:14 --------------------------------------------------------------------------
56+
20 | @annot({val y: Int = 2}) val y3: Int = 0 // error
57+
| ^^^^^^^^^^^^^^
58+
| Implementation restriction: expression cannot be used inside an annotation argument.
59+
| Tree: val y: Int = 2
60+
| Type: (y : Int)
61+
-- Error: tests/neg/annot-invalid.scala:21:14 --------------------------------------------------------------------------
62+
21 | @annot({def f = 2}) val y4: Int = 0 // error
63+
| ^^^^^^^^^
64+
| Implementation restriction: expression cannot be used inside an annotation argument.
65+
| Tree: def f: Int = 2
66+
| Type: (f : => Int)
67+
-- Error: tests/neg/annot-invalid.scala:22:18 --------------------------------------------------------------------------
68+
22 | @annot((x: Int) => x) val y5: Int = 0 // error
69+
| ^^^^^^^^^^^^^
70+
| Implementation restriction: expression cannot be used inside an annotation argument.
71+
| Tree: closure($anonfun)
72+
| Type: Int => Int
73+
-- Error: tests/neg/annot-invalid.scala:23:9 ---------------------------------------------------------------------------
74+
23 | @annot(O.g) val y6: Int = 0 // error
75+
| ^^^
76+
| Implementation restriction: expression cannot be used inside an annotation argument.
77+
| Tree: closure($anonfun)
78+
| Type: Int => Int
79+
-- Error: tests/neg/annot-invalid.scala:25:13 --------------------------------------------------------------------------
80+
25 | @annot('{4}) val y7: Int = 0 // error
81+
| ^
82+
| Implementation restriction: expression cannot be used inside an annotation argument.
83+
| Tree: '{4}
84+
| Type: (scala.quoted.Quotes) ?=> scala.quoted.Expr[Int]

tests/neg/annot-invalid.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import scala.quoted.Quotes
2+
3+
class annot[T](arg: T) extends scala.annotation.Annotation
4+
5+
def main =
6+
object O:
7+
def g(x: Int): Int = x
8+
9+
val x1: Int @annot(new Object {}) = 0 // error
10+
val x2: Int @annot({class C}) = 0 // error
11+
val x3: Int @annot({val y: Int = 2}) = 0 // error
12+
val x4: Int @annot({def f = 2}) = 0 // error
13+
val x5: Int @annot((x: Int) => x) = 0 // error
14+
val x6: Int @annot(O.g) = 0 // error
15+
def withQuotes(using Quotes) =
16+
val x7: Int @annot('{4}) = 0 // error
17+
18+
@annot(new Object {}) val y1: Int = 0 // error
19+
@annot({class C}) val y2: Int = 0 // error
20+
@annot({val y: Int = 2}) val y3: Int = 0 // error
21+
@annot({def f = 2}) val y4: Int = 0 // error
22+
@annot((x: Int) => x) val y5: Int = 0 // error
23+
@annot(O.g) val y6: Int = 0 // error
24+
def withQuotes2(using Quotes) =
25+
@annot('{4}) val y7: Int = 0 // error
26+
27+
()

0 commit comments

Comments
 (0)