sepuri sai krishna created SPARK-58713:
------------------------------------------

             Summary: overlay() returns wrong results for out-of-range position 
arguments
                 Key: SPARK-58713
                 URL: https://issues.apache.org/jira/browse/SPARK-58713
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 4.2.0
            Reporter: sepuri sai krishna


{{Overlay.calculate}} computes its two slice boundaries in {{int}} arithmetic, 
so a position argument near the ends of the {{int}} range wraps and the 
function silently returns a wrong result. Both the STRING and the BINARY 
overload are affected, on the interpreted and the codegen path, with default 
configuration.

{code:scala}
def calculate(input: UTF8String, replace: UTF8String, pos: Int, len: Int): 
UTF8String = {
  val builder = new UTF8StringBuilder
  builder.append(input.substringSQL(1, pos - 1))          // (1) `pos - 1` 
overflows
  builder.append(replace)
  val length = if (len >= 0) len else replace.numChars
  builder.append(input.substringSQL(pos + length, Int.MaxValue))  // (2) `pos + 
length` overflows
  builder.build()
}
{code}

The BINARY overload has the same two expressions, via 
{{ByteArray.subStringSQL}}.

For {{pos == Int.MinValue}}, {{pos - 1}} wraps to {{Int.MaxValue}}, so the 
leading slice becomes the whole input instead of the empty string. For {{pos}} 
near {{Int.MaxValue}}, {{pos + length}} wraps to a large negative value, which 
{{substringSQL}} interprets as an offset from the end of the input, so the 
trailing slice becomes the whole input instead of the empty string. In both 
cases the input is duplicated around the replacement.

h3. Reproduction

{code:sql}
SELECT overlay('Spark SQL' PLACING '_' FROM 2147483647 FOR 5);
-- actual:   'Spark SQL_Spark SQL'
-- expected: 'Spark SQL_'

SELECT overlay('Spark SQL' PLACING '_' FROM -2147483648 FOR 1);
-- actual:   'Spark SQL_Spark SQL'
-- expected: '_Spark SQL'
{code}

Measured on current master, input {{'Spark SQL'}} and replacement {{'_'}}:

|| pos || len || actual (STRING) || actual (BINARY) || expected ||
| {{Int.MaxValue}} | 5 | {{'Spark SQL_Spark SQL'}} | {{'Spark SQL_Spark SQL'}} 
| {{'Spark SQL_'}} |
| {{Int.MaxValue}} | -1 | {{'Spark SQL_Spark SQ'}} | {{'Spark SQL_Spark SQL'}} 
| {{'Spark SQL_'}} |
| {{Int.MaxValue - 2}} | 10 | {{'Spark SQL_Spark SQL'}} | {{'Spark SQL_Spark 
SQL'}} | {{'Spark SQL_'}} |
| {{Int.MinValue}} | 1 | {{'Spark SQL_Spark SQL'}} | {{'Spark SQL_Spark SQL'}} 
| {{'_Spark SQL'}} |
| {{Int.MinValue}} | -1 | {{'Spark SQL_Spark SQL'}} | {{'Spark SQL_Spark SQL'}} 
| {{'_Spark SQL'}} |

All in-range positions are unaffected; {{pos}} values such as 2, 6, 7, 100 and 
-1 produce identical results before and after the fix.

Note the STRING and BINARY columns already disagree for {{pos = Int.MaxValue, 
len = -1}}, which is a symptom of the separate clamping problem fixed under 
SPARK-58708. That fix brings BINARY into line with STRING for that row, but 
both remain wrong until the arithmetic here is corrected -- the overflow 
happens in {{Overlay.calculate}} before {{substringSQL}} is ever reached, so 
SPARK-58708 does not and cannot fix it.

h3. Proposed fix

Compute both boundaries in {{long}} and clamp into the {{int}} range before 
calling {{substringSQL}}:

{code:scala}
private def clamp(v: Long): Int =
  math.max(Int.MinValue, math.min(Int.MaxValue, v)).toInt
{code}

applied to {{pos - 1}} and {{pos + length}} in both overloads. Clamping rather 
than throwing keeps the behaviour consistent with {{substring()}}, which 
saturates rather than erroring for out-of-range positions. With this change all 
five rows above produce the expected column, and the in-range cases are 
unchanged.

h3. Impact

Wrong results, silently. No error is raised and no configuration is involved. 
The affected argument range is narrow, so the practical exposure is low, but 
the failure mode is a duplicated input rather than an obviously bogus value, 
which makes it hard to notice.




--
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