This is an automated email from the ASF dual-hosted git repository.
MaxGekk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/master by this push:
new dcde6956b64c [SPARK-57726][SQL] Fix NPE in AttributeReference.hashCode
when the attribute name is null
dcde6956b64c is described below
commit dcde6956b64cfb024c7684e136666dcb054cbd34
Author: Maxim Gekk <[email protected]>
AuthorDate: Sun Jun 28 11:22:15 2026 +0200
[SPARK-57726][SQL] Fix NPE in AttributeReference.hashCode when the
attribute name is null
### What changes were proposed in this pull request?
`AttributeReference.hashCode` computes `name.hashCode()` directly:
```scala
override def hashCode: Int = {
var h = 17
h = h * 37 + name.hashCode() // NPE if name == null
...
}
```
This throws a `NullPointerException` when the attribute has a null name.
This PR makes the name contribution null-safe using `Objects.hashCode(name)`
(`java.util.Objects` is already imported).
### Why are the changes needed?
`AttributeReference.equals` already treats the name nullably (`name ==
ar.name`), but `hashCode` does not. An `AttributeReference` can carry a null
name (e.g. a `StructField` permits a null name and it flows into
`AttributeReference` via `DataTypeUtils.toAttribute`). When such an attribute
is placed in a hash-based collection (`HashSet`/`HashMap`, or via
`distinct`/`toSet`), `hashCode` throws an NPE.
This was noticed during review of #56831 (SPARK-57725), which fixes a
related but distinct null-name NPE in `AttributeSeq`'s case-insensitive name
maps. The two issues are independent and are addressed separately.
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
Added a test in `NamedExpressionSuite` asserting that
`AttributeReference.hashCode` does not throw on a null-named attribute and that
the equals/hashCode contract holds:
```
build/sbt 'catalyst/testOnly *NamedExpressionSuite'
```
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Cursor
Closes #56832 from MaxGekk/fix-attrref-hashcode-npe.
Authored-by: Maxim Gekk <[email protected]>
Signed-off-by: Max Gekk <[email protected]>
---
.../spark/sql/catalyst/expressions/namedExpressions.scala | 4 +++-
.../sql/catalyst/expressions/NamedExpressionSuite.scala | 13 +++++++++++++
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/namedExpressions.scala
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/namedExpressions.scala
index d27f140d083b..c7a216548085 100644
---
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/namedExpressions.scala
+++
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/namedExpressions.scala
@@ -312,7 +312,9 @@ case class AttributeReference(
override def hashCode: Int = {
// See http://stackoverflow.com/questions/113511/hash-code-implementation
var h = 17
- h = h * 37 + name.hashCode()
+ // Use Objects.hashCode to stay null-safe: an AttributeReference can carry
a null name
+ // (e.g. from a StructField built with a null name), and equals already
treats name nullably.
+ h = h * 37 + Objects.hashCode(name)
h = h * 37 + dataType.hashCode()
h = h * 37 + nullable.hashCode()
h = h * 37 + metadata.hashCode()
diff --git
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/NamedExpressionSuite.scala
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/NamedExpressionSuite.scala
index 3e6f40f3b1ca..362313c1e55f 100644
---
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/NamedExpressionSuite.scala
+++
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/expressions/NamedExpressionSuite.scala
@@ -64,4 +64,17 @@ class NamedExpressionSuite extends SparkFunSuite {
assert(!alias.metadata.contains(nonInheritableMetadataKey))
assert(alias.metadata.contains("key"))
}
+
+ test("SPARK-57726: AttributeReference.hashCode is null-safe when the name is
null") {
+ // An AttributeReference can carry a null name (e.g. from a StructField
built with a null
+ // name). hashCode must not throw a NullPointerException so the attribute
can be used in
+ // hash-based collections; equals is already null-safe on the name.
+ val exprId = NamedExpression.newExprId
+ val a1 = AttributeReference(null, IntegerType)(exprId)
+ val a2 = AttributeReference(null, IntegerType)(exprId)
+ a1.hashCode()
+ assert(a1 == a2)
+ assert(a1.hashCode() == a2.hashCode())
+ assert(Set(a1).contains(a2))
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]