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 1828740989 MINOR: [C++][Gandiva] cast to unsigned char before ctype 
calls (#50124)
1828740989 is described below

commit 1828740989d8feb8abdd4a2beed2d48257da4359
Author: metsw24-max <[email protected]>
AuthorDate: Mon Jul 6 18:45:54 2026 +0530

    MINOR: [C++][Gandiva] cast to unsigned char before ctype calls (#50124)
    
    ### Rationale for this change
    
    The Gandiva precompiled string and time helpers feed raw `char` bytes into 
`isdigit`/`isxdigit`/`isalpha`/`toupper`. Where `char` is signed, any byte 
above 0x7f (so any multi-byte UTF-8 value coming from untrusted column data) is 
sign-extended to a negative `int`, which sits outside the `unsigned char`/`EOF` 
domain those functions are defined for. glibc tolerates it, but a strict ctype 
implementation indexes its lookup table before the start of the array, which is 
undefined behaviour. [...]
    
    ### What changes are included in this PR?
    
    Cast each ctype argument to `unsigned char` at the call site:
    
    * `time.cc`: `castDATE_utf8`, `castTIMESTAMP_utf8`, `castTIME_utf8`
    * `string_ops.cc`: `binary_string`, the `cast<INT>_varbinary` macro, 
`from_hex_utf8`, `soundex_utf8`
    
    The sweep covers both files so no call site is left behind. There is no 
behaviour change where `char` is unsigned or the ctype implementation is 
lenient; it just keeps the argument in range everywhere.
    
    ### Are these changes tested?
    
    No new test. This is a localised undefined-behaviour fix with no observable 
change on the platforms CI runs (`char` is unsigned or the ctype lookup is 
lenient there), and the existing Gandiva precompiled tests already exercise 
these functions.
    
    ### Are there any user-facing changes?
    
    No.
    
    Authored-by: metsw24-max <[email protected]>
    Signed-off-by: Sutou Kouhei <[email protected]>
---
 cpp/src/gandiva/precompiled/string_ops.cc | 16 +++++++++-------
 cpp/src/gandiva/precompiled/time.cc       |  6 +++---
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/cpp/src/gandiva/precompiled/string_ops.cc 
b/cpp/src/gandiva/precompiled/string_ops.cc
index 0dd02cb1d8..90484f2970 100644
--- a/cpp/src/gandiva/precompiled/string_ops.cc
+++ b/cpp/src/gandiva/precompiled/string_ops.cc
@@ -2357,7 +2357,8 @@ const char* binary_string(gdv_int64 context, const char* 
text, gdv_int32 text_le
         (text[i + 1] == 'x' || text[i + 1] == 'X')) {
       char hd1 = text[i + 2];
       char hd2 = text[i + 3];
-      if (isxdigit(hd1) && isxdigit(hd2)) {
+      if (isxdigit(static_cast<unsigned char>(hd1)) &&
+          isxdigit(static_cast<unsigned char>(hd2))) {
         // [a-fA-F0-9]
         ret[j] = to_binary_from_hex(hd1) * 16 + to_binary_from_hex(hd2);
         i += 3;
@@ -2406,7 +2407,7 @@ const char* binary_string(gdv_int64 context, const char* 
text, gdv_int32 text_le
     int read_index = 0;                                                        
         \
     while (read_index < in_len) {                                              
         \
       char c1 = in[read_index];                                                
         \
-      if (isxdigit(c1)) {                                                      
         \
+      if (isxdigit(static_cast<unsigned char>(c1))) {                          
         \
         digit = to_binary_from_hex(c1);                                        
         \
                                                                                
         \
         OUT_TYPE next = result * 16 - digit;                                   
         \
@@ -2956,7 +2957,8 @@ const char* from_hex_utf8(int64_t context, const char* 
text, int32_t text_len,
   for (int32_t i = 0; i < text_len; i += 2) {
     char b1 = text[i];
     char b2 = text[i + 1];
-    if (isxdigit(b1) && isxdigit(b2)) {
+    if (isxdigit(static_cast<unsigned char>(b1)) &&
+        isxdigit(static_cast<unsigned char>(b2))) {
       // [a-fA-F0-9]
       ret[j++] = to_binary_from_hex(b1) * 16 + to_binary_from_hex(b2);
     } else {
@@ -3024,9 +3026,9 @@ const char* soundex_utf8(gdv_int64 context, const char* 
in, gdv_int32 in_len,
 
   int start_idx = 0;
   for (int i = 0; i < in_len; ++i) {
-    if (isalpha(in[i]) > 0) {
+    if (isalpha(static_cast<unsigned char>(in[i])) > 0) {
       // Retain the first letter
-      ret[0] = toupper(in[i]);
+      ret[0] = toupper(static_cast<unsigned char>(in[i]));
       start_idx = i + 1;
       break;
     }
@@ -3042,8 +3044,8 @@ const char* soundex_utf8(gdv_int64 context, const char* 
in, gdv_int32 in_len,
   soundex[0] = '\0';
   // Replace consonants with digits and special letters with 0
   for (int i = start_idx; i < in_len; i++) {
-    if (isalpha(in[i]) > 0) {
-      c = toupper(in[i]) - 65;
+    if (isalpha(static_cast<unsigned char>(in[i])) > 0) {
+      c = toupper(static_cast<unsigned char>(in[i])) - 65;
       if (mappings[c] != soundex[si - 1]) {
         soundex[si] = mappings[c];
         si++;
diff --git a/cpp/src/gandiva/precompiled/time.cc 
b/cpp/src/gandiva/precompiled/time.cc
index f7028730eb..2b60f63651 100644
--- a/cpp/src/gandiva/precompiled/time.cc
+++ b/cpp/src/gandiva/precompiled/time.cc
@@ -648,7 +648,7 @@ gdv_date64 castDATE_utf8(int64_t context, const char* 
input, gdv_int32 length) {
   int dateIndex = 0, index = 0, value = 0;
   int year_str_len = 0;
   while (dateIndex < 3 && index < length) {
-    if (!isdigit(input[index])) {
+    if (!isdigit(static_cast<unsigned char>(input[index]))) {
       dateFields[dateIndex++] = value;
       value = 0;
     } else {
@@ -718,7 +718,7 @@ gdv_timestamp castTIMESTAMP_utf8(int64_t context, const 
char* input, gdv_int32 l
   int year_str_len = 0, sub_seconds_len = 0;
   int ts_field_index = TimeFields::kYear, index = 0, value = 0;
   while (ts_field_index < TimeFields::kMax && index < length) {
-    if (isdigit(input[index])) {
+    if (isdigit(static_cast<unsigned char>(input[index]))) {
       value = (value * 10) + (input[index] - '0');
       if (ts_field_index == TimeFields::kYear) {
         year_str_len++;
@@ -847,7 +847,7 @@ gdv_time32 castTIME_utf8(int64_t context, const char* 
input, int32_t length) {
 
   bool has_invalid_digit = false;
   while (time_field_idx < TimeFields::kDisplacementHours && index < length) {
-    if (isdigit(input[index])) {
+    if (isdigit(static_cast<unsigned char>(input[index]))) {
       value = (value * 10) + (input[index] - '0');
 
       if (time_field_idx == TimeFields::kSubSeconds) {

Reply via email to