From 16ab94f61a8e41b387e238706628e635ff0af02d Mon Sep 17 00:00:00 2001 From: Timo Klock Date: Wed, 11 Nov 2026 12:14:50 +0000 Subject: [PATCH] Accept end == size() in two-argument string substring `.substring(start, end)` failed with " or is greater than .size()" whenever `end` equalled the string's length and `start < end`, so `'abc'.substring(1, 3)` errored instead of returning `'bc'`. The end offset is exclusive, so `end == size()` is the only way to take a range that includes the last character. Both `SubstringImpl(..., start, end)` overloads returned from inside the loop only when they reached `end` with input left, and after the loop handled only `start == end == size`. The post-loop check now records `start` and returns when `end` sits at the end of the input. --- common/values/string_value.cc | 14 ++++++++++---- common/values/string_value_test.cc | 6 ++++++ extensions/strings_test.cc | 3 +++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/common/values/string_value.cc b/common/values/string_value.cc index b9495fa69..914f6e496 100644 --- a/common/values/string_value.cc +++ b/common/values/string_value.cc @@ -667,8 +667,11 @@ absl::StatusOr> SubstringImpl( ++size_code_points; size_code_units += code_units; } - if (size_code_points == start && start == end) { - return std::pair{size_code_units, size_code_units}; + if (size_code_points == start) { + start_code_units = size_code_units; + } + if (size_code_points == end) { + return std::pair{start_code_units, size_code_units}; } return absl::InvalidArgumentError( ".substring(, ): or is greater than " @@ -697,8 +700,11 @@ absl::StatusOr> SubstringImpl(const absl::Cord& cord, ++size_code_points; size_code_units += code_units; } - if (size_code_points == start && start == end) { - return std::pair{size_code_units, size_code_units}; + if (size_code_points == start) { + start_code_units = size_code_units; + } + if (size_code_points == end) { + return std::pair{start_code_units, size_code_units}; } return absl::InvalidArgumentError( ".substring(, ): or is greater than " diff --git a/common/values/string_value_test.cc b/common/values/string_value_test.cc index 6bfcb83aa..c64151034 100644 --- a/common/values/string_value_test.cc +++ b/common/values/string_value_test.cc @@ -475,6 +475,12 @@ TEST_F(StringValueTest, Substring) { EXPECT_THAT(unicode_view.Substring(1, 2, arena()), StringValueIs("€")); EXPECT_THAT(unicode_cord.Substring(2, 4, arena()), StringValueIs("€€")); EXPECT_THAT(unicode_view.Substring(2, 4, arena()), StringValueIs("€€")); + EXPECT_THAT(unicode_cord.Substring(4, 6, arena()), StringValueIs("€€")); + EXPECT_THAT(unicode_view.Substring(4, 6, arena()), StringValueIs("€€")); + EXPECT_THAT(unicode_cord.Substring(0, 6, arena()), StringValueIs("€€€€€€")); + EXPECT_THAT(unicode_view.Substring(0, 6, arena()), StringValueIs("€€€€€€")); + EXPECT_THAT(unicode_cord.Substring(6, 6, arena()), StringValueIs("")); + EXPECT_THAT(unicode_view.Substring(6, 6, arena()), StringValueIs("")); EXPECT_THAT(unicode_cord.Substring(2, arena()), StringValueIs("€€€€")); EXPECT_THAT(unicode_view.Substring(2, arena()), StringValueIs("€€€€")); diff --git a/extensions/strings_test.cc b/extensions/strings_test.cc index c3059808f..46d9908eb 100644 --- a/extensions/strings_test.cc +++ b/extensions/strings_test.cc @@ -221,7 +221,10 @@ INSTANTIATE_TEST_SUITE_P( "'tacocat'.substring(4) == 'cat'", "'tacocat'.substring(7) == ''", "'tacocat'.substring(0, 4) == 'taco'", "'tacocat'.substring(4, 4) == ''", + "'tacocat'.substring(4, 7) == 'cat'", + "'tacocat'.substring(0, 7) == 'tacocat'", "'ta©o©αT'.substring(2, 6) == '©o©α'", + "'ta©o©αT'.substring(4, 7) == '©αT'", "'ta©o©αT'.substring(7, 7) == ''", // Tests for reverse()