|
18 | 18 | #include <gmock/gmock.h> |
19 | 19 | #include <gtest/gtest.h> |
20 | 20 |
|
| 21 | +#include <cstring> |
21 | 22 | #include <limits> |
| 23 | +#include <memory> |
22 | 24 |
|
23 | 25 | #include "gandiva/execution_context.h" |
24 | 26 | #include "gandiva/precompiled/types.h" |
@@ -1608,6 +1610,60 @@ TEST(TestStringOps, TestRpadString) { |
1608 | 1610 | EXPECT_EQ(std::string(out_str + 5000, 2), "α"); |
1609 | 1611 | } |
1610 | 1612 |
|
| 1613 | +TEST(TestStringOps, TestPadMalformedUtf8NoOverread) { |
| 1614 | + gandiva::ExecutionContext ctx; |
| 1615 | + uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx); |
| 1616 | + gdv_int32 out_len = 0; |
| 1617 | + |
| 1618 | + // A 4-byte utf8 lead byte followed by non-continuation bytes and no trailing |
| 1619 | + // space. utf8_length_ignore_invalid() used to extend the glyph length past |
| 1620 | + // the end of the buffer while scanning the continuation bytes. The input is |
| 1621 | + // held in an exactly-sized heap buffer so any over-read trips AddressSanitizer. |
| 1622 | + const char bytes[] = {'\xF0', 'a', 'a', 'a'}; |
| 1623 | + const auto text_len = static_cast<gdv_int32>(sizeof(bytes)); |
| 1624 | + std::unique_ptr<char[]> text(new char[text_len]); |
| 1625 | + std::memcpy(text.get(), bytes, text_len); |
| 1626 | + const std::string text_str(text.get(), text_len); |
| 1627 | + |
| 1628 | + // The lone lead byte counts as one invalid glyph and the three 'a's as one |
| 1629 | + // each, so the length is 4 and padding to width 6 adds two fill characters. |
| 1630 | + const char* out_str = |
| 1631 | + lpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 6, " ", 1, &out_len); |
| 1632 | + EXPECT_EQ(out_len, 6); |
| 1633 | + EXPECT_EQ(std::string(out_str, out_len), " " + text_str); |
| 1634 | + |
| 1635 | + out_str = rpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 6, " ", 1, &out_len); |
| 1636 | + EXPECT_EQ(out_len, 6); |
| 1637 | + EXPECT_EQ(std::string(out_str, out_len), text_str + " "); |
| 1638 | +} |
| 1639 | + |
| 1640 | +TEST(TestStringOps, TestPadMalformedUtf8KeepsValidGlyph) { |
| 1641 | + gandiva::ExecutionContext ctx; |
| 1642 | + uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx); |
| 1643 | + gdv_int32 out_len = 0; |
| 1644 | + |
| 1645 | + // {0xF0, 'a', 0xE2, 0x82, 0xAC}: malformed 4-byte lead + ASCII 'a' + U+20AC €. |
| 1646 | + // 0xF0 alone counts as one invalid glyph, then 'a' and € follow on their own, |
| 1647 | + // so the count is 3. If the inner scan kept char_len at 4 it would advance the |
| 1648 | + // outer loop past 'a', 0xE2, 0x82 and only see the orphaned 0xAC, giving 2. |
| 1649 | + // The input sits in an exactly-sized heap buffer so any over-read trips ASAN. |
| 1650 | + const char bytes[] = {'\xF0', 'a', '\xE2', '\x82', '\xAC'}; |
| 1651 | + const auto text_len = static_cast<gdv_int32>(sizeof(bytes)); |
| 1652 | + std::unique_ptr<char[]> text(new char[text_len]); |
| 1653 | + std::memcpy(text.get(), bytes, text_len); |
| 1654 | + const std::string text_str(text.get(), text_len); |
| 1655 | + |
| 1656 | + // 3 glyphs padded to width 5 adds two fill characters, out_len = 2 + 5 = 7. |
| 1657 | + const char* out_str = |
| 1658 | + lpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 5, " ", 1, &out_len); |
| 1659 | + EXPECT_EQ(out_len, 7); |
| 1660 | + EXPECT_EQ(std::string(out_str, out_len), " " + text_str); |
| 1661 | + |
| 1662 | + out_str = rpad_utf8_int32_utf8(ctx_ptr, text.get(), text_len, 5, " ", 1, &out_len); |
| 1663 | + EXPECT_EQ(out_len, 7); |
| 1664 | + EXPECT_EQ(std::string(out_str, out_len), text_str + " "); |
| 1665 | +} |
| 1666 | + |
1611 | 1667 | TEST(TestStringOps, TestRtrim) { |
1612 | 1668 | gandiva::ExecutionContext ctx; |
1613 | 1669 | uint64_t ctx_ptr = reinterpret_cast<gdv_int64>(&ctx); |
@@ -1903,6 +1959,20 @@ TEST(TestStringOps, TestByteSubstr) { |
1903 | 1959 | out_str = byte_substr_binary_int32_int32(ctx_ptr, "TestString", 10, -100, 10, &out_len); |
1904 | 1960 | EXPECT_EQ(std::string(out_str, out_len), "TestString"); |
1905 | 1961 | EXPECT_FALSE(ctx.has_error()); |
| 1962 | + |
| 1963 | + // offset past the end of the text must yield an empty result, not a negative |
| 1964 | + // length that memcpy reads as an out-of-bounds copy |
| 1965 | + out_str = byte_substr_binary_int32_int32(ctx_ptr, "TestString", 10, 15, 10, &out_len); |
| 1966 | + EXPECT_EQ(out_len, 0); |
| 1967 | + EXPECT_EQ(std::string(out_str, out_len), ""); |
| 1968 | + EXPECT_FALSE(ctx.has_error()); |
| 1969 | + |
| 1970 | + // a huge length must be truncated to the remaining bytes, not overflow |
| 1971 | + // startPos + length and copy far past the end of the text |
| 1972 | + out_str = |
| 1973 | + byte_substr_binary_int32_int32(ctx_ptr, "TestString", 10, 2, 2147483647, &out_len); |
| 1974 | + EXPECT_EQ(std::string(out_str, out_len), "estString"); |
| 1975 | + EXPECT_FALSE(ctx.has_error()); |
1906 | 1976 | } |
1907 | 1977 |
|
1908 | 1978 | TEST(TestStringOps, TestStrPos) { |
|
0 commit comments