mithuncy opened a new issue, #23823:
URL: https://github.com/apache/datafusion/issues/23823

   `datafusion_proto::bytes::logical_plan_to_bytes` can abort with a native 
stack overflow in a debug build while serializing a small, valid `LogicalPlan`.
   
   The overflow occurs during recursive logical-plan-to-protobuf conversion in:
   
   ```rust
   LogicalPlanNode::try_from_logical_plan(...)
   ```
   
   ## Reproducer
   
   Tested on macOS/aarch64 with DataFusion 54.0.0.
   
   ```rust
   use std::sync::Arc;
   
   use datafusion_common::DFSchema;
   use datafusion_expr::logical_plan::{EmptyRelation, LogicalPlan, 
LogicalPlanBuilder};
   use datafusion_proto::bytes::logical_plan_to_bytes;
   
   fn main() {
       let mut plan = LogicalPlan::EmptyRelation(EmptyRelation {
           produce_one_row: false,
           schema: Arc::new(DFSchema::empty()),
       });
   
       // SubqueryAlias x 10 -> EmptyRelation.
       // This requires 11 try_from_logical_plan invocations during 
serialization.
       for level in 0..10 {
           plan = LogicalPlanBuilder::from(plan)
               .alias(format!("level_{level}"))
               .unwrap()
               .build()
               .unwrap();
       }
   
       std::thread::Builder::new()
           .name("two-megabyte-stack".into())
           .stack_size(2 * 1024 * 1024)
           .spawn(move || logical_plan_to_bytes(&plan).unwrap())
           .unwrap()
           .join()
           .unwrap();
   }
   ```
   
   The default debug profile aborts:
   
   ```text
   thread 'two-megabyte-stack' has overflowed its stack
   fatal runtime error: stack overflow, aborting
   ```
   
   The same reproducer completes with `cargo run --release`.
   
   ## Measurement
   
   I added a temporary AArch64 stack-pointer probe at entry to 
`LogicalPlanNode::try_from_logical_plan`.
   
   In the instrumented debug build, each nested entry adds approximately 194 KB 
of stack:
   
   ```text
   depth  2:   193,952 bytes
   depth  3:   387,936 bytes
   depth 10: 1,745,824 bytes
   depth 11: 1,939,808 bytes
   depth 12: 2,133,792 bytes
   ```
   
   The 2 MiB test thread aborts while attempting to enter depth 11. With a 4 
MiB test thread, depth 11 completes and the 11-alias plan reaches depth 12.
   
   In the identically instrumented release build, each nested entry adds 14,976 
bytes; depth 12 completes at 164,736 bytes.
   
   The exact sizes are target/toolchain dependent, but the large 
debug-versus-release difference is reproducible.
   
   ## Expected behavior
   
   Serializing this valid logical plan should complete, or return a recoverable 
error. It should not abort the process through native stack overflow.
   
   ## Possible direction
   
   `try_from_logical_plan` is a large recursive `match` over all `LogicalPlan` 
variants. Reducing the size of the recursive path, or using iterative 
post-order conversion, would make logical-plan serialization stack-safe.
   
   Related prior art:
   
   - #1047 reduced a large recursive debug stack frame by separating 
non-recursive work from the recursive path.
   - #5693 tracks stack-safe logical-expression serde.
   - #16788 tracks broader DataFusion stack-safety restructuring.
   


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