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

jbonofre pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-java.git


The following commit(s) were added to refs/heads/main by this push:
     new 4b2d53ed1 GH-1205: Fix byte-array element leak in FromSchemaByteArray 
(#1249)
4b2d53ed1 is described below

commit 4b2d53ed1639173b30aa0dc694ce3a0fa9965bb6
Author: Prateek Ganigi <[email protected]>
AuthorDate: Tue Oct 6 01:14:40 2026 -0700

    GH-1205: Fix byte-array element leak in FromSchemaByteArray (#1249)
    
    ## What's Changed
    
    `FromSchemaByteArray()` in `dataset/src/main/cpp/jni_util.cc` acquires
    the Java byte-array elements via `GetByteArrayElements()`, but the
    matching `ReleaseByteArrayElements()` was only reached on the success
    path. When `arrow::ipc::ReadSchema()` failed, `ARROW_ASSIGN_OR_RAISE`
    returned early and skipped the release, leaking the pinned/copied array
    buffer on every call with malformed or incompatible serialized schema
    bytes (e.g. via `createDataset()`).
    
    This releases the elements through an RAII guard so they are always
    freed on every exit path - success or error, and stays correct if
    additional early returns are added later.
    
    Closes #1205.
---
 dataset/src/main/cpp/jni_util.cc                   | 12 ++--
 .../arrow/dataset/jni/TestFromSchemaByteArray.java | 79 ++++++++++++++++++++++
 2 files changed, 87 insertions(+), 4 deletions(-)

diff --git a/dataset/src/main/cpp/jni_util.cc b/dataset/src/main/cpp/jni_util.cc
index 35bfb328f..23c9db06c 100644
--- a/dataset/src/main/cpp/jni_util.cc
+++ b/dataset/src/main/cpp/jni_util.cc
@@ -352,13 +352,17 @@ arrow::Result<std::shared_ptr<arrow::Schema>> 
FromSchemaByteArray(
   arrow::ipc::DictionaryMemo in_memo;
   int schemaBytes_len = env->GetArrayLength(schemaBytes);
   jbyte* schemaBytes_data = env->GetByteArrayElements(schemaBytes, nullptr);
+  // Ensure the pinned/copied Java array elements are always released, even 
when
+  // schema parsing fails and an error Result is returned early below.
+  auto release_elements = [&](jbyte* data) {
+    env->ReleaseByteArrayElements(schemaBytes, data, JNI_ABORT);
+  };
+  std::unique_ptr<jbyte, decltype(release_elements)> 
elements_guard(schemaBytes_data,
+                                                                    
release_elements);
   auto serialized_schema = std::make_shared<arrow::Buffer>(
       reinterpret_cast<uint8_t*>(schemaBytes_data), schemaBytes_len);
   arrow::io::BufferReader buf_reader(serialized_schema);
-  ARROW_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Schema> schema,
-                        arrow::ipc::ReadSchema(&buf_reader, &in_memo))
-  env->ReleaseByteArrayElements(schemaBytes, schemaBytes_data, JNI_ABORT);
-  return schema;
+  return arrow::ipc::ReadSchema(&buf_reader, &in_memo);
 }
 arrow::Status ExportRecordBatch(JNIEnv* env, const 
std::shared_ptr<RecordBatch>& batch,
                                 jlong struct_array) {
diff --git 
a/dataset/src/test/java/org/apache/arrow/dataset/jni/TestFromSchemaByteArray.java
 
b/dataset/src/test/java/org/apache/arrow/dataset/jni/TestFromSchemaByteArray.java
new file mode 100644
index 000000000..238678498
--- /dev/null
+++ 
b/dataset/src/test/java/org/apache/arrow/dataset/jni/TestFromSchemaByteArray.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.arrow.dataset.jni;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import org.apache.arrow.dataset.ParquetWriteSupport;
+import org.apache.arrow.dataset.file.FileFormat;
+import org.apache.arrow.dataset.file.FileSystemDatasetFactory;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+/**
+ * Regression test for the native {@code FromSchemaByteArray} helper 
(GH-1205). Passing malformed
+ * serialized schema bytes to {@code JniWrapper#createDataset} exercises the 
former leak branch in
+ * {@code FromSchemaByteArray} (where {@code ReleaseByteArrayElements} was 
skipped on error) and
+ * asserts it fails gracefully with a Java exception rather than crashing.
+ *
+ * <p>This guards the error path; it does not directly assert that the native 
byte-array elements
+ * were released. The leaked bytes are a JVM-internal copy made by {@code 
GetByteArrayElements},
+ * which is not tracked by {@code NativeMemoryPool} nor observable through any 
portable Java API, so
+ * a deterministic leak assertion isn't feasible here. See the PR #1249 
discussion for details.
+ */
+public class TestFromSchemaByteArray extends TestNativeDataset {
+
+  @TempDir public File TMP;
+
+  public static final String AVRO_SCHEMA_USER = "user.avsc";
+
+  private static long factoryId(NativeDatasetFactory factory) throws Exception 
{
+    Field field = 
NativeDatasetFactory.class.getDeclaredField("datasetFactoryId");
+    field.setAccessible(true);
+    return field.getLong(factory);
+  }
+
+  @Test
+  public void testCreateDatasetWithMalformedSchemaBytes() throws Exception {
+    ParquetWriteSupport writeSupport =
+        ParquetWriteSupport.writeTempFile(AVRO_SCHEMA_USER, TMP, 1, "a");
+    FileSystemDatasetFactory factory =
+        new FileSystemDatasetFactory(
+            rootAllocator(),
+            NativeMemoryPool.getDefault(),
+            FileFormat.PARQUET,
+            writeSupport.getOutputURI());
+    try {
+      final long datasetFactoryId = factoryId(factory);
+      // Bytes that are not a valid serialized Arrow schema, so native 
ReadSchema fails and the
+      // error path of FromSchemaByteArray is taken.
+      final byte[] malformedSchemaBytes = new byte[] {0, 1, 2, 3, 4, 5, 6, 7};
+
+      // Repeat many times to keep hitting the error path that previously 
skipped the element
+      // release; each iteration must fail gracefully rather than crash.
+      for (int i = 0; i < 1000; i++) {
+        assertThrows(
+            RuntimeException.class,
+            () -> JniWrapper.get().createDataset(datasetFactoryId, 
malformedSchemaBytes));
+      }
+    } finally {
+      factory.close();
+    }
+  }
+}

Reply via email to