@@ -1914,21 +1914,29 @@ const char* replace_utf8_utf8_utf8(gdv_int64 context, const char* text,
19141914 gdv_int32 text_len, const char * from_str,
19151915 gdv_int32 from_str_len, const char * to_str,
19161916 gdv_int32 to_str_len, gdv_int32* out_len) {
1917- // Count non-overlapping matches to size the output buffer exactly, so large
1918- // results are not capped by an arbitrary limit.
1919- gdv_int64 num_matches = 0 ;
1920- if (from_str_len > 0 && from_str_len <= text_len) {
1921- for (gdv_int32 i = 0 ; i <= text_len - from_str_len;) {
1922- if (memcmp (text + i, from_str, from_str_len) == 0 ) {
1923- num_matches++;
1924- i += from_str_len;
1925- } else {
1926- i++;
1917+ // Size the output buffer to the exact result, so large results are not capped
1918+ // by an arbitrary limit. When the replacement is no longer than the matched
1919+ // text, the result can only shrink or stay the same, so text_len is a safe
1920+ // bound and we can skip scanning. Otherwise count non-overlapping matches to
1921+ // get the exact expanded size.
1922+ gdv_int64 max_length;
1923+ if (to_str_len <= from_str_len) {
1924+ max_length = text_len;
1925+ } else {
1926+ gdv_int64 num_matches = 0 ;
1927+ if (from_str_len > 0 && from_str_len <= text_len) {
1928+ for (gdv_int32 i = 0 ; i <= text_len - from_str_len;) {
1929+ if (memcmp (text + i, from_str, from_str_len) == 0 ) {
1930+ num_matches++;
1931+ i += from_str_len;
1932+ } else {
1933+ i++;
1934+ }
19271935 }
19281936 }
1937+ max_length =
1938+ static_cast <gdv_int64>(text_len) + num_matches * (to_str_len - from_str_len);
19291939 }
1930- gdv_int64 max_length =
1931- static_cast <gdv_int64>(text_len) + num_matches * (to_str_len - from_str_len);
19321940 // Gandiva variable-length output uses int32 offsets, so a single output string
19331941 // cannot exceed INT_MAX bytes. Report this explicitly instead of letting the
19341942 // cast below wrap silently.
0 commit comments