Joel Robin created SPARK-59097:
----------------------------------

             Summary: UnsafeProjection codegen throws NullPointerException on a 
null in a nested non-nullable field, where the interpreted path writes null
                 Key: SPARK-59097
                 URL: https://issues.apache.org/jira/browse/SPARK-59097
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 4.2.0, 3.5.2, 5.0.0
            Reporter: Joel Robin


A struct, array or map value that is declared non-nullable but is actually null 
at runtime gets handled two different ways depending on which UnsafeProjection 
you end up with. The interpreted one writes null and carries on. The generated 
one dereferences the null and throws. Both are implementations of the same 
thing, so which one you get should not decide whether the query survives.

It surfaces as a task failure like this:

{code}
java.lang.NullPointerException: Cannot invoke
  "org.apache.spark.sql.catalyst.util.ArrayData.numElements()" because 
"<local10>" is null
  at ...GeneratedClass$SpecificUnsafeProjection.writeFields_1_3$(Unknown Source)
  at ...GeneratedClass$SpecificUnsafeProjection.apply(Unknown Source)
  at ...GeneratedClass$GeneratedIteratorForCodegenStage31.processNext(Unknown 
Source)
  at 
org.apache.spark.shuffle.sort.BypassMergeSortShuffleWriter.write(BypassMergeSortShuffleWriter.java:154)
{code}


h3. Why codegen throws

writeExpressionsToBuffer drops the null check whenever the declared nullability 
says the value cannot be null (GenerateUnsafeProjection.scala:156):

{code:scala}
if (!nullable || input.isNull == FalseLiteral) {
  // The value is statically known to be non-null, so skip the null check and 
the
  // (dead) setNull branch and just write the value.
{code}

Array elements get the same treatment when containsNull is false (:248-249). 
writeArrayToBuffer then does the dereference (:257):

{code:java}
final ArrayData tmpInput = <input>;
if (tmpInput instanceof UnsafeArrayData) { ... }
else { final int numElements = tmpInput.numElements();   // throws here
{code}

tmpInput is the <localN> named in the NPE message.

InterpretedUnsafeProjection.generateFieldWriter does the opposite, and the 
comment is explicit about it (InterpretedUnsafeProjection.scala:251):

{code:scala}
// Always wrap the writer with a null safe version.
{code}

It does take nullable as a parameter, but only to work out the child writer's 
nullability. It
never consults it when deciding whether to null-check. So the interpreted path 
tolerates an
inaccurate declaration and codegen does not.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to