From c2ae232042a0c37fc9bc2674daa73648f48ae86f Mon Sep 17 00:00:00 2001 From: sahvx655-wq Date: Tue, 2 Jun 2026 11:58:15 +0530 Subject: [PATCH 1/2] fix code unit/code point mix-up in cord substring length --- common/values/string_value.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/values/string_value.cc b/common/values/string_value.cc index 98912d32c..c14756bb2 100644 --- a/common/values/string_value.cc +++ b/common/values/string_value.cc @@ -742,7 +742,7 @@ absl::StatusOr SubstringImpl(const absl::Cord& cord, uint64_t start, } if (size_code_points == end) { return cord.Subcord(start_code_units, - size_code_points - start_code_units); + size_code_units - start_code_units); } char32_t code_point; size_t code_units; From cba6300daa3b0578deaf83270229d1b018e79e2a Mon Sep 17 00:00:00 2001 From: sahvx655-wq Date: Tue, 23 Jun 2026 17:07:54 +0530 Subject: [PATCH 2/2] Add regression test for cord substring code unit length --- common/values/string_value_test.cc | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/common/values/string_value_test.cc b/common/values/string_value_test.cc index 201724905..380746e1e 100644 --- a/common/values/string_value_test.cc +++ b/common/values/string_value_test.cc @@ -412,6 +412,35 @@ TEST_F(StringValueTest, CharAt) { ".charAt(): is less than 0"))); } +TEST_F(StringValueTest, Substring) { + using ::cel::test::ErrorValueIs; + using ::cel::test::StringValueIs; + + // Each '€' is three bytes, so the cord built here is 18 bytes and is stored + // as a large (cord-backed) value. The substring length must be measured in + // code units, not code points, otherwise the cord overload truncates a + // multi-byte character or underflows the length. + StringValue unicode_cord = StringValue(absl::Cord("€€€€€€")); + StringValue unicode_view = StringValue("€€€€€€"); + + EXPECT_THAT(unicode_cord.Substring(0, 2), StringValueIs("€€")); + EXPECT_THAT(unicode_view.Substring(0, 2), StringValueIs("€€")); + EXPECT_THAT(unicode_cord.Substring(1, 2), StringValueIs("€")); + EXPECT_THAT(unicode_view.Substring(1, 2), StringValueIs("€")); + EXPECT_THAT(unicode_cord.Substring(2, 4), StringValueIs("€€")); + EXPECT_THAT(unicode_view.Substring(2, 4), StringValueIs("€€")); + EXPECT_THAT(unicode_cord.Substring(2), StringValueIs("€€€€")); + EXPECT_THAT(unicode_view.Substring(2), StringValueIs("€€€€")); + + EXPECT_THAT(unicode_cord.Substring(0, 7), + ErrorValueIs(absl::InvalidArgumentError( + ".substring(, ): or is " + "greater than .size()"))); + EXPECT_THAT(unicode_cord.Substring(-1), + ErrorValueIs(absl::InvalidArgumentError( + ".substring(): is less than 0"))); +} + TEST_F(StringValueTest, Join) { using ::cel::runtime_internal::CreateNoMatchingOverloadError; using ::cel::test::ErrorValueIs;