diff --git a/common/BUILD b/common/BUILD index 8983d3b4c..88c7e0b62 100644 --- a/common/BUILD +++ b/common/BUILD @@ -960,3 +960,49 @@ cc_test( "@com_google_protobuf//:protobuf", ], ) + +cc_library( + name = "arena_bytes", + hdrs = ["arena_bytes.h"], + deps = [ + "@com_google_absl//absl/base", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_test( + name = "arena_bytes_test", + srcs = ["arena_bytes_test.cc"], + deps = [ + ":arena_bytes", + "//internal:testing", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/hash:hash_testing", + "@com_google_absl//absl/strings:string_view", + ], +) + +cc_library( + name = "arena_bytes_pool", + hdrs = ["arena_bytes_pool.h"], + deps = [ + ":arena_bytes", + "//internal:string_pool", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/base:nullability", + "@com_google_absl//absl/strings:string_view", + "@com_google_protobuf//:protobuf", + ], +) + +cc_test( + name = "arena_bytes_pool_test", + srcs = ["arena_bytes_pool_test.cc"], + deps = [ + ":arena_bytes_pool", + "//internal:testing", + "@com_google_protobuf//:protobuf", + ], +) diff --git a/common/arena_bytes.h b/common/arena_bytes.h new file mode 100644 index 000000000..baf5ab4e2 --- /dev/null +++ b/common/arena_bytes.h @@ -0,0 +1,252 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_H_ + +#include +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/base/casts.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/strings/string_view.h" + +namespace cel { + +class ArenaBytesPool; + +// Bug in current Abseil LTS. Fixed in +// https://github.com/abseil/abseil-cpp/commit/fd7713cb9a97c49096211ff40de280b6cebbb21c +// which is not yet in an LTS. +#if defined(__clang__) && (!defined(__clang_major__) || __clang_major__ >= 13) +#define CEL_ATTRIBUTE_ARENA_BYTES_VIEW ABSL_ATTRIBUTE_VIEW +#else +#define CEL_ATTRIBUTE_ARENA_BYTES_VIEW +#endif + +// `ArenaBytes` is a read-only string which is either backed by a static string +// literal or owned by the `ArenaBytesPool` that created it. It is compatible +// with `absl::string_view` and is implicitly convertible to it. +class CEL_ATTRIBUTE_ARENA_BYTES_VIEW ArenaBytes final { + private: + template + static constexpr bool IsStringLiteral(const char (&string)[N]) { + static_assert(N > 0); + for (size_t i = 0; i < N - 1; ++i) { + if (string[i] == '\0') { + return false; + } + } + return string[N - 1] == '\0'; + } + + public: + using traits_type = std::char_traits; + using value_type = char; + using pointer = char*; + using const_pointer = const char*; + using reference = char&; + using const_reference = const char&; + using const_iterator = const_pointer; + using iterator = const_iterator; + using const_reverse_iterator = std::reverse_iterator; + using reverse_iterator = const_reverse_iterator; + using size_type = size_t; + using difference_type = ptrdiff_t; + + using absl_internal_is_view = std::true_type; + + template + static constexpr ArenaBytes Static(const char (&bytes)[N]) +#if ABSL_HAVE_ATTRIBUTE(enable_if) + __attribute__((enable_if(IsStringLiteral(bytes), + "chosen when 'bytes' is a string literal"))) +#endif + { + static_assert(N > 0); + static_assert(N - 1 <= absl::string_view().max_size()); + return ArenaBytes(bytes); + } + + ArenaBytes() = default; + ArenaBytes(const ArenaBytes&) = default; + ArenaBytes& operator=(const ArenaBytes&) = default; + + constexpr size_type size() const { return size_; } + + constexpr bool empty() const { return size() == 0; } + + constexpr size_type max_size() const { + return absl::string_view().max_size(); + } + + constexpr absl::Nonnull data() const { return data_; } + + constexpr const_reference front() const { + ABSL_ASSERT(!empty()); + return data()[0]; + } + + constexpr const_reference back() const { + ABSL_ASSERT(!empty()); + return data()[size() - 1]; + } + + constexpr const_reference operator[](size_type index) const { + ABSL_ASSERT(index < size()); + return data()[index]; + } + + constexpr void remove_prefix(size_type n) { + ABSL_ASSERT(n <= size()); + data_ += n; + size_ -= n; + } + + constexpr void remove_suffix(size_type n) { + ABSL_ASSERT(n <= size()); + size_ -= n; + } + + constexpr const_iterator begin() const { return data(); } + + constexpr const_iterator cbegin() const { return begin(); } + + constexpr const_iterator end() const { return data() + size(); } + + constexpr const_iterator cend() const { return end(); } + + constexpr const_reverse_iterator rbegin() const { + return std::make_reverse_iterator(end()); + } + + constexpr const_reverse_iterator crbegin() const { return rbegin(); } + + constexpr const_reverse_iterator rend() const { + return std::make_reverse_iterator(begin()); + } + + constexpr const_reverse_iterator crend() const { return rend(); } + + // NOLINTNEXTLINE(google-explicit-constructor) + constexpr operator absl::string_view() const { + return absl::string_view(data(), size()); + } + + private: + friend class ArenaBytesPool; + + constexpr explicit ArenaBytes(absl::string_view value) + : data_(value.data()), size_(static_cast(value.size())) { + ABSL_ASSERT(value.data() != nullptr); + ABSL_ASSERT(value.size() <= max_size()); + } + + absl::Nonnull data_ = ""; + size_type size_ = 0; +}; + +constexpr bool operator==(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) == + absl::implicit_cast(rhs); +} + +constexpr bool operator==(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) == rhs; +} + +constexpr bool operator==(absl::string_view lhs, ArenaBytes rhs) { + return lhs == absl::implicit_cast(rhs); +} + +constexpr bool operator!=(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) != + absl::implicit_cast(rhs); +} + +constexpr bool operator!=(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) != rhs; +} + +constexpr bool operator!=(absl::string_view lhs, ArenaBytes rhs) { + return lhs != absl::implicit_cast(rhs); +} + +constexpr bool operator<(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) < + absl::implicit_cast(rhs); +} + +constexpr bool operator<(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) < rhs; +} + +constexpr bool operator<(absl::string_view lhs, ArenaBytes rhs) { + return lhs < absl::implicit_cast(rhs); +} + +constexpr bool operator<=(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) <= + absl::implicit_cast(rhs); +} + +constexpr bool operator<=(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) <= rhs; +} + +constexpr bool operator<=(absl::string_view lhs, ArenaBytes rhs) { + return lhs <= absl::implicit_cast(rhs); +} + +constexpr bool operator>(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) > + absl::implicit_cast(rhs); +} + +constexpr bool operator>(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) > rhs; +} + +constexpr bool operator>(absl::string_view lhs, ArenaBytes rhs) { + return lhs > absl::implicit_cast(rhs); +} + +constexpr bool operator>=(ArenaBytes lhs, ArenaBytes rhs) { + return absl::implicit_cast(lhs) >= + absl::implicit_cast(rhs); +} + +constexpr bool operator>=(ArenaBytes lhs, absl::string_view rhs) { + return absl::implicit_cast(lhs) >= rhs; +} + +constexpr bool operator>=(absl::string_view lhs, ArenaBytes rhs) { + return lhs >= absl::implicit_cast(rhs); +} + +template +H AbslHashValue(H state, ArenaBytes arena_string) { + return H::combine(std::move(state), + absl::implicit_cast(arena_string)); +} + +#undef CEL_ATTRIBUTE_ARENA_BYTES_VIEW + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_H_ diff --git a/common/arena_bytes_pool.h b/common/arena_bytes_pool.h new file mode 100644 index 000000000..fbea50c05 --- /dev/null +++ b/common/arena_bytes_pool.h @@ -0,0 +1,64 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_POOL_H_ +#define THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_POOL_H_ + +#include + +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" +#include "absl/strings/string_view.h" +#include "common/arena_bytes.h" +#include "internal/string_pool.h" +#include "google/protobuf/arena.h" + +namespace cel { + +class ArenaBytesPool; + +absl::Nonnull> NewArenaBytesPool( + absl::Nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND); + +class ArenaBytesPool final { + public: + ArenaBytesPool(const ArenaBytesPool&) = delete; + ArenaBytesPool(ArenaBytesPool&&) = delete; + ArenaBytesPool& operator=(const ArenaBytesPool&) = delete; + ArenaBytesPool& operator=(ArenaBytesPool&&) = delete; + + ArenaBytes InternBytes(absl::string_view bytes) { + return ArenaBytes(strings_.InternString(bytes)); + } + + ArenaBytes InternBytes(ArenaBytes) = delete; + + private: + friend absl::Nonnull> NewArenaBytesPool( + absl::Nonnull); + + explicit ArenaBytesPool(absl::Nonnull arena) + : strings_(arena) {} + + internal::StringPool strings_; +}; + +inline absl::Nonnull> NewArenaBytesPool( + absl::Nonnull arena ABSL_ATTRIBUTE_LIFETIME_BOUND) { + return std::unique_ptr(new ArenaBytesPool(arena)); +} + +} // namespace cel + +#endif // THIRD_PARTY_CEL_CPP_COMMON_ARENA_BYTES_POOL_H_ diff --git a/common/arena_bytes_pool_test.cc b/common/arena_bytes_pool_test.cc new file mode 100644 index 000000000..85bb31d1a --- /dev/null +++ b/common/arena_bytes_pool_test.cc @@ -0,0 +1,32 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "common/arena_bytes_pool.h" + +#include "internal/testing.h" +#include "google/protobuf/arena.h" + +namespace cel { +namespace { + +TEST(ArenaBytesPool, InternBytes) { + google::protobuf::Arena arena; + auto bytes_pool = NewArenaBytesPool(&arena); + auto expected = bytes_pool->InternBytes("Hello World!"); + auto got = bytes_pool->InternBytes("Hello World!"); + EXPECT_EQ(expected.data(), got.data()); +} + +} // namespace +} // namespace cel diff --git a/common/arena_bytes_test.cc b/common/arena_bytes_test.cc new file mode 100644 index 000000000..72be68a1d --- /dev/null +++ b/common/arena_bytes_test.cc @@ -0,0 +1,126 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "common/arena_bytes.h" + +#include "absl/hash/hash.h" +#include "absl/hash/hash_testing.h" +#include "absl/strings/string_view.h" +#include "internal/testing.h" + +namespace cel { +namespace { + +using ::testing::Eq; +using ::testing::Ge; +using ::testing::Gt; +using ::testing::IsEmpty; +using ::testing::Le; +using ::testing::Lt; +using ::testing::Ne; +using ::testing::SizeIs; + +TEST(ArenaBytes, Default) { + ArenaBytes string; + EXPECT_THAT(string, IsEmpty()); + EXPECT_THAT(string, SizeIs(0)); + EXPECT_THAT(string, Eq(ArenaBytes())); +} + +TEST(ArenaBytes, Iterator) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + auto it = string.cbegin(); + EXPECT_THAT(*it++, Eq('H')); + EXPECT_THAT(*it++, Eq('e')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq(' ')); + EXPECT_THAT(*it++, Eq('W')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('r')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('d')); + EXPECT_THAT(*it++, Eq('!')); + EXPECT_THAT(it, Eq(string.cend())); +} + +TEST(ArenaBytes, ReverseIterator) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + auto it = string.crbegin(); + EXPECT_THAT(*it++, Eq('!')); + EXPECT_THAT(*it++, Eq('d')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('r')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('W')); + EXPECT_THAT(*it++, Eq(' ')); + EXPECT_THAT(*it++, Eq('o')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('l')); + EXPECT_THAT(*it++, Eq('e')); + EXPECT_THAT(*it++, Eq('H')); + EXPECT_THAT(it, Eq(string.crend())); +} + +TEST(ArenaBytes, RemovePrefix) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + string.remove_prefix(6); + EXPECT_EQ(string, "World!"); +} + +TEST(ArenaBytes, RemoveSuffix) { + ArenaBytes string = ArenaBytes::Static("Hello World!"); + string.remove_suffix(7); + EXPECT_EQ(string, "Hello"); +} + +TEST(ArenaBytes, Equal) { + EXPECT_THAT(ArenaBytes::Static("1"), Eq(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, NotEqual) { + EXPECT_THAT(ArenaBytes::Static("1"), Ne(ArenaBytes::Static("2"))); +} + +TEST(ArenaBytes, Less) { + EXPECT_THAT(ArenaBytes::Static("1"), Lt(ArenaBytes::Static("2"))); +} + +TEST(ArenaBytes, LessEqual) { + EXPECT_THAT(ArenaBytes::Static("1"), Le(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, Greater) { + EXPECT_THAT(ArenaBytes::Static("2"), Gt(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, GreaterEqual) { + EXPECT_THAT(ArenaBytes::Static("1"), Ge(ArenaBytes::Static("1"))); +} + +TEST(ArenaBytes, ImplementsAbslHashCorrectly) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( + {ArenaBytes::Static(""), ArenaBytes::Static("Hello World!"), + ArenaBytes::Static("How much wood could a woodchuck chuck if a " + "woodchuck could chuck wood?")})); +} + +TEST(ArenaBytes, Hash) { + EXPECT_EQ(absl::HashOf(ArenaBytes::Static("Hello World!")), + absl::HashOf(absl::string_view("Hello World!"))); +} + +} // namespace +} // namespace cel