This is an automated email from the ASF dual-hosted git repository.

dongjoon-hyun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/spark-connect-swift.git


The following commit(s) were added to refs/heads/main by this push:
     new 0a6f81a  [SPARK-58051] Replace release-stripped asserts with runtime 
validation
0a6f81a is described below

commit 0a6f81a55766db05dacf710b6637f35b7eda4b98
Author: Dongjoon Hyun <[email protected]>
AuthorDate: Wed Jul 8 13:58:27 2026 -0700

    [SPARK-58051] Replace release-stripped asserts with runtime validation
    
    ### What changes were proposed in this pull request?
    
    Replace release-stripped `assert(...)` calls with `guard`/`throw` 
validation and add `SparkConnectError.InvalidArrowData`:
    
    - `DataFrame.execute()` — validate Arrow IPC continuation tokens and 
bounds-check server-supplied sizes before slicing.
    - `DataFrame.show()` — require one row / one column before accessing 
`rows[0]`.
    - `DataFrameWriter.executeWriteOperation` — reject `path` and `tableName` 
both set.
    - `SparkSession.Builder.create()` — require `setConf` success.
    
    ### Why are the changes needed?
    
    `assert(...)` is compiled out in release builds, so these checks never ran 
in production. A malformed server response could trap while slicing on 
unvalidated sizes. `guard`/`throw` makes the checks effective in release and 
turns traps into recoverable errors.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Pass the CIs.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Opus 4.8
    
    Closes #435 from dongjoon-hyun/SPARK-58051.
    
    Authored-by: Dongjoon Hyun <[email protected]>
    Signed-off-by: Dongjoon Hyun <[email protected]>
---
 Sources/SparkConnect/DataFrame+Actions.swift | 31 +++++++++++++++++++++-------
 Sources/SparkConnect/DataFrameWriter.swift   |  4 +++-
 Sources/SparkConnect/SparkConnectError.swift |  1 +
 Sources/SparkConnect/SparkSession.swift      |  4 +++-
 4 files changed, 31 insertions(+), 9 deletions(-)

diff --git a/Sources/SparkConnect/DataFrame+Actions.swift 
b/Sources/SparkConnect/DataFrame+Actions.swift
index 7962bcc..5b5299a 100644
--- a/Sources/SparkConnect/DataFrame+Actions.swift
+++ b/Sources/SparkConnect/DataFrame+Actions.swift
@@ -51,21 +51,37 @@ extension DataFrame {
           let ipcStreamBytes = m.arrowBatch.data
           if !ipcStreamBytes.isEmpty && m.arrowBatch.rowCount > 0 {
             let IPC_CONTINUATION_TOKEN = Int32(-1)
+            let totalSize = Int64(ipcStreamBytes.count)
             // Schema
-            assert(ipcStreamBytes[0..<4].int32 == IPC_CONTINUATION_TOKEN)
+            guard totalSize >= 8,
+              ipcStreamBytes[0..<4].int32 == IPC_CONTINUATION_TOKEN
+            else {
+              throw SparkConnectError.InvalidArrowData
+            }
             let schemaSize = Int64(ipcStreamBytes[4..<8].int32)
+            guard schemaSize >= 0, 8 + schemaSize + 4 <= totalSize else {
+              throw SparkConnectError.InvalidArrowData
+            }
             let schema = Data(ipcStreamBytes[8..<(8 + schemaSize)])
 
             // Arrow IPC Data
-            assert(
-              ipcStreamBytes[(8 + schemaSize)..<(8 + schemaSize + 4)].int32
-                == IPC_CONTINUATION_TOKEN)
+            guard ipcStreamBytes[(8 + schemaSize)..<(8 + schemaSize + 4)].int32
+              == IPC_CONTINUATION_TOKEN
+            else {
+              throw SparkConnectError.InvalidArrowData
+            }
             var pos: Int64 = 8 + schemaSize + 4
+            guard pos + 4 <= totalSize else {
+              throw SparkConnectError.InvalidArrowData
+            }
             let dataHeaderSize = Int64(ipcStreamBytes[pos..<(pos + 4)].int32)
             pos += 4
+            guard dataHeaderSize >= 0, pos + dataHeaderSize + 8 <= totalSize 
else {
+              throw SparkConnectError.InvalidArrowData
+            }
             let dataHeader = Data(ipcStreamBytes[pos..<(pos + dataHeaderSize)])
             pos += dataHeaderSize
-            let dataBodySize = Int64(ipcStreamBytes.count) - pos - 8
+            let dataBodySize = totalSize - pos - 8
             let dataBody = Data(ipcStreamBytes[pos..<(pos + dataBodySize)])
 
             // Read ArrowBatches
@@ -196,8 +212,9 @@ extension DataFrame {
   ///   - vertical: If set to true, prints output rows vertically (one line 
per column value).
   public func show(_ numRows: Int32, _ truncate: Int32, _ vertical: Bool = 
false) async throws {
     let rows = try await showString(numRows, truncate, vertical).collect()
-    assert(rows.count == 1)
-    assert(rows[0].length == 1)
+    guard rows.count == 1, rows[0].length == 1 else {
+      throw SparkConnectError.InvalidArrowData
+    }
     print(try rows[0].get(0) as! String)
   }
 
diff --git a/Sources/SparkConnect/DataFrameWriter.swift 
b/Sources/SparkConnect/DataFrameWriter.swift
index df35c45..07a3f82 100644
--- a/Sources/SparkConnect/DataFrameWriter.swift
+++ b/Sources/SparkConnect/DataFrameWriter.swift
@@ -147,7 +147,9 @@ public actor DataFrameWriter: Sendable {
     var write = f()
 
     // Cannot both be set
-    assert(!(!write.path.isEmpty && !write.table.tableName.isEmpty))
+    guard write.path.isEmpty || write.table.tableName.isEmpty else {
+      throw SparkConnectError.InvalidArgument
+    }
 
     let plan = await self.df.getPlan() as! Plan
     write.input = plan.root
diff --git a/Sources/SparkConnect/SparkConnectError.swift 
b/Sources/SparkConnect/SparkConnectError.swift
index a0d4efe..45de0a9 100644
--- a/Sources/SparkConnect/SparkConnectError.swift
+++ b/Sources/SparkConnect/SparkConnectError.swift
@@ -23,6 +23,7 @@ public enum SparkConnectError: Error {
   case ColumnNotFound
   case DataSourceNotFound
   case InvalidArgument
+  case InvalidArrowData
   case InvalidSessionID
   case InvalidType
   case InvalidViewName
diff --git a/Sources/SparkConnect/SparkSession.swift 
b/Sources/SparkConnect/SparkSession.swift
index 5a007f6..6a366aa 100644
--- a/Sources/SparkConnect/SparkSession.swift
+++ b/Sources/SparkConnect/SparkSession.swift
@@ -608,7 +608,9 @@ public actor SparkSession {
       let response = try await session.client.connect(session.sessionID)
       await session.setVersion(response.sparkVersion.version)
       let isSuccess = try await session.client.setConf(map: sparkConf)
-      assert(isSuccess)
+      guard isSuccess else {
+        throw SparkConnectError.InvalidArgument
+      }
       return session
     }
 


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

Reply via email to