diff --git a/WORKSPACE b/WORKSPACE index 1f4fef685f..20dfc57ad3 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -52,6 +52,15 @@ http_archive( urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.2.zip"], ) +http_archive( + name = "com_google_absl", + sha256 = "e7fdfe0bed87702a22c5b73b6b5fe08bedd25f17d617e52df6061b0f47d480b0", + strip_prefix = "abseil-cpp-e6044634dd7caec2d79a13aecc9e765023768757", + urls = [ + "https://github.com/abseil/abseil-cpp/archive/e6044634dd7caec2d79a13aecc9e765023768757.tar.gz" + ], +) + load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies") rules_fuzzing_dependencies() diff --git a/bazel/workspace_deps.bzl b/bazel/workspace_deps.bzl index 84ad53a780..bfe1ea5cbc 100644 --- a/bazel/workspace_deps.bzl +++ b/bazel/workspace_deps.bzl @@ -15,9 +15,9 @@ def upb_deps(): maybe( http_archive, name = "com_google_absl", - url = "https://github.com/abseil/abseil-cpp/archive/b9b925341f9e90f5e7aa0cf23f036c29c7e454eb.zip", - strip_prefix = "abseil-cpp-b9b925341f9e90f5e7aa0cf23f036c29c7e454eb", - sha256 = "bb2a0b57c92b6666e8acb00f4cbbfce6ddb87e83625fb851b0e78db581340617", + url = "https://github.com/abseil/abseil-cpp/archive/e6044634dd7caec2d79a13aecc9e765023768757.tar.gz", + strip_prefix = "abseil-cpp-e6044634dd7caec2d79a13aecc9e765023768757", + sha256 = "e7fdfe0bed87702a22c5b73b6b5fe08bedd25f17d617e52df6061b0f47d480b0", ) maybe( diff --git a/benchmarks/BUILD b/benchmarks/BUILD index a7852c776d..9515e8653e 100644 --- a/benchmarks/BUILD +++ b/benchmarks/BUILD @@ -24,7 +24,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # begin:google_only -# load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", "cc_proto_library") +# load("@rules_cc//cc:defs.bzl", "cc_proto_library") # end:google_only load( diff --git a/benchmarks/build_defs.bzl b/benchmarks/build_defs.bzl index 3fc4dd22a8..168dab6634 100644 --- a/benchmarks/build_defs.bzl +++ b/benchmarks/build_defs.bzl @@ -24,7 +24,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # begin:google_only -# load("//tools/build_defs/proto/cpp:cc_proto_library.bzl", _cc_proto_library = "cc_proto_library") +# load("@rules_cc//cc:defs.bzl", _cc_proto_library = "cc_proto_library") # # _is_google3 = True # end:google_only diff --git a/protos/BUILD b/protos/BUILD new file mode 100644 index 0000000000..f68a1ec38c --- /dev/null +++ b/protos/BUILD @@ -0,0 +1,98 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# 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 Google LLC 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. + +load( + "//bazel:build_defs.bzl", + "UPB_DEFAULT_CPPOPTS", +) +load( + "//protos/bazel:upb_cc_proto_library.bzl", + "upb_cc_proto_library_copts", +) + +licenses(["notice"]) + +cc_library( + name = "protos", + srcs = ["protos.cc"], + hdrs = ["protos.h"], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + "//:mini_table", + "//:upb", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:str_format", + ], +) + +cc_library( + name = "protos_internal", + hdrs = ["protos_internal.h"], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + ":protos", + "//:mini_table", + "//:upb", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/strings:str_format", + ], +) + +# 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", + ], +) + +cc_test( + name = "protos_internal_test", + srcs = ["protos_internal_test.cc"], + copts = UPB_DEFAULT_CPPOPTS, + deps = [ + ":protos_internal", + "//:upb", + "//protos_generator/tests:test_model_upb_cc_proto", + "//protos_generator/tests:test_model_upb_proto", + "@com_google_googletest//:gtest_main", + ], +) + +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"], +) diff --git a/protos/bazel/BUILD b/protos/bazel/BUILD new file mode 100644 index 0000000000..607edae9ca --- /dev/null +++ b/protos/bazel/BUILD @@ -0,0 +1,39 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# 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 Google LLC 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. + +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") + +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 0000000000..77b2f67cab --- /dev/null +++ b/protos/bazel/upb_cc_proto_library.bzl @@ -0,0 +1,346 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# 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 Google LLC 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. + +"""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", "UpbWrappedGeneratedSrcsInfo", "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, + ) + + blaze_only_args = {} + + if _is_google3: + blaze_only_args["grep_includes"] = ctx.file._grep_includes + + (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, + **blaze_only_args + ) + + # 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, + **blaze_only_args + ) + + return CcInfo( + compilation_context = compilation_context, + linking_context = linking_context, + ) + +# Build setting for whether fasttable code generation is enabled ############### + +FastTableEnabledInfo = provider( + "Provides fasttable configuration to compiler", + fields = { + "enabled": "whether fasttable is enabled", + }, +) + +def fasttable_enabled_impl(ctx): + raw_setting = ctx.build_setting_value + + if raw_setting: + # TODO(haberman): check that the target CPU supports fasttable. + pass + + return FastTableEnabledInfo(enabled = raw_setting) + +upb_fasttable_enabled = rule( + implementation = fasttable_enabled_impl, + build_setting = config.bool(flag = True), +) + +# 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() + fasttable_enabled = (hasattr(ctx.attr, "_fasttable_enabled") and + ctx.attr._fasttable_enabled[FastTableEnabledInfo].enabled) + codegen_params = "fasttable:" if fasttable_enabled else "" + 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 = [ + "--" + generator + "_out=" + codegen_params + _get_real_root(srcs[0]), + "--plugin=protoc-gen-" + generator + "=" + tool.path, + "--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets]), + ] + + [_get_real_short_path(file) for file in proto_sources], + 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 + elif UpbWrappedGeneratedSrcsInfo in dep: + srcs = dep[UpbWrappedGeneratedSrcsInfo].srcs + else: + fail("proto_library rule must generate UpbWrappedGeneratedSrcsInfo or " + + "_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) + +def _maybe_add(d): + if _is_google3: + d["_grep_includes"] = attr.label( + allow_single_file = True, + cfg = "exec", + default = "@bazel_tools//tools/cpp:grep-includes", + ) + return d + +_upb_cc_proto_library_aspect = aspect( + attrs = _maybe_add({ + "_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 = "@com_google_protobuf//: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..) + "//: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", + ], + ), + }), + implementation = _upb_cc_proto_library_aspect_impl, + provides = [ + _UpbCcWrappedCcInfo, + _WrappedCcGeneratedSrcsInfo, + ], + required_aspect_providers = [ + UpbWrappedCcInfo, + UpbWrappedGeneratedSrcsInfo, + ], + 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 0000000000..7a651c497f --- /dev/null +++ b/protos/protos.cc @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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 "absl/strings/str_format.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_; +} + +absl::StatusOr Serialize(const upb_Message* message, + const upb_MiniTable* mini_table, + upb_Arena* arena, int options) { + 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); +} + +} // namespace internal + +} // namespace protos diff --git a/protos/protos.h b/protos/protos.h new file mode 100644 index 0000000000..85c61e12a8 --- /dev/null +++ b/protos/protos.h @@ -0,0 +1,407 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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 + +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "upb/decode.h" +#include "upb/encode.h" +#include "upb/mini_table.h" +#include "upb/upb.hpp" + +namespace protos { + +using Arena = ::upb::Arena; +class ExtensionRegistry; + +template +using Proxy = std::conditional_t::value, + typename std::remove_const_t::CProxy, + typename T::Proxy>; + +// Provides convenient access to Proxy and CProxy message types. +// +// Using rebinding and handling of const, Ptr and Ptr +// allows copying const with T* const and avoids using non-copyable Proxy types +// directly. +template +class Ptr final { + public: + Ptr() = delete; + + // Implicit conversions + Ptr(T* m) : p_(m) {} // NOLINT + Ptr(const Proxy* p) : p_(*p) {} // NOLINT + Ptr(Proxy p) : p_(p) {} // NOLINT + Ptr(const Ptr& m) = default; + + Ptr& operator=(Ptr v) & { + Proxy::Rebind(p_, v.p_); + return *this; + } + + Proxy operator*() const { return p_; } + Proxy* operator->() const { + return const_cast*>(std::addressof(p_)); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wclass-conversion" +#endif + template ::value, int> = 0> + operator Ptr() const { + Proxy p(p_); + return Ptr(&p); + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + private: + Ptr(void* msg, upb_Arena* arena) : p_(msg, arena) {} // NOLINT + + friend class Ptr; + friend typename T::Access; + + Proxy 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) { + upb_alloc* alloc = upb_Arena_Alloc(arena); + const size_t str_size = str.size(); + char* buffer = static_cast(upb_malloc(alloc, str_size)); + memcpy(buffer, str.data(), str_size); + return upb_StringView_FromDataAndSize(buffer, str_size); +} + +template +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 ""; } + 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 { +template +T CreateMessage() { + return T(); +} + +template +typename T::Proxy CreateMessageProxy(void* msg, upb_Arena* arena) { + return typename T::Proxy(msg, arena); +} + +template +typename T::CProxy CreateMessage(upb_Message* msg) { + return typename T::CProxy(msg); +} + +class ExtensionMiniTableProvider { + public: + ExtensionMiniTableProvider(const upb_MiniTable_Extension* mini_table_ext) + : mini_table_ext_(mini_table_ext) {} + const upb_MiniTable_Extension* mini_table_ext() const { + return mini_table_ext_; + } + + private: + const upb_MiniTable_Extension* 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 bar(&namespace_bar_ext); +template +class ExtensionIdentifier : public ExtensionMiniTableProvider { + public: + using Extension = ExtensionType; + using Extendee = ExtendeeType; + + ExtensionIdentifier(const upb_MiniTable_Extension* mini_table_ext) + : ExtensionMiniTableProvider(mini_table_ext) {} +}; + +template +void* GetInternalMsg(const T& message) { + return message.msg(); +} + +template +void* GetInternalMsg(const Ptr& message) { + return message->msg(); +} + +template +upb_Arena* GetArena(const T& message) { + return static_cast(message.GetInternalArena()); +} + +template +upb_Arena* GetArena(const Ptr& message) { + return static_cast(message->GetInternalArena()); +} + +upb_ExtensionRegistry* GetUpbExtensions( + const ExtensionRegistry& extension_registry); + +absl::StatusOr Serialize(const upb_Message* message, + const upb_MiniTable* mini_table, + upb_Arena* arena, int options); + +} // namespace internal + +class ExtensionRegistry { + public: + ExtensionRegistry( + const std::vector + 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 +using EnableIfProtosClass = std::enable_if_t< + std::is_base_of::value && + std::is_base_of::value>; + +template +using EnableIfMutableProto = std::enable_if_t::value>; + +template > +bool HasExtension( + const T& message, + const ::protos::internal::ExtensionIdentifier& id) { + return _upb_Message_Getext(message.msg(), id.mini_table_ext()) != nullptr; +} + +template > +bool HasExtension( + const Ptr& message, + const ::protos::internal::ExtensionIdentifier& id) { + return _upb_Message_Getext(message->msg(), id.mini_table_ext()) != nullptr; +} + +template , typename = EnableIfMutableProto> +void ClearExtension( + const Ptr& message, + const ::protos::internal::ExtensionIdentifier& id) { + _upb_Message_Clearext(message->msg(), id.mini_table_ext()); +} + +template > +void ClearExtension( + const T& message, + const ::protos::internal::ExtensionIdentifier& id) { + _upb_Message_Clearext(message.msg(), id.mini_table_ext()); +} + +template > +absl::Status SetExtension( + const T& message, + const ::protos::internal::ExtensionIdentifier& id, + Extension& value) { + auto* message_arena = static_cast(message.GetInternalArena()); + upb_Message_Extension* msg_ext = _upb_Message_GetOrCreateExtension( + message.msg(), id.mini_table_ext(), message_arena); + if (!msg_ext) { + return MessageAllocationError(); + } + auto* extension_arena = static_cast(value.GetInternalArena()); + if (message_arena != extension_arena) { + upb_Arena_Fuse(message_arena, extension_arena); + } + msg_ext->data.ptr = value.msg(); + return absl::OkStatus(); +} + +template , typename = EnableIfMutableProto> +absl::Status SetExtension( + const Ptr& message, + const ::protos::internal::ExtensionIdentifier& id, + Extension& value) { + auto* message_arena = static_cast(message->GetInternalArena()); + upb_Message_Extension* msg_ext = _upb_Message_GetOrCreateExtension( + message->msg(), id.mini_table_ext(), message_arena); + if (!msg_ext) { + return MessageAllocationError(); + } + auto* extension_arena = static_cast(message->GetInternalArena()); + if (message_arena != extension_arena) { + upb_Arena_Fuse(message_arena, extension_arena); + } + msg_ext->data.ptr = ::protos::internal::GetInternalMsg(value); + return absl::OkStatus(); +} + +template > +absl::StatusOr> GetExtension( + const T& message, + const ::protos::internal::ExtensionIdentifier& id) { + const upb_Message_Extension* ext = + _upb_Message_Getext(message.msg(), id.mini_table_ext()); + if (!ext) { + return ExtensionNotFoundError(id.mini_table_ext()->field.number); + } + return Ptr( + ::protos::internal::CreateMessage(ext->data.ptr)); +} + +template > +absl::StatusOr> GetExtension( + const Ptr& message, + const ::protos::internal::ExtensionIdentifier& id) { + const upb_Message_Extension* ext = + _upb_Message_Getext(message->msg(), id.mini_table_ext()); + if (!ext) { + return ExtensionNotFoundError(id.mini_table_ext()->field.number); + } + return Ptr( + ::protos::internal::CreateMessage(ext->data.ptr)); +} + +template +bool Parse(T& message, absl::string_view bytes) { + _upb_Message_Clear(message.msg(), T::minitable()); + auto* arena = static_cast(message.GetInternalArena()); + return upb_Decode(bytes.data(), bytes.size(), message.msg(), T::minitable(), + /* extreg= */ nullptr, /* options= */ 0, + arena) == kUpb_DecodeStatus_Ok; +} + +template +absl::StatusOr Parse(absl::string_view bytes, int options = 0) { + T message; + auto* arena = static_cast(message.GetInternalArena()); + upb_DecodeStatus status = + upb_Decode(bytes.data(), bytes.size(), message.msg(), T::minitable(), + /* extreg= */ nullptr, /* options= */ 0, arena); + if (status == kUpb_DecodeStatus_Ok) { + return message; + } + return MessageDecodeError(status); +} + +template +absl::StatusOr Parse(absl::string_view bytes, + const ::protos::ExtensionRegistry& extension_registry, + int options = 0) { + T message; + auto* arena = static_cast(message.GetInternalArena()); + upb_DecodeStatus status = + upb_Decode(bytes.data(), bytes.size(), message.msg(), T::minitable(), + ::protos::internal::GetUpbExtensions(extension_registry), + /* options= */ 0, arena); + if (status == kUpb_DecodeStatus_Ok) { + return message; + } + return MessageDecodeError(status); +} + +template +absl::StatusOr Serialize(const T& message, upb::Arena& arena, + int options = 0) { + return ::protos::internal::Serialize(message.msg(), T::minitable(), + arena.ptr(), options); +} + +template +absl::StatusOr Serialize(Ptr message, + upb::Arena& arena, + int options = 0) { + return ::protos::internal::Serialize(message->msg(), T::minitable(), + arena.ptr(), options); +} + +} // namespace protos + +#endif // UPB_PROTOS_PROTOS_H_ diff --git a/protos/protos_internal.h b/protos/protos_internal.h new file mode 100644 index 0000000000..85ba4d0679 --- /dev/null +++ b/protos/protos_internal.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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 "protos/protos.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 +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 0000000000..835601c549 --- /dev/null +++ b/protos/protos_internal_test.cc @@ -0,0 +1,55 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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/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); + protos_generator_test_TestModel_set_int_value_with_default(message, 123); + + EXPECT_NE(message, nullptr); + // Move ownership. + TestModel model = + protos::internal::MoveMessage(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_generator/BUILD b/protos_generator/BUILD new file mode 100644 index 0000000000..394668e42d --- /dev/null +++ b/protos_generator/BUILD @@ -0,0 +1,85 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# 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 Google LLC 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. + +load( + "//bazel:build_defs.bzl", + "UPB_DEFAULT_CPPOPTS", +) + +licenses(["notice"]) + +cc_binary( + name = "protoc-gen-upb-protos", + srcs = [ + "protoc-gen-upb-protos.cc", + ], + copts = UPB_DEFAULT_CPPOPTS, + visibility = ["//visibility:public"], + deps = [ + ":generator", + "//:mini_table", + "//upbc:file_layout", + "@com_google_absl//absl/base:core_headers", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", + "@com_google_protobuf//src/google/protobuf/compiler:code_generator", + ], +) + +cc_library( + name = "generator", + srcs = [ + "gen_accessors.cc", + "gen_enums.cc", + "gen_extensions.cc", + "gen_messages.cc", + "gen_utils.cc", + "output.cc", + ], + hdrs = [ + "gen_accessors.h", + "gen_enums.h", + "gen_extensions.h", + "gen_messages.h", + "gen_utils.h", + "output.h", + ], + visibility = ["//visibility:private"], + deps = [ + "//upbc:common", + "//upbc:file_layout", + "//upbc:keywords", + "@com_google_absl//absl/base:log_severity", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_protobuf//:protobuf", + "@com_google_protobuf//src/google/protobuf/compiler:code_generator", + ], +) diff --git a/protos_generator/gen_accessors.cc b/protos_generator/gen_accessors.cc new file mode 100644 index 0000000000..740daab092 --- /dev/null +++ b/protos_generator/gen_accessors.cc @@ -0,0 +1,722 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_generator/gen_accessors.h" + +#include "google/protobuf/descriptor.h" +#include "absl/container/flat_hash_set.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "protos_generator/gen_utils.h" +#include "protos_generator/output.h" +#include "upbc/file_layout.h" +#include "upbc/keywords.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +using FileLayout = ::upbc::FileLayout; +using NameToFieldDescriptorMap = + absl::flat_hash_map; + +void WriteFieldAccessorHazzer(const protobuf::Descriptor* desc, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const FileLayout& layout, Output& output); +void WriteFieldAccessorClear(const protobuf::Descriptor* desc, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const FileLayout& layout, Output& output); +void WriteMapFieldAccessors(const protobuf::Descriptor* desc, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + Output& output); + +void WriteMapAccessorDefinitions(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const absl::string_view class_name, + Output& output); +void WriteRepeatedMessageAccessor(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + absl::string_view class_name, Output& output); +void WriteRepeatedStringAccessor(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const absl::string_view class_name, + Output& output); +void WriteRepeatedScalarAccessor(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + absl::string_view class_name, Output& output); + +// Returns C++ class member name by resolving naming conflicts across +// proto field names (such as clear_ prefixes) and keyword collisions. +// +// The Upb C generator prefixes all accessors with package and class names +// avoiding collisions. Therefore we need to use raw field names when calling +// into C accessors but need to fully resolve conflicts for C++ class members. +std::string ResolveFieldName(const protobuf::FieldDescriptor* field, + const NameToFieldDescriptorMap& field_names); + +NameToFieldDescriptorMap CreateFieldNameMap( + const protobuf::Descriptor* message) { + NameToFieldDescriptorMap field_names; + for (int i = 0; i < message->field_count(); i++) { + const protobuf::FieldDescriptor* field = message->field(i); + field_names.emplace(field->name(), field); + } + return field_names; +} + +void WriteFieldAccessorsInHeader(const protobuf::Descriptor* desc, + Output& output) { + FileLayout layout(desc->file()); + + // Generate const methods. + OutputIndenter i(output); + + auto field_names = CreateFieldNameMap(desc); + + for (auto field : ::upbc::FieldNumberOrder(desc)) { + std::string resolved_field_name = ResolveFieldName(field, field_names); + absl::string_view upbc_name = field->name(); + WriteFieldAccessorHazzer(desc, field, resolved_field_name, layout, output); + WriteFieldAccessorClear(desc, field, resolved_field_name, layout, output); + + if (field->is_map()) { + WriteMapFieldAccessors(desc, field, resolved_field_name, output); + } else if (desc->options().map_entry()) { + // TODO(b/237399867) Implement map entry + } else if (field->is_repeated()) { + output( + R"cc( + inline size_t $1_size() const { + size_t len; + $0_$2(msg_, &len); + return len; + } + + inline void clear_$1() { $0_clear_$2(msg_); } + )cc", + MessageName(desc), resolved_field_name, upbc_name); + + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output( + R"cc( + $1 $2(size_t index) const; + absl::StatusOr<$0> add_$2(); + $0 mutable_$3(size_t index) const; + )cc", + MessagePtrConstType(field, /* const */ false), + MessagePtrConstType(field, /* const */ true), resolved_field_name, + upbc_name); + } else { + output( + R"cc( + $0 $1(size_t index) const; + bool add_$1($0 val); + void set_$1(size_t index, $0 val); + bool resize_$1(size_t len); + )cc", + CppConstType(field), resolved_field_name); + } + } else { + // non-repeated. + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_STRING) { + output(R"cc( + $0 $1() const; + void set_$1($0 value); + )cc", + CppConstType(field), resolved_field_name); + } else if (field->cpp_type() == + protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output(R"cc( + $1 $2() const; + $0 mutable_$3(); + )cc", + MessagePtrConstType(field, /* const */ false), + MessagePtrConstType(field, /* const */ true), + resolved_field_name, upbc_name); + } else { + output( + R"cc( + inline $0 $1() const { return $2_$3(msg_); } + inline void set_$1($0 value) { return $2_set_$3(msg_, value); } + )cc", + CppConstType(field), resolved_field_name, MessageName(desc), + upbc_name); + } + } + } +} + +void WriteFieldAccessorHazzer(const protobuf::Descriptor* desc, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const FileLayout& layout, Output& output) { + // Generate hazzer (if any). + if (layout.HasHasbit(field) || field->real_containing_oneof()) { + absl::string_view upbc_name = field->name(); + // Has presence. + output("inline bool has_$0() const { return $1_has_$2(msg_); }\n", + resolved_field_name, MessageName(desc), upbc_name); + } +} + +void WriteFieldAccessorClear(const protobuf::Descriptor* desc, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const FileLayout& layout, Output& output) { + if (layout.HasHasbit(field) || field->real_containing_oneof()) { + absl::string_view upbc_name = field->name(); + output("void clear_$0() { $2_clear_$1(msg_); }\n", resolved_field_name, + upbc_name, MessageName(desc)); + } +} + +void WriteMapFieldAccessors(const protobuf::Descriptor* desc, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + Output& output) { + const protobuf::Descriptor* entry = field->message_type(); + const protobuf::FieldDescriptor* key = entry->FindFieldByNumber(1); + const protobuf::FieldDescriptor* val = entry->FindFieldByNumber(2); + absl::string_view upbc_name = field->name(); + output( + R"cc( + inline size_t $0_size() const { return $1_$3_size(msg_); } + inline void clear_$0() { $1_clear_$3(msg_); } + void delete_$0($2 key); + )cc", + resolved_field_name, MessageName(desc), CppConstType(key), upbc_name); + + if (val->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output( + R"cc( + bool set_$0($1 key, $3 value); + bool set_$0($1 key, $4 value); + absl::StatusOr<$3> get_$0($1 key); + )cc", + resolved_field_name, CppConstType(key), CppConstType(val), + MessagePtrConstType(val, /* is_const */ true), + MessagePtrConstType(val, /* is_const */ false)); + } else { + output( + R"cc( + bool set_$0($1 key, $2 value); + absl::StatusOr<$2> get_$0($1 key); + )cc", + resolved_field_name, CppConstType(key), CppConstType(val)); + } +} + +void WriteAccessorsInSource(const protobuf::Descriptor* desc, + const FileLayout& layout, Output& output) { + std::string class_name = ClassName(desc); + absl::StrAppend(&class_name, "Access"); + output("namespace internal {\n"); + const char arena_expression[] = "arena_"; + auto field_names = CreateFieldNameMap(desc); + + // Generate const methods. + OutputIndenter i(output); + for (auto field : ::upbc::FieldNumberOrder(desc)) { + std::string resolved_field_name = ResolveFieldName(field, field_names); + if (field->is_map()) { + WriteMapAccessorDefinitions(desc, field, resolved_field_name, class_name, + output); + } else if (desc->options().map_entry()) { + // TODO(b/237399867) Implement map entry + } else if (field->is_repeated()) { + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + WriteRepeatedMessageAccessor(desc, field, resolved_field_name, + class_name, output); + } else if (field->cpp_type() == + protobuf::FieldDescriptor::CPPTYPE_STRING) { + WriteRepeatedStringAccessor(desc, field, resolved_field_name, + class_name, output); + } else { + WriteRepeatedScalarAccessor(desc, field, resolved_field_name, + class_name, output); + } + } else { + absl::string_view upbc_name = field->name(); + // non-repeated field. + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_STRING) { + output( + R"cc( + $1 $0::$2() const { + return ::protos::UpbStrToStringView($3_$4(msg_)); + } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(desc), upbc_name); + // Set string. + output( + R"cc( + void $0::set_$2($1 value) { + $4_set_$3(msg_, ::protos::UpbStrFromStringView(value, $5)); + } + )cc", + class_name, CppConstType(field), resolved_field_name, upbc_name, + MessageName(desc), arena_expression); + } else if (field->cpp_type() == + protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output( + R"cc( + $1 $0::$2() const { + if (!has_$2()) { + return $4::default_instance(); + } + return ::protos::internal::CreateMessage<$4>((upb_Message*)($3_$5(msg_))); + } + )cc", + class_name, MessagePtrConstType(field, /* is_const */ true), + resolved_field_name, MessageName(desc), + MessageBaseType(field, /* maybe_const */ false), upbc_name); + + output( + R"cc( + $1 $0::mutable_$2() { + return ::protos::internal::CreateMessageProxy<$4>( + (upb_Message*)($3_mutable_$5(msg_, $6)), $6); + } + )cc", + class_name, MessagePtrConstType(field, /* is_const */ false), + resolved_field_name, MessageName(desc), + MessageBaseType(field, /* maybe_const */ false), upbc_name, + arena_expression); + } + } + } + output("\n"); + output("} // namespace internal\n\n"); +} + +void WriteRepeatedMessageAccessor(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const absl::string_view class_name, + Output& output) { + const char arena_expression[] = "arena_"; + absl::string_view upbc_name = field->name(); + output( + R"cc( + $1 $0::$2(size_t index) const { + size_t len; + auto* ptr = $3_$5(msg_, &len); + assert(0 <= index && index < len); + return ::protos::internal::CreateMessage<$4>((upb_Message*)*(ptr + index)); + } + )cc", + class_name, MessagePtrConstType(field, /* is_const */ true), + resolved_field_name, MessageName(message), + MessageBaseType(field, /* maybe_const */ false), upbc_name); + output( + R"cc( + absl::StatusOr<$1> $0::add_$2() { + auto new_msg = $3_add_$6(msg_, $5); + if (!new_msg) { + return ::protos::MessageAllocationError(); + } + return ::protos::internal::CreateMessageProxy<$4>((upb_Message*)new_msg, $5); + } + )cc", + class_name, MessagePtrConstType(field, /* const */ false), + resolved_field_name, MessageName(message), + MessageBaseType(field, /* maybe_const */ false), arena_expression, + upbc_name); + output( + R"cc( + $1 $0::mutable_$2(size_t index) const { + size_t len; + auto* ptr = $3_$6(msg_, &len); + assert(0 <= index && index < len); + return ::protos::internal::CreateMessageProxy<$4>( + (upb_Message*)*(ptr + index), $5); + } + )cc", + class_name, MessagePtrConstType(field, /* is_const */ false), + resolved_field_name, MessageName(message), + MessageBaseType(field, /* maybe_const */ false), arena_expression, + upbc_name); +} + +void WriteRepeatedStringAccessor(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const absl::string_view class_name, + Output& output) { + absl::string_view upbc_name = field->name(); + output( + R"cc( + $1 $0::$2(size_t index) const { + size_t len; + auto* ptr = $3_mutable_$4(msg_, &len); + assert(0 <= index && index < len); + return ::protos::UpbStrToStringView(*(ptr + index)); + } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(message), upbc_name); + output( + R"cc( + bool $0::resize_$1(size_t len) { + return $2_resize_$3(msg_, len, arena_); + } + )cc", + class_name, resolved_field_name, MessageName(message), upbc_name); + output( + R"cc( + bool $0::add_$2($1 val) { + return $3_add_$4(msg_, ::protos::UpbStrFromStringView(val, arena_), arena_); + } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(message), upbc_name); + output( + R"cc( + void $0::set_$2(size_t index, $1 val) { + size_t len; + auto* ptr = $3_mutable_$4(msg_, &len); + assert(0 <= index && index < len); + *(ptr + index) = ::protos::UpbStrFromStringView(val, arena_); + } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(message), upbc_name); +} + +void WriteRepeatedScalarAccessor(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const absl::string_view class_name, + Output& output) { + absl::string_view upbc_name = field->name(); + output( + R"cc( + $1 $0::$2(size_t index) const { + size_t len; + auto* ptr = $3_mutable_$4(msg_, &len); + assert(0 <= index && index < len); + return *(ptr + index); + } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(message), upbc_name); + output( + R"cc( + bool $0::resize_$1(size_t len) { + return $2_resize_$3(msg_, len, arena_); + } + )cc", + class_name, resolved_field_name, MessageName(message), upbc_name); + output( + R"cc( + bool $0::add_$2($1 val) { return $3_add_$4(msg_, val, arena_); } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(message), upbc_name); + output( + R"cc( + void $0::set_$2(size_t index, $1 val) { + size_t len; + auto* ptr = $3_mutable_$4(msg_, &len); + assert(0 <= index && index < len); + *(ptr + index) = val; + } + )cc", + class_name, CppConstType(field), resolved_field_name, + MessageName(message), upbc_name); +} + +void WriteMapAccessorDefinitions(const protobuf::Descriptor* message, + const protobuf::FieldDescriptor* field, + const absl::string_view resolved_field_name, + const absl::string_view class_name, + Output& output) { + const protobuf::Descriptor* entry = field->message_type(); + const protobuf::FieldDescriptor* key = entry->FindFieldByNumber(1); + const protobuf::FieldDescriptor* val = entry->FindFieldByNumber(2); + absl::string_view upbc_name = field->name(); + absl::string_view converted_key_name = "key"; + absl::string_view optional_conversion_code = ""; + + if (key->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_STRING) { + // Insert conversion from absl::string_view to upb_StringView. + // Creates upb_StringView on stack to prevent allocation. + converted_key_name = "upb_key"; + optional_conversion_code = + "upb_StringView upb_key = {key.data(), key.size()};\n"; + } + if (val->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output( + R"cc( + bool $0::set_$1($2 key, $3 value) { + $6return $4_$8_set(msg_, $7, ($5*)value->msg(), arena_); + } + )cc", + class_name, resolved_field_name, CppConstType(key), + MessagePtrConstType(val, /* is_const */ true), MessageName(message), + MessageName(val->message_type()), optional_conversion_code, + converted_key_name, upbc_name); + output( + R"cc( + bool $0::set_$1($2 key, $3 value) { + $6return $4_$8_set(msg_, $7, ($5*)value->msg(), arena_); + } + )cc", + class_name, resolved_field_name, CppConstType(key), + MessagePtrConstType(val, /* is_const */ false), MessageName(message), + MessageName(val->message_type()), optional_conversion_code, + converted_key_name, upbc_name); + output( + R"cc( + absl::StatusOr<$3> $0::get_$1($2 key) { + $5* msg_value; + $7bool success = $4_$9_get(msg_, $8, &msg_value); + if (success) { + return ::protos::internal::CreateMessage<$6>(msg_value); + } + return absl::NotFoundError(""); + } + )cc", + class_name, resolved_field_name, CppConstType(key), + MessagePtrConstType(val, /* is_const */ true), MessageName(message), + MessageName(val->message_type()), + QualifiedClassName(val->message_type()), optional_conversion_code, + converted_key_name, upbc_name); + output( + R"cc( + void $0::delete_$1($2 key) { $6$4_$8_delete(msg_, $7); } + )cc", + class_name, resolved_field_name, CppConstType(key), + MessagePtrConstType(val, /* is_const */ false), MessageName(message), + MessageName(val->message_type()), optional_conversion_code, + converted_key_name, upbc_name); + } else if (val->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_STRING) { + output( + R"cc( + bool $0::set_$1($2 key, $3 value) { + $5return $4_$7_set(msg_, $6, + ::protos::UpbStrFromStringView(value, arena_), + arena_); + } + )cc", + class_name, resolved_field_name, CppConstType(key), CppConstType(val), + MessageName(message), optional_conversion_code, converted_key_name, + upbc_name); + output( + R"cc( + absl::StatusOr<$3> $0::get_$1($2 key) { + upb_StringView value; + $5bool success = $4_$7_get(msg_, $6, &value); + if (success) { + return absl::string_view(value.data, value.size); + } + return absl::NotFoundError(""); + } + )cc", + class_name, resolved_field_name, CppConstType(key), CppConstType(val), + MessageName(message), optional_conversion_code, converted_key_name, + upbc_name); + output( + R"cc( + void $0::delete_$1($2 key) { $5$4_$7_delete(msg_, $6); } + )cc", + class_name, resolved_field_name, CppConstType(key), CppConstType(val), + MessageName(message), optional_conversion_code, converted_key_name, + upbc_name); + } else { + output( + R"cc( + bool $0::set_$1($2 key, $3 value) { + $5return $4_$7_set(msg_, $6, value, arena_); + } + )cc", + class_name, resolved_field_name, CppConstType(key), CppConstType(val), + MessageName(message), optional_conversion_code, converted_key_name, + upbc_name); + output( + R"cc( + absl::StatusOr<$3> $0::get_$1($2 key) { + $3 value; + $5bool success = $4_$7_get(msg_, $6, &value); + if (success) { + return value; + } + return absl::NotFoundError(""); + } + )cc", + class_name, resolved_field_name, CppConstType(key), CppConstType(val), + MessageName(message), optional_conversion_code, converted_key_name, + upbc_name); + output( + R"cc( + void $0::delete_$1($2 key) { $5$4_$7_delete(msg_, $6); } + )cc", + class_name, resolved_field_name, CppConstType(key), CppConstType(val), + MessageName(message), optional_conversion_code, converted_key_name, + upbc_name); + } +} + +void WriteUsingAccessorsInHeader(const protobuf::Descriptor* desc, + MessageClassType handle_type, Output& output) { + FileLayout layout(desc->file()); + bool read_only = handle_type == MessageClassType::kMessageCProxy; + + // Generate const methods. + OutputIndenter i(output); + std::string class_name = ClassName(desc); + auto field_names = CreateFieldNameMap(desc); + + for (auto field : ::upbc::FieldNumberOrder(desc)) { + std::string resolved_field_name = ResolveFieldName(field, field_names); + // Generate hazzer (if any). + if (layout.HasHasbit(field) || field->real_containing_oneof()) { + // Has presence. + output("using $0Access::has_$1;\n", class_name, resolved_field_name); + } + // Generate clear. + if (layout.HasHasbit(field) || field->real_containing_oneof()) { + output("using $0Access::clear_$1;\n", class_name, resolved_field_name); + } + if (field->is_map()) { + output( + R"cc( + using $0Access::$1_size; + using $0Access::clear_$1; + using $0Access::delete_$1; + using $0Access::get_$1; + using $0Access::set_$1; + )cc", + class_name, resolved_field_name); + } else if (desc->options().map_entry()) { + // TODO(b/237399867) Implement map entry + } else if (field->is_repeated()) { + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output( + R"cc( + using $0Access::$1; + using $0Access::$1_size; + using $0Access::mutable_$1; + )cc", + class_name, resolved_field_name); + if (!read_only) { + output( + R"cc( + using $0Access::add_$1; + using $0Access::clear_$1; + )cc", + class_name, resolved_field_name); + } + } else { + output( + R"cc( + using $0Access::$1; + using $0Access::$1_size; + )cc", + class_name, resolved_field_name); + if (!read_only) { + output( + R"cc( + using $0Access::add_$1; + using $0Access::clear_$1; + using $0Access::resize_$1; + using $0Access::set_$1; + )cc", + class_name, resolved_field_name); + } + } + } else { + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + output("using $0Access::$1;\n", ClassName(desc), resolved_field_name); + if (!read_only) { + output("using $0Access::mutable_$1;\n", class_name, + resolved_field_name); + } + } else { + output("using $0Access::$1;\n", class_name, resolved_field_name); + if (!read_only) { + output("using $0Access::set_$1;\n", class_name, resolved_field_name); + } + } + } + } + output("using $0Access::msg;\n", class_name); +} + +std::string ResolveFieldName(const protobuf::FieldDescriptor* field, + const NameToFieldDescriptorMap& field_names) { + // C++ implementation specific reserved names. + static const auto& kReservedNames = + *new absl::flat_hash_set({ + "msg", + "msg_", + "arena", + "arena_", + }); + + // List of generated accessor prefixes to check against. + // Example: + // optional repeated string phase = 236; + // optional bool clear_phase = 237; + static constexpr absl::string_view kAccessorPrefixes[] = { + "clear_", + "delete_", + "add_", + "resize_", + }; + + absl::string_view field_name = field->name(); + if (kReservedNames.count(field_name) > 0) { + if (absl::EndsWith(field_name, "_")) { + return absl::StrCat(field_name, "_"); + } else { + return absl::StrCat(field_name, "__"); + } + } + for (const auto prefix : kAccessorPrefixes) { + // If field name starts with a prefix such as clear_ and the proto + // contains a field name with trailing end, depending on type of field + // (repeated, map, message) we have a conflict to resolve. + if (absl::StartsWith(field_name, prefix)) { + auto match = field_names.find(field_name.substr(prefix.size())); + if (match != field_names.end()) { + const auto* candidate = match->second; + if (candidate->is_repeated() || candidate->is_map()) { + return absl::StrCat(field_name, "_"); + } + } + } + } + return upbc::ResolveKeywordConflict(std::string(field_name)); +} + +} // namespace protos_generator diff --git a/protos_generator/gen_accessors.h b/protos_generator/gen_accessors.h new file mode 100644 index 0000000000..90da017db3 --- /dev/null +++ b/protos_generator/gen_accessors.h @@ -0,0 +1,46 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_GENERATOR_ACCESSORS_H_ +#define UPB_PROTOS_GENERATOR_ACCESSORS_H_ + +#include "google/protobuf/descriptor.h" +#include "protos_generator/gen_utils.h" +#include "protos_generator/output.h" +#include "upbc/file_layout.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +void WriteFieldAccessorsInHeader(const protobuf::Descriptor* desc, + Output& output); +void WriteAccessorsInSource(const protobuf::Descriptor* desc, + const ::upbc::FileLayout& layout, Output& output); +void WriteUsingAccessorsInHeader(const protobuf::Descriptor* desc, + MessageClassType handle_type, Output& output); +} // namespace protos_generator + +#endif // UPB_PROTOS_GENERATOR_ACCESSORS_H_ diff --git a/protos_generator/gen_enums.cc b/protos_generator/gen_enums.cc new file mode 100644 index 0000000000..0197cd18b6 --- /dev/null +++ b/protos_generator/gen_enums.cc @@ -0,0 +1,126 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_generator/gen_enums.h" + +#include "google/protobuf/descriptor.pb.h" +#include "google/protobuf/descriptor.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +// Convert enum value to C++ literal. +// +// In C++, an value of -2147483648 gets interpreted as the negative of +// 2147483648, and since 2147483648 can't fit in an integer, this produces a +// compiler warning. This works around that issue. +std::string EnumInt32ToString(int number) { + if (number == std::numeric_limits::min()) { + // This needs to be special-cased, see explanation here: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52661 + return absl::StrCat(number + 1, " - 1"); + } else { + return absl::StrCat(number); + } +} + +std::string EnumTypeName(const protobuf::EnumDescriptor* enum_descriptor) { + auto containing_type = enum_descriptor->containing_type(); + if (containing_type == nullptr) { + // enums types with no package name are prefixed with protos_ to prevent + // conflicts with generated C headers. + if (enum_descriptor->file()->package().empty()) { + return absl::StrCat("protos_", ToCIdent(enum_descriptor->name())); + } + return ToCIdent(enum_descriptor->name()); + } else { + return ToCIdent( + absl::StrCat(containing_type->name(), "_", enum_descriptor->name())); + } +} + +std::string EnumValueSymbolInNameSpace( + const protobuf::EnumDescriptor* desc, + const protobuf::EnumValueDescriptor* value) { + auto containing_type = desc->containing_type(); + if (containing_type != nullptr) { + return ToCIdent(absl::StrCat(containing_type->name(), "_", desc->name(), + "_", value->name())); + } else { + // protos enum values with no package name are prefixed with protos_ to + // prevent conflicts with generated C headers. + if (desc->file()->package().empty()) { + return absl::StrCat("protos_", ToCIdent(value->name())); + } + return ToCIdent(value->name()); + } +} + +void WriteEnumValues(const protobuf::EnumDescriptor* desc, Output& output) { + std::vector values; + auto value_count = desc->value_count(); + values.reserve(value_count); + for (int i = 0; i < value_count; i++) { + values.push_back(desc->value(i)); + } + std::sort(values.begin(), values.end(), + [](const protobuf::EnumValueDescriptor* a, + const protobuf::EnumValueDescriptor* b) { + return a->number() < b->number(); + }); + + for (size_t i = 0; i < values.size(); i++) { + auto value = values[i]; + output(" $0", EnumValueSymbolInNameSpace(desc, value)); + if (value->options().deprecated()) { + output(" ABSL_DEPRECATED(\"Proto enum $0\")", + EnumValueSymbolInNameSpace(desc, value)); + } + output(" = $0", EnumInt32ToString(value->number())); + if (i != values.size() - 1) { + output(","); + } + output("\n"); + } +} + +void WriteEnumDeclarations( + const std::vector& enums, Output& output) { + for (auto enumdesc : enums) { + output("enum $0 : int {\n", EnumTypeName(enumdesc)); + WriteEnumValues(enumdesc, output); + output("};\n\n"); + } +} + +void WriteHeaderEnumForwardDecls( + std::vector& enums, Output& output) { + for (const auto* enumdesc : enums) { + output("enum $0 : int;\n", EnumTypeName(enumdesc)); + } +} + +} // namespace protos_generator diff --git a/protos_generator/gen_enums.h b/protos_generator/gen_enums.h new file mode 100644 index 0000000000..8e7d6ea678 --- /dev/null +++ b/protos_generator/gen_enums.h @@ -0,0 +1,44 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_GENERATOR_ENUMS_H_ +#define UPB_PROTOS_GENERATOR_ENUMS_H_ + +#include "google/protobuf/descriptor.h" +#include "protos_generator/output.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +std::string EnumTypeName(const protobuf::EnumDescriptor* enum_descriptor); +void WriteHeaderEnumForwardDecls( + std::vector& enums, Output& output); +void WriteEnumDeclarations( + const std::vector& enums, Output& output); + +} // namespace protos_generator + +#endif // UPB_PROTOS_GENERATOR_ENUMS_H_ diff --git a/protos_generator/gen_extensions.cc b/protos_generator/gen_extensions.cc new file mode 100644 index 0000000000..94a0a12501 --- /dev/null +++ b/protos_generator/gen_extensions.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_generator/gen_extensions.h" + +#include "absl/strings/str_cat.h" +#include "protos_generator/gen_utils.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +std::string ExtensionIdentifierBase(const protobuf::FieldDescriptor* ext) { + assert(ext->is_extension()); + std::string ext_scope; + if (ext->extension_scope()) { + return MessageName(ext->extension_scope()); + } else { + return ToCIdent(ext->file()->package()); + } +} + +std::string ContainingTypeName(const protobuf::FieldDescriptor* ext) { + return ext->containing_type()->file() != ext->file() + ? QualifiedClassName(ext->containing_type()) + : ClassName(ext->containing_type()); +} + +void WriteExtensionIdentifierHeader(const protobuf::FieldDescriptor* ext, + Output& output) { + std::string mini_table_name = + absl::StrCat(ExtensionIdentifierBase(ext), "_", ext->name(), "_ext"); + if (ext->extension_scope()) { + output(R"cc( + static ::protos::internal::ExtensionIdentifier<$0, $1> $2; + )cc", + ContainingTypeName(ext), CppTypeParameterName(ext), ext->name()); + } else { + output( + R"cc( + extern ::protos::internal::ExtensionIdentifier<$0, $1> $2; + )cc", + ContainingTypeName(ext), CppTypeParameterName(ext), ext->name()); + } +} + +void WriteExtensionIdentifiersHeader( + const std::vector& extensions, + Output& output) { + for (const auto* ext : extensions) { + if (!ext->extension_scope()) { + WriteExtensionIdentifierHeader(ext, output); + } + } +} + +void WriteExtensionIdentifier(const protobuf::FieldDescriptor* ext, + Output& output) { + std::string mini_table_name = + absl::StrCat(ExtensionIdentifierBase(ext), "_", ext->name(), "_ext"); + if (ext->extension_scope()) { + output( + R"cc( + ::protos::internal::ExtensionIdentifier<$0, $3> $4::$2(&$1); + )cc", + ContainingTypeName(ext), mini_table_name, ext->name(), + CppTypeParameterName(ext), ClassName(ext->extension_scope())); + } else { + output( + R"cc( + ::protos::internal::ExtensionIdentifier<$0, $3> $2(&$1); + )cc", + ContainingTypeName(ext), mini_table_name, ext->name(), + CppTypeParameterName(ext)); + } +} + +void WriteExtensionIdentifiers( + const std::vector& extensions, + Output& output) { + for (const auto* ext : extensions) { + if (!ext->extension_scope()) { + WriteExtensionIdentifier(ext, output); + } + } +} + +} // namespace protos_generator diff --git a/protos_generator/gen_extensions.h b/protos_generator/gen_extensions.h new file mode 100644 index 0000000000..7313acbe70 --- /dev/null +++ b/protos_generator/gen_extensions.h @@ -0,0 +1,49 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_GENERATOR_GEN_EXTENSIONS_H_ +#define UPB_PROTOS_GENERATOR_GEN_EXTENSIONS_H_ + +#include "google/protobuf/descriptor.h" +#include "protos_generator/output.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +void WriteExtensionIdentifiersHeader( + const std::vector& extensions, + Output& output); +void WriteExtensionIdentifierHeader(const protobuf::FieldDescriptor* ext, + Output& output); +void WriteExtensionIdentifiers( + const std::vector& extensions, + Output& output); +void WriteExtensionIdentifier(const protobuf::FieldDescriptor* ext, + Output& output); + +} // namespace protos_generator + +#endif // UPB_PROTOS_GENERATOR_GEN_EXTENSIONS_H_ diff --git a/protos_generator/gen_messages.cc b/protos_generator/gen_messages.cc new file mode 100644 index 0000000000..7433ba5050 --- /dev/null +++ b/protos_generator/gen_messages.cc @@ -0,0 +1,380 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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_generator/gen_messages.h" + +#include "google/protobuf/descriptor.pb.h" +#include "google/protobuf/descriptor.h" +#include "protos_generator/gen_accessors.h" +#include "protos_generator/gen_extensions.h" +#include "protos_generator/gen_utils.h" +#include "protos_generator/output.h" +#include "upbc/common.h" +#include "upbc/file_layout.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +void WriteModelAccessDeclaration(const protobuf::Descriptor* descriptor, + Output& output); +void WriteModelPublicDeclaration( + const protobuf::Descriptor* descriptor, + const std::vector& file_exts, + Output& output); +void WriteExtensionIdentifiersInClassHeader( + const protobuf::Descriptor* message, + const std::vector& file_exts, + Output& output); +void WriteModelProxyDeclaration(const protobuf::Descriptor* descriptor, + Output& output); +void WriteModelCProxyDeclaration(const protobuf::Descriptor* descriptor, + Output& output); +void WriteInternalForwardDeclarationsInHeader( + const protobuf::Descriptor* message, Output& output); +void WriteDefaultInstanceHeader(const protobuf::Descriptor* message, + Output& output); +void WriteExtensionIdentifiersImplementation( + const protobuf::Descriptor* message, + const std::vector& file_exts, + Output& output); + +// Writes message class declarations into .upb.proto.h. +// +// For each proto Foo, FooAccess and FooProxy/FooCProxy are generated +// that are exposed to users as Foo , Ptr and Ptr. +void WriteMessageClassDeclarations( + const protobuf::Descriptor* descriptor, + const std::vector& file_exts, + Output& output) { + if (IsMapEntryMessage(descriptor)) { + // Skip map entry generation. Low level accessors for maps are + // generated that don't require a separate map type. + return; + } + + // Forward declaration of Proto Class for GCC handling of free friend method. + output("class $0;", ClassName(descriptor)); + output("namespace internal {\n"); + WriteModelAccessDeclaration(descriptor, output); + output("\n"); + WriteInternalForwardDeclarationsInHeader(descriptor, output); + output("\n"); + output("} // namespace internal\n"); + WriteModelPublicDeclaration(descriptor, file_exts, output); + output("namespace internal {\n"); + WriteModelProxyDeclaration(descriptor, output); + WriteModelCProxyDeclaration(descriptor, output); + output("} // namespace internal\n"); +} + +void WriteModelAccessDeclaration(const protobuf::Descriptor* descriptor, + Output& output) { + output( + R"cc( + + class $0Access { + public: + $0Access() {} + $0Access($1* msg, upb_Arena* arena) : msg_(msg), arena_(arena) {} // NOLINT + $0Access(const $1* msg, upb_Arena* arena) + : msg_(const_cast<$1*>(msg)), arena_(arena) {} // NOLINT + void* GetInternalArena() const { return arena_; } + )cc", + ClassName(descriptor), MessageName(descriptor)); + WriteFieldAccessorsInHeader(descriptor, output); + output.Indent(); + output( + R"cc( + private: + void* msg() const { return msg_; } + + friend class $2; + friend class $0Proxy; + friend class $0CProxy; + friend void* ::protos::internal::GetInternalMsg<$2>(const $2& message); + friend void* ::protos::internal::GetInternalMsg<$2>( + const ::protos::Ptr<$2>& message); + $1* msg_; + upb_Arena* arena_; + )cc", + ClassName(descriptor), MessageName(descriptor), + QualifiedClassName(descriptor)); + output.Outdent(); + output("};\n"); +} + +void WriteModelPublicDeclaration( + const protobuf::Descriptor* descriptor, + const std::vector& file_exts, + Output& output) { + output( + R"cc( + class $0 final : private internal::$0Access { + public: + using Access = internal::$0Access; + using Proxy = internal::$0Proxy; + using CProxy = internal::$0CProxy; + $0(); + $0(const $0& m) = delete; + $0& operator=(const $0& m) = delete; + $0($0&& m) + : Access(m.msg_, m.arena_), + owned_arena_(std::move(m.owned_arena_)) {} + $0& operator=($0&& m) { + msg_ = m.msg_; + arena_ = m.arena_; + m.msg_ = nullptr; + m.arena_ = nullptr; + owned_arena_ = std::move(m.owned_arena_); + return *this; + } + )cc", + ClassName(descriptor)); + + WriteUsingAccessorsInHeader(descriptor, MessageClassType::kMessage, output); + WriteDefaultInstanceHeader(descriptor, output); + WriteExtensionIdentifiersInClassHeader(descriptor, file_exts, output); + output.Indent(); + output.Indent(); + if (descriptor->extension_range_count()) { + // for typetrait checking + output("using ExtendableType = $0;\n", ClassName(descriptor)); + } + // Note: free function friends that are templates such as ::protos::Parse + // require explicit <$2> type parameter in declaration to be able to compile + // with gcc otherwise the compiler will fail with + // "has not been declared within namespace" error. Even though there is a + // namespace qualifier, cross namespace matching fails. + output( + R"cc( + static const upb_MiniTable* minitable(); + using $0Access::GetInternalArena; + + private: + $0(upb_Message* msg, upb_Arena* arena) : $0Access() { + msg_ = ($1*)msg; + arena_ = owned_arena_.ptr(); + upb_Arena_Fuse(arena_, arena); + } + ::protos::Arena owned_arena_; + friend Proxy; + friend CProxy; + friend absl::StatusOr<$2>(::protos::Parse<$2>(absl::string_view bytes, + int options)); + friend absl::StatusOr<$2>(::protos::Parse<$2>( + absl::string_view bytes, + const ::protos::ExtensionRegistry& extension_registry, + int options)); + friend upb_Arena* ::protos::internal::GetArena<$0>(const $0& message); + friend upb_Arena* ::protos::internal::GetArena<$0>( + const ::protos::Ptr<$0>& message); + friend $0(::protos::internal::MoveMessage<$0>(upb_Message* msg, + upb_Arena* arena)); + )cc", + ClassName(descriptor), MessageName(descriptor), + QualifiedClassName(descriptor)); + output.Outdent(); + output("};\n\n"); +} + +void WriteModelProxyDeclaration(const protobuf::Descriptor* descriptor, + Output& output) { + // Foo::Proxy. + output( + R"cc( + class $0Proxy final : private internal::$0Access { + public: + $0Proxy() = delete; + $0Proxy(const $0Proxy& m) : internal::$0Access() { + msg_ = m.msg_; + arena_ = m.arena_; + } + $0Proxy operator=(const $0Proxy& m) { + msg_ = m.msg_; + arena_ = m.arena_; + return *this; + } + using $0Access::GetInternalArena; + )cc", + ClassName(descriptor)); + + WriteUsingAccessorsInHeader(descriptor, MessageClassType::kMessageProxy, + output); + output("\n"); + output.Indent(1); + output( + R"cc( + private: + $0Proxy(void* msg, upb_Arena* arena) : internal::$0Access(($1*)msg, arena) {} + friend $0::Proxy(::protos::CreateMessage<$0>(::protos::Arena& arena)); + friend $0::Proxy(::protos::internal::CreateMessageProxy<$0>( + upb_Message*, upb_Arena*)); + friend class $0CProxy; + friend class $0Access; + friend class ::protos::Ptr<$0>; + friend class ::protos::Ptr; + friend upb_Arena* ::protos::internal::GetArena<$2>(const $2& message); + friend upb_Arena* ::protos::internal::GetArena<$2>( + const ::protos::Ptr<$2>& message); + static void Rebind($0Proxy& lhs, const $0Proxy& rhs) { + lhs.msg_ = rhs.msg_; + lhs.arena_ = rhs.arena_; + } + )cc", + ClassName(descriptor), MessageName(descriptor), + QualifiedClassName(descriptor)); + output.Outdent(1); + output("};\n\n"); +} + +void WriteModelCProxyDeclaration(const protobuf::Descriptor* descriptor, + Output& output) { + // Foo::CProxy. + output( + R"cc( + class $0CProxy final : private internal::$0Access { + public: + $0CProxy() = delete; + $0CProxy(const $0* m) : internal::$0Access(m->msg_, nullptr) {} + using $0Access::GetInternalArena; + )cc", + ClassName(descriptor), MessageName(descriptor)); + + WriteUsingAccessorsInHeader(descriptor, MessageClassType::kMessageProxy, + output); + + output.Indent(1); + output( + R"cc( + private: + $0CProxy(void* msg) : internal::$0Access(($1*)msg, nullptr){}; + friend $0::CProxy(::protos::internal::CreateMessage<$0>(upb_Message* msg)); + friend class ::protos::Ptr<$0>; + friend class ::protos::Ptr; + static void Rebind($0CProxy& lhs, const $0CProxy& rhs) { + lhs.msg_ = rhs.msg_; + lhs.arena_ = rhs.arena_; + } + )cc", + ClassName(descriptor), MessageName(descriptor)); + output.Outdent(1); + output("};\n\n"); +} + +void WriteDefaultInstanceHeader(const protobuf::Descriptor* message, + Output& output) { + output(" static ::protos::Ptr default_instance();\n", + ClassName(message)); +} + +void WriteMessageImplementation( + const protobuf::Descriptor* descriptor, + const std::vector& file_exts, + Output& output) { + bool message_is_map_entry = descriptor->options().map_entry(); + if (!message_is_map_entry) { + // Constructor. + output( + R"cc( + $0::$0() : $0Access() { + arena_ = owned_arena_.ptr(); + msg_ = $1_new(arena_); + } + )cc", + ClassName(descriptor), MessageName(descriptor)); + // Minitable + OutputIndenter i(output); + output( + R"cc( + const upb_MiniTable* $0::minitable() { return &$1; } + )cc", + ClassName(descriptor), ::upbc::MessageInit(descriptor)); + } + + ::upbc::FileLayout layout(descriptor->file()); + WriteAccessorsInSource(descriptor, layout, output); + + if (!message_is_map_entry) { + output( + R"cc( + struct $0DefaultTypeInternal { + $1* msg; + }; + $0DefaultTypeInternal _$0_default_instance_ = + $0DefaultTypeInternal{$1_new(upb_Arena_New())}; + )cc", + ClassName(descriptor), MessageName(descriptor)); + + output( + R"cc( + ::protos::Ptr $0::default_instance() { + return ::protos::internal::CreateMessage<$0>( + (upb_Message *)_$0_default_instance_.msg); + } + )cc", + ClassName(descriptor)); + } +} + +void WriteInternalForwardDeclarationsInHeader( + const protobuf::Descriptor* message, Output& output) { + // Write declaration for internal re-usable default_instance without + // leaking implementation. + output( + R"cc( + struct $0DefaultTypeInternal; + extern $0DefaultTypeInternal _$0_default_instance_; + )cc", + ClassName(message)); +} + +void WriteExtensionIdentifiersInClassHeader( + const protobuf::Descriptor* message, + const std::vector& file_exts, + Output& output) { + for (auto* ext : file_exts) { + if (ext->extension_scope() && + ext->extension_scope()->full_name() == message->full_name()) { + WriteExtensionIdentifierHeader(ext, output); + } + } +} + +void WriteExtensionIdentifiersImplementation( + const protobuf::Descriptor* message, + const std::vector& file_exts, + Output& output) { + for (auto* ext : file_exts) { + if (ext->extension_scope() && + ext->extension_scope()->full_name() == message->full_name()) { + WriteExtensionIdentifier(ext, output); + } + } +} + +} // namespace protos_generator diff --git a/protos_generator/gen_messages.h b/protos_generator/gen_messages.h new file mode 100644 index 0000000000..0b8aa95f1f --- /dev/null +++ b/protos_generator/gen_messages.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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_GENERATOR_GEN_MESSAGES_H_ +#define UPB_PROTOS_GENERATOR_GEN_MESSAGES_H_ + +#include "google/protobuf/descriptor.h" +#include "protos_generator/output.h" + +namespace protos_generator { +namespace protobuf = ::google::protobuf; + +void WriteMessageClassDeclarations( + const protobuf::Descriptor* descriptor, + const std::vector& file_exts, + Output& output); +void WriteMessageImplementation( + const protobuf::Descriptor* descriptor, + const std::vector& file_exts, + Output& output); +} // namespace protos_generator + +#endif // UPB_PROTOS_GENERATOR_GEN_MESSAGES_H_ diff --git a/protos_generator/gen_utils.cc b/protos_generator/gen_utils.cc new file mode 100644 index 0000000000..bd8a2b3661 --- /dev/null +++ b/protos_generator/gen_utils.cc @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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_generator/gen_utils.h" + +#include + +#include "absl/strings/str_cat.h" +// begin:google_only +// #include "absl/log/check.h" +// #include "absl/strings/str_replace.h" +// end:google_only +#include "absl/strings/str_split.h" +#include "upbc/keywords.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +// begin:github_only +#ifndef DCHECK +#ifndef NDEBUG +#define DCHECK(condition) if(!(condition)) LOG(FATAL); +#else +#define DCHECK(condition) if(!(true || condition)) LOG(FATAL); +#endif +#endif +// end:github_only + +std::string DotsToColons(const std::string& name) { + return absl::StrReplaceAll(name, {{".", "::"}}); +} + +std::string Namespace(const std::string& package) { + if (package.empty()) return ""; + return "::" + DotsToColons(package); +} + +// Return the qualified C++ name for a file level symbol. +std::string QualifiedFileLevelSymbol(const protobuf::FileDescriptor* file, + const std::string& name) { + if (file->package().empty()) { + return absl::StrCat("::", name); + } + // Append ::protos postfix to package name. + return absl::StrCat(Namespace(file->package()), "::protos::", name); +} + +std::string ClassName(const protobuf::Descriptor* descriptor) { + const protobuf::Descriptor* parent = descriptor->containing_type(); + std::string res; + if (parent) res += ClassName(parent) + "_"; + absl::StrAppend(&res, descriptor->name()); + return ::upbc::ResolveKeywordConflict(res); +} + +std::string QualifiedClassName(const protobuf::Descriptor* descriptor) { + return QualifiedFileLevelSymbol(descriptor->file(), ClassName(descriptor)); +} + +std::string QualifiedInternalClassName(const protobuf::Descriptor* descriptor) { + return QualifiedFileLevelSymbol( + descriptor->file(), absl::StrCat("internal::", ClassName(descriptor))); +} + +std::string CppSourceFilename(const google::protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + ".upb.proto.cc"; +} + +std::string UpbCFilename(const google::protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + ".upb.h"; +} + +std::string ForwardingHeaderFilename(const google::protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + ".upb.fwd.h"; +} + +std::string CppHeaderFilename(const google::protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + ".upb.proto.h"; +} + +std::string NamespaceFromPackageName(absl::string_view package_name) { + return absl::StrCat(absl::StrReplaceAll(package_name, {{".", "::"}}), + "::protos"); +} + +void WriteStartNamespace(const protobuf::FileDescriptor* file, Output& output) { + // Skip namespace generation if package name is not specified. + if (file->package().empty()) { + return; + } + + output("namespace $0 {\n\n", NamespaceFromPackageName(file->package())); +} + +void WriteEndNamespace(const protobuf::FileDescriptor* file, Output& output) { + if (file->package().empty()) { + return; + } + output("} // namespace $0\n\n", NamespaceFromPackageName(file->package())); +} + +std::string CppTypeInternal(const protobuf::FieldDescriptor* field, + bool is_const, bool is_type_parameter) { + std::string maybe_const = is_const ? "const " : ""; + switch (field->cpp_type()) { + case protobuf::FieldDescriptor::CPPTYPE_MESSAGE: { + if (is_type_parameter) { + return absl::StrCat(maybe_const, + QualifiedClassName(field->message_type())); + } else { + return absl::StrCat(maybe_const, + QualifiedClassName(field->message_type()), "*"); + } + } + case protobuf::FieldDescriptor::CPPTYPE_BOOL: + return "bool"; + case protobuf::FieldDescriptor::CPPTYPE_FLOAT: + return "float"; + case protobuf::FieldDescriptor::CPPTYPE_INT32: + case protobuf::FieldDescriptor::CPPTYPE_ENUM: + return "int32_t"; + case protobuf::FieldDescriptor::CPPTYPE_UINT32: + return "uint32_t"; + case protobuf::FieldDescriptor::CPPTYPE_DOUBLE: + return "double"; + case protobuf::FieldDescriptor::CPPTYPE_INT64: + return "int64_t"; + case protobuf::FieldDescriptor::CPPTYPE_UINT64: + return "uint64_t"; + case protobuf::FieldDescriptor::CPPTYPE_STRING: + return "absl::string_view"; + default: + LOG(FATAL) << "Unexpected type: " << field->cpp_type(); + } +} + +std::string CppConstType(const protobuf::FieldDescriptor* field) { + return CppTypeInternal(field, /* is_const= */ true, + /* is_type_parameter= */ false); +} + +std::string CppTypeParameterName(const protobuf::FieldDescriptor* field) { + return CppTypeInternal(field, /* is_const= */ false, + /* is_type_parameter= */ true); +} + +std::string MessageBaseType(const protobuf::FieldDescriptor* field, + bool is_const) { + DCHECK(field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE); + std::string maybe_const = is_const ? "const " : ""; + return maybe_const + QualifiedClassName(field->message_type()); +} + +std::string MessagePtrConstType(const protobuf::FieldDescriptor* field, + bool is_const) { + DCHECK(field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE); + std::string maybe_const = is_const ? "const " : ""; + return "::protos::Ptr<" + maybe_const + + QualifiedClassName(field->message_type()) + ">"; +} + +std::string MessageCProxyType(const protobuf::FieldDescriptor* field, + bool is_const) { + DCHECK(field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE); + std::string maybe_const = is_const ? "const " : ""; + return maybe_const + QualifiedInternalClassName(field->message_type()) + + "CProxy"; +} + +std::string MessageProxyType(const protobuf::FieldDescriptor* field, + bool is_const) { + DCHECK(field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE); + std::string maybe_const = is_const ? "const " : ""; + return maybe_const + QualifiedInternalClassName(field->message_type()) + + "Proxy"; +} + +} // namespace protos_generator diff --git a/protos_generator/gen_utils.h b/protos_generator/gen_utils.h new file mode 100644 index 0000000000..02ab8b51ea --- /dev/null +++ b/protos_generator/gen_utils.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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_GENERATOR_GEN_UTILS_H_ +#define UPB_PROTOS_GENERATOR_GEN_UTILS_H_ + +#include "google/protobuf/compiler/code_generator.h" +#include "google/protobuf/compiler/plugin.h" +#include "google/protobuf/descriptor.pb.h" +#include "google/protobuf/descriptor.h" +#include "google/protobuf/wire_format.h" +#include "absl/container/flat_hash_map.h" +#include "protos_generator/output.h" + +namespace protos_generator { + +namespace protobuf = ::google::protobuf; + +enum class MessageClassType { + kMessage, + kMessageCProxy, + kMessageProxy, + kMessageAccess, +}; + +std::string ClassName(const protobuf::Descriptor* descriptor); +std::string QualifiedClassName(const protobuf::Descriptor* descriptor); +std::string QualifiedInternalClassName(const protobuf::Descriptor* descriptor); + +inline bool IsMapEntryMessage(const protobuf::Descriptor* descriptor) { + return descriptor->options().map_entry(); +} +std::string CppSourceFilename(const google::protobuf::FileDescriptor* file); +std::string ForwardingHeaderFilename(const google::protobuf::FileDescriptor* file); +std::string UpbCFilename(const google::protobuf::FileDescriptor* file); +std::string CppHeaderFilename(const google::protobuf::FileDescriptor* file); + +void WriteStartNamespace(const protobuf::FileDescriptor* file, Output& output); +void WriteEndNamespace(const protobuf::FileDescriptor* file, Output& output); + +std::string CppConstType(const protobuf::FieldDescriptor* field); +std::string CppTypeParameterName(const protobuf::FieldDescriptor* field); + +std::string MessageBaseType(const protobuf::FieldDescriptor* field, + bool is_const); +// Generate protos::Ptr to be used in accessors as public +// signatures. +std::string MessagePtrConstType(const protobuf::FieldDescriptor* field, + bool is_const); +std::string MessageCProxyType(const protobuf::FieldDescriptor* field, + bool is_const); +std::string MessageProxyType(const protobuf::FieldDescriptor* field, + bool is_const); + +} // namespace protos_generator + +#endif // UPB_PROTOS_GENERATOR_GEN_UTILS_H_ diff --git a/protos_generator/output.cc b/protos_generator/output.cc new file mode 100644 index 0000000000..e5a9b79dd5 --- /dev/null +++ b/protos_generator/output.cc @@ -0,0 +1,85 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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_generator/output.h" + +#include "absl/strings/str_replace.h" + +namespace protos_generator { +namespace { + +namespace protobuf = ::google::protobuf; + +} // namespace + +std::string StripExtension(absl::string_view fname) { + size_t lastdot = fname.find_last_of('.'); + if (lastdot == std::string::npos) { + return std::string(fname); + } + return std::string(fname.substr(0, lastdot)); +} + +std::string ToCIdent(absl::string_view str) { + return absl::StrReplaceAll(str, {{".", "_"}, {"/", "_"}, {"-", "_"}}); +} + +std::string ToPreproc(absl::string_view str) { + return absl::AsciiStrToUpper(ToCIdent(str)); +} + +void EmitFileWarning(const protobuf::FileDescriptor* file, Output& output) { + output( + R"cc( + /* This file was generated by protos_generator (the upb C++ compiler) " + from the input + * file: + * + * $0 + * + * Do not edit -- your changes will be discarded when the file is + * regenerated. */ + )cc", + file->name()); + output("\n"); +} + +std::string MessageName(const protobuf::Descriptor* descriptor) { + return ToCIdent(descriptor->full_name()); +} + +std::string FileLayoutName(const google::protobuf::FileDescriptor* file) { + return ToCIdent(file->name()) + "_upb_file_layout"; +} + +std::string CHeaderFilename(const google::protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + ".upb.h"; +} + +std::string CSourceFilename(const google::protobuf::FileDescriptor* file) { + return StripExtension(file->name()) + ".upb.c"; +} + +} // namespace protos_generator diff --git a/protos_generator/output.h b/protos_generator/output.h new file mode 100644 index 0000000000..fa6c683391 --- /dev/null +++ b/protos_generator/output.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2009-2021, Google LLC + * All rights reserved. + * + * 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 Google LLC 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_GENERATOR_OUTPUT_H +#define UPB_PROTOS_GENERATOR_OUTPUT_H + +#include + +#include "google/protobuf/io/zero_copy_stream.h" +#include "google/protobuf/descriptor.h" +#include "absl/base/log_severity.h" +#include "absl/log/log.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/substitute.h" + +namespace protos_generator { + +class Output { + public: + Output(google::protobuf::io::ZeroCopyOutputStream* stream) : stream_(stream) {} + ~Output() { stream_->BackUp((int)buffer_size_); } + + template + void operator()(absl::string_view format, const Arg&... arg) { + Write(absl::Substitute(format, arg...)); + } + + // Indentation size in characters. + static constexpr size_t kIndentationSize = 2; + + void Indent() { Indent(kIndentationSize); } + void Indent(size_t size) { indent_ += size; } + + void Outdent() { Outdent(kIndentationSize); } + void Outdent(size_t size) { + if (indent_ < size) { + LOG(FATAL) << "mismatched Output indent/unindent calls"; + } + indent_ -= size; + } + + private: + void Write(absl::string_view data) { + std::string stripped; + if (absl::StartsWith(data, "\n ")) { + size_t indent = data.substr(1).find_first_not_of(' '); + if (indent > indent_) { + indent -= indent_; + } + if (indent != absl::string_view::npos) { + // Remove indentation from all lines. + auto line_prefix = data.substr(0, indent + 1); + // The final line has an extra newline and is indented two less, eg. + // R"cc( + // UPB_INLINE $0 $1_$2(const $1 *msg) { + // return $1_has_$2(msg) ? *UPB_PTR_AT(msg, $3, $0) : $4; + // } + // )cc", + std::string last_line_prefix = std::string(line_prefix); + last_line_prefix.resize(last_line_prefix.size() - 2); + data.remove_prefix(line_prefix.size()); + stripped = absl::StrReplaceAll( + data, {{line_prefix, "\n"}, {last_line_prefix, "\n"}}); + data = stripped; + } + } else { + WriteIndent(); + } + WriteRaw(data); + } + + void WriteRaw(absl::string_view data) { + while (!data.empty()) { + RefreshOutput(); + size_t to_write = std::min(data.size(), buffer_size_); + memcpy(output_buffer_, data.data(), to_write); + data.remove_prefix(to_write); + output_buffer_ += to_write; + buffer_size_ -= to_write; + } + } + + void WriteIndent() { + if (indent_ == 0) { + return; + } + size_t size = indent_; + while (size > buffer_size_) { + if (buffer_size_ > 0) { + memset(output_buffer_, ' ', buffer_size_); + } + size -= buffer_size_; + buffer_size_ = 0; + RefreshOutput(); + } + memset(output_buffer_, ' ', size); + output_buffer_ += size; + buffer_size_ -= size; + } + + void RefreshOutput() { + while (buffer_size_ == 0) { + void* void_buffer; + int size; + if (!stream_->Next(&void_buffer, &size)) { + fprintf(stderr, "upbc: Failed to write to to output\n"); + abort(); + } + output_buffer_ = static_cast(void_buffer); + buffer_size_ = size; + } + } + + google::protobuf::io::ZeroCopyOutputStream* stream_; + char* output_buffer_ = nullptr; + size_t buffer_size_ = 0; + // Current indentation size in characters. + size_t indent_ = 0; + friend class OutputIndenter; +}; + +class OutputIndenter { + public: + OutputIndenter(Output& output) + : OutputIndenter(output, Output::kIndentationSize) {} + OutputIndenter(Output& output, size_t indent_size) + : indent_size_(indent_size), output_(output) { + output.Indent(indent_size); + } + ~OutputIndenter() { output_.Outdent(indent_size_); } + + private: + size_t indent_size_; + Output& output_; +}; + +std::string StripExtension(absl::string_view fname); +std::string ToCIdent(absl::string_view str); +std::string ToPreproc(absl::string_view str); +void EmitFileWarning(const google::protobuf::FileDescriptor* file, Output& output); +std::string MessageName(const google::protobuf::Descriptor* descriptor); +std::string FileLayoutName(const google::protobuf::FileDescriptor* file); +std::string CHeaderFilename(const google::protobuf::FileDescriptor* file); +std::string CSourceFilename(const google::protobuf::FileDescriptor* file); + +} // namespace protos_generator + +#endif // UPB_PROTOS_GENERATOR_OUTPUT_H diff --git a/protos_generator/protoc-gen-upb-protos.cc b/protos_generator/protoc-gen-upb-protos.cc new file mode 100644 index 0000000000..f9522f7088 --- /dev/null +++ b/protos_generator/protoc-gen-upb-protos.cc @@ -0,0 +1,306 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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 + +#include "google/protobuf/compiler/code_generator.h" +#include "google/protobuf/compiler/plugin.h" +#include "google/protobuf/descriptor.pb.h" +#include "protos_generator/gen_enums.h" +#include "protos_generator/gen_extensions.h" +#include "protos_generator/gen_messages.h" +#include "protos_generator/gen_utils.h" +#include "protos_generator/output.h" +#include "upbc/file_layout.h" + +namespace protos_generator { +namespace { + +namespace protoc = ::google::protobuf::compiler; +namespace protobuf = ::google::protobuf; +using FileDescriptor = ::google::protobuf::FileDescriptor; +using FileLayout = ::upbc::FileLayout; + +void WriteSource(const FileLayout& layout, Output& output, + bool fasttable_enabled); +void WriteHeader(const FileLayout& layout, Output& output); +void WriteForwardingHeader(const FileLayout& layout, Output& output); +void WriteMessageImplementations(const protobuf::FileDescriptor* file, + Output& output); +void WriteTypedefForwardingHeader( + const protobuf::FileDescriptor* file, + const std::vector& file_messages, + Output& output); +void WriteHeaderMessageForwardDecls( + const protobuf::FileDescriptor* file, + const std::vector& file_messages, + const std::vector& file_exts, + Output& output); + +class Generator : public protoc::CodeGenerator { + public: + ~Generator() override {} + bool Generate(const protobuf::FileDescriptor* file, + const std::string& parameter, protoc::GeneratorContext* context, + std::string* error) const override; + uint64_t GetSupportedFeatures() const override { + return FEATURE_PROTO3_OPTIONAL; + } +}; + +bool Generator::Generate(const protobuf::FileDescriptor* file, + const std::string& parameter, + protoc::GeneratorContext* context, + std::string* error) const { + bool fasttable_enabled = false; + std::vector> params; + google::protobuf::compiler::ParseGeneratorParameter(parameter, ¶ms); + + for (const auto& pair : params) { + if (pair.first == "fasttable") { + fasttable_enabled = true; + } else { + *error = "Unknown parameter: " + pair.first; + return false; + } + } + + FileLayout layout(file); + + // Write model.upb.fwd.h + Output forwarding_header_output( + context->Open(ForwardingHeaderFilename(file))); + WriteForwardingHeader(layout, forwarding_header_output); + // Write model.upb.proto.h + Output header_output(context->Open(CppHeaderFilename(file))); + WriteHeader(layout, header_output); + // Write model.upb.proto.cc + Output cc_output(context->Open(CppSourceFilename(file))); + WriteSource(layout, cc_output, fasttable_enabled); + return true; +} + +// The forwarding header defines Access/Proxy/CProxy for message classes +// used to include when referencing dependencies to prevent transitive +// dependency headers from being included. +void WriteForwardingHeader(const FileLayout& layout, Output& output) { + const protobuf::FileDescriptor* file = layout.descriptor(); + EmitFileWarning(file, output); + output( + R"cc( +#ifndef $0_UPB_FWD_H_ +#define $0_UPB_FWD_H_ + )cc", + ToPreproc(file->name())); + output("\n"); + const std::vector this_file_messages = + ::upbc::SortedMessages(file); + WriteTypedefForwardingHeader(file, this_file_messages, output); + output("#endif /* $0_UPB_FWD_H_ */\n", ToPreproc(file->name())); +} + +void WriteHeader(const FileLayout& layout, Output& output) { + const protobuf::FileDescriptor* file = layout.descriptor(); + EmitFileWarning(file, output); + output( + R"cc( +#ifndef $0_UPB_PROTO_H_ +#define $0_UPB_PROTO_H_ + +#include "protos/protos.h" +#include "protos/protos_internal.h" +#include "upb/upb.hpp" + +#include "absl/strings/string_view.h" +#include "absl/status/statusor.h" +#include "upb/msg_internal.h" + )cc", + ToPreproc(file->name())); + + // Import headers for proto public dependencies. + for (int i = 0; i < file->public_dependency_count(); i++) { + if (i == 0) { + output("// Public Imports.\n"); + } + output("#include \"$0\"\n", CppHeaderFilename(file->public_dependency(i))); + if (i == file->public_dependency_count() - 1) { + output("\n"); + } + } + + output("#include \"upb/port_def.inc\"\n"); + + const std::vector this_file_messages = + ::upbc::SortedMessages(file); + const std::vector this_file_exts = + ::upbc::SortedExtensions(file); + + if (!this_file_messages.empty()) { + output("\n"); + } + + WriteHeaderMessageForwardDecls(file, this_file_messages, this_file_exts, + output); + WriteStartNamespace(file, output); + + std::vector this_file_enums = + ::upbc::SortedEnums(file); + + // Write Class and Enums. + WriteEnumDeclarations(this_file_enums, output); + output("\n"); + + for (auto message : this_file_messages) { + WriteMessageClassDeclarations(message, this_file_exts, output); + } + output("\n"); + + WriteExtensionIdentifiersHeader(this_file_exts, output); + output("\n"); + + WriteEndNamespace(file, output); + + output("\n#include \"upb/port_undef.inc\"\n\n"); + // End of "C" section. + + output("#endif /* $0_UPB_PROTO_H_ */\n", ToPreproc(file->name())); +} + +// Writes a .upb.cc source file. +void WriteSource(const FileLayout& layout, Output& output, + bool fasttable_enabled) { + const protobuf::FileDescriptor* file = layout.descriptor(); + EmitFileWarning(file, output); + + output( + R"cc( +#include +#include "absl/strings/string_view.h" +#include "upb/msg_internal.h" +#include "protos/protos.h" +#include "$0" + )cc", + CppHeaderFilename(file)); + + for (int i = 0; i < file->dependency_count(); i++) { + output("#include \"$0\"\n", CppHeaderFilename(file->dependency(i))); + } + output("#include \"upb/port_def.inc\"\n"); + + WriteStartNamespace(file, output); + WriteMessageImplementations(file, output); + const std::vector this_file_exts = + ::upbc::SortedExtensions(file); + WriteExtensionIdentifiers(this_file_exts, output); + WriteEndNamespace(file, output); + + output("#include \"upb/port_undef.inc\"\n\n"); +} + +void WriteMessageImplementations(const protobuf::FileDescriptor* file, + Output& output) { + const std::vector file_exts = + ::upbc::SortedExtensions(file); + const std::vector this_file_messages = + ::upbc::SortedMessages(file); + for (auto message : this_file_messages) { + WriteMessageImplementation(message, file_exts, output); + } +} + +void WriteTypedefForwardingHeader( + const protobuf::FileDescriptor* file, + const std::vector& file_messages, + Output& output) { + WriteStartNamespace(file, output); + + // Forward-declare types defined in this file. + for (auto message : file_messages) { + output( + R"cc( + class $0; + namespace internal { + class $0Access; + class $0Proxy; + class $0CProxy; + } // namespace internal + )cc", + ClassName(message)); + } + output("\n"); + WriteEndNamespace(file, output); +} + +/// Writes includes for upb C minitables and fwd.h for transitive typedefs. +void WriteHeaderMessageForwardDecls( + const protobuf::FileDescriptor* file, + const std::vector& file_messages, + const std::vector& file_exts, + Output& output) { + // Import forward-declaration of types defined in this file. + output("#include \"$0\"\n", UpbCFilename(file)); + output("#include \"$0\"\n", ForwardingHeaderFilename(file)); + // Forward-declare types not in this file, but used as submessages. + // Order by full name for consistent ordering. + std::map forward_messages; + + for (auto* message : file_messages) { + for (int i = 0; i < message->field_count(); i++) { + const protobuf::FieldDescriptor* field = message->field(i); + if (field->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE && + field->file() != field->message_type()->file()) { + forward_messages[field->message_type()->full_name()] = + field->message_type(); + } + } + } + for (auto* ext : file_exts) { + if (ext->file() != ext->containing_type()->file()) { + forward_messages[ext->containing_type()->full_name()] = + ext->containing_type(); + if (ext->cpp_type() == protobuf::FieldDescriptor::CPPTYPE_MESSAGE) { + forward_messages[ext->message_type()->full_name()] = + ext->message_type(); + } + } + } + std::map files_to_import; + for (const auto& pair : forward_messages) { + files_to_import[ForwardingHeaderFilename(pair.second->file())] = file; + } + for (const auto& pair : files_to_import) { + output("#include \"$0\"\n", UpbCFilename(pair.second)); + output("#include \"$0\"\n", pair.first); + } + output("\n"); +} + +} // namespace +} // namespace protos_generator + +int main(int argc, char** argv) { + protos_generator::Generator generator_cc; + return google::protobuf::compiler::PluginMain(argc, argv, &generator_cc); +} diff --git a/protos_generator/tests/BUILD b/protos_generator/tests/BUILD new file mode 100644 index 0000000000..4151ba7f90 --- /dev/null +++ b/protos_generator/tests/BUILD @@ -0,0 +1,118 @@ +# Copyright (c) 2009-2021, Google LLC +# All rights reserved. +# +# 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 Google LLC 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. + +load( + "//bazel:build_defs.bzl", + "UPB_DEFAULT_CPPOPTS", +) +load( + "//protos/bazel:upb_cc_proto_library.bzl", + "upb_cc_proto_library", +) +load( + "//bazel:upb_proto_library.bzl", + "upb_proto_library", +) +load( + "@rules_cc//cc:defs.bzl", + "cc_proto_library", +) + +licenses(["notice"]) + +proto_library( + name = "test_model_proto", + srcs = [ + "child_model.proto", + "test_enum.proto", + "test_extension.proto", + "test_model.proto", + ], +) + +proto_library( + name = "no_package_proto", + srcs = [ + "no_package.proto", + ], +) + +upb_proto_library( + name = "test_model_upb_proto", + visibility = [ + "//protos:__pkg__", + ], + deps = [":test_model_proto"], +) + +upb_cc_proto_library( + name = "test_model_upb_cc_proto", + visibility = [ + "//protos:__pkg__", + "//protos_generator/nc_tests:__pkg__", + ], + deps = [":test_model_proto"], +) + +upb_cc_proto_library( + name = "no_package_upb_cc_proto", + deps = [":no_package_proto"], +) + +cc_proto_library( + name = "test_model_cc_proto", + deps = [":test_model_proto"], +) + +# begin:google_only +# proto_library( +# name = "legacy_name_proto", +# srcs = [ +# "legacy-name.proto", +# ], +# ) +# +# upb_cc_proto_library( +# name = "legacy_name_test_proto", +# visibility = ["//protos_generator/nc_tests:__pkg__"], +# deps = [":legacy_name_proto"], +# ) +# end:google_only + +cc_test( + name = "test_generated_cc_code", + srcs = ["test_generated.cc"], + copts = UPB_DEFAULT_CPPOPTS, + deps = [ + # begin:google_only +# ":legacy_name_test_proto", + # end:google_only + ":no_package_upb_cc_proto", + ":test_model_upb_cc_proto", + ":test_model_upb_proto", + "@com_google_googletest//:gtest_main", + "//:upb", + ], +) diff --git a/protos_generator/tests/child_model.proto b/protos_generator/tests/child_model.proto new file mode 100644 index 0000000000..05d0c41d4e --- /dev/null +++ b/protos_generator/tests/child_model.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package protos_generator.test; + +import public "protos_generator/tests/test_enum.proto"; + +message ChildModel1 { + optional bool child_b1 = 44; + optional string child_str1 = 56; +} + +message ChildModel3 { + string sub_key = 1; + bool bool1 = 2; + int32 i32 = 3; + optional string opt_str = 4; + optional bool opt_bool = 5; + optional int32 opt_i32 = 6; +} diff --git a/protos_generator/tests/legacy-name.proto b/protos_generator/tests/legacy-name.proto new file mode 100644 index 0000000000..8685219008 --- /dev/null +++ b/protos_generator/tests/legacy-name.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package protos_generator.test; + +// option java_multiple_files = true; + +enum LegacyEnum { + PHASE_DEFAULT = 0; + PHASE_BUSY = 1; +} diff --git a/protos_generator/tests/no_package.proto b/protos_generator/tests/no_package.proto new file mode 100644 index 0000000000..a52376b9ab --- /dev/null +++ b/protos_generator/tests/no_package.proto @@ -0,0 +1,8 @@ +syntax = "proto2"; + +// option java_multiple_files = true; + +enum EnumWithNoPackage { + CELSIUS = 1; + FAHRENHEIT = 2; +} diff --git a/protos_generator/tests/test_enum.proto b/protos_generator/tests/test_enum.proto new file mode 100644 index 0000000000..c5354ccb8d --- /dev/null +++ b/protos_generator/tests/test_enum.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package protos_generator.test; + +enum TestEnum { + DEVICE_UNKNOWN = 0; + DEVICE_KEYBOARD = 1; + DEVICE_MOUSE = 2; + DEVICE_MONITOR = 3; +} diff --git a/protos_generator/tests/test_extension.proto b/protos_generator/tests/test_extension.proto new file mode 100644 index 0000000000..e7207739f1 --- /dev/null +++ b/protos_generator/tests/test_extension.proto @@ -0,0 +1,12 @@ +syntax = "proto2"; + +package protos_generator.test.someotherpackage; + +import "protos_generator/tests/test_model.proto"; + +// Define extension that is extending proto outside this package with a type +// defined in different file. + +extend TestModel { + optional ThemeExtension styling = 13001; +} diff --git a/protos_generator/tests/test_generated.cc b/protos_generator/tests/test_generated.cc new file mode 100644 index 0000000000..f9fcea53fe --- /dev/null +++ b/protos_generator/tests/test_generated.cc @@ -0,0 +1,582 @@ +// Copyright (c) 2009-2021, Google LLC +// All rights reserved. +// +// 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 Google LLC 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 + +#include "gtest/gtest.h" +#include "protos_generator/tests/child_model.upb.proto.h" +#include "protos_generator/tests/no_package.upb.proto.h" +#include "protos_generator/tests/test_model.upb.proto.h" +#include "upb/string_view.h" + +using ::protos_generator::test::protos::ChildModel1; +using ::protos_generator::test::protos::other_ext; +using ::protos_generator::test::protos::RED; +using ::protos_generator::test::protos::TestEnum; +using ::protos_generator::test::protos::TestModel; +using ::protos_generator::test::protos::TestModel_Category_IMAGES; +using ::protos_generator::test::protos::TestModel_Category_NEWS; +using ::protos_generator::test::protos::TestModel_Category_VIDEO; +using ::protos_generator::test::protos::theme; +using ::protos_generator::test::protos::ThemeExtension; + +TEST(CppGeneratedCode, Constructor) { TestModel test_model; } + +TEST(CppGeneratedCode, MessageEnum) { EXPECT_EQ(5, TestModel_Category_IMAGES); } + +TEST(CppGeneratedCode, ImportedEnum) { EXPECT_EQ(3, TestEnum::DEVICE_MONITOR); } + +TEST(CppGeneratedCode, Enum) { EXPECT_EQ(1, RED); } + +TEST(CppGeneratedCode, EnumNoPackage) { EXPECT_EQ(1, ::protos_CELSIUS); } + +TEST(CppGeneratedCode, ArenaConstructor) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + EXPECT_EQ(false, testModel.has_b1()); +} + +TEST(CppGeneratedCode, Booleans) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + EXPECT_FALSE(testModel.b1()); + testModel.set_b1(true); + EXPECT_TRUE(testModel.b1()); + testModel.set_b1(false); + EXPECT_FALSE(testModel.b1()); + testModel.set_b1(true); + EXPECT_TRUE(testModel.b1()); + testModel.clear_b1(); + EXPECT_FALSE(testModel.has_b1()); +} + +TEST(CppGeneratedCode, ScalarInt32) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + // Test int32 defaults. + EXPECT_EQ(testModel.value(), 0); + EXPECT_FALSE(testModel.has_value()); + // Floating point defautls. + EXPECT_EQ(std::numeric_limits::infinity(), + testModel.float_value_with_default()); + EXPECT_EQ(-std::numeric_limits::infinity(), + testModel.double_value_with_default()); + + // Set value. + testModel.set_value(5); + EXPECT_TRUE(testModel.has_value()); + EXPECT_EQ(testModel.value(), 5); + // Change value. + testModel.set_value(10); + EXPECT_TRUE(testModel.has_value()); + EXPECT_EQ(testModel.value(), 10); + // Clear value. + testModel.clear_value(); + EXPECT_FALSE(testModel.has_value()); + EXPECT_EQ(testModel.value(), 0); +} + +const char kTestStr1[] = "abcdefg"; +const char kTestStr2[] = "just another test string"; + +TEST(CppGeneratedCode, Strings) { + TestModel testModel; + testModel.set_str1(kTestStr1); + testModel.set_str2(kTestStr2); + EXPECT_EQ(testModel.str1(), kTestStr1); + EXPECT_EQ(testModel.str2(), kTestStr2); + EXPECT_TRUE(testModel.has_str1()); + EXPECT_TRUE(testModel.has_str2()); + + testModel.clear_str1(); + EXPECT_FALSE(testModel.has_str1()); + EXPECT_TRUE(testModel.has_str2()); +} + +TEST(CppGeneratedCode, ScalarUInt32) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + // Test defaults. + EXPECT_EQ(testModel.optional_uint32(), 0); + EXPECT_FALSE(testModel.has_optional_uint32()); + // Set value. + testModel.set_optional_uint32(0xA0001000); + EXPECT_TRUE(testModel.has_optional_uint32()); + EXPECT_EQ(testModel.optional_uint32(), 0xA0001000); + // Change value. + testModel.set_optional_uint32(0x70002000); + EXPECT_TRUE(testModel.has_optional_uint32()); + EXPECT_EQ(testModel.optional_uint32(), 0x70002000); + // Clear value. + testModel.clear_optional_uint32(); + EXPECT_FALSE(testModel.has_optional_uint32()); + EXPECT_EQ(testModel.optional_uint32(), 0); +} + +TEST(CppGeneratedCode, ScalarInt64) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + // Test defaults. + EXPECT_EQ(testModel.optional_int64(), 0); + EXPECT_FALSE(testModel.has_optional_int64()); + // Set value. + testModel.set_optional_int64(0xFF00CCDDA0001000); + EXPECT_TRUE(testModel.has_optional_int64()); + EXPECT_EQ(testModel.optional_int64(), 0xFF00CCDDA0001000); + // Change value. + testModel.set_optional_int64(0xFF00CCDD70002000); + EXPECT_TRUE(testModel.has_optional_int64()); + EXPECT_EQ(testModel.optional_int64(), 0xFF00CCDD70002000); + // Clear value. + testModel.clear_optional_int64(); + EXPECT_FALSE(testModel.has_optional_int64()); + EXPECT_EQ(testModel.optional_int64(), 0); + // Set after clear. + testModel.set_optional_int64(0xFF00CCDDA0001000); + EXPECT_TRUE(testModel.has_optional_int64()); + EXPECT_EQ(testModel.optional_int64(), 0xFF00CCDDA0001000); +} + +TEST(CppGeneratedCode, ScalarFloat) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + // Test defaults. + EXPECT_EQ(testModel.optional_float(), 0.0f); + EXPECT_FALSE(testModel.has_optional_float()); + EXPECT_EQ(std::numeric_limits::infinity(), + testModel.float_value_with_default()); + EXPECT_EQ(-std::numeric_limits::infinity(), + testModel.double_value_with_default()); + // Set value. + testModel.set_optional_float(3.14159265f); + EXPECT_TRUE(testModel.has_optional_float()); + EXPECT_NEAR(testModel.optional_float(), 3.14159265f, 1e-9f); + // Change value. + testModel.set_optional_float(-2.0f); + EXPECT_TRUE(testModel.has_optional_float()); + EXPECT_NEAR(testModel.optional_float(), -2, 1e-9f); + // Clear value. + testModel.clear_optional_float(); + EXPECT_FALSE(testModel.has_optional_float()); + EXPECT_EQ(testModel.optional_float(), 0.0f); + // Set after clear. + testModel.set_optional_float(3.14159265f); + EXPECT_TRUE(testModel.has_optional_float()); + EXPECT_NEAR(testModel.optional_float(), 3.14159265f, 1e-9f); +} + +TEST(CppGeneratedCode, ScalarDouble) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + // Test defaults. + EXPECT_EQ(testModel.optional_double(), 0.0); + EXPECT_FALSE(testModel.has_optional_double()); + // Set value. + testModel.set_optional_double(3.141592653589793); + EXPECT_TRUE(testModel.has_optional_double()); + EXPECT_NEAR(testModel.optional_double(), 3.141592653589793, 1e-16f); + // Change value. + testModel.set_optional_double(-1.0); + EXPECT_TRUE(testModel.has_optional_double()); + EXPECT_NEAR(testModel.optional_double(), -1.0, 1e-16f); + // Clear value. + testModel.clear_optional_double(); + EXPECT_FALSE(testModel.has_optional_double()); + EXPECT_EQ(testModel.optional_double(), 0.0f); + // Set after clear. + testModel.set_optional_double(3.141592653589793); + EXPECT_TRUE(testModel.has_optional_double()); + EXPECT_NEAR(testModel.optional_double(), 3.141592653589793, 1e-16f); +} + +TEST(CppGeneratedCode, Enums) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + + // Check enum default value. + EXPECT_EQ(TestModel_Category_IMAGES, 5); + + // Test defaults. + EXPECT_FALSE(testModel.has_category()); + EXPECT_EQ(testModel.category(), TestModel_Category_IMAGES); + // Set value. + testModel.set_category(TestModel_Category_NEWS); + EXPECT_TRUE(testModel.has_category()); + EXPECT_EQ(testModel.category(), TestModel_Category_NEWS); + // Change value. + testModel.set_category(TestModel_Category_VIDEO); + EXPECT_TRUE(testModel.has_category()); + EXPECT_EQ(testModel.category(), TestModel_Category_VIDEO); + // Clear value. + testModel.clear_category(); + EXPECT_FALSE(testModel.has_category()); + EXPECT_EQ(testModel.category(), TestModel_Category_IMAGES); + // Set after clear. + testModel.set_category(TestModel_Category_VIDEO); + EXPECT_TRUE(testModel.has_category()); + EXPECT_EQ(testModel.category(), TestModel_Category_VIDEO); +} + +TEST(CppGeneratedCode, FieldWithDefaultValue) { + ::protos::Arena arena; + auto testModel = ::protos::CreateMessage(arena); + + EXPECT_FALSE(testModel.has_int_value_with_default()); + EXPECT_EQ(testModel.int_value_with_default(), 65); + testModel.set_int_value_with_default(10); + EXPECT_EQ(testModel.int_value_with_default(), 10); + + EXPECT_FALSE(testModel.has_string_value_with_default()); + EXPECT_EQ(testModel.string_value_with_default(), "hello"); + testModel.set_string_value_with_default("new string"); + EXPECT_EQ(testModel.string_value_with_default(), "new string"); +} + +TEST(CppGeneratedCode, OneOfFields) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + + EXPECT_FALSE(test_model.has_oneof_member1()); + EXPECT_FALSE(test_model.has_oneof_member2()); + + test_model.set_oneof_member1("one of string"); + EXPECT_TRUE(test_model.has_oneof_member1()); + EXPECT_FALSE(test_model.has_oneof_member2()); + EXPECT_EQ(test_model.oneof_member1(), "one of string"); + + test_model.set_oneof_member2(true); + EXPECT_FALSE(test_model.has_oneof_member1()); + EXPECT_TRUE(test_model.has_oneof_member2()); + EXPECT_EQ(test_model.oneof_member2(), true); + + test_model.clear_oneof_member2(); + EXPECT_FALSE(test_model.has_oneof_member1()); + EXPECT_FALSE(test_model.has_oneof_member2()); + EXPECT_EQ(test_model.oneof_member1(), ""); + EXPECT_EQ(test_model.oneof_member2(), false); +} + +TEST(CppGeneratedCode, Messages) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(false, test_model.has_child_model_1()); + auto child_model = test_model.child_model_1(); + EXPECT_EQ(false, child_model->has_child_b1()); + EXPECT_EQ(false, child_model->child_b1()); + auto mutable_child = test_model.mutable_child_model_1(); + mutable_child->set_child_b1(true); + EXPECT_EQ(true, mutable_child->has_child_b1()); + EXPECT_EQ(true, mutable_child->child_b1()); + // The View should not change due to mutation since it + // is default_instance. + EXPECT_EQ(false, child_model->has_child_b1()); + // Readonly View should now show change. + child_model = test_model.child_model_1(); + EXPECT_EQ(true, child_model->has_child_b1()); + EXPECT_EQ(true, child_model->child_b1()); + // Clear message field. + EXPECT_EQ(true, test_model.has_child_model_1()); + test_model.clear_child_model_1(); + EXPECT_EQ(false, test_model.has_child_model_1()); +} + +TEST(CppGeneratedCode, NestedMessages) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + auto nested_child = test_model.nested_child_1(); + EXPECT_EQ(0, nested_child->nested_child_name().size()); + auto mutable_nested_child = test_model.mutable_nested_child_1(); + EXPECT_EQ(false, mutable_nested_child->has_nested_child_name()); + mutable_nested_child->set_nested_child_name(kTestStr1); + EXPECT_EQ(true, mutable_nested_child->has_nested_child_name()); +} + +TEST(CppGeneratedCode, RepeatedMessages) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(0, test_model.child_model_2_size()); + // Should be able to clear repeated field when empty. + test_model.clear_child_model_2(); + EXPECT_EQ(0, test_model.child_model_2_size()); + // Add 2 children. + auto new_child = test_model.add_child_model_2(); + EXPECT_EQ(true, new_child.ok()); + new_child.value()->set_child_str1(kTestStr1); + new_child = test_model.add_child_model_2(); + EXPECT_EQ(true, new_child.ok()); + new_child.value()->set_child_str1(kTestStr2); + EXPECT_EQ(2, test_model.child_model_2_size()); + // Mutable access. + auto mutable_first = test_model.mutable_child_model_2(0); + EXPECT_EQ(mutable_first->child_str1(), kTestStr1); + mutable_first->set_child_str1("change1"); + auto mutable_second = test_model.mutable_child_model_2(1); + EXPECT_EQ(mutable_second->child_str1(), kTestStr2); + mutable_second->set_child_str1("change2"); + // Check mutations using views. + auto view_first = test_model.child_model_2(0); + EXPECT_EQ(view_first->child_str1(), "change1"); + auto view_second = test_model.child_model_2(1); + EXPECT_EQ(view_second->child_str1(), "change2"); +} + +TEST(CppGeneratedCode, RepeatedScalar) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(0, test_model.value_array_size()); + // Should be able to clear repeated field when empty. + test_model.clear_value_array(); + EXPECT_EQ(0, test_model.value_array_size()); + // Add 2 children. + EXPECT_EQ(true, test_model.add_value_array(5)); + EXPECT_EQ(true, test_model.add_value_array(6)); + EXPECT_EQ(2, test_model.value_array_size()); + EXPECT_EQ(5, test_model.value_array(0)); + EXPECT_EQ(6, test_model.value_array(1)); + EXPECT_EQ(true, test_model.resize_value_array(3)); + EXPECT_EQ(3, test_model.value_array_size()); + test_model.set_value_array(2, 7); + EXPECT_EQ(5, test_model.value_array(0)); + EXPECT_EQ(6, test_model.value_array(1)); + EXPECT_EQ(7, test_model.value_array(2)); +} + +TEST(CppGeneratedCode, RepeatedStrings) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(0, test_model.repeated_string_size()); + // Should be able to clear repeated field when empty. + test_model.clear_repeated_string(); + EXPECT_EQ(0, test_model.repeated_string_size()); + // Add 2 children. + EXPECT_EQ(true, test_model.add_repeated_string("Hello")); + EXPECT_EQ(true, test_model.add_repeated_string("World")); + EXPECT_EQ(2, test_model.repeated_string_size()); + EXPECT_EQ("Hello", test_model.repeated_string(0)); + EXPECT_EQ("World", test_model.repeated_string(1)); + EXPECT_EQ(true, test_model.resize_repeated_string(3)); + EXPECT_EQ(3, test_model.repeated_string_size()); + test_model.set_repeated_string(2, "Test"); + EXPECT_EQ("Hello", test_model.repeated_string(0)); + EXPECT_EQ("World", test_model.repeated_string(1)); + EXPECT_EQ("Test", test_model.repeated_string(2)); +} + +TEST(CppGeneratedCode, MessageMapInt32KeyMessageValue) { + const int key_test_value = 3; + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(0, test_model.child_map_size()); + test_model.clear_child_map(); + EXPECT_EQ(0, test_model.child_map_size()); + auto child_model1 = ::protos::CreateMessage(arena); + child_model1.set_child_str1("abc"); + test_model.set_child_map(key_test_value, child_model1); + auto map_result = test_model.get_child_map(key_test_value); + EXPECT_EQ(true, map_result.ok()); + EXPECT_EQ("abc", map_result.value()->child_str1()); + test_model.delete_child_map(key_test_value); + auto map_result_after_delete = test_model.get_child_map(key_test_value); + EXPECT_EQ(false, map_result_after_delete.ok()); +} + +TEST(CppGeneratedCode, MessageMapStringKeyAndStringValue) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(0, test_model.str_to_str_map_size()); + test_model.clear_str_to_str_map(); + EXPECT_EQ(0, test_model.str_to_str_map_size()); + test_model.set_str_to_str_map("first", "abc"); + test_model.set_str_to_str_map("second", "def"); + auto result = test_model.get_str_to_str_map("second"); + EXPECT_EQ(true, result.ok()); + EXPECT_EQ("def", result.value()); + test_model.delete_str_to_str_map("first"); + auto result_after_delete = test_model.get_str_to_str_map("first"); + EXPECT_EQ(false, result_after_delete.ok()); +} + +TEST(CppGeneratedCode, MessageMapStringKeyAndInt32Value) { + ::protos::Arena arena; + auto test_model = ::protos::CreateMessage(arena); + EXPECT_EQ(0, test_model.str_to_int_map_size()); + test_model.clear_str_to_int_map(); + EXPECT_EQ(0, test_model.str_to_int_map_size()); + test_model.set_str_to_int_map("first", 10); + test_model.set_str_to_int_map("second", 20); + auto result = test_model.get_str_to_int_map("second"); + EXPECT_EQ(true, result.ok()); + EXPECT_EQ(20, result.value()); + test_model.delete_str_to_int_map("first"); + auto result_after_delete = test_model.get_str_to_int_map("first"); + EXPECT_EQ(false, result_after_delete.ok()); +} + +TEST(CppGeneratedCode, HasExtension) { + TestModel model; + EXPECT_EQ(false, ::protos::HasExtension(model, theme)); +} + +TEST(CppGeneratedCode, HasExtensionPtr) { + TestModel model; + EXPECT_EQ(false, ::protos::HasExtension(model.recursive_child(), theme)); +} + +TEST(CppGeneratedCode, ClearExtensionWithEmptyExtension) { + TestModel model; + EXPECT_EQ(false, ::protos::HasExtension(model, theme)); + ::protos::ClearExtension(model, theme); + EXPECT_EQ(false, ::protos::HasExtension(model, theme)); +} + +TEST(CppGeneratedCode, ClearExtensionWithEmptyExtensionPtr) { + TestModel model; + ::protos::Ptr recursive_child = model.mutable_recursive_child(); + ::protos::ClearExtension(recursive_child, theme); + EXPECT_EQ(false, ::protos::HasExtension(recursive_child, theme)); +} + +TEST(CppGeneratedCode, SetExtension) { + TestModel model; + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + EXPECT_EQ(false, ::protos::HasExtension(model, theme)); + EXPECT_EQ(true, ::protos::SetExtension(model, theme, extension1).ok()); + EXPECT_EQ(true, ::protos::HasExtension(model, theme)); +} + +TEST(CppGeneratedCode, SetExtensionOnMutableChild) { + TestModel model; + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + EXPECT_EQ(false, + ::protos::HasExtension(model.mutable_recursive_child(), theme)); + EXPECT_EQ(true, ::protos::SetExtension(model.mutable_recursive_child(), theme, + extension1) + .ok()); + EXPECT_EQ(true, + ::protos::HasExtension(model.mutable_recursive_child(), theme)); +} + +TEST(CppGeneratedCode, GetExtension) { + TestModel model; + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + EXPECT_EQ(false, ::protos::HasExtension(model, theme)); + EXPECT_EQ(true, ::protos::SetExtension(model, theme, extension1).ok()); + EXPECT_EQ("Hello World", + ::protos::GetExtension(model, theme).value()->ext_name()); +} + +TEST(CppGeneratedCode, GetExtensionOnMutableChild) { + TestModel model; + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + ::protos::Ptr mutable_recursive_child = + model.mutable_recursive_child(); + EXPECT_EQ(false, ::protos::HasExtension(mutable_recursive_child, theme)); + EXPECT_EQ( + true, + ::protos::SetExtension(mutable_recursive_child, theme, extension1).ok()); + EXPECT_EQ("Hello World", + ::protos::GetExtension(mutable_recursive_child, theme) + .value() + ->ext_name()); +} + +TEST(CppGeneratedCode, GetExtensionOnImmutableChild) { + TestModel model; + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + ::protos::Ptr mutable_recursive_child = + model.mutable_recursive_child(); + EXPECT_EQ(false, ::protos::HasExtension(mutable_recursive_child, theme)); + EXPECT_EQ( + true, + ::protos::SetExtension(mutable_recursive_child, theme, extension1).ok()); + ::protos::Ptr recursive_child = model.recursive_child(); + EXPECT_EQ("Hello World", + ::protos::GetExtension(recursive_child, theme).value()->ext_name()); +} + +TEST(CppGeneratedCode, SerializeUsingArena) { + TestModel model; + model.set_str1("Hello World"); + ::upb::Arena arena; + absl::StatusOr bytes = ::protos::Serialize(model, arena); + EXPECT_EQ(true, bytes.ok()); + TestModel parsed_model = ::protos::Parse(bytes.value()).value(); + EXPECT_EQ("Hello World", parsed_model.str1()); +} + +TEST(CppGeneratedCode, SerializeNestedMessageUsingArena) { + TestModel model; + model.mutable_recursive_child()->set_str1("Hello World"); + ::upb::Arena arena; + ::protos::Ptr child = model.recursive_child(); + absl::StatusOr bytes = ::protos::Serialize(child, arena); + EXPECT_EQ(true, bytes.ok()); + TestModel parsed_model = ::protos::Parse(bytes.value()).value(); + EXPECT_EQ("Hello World", parsed_model.str1()); +} + +TEST(CppGeneratedCode, Parse) { + TestModel model; + model.set_str1("Test123"); + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + EXPECT_EQ(true, ::protos::SetExtension(model, theme, extension1).ok()); + ::upb::Arena arena; + auto bytes = ::protos::Serialize(model, arena); + EXPECT_EQ(true, bytes.ok()); + TestModel parsed_model = ::protos::Parse(bytes.value()).value(); + EXPECT_EQ("Test123", parsed_model.str1()); + // Should not return an extension since we did not pass ExtensionRegistry. + EXPECT_EQ(false, ::protos::GetExtension(parsed_model, theme).ok()); +} + +TEST(CppGeneratedCode, ParseWithExtensionRegistry) { + TestModel model; + model.set_str1("Test123"); + ThemeExtension extension1; + extension1.set_ext_name("Hello World"); + EXPECT_EQ(true, ::protos::SetExtension(model, theme, extension1).ok()); + ::upb::Arena arena; + auto bytes = ::protos::Serialize(model, arena); + EXPECT_EQ(true, bytes.ok()); + ::protos::ExtensionRegistry extensions({&theme, &other_ext}, arena); + TestModel parsed_model = + ::protos::Parse(bytes.value(), extensions).value(); + EXPECT_EQ("Test123", parsed_model.str1()); + EXPECT_EQ(true, ::protos::GetExtension(parsed_model, theme).ok()); +} + +TEST(CppGeneratedCode, NameCollisions) { + TestModel model; + model.set_template_("test"); + EXPECT_EQ("test", model.template_()); + model.set_arena__("test"); + EXPECT_EQ("test", model.arena__()); +} diff --git a/protos_generator/tests/test_model.proto b/protos_generator/tests/test_model.proto new file mode 100644 index 0000000000..910915353b --- /dev/null +++ b/protos_generator/tests/test_model.proto @@ -0,0 +1,149 @@ +syntax = "proto2"; + +package protos_generator.test; + +import "protos_generator/tests/child_model.proto"; + +message TestModelContainer { + repeated TestModel models = 1; + optional ChildModel3 proto_3_child = 2; +} + +message TestModel { + optional int32 value = 1; + repeated int32 value_array = 2; // _UPB_MODE_ARRAY + repeated int32 value_packed_array = 3 + [packed = true]; // _UPB_MODE_ARRAY | _UPB_MODE_IS_PACKED + repeated int32 value_deprec = 4 [deprecated = true]; + optional string str1 = 115; + optional bool b1 = 9; + optional bool b2 = 10; + optional string str2 = 50; + optional string str3 = 11; + optional float optional_float = 14; + optional double optional_double = 15; + optional int64 optional_int64 = 16; + optional uint32 optional_uint32 = 17; + optional uint64 optional_uint64 = 18; + optional sint32 optional_sint32 = 19; + optional sint64 optional_sint64 = 20; + optional fixed32 optional_fixed32 = 21; + optional fixed64 optional_fixed64 = 22; + optional sfixed32 optional_sfixed32 = 23; + optional sfixed64 optional_sfixed64 = 24; + repeated int64 repeated_int64 = 25; + repeated uint64 repeated_uint64 = 26; + repeated fixed64 repeated_fixed64 = 27; + repeated sfixed64 repeated_sfixed64 = 28; + repeated bool repeated_bool = 29; + repeated string repeated_string = 35; + optional bytes optional_bytes = 36; + message NestedChild { + optional string nested_child_name = 211; + } + optional NestedChild nested_child_1 = 212; + optional ChildModel1 child_model_1 = 222; + repeated ChildModel1 child_model_2 = 223; + optional ChildModel1 bar = 224; + oneof child_oneof1 { + string oneof_member1 = 98; + bool oneof_member2 = 99; + } + optional int32 int_value_with_default = 31 + [default = 65]; // Not supported yet + optional string string_value_with_default = 32 + [default = "hello"]; // Not supported yet + optional float float_value_with_default = 33 [default = inf]; + optional float double_value_with_default = 34 [default = -inf]; + + map child_map = 225; + optional TestModel recursive_child = 226; + map child_str_map = 227; + map str_to_int_map = 228; + map str_to_str_map = 229; + + extend TestAnnotation { + optional OtherExtension in_message_ext = 15000; + } + + enum Category { + IMAGES = 5; + NEWS = 6; + VIDEO = 7; + RADIO = 8 [deprecated = true]; + } + optional Category category = 37; + + // keyword collisions (double, template, ...) + oneof type { + string string = 230; + int64 int64 = 231; + double double = 232; + } + optional string template = 233; + optional string msg = 234; + optional string arena = 235; + + // Tests publicly imported enum. + optional TestEnum imported_enum = 238; + + // TODO(243705098) accessor name collision with field name + // optional repeated string phase = 236; + // optional bool clear_phase = 237; + + extensions 10000 to max; +} + +// Old version with fewer fields to test backward/forward compatibility. +message TestModelContainerV1 { + repeated TestModelV1 models = 1; +} + +message TestModelV1 { + optional int32 value = 1; + repeated int32 value2 = 2; + repeated int32 value3 = 3 [packed = true]; + repeated int32 value4 = 4 [deprecated = true]; + optional bool b1 = 9; + optional bool b2 = 10; + optional string str2 = 50; +} + +enum PrimaryColors { + RED = 1; + GREEN = 2; + BLUE = 3; +} + +// TestModel extension. +message ThemeExtension { + optional string ext_name = 1; + optional bool ext_bool = 2; +} + +extend TestModel { + optional ThemeExtension theme = 12001; +} + +message OtherExtension { + optional string ext2_name = 1; +} + +extend TestModel { + optional OtherExtension other_ext = 12002; +} + +message TestAnnotation { + extensions 10000 to max; +} + +message TestMessageHasEnum { + optional EnumDeclaredAfterMessage enum_declared_after_message = 1; +} + +enum EnumDeclaredAfterMessage { + ZERO = 0; + ONE = 1; + TWO = 2; + THREE = 3; +} diff --git a/upbc/BUILD b/upbc/BUILD index a68057ded9..227d748440 100644 --- a/upbc/BUILD +++ b/upbc/BUILD @@ -75,7 +75,7 @@ cc_library( "common.h", ], copts = UPB_DEFAULT_CPPOPTS, - visibility = ["//third_party/upb/protos_generator:__pkg__"], + visibility = ["//protos_generator:__pkg__"], deps = [ "@com_google_absl//absl/strings", "@com_google_protobuf//:protobuf", @@ -91,7 +91,7 @@ cc_library( "file_layout.h", ], copts = UPB_DEFAULT_CPPOPTS, - visibility = ["//third_party/upb/protos_generator:__pkg__"], + visibility = ["//protos_generator:__pkg__"], deps = [ ":common", "//:mini_table", @@ -112,7 +112,7 @@ cc_library( "keywords.h", ], copts = UPB_DEFAULT_CPPOPTS, - visibility = ["//third_party/upb/protos_generator:__pkg__"], + visibility = ["//protos_generator:__pkg__"], ) cc_library( @@ -124,7 +124,7 @@ cc_library( "names.h", ], copts = UPB_DEFAULT_CPPOPTS, - visibility = ["//third_party/upb/protos_generator:__pkg__"], + visibility = ["//protos_generator:__pkg__"], deps = [ "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/strings", diff --git a/upbc/protoc-gen-upb.cc b/upbc/protoc-gen-upb.cc index 72ee8b2fb2..c97030aae0 100644 --- a/upbc/protoc-gen-upb.cc +++ b/upbc/protoc-gen-upb.cc @@ -141,7 +141,7 @@ std::string SizeLg2(const protobuf::FieldDescriptor* field) { case protobuf::FieldDescriptor::CPPTYPE_ENUM: return std::to_string(2); case protobuf::FieldDescriptor::CPPTYPE_BOOL: - return std::to_string(1); + return std::to_string(0); case protobuf::FieldDescriptor::CPPTYPE_FLOAT: return std::to_string(2); case protobuf::FieldDescriptor::CPPTYPE_INT32: