SEPURI-SAI-KRISHNA opened a new pull request, #57922:
URL: https://github.com/apache/spark/pull/57922

   ### What changes were proposed in this pull request?
   
   This PR fixes three integer-overflow / missing-guard bugs in the argument 
handling of
   `substring()`, `lpad()` and `rpad()`, all of which let an out-of-range `len` 
argument reach an
   allocation it should never have reached.
   
   `ByteArray.subStringSQL` (the BINARY implementation of `substring()`) 
computed its end offset in
   `int` arithmetic:
   
   ```java
   if ((bytes.length - start) < len) {   // overflows when len is large negative
     end = bytes.length;
   } else {
     end = start + len;                  // overflows when start + len > 
Integer.MAX_VALUE
   }
   start = Math.max(start, 0);           // start clamped only AFTER it was 
used above
   ```
   
   When `start` is still negative, `bytes.length - start` overflows, the `end = 
bytes.length` clamp is
   skipped, and `end` keeps an out-of-range value. `Arrays.copyOfRange` accepts 
an end past the array
   length and zero-pads the remainder, so the result is silently a large array 
of zero bytes. This is
   now computed in `long` and clamped to `bytes.length`, with `start` clamped 
only afterwards.
   
   `ByteArray.lpad` / `ByteArray.rpad` guarded only the exact value `0` before 
`new byte[len]`, so a
   negative `len` threw `NegativeArraySizeException`. The guard is now `len <= 
0`, which also makes
   `padWithEmptyPattern` unreachable with a non-positive length.
   
   `UTF8String.lpad` / `UTF8String.rpad` start with `int spaces = len - 
numChars()`, which wraps to a
   large positive value when `len == Integer.MIN_VALUE`. That skips the `spaces 
<= 0` branch and takes
   the padding branch, failing in `Math.toIntExact`. Both now return the empty 
string up front for
   `len <= 0`, before the wrapping subtraction is evaluated.
   
   With all three in place, a non-positive length yields the empty value for 
STRING and BINARY alike
   across the whole `int` range, and no argument combination can produce a 
result longer than the
   input.
   
   ### Why are the changes needed?
   
   All three are reachable from plain SQL with default configuration, on both 
the interpreted and
   codegen paths.
   
   ```sql
   -- 1. wrong results, and an allocation unrelated to the size of the input
   SELECT length(substring(CAST('Spark SQL' AS BINARY), -1207959552, 
-1207959552));
   -- before: 1879048201  (a 1.75 GiB array of mostly zero bytes)
   -- after:  0
   
   SELECT substring(CAST('Spark SQL' AS BINARY), -2147483648, 5);
   -- before: the whole 9-byte input
   -- after:  empty
   
   -- 2. raw JVM exception rather than a SparkThrowable
   SELECT lpad(CAST('hi' AS BINARY), -1, CAST('??' AS BINARY));
   -- before: java.lang.NegativeArraySizeException: -1
   -- after:  empty binary
   
   -- 3. same, on the STRING side, at one specific length
   SELECT lpad('hello', -2147483648, '??');
   -- before: java.lang.ArithmeticException: integer overflow
   -- after:  empty string
   ```
   
   The equivalent STRING expressions already returned `''` for the first two 
cases, so this is also a
   STRING/BINARY consistency fix rather than new behaviour being invented. The 
STRING path was fixed
   for this class of overflow in SPARK-32115, but `ByteArray.subStringSQL` 
(added by SPARK-28412) was
   never brought in line, even though its comment says the offsets are computed 
"according to
   UTF8String#subStringSQL". The pad overloads were added by SPARK-37047.
   
   Beyond returning wrong results, the first case is a robustness concern: the 
allocation size is
   driven by user-supplied arguments and is unrelated to the size of the input, 
so a modestly sized
   query can allocate gigabytes and OOM the executor.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes, for the argument combinations described above, all of which are 
currently broken.
   
   | Expression | Before | After |
   |---|---|---|
   | `substring(<binary>, -1207959552, -1207959552)` | 1879048201-byte array of 
zeros | empty binary |
   | `substring(<binary>, -2147483648, 5)` | the whole input | empty binary |
   | `lpad(<binary>, -1, <pad>)` | `NegativeArraySizeException` | empty binary |
   | `rpad(<binary>, -1, <pad>)` | `NegativeArraySizeException` | empty binary |
   | `lpad('hello', -2147483648, '??')` | `ArithmeticException` | empty string |
   
   In every case the new result matches what the corresponding STRING 
expression already returned for
   non-positive lengths. No behaviour changes for any in-range argument.
   
   ### How was this patch tested?
   
   New unit tests:
   
   - `ByteArraySuite.testSubStringSQL` and `ByteArraySuite.testPad` cover the 
positive, zero and
     negative positions, the overflowing combinations above, and the null-input 
cases.
   - `UTF8StringSuite.pad` gains the non-positive lengths including 
`Integer.MIN_VALUE`, for both an
     empty and a non-empty padding pattern.
   - `StringExpressionsSuite` covers the same combinations at the expression 
level for `Substring`,
     `StringLPad` / `StringRPad` and `BinaryPad`. These go through 
`checkEvaluation`, so both the
     interpreted and the codegen paths are exercised.
   
   In addition, `ByteArray.subStringSQL` was checked against 
`UTF8String.substringSQL` as an oracle
   over 3125 `(input, pos, len)` combinations built from boundary values 
(`Integer.MIN_VALUE`,
   `Integer.MAX_VALUE`, `+/-1207959552`, and small offsets around the input 
length). For pure-ASCII
   inputs character indexing and byte indexing coincide, so the two must agree 
exactly. Current master
   reports 239 mismatches; with this patch, 0.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Opus 5)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to