MaxGekk opened a new pull request, #58836:
URL: https://github.com/apache/spark/pull/58836

   ### What changes were proposed in this pull request?
   
   Make `PostProcessor.replaceTokenByIdentifier` in the shared SQL parser 
rewrite a rule context only when its parent still holds that context as its 
last child, which is the state every normal exit of `quotedIdentifier`, 
`backQuotedIdentifier` and `nonReserved` is in. In any other state the listener 
leaves the tree alone and lets whatever is unwinding the parser propagate.
   
   ANTLR's generated rule methods call `exitRule` from a `finally` block, so 
parse listeners also run while a `StackOverflowError` unwinds the parser. If 
the overflow strikes inside `replaceTokenByIdentifier` itself, after 
`parent.removeLastChild()` has detached the context and before 
`parent.addChild` has attached the replacement token, the parser's current 
context is left pointing at that context, and every enclosing rule's `finally` 
calls `exitRule` on it again. The second run finds the parent empty, 
`ArrayList.remove(-1)` throws, and that exception replaces the original error 
on the way out:
   
   ```
   java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 0
     at java.util.ArrayList.remove(ArrayList.java:552)
     at 
org.antlr.v4.runtime.ParserRuleContext.removeLastChild(ParserRuleContext.java:196)
     at 
org.apache.spark.sql.catalyst.parser.PostProcessor$.replaceTokenByIdentifier(parsers.scala:343)
     at 
org.apache.spark.sql.catalyst.parser.PostProcessor$.exitQuotedIdentifier(parsers.scala:312)
     at 
org.apache.spark.sql.catalyst.parser.SqlBaseParser$QuotedIdentifierContext.exitRule(SqlBaseParser.java:36942)
     at org.antlr.v4.runtime.Parser.exitRule(Parser.java:642)
     at 
org.apache.spark.sql.catalyst.parser.SqlBaseParser.singleDataType(SqlBaseParser.java:2889)
   ```
   
   With the guard, the second run finds the parent no longer holding the 
context and returns, and the `StackOverflowError` reaches the caller that 
expects it. On a normal exit the parent's last child is always the context 
being exited, so the guard changes nothing on the success path. A guard on the 
context's own children alone is not enough, and was tried first: the context 
still holds its matched token when the listener runs the second time; it is the 
parent that has changed.
   
   ### Why are the changes needed?
   
   `SchemaConverters.parseCatalystType` in the Avro connector converts a 
`StackOverflowError` from a pathologically nested Catalyst type into an 
`IncompatibleSchemaException` naming the property it came from (SPARK-59311). 
On Java 25 the map-key case of that conversion never fires, because the 
overflow lands inside `quotedIdentifier` and the listener above turns it into 
an `IndexOutOfBoundsException`. The test `AvroSchemaHelperSuite."SPARK-59311: a 
pathologically nested map-key type names the map-key property on overflow"` 
therefore fails on every Java 25 run of `build_java25.yml` on master since the 
SPARK-59311 follow-up, while the Java 17 job passes. Where the overflow lands 
depends on frame sizes, so the same replacement can happen at any JDK to any 
caller of the parser that is prepared for a `StackOverflowError`; the Avro test 
is where it happened to become visible.
   
   ### Does this PR introduce _any_ user-facing change?
   
   No. A parse that overflows the stack now surfaces the overflow itself, or 
whatever the caller converts it into, instead of an unrelated 
`IndexOutOfBoundsException` from inside the parser.
   
   ### How was this patch tested?
   
   The existing `AvroSchemaHelperSuite` on JDK 25 (openjdk 25.0.4, x86_64), 
where the map-key test failed three runs out of three before the change and the 
whole suite passes with it:
   
   ```
   build/sbt "avro/testOnly *AvroSchemaHelperSuite"
   Tests: succeeded 10, failed 0, canceled 0, ignored 0, pending 0
   ```
   
   The failure is order dependent, which is why it looked deterministic in CI: 
the test passes when run alone on JDK 25 and fails after its sibling test has 
warmed the parser with the same thousand-deep parse, which is the order the 
suite always runs in. The suite is run whole for that reason. The Catalyst 
parser suites (`DataTypeParserSuite`, `ExpressionParserSuite`, 
`DDLParserSuite`, `PlanParserSuite`, `TableIdentifierParserSuite`, 
`ErrorParserSuite`, `ParserUtilsSuite`) pass unchanged.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Code (Claude Fable 5.1)
   


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