Skip to content

Commit 71b1101

Browse files
authored
Forbid StringConstant(null) (#23064)
Fixes #23008. Started during the Scala 3 Compiler Spree of Monday, April 28th, 2025. `Constant` is an object in Dotty core that has several `apply` overloads, including `Constant(String)` and `Constant(Null)`, that create `Constant` instances with the appropriate tags: https://github.com/scala/scala3/blob/bbe2dde1ee10adc05ceece96ab82af8a66a21880/compiler/src/dotty/tools/dotc/core/Constants.scala#L229 https://github.com/scala/scala3/blob/bbe2dde1ee10adc05ceece96ab82af8a66a21880/compiler/src/dotty/tools/dotc/core/Constants.scala#L238 It is called from the quotes reflection API with an argument `x: String`, which always dispatched to the former overload: https://github.com/scala/scala3/blob/9e7aab7ab8b2798175abf71737ffe822e50f9c30/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala#L2532 When this API is called from code without explicit nulls, `null` can be passed as argument `x`. That still dispatches to the `Constant(String)` overload, yielding a `Constant` with the wrong tag. This PR fixes the issue by forbidding `null` as an argument of `StringConstant`.
2 parents a7d15b4 + 2e3c803 commit 71b1101

File tree

5 files changed

+43
-2
lines changed

5 files changed

+43
-2
lines changed

compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala

+6-1
Original file line numberDiff line numberDiff line change
@@ -2529,7 +2529,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler
25292529
end StringConstantTypeTest
25302530

25312531
object StringConstant extends StringConstantModule:
2532-
def apply(x: String): StringConstant = dotc.core.Constants.Constant(x)
2532+
def apply(x: String): StringConstant =
2533+
require(x != null, "value of StringConstant cannot be `null`")
2534+
// A `null` constant must be represented as a `NullConstant`, c.f. a
2535+
// constant with `tag == NullTag`, which is not a `StringConstant`.
2536+
// See issue 23008.
2537+
dotc.core.Constants.Constant(x)
25332538
def unapply(constant: StringConstant): Some[String] = Some(constant.stringValue)
25342539
end StringConstant
25352540

library/src/scala/quoted/Quotes.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -3640,7 +3640,10 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching =>
36403640

36413641
/** Methods of the module object `val StringConstant` */
36423642
trait StringConstantModule { this: StringConstant.type =>
3643-
/** Create a constant String value */
3643+
/** Create a constant String value
3644+
*
3645+
* @throw `IllegalArgumentException` if the argument is `null`
3646+
*/
36443647
def apply(x: String): StringConstant
36453648
/** Match String value constant and extract its value */
36463649
def unapply(constant: StringConstant): Some[String]

tests/neg-macros/i23008.check

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
-- Error: tests/neg-macros/i23008/Test_2.scala:1:24 --------------------------------------------------------------------
3+
1 |@main def Test = Macros.buildString // error
4+
| ^^^^^^^^^^^^^^^^^^
5+
| Exception occurred while executing macro expansion.
6+
| java.lang.IllegalArgumentException: requirement failed: value of StringConstant cannot be `null`
7+
| at scala.Predef$.require(Predef.scala:337)
8+
| at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2533)
9+
| at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2532)
10+
| at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:80)
11+
| at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:78)
12+
| at scala.quoted.Expr$.apply(Expr.scala:70)
13+
| at Macros$.buildStringCode(Macro_1.scala:9)
14+
|
15+
|---------------------------------------------------------------------------------------------------------------------
16+
|Inline stack trace
17+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
18+
|This location contains code that was inlined from Macro_1.scala:4
19+
4 | inline def buildString = ${buildStringCode}
20+
| ^^^^^^^^^^^^^^^^^^
21+
---------------------------------------------------------------------------------------------------------------------

tests/neg-macros/i23008/Macro_1.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted.*
2+
3+
object Macros {
4+
inline def buildString = ${buildStringCode}
5+
6+
def buildStringCode(using Quotes): Expr[String] = {
7+
import quotes.reflect.*
8+
val str: String = null
9+
Expr(str)
10+
}
11+
}

tests/neg-macros/i23008/Test_2.scala

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@main def Test = Macros.buildString // error

0 commit comments

Comments
 (0)