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]

Reply via email to