-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix Constant
tag of Expr(null: String)
#23064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: Hamza Remmal <hamza@remmal.net> Co-authored-by: HarrisL2 <harris.yh.lau@gmail.com> Co-authored-by: Abdullah Arif Jafri <abdullahjafri2001@gmail.com>
@@ -2529,7 +2529,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler | |||
end StringConstantTypeTest | |||
|
|||
object StringConstant extends StringConstantModule: | |||
def apply(x: String): StringConstant = dotc.core.Constants.Constant(x) | |||
def apply(x: String): StringConstant = dotc.core.Constants.Constant(x: Any) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a fix to what I believe is a soundness hole in explicit nulls. I'm going to try to have another example with more details about this soundness issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is unsafe data feed into explicit nulls, there is nothing we can do other than runtime check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is a valid fix, then comments is needed inline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be it is better to add:
object dotc.core.Constants.Constant{
val nullConstant: Constant = new Constant(null, NullTag)
}
def apply(x: String): StringConstant = if (x==null) {dotc.core.Constants.nullConstant} else {dotc.core.Constants.Constant(x)}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was also surprised when I discovered true/false/null
constants are not re-used/interned/hash-consed. I don't know if that would have a significant impact. I think this could be discussed in a separate issue or PR.
Is the intended behaviour of |
I believe its not allowed, but not very well documented.
|
If this is the case, then Expr(null: String) should throw an exception. We spent a lot of time trying to understand why If this is indeed how it works, it represents a rather unfriendly design when interacting with Java objects. |
IMHO: The example of a good design is Option /** An Option factory which creates Some(x) if the argument is not null,
* and None if it is null.
*
* @param x the value
* @return Some(value) if value != null, None if value == null
*/
def apply[A](x: A): Option[A] = if (x == null) None else Some(x) |
The point here is that a Dotty scala3/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala Lines 574 to 575 in 08f2e4a
However,
I also could not find any documentation. Thus, it seems up to us to decide whether Accepting |
If we decide to allow |
Indeed, that would also look like nicer to us! We discussed it on Monday during the Spree and concluded that would be a backward-incompatible change, but I am actually not sure. |
By the way, while trying to change the parameter type to |
I guess the file calling the quotes api is not in explicit nulls, so |
Something else I just noticed: the method returns a scala3/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala Lines 2526 to 2529 in bbe2dde
So we shouldn't return a We could change the return type to Maybe it's better after all to just |
But isn't the overload statically resolved, in the file with explicit nulls? In the situation I describe, the call |
Oh, I see what you meam. No, explicit nulls is disabled in |
Ah, that explains it! Thanks, I missed this import. |
I'm ok with this, given a |
We can solve the issue in StringToExpr |
What do you suggest for In any case, we still need to do something for |
IIUC: Our primary issue is that import quotes.reflect.*
val str: String = null
val exprString = Expr(str) It seems we can fix it just to rewrite StringToExpr given StringToExpr[T <: String]: ToExpr[T] with {
def apply(x: T)(using Quotes) =
import quotes.reflect.*
Literal(if (x==null) {nullListeral} else {StringConstant(x)}).asExpr.asInstanceOf[Expr[T]]
} it seems the issue can be resolved without modifying the declaration of StringConstant. |
IMHO: Yes, StringConstant must not return broken result silently. |
I have just pushed a commit that allows |
But now if we allow |
IMHO: There are no other java primitives in the
But it is scala types and may be it should not be null in any case. |
Throwing exception in |
Yes, but what about non-primitive types? We also get runtime errors in these cases if we pass def nullTestImpl()(using Quotes): Expr[Unit] =
Expr(null: Option[Int]) // MatchError: null
Expr(null: (Int, Int)) // NullPointerException: Cannot invoke "scala.Tuple2._1()" because "tup$2" is null
Expr(null: Array[Int]) // NullPointerException: Cannot read the array length because "array" is null |
IMHO:
|
Fixes #23008. Started during the Scala 3 Compiler Spree of Monday, April 28th, 2025.
The
Constant
is an object in Dotty core that has severalapply
overloads, includingConstant(String)
andConstant(Null)
, that createConstant
instances with the appropriate tags:scala3/compiler/src/dotty/tools/dotc/core/Constants.scala
Line 229 in bbe2dde
scala3/compiler/src/dotty/tools/dotc/core/Constants.scala
Line 238 in bbe2dde
It is called from the quotes reflection API with an argument
x: String
, which always dispatched to the former overload:scala3/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Line 2532 in 9e7aab7
When this API is called from code without explicit nulls,
null
can be passed as argumentx
. That still dispatches to theConstant(String)
overload, yielding aConstant
with the wrong tag.This PR fixes the issue by forbidding
null
as an argument ofStringConstant
, but allowing it as an argument ofStringToExpr
and returning a properNullConstant
(a constant with tagNullTag
) in that case.