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

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


The following commit(s) were added to refs/heads/main by this push:
     new 2c1293f3c5 GH-47447: [C++] Fix replace_with_mask for null type arrays 
(#49950)
2c1293f3c5 is described below

commit 2c1293f3c5438202e133a59d9478cecef33b35ea
Author: AnkitAhlawat <[email protected]>
AuthorDate: Thu May 28 08:10:56 2026 +0530

    GH-47447: [C++] Fix replace_with_mask for null type arrays (#49950)
    
    ### Rationale for this change
    replace_with_mask crashes when called with null type arrays because a code 
path incorrectly returns Status::OK() from a function with return type 
Result<int64_t>.
    This PR fixes the issue by ensuring the function returns a valid int64_t 
value instead of Status::OK() for successful execution.
    
    ### What changes are included in this PR?
    
    1. Fixed the replace_with_mask kernel implementation for null type arrays.
    2.  Replaced the invalid Status::OK() return path with a valid int64_t 
result.
    3.  Prevented construction of Result<int64_t> with an OK Status.
    
    ### Are these changes tested?
    Yes, manually run the test case
    ### Are there any user-facing changes?
    No
    
    **This PR contains a "Critical Fix".**
    
    This change fixes a crash in replace_with_mask when operating on null type 
arrays. The crash occurs due to an invalid successful return path internally 
constructing Result<T> with Status::OK().
    
    * GitHub Issue: #47447
    
    Authored-by: [email protected] <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 cpp/src/arrow/compute/kernels/vector_replace.cc    |  8 ++---
 .../arrow/compute/kernels/vector_replace_test.cc   | 36 ++++++++++++++++++++++
 python/pyarrow/tests/test_compute.py               | 28 +++++++++++++++++
 3 files changed, 68 insertions(+), 4 deletions(-)

diff --git a/cpp/src/arrow/compute/kernels/vector_replace.cc 
b/cpp/src/arrow/compute/kernels/vector_replace.cc
index 1a3e743784..ae39977aae 100644
--- a/cpp/src/arrow/compute/kernels/vector_replace.cc
+++ b/cpp/src/arrow/compute/kernels/vector_replace.cc
@@ -217,15 +217,15 @@ struct ReplaceMaskImpl<Type, enable_if_null<Type>> {
   static Result<int64_t> ExecScalarMask(KernelContext* ctx, const ArraySpan& 
array,
                                         const BooleanScalar& mask, ExecValue 
replacements,
                                         int64_t replacements_offset, 
ExecResult* out) {
-    out->value = array;
-    return Status::OK();
+    out->value = array.ToArrayData();
+    return replacements_offset;
   }
   static Result<int64_t> ExecArrayMask(KernelContext* ctx, const ArraySpan& 
array,
                                        const ArraySpan& mask, int64_t 
mask_offset,
                                        ExecValue replacements,
                                        int64_t replacements_offset, 
ExecResult* out) {
-    out->value = array;
-    return Status::OK();
+    out->value = array.ToArrayData();
+    return replacements_offset;
   }
 };
 
diff --git a/cpp/src/arrow/compute/kernels/vector_replace_test.cc 
b/cpp/src/arrow/compute/kernels/vector_replace_test.cc
index 587b9f2a60..9dc8e70ab6 100644
--- a/cpp/src/arrow/compute/kernels/vector_replace_test.cc
+++ b/cpp/src/arrow/compute/kernels/vector_replace_test.cc
@@ -199,6 +199,13 @@ class TestReplaceBoolean : public 
TestReplaceKernel<BooleanType> {
   }
 };
 
+class TestReplaceNull : public TestReplaceKernel<NullType> {
+ protected:
+  std::shared_ptr<DataType> type() override {
+    return TypeTraits<NullType>::type_singleton();
+  }
+};
+
 class TestReplaceFixedSizeBinary : public 
TestReplaceKernel<FixedSizeBinaryType> {
  protected:
   std::shared_ptr<DataType> type() override { return fixed_size_binary(3); }
@@ -538,6 +545,35 @@ TEST_F(TestReplaceBoolean, ReplaceWithMask) {
   }
 }
 
+TEST_F(TestReplaceNull, ReplaceWithMask) {
+  std::vector<ReplaceWithMaskCase> cases = {
+      {this->array("[]"), this->mask_scalar(false), this->array("[]"), 
this->array("[]")},
+      {this->array("[]"), this->mask_scalar(true), this->array("[]"), 
this->array("[]")},
+      {this->array("[]"), this->null_mask_scalar(), this->array("[]"), 
this->array("[]")},
+
+      {this->array("[null]"), this->mask_scalar(false), this->array("[]"),
+       this->array("[null]")},
+
+      {this->array("[null]"), this->mask_scalar(true), this->array("[null]"),
+       this->array("[null]")},
+
+      {this->array("[null]"), this->null_mask_scalar(), this->array("[]"),
+       this->array("[null]")},
+
+      {this->array("[null, null]"), this->mask("[false, false]"), 
this->array("[]"),
+       this->array("[null, null]")},
+      {this->array("[null, null]"), this->mask("[true, true]"),
+       this->array("[null, null]"), this->array("[null, null]")},
+      {this->array("[null, null]"), this->mask("[null, null]"), 
this->array("[]"),
+       this->array("[null, null]")},
+  };
+
+  for (auto test_case : cases) {
+    this->Assert(ReplaceWithMask, test_case.input, test_case.mask, 
test_case.replacements,
+                 test_case.expected);
+  }
+}
+
 TEST_F(TestReplaceBoolean, ReplaceWithMaskErrors) {
   EXPECT_RAISES_WITH_MESSAGE_THAT(
       Invalid,
diff --git a/python/pyarrow/tests/test_compute.py 
b/python/pyarrow/tests/test_compute.py
index 4e44a912d9..1c82d6c944 100644
--- a/python/pyarrow/tests/test_compute.py
+++ b/python/pyarrow/tests/test_compute.py
@@ -1269,6 +1269,34 @@ def test_extract_regex_span():
     assert struct.tolist() == expected
 
 
+def test_replace_with_mask_null_type():
+    # GH-47447: replace_with_mask crashed for null type arrays
+    input = pa.array([None], pa.null())
+    replacements = pa.array([None], pa.null())
+
+    result = pc.replace_with_mask(input, True, replacements)
+    assert result.type == pa.null()
+    result.validate(full=True)
+    assert result.to_pylist() == [None]
+
+    result = pc.replace_with_mask(input, False, replacements)
+    assert result.type == pa.null()
+    result.validate(full=True)
+    assert result.to_pylist() == [None]
+
+    mask = pa.array([True])
+    result = pc.replace_with_mask(input, mask, replacements)
+    assert result.type == pa.null()
+    result.validate(full=True)
+    assert result.to_pylist() == [None]
+
+    mask = pa.array([False])
+    result = pc.replace_with_mask(input, mask, replacements)
+    assert result.type == pa.null()
+    result.validate(full=True)
+    assert result.to_pylist() == [None]
+
+
 def test_binary_join():
     ar_list = pa.array([['foo', 'bar'], None, []])
     expected = pa.array(['foo-bar', None, ''])

Reply via email to