Skip to content

Commit 6d6bf39

Browse files
committed
More tests
1 parent e2b7c99 commit 6d6bf39

2 files changed

Lines changed: 40 additions & 12 deletions

File tree

cpp/src/gandiva/precompiled/string_ops.cc

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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.

cpp/src/gandiva/precompiled/string_ops_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1995,6 +1995,26 @@ TEST(TestStringOps, TestReplace) {
19951995
EXPECT_EQ(std::string(out_str, out_len), large_shrink_expected);
19961996
EXPECT_FALSE(ctx.has_error());
19971997

1998+
// Edge case: result size of exactly 0 (every byte of text is removed). Takes
1999+
// the no-scan shrink path (to_str_len <= from_str_len).
2000+
out_str = replace_utf8_utf8_utf8(ctx_ptr, "aaaa", 4, "a", 1, "", 0, &out_len);
2001+
EXPECT_EQ(out_len, 0);
2002+
EXPECT_EQ(std::string(out_str, out_len), "");
2003+
EXPECT_FALSE(ctx.has_error());
2004+
2005+
// Edge case: result size one past the INT_MAX boundary. 65536 single-char
2006+
// matches each expanding to 32768 bytes gives max_length = 65536 * 32768 =
2007+
// 2^31 = INT_MAX + 1, so it is reported cleanly (guard fires before any alloc).
2008+
std::string boundary_in(65536, 'a');
2009+
std::string boundary_to(32768, 'b');
2010+
replace_utf8_utf8_utf8(ctx_ptr, boundary_in.data(),
2011+
static_cast<int32_t>(boundary_in.size()), "a", 1,
2012+
boundary_to.data(), static_cast<int32_t>(boundary_to.size()),
2013+
&out_len);
2014+
EXPECT_THAT(ctx.get_error(), ::testing::HasSubstr("exceeds maximum size"));
2015+
EXPECT_EQ(out_len, 0);
2016+
ctx.Reset();
2017+
19982018
// Output that would exceed INT_MAX (2GB) is reported cleanly rather than
19992019
// silently wrapping the int32 size. 50000 matches each expanding to 50000
20002020
// bytes implies max_length = 2.5e9; the guard fires before any large alloc.

0 commit comments

Comments
 (0)