Reorganize upb file structure This change moves almost everything in the `upb/` directory up one level, so that for example `upb/upb/generated_code_support.h` becomes just `upb/generated_code_support.h`. The only exceptions I made to this were that I left `upb/cmake` and `upb/BUILD` where they are, mostly because that avoids conflict with other files and the current locations seem reasonable for now. The `python/` directory is a little bit of a challenge because we had to merge the existing directory there with `upb/python/`. I made `upb/python/BUILD` into the BUILD file for the merged directory, and it effectively loads the contents of the other BUILD file via `python/build_targets.bzl`, but I plan to clean this up soon. PiperOrigin-RevId: 568651768
diff --git a/protos/BUILD b/protos/BUILD new file mode 100644 index 0000000..648d938 --- /dev/null +++ b/protos/BUILD
@@ -0,0 +1,170 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +load( + "//bazel:build_defs.bzl", + "UPB_DEFAULT_CPPOPTS", +) +load( + "//protos/bazel:upb_cc_proto_library.bzl", + "upb_cc_proto_library_copts", +) + +# begin:google_only +# package(default_applicable_licenses = ["//upb:license"]) +# end:google_only + +licenses(["notice"]) + +cc_library( + name = "repeated_field", + hdrs = [ + "repeated_field.h", + "repeated_field_iterator.h", + ], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + ":protos", + ":protos_traits", + "//upb:base", + "//upb:collections", + "//upb:mem", + "//upb:message_copy", + "//upb:message_types", + "//upb:port", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/strings", + ], +) + +cc_library( + name = "protos", + srcs = [ + "protos.cc", + ], + hdrs = [ + "protos.h", + ], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + ":protos_extension_lock", + "//upb:base", + "//upb:mem", + "//upb:message_accessors_internal", + "//upb:message_copy", + "//upb:message_internal", + "//upb:message_promote", + "//upb:message_types", + "//upb:mini_table", + "//upb:wire", + "//upb:wire_types", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:str_format", + ], +) + +# Internally used type traits. +cc_library( + name = "protos_traits", + hdrs = [ + "protos_traits.h", + ], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:private"], +) + +cc_library( + name = "protos_internal", + hdrs = ["protos_internal.h"], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + ":protos", + "//upb:mem", + "//upb:message", + "//upb:mini_table", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:str_format", + ], +) + +cc_library( + name = "protos_extension_lock", + srcs = ["protos_extension_lock.cc"], + hdrs = ["protos_extension_lock.h"], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + "//upb:message", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/synchronization", + ], +) + +# Common support code for C++ generated code. +cc_library( + name = "generated_protos_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me", + hdrs = [ + "protos_internal.h", + ], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + ":protos", + ":protos_internal", + ":repeated_field", + "//upb:mem", + "//upb:message", + ], +) + +cc_test( + name = "protos_internal_test", + srcs = ["protos_internal_test.cc"], + copts = UPB_DEFAULT_CPPOPTS, + deps = [ + ":protos_internal", + "@com_google_googletest//:gtest_main", + "//upb:mem", + "//protos_generator/tests:test_model_upb_cc_proto", + "//protos_generator/tests:test_model_upb_proto", + ], +) + +upb_cc_proto_library_copts( + name = "upb_cc_proto_library_copts__for_generated_code_only_do_not_use", + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], +) + +cc_test( + name = "repeated_field_iterator_test", + srcs = ["repeated_field_iterator_test.cc"], + deps = [ + ":repeated_field", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "protos_extension_lock_test", + srcs = ["protos_extension_lock_test.cc"], + deps = [ + "@com_google_googletest//:gtest_main", + "//upb:mem", + "//protos", + "//protos:protos_extension_lock", + "//protos_generator/tests:test_model_upb_cc_proto", + "@com_google_absl//absl/hash", + "@com_google_absl//absl/log:absl_check", + ], +)
diff --git a/protos/README.md b/protos/README.md new file mode 100644 index 0000000..dc09201 --- /dev/null +++ b/protos/README.md
@@ -0,0 +1,7 @@ +`protos` C++ API +=============== + +`protos` is a C++ protobuf implementation backed by μpb, introduced in 2022. +The API is currently experimental and may undergo incompatible changes without +notice. The supported C++ implementation is +[here](https://github.com/protocolbuffers/protobuf/tree/main/src).
diff --git a/protos/bazel/BUILD b/protos/bazel/BUILD new file mode 100644 index 0000000..ea91541 --- /dev/null +++ b/protos/bazel/BUILD
@@ -0,0 +1,25 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +# begin:google_only +# package(default_applicable_licenses = ["//upb:license"]) +# end:google_only + +licenses(["notice"]) + +bzl_library( + name = "upb_cc_proto_library_bzl", + srcs = ["upb_cc_proto_library.bzl"], + visibility = ["//visibility:public"], + deps = [ + "@bazel_skylib//lib:paths", + "//bazel:upb_proto_library_bzl", + "@bazel_tools//tools/cpp:toolchain_utils.bzl", + ], +)
diff --git a/protos/bazel/upb_cc_proto_library.bzl b/protos/bazel/upb_cc_proto_library.bzl new file mode 100644 index 0000000..b59ae65 --- /dev/null +++ b/protos/bazel/upb_cc_proto_library.bzl
@@ -0,0 +1,289 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file or at +# https://developers.google.com/open-source/licenses/bsd + +"""Public rules for using upb protos: + - upb_cc_proto_library() +""" + +load("@bazel_skylib//lib:paths.bzl", "paths") +load("//bazel:upb_proto_library.bzl", "GeneratedSrcsInfo", "UpbWrappedCcInfo", "upb_proto_library_aspect") + +# begin:google_only +# load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain") +# +# end:google_only +# begin:github_only +# Compatibility code for Bazel 4.x. Remove this when we drop support for Bazel 4.x. +load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") + +def use_cpp_toolchain(): + return ["@bazel_tools//tools/cpp:toolchain_type"] +# end:github_only + +# Generic support code ######################################################### + +# begin:github_only +_is_google3 = False +# end:github_only + +# begin:google_only +# _is_google3 = True +# end:google_only + +def _get_real_short_path(file): + # For some reason, files from other archives have short paths that look like: + # ../com_google_protobuf/google/protobuf/descriptor.proto + short_path = file.short_path + if short_path.startswith("../"): + second_slash = short_path.index("/", 3) + short_path = short_path[second_slash + 1:] + + # Sometimes it has another few prefixes like: + # _virtual_imports/any_proto/google/protobuf/any.proto + # benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto + # We want just google/protobuf/any.proto. + virtual_imports = "_virtual_imports/" + if virtual_imports in short_path: + short_path = short_path.split(virtual_imports)[1].split("/", 1)[1] + return short_path + +def _get_real_root(file): + real_short_path = _get_real_short_path(file) + return file.path[:-len(real_short_path) - 1] + +def _generate_output_file(ctx, src, extension): + real_short_path = _get_real_short_path(src) + real_short_path = paths.relativize(real_short_path, ctx.label.package) + output_filename = paths.replace_extension(real_short_path, extension) + ret = ctx.actions.declare_file(output_filename) + return ret + +def _filter_none(elems): + out = [] + for elem in elems: + if elem: + out.append(elem) + return out + +def _cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos): + """Like cc_library(), but callable from rules. + + Args: + ctx: Rule context. + name: Unique name used to generate output files. + hdrs: Public headers that can be #included from other rules. + srcs: C/C++ source files. + copts: Additional options for cc compilation. + dep_ccinfos: CcInfo providers of dependencies we should build/link against. + + Returns: + CcInfo provider for this compilation. + """ + + compilation_contexts = [info.compilation_context for info in dep_ccinfos] + linking_contexts = [info.linking_context for info in dep_ccinfos] + toolchain = find_cpp_toolchain(ctx) + feature_configuration = cc_common.configure_features( + ctx = ctx, + cc_toolchain = toolchain, + requested_features = ctx.features, + unsupported_features = ctx.disabled_features, + ) + + (compilation_context, compilation_outputs) = cc_common.compile( + actions = ctx.actions, + feature_configuration = feature_configuration, + cc_toolchain = toolchain, + name = name, + srcs = srcs, + public_hdrs = hdrs, + user_compile_flags = copts, + compilation_contexts = compilation_contexts, + ) + + # buildifier: disable=unused-variable + (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs( + actions = ctx.actions, + name = name, + feature_configuration = feature_configuration, + cc_toolchain = toolchain, + compilation_outputs = compilation_outputs, + linking_contexts = linking_contexts, + ) + + return CcInfo( + compilation_context = compilation_context, + linking_context = linking_context, + ) + +# Dummy rule to expose select() copts to aspects ############################## + +UpbCcProtoLibraryCoptsInfo = provider( + "Provides copts for upb cc proto targets", + fields = { + "copts": "copts for upb_cc_proto_library()", + }, +) + +def upb_cc_proto_library_copts_impl(ctx): + return UpbCcProtoLibraryCoptsInfo(copts = ctx.attr.copts) + +upb_cc_proto_library_copts = rule( + implementation = upb_cc_proto_library_copts_impl, + attrs = {"copts": attr.string_list(default = [])}, +) + +_UpbCcWrappedCcInfo = provider("Provider for cc_info for protos", fields = ["cc_info"]) +_WrappedCcGeneratedSrcsInfo = provider("Provider for generated sources", fields = ["srcs"]) + +def _compile_upb_cc_protos(ctx, generator, proto_info, proto_sources): + if len(proto_sources) == 0: + return GeneratedSrcsInfo(srcs = [], hdrs = []) + + tool = getattr(ctx.executable, "_gen_" + generator) + srcs = [_generate_output_file(ctx, name, ".upb.proto.cc") for name in proto_sources] + hdrs = [_generate_output_file(ctx, name, ".upb.proto.h") for name in proto_sources] + hdrs += [_generate_output_file(ctx, name, ".upb.fwd.h") for name in proto_sources] + transitive_sets = proto_info.transitive_descriptor_sets.to_list() + + args = ctx.actions.args() + args.use_param_file(param_file_arg = "@%s") + args.set_param_file_format("multiline") + + args.add("--" + generator + "_out=" + _get_real_root(srcs[0])) + args.add("--plugin=protoc-gen-" + generator + "=" + tool.path) + args.add("--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets])) + args.add_all(proto_sources, map_each = _get_real_short_path) + + ctx.actions.run( + inputs = depset( + direct = [proto_info.direct_descriptor_set], + transitive = [proto_info.transitive_descriptor_sets], + ), + tools = [tool], + outputs = srcs + hdrs, + executable = ctx.executable._protoc, + arguments = [args], + progress_message = "Generating upb cc protos for :" + ctx.label.name, + ) + return GeneratedSrcsInfo(srcs = srcs, hdrs = hdrs) + +def _upb_cc_proto_rule_impl(ctx): + if len(ctx.attr.deps) != 1: + fail("only one deps dependency allowed.") + dep = ctx.attr.deps[0] + + if _WrappedCcGeneratedSrcsInfo in dep: + srcs = dep[_WrappedCcGeneratedSrcsInfo].srcs + else: + fail("proto_library rule must generate _WrappedCcGeneratedSrcsInfo (aspect should have " + + "handled this).") + + if _UpbCcWrappedCcInfo in dep: + cc_info = dep[_UpbCcWrappedCcInfo].cc_info + elif UpbWrappedCcInfo in dep: + cc_info = dep[UpbWrappedCcInfo].cc_info + else: + fail("proto_library rule must generate UpbWrappedCcInfo or " + + "_UpbCcWrappedCcInfo (aspect should have handled this).") + + lib = cc_info.linking_context.linker_inputs.to_list()[0].libraries[0] + files = _filter_none([ + lib.static_library, + lib.pic_static_library, + lib.dynamic_library, + ]) + return [ + DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs)), + srcs, + cc_info, + ] + +def _upb_cc_proto_aspect_impl(target, ctx, generator, cc_provider, file_provider): + proto_info = target[ProtoInfo] + files = _compile_upb_cc_protos(ctx, generator, proto_info, proto_info.direct_sources) + deps = ctx.rule.attr.deps + getattr(ctx.attr, "_" + generator) + dep_ccinfos = [dep[CcInfo] for dep in deps if CcInfo in dep] + dep_ccinfos += [dep[UpbWrappedCcInfo].cc_info for dep in deps if UpbWrappedCcInfo in dep] + dep_ccinfos += [dep[_UpbCcWrappedCcInfo].cc_info for dep in deps if _UpbCcWrappedCcInfo in dep] + if UpbWrappedCcInfo not in target: + fail("Target should have UpbWrappedCcInfo provider") + dep_ccinfos.append(target[UpbWrappedCcInfo].cc_info) + cc_info = _cc_library_func( + ctx = ctx, + name = ctx.rule.attr.name + "." + generator, + hdrs = files.hdrs, + srcs = files.srcs, + copts = ctx.attr._ccopts[UpbCcProtoLibraryCoptsInfo].copts, + dep_ccinfos = dep_ccinfos, + ) + return [cc_provider(cc_info = cc_info), file_provider(srcs = files)] + +def _upb_cc_proto_library_aspect_impl(target, ctx): + return _upb_cc_proto_aspect_impl(target, ctx, "upbprotos", _UpbCcWrappedCcInfo, _WrappedCcGeneratedSrcsInfo) + +_upb_cc_proto_library_aspect = aspect( + attrs = { + "_ccopts": attr.label( + default = "//protos:upb_cc_proto_library_copts__for_generated_code_only_do_not_use", + ), + "_gen_upbprotos": attr.label( + executable = True, + cfg = "exec", + default = "//protos_generator:protoc-gen-upb-protos", + ), + "_protoc": attr.label( + executable = True, + cfg = "exec", + default = "//:protoc", + ), + "_cc_toolchain": attr.label( + default = "@bazel_tools//tools/cpp:current_cc_toolchain", + ), + "_upbprotos": attr.label_list( + default = [ + # TODO: Add dependencies for cc runtime (absl/string etc..) + "//upb:generated_cpp_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me", + "//protos:generated_protos_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/status:statusor", + "//protos", + "//protos:repeated_field", + ], + ), + }, + implementation = _upb_cc_proto_library_aspect_impl, + provides = [ + _UpbCcWrappedCcInfo, + _WrappedCcGeneratedSrcsInfo, + ], + required_aspect_providers = [ + UpbWrappedCcInfo, + ], + attr_aspects = ["deps"], + fragments = ["cpp"], + toolchains = use_cpp_toolchain(), + incompatible_use_toolchain_transition = True, +) + +upb_cc_proto_library = rule( + output_to_genfiles = True, + implementation = _upb_cc_proto_rule_impl, + attrs = { + "deps": attr.label_list( + aspects = [ + upb_proto_library_aspect, + _upb_cc_proto_library_aspect, + ], + allow_rules = ["proto_library"], + providers = [ProtoInfo], + ), + "_ccopts": attr.label( + default = "//protos:upb_cc_proto_library_copts__for_generated_code_only_do_not_use", + ), + }, +)
diff --git a/protos/protos.cc b/protos/protos.cc new file mode 100644 index 0000000..29b9fbe --- /dev/null +++ b/protos/protos.cc
@@ -0,0 +1,221 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "protos/protos.h" + +#include <atomic> +#include <cstddef> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "protos/protos_extension_lock.h" +#include "upb/mem/arena.h" +#include "upb/message/copy.h" +#include "upb/message/internal/extension.h" +#include "upb/message/promote.h" +#include "upb/message/types.h" +#include "upb/mini_table/extension.h" +#include "upb/mini_table/extension_registry.h" +#include "upb/mini_table/message.h" +#include "upb/wire/decode.h" +#include "upb/wire/encode.h" + +namespace protos { + +// begin:google_only +// absl::Status MessageAllocationError(SourceLocation loc) { +// return absl::Status(absl::StatusCode::kInternal, +// "Upb message allocation error", loc); +// } +// +// absl::Status ExtensionNotFoundError(int extension_number, SourceLocation loc) { +// return absl::Status( +// absl::StatusCode::kInternal, +// absl::StrFormat("Extension %d not found", extension_number), loc); +// } +// +// absl::Status MessageEncodeError(upb_EncodeStatus status, SourceLocation loc) { +// return absl::Status(absl::StatusCode::kInternal, +// absl::StrFormat("Upb message encoding error %d", status), +// loc +// +// ); +// } +// +// absl::Status MessageDecodeError(upb_DecodeStatus status, SourceLocation loc +// +// ) { +// return absl::Status(absl::StatusCode::kInternal, +// absl::StrFormat("Upb message parse error %d", status), loc +// +// ); +// } +// end:google_only + +// begin:github_only +absl::Status MessageAllocationError(SourceLocation loc) { + return absl::Status(absl::StatusCode::kUnknown, + "Upb message allocation error"); +} + +absl::Status ExtensionNotFoundError(int ext_number, SourceLocation loc) { + return absl::Status(absl::StatusCode::kUnknown, + absl::StrFormat("Extension %d not found", ext_number)); +} + +absl::Status MessageEncodeError(upb_EncodeStatus s, SourceLocation loc) { + return absl::Status(absl::StatusCode::kUnknown, "Encoding error"); +} + +absl::Status MessageDecodeError(upb_DecodeStatus status, SourceLocation loc + +) { + return absl::Status(absl::StatusCode::kUnknown, "Upb message parse error"); +} +// end:github_only + +namespace internal { + +upb_ExtensionRegistry* GetUpbExtensions( + const ExtensionRegistry& extension_registry) { + return extension_registry.registry_; +} + +/** + * MessageLock(msg) acquires lock on msg when constructed and releases it when + * destroyed. + */ +class MessageLock { + public: + explicit MessageLock(const upb_Message* msg) : msg_(msg) { + UpbExtensionLocker locker = + upb_extension_locker_global.load(std::memory_order_acquire); + unlocker_ = (locker != nullptr) ? locker(msg) : nullptr; + } + MessageLock(const MessageLock&) = delete; + void operator=(const MessageLock&) = delete; + ~MessageLock() { + if (unlocker_ != nullptr) { + unlocker_(msg_); + } + } + + private: + const upb_Message* msg_; + UpbExtensionUnlocker unlocker_; +}; + +bool HasExtensionOrUnknown(const upb_Message* msg, + const upb_MiniTableExtension* eid) { + MessageLock msg_lock(msg); + return _upb_Message_Getext(msg, eid) != nullptr || + upb_MiniTable_FindUnknown(msg, eid->field.number, 0).status == + kUpb_FindUnknown_Ok; +} + +const upb_Message_Extension* GetOrPromoteExtension( + upb_Message* msg, const upb_MiniTableExtension* eid, upb_Arena* arena) { + MessageLock msg_lock(msg); + const upb_Message_Extension* ext = _upb_Message_Getext(msg, eid); + if (ext == nullptr) { + upb_GetExtension_Status ext_status = upb_MiniTable_GetOrPromoteExtension( + (upb_Message*)msg, eid, 0, arena, &ext); + if (ext_status != kUpb_GetExtension_Ok) { + ext = nullptr; + } + } + return ext; +} + +absl::StatusOr<absl::string_view> Serialize(const upb_Message* message, + const upb_MiniTable* mini_table, + upb_Arena* arena, int options) { + MessageLock msg_lock(message); + size_t len; + char* ptr; + upb_EncodeStatus status = + upb_Encode(message, mini_table, options, arena, &ptr, &len); + if (status == kUpb_EncodeStatus_Ok) { + return absl::string_view(ptr, len); + } + return MessageEncodeError(status); +} + +void DeepCopy(upb_Message* target, const upb_Message* source, + const upb_MiniTable* mini_table, upb_Arena* arena) { + MessageLock msg_lock(source); + upb_Message_DeepCopy(target, source, mini_table, arena); +} + +upb_Message* DeepClone(const upb_Message* source, + const upb_MiniTable* mini_table, upb_Arena* arena) { + MessageLock msg_lock(source); + return upb_Message_DeepClone(source, mini_table, arena); +} + +absl::Status MoveExtension(upb_Message* message, upb_Arena* message_arena, + const upb_MiniTableExtension* ext, + upb_Message* extension, upb_Arena* extension_arena) { + upb_Message_Extension* msg_ext = + _upb_Message_GetOrCreateExtension(message, ext, message_arena); + if (!msg_ext) { + return MessageAllocationError(); + } + if (message_arena != extension_arena) { + // Try fuse, if fusing is not allowed or fails, create copy of extension. + if (!upb_Arena_Fuse(message_arena, extension_arena)) { + msg_ext->data.ptr = + DeepClone(extension, msg_ext->ext->sub.submsg, message_arena); + return absl::OkStatus(); + } + } + msg_ext->data.ptr = extension; + return absl::OkStatus(); +} + +absl::Status SetExtension(upb_Message* message, upb_Arena* message_arena, + const upb_MiniTableExtension* ext, + const upb_Message* extension) { + upb_Message_Extension* msg_ext = + _upb_Message_GetOrCreateExtension(message, ext, message_arena); + if (!msg_ext) { + return MessageAllocationError(); + } + // Clone extension into target message arena. + msg_ext->data.ptr = + DeepClone(extension, msg_ext->ext->sub.submsg, message_arena); + return absl::OkStatus(); +} + +} // namespace internal + +} // namespace protos
diff --git a/protos/protos.h b/protos/protos.h new file mode 100644 index 0000000..195d793 --- /dev/null +++ b/protos/protos.h
@@ -0,0 +1,551 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef UPB_PROTOS_PROTOS_H_ +#define UPB_PROTOS_PROTOS_H_ + +#include <type_traits> +#include <vector> + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "upb/base/status.hpp" +#include "upb/mem/arena.hpp" +#include "upb/message/copy.h" +#include "upb/message/internal/accessors.h" +#include "upb/message/internal/extension.h" +#include "upb/wire/decode.h" +#include "upb/wire/encode.h" + +namespace protos { + +using Arena = ::upb::Arena; +class ExtensionRegistry; + +template <typename T> +using Proxy = std::conditional_t<std::is_const<T>::value, + typename std::remove_const_t<T>::CProxy, + typename T::Proxy>; + +// Provides convenient access to Proxy and CProxy message types. +// +// Using rebinding and handling of const, Ptr<Message> and Ptr<const Message> +// allows copying const with T* const and avoids using non-copyable Proxy types +// directly. +template <typename T> +class Ptr final { + public: + Ptr() = delete; + + // Implicit conversions + Ptr(T* m) : p_(m) {} // NOLINT + Ptr(const Proxy<T>* p) : p_(*p) {} // NOLINT + Ptr(Proxy<T> p) : p_(p) {} // NOLINT + Ptr(const Ptr& m) = default; + + Ptr& operator=(Ptr v) & { + Proxy<T>::Rebind(p_, v.p_); + return *this; + } + + Proxy<T> operator*() const { return p_; } + Proxy<T>* operator->() const { + return const_cast<Proxy<T>*>(std::addressof(p_)); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wclass-conversion" +#endif + template <typename U = T, std::enable_if_t<!std::is_const<U>::value, int> = 0> + operator Ptr<const T>() const { + Proxy<const T> p(p_); + return Ptr<const T>(&p); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + private: + Ptr(void* msg, upb_Arena* arena) : p_(msg, arena) {} // NOLINT + + friend class Ptr<const T>; + friend typename T::Access; + + Proxy<T> p_; +}; + +inline absl::string_view UpbStrToStringView(upb_StringView str) { + return absl::string_view(str.data, str.size); +} + +// TODO: update bzl and move to upb runtime / protos.cc. +inline upb_StringView UpbStrFromStringView(absl::string_view str, + upb_Arena* arena) { + const size_t str_size = str.size(); + char* buffer = static_cast<char*>(upb_Arena_Malloc(arena, str_size)); + memcpy(buffer, str.data(), str_size); + return upb_StringView_FromDataAndSize(buffer, str_size); +} + +template <typename T> +typename T::Proxy CreateMessage(::protos::Arena& arena) { + return typename T::Proxy(upb_Message_New(T::minitable(), arena.ptr()), + arena.ptr()); +} + +// begin:github_only +// This type exists to work around an absl type that has not yet been +// released. +struct SourceLocation { + static SourceLocation current() { return {}; } + absl::string_view file_name() { return "<unknown>"; } + int line() { return 0; } +}; +// end:github_only + +// begin:google_only +// using SourceLocation = absl::SourceLocation; +// end:google_only + +absl::Status MessageAllocationError( + SourceLocation loc = SourceLocation::current()); + +absl::Status ExtensionNotFoundError( + int extension_number, SourceLocation loc = SourceLocation::current()); + +absl::Status MessageDecodeError(upb_DecodeStatus status, + SourceLocation loc = SourceLocation::current()); + +absl::Status MessageEncodeError(upb_EncodeStatus status, + SourceLocation loc = SourceLocation::current()); + +namespace internal { +struct PrivateAccess { + template <typename T> + static auto* GetInternalMsg(T&& message) { + return message->msg(); + } + template <typename T> + static auto Proxy(void* p, upb_Arena* arena) { + return typename T::Proxy(p, arena); + } + template <typename T> + static auto CProxy(const void* p, upb_Arena* arena) { + return typename T::CProxy(p, arena); + } +}; + +template <typename T> +auto* GetInternalMsg(T&& message) { + return PrivateAccess::GetInternalMsg(std::forward<T>(message)); +} + +template <typename T> +T CreateMessage() { + return T(); +} + +template <typename T> +typename T::Proxy CreateMessageProxy(void* msg, upb_Arena* arena) { + return typename T::Proxy(msg, arena); +} + +template <typename T> +typename T::CProxy CreateMessage(const upb_Message* msg, upb_Arena* arena) { + return PrivateAccess::CProxy<T>(msg, arena); +} + +class ExtensionMiniTableProvider { + public: + constexpr explicit ExtensionMiniTableProvider( + const upb_MiniTableExtension* mini_table_ext) + : mini_table_ext_(mini_table_ext) {} + const upb_MiniTableExtension* mini_table_ext() const { + return mini_table_ext_; + } + + private: + const upb_MiniTableExtension* mini_table_ext_; +}; + +// ------------------------------------------------------------------- +// ExtensionIdentifier +// This is the type of actual extension objects. E.g. if you have: +// extend Foo { +// optional MyExtension bar = 1234; +// } +// then "bar" will be defined in C++ as: +// ExtensionIdentifier<Foo, MyExtension> bar(&namespace_bar_ext); +template <typename ExtendeeType, typename ExtensionType> +class ExtensionIdentifier : public ExtensionMiniTableProvider { + public: + using Extension = ExtensionType; + using Extendee = ExtendeeType; + + constexpr explicit ExtensionIdentifier( + const upb_MiniTableExtension* mini_table_ext) + : ExtensionMiniTableProvider(mini_table_ext) {} +}; + +template <typename T> +upb_Arena* GetArena(Ptr<T> message) { + return static_cast<upb_Arena*>(message->GetInternalArena()); +} + +template <typename T> +upb_Arena* GetArena(T* message) { + return static_cast<upb_Arena*>(message->GetInternalArena()); +} + +template <typename T> +const upb_MiniTable* GetMiniTable(const T*) { + return T::minitable(); +} + +template <typename T> +const upb_MiniTable* GetMiniTable(Ptr<T>) { + return T::minitable(); +} + +upb_ExtensionRegistry* GetUpbExtensions( + const ExtensionRegistry& extension_registry); + +absl::StatusOr<absl::string_view> Serialize(const upb_Message* message, + const upb_MiniTable* mini_table, + upb_Arena* arena, int options); + +bool HasExtensionOrUnknown(const upb_Message* msg, + const upb_MiniTableExtension* eid); + +const upb_Message_Extension* GetOrPromoteExtension( + upb_Message* msg, const upb_MiniTableExtension* eid, upb_Arena* arena); + +void DeepCopy(upb_Message* target, const upb_Message* source, + const upb_MiniTable* mini_table, upb_Arena* arena); + +upb_Message* DeepClone(const upb_Message* source, + const upb_MiniTable* mini_table, upb_Arena* arena); + +absl::Status MoveExtension(upb_Message* message, upb_Arena* message_arena, + const upb_MiniTableExtension* ext, + upb_Message* extension, upb_Arena* extension_arena); + +absl::Status SetExtension(upb_Message* message, upb_Arena* message_arena, + const upb_MiniTableExtension* ext, + const upb_Message* extension); + +} // namespace internal + +template <typename T> +void DeepCopy(Ptr<const T> source_message, Ptr<T> target_message) { + static_assert(!std::is_const_v<T>); + ::protos::internal::DeepCopy( + internal::GetInternalMsg(target_message), + internal::GetInternalMsg(source_message), T::minitable(), + static_cast<upb_Arena*>(target_message->GetInternalArena())); +} + +template <typename T> +typename T::Proxy CloneMessage(Ptr<T> message, upb_Arena* arena) { + return internal::PrivateAccess::Proxy<T>( + ::protos::internal::DeepClone(internal::GetInternalMsg(message), + T::minitable(), arena), + arena); +} + +template <typename T> +void DeepCopy(Ptr<const T> source_message, T* target_message) { + static_assert(!std::is_const_v<T>); + DeepCopy(source_message, protos::Ptr(target_message)); +} + +template <typename T> +void DeepCopy(const T* source_message, Ptr<T> target_message) { + static_assert(!std::is_const_v<T>); + DeepCopy(protos::Ptr(source_message), target_message); +} + +template <typename T> +void DeepCopy(const T* source_message, T* target_message) { + static_assert(!std::is_const_v<T>); + DeepCopy(protos::Ptr(source_message), protos::Ptr(target_message)); +} + +template <typename T> +void ClearMessage(Ptr<T> message) { + static_assert(!std::is_const_v<T>, ""); + upb_Message_Clear(internal::GetInternalMsg(message), T::minitable()); +} + +template <typename T> +void ClearMessage(T* message) { + ClearMessage(protos::Ptr(message)); +} + +class ExtensionRegistry { + public: + ExtensionRegistry( + const std::vector<const ::protos::internal::ExtensionMiniTableProvider*>& + extensions, + const upb::Arena& arena) + : registry_(upb_ExtensionRegistry_New(arena.ptr())) { + if (registry_) { + for (const auto& ext_provider : extensions) { + const auto* ext = ext_provider->mini_table_ext(); + bool success = upb_ExtensionRegistry_AddArray(registry_, &ext, 1); + if (!success) { + registry_ = nullptr; + break; + } + } + } + } + + private: + friend upb_ExtensionRegistry* ::protos::internal::GetUpbExtensions( + const ExtensionRegistry& extension_registry); + upb_ExtensionRegistry* registry_; +}; + +template <typename T> +using EnableIfProtosClass = std::enable_if_t< + std::is_base_of<typename T::Access, T>::value && + std::is_base_of<typename T::Access, typename T::ExtendableType>::value>; + +template <typename T> +using EnableIfMutableProto = std::enable_if_t<!std::is_const<T>::value>; + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +ABSL_MUST_USE_RESULT bool HasExtension( + Ptr<T> message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) { + return ::protos::internal::HasExtensionOrUnknown( + ::protos::internal::GetInternalMsg(message), id.mini_table_ext()); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +ABSL_MUST_USE_RESULT bool HasExtension( + const T* message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) { + return HasExtension(protos::Ptr(message), id); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>, typename = EnableIfMutableProto<T>> +void ClearExtension( + Ptr<T> message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) { + static_assert(!std::is_const_v<T>, ""); + _upb_Message_ClearExtensionField(internal::GetInternalMsg(message), + id.mini_table_ext()); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +void ClearExtension( + T* message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) { + ClearExtension(::protos::Ptr(message), id); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>, typename = EnableIfMutableProto<T>> +absl::Status SetExtension( + Ptr<T> message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id, + const Extension& value) { + static_assert(!std::is_const_v<T>); + auto* message_arena = static_cast<upb_Arena*>(message->GetInternalArena()); + return ::protos::internal::SetExtension(internal::GetInternalMsg(message), + message_arena, id.mini_table_ext(), + internal::GetInternalMsg(&value)); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>, typename = EnableIfMutableProto<T>> +absl::Status SetExtension( + Ptr<T> message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id, + Extension&& value) { + Extension ext = std::move(value); + static_assert(!std::is_const_v<T>); + auto* message_arena = static_cast<upb_Arena*>(message->GetInternalArena()); + auto* extension_arena = static_cast<upb_Arena*>(ext.GetInternalArena()); + return ::protos::internal::MoveExtension( + internal::GetInternalMsg(message), message_arena, id.mini_table_ext(), + internal::GetInternalMsg(&ext), extension_arena); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +absl::Status SetExtension( + T* message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id, + const Extension& value) { + return ::protos::SetExtension(::protos::Ptr(message), id, value); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +absl::Status SetExtension( + T* message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id, + Extension&& value) { + return ::protos::SetExtension(::protos::Ptr(message), id, + std::forward<Extension>(value)); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +absl::StatusOr<Ptr<const Extension>> GetExtension( + Ptr<T> message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) { + // TODO: Fix const correctness issues. + const upb_Message_Extension* ext = ::protos::internal::GetOrPromoteExtension( + const_cast<upb_Message*>(internal::GetInternalMsg(message)), + id.mini_table_ext(), ::protos::internal::GetArena(message)); + if (!ext) { + return ExtensionNotFoundError(id.mini_table_ext()->field.number); + } + return Ptr<const Extension>(::protos::internal::CreateMessage<Extension>( + ext->data.ptr, ::protos::internal::GetArena(message))); +} + +template <typename T, typename Extendee, typename Extension, + typename = EnableIfProtosClass<T>> +absl::StatusOr<Ptr<const Extension>> GetExtension( + const T* message, + const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) { + return GetExtension(protos::Ptr(message), id); +} + +template <typename T> +ABSL_MUST_USE_RESULT bool Parse(Ptr<T> message, absl::string_view bytes) { + static_assert(!std::is_const_v<T>); + upb_Message_Clear(internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message)); + auto* arena = static_cast<upb_Arena*>(message->GetInternalArena()); + return upb_Decode(bytes.data(), bytes.size(), + internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message), + /* extreg= */ nullptr, /* options= */ 0, + arena) == kUpb_DecodeStatus_Ok; +} + +template <typename T> +ABSL_MUST_USE_RESULT bool Parse( + Ptr<T> message, absl::string_view bytes, + const ::protos::ExtensionRegistry& extension_registry) { + static_assert(!std::is_const_v<T>); + upb_Message_Clear(internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message)); + auto* arena = static_cast<upb_Arena*>(message->GetInternalArena()); + return upb_Decode(bytes.data(), bytes.size(), + internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message), + /* extreg= */ + ::protos::internal::GetUpbExtensions(extension_registry), + /* options= */ 0, arena) == kUpb_DecodeStatus_Ok; +} + +template <typename T> +ABSL_MUST_USE_RESULT bool Parse( + T* message, absl::string_view bytes, + const ::protos::ExtensionRegistry& extension_registry) { + static_assert(!std::is_const_v<T>); + return Parse(protos::Ptr(message, bytes, extension_registry)); +} + +template <typename T> +ABSL_MUST_USE_RESULT bool Parse(T* message, absl::string_view bytes) { + static_assert(!std::is_const_v<T>); + upb_Message_Clear(internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message)); + auto* arena = static_cast<upb_Arena*>(message->GetInternalArena()); + return upb_Decode(bytes.data(), bytes.size(), + internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message), + /* extreg= */ nullptr, /* options= */ 0, + arena) == kUpb_DecodeStatus_Ok; +} + +template <typename T> +absl::StatusOr<T> Parse(absl::string_view bytes, int options = 0) { + T message; + auto* arena = static_cast<upb_Arena*>(message.GetInternalArena()); + upb_DecodeStatus status = + upb_Decode(bytes.data(), bytes.size(), message.msg(), + ::protos::internal::GetMiniTable(&message), + /* extreg= */ nullptr, /* options= */ 0, arena); + if (status == kUpb_DecodeStatus_Ok) { + return message; + } + return MessageDecodeError(status); +} + +template <typename T> +absl::StatusOr<T> Parse(absl::string_view bytes, + const ::protos::ExtensionRegistry& extension_registry, + int options = 0) { + T message; + auto* arena = static_cast<upb_Arena*>(message.GetInternalArena()); + upb_DecodeStatus status = + upb_Decode(bytes.data(), bytes.size(), message.msg(), + ::protos::internal::GetMiniTable(&message), + ::protos::internal::GetUpbExtensions(extension_registry), + /* options= */ 0, arena); + if (status == kUpb_DecodeStatus_Ok) { + return message; + } + return MessageDecodeError(status); +} + +template <typename T> +absl::StatusOr<absl::string_view> Serialize(const T* message, upb::Arena& arena, + int options = 0) { + return ::protos::internal::Serialize( + internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message), arena.ptr(), options); +} + +template <typename T> +absl::StatusOr<absl::string_view> Serialize(Ptr<T> message, upb::Arena& arena, + int options = 0) { + return ::protos::internal::Serialize( + internal::GetInternalMsg(message), + ::protos::internal::GetMiniTable(message), arena.ptr(), options); +} + +} // namespace protos + +#endif // UPB_PROTOS_PROTOS_H_
diff --git a/protos/protos_extension_lock.cc b/protos/protos_extension_lock.cc new file mode 100644 index 0000000..dbb2fc2 --- /dev/null +++ b/protos/protos_extension_lock.cc
@@ -0,0 +1,39 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "protos/protos_extension_lock.h" + +#include <atomic> + +namespace protos::internal { + +std::atomic<UpbExtensionLocker> upb_extension_locker_global; + +} // namespace protos::internal
diff --git a/protos/protos_extension_lock.h b/protos/protos_extension_lock.h new file mode 100644 index 0000000..36d66f1 --- /dev/null +++ b/protos/protos_extension_lock.h
@@ -0,0 +1,54 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef UPB_PROTOS_PROTOS_EXTENSION_LOCK_H_ +#define UPB_PROTOS_PROTOS_EXTENSION_LOCK_H_ + +#include <atomic> + +namespace protos::internal { + +// TODO: Temporary locking api for cross-language +// concurrency issue around extension api that uses lazy promotion +// from unknown data to upb_MiniTableExtension. Will be replaced by +// a core runtime solution in the future. +// +// Any api(s) using unknown or extension data (GetOrPromoteExtension, +// Serialize and others) call lock/unlock to provide a way for +// mixed language implementations to avoid race conditions) +using UpbExtensionUnlocker = void (*)(const void*); +using UpbExtensionLocker = UpbExtensionUnlocker (*)(const void*); + +// TODO: Expose as function instead of global. +extern std::atomic<UpbExtensionLocker> upb_extension_locker_global; + +} // namespace protos::internal + +#endif // UPB_PROTOS_PROTOS_EXTENSION_LOCK_H_
diff --git a/protos/protos_extension_lock_test.cc b/protos/protos_extension_lock_test.cc new file mode 100644 index 0000000..a0ce399 --- /dev/null +++ b/protos/protos_extension_lock_test.cc
@@ -0,0 +1,147 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "protos/protos_extension_lock.h" + +#include <atomic> +#include <mutex> +#include <string> +#include <thread> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "absl/hash/hash.h" +#include "absl/log/absl_check.h" +#include "protos/protos.h" +#include "protos_generator/tests/test_model.upb.proto.h" +#include "upb/mem/arena.hpp" + +#ifndef ASSERT_OK +#define ASSERT_OK(x) ASSERT_TRUE(x.ok()) +#endif // ASSERT_OK +#ifndef EXPECT_OK +#define EXPECT_OK(x) EXPECT_TRUE(x.ok()) +#endif // EXPECT_OK + +namespace protos_generator::test::protos { + +namespace { + +std::string GenerateTestData() { + TestModel model; + model.set_str1("str"); + ThemeExtension extension1; + extension1.set_ext_name("theme"); + ABSL_CHECK_OK(::protos::SetExtension(&model, theme, extension1)); + ThemeExtension extension2; + extension2.set_ext_name("theme_extension"); + ABSL_CHECK_OK(::protos::SetExtension(&model, ThemeExtension::theme_extension, + extension2)); + ::upb::Arena arena; + auto bytes = ::protos::Serialize(&model, arena); + ABSL_CHECK_OK(bytes); + return std::string(bytes->data(), bytes->size()); +} + +std::mutex m[8]; +void unlock_func(const void* msg) { m[absl::HashOf(msg) & 0x7].unlock(); } +::protos::internal::UpbExtensionUnlocker lock_func(const void* msg) { + m[absl::HashOf(msg) & 0x7].lock(); + return &unlock_func; +} + +void TestConcurrentExtensionAccess(::protos::ExtensionRegistry registry) { + ::protos::internal::upb_extension_locker_global.store( + &lock_func, std::memory_order_release); + const std::string payload = GenerateTestData(); + TestModel parsed_model = + ::protos::Parse<TestModel>(payload, registry).value(); + const auto test_main = [&] { EXPECT_EQ("str", parsed_model.str1()); }; + const auto test_theme = [&] { + ASSERT_TRUE(::protos::HasExtension(&parsed_model, theme)); + auto ext = ::protos::GetExtension(&parsed_model, theme); + ASSERT_OK(ext); + EXPECT_EQ((*ext)->ext_name(), "theme"); + }; + const auto test_theme_extension = [&] { + auto ext = + ::protos::GetExtension(&parsed_model, ThemeExtension::theme_extension); + ASSERT_OK(ext); + EXPECT_EQ((*ext)->ext_name(), "theme_extension"); + }; + const auto test_serialize = [&] { + ::upb::Arena arena; + EXPECT_OK(::protos::Serialize(&parsed_model, arena)); + }; + const auto test_copy_constructor = [&] { + TestModel copy_a = parsed_model; + TestModel copy_b = parsed_model; + EXPECT_EQ(copy_a.has_str1(), copy_b.has_str1()); + }; + std::thread t1(test_main); + std::thread t2(test_main); + std::thread t3(test_theme); + std::thread t4(test_theme); + std::thread t5(test_theme_extension); + std::thread t6(test_theme_extension); + std::thread t7(test_serialize); + std::thread t8(test_copy_constructor); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + t5.join(); + t6.join(); + t7.join(); + t8.join(); + test_main(); + test_theme(); + test_theme_extension(); +} + +TEST(CppGeneratedCode, ConcurrentAccessDoesNotRaceBothLazy) { + ::upb::Arena arena; + TestConcurrentExtensionAccess({{}, arena}); +} + +TEST(CppGeneratedCode, ConcurrentAccessDoesNotRaceOneLazyOneEager) { + ::upb::Arena arena; + TestConcurrentExtensionAccess({{&theme}, arena}); + TestConcurrentExtensionAccess({{&ThemeExtension::theme_extension}, arena}); +} + +TEST(CppGeneratedCode, ConcurrentAccessDoesNotRaceBothEager) { + ::upb::Arena arena; + TestConcurrentExtensionAccess( + {{&theme, &ThemeExtension::theme_extension}, arena}); +} + +} // namespace +} // namespace protos_generator::test::protos
diff --git a/protos/protos_internal.h b/protos/protos_internal.h new file mode 100644 index 0000000..0a1f194 --- /dev/null +++ b/protos/protos_internal.h
@@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef UPB_PROTOS_PROTOS_INTERNAL_H_ +#define UPB_PROTOS_PROTOS_INTERNAL_H_ + +#include "upb/mem/arena.h" +#include "upb/message/message.h" + +namespace protos::internal { + +// Moves ownership of a message created in a source arena. +// +// Utility function to provide a way to move ownership across languages or VMs. +template <typename T> +T MoveMessage(upb_Message* msg, upb_Arena* arena) { + return T(msg, arena); +} + +} // namespace protos::internal +#endif
diff --git a/protos/protos_internal_test.cc b/protos/protos_internal_test.cc new file mode 100644 index 0000000..a173665 --- /dev/null +++ b/protos/protos_internal_test.cc
@@ -0,0 +1,60 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "protos/protos_internal.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include "protos_generator/tests/test_model.upb.h" +#include "protos_generator/tests/test_model.upb.proto.h" +#include "upb/mem/arena.h" + +namespace protos::testing { +namespace { +using ::protos_generator::test::protos::TestModel; + +TEST(CppGeneratedCode, InternalMoveMessage) { + // Generate message (simulating message created in another VM/language) + upb_Arena* source_arena = upb_Arena_New(); + protos_generator_test_TestModel* message = + protos_generator_test_TestModel_new(source_arena); + ASSERT_NE(message, nullptr); + protos_generator_test_TestModel_set_int_value_with_default(message, 123); + + // Move ownership. + TestModel model = + protos::internal::MoveMessage<TestModel>(message, source_arena); + // Now that we have moved ownership, free original arena. + upb_Arena_Free(source_arena); + EXPECT_EQ(model.int_value_with_default(), 123); +} + +} // namespace +} // namespace protos::testing
diff --git a/protos/protos_traits.h b/protos/protos_traits.h new file mode 100644 index 0000000..92b9dc3 --- /dev/null +++ b/protos/protos_traits.h
@@ -0,0 +1,44 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef THIRD_PARTY_UPB_PROTOS_PROTOS_TRAITS_H_ +#define THIRD_PARTY_UPB_PROTOS_PROTOS_TRAITS_H_ + +#include <type_traits> + +namespace protos::internal { + +template <typename T, typename T2> +using add_const_if_T_is_const = + std::conditional_t<std::is_const_v<T>, const T2, T2>; + +} // namespace protos::internal + +#endif // THIRD_PARTY_UPB_PROTOS_PROTOS_TRAITS_H_
diff --git a/protos/repeated_field.h b/protos/repeated_field.h new file mode 100644 index 0000000..6edcfb5 --- /dev/null +++ b/protos/repeated_field.h
@@ -0,0 +1,325 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef UPB_PROTOS_REPEATED_FIELD_H_ +#define UPB_PROTOS_REPEATED_FIELD_H_ + +#include <cstddef> +#include <iterator> +#include <type_traits> + +#include "absl/strings/string_view.h" +#include "protos/protos.h" +#include "protos/protos_traits.h" +#include "protos/repeated_field_iterator.h" +#include "upb/base/string_view.h" +#include "upb/collections/array.h" +#include "upb/mem/arena.h" +#include "upb/message/copy.h" +#include "upb/message/types.h" + +// Must be last: +#include "upb/port/def.inc" + +namespace protos { + +namespace internal { + +// Shared implementation of repeated fields for absl::string_view and +// message types for mutable and immutable variants. +// +// Immutable (const accessor), constructs this class with a nullptr upb_Array* +// when the underlying array in the message is empty. +// +// Mutable accessors on the other hand, will allocate a new empty non-null +// upb_Array* for the message when the RepeatedFieldProxy is constructed. +template <class T> +class RepeatedFieldProxyBase { + using Array = add_const_if_T_is_const<T, upb_Array>; + + public: + explicit RepeatedFieldProxyBase(Array* arr, upb_Arena* arena) + : arr_(arr), arena_(arena) {} + + size_t size() const { return arr_ != nullptr ? upb_Array_Size(arr_) : 0; } + + bool empty() const { return size() == 0; } + + protected: + // Returns upb_Array message member. + inline upb_Message* GetMessage(size_t n) const; + + Array* arr_; + upb_Arena* arena_; +}; + +template <class T> +upb_Message* RepeatedFieldProxyBase<T>::GetMessage(size_t n) const { + auto** messages = + static_cast<upb_Message**>(upb_Array_MutableDataPtr(this->arr_)); + return messages[n]; +} + +template <class T> +class RepeatedFieldProxyMutableBase : public RepeatedFieldProxyBase<T> { + public: + RepeatedFieldProxyMutableBase(upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyBase<T>(arr, arena) {} + + void clear() { upb_Array_Resize(this->arr_, 0, this->arena_); } +}; + +// RepeatedField proxy for repeated messages. +template <class T> +class RepeatedFieldProxy + : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>, + RepeatedFieldProxyMutableBase<T>> { + static_assert(!std::is_same_v<T, absl::string_view>, ""); + static_assert(!std::is_same_v<T, const absl::string_view>, ""); + static_assert(!std::is_arithmetic_v<T>, ""); + static constexpr bool kIsConst = std::is_const_v<T>; + + public: + using value_type = std::remove_const_t<T>; + using size_type = size_t; + using difference_type = ptrdiff_t; + using iterator = internal::Iterator<MessageIteratorPolicy<T>>; + using reference = typename iterator::reference; + using pointer = typename iterator::pointer; + using reverse_iterator = std::reverse_iterator<iterator>; + + explicit RepeatedFieldProxy(const upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyBase<T>(arr, arena) {} + RepeatedFieldProxy(upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyMutableBase<T>(arr, arena) {} + // Constructor used by ::protos::Ptr. + RepeatedFieldProxy(const RepeatedFieldProxy&) = default; + + // T::CProxy [] operator specialization. + typename T::CProxy operator[](size_t n) const { + upb_MessageValue message_value = upb_Array_Get(this->arr_, n); + return ::protos::internal::CreateMessage<typename std::remove_const_t<T>>( + (upb_Message*)message_value.msg_val, this->arena_); + } + + // TODO : Audit/Finalize based on Iterator Design. + // T::Proxy [] operator specialization. + template <int&... DeductionBlocker, bool b = !kIsConst, + typename = std::enable_if_t<b>> + typename T::Proxy operator[](size_t n) { + return ::protos::internal::CreateMessageProxy<T>(this->GetMessage(n), + this->arena_); + } + + // Mutable message reference specialization. + template <int&... DeductionBlocker, bool b = !kIsConst, + typename = std::enable_if_t<b>> + void push_back(const T& t) { + upb_MessageValue message_value; + message_value.msg_val = upb_Message_DeepClone( + PrivateAccess::GetInternalMsg(&t), ::protos::internal::GetMiniTable(&t), + this->arena_); + upb_Array_Append(this->arr_, message_value, this->arena_); + } + + // Mutable message add using move. + template <int&... DeductionBlocker, bool b = !kIsConst, + typename = std::enable_if_t<b>> + void push_back(T&& msg) { + upb_MessageValue message_value; + message_value.msg_val = PrivateAccess::GetInternalMsg(&msg); + upb_Arena_Fuse(GetArena(&msg), this->arena_); + upb_Array_Append(this->arr_, message_value, this->arena_); + T moved_msg = std::move(msg); + } + + iterator begin() const { + return iterator({static_cast<upb_Message**>( + const_cast<void*>(upb_Array_DataPtr(this->arr_))), + this->arena_}); + } + iterator end() const { return begin() + this->size(); } + reverse_iterator rbegin() const { return reverse_iterator(end()); } + reverse_iterator rend() const { return reverse_iterator(begin()); } + + private: + friend class ::protos::Ptr<T>; +}; + +// RepeatedField proxy for repeated strings. +template <class T> +class RepeatedFieldStringProxy + : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>, + RepeatedFieldProxyMutableBase<T>> { + static_assert(std::is_same_v<T, absl::string_view> || + std::is_same_v<T, const absl::string_view>, + ""); + static constexpr bool kIsConst = std::is_const_v<T>; + + public: + using value_type = std::remove_const_t<T>; + using size_type = size_t; + using difference_type = ptrdiff_t; + using iterator = internal::Iterator<StringIteratorPolicy<T>>; + using reference = typename iterator::reference; + using pointer = typename iterator::pointer; + using reverse_iterator = std::reverse_iterator<iterator>; + + // Immutable constructor. + explicit RepeatedFieldStringProxy(const upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyBase<T>(arr, arena) {} + // Mutable constructor. + RepeatedFieldStringProxy(upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyMutableBase<T>(arr, arena) {} + // Constructor used by ::protos::Ptr. + RepeatedFieldStringProxy(const RepeatedFieldStringProxy&) = default; + + reference operator[](size_t n) const { return begin()[n]; } + + template <int&... DeductionBlocker, bool b = !kIsConst, + typename = std::enable_if_t<b>> + void push_back(T t) { + upb_MessageValue message_value; + // Copy string to arena. + UPB_ASSERT(this->arena_); + char* data = (char*)upb_Arena_Malloc(this->arena_, t.size()); + UPB_ASSERT(data); + memcpy(data, t.data(), t.size()); + message_value.str_val = upb_StringView_FromDataAndSize(data, t.size()); + upb_Array_Append(this->arr_, message_value, this->arena_); + } + + iterator begin() const { return iterator({this->arr_, this->arena_, 0}); } + iterator end() const { + return iterator({this->arr_, this->arena_, this->size()}); + } + reverse_iterator rbegin() const { return reverse_iterator(end()); } + reverse_iterator rend() const { return reverse_iterator(begin()); } +}; + +// RepeatedField proxy for repeated scalar types. +template <typename T> +class RepeatedFieldScalarProxy + : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>, + RepeatedFieldProxyMutableBase<T>> { + static_assert(std::is_arithmetic_v<T>, ""); + static constexpr bool kIsConst = std::is_const_v<T>; + + public: + using value_type = std::remove_const_t<T>; + using size_type = size_t; + using difference_type = ptrdiff_t; + using iterator = internal::Iterator<ScalarIteratorPolicy<T>>; + using reference = typename iterator::reference; + using pointer = typename iterator::pointer; + using reverse_iterator = std::reverse_iterator<iterator>; + + explicit RepeatedFieldScalarProxy(const upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyBase<T>(arr, arena) {} + RepeatedFieldScalarProxy(upb_Array* arr, upb_Arena* arena) + : RepeatedFieldProxyMutableBase<T>(arr, arena) {} + // Constructor used by ::protos::Ptr. + RepeatedFieldScalarProxy(const RepeatedFieldScalarProxy&) = default; + + T operator[](size_t n) const { + upb_MessageValue message_value = upb_Array_Get(this->arr_, n); + typename std::remove_const_t<T> val; + memcpy(&val, &message_value, sizeof(T)); + return val; + } + + template <int&... DeductionBlocker, bool b = !kIsConst, + typename = std::enable_if_t<b>> + void push_back(T t) { + upb_MessageValue message_value; + memcpy(&message_value, &t, sizeof(T)); + upb_Array_Append(this->arr_, message_value, this->arena_); + } + + iterator begin() const { return iterator({unsafe_array()}); } + iterator cbegin() const { return begin(); } + iterator end() const { return iterator({unsafe_array() + this->size()}); } + iterator cend() const { return end(); } + + // Reverse iterator support. + reverse_iterator rbegin() const { return reverse_iterator(end()); } + reverse_iterator rend() const { return reverse_iterator(begin()); } + reverse_iterator crbegin() const { return reverse_iterator(end()); } + reverse_iterator crend() const { return reverse_iterator(begin()); } + + private: + T* unsafe_array() const { + if (kIsConst) { + const void* unsafe_ptr = ::upb_Array_DataPtr(this->arr_); + return static_cast<T*>(const_cast<void*>(unsafe_ptr)); + } + if (!kIsConst) { + void* unsafe_ptr = + ::upb_Array_MutableDataPtr(const_cast<upb_Array*>(this->arr_)); + return static_cast<T*>(unsafe_ptr); + } + } +}; + +} // namespace internal + +template <typename T> +class RepeatedField { + static constexpr bool kIsString = std::is_same_v<T, absl::string_view>; + static constexpr bool kIsScalar = std::is_arithmetic_v<T>; + + public: + using Proxy = std::conditional_t< + kIsScalar, internal::RepeatedFieldScalarProxy<T>, + std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<T>, + internal::RepeatedFieldProxy<T>>>; + using CProxy = std::conditional_t< + kIsScalar, internal::RepeatedFieldScalarProxy<const T>, + std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<const T>, + internal::RepeatedFieldProxy<const T>>>; + // TODO: T supports incomplete type from fwd.h forwarding headers + // We would like to reference T::CProxy. Validate forwarding header design. + using ValueProxy = std::conditional_t< + kIsScalar, T, + std::conditional_t<kIsString, absl::string_view, ::protos::Ptr<T>>>; + using ValueCProxy = std::conditional_t< + kIsScalar, const T, + std::conditional_t<kIsString, absl::string_view, ::protos::Ptr<const T>>>; + using Access = std::conditional_t< + kIsScalar, internal::RepeatedFieldScalarProxy<T>, + std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<T>, + internal::RepeatedFieldProxy<T>>>; +}; + +} // namespace protos + +#include "upb/port/undef.inc" + +#endif // UPB_PROTOS_REPEATED_FIELD_H_
diff --git a/protos/repeated_field_iterator.h b/protos/repeated_field_iterator.h new file mode 100644 index 0000000..a552d76 --- /dev/null +++ b/protos/repeated_field_iterator.h
@@ -0,0 +1,396 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#ifndef UPB_PROTOS_REPEATED_FIELD_ITERATOR_H_ +#define UPB_PROTOS_REPEATED_FIELD_ITERATOR_H_ + +#include <cstddef> +#include <cstring> +#include <iterator> +#include <type_traits> + +#include "absl/strings/string_view.h" +#include "protos/protos.h" +#include "upb/base/string_view.h" +#include "upb/collections/array.h" +#include "upb/mem/arena.h" +#include "upb/message/copy.h" + +// Must be last: +#include "upb/message/types.h" +#include "upb/port/def.inc" + +namespace protos { +namespace internal { + +// TODO: Implement std iterator for messages +template <typename T> +class RepeatedFieldScalarProxy; +template <typename T> +class RepeatedFieldStringProxy; + +struct IteratorTestPeer; + +template <typename T> +class Iterator; + +template <typename PolicyT> +class ReferenceProxy; + +template <typename PolicyT> +class InjectedRelationalsImpl { + using RP = ReferenceProxy<PolicyT>; + using V = typename PolicyT::value_type; + friend bool operator==(RP a, V b) { return static_cast<V>(a) == b; } + friend bool operator==(V a, RP b) { return a == static_cast<V>(b); } + friend bool operator==(RP a, RP b) { + return static_cast<V>(a) == static_cast<V>(b); + } + friend bool operator!=(RP a, V b) { return static_cast<V>(a) != b; } + friend bool operator!=(V a, RP b) { return a != static_cast<V>(b); } + friend bool operator!=(RP a, RP b) { + return static_cast<V>(a) != static_cast<V>(b); + } + friend bool operator<(RP a, V b) { return static_cast<V>(a) < b; } + friend bool operator<(V a, RP b) { return a < static_cast<V>(b); } + friend bool operator<(RP a, RP b) { + return static_cast<V>(a) < static_cast<V>(b); + } + friend bool operator<=(RP a, V b) { return static_cast<V>(a) <= b; } + friend bool operator<=(V a, RP b) { return a <= static_cast<V>(b); } + friend bool operator<=(RP a, RP b) { + return static_cast<V>(a) <= static_cast<V>(b); + } + friend bool operator>(RP a, V b) { return static_cast<V>(a) > b; } + friend bool operator>(V a, RP b) { return a > static_cast<V>(b); } + friend bool operator>(RP a, RP b) { + return static_cast<V>(a) > static_cast<V>(b); + } + friend bool operator>=(RP a, V b) { return static_cast<V>(a) >= b; } + friend bool operator>=(V a, RP b) { return a >= static_cast<V>(b); } + friend bool operator>=(RP a, RP b) { + return static_cast<V>(a) >= static_cast<V>(b); + } +}; +class NoInjectedRelationalsImpl {}; + +// We need to inject relationals for the string references because the +// relationals for string_view are templates and won't allow for implicit +// conversions from ReferenceProxy to string_view before deduction. +template <typename PolicyT> +using InjectedRelationals = std::conditional_t< + std::is_same_v<std::remove_const_t<typename PolicyT::value_type>, + absl::string_view>, + InjectedRelationalsImpl<PolicyT>, NoInjectedRelationalsImpl>; + +template <typename PolicyT> +class ReferenceProxy : InjectedRelationals<PolicyT> { + using value_type = typename PolicyT::value_type; + + public: + ReferenceProxy(const ReferenceProxy&) = default; + ReferenceProxy& operator=(const ReferenceProxy& other) { + // Assign through the references + // TODO: Make this better for strings to avoid the copy. + it_.Set(other.it_.Get()); + return *this; + } + friend void swap(ReferenceProxy a, ReferenceProxy b) { a.it_.swap(b.it_); } + + operator value_type() const { return it_.Get(); } + void operator=(const value_type& value) const { it_.Set(value); } + void operator=(value_type&& value) const { it_.Set(std::move(value)); } + Iterator<PolicyT> operator&() const { return Iterator<PolicyT>(it_); } + + private: + friend IteratorTestPeer; + friend ReferenceProxy<typename PolicyT::AddConst>; + friend Iterator<PolicyT>; + + explicit ReferenceProxy(typename PolicyT::Payload elem) : it_(elem) {} + typename PolicyT::Payload it_; +}; + +template <template <typename> class PolicyTemplate, typename T> +class ReferenceProxy<PolicyTemplate<const T>> + : InjectedRelationals<PolicyTemplate<const T>> { + using PolicyT = PolicyTemplate<const T>; + using value_type = typename PolicyT::value_type; + + public: + ReferenceProxy(ReferenceProxy<PolicyTemplate<T>> p) : it_(p.it_) {} + ReferenceProxy(const ReferenceProxy&) = default; + ReferenceProxy& operator=(const ReferenceProxy&) = delete; + + operator value_type() const { return it_.Get(); } + Iterator<PolicyT> operator&() const { return Iterator<PolicyT>(it_); } + + private: + friend IteratorTestPeer; + friend Iterator<PolicyT>; + + explicit ReferenceProxy(typename PolicyT::Payload elem) : it_(elem) {} + typename PolicyT::Payload it_; +}; + +template <typename PolicyT> +class Iterator { + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = std::remove_const_t<typename PolicyT::value_type>; + using difference_type = std::ptrdiff_t; + using pointer = Iterator; + using reference = + std::conditional_t<PolicyT::kUseReferenceProxy, ReferenceProxy<PolicyT>, + typename PolicyT::value_type>; + + constexpr Iterator() noexcept : it_(nullptr) {} + Iterator(const Iterator& other) = default; + Iterator& operator=(const Iterator& other) = default; + template < + typename P = PolicyT, + typename = std::enable_if_t<std::is_const<typename P::value_type>::value>> + Iterator(const Iterator<typename P::RemoveConst>& other) : it_(other.it_) {} + + constexpr reference operator*() const noexcept { + if constexpr (PolicyT::kUseReferenceProxy) { + return reference(it_); + } else { + return it_.Get(); + } + } + // No operator-> needed because T is a scalar. + + private: + // Hide the internal type. + using iterator = Iterator; + + public: + // {inc,dec}rementable + constexpr iterator& operator++() noexcept { + it_.AddOffset(1); + return *this; + } + constexpr iterator operator++(int) noexcept { + auto copy = *this; + ++*this; + return copy; + } + constexpr iterator& operator--() noexcept { + it_.AddOffset(-1); + return *this; + } + constexpr iterator operator--(int) noexcept { + auto copy = *this; + --*this; + return copy; + } + + // equality_comparable + friend constexpr bool operator==(const iterator& x, + const iterator& y) noexcept { + return x.it_.Index() == y.it_.Index(); + } + friend constexpr bool operator!=(const iterator& x, + const iterator& y) noexcept { + return !(x == y); + } + + // less_than_comparable + friend constexpr bool operator<(const iterator& x, + const iterator& y) noexcept { + return x.it_.Index() < y.it_.Index(); + } + friend constexpr bool operator<=(const iterator& x, + const iterator& y) noexcept { + return !(y < x); + } + friend constexpr bool operator>(const iterator& x, + const iterator& y) noexcept { + return y < x; + } + friend constexpr bool operator>=(const iterator& x, + const iterator& y) noexcept { + return !(x < y); + } + + constexpr iterator& operator+=(difference_type d) noexcept { + it_.AddOffset(d); + return *this; + } + constexpr iterator operator+(difference_type d) const noexcept { + auto copy = *this; + copy += d; + return copy; + } + friend constexpr iterator operator+(const difference_type d, + iterator it) noexcept { + return it + d; + } + + constexpr iterator& operator-=(difference_type d) noexcept { + it_.AddOffset(-d); + return *this; + } + constexpr iterator operator-(difference_type d) const noexcept { + auto copy = *this; + copy -= d; + return copy; + } + + // indexable + constexpr reference operator[](difference_type d) const noexcept { + auto copy = *this; + copy += d; + return *copy; + } + + // random access iterator + friend constexpr difference_type operator-(iterator x, iterator y) noexcept { + return x.it_.Index() - y.it_.Index(); + } + + private: + friend IteratorTestPeer; + friend ReferenceProxy<PolicyT>; + friend Iterator<typename PolicyT::AddConst>; + template <typename U> + friend class RepeatedFieldScalarProxy; + template <typename U> + friend class RepeatedFieldStringProxy; + template <typename U> + friend class RepeatedFieldProxy; + + // Create from internal::RepeatedFieldScalarProxy. + explicit Iterator(typename PolicyT::Payload it) noexcept : it_(it) {} + + // The internal iterator. + typename PolicyT::Payload it_; +}; + +template <typename T> +struct ScalarIteratorPolicy { + static constexpr bool kUseReferenceProxy = true; + using value_type = T; + using RemoveConst = ScalarIteratorPolicy<std::remove_const_t<T>>; + using AddConst = ScalarIteratorPolicy<const T>; + + struct Payload { + T* value; + void AddOffset(ptrdiff_t offset) { value += offset; } + T Get() const { return *value; } + void Set(T new_value) const { *value = new_value; } + T* Index() const { return value; } + + void swap(Payload& other) { + using std::swap; + swap(*value, *other.value); + } + + operator typename ScalarIteratorPolicy<const T>::Payload() const { + return {value}; + } + }; +}; + +template <typename T> +struct StringIteratorPolicy { + static constexpr bool kUseReferenceProxy = true; + using value_type = T; + using RemoveConst = StringIteratorPolicy<std::remove_const_t<T>>; + using AddConst = StringIteratorPolicy<const T>; + + struct Payload { + using Array = + std::conditional_t<std::is_const_v<T>, const upb_Array, upb_Array>; + Array* arr; + upb_Arena* arena; + size_t index; + + void AddOffset(ptrdiff_t offset) { index += offset; } + absl::string_view Get() const { + upb_MessageValue message_value = upb_Array_Get(arr, index); + return absl::string_view(message_value.str_val.data, + message_value.str_val.size); + } + void Set(absl::string_view new_value) const { + char* data = + static_cast<char*>(upb_Arena_Malloc(arena, new_value.size())); + memcpy(data, new_value.data(), new_value.size()); + upb_MessageValue message_value; + message_value.str_val = + upb_StringView_FromDataAndSize(data, new_value.size()); + upb_Array_Set(arr, index, message_value); + } + size_t Index() const { return index; } + + void swap(Payload& other) { + upb_MessageValue a = upb_Array_Get(this->arr, this->index); + upb_MessageValue b = upb_Array_Get(other.arr, other.index); + upb_Array_Set(this->arr, this->index, b); + upb_Array_Set(other.arr, other.index, a); + } + + operator typename StringIteratorPolicy<const T>::Payload() const { + return {arr, arena, index}; + } + }; +}; + +template <typename T> +struct MessageIteratorPolicy { + static constexpr bool kUseReferenceProxy = false; + using value_type = std::conditional_t<std::is_const_v<T>, typename T::CProxy, + typename T::Proxy>; + using RemoveConst = MessageIteratorPolicy<std::remove_const_t<T>>; + using AddConst = MessageIteratorPolicy<const T>; + + struct Payload { + using Array = + std::conditional_t<std::is_const_v<T>, const upb_Array, upb_Array>; + upb_Message** arr; + upb_Arena* arena; + + void AddOffset(ptrdiff_t offset) { arr += offset; } + auto Get() const { + if constexpr (std::is_const_v<T>) { + return ::protos::internal::CreateMessage< + typename std::remove_const_t<T>>(*arr, arena); + } else { + return ::protos::internal::CreateMessageProxy<T>(*arr, arena); + } + } + auto Index() const { return arr; } + }; +}; + +} // namespace internal +} // namespace protos + +#endif // UPB_PROTOS_REPEATED_FIELD_ITERATOR_H_
diff --git a/protos/repeated_field_iterator_test.cc b/protos/repeated_field_iterator_test.cc new file mode 100644 index 0000000..a8de900 --- /dev/null +++ b/protos/repeated_field_iterator_test.cc
@@ -0,0 +1,478 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2023 Google LLC. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google LLC nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "protos/repeated_field_iterator.h" + +#include <algorithm> +#include <array> +#include <numeric> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using ::testing::ElementsAre; + +namespace protos { +namespace internal { + +template <typename T> +using ScalarRef = ReferenceProxy<ScalarIteratorPolicy<T>>; +template <typename T> +using ScalarIterator = Iterator<ScalarIteratorPolicy<T>>; + +template <typename T> +using StringRef = ReferenceProxy<StringIteratorPolicy<T>>; +template <typename T> +using StringIterator = Iterator<StringIteratorPolicy<T>>; + +struct IteratorTestPeer { + template <typename T> + static ScalarRef<T> MakeScalarRefProxy(T& ref) { + return ScalarRef<T>({&ref}); + } + + template <typename T> + static ScalarIterator<T> MakeScalarIterator(T* ptr) { + return ScalarIterator<T>({ptr}); + } + + template <typename T> + static StringRef<T> MakeStringRefProxy(upb_Array* arr, protos::Arena& arena) { + return StringRef<T>({arr, arena.ptr(), 0}); + } + + template <typename T> + static StringIterator<T> MakeStringIterator(upb_Array* arr, + protos::Arena& arena) { + return StringIterator<T>({arr, arena.ptr()}); + } +}; + +namespace { + +TEST(ScalarReferenceTest, BasicOperationsWork) { + int i = 0; + ScalarRef<int> p = IteratorTestPeer::MakeScalarRefProxy(i); + ScalarRef<const int> cp = + IteratorTestPeer::MakeScalarRefProxy(std::as_const(i)); + EXPECT_EQ(i, 0); + p = 17; + EXPECT_EQ(i, 17); + EXPECT_EQ(p, 17); + EXPECT_EQ(cp, 17); + i = 13; + EXPECT_EQ(p, 13); + EXPECT_EQ(cp, 13); + + EXPECT_FALSE((std::is_assignable<decltype(cp), int>::value)); + + // Check that implicit conversion works T -> const T + ScalarRef<const int> cp2 = p; + EXPECT_EQ(cp2, 13); + + EXPECT_FALSE((std::is_convertible<decltype(cp), ScalarRef<int>>::value)); +} + +TEST(ScalarReferenceTest, AssignmentAndSwap) { + int i = 3; + int j = 5; + ScalarRef<int> p = IteratorTestPeer::MakeScalarRefProxy(i); + ScalarRef<int> p2 = IteratorTestPeer::MakeScalarRefProxy(j); + + EXPECT_EQ(p, 3); + EXPECT_EQ(p2, 5); + swap(p, p2); + EXPECT_EQ(p, 5); + EXPECT_EQ(p2, 3); + + p = p2; + EXPECT_EQ(p, 3); + EXPECT_EQ(p2, 3); +} + +template <typename T, typename U> +std::array<bool, 6> RunCompares(const T& a, const U& b) { + // Verify some basic properties here. + // Equivalencies + EXPECT_EQ((a == b), (b == a)); + EXPECT_EQ((a != b), (b != a)); + EXPECT_EQ((a < b), (b > a)); + EXPECT_EQ((a > b), (b < a)); + EXPECT_EQ((a <= b), (b >= a)); + EXPECT_EQ((a >= b), (b <= a)); + + // Opposites + EXPECT_NE((a == b), (a != b)); + EXPECT_NE((a < b), (a >= b)); + EXPECT_NE((a > b), (a <= b)); + + return {{ + (a == b), + (a != b), + (a < b), + (a <= b), + (a > b), + (a >= b), + }}; +} + +template <typename T> +void TestScalarIterator(T* array) { + ScalarIterator<T> it = IteratorTestPeer::MakeScalarIterator(array); + // Copy + auto it2 = it; + + EXPECT_THAT(RunCompares(it, it2), + ElementsAre(true, false, false, true, false, true)); + + // Increment + EXPECT_EQ(*++it, 11); + EXPECT_EQ(*it2, 10); + EXPECT_EQ(*it++, 11); + EXPECT_EQ(*it2, 10); + EXPECT_EQ(*it, 12); + EXPECT_EQ(*it2, 10); + + EXPECT_THAT(RunCompares(it, it2), + ElementsAre(false, true, false, false, true, true)); + + // Assign + it2 = it; + EXPECT_EQ(*it, 12); + EXPECT_EQ(*it2, 12); + + // Decrement + EXPECT_EQ(*--it, 11); + EXPECT_EQ(*it--, 11); + EXPECT_EQ(*it, 10); + + it += 5; + EXPECT_EQ(*it, 15); + EXPECT_EQ(it - it2, 3); + EXPECT_EQ(it2 - it, -3); + it -= 3; + EXPECT_EQ(*it, 12); + EXPECT_EQ(it[6], 18); + EXPECT_EQ(it[-1], 11); +} + +TEST(ScalarIteratorTest, BasicOperationsWork) { + int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + TestScalarIterator<const int>(array); + TestScalarIterator<int>(array); +} + +TEST(ScalarIteratorTest, Convertibility) { + int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(array); + it += 4; + ScalarIterator<const int> cit = it; + EXPECT_EQ(*it, 14); + EXPECT_EQ(*cit, 14); + it += 2; + EXPECT_EQ(*it, 16); + EXPECT_EQ(*cit, 14); + cit = it; + EXPECT_EQ(*it, 16); + EXPECT_EQ(*cit, 16); + + EXPECT_FALSE((std::is_convertible<ScalarIterator<const int>, + ScalarIterator<int>>::value)); + EXPECT_FALSE((std::is_assignable<ScalarIterator<int>, + ScalarIterator<const int>>::value)); +} + +TEST(ScalarIteratorTest, MutabilityOnlyWorksOnMutable) { + int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(array); + EXPECT_EQ(array[3], 13); + it[3] = 113; + EXPECT_EQ(array[3], 113); + ScalarIterator<const int> cit = it; + EXPECT_FALSE((std::is_assignable<decltype(*cit), int>::value)); + EXPECT_FALSE((std::is_assignable<decltype(cit[1]), int>::value)); +} + +TEST(ScalarIteratorTest, IteratorReferenceInteraction) { + int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; + ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(array); + EXPECT_EQ(it[4], 14); + // op& from references goes back to iterator. + ScalarIterator<int> it2 = &it[4]; + EXPECT_EQ(it + 4, it2); +} + +TEST(ScalarIteratorTest, IteratorBasedAlgorithmsWork) { + // We use a vector here to make testing it easier. + std::vector<int> v(10, 0); + ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(v.data()); + EXPECT_THAT(v, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); + std::iota(it, it + 10, 10); + EXPECT_THAT(v, ElementsAre(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)); + EXPECT_EQ(it + 5, std::find(it, it + 10, 15)); + EXPECT_EQ(145, std::accumulate(it, it + 10, 0)); + std::sort(it, it + 10, [](int a, int b) { + return std::tuple(a % 2, a) < std::tuple(b % 2, b); + }); + EXPECT_THAT(v, ElementsAre(10, 12, 14, 16, 18, 11, 13, 15, 17, 19)); +} + +const char* CloneString(protos::Arena& arena, absl::string_view str) { + char* data = (char*)upb_Arena_Malloc(arena.ptr(), str.size()); + memcpy(data, str.data(), str.size()); + return data; +} +upb_Array* MakeStringArray(protos::Arena& arena, + const std::vector<std::string>& input) { + upb_Array* arr = upb_Array_New(arena.ptr(), kUpb_CType_String); + for (absl::string_view str : input) { + upb_MessageValue message_value; + message_value.str_val = + upb_StringView_FromDataAndSize(CloneString(arena, str), str.size()); + upb_Array_Append(arr, message_value, arena.ptr()); + } + return arr; +} + +TEST(StringReferenceTest, BasicOperationsWork) { + protos::Arena arena; + upb_Array* arr = MakeStringArray(arena, {""}); + + auto read = [&] { + upb_MessageValue message_value = upb_Array_Get(arr, 0); + return absl::string_view(message_value.str_val.data, + message_value.str_val.size); + }; + + StringRef<absl::string_view> p = + IteratorTestPeer::MakeStringRefProxy<absl::string_view>(arr, arena); + StringRef<const absl::string_view> cp = + IteratorTestPeer::MakeStringRefProxy<const absl::string_view>(arr, arena); + EXPECT_EQ(read(), ""); + EXPECT_EQ(p, ""); + p = "ABC"; + EXPECT_EQ(read(), "ABC"); + EXPECT_EQ(p, "ABC"); + EXPECT_EQ(cp, "ABC"); + const_cast<char*>(read().data())[0] = 'X'; + EXPECT_EQ(read(), "XBC"); + EXPECT_EQ(p, "XBC"); + EXPECT_EQ(cp, "XBC"); + + EXPECT_FALSE((std::is_assignable<decltype(cp), int>::value)); + + // Check that implicit conversion works T -> const T + StringRef<const absl::string_view> cp2 = p; + EXPECT_EQ(cp2, "XBC"); + + EXPECT_FALSE( + (std::is_convertible<decltype(cp), StringRef<absl::string_view>>::value)); + + EXPECT_THAT(RunCompares(p, "XBC"), + ElementsAre(true, false, false, true, false, true)); + EXPECT_THAT(RunCompares(p, "YBC"), + ElementsAre(false, true, true, true, false, false)); + EXPECT_THAT(RunCompares(p, "RBC"), + ElementsAre(false, true, false, false, true, true)); + EXPECT_THAT(RunCompares(p, "XB"), + ElementsAre(false, true, false, false, true, true)); + EXPECT_THAT(RunCompares(p, "XBCD"), + ElementsAre(false, true, true, true, false, false)); +} + +TEST(StringReferenceTest, AssignmentAndSwap) { + protos::Arena arena; + upb_Array* arr1 = MakeStringArray(arena, {"ABC"}); + upb_Array* arr2 = MakeStringArray(arena, {"DEF"}); + + auto p = IteratorTestPeer::MakeStringRefProxy<absl::string_view>(arr1, arena); + auto p2 = + IteratorTestPeer::MakeStringRefProxy<absl::string_view>(arr2, arena); + + EXPECT_EQ(p, "ABC"); + EXPECT_EQ(p2, "DEF"); + swap(p, p2); + EXPECT_EQ(p, "DEF"); + EXPECT_EQ(p2, "ABC"); + + p = p2; + EXPECT_EQ(p, "ABC"); + EXPECT_EQ(p2, "ABC"); +} + +template <typename T> +void TestStringIterator(protos::Arena& arena, upb_Array* array) { + StringIterator<T> it = IteratorTestPeer::MakeStringIterator<T>(array, arena); + // Copy + auto it2 = it; + + EXPECT_THAT(RunCompares(it, it2), + ElementsAre(true, false, false, true, false, true)); + + // Increment + EXPECT_EQ(*++it, "11"); + EXPECT_EQ(*it2, "10"); + EXPECT_EQ(*it++, "11"); + EXPECT_EQ(*it2, "10"); + EXPECT_EQ(*it, "12"); + EXPECT_EQ(*it2, "10"); + + EXPECT_THAT(RunCompares(it, it2), + ElementsAre(false, true, false, false, true, true)); + + // Assign + it2 = it; + EXPECT_EQ(*it, "12"); + EXPECT_EQ(*it2, "12"); + + // Decrement + EXPECT_EQ(*--it, "11"); + EXPECT_EQ(*it--, "11"); + EXPECT_EQ(*it, "10"); + + it += 5; + EXPECT_EQ(*it, "15"); + EXPECT_EQ(it - it2, 3); + EXPECT_EQ(it2 - it, -3); + it -= 3; + EXPECT_EQ(*it, "12"); + EXPECT_EQ(it[6], "18"); + EXPECT_EQ(it[-1], "11"); +} + +TEST(StringIteratorTest, BasicOperationsWork) { + protos::Arena arena; + auto* array = MakeStringArray( + arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"}); + TestStringIterator<const absl::string_view>(arena, array); + TestStringIterator<absl::string_view>(arena, array); +} + +TEST(StringIteratorTest, Convertibility) { + protos::Arena arena; + auto* array = MakeStringArray( + arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"}); + StringIterator<absl::string_view> it = + IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena); + it += 4; + StringIterator<const absl::string_view> cit = it; + EXPECT_EQ(*it, "14"); + EXPECT_EQ(*cit, "14"); + it += 2; + EXPECT_EQ(*it, "16"); + EXPECT_EQ(*cit, "14"); + cit = it; + EXPECT_EQ(*it, "16"); + EXPECT_EQ(*cit, "16"); + + EXPECT_FALSE((std::is_convertible<StringIterator<const absl::string_view>, + StringIterator<absl::string_view>>::value)); + EXPECT_FALSE( + (std::is_assignable<StringIterator<absl::string_view>, + StringIterator<const absl::string_view>>::value)); +} + +TEST(StringIteratorTest, MutabilityOnlyWorksOnMutable) { + protos::Arena arena; + auto* array = MakeStringArray( + arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"}); + StringIterator<absl::string_view> it = + IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena); + + auto read = [&] { + upb_MessageValue message_value = upb_Array_Get(array, 3); + return absl::string_view(message_value.str_val.data, + message_value.str_val.size); + }; + + EXPECT_EQ(read(), "13"); + it[3] = "113"; + EXPECT_EQ(read(), "113"); + StringIterator<const absl::string_view> cit = it; + EXPECT_FALSE((std::is_assignable<decltype(*cit), absl::string_view>::value)); + EXPECT_FALSE( + (std::is_assignable<decltype(cit[1]), absl::string_view>::value)); +} + +TEST(StringIteratorTest, IteratorReferenceInteraction) { + protos::Arena arena; + auto* array = MakeStringArray( + arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"}); + StringIterator<absl::string_view> it = + IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena); + EXPECT_EQ(it[4], "14"); + // op& from references goes back to iterator. + StringIterator<absl::string_view> it2 = &it[4]; + EXPECT_EQ(it + 4, it2); +} + +TEST(StringIteratorTest, IteratorBasedAlgorithmsWork) { + protos::Arena arena; + auto* array = MakeStringArray( + arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"}); + StringIterator<absl::string_view> it = + IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena); + + auto read = [&] { + std::vector<absl::string_view> v; + for (int i = 0; i < 10; ++i) { + upb_MessageValue message_value = upb_Array_Get(array, i); + v.emplace_back(message_value.str_val.data, message_value.str_val.size); + } + return v; + }; + + EXPECT_THAT(read(), ElementsAre("10", "11", "12", "13", "14", // + "15", "16", "17", "18", "19")); + std::sort(it, it + 10, [](absl::string_view a, absl::string_view b) { + return std::tuple(a[1] % 2, a) < std::tuple(b[1] % 2, b); + }); + EXPECT_THAT(read(), ElementsAre("10", "12", "14", "16", "18", // + "11", "13", "15", "17", "19")); + // Now sort with the default less. + std::sort(it, it + 10); + EXPECT_THAT(read(), ElementsAre("10", "11", "12", "13", "14", // + "15", "16", "17", "18", "19")); + + // Mutable algorithm + std::generate(it, it + 10, + [i = 0]() mutable { return std::string(i++, 'x'); }); + EXPECT_THAT(read(), + ElementsAre("", "x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx", + "xxxxxxx", "xxxxxxxx", "xxxxxxxxx")); +} + +} // namespace +} // namespace internal +} // namespace protos