Merge branch 'main' into 25.x

pull/14617/head
Jie Luo 1 year ago committed by GitHub
commit a077f68b1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 44
      .github/workflows/janitor.yml
  2. 2
      .github/workflows/scorecard.yml
  3. 14
      .github/workflows/staleness_check.yml
  4. 10
      .github/workflows/staleness_refresh.yml
  5. 51
      .github/workflows/test_cpp.yml
  6. 1
      .github/workflows/test_python.yml
  7. 5
      .github/workflows/test_runner.yml
  8. 16
      .github/workflows/test_upb.yml
  9. 4
      .github/workflows/update_php_repo.yml
  10. 1
      BUILD.bazel
  11. 161
      Cargo.bazel.lock
  12. 8
      Cargo.lock
  13. 7
      WORKSPACE
  14. 2
      bazel/BUILD
  15. 6
      benchmarks/BUILD
  16. 21
      cmake/conformance.cmake
  17. 1
      cmake/tests.cmake
  18. 14
      conformance/BUILD.bazel
  19. 12
      conformance/bazel_conformance_test_runner.sh
  20. 1345
      conformance/binary_json_conformance_suite.cc
  21. 101
      conformance/binary_json_conformance_suite.h
  22. 8
      conformance/conformance_cpp.cc
  23. 86
      conformance/conformance_test.cc
  24. 18
      conformance/conformance_test.h
  25. 16
      conformance/conformance_test_runner.cc
  26. 3
      conformance/defs.bzl
  27. 33
      conformance/failure_list_cpp.txt
  28. 4
      conformance/failure_list_csharp.txt
  29. 6
      conformance/failure_list_java.txt
  30. 6
      conformance/failure_list_jruby.txt
  31. 277
      conformance/text_format_conformance_suite.cc
  32. 59
      conformance/text_format_conformance_suite.h
  33. 20
      conformance/text_format_failure_list_cpp.txt
  34. 25
      conformance/text_format_failure_list_python.txt
  35. 24
      conformance/text_format_failure_list_python_cpp.txt
  36. 3
      docs/design/editions/README.md
  37. 150
      docs/design/editions/editions-feature-extension-layout.md
  38. 144
      docs/design/editions/legacy-syntax-editions.md
  39. 139
      docs/design/editions/minimum-required-edition.md
  40. 5
      docs/options.md
  41. 7
      docs/third_party.md
  42. 377
      java/core/src/test/java/com/google/protobuf/GeneratedMessageTest.java
  43. 6
      php/ext/google/protobuf/php-upb.c
  44. 2
      php/ext/google/protobuf/php-upb.h
  45. 52
      php/src/Google/Protobuf/Internal/Message.php
  46. 13
      php/src/Google/Protobuf/Internal/RepeatedField.php
  47. 597
      php/tests/DebugInfoTest.php
  48. 4
      pkg/cc_dist_library.bzl
  49. 4
      protos_generator/tests/BUILD
  50. 2
      protos_generator/tests/test_generated.cc
  51. 1
      python/dist/BUILD.bazel
  52. 2
      python/google/protobuf/internal/api_implementation.py
  53. 67
      python/google/protobuf/internal/descriptor_test.py
  54. 4
      python/google/protobuf/internal/reflection_test.py
  55. 11
      python/google/protobuf/internal/text_format_test.py
  56. 11
      python/google/protobuf/text_encoding.py
  57. 6
      ruby/ext/google/protobuf_c/ruby-upb.c
  58. 2
      ruby/ext/google/protobuf_c/ruby-upb.h
  59. 9
      rust/BUILD
  60. 3
      rust/aspects.bzl
  61. 154
      rust/cpp.rs
  62. 3
      rust/cpp_kernel/BUILD
  63. 39
      rust/cpp_kernel/cpp_api.cc
  64. 18
      rust/internal.rs
  65. 107
      rust/primitive.rs
  66. 233
      rust/repeated.rs
  67. 4
      rust/shared.rs
  68. 2
      rust/test/cpp/interop/test_utils.cc
  69. 20
      rust/test/shared/BUILD
  70. 121
      rust/test/shared/accessors_proto3_test.rs
  71. 420
      rust/test/shared/accessors_test.rs
  72. 202
      rust/upb.rs
  73. 1
      rust/upb_kernel/BUILD
  74. 1
      rust/upb_kernel/upb_api.c
  75. 2
      rust/utf8.rs
  76. 55
      rust/vtable.rs
  77. 1
      src/BUILD.bazel
  78. 2
      src/file_lists.cmake
  79. 47
      src/google/protobuf/BUILD.bazel
  80. 25
      src/google/protobuf/any.pb.cc
  81. 6
      src/google/protobuf/any.pb.h
  82. 59
      src/google/protobuf/api.pb.cc
  83. 18
      src/google/protobuf/api.pb.h
  84. 35
      src/google/protobuf/arena.h
  85. 10
      src/google/protobuf/arena_unittest.cc
  86. 4
      src/google/protobuf/arenastring.h
  87. 1
      src/google/protobuf/arenaz_sampler_test.cc
  88. 28
      src/google/protobuf/compiler/BUILD.bazel
  89. 6
      src/google/protobuf/compiler/allowlists/BUILD.bazel
  90. 4
      src/google/protobuf/compiler/allowlists/allowlist.h
  91. 29
      src/google/protobuf/compiler/allowlists/weak_imports.cc
  92. 13
      src/google/protobuf/compiler/code_generator.cc
  93. 6
      src/google/protobuf/compiler/code_generator.h
  94. 18
      src/google/protobuf/compiler/code_generator_unittest.cc
  95. 76
      src/google/protobuf/compiler/command_line_interface.cc
  96. 4
      src/google/protobuf/compiler/command_line_interface.h
  97. 17
      src/google/protobuf/compiler/command_line_interface_unittest.cc
  98. 6
      src/google/protobuf/compiler/cpp/BUILD.bazel
  99. 1
      src/google/protobuf/compiler/cpp/enum.cc
  100. 14
      src/google/protobuf/compiler/cpp/field_generators/cord_field.cc
  101. Some files were not shown because too many files have changed in this diff Show More

@ -28,3 +28,47 @@ jobs:
echo "Closing #$pr..."
gh pr close --comment "Auto-closing Copybara pull request" --delete-branch "$pr"
done
stale-others:
name: Close stale non-copybara PRs and issues
runs-on: ubuntu-latest
steps:
- uses: actions/stale@b69b346013879cedbf50c69f572cd85439a41936
with:
stale-issue-message: >
We triage inactive PRs and issues in order to make it easier to find
active work. If this issue should remain active or becomes active
again, please add a comment.
This issue is labeled `inactive` because the last activity was over
90 days ago.
close-pr-message: >
We triage inactive PRs and issues in order to make it easier to find
active work. If this issue should remain active or becomes active
again, please reopen it.
This issue was closed and archived because there has been no new
activity in the 14 days since the `inactive` label was added.
stale-pr-message: >
We triage inactive PRs and issues in order to make it easier to find
active work. If this PR should remain active, please add a comment.
This PR is labeled `inactive` because the last activity was over 90
days ago. This PR will be closed and archived after 14 additional
days without activity.
close-pr-message: >
We triage inactive PRs and issues in order to make it easier to find
active work. If this PR should remain active or becomes active
again, please reopen it.
This PR was closed and archived because there has been no new
activity in the 14 days since the `inactive` label was added.
stale-issue-label: 'inactive'
stale-pr-label: 'inactive'
days-before-stale: 90
days-before-close: 14
operations-per-run: 100

@ -27,7 +27,7 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
persist-credentials: false

@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
branch: [main, 22.x, 23.x, 24.x]
branch: [main, 22.x, 23.x, 24.x, 25.x]
os: [{ name: Linux, value: ubuntu-latest}]
name: Test staleness ${{ matrix.os.name }} ${{ github.head_ref && 'PR' || matrix.branch }}
@ -49,10 +49,14 @@ jobs:
# In branches where automatic updates work as post-submits, we don't want to run staleness
# tests along with user changes. Any stale files will be automatically fixed in a follow-up
# commit.
run: |
uses: protocolbuffers/protobuf-ci/bazel@v2
with:
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
bazel-cache: staleness
bash: >
if [[ -z $COMMIT_TRIGGERED_RUN || -z $MAIN_RUN ]]; then
bazel query 'attr(tags, "staleness_test", //...)' | xargs bazel test $BAZEL_FLAGS || \
echo "Please run ./regenerate_stale_files.sh to regenerate stale files"
bazel query 'attr(tags, "staleness_test", //...)' | xargs bazel test $BAZEL_FLAGS ||
echo "Please run ./regenerate_stale_files.sh to regenerate stale files";
else
bazel query 'attr(tags, "staleness_test", //...)'
bazel query 'attr(tags, "staleness_test", //...)';
fi

@ -24,12 +24,16 @@ jobs:
fail-fast: false # Don't cancel all jobs if one fails.
steps:
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
- uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
# Note: this token has an expiration date, so if the workflow starts
# failing then you may need to generate a fresh token.
token: ${{ secrets.BOT_ACCESS_TOKEN }}
- name: Configure name and email address in Git
run: cd ${{ github.workspace }} && git config user.name "Protobuf Team Bot" && git config user.email "protobuf-team-bot@google.com"
run: git config user.name "Protobuf Team Bot" && git config user.email "protobuf-team-bot@google.com"
- name: Commit and push update
run: cd ${{ github.workspace }} && ./ci/push_auto_update.sh
uses: protocolbuffers/protobuf-ci/bazel@v2
with:
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
bazel-cache: staleness
bash: ci/push_auto_update.sh

@ -172,7 +172,7 @@ jobs:
image: us-docker.pkg.dev/protobuf-build/containers/test/linux/cmake:3.13.3-63dd26c0c7a808d92673a3e52e848189d4ab0f17
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
command: >-
/install.sh -DCMAKE_CXX_STANDARD=14 ${{ env.SCCACHE_CMAKE_FLAGS }} -Dprotobuf_USE_EXTERNAL_GTEST=ON -Dprotobuf_ABSL_PROVIDER=package \&\&
/install.sh -DCMAKE_CXX_STANDARD=14 ${{ env.SCCACHE_CMAKE_FLAGS }} -Dprotobuf_USE_EXTERNAL_GTEST=ON -Dprotobuf_ABSL_PROVIDER=package -Dprotobuf_BUILD_SHARED_LIBS=ON \&\&
/test.sh
${{ env.SCCACHE_CMAKE_FLAGS }}
-Dprotobuf_REMOVE_INSTALLED_HEADERS=ON
@ -324,8 +324,8 @@ jobs:
# for Apple Silicon to detect issues there.
bazel: build --cpu=darwin_arm64 //src/...
- name: Windows
os: windows-2019
cache_key: windows-2019
os: windows-2022
cache_key: windows-2022
bazel: test //src/... @com_google_protobuf_examples//... --test_tag_filters=-conformance --build_tag_filters=-conformance
name: ${{ matrix.name }} Bazel
runs-on: ${{ matrix.os }}
@ -351,14 +351,6 @@ jobs:
flags: -DCMAKE_CXX_STANDARD=14
cache-prefix: macos-cmake
- name: Windows CMake
os: windows-2019
flags: >-
-G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF
-Dprotobuf_BUILD_SHARED_LIBS=OFF
-Dprotobuf_BUILD_EXAMPLES=ON
vsversion: '2019'
cache-prefix: windows-2019-cmake
- name: Windows CMake 2022
os: windows-2022
flags: >-
-G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF
@ -366,29 +358,39 @@ jobs:
-Dprotobuf_BUILD_EXAMPLES=ON
vsversion: '2022'
cache-prefix: windows-2022-cmake
- name: Windows CMake 32-bit
- name: Windows CMake 2019
os: windows-2019
flags: >-
-G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF
-Dprotobuf_BUILD_SHARED_LIBS=OFF
-Dprotobuf_BUILD_EXAMPLES=ON
vsversion: '2019'
cache-prefix: windows-2019-cmake
# windows-2019 has python3.7 installed, which is incompatible with the latest gcloud
python-version: '3.8'
- name: Windows CMake 32-bit
os: windows-2022
flags: >-
-G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF
vsversion: '2022'
windows-arch: 'win32'
cache-prefix: windows-2019-win32-cmake
cache-prefix: windows-2022-win32-cmake
- name: Windows CMake Shared
os: windows-2019
os: windows-2022
flags: >-
-G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF
-Dprotobuf_BUILD_SHARED_LIBS=ON
vsversion: '2019'
cache-prefix: windows-2019-cmake
vsversion: '2022'
cache-prefix: windows-2022-cmake
- name: Windows CMake Install
os: windows-2019
os: windows-2022
install-flags: -G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF -Dprotobuf_BUILD_TESTS=OFF
flags: >-
-G Ninja -Dprotobuf_WITH_ZLIB=OFF -Dprotobuf_BUILD_CONFORMANCE=OFF
-Dprotobuf_REMOVE_INSTALLED_HEADERS=ON
-Dprotobuf_BUILD_PROTOBUF_BINARIES=OFF
vsversion: '2019'
cache-prefix: windows-2019-cmake
vsversion: '2022'
cache-prefix: windows-2022-cmake
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
steps:
@ -405,6 +407,17 @@ jobs:
arch: ${{ matrix.windows-arch || 'x64' }}
vsversion: ${{ matrix.vsversion }}
# Workaround for incompatibility between gcloud and windows-2019 runners.
- name: Install Python
if: ${{ matrix.python-version }}
uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
with:
python-version: ${{ matrix.python-version }}
- name: Use custom python for gcloud
if: ${{ matrix.python-version }}
run: echo "CLOUDSDK_PYTHON=${Python3_ROOT_DIR}\\python3" >> $GITHUB_ENV
shell: bash
- name: Setup sccache
uses: protocolbuffers/protobuf-ci/sccache@v2
with:

@ -76,6 +76,7 @@ jobs:
with:
python-version: ${{ matrix.version }}
cache: pip
cache-dependency-path: 'python/requirements.txt'
- name: Validate version
run: python3 --version | grep ${{ matrix.version }} || (echo "Invalid Python version - $(python3 --version)" && exit 1)

@ -12,9 +12,8 @@ name: Tests
on:
# continuous
schedule:
# TODO Reduce this frequency
# Run every 15 minutes for now to gather statistics
- cron: "*/15 * * * *"
# Run every hour
- cron: "0 * * * *"
# postsubmit
push:

@ -64,19 +64,23 @@ jobs:
strategy:
fail-fast: false # Don't cancel all jobs if one fails.
name: Windows
runs-on: windows-2019
runs-on: windows-2022
steps:
- name: Checkout pending changes
uses: protocolbuffers/protobuf-ci/checkout@v2
with:
ref: ${{ inputs.safe-checkout }}
- uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
with:
cache: pip
cache-dependency-path: 'python/requirements.txt'
- name: Run tests
uses: protocolbuffers/protobuf-ci/bazel@v2
with:
credentials: ${{ secrets.GAR_SERVICE_ACCOUNT }}
bazel-cache: "upb-bazel-windows"
bazel: test --cxxopt=/std:c++17 --host_cxxopt=/std:c++17 //upb/... //upb_generator/... //python/... //protos/... //protos_generator/...
exclude-targets: -//python:conformance_test -//upb:def_builder_test
exclude-targets: -//python:conformance_test -//upb/reflection:def_builder_test
macos:
strategy:
@ -92,6 +96,10 @@ jobs:
uses: protocolbuffers/protobuf-ci/checkout@v2
with:
ref: ${{ inputs.safe-checkout }}
- uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
with:
cache: pip
cache-dependency-path: 'python/requirements.txt'
- name: Run tests
uses: protocolbuffers/protobuf-ci/bazel@v2
with:
@ -195,7 +203,7 @@ jobs:
with:
name: requirements
path: requirements
- uses: actions/setup-python@v2
- uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
with:
python-version: ${{ matrix.python-version }}
architecture: ${{ matrix.architecture }}
@ -250,7 +258,7 @@ jobs:
path: wheels
- name: Delete Binary Wheels
run: find wheels -type f | grep -v none-any | xargs rm
- uses: actions/setup-python@v2
- uses: actions/setup-python@61a6322f88396a6271a6ee3565807d608ecaddd1 # v4.7.0
with:
python-version: ${{ matrix.python-version }}
- name: Setup Python venv

@ -15,12 +15,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout protobuf-php
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
repository: protocolbuffers/protobuf-php
token: ${{ secrets.BOT_ACCESS_TOKEN }}
- name: Clone protobuf
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with:
path: protobuf
- name: Configure Git Bot

@ -224,7 +224,6 @@ alias(
cc_library(
name = "protobuf",
copts = COPTS,
include_prefix = "google/protobuf/io",
linkopts = LINK_OPTS,
visibility = ["//visibility:public"],
deps = [

@ -1,5 +1,5 @@
{
"checksum": "8bc2d235f612e77f4dca1b6886cc8bd14df348168fea27a687805ed9518a8f1a",
"checksum": "f93f5d1848bc00c6384273f9fb5273cc1b7fc0cb4dbc2afd776d2feb7b37f3ae",
"crates": {
"aho-corasick 1.1.2": {
"name": "aho-corasick",
@ -108,6 +108,15 @@
"selects": {}
},
"edition": "2018",
"proc_macro_deps": {
"common": [
{
"id": "paste 1.0.14",
"target": "paste"
}
],
"selects": {}
},
"version": "0.0.1"
},
"license": null
@ -318,6 +327,59 @@
},
"license": "MIT OR Apache-2.0"
},
"paste 1.0.14": {
"name": "paste",
"version": "1.0.14",
"repository": {
"Http": {
"url": "https://crates.io/api/v1/crates/paste/1.0.14/download",
"sha256": "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
}
},
"targets": [
{
"ProcMacro": {
"crate_name": "paste",
"crate_root": "src/lib.rs",
"srcs": [
"**/*.rs"
]
}
},
{
"BuildScript": {
"crate_name": "build_script_build",
"crate_root": "build.rs",
"srcs": [
"**/*.rs"
]
}
}
],
"library_target_name": "paste",
"common_attrs": {
"compile_data_glob": [
"**"
],
"deps": {
"common": [
{
"id": "paste 1.0.14",
"target": "build_script_build"
}
],
"selects": {}
},
"edition": "2018",
"version": "1.0.14"
},
"build_script_attrs": {
"data_glob": [
"**"
]
},
"license": "MIT OR Apache-2.0"
},
"proc-macro2 1.0.69": {
"name": "proc-macro2",
"version": "1.0.69",
@ -767,6 +829,99 @@
"workspace_members": {
"direct-cargo-bazel-deps 0.0.1": ""
},
"conditions": {}
"conditions": {
"aarch64-apple-darwin": [
"aarch64-apple-darwin"
],
"aarch64-apple-ios": [
"aarch64-apple-ios"
],
"aarch64-apple-ios-sim": [
"aarch64-apple-ios-sim"
],
"aarch64-fuchsia": [
"aarch64-fuchsia"
],
"aarch64-linux-android": [
"aarch64-linux-android"
],
"aarch64-pc-windows-msvc": [
"aarch64-pc-windows-msvc"
],
"aarch64-unknown-linux-gnu": [
"aarch64-unknown-linux-gnu"
],
"arm-unknown-linux-gnueabi": [
"arm-unknown-linux-gnueabi"
],
"armv7-linux-androideabi": [
"armv7-linux-androideabi"
],
"armv7-unknown-linux-gnueabi": [
"armv7-unknown-linux-gnueabi"
],
"i686-apple-darwin": [
"i686-apple-darwin"
],
"i686-linux-android": [
"i686-linux-android"
],
"i686-pc-windows-msvc": [
"i686-pc-windows-msvc"
],
"i686-unknown-freebsd": [
"i686-unknown-freebsd"
],
"i686-unknown-linux-gnu": [
"i686-unknown-linux-gnu"
],
"powerpc-unknown-linux-gnu": [
"powerpc-unknown-linux-gnu"
],
"riscv32imc-unknown-none-elf": [
"riscv32imc-unknown-none-elf"
],
"riscv64gc-unknown-none-elf": [
"riscv64gc-unknown-none-elf"
],
"s390x-unknown-linux-gnu": [
"s390x-unknown-linux-gnu"
],
"thumbv7em-none-eabi": [
"thumbv7em-none-eabi"
],
"thumbv8m.main-none-eabi": [
"thumbv8m.main-none-eabi"
],
"wasm32-unknown-unknown": [
"wasm32-unknown-unknown"
],
"wasm32-wasi": [
"wasm32-wasi"
],
"x86_64-apple-darwin": [
"x86_64-apple-darwin"
],
"x86_64-apple-ios": [
"x86_64-apple-ios"
],
"x86_64-fuchsia": [
"x86_64-fuchsia"
],
"x86_64-linux-android": [
"x86_64-linux-android"
],
"x86_64-pc-windows-msvc": [
"x86_64-pc-windows-msvc"
],
"x86_64-unknown-freebsd": [
"x86_64-unknown-freebsd"
],
"x86_64-unknown-linux-gnu": [
"x86_64-unknown-linux-gnu"
],
"x86_64-unknown-none": [
"x86_64-unknown-none"
]
}
}

8
Cargo.lock generated

@ -22,6 +22,7 @@ name = "direct-cargo-bazel-deps"
version = "0.0.1"
dependencies = [
"googletest",
"paste",
]
[[package]]
@ -61,6 +62,12 @@ dependencies = [
"autocfg",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "proc-macro2"
version = "1.0.69"
@ -130,4 +137,3 @@ name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

@ -174,8 +174,8 @@ bind(
http_archive(
name = "rules_rust",
sha256 = "4a9cb4fda6ccd5b5ec393b2e944822a62e050c7c06f1ea41607f14c4fdec57a2",
urls = ["https://github.com/bazelbuild/rules_rust/releases/download/0.25.1/rules_rust-v0.25.1.tar.gz"],
sha256 = "9ecd0f2144f0a24e6bc71ebcc50a1ee5128cedeceb32187004532c9710cb2334",
urls = ["https://github.com/bazelbuild/rules_rust/releases/download/0.29.1/rules_rust-v0.29.1.tar.gz"],
)
load("@rules_rust//rust:repositories.bzl", "rules_rust_dependencies", "rust_register_toolchains")
@ -193,6 +193,9 @@ crates_repository(
"googletest": crate.spec(
version = ">0.0.0",
),
"paste": crate.spec(
version = ">=1",
),
},
)

@ -21,7 +21,7 @@ py_binary(
)
# py_proto_library() is private rule, only intended for internal use by upb.
# Hopefully py_proto_library() will eventually be availble in rules_proto or
# Hopefully py_proto_library() will eventually be available in rules_proto or
# another upstream package.
bzl_library(
name = "py_proto_library_bzl",

@ -13,7 +13,7 @@ load("@rules_python//python:defs.bzl", "py_binary")
load(
"//bazel:upb_proto_library.bzl",
"upb_proto_library",
"upb_c_proto_library",
"upb_proto_reflection_library",
)
load(
@ -35,7 +35,7 @@ proto_library(
srcs = ["descriptor.proto"],
)
upb_proto_library(
upb_c_proto_library(
name = "benchmark_descriptor_upb_proto",
deps = [":descriptor_proto"],
)
@ -154,7 +154,7 @@ proto_library(
)
[(
upb_proto_library(
upb_c_proto_library(
name = k + "_upb_proto",
deps = [":" + k + "_proto"],
),

@ -33,10 +33,20 @@ add_custom_command(
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.pb.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.pb.h
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.pb.cc
DEPENDS ${protobuf_PROTOC_EXE} ${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.proto
${protobuf_PROTOC_EXE} ${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.proto
COMMAND ${protobuf_PROTOC_EXE} ${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto3_editions.pb.h
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto3_editions.pb.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto2_editions.pb.h
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto2_editions.pb.cc
DEPENDS ${protobuf_PROTOC_EXE}
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto3_editions.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto2_editions.proto
COMMAND ${protobuf_PROTOC_EXE}
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto3_editions.proto
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto2_editions.proto
--proto_path=${protobuf_SOURCE_DIR}/src
--cpp_out=${protoc_cpp_args}${protobuf_SOURCE_DIR}/src
)
@ -48,6 +58,10 @@ add_library(libconformance_common ${protobuf_SHARED_OR_STATIC}
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto2.pb.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.pb.h
${protobuf_SOURCE_DIR}/src/google/protobuf/test_messages_proto3.pb.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto3_editions.pb.h
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto3_editions.pb.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto2_editions.pb.h
${protobuf_SOURCE_DIR}/src/google/protobuf/editions/golden/test_messages_proto2_editions.pb.cc
)
target_link_libraries(libconformance_common
${protobuf_LIB_PROTOBUF}
@ -100,6 +114,7 @@ add_test(NAME conformance_cpp_test
--failure_list ${protobuf_SOURCE_DIR}/conformance/failure_list_cpp.txt
--text_format_failure_list ${protobuf_SOURCE_DIR}/conformance/text_format_failure_list_cpp.txt
--output_dir ${protobuf_TEST_XML_OUTDIR}
--maximum_edition 2023
${CMAKE_CURRENT_BINARY_DIR}/conformance_cpp
DEPENDS conformance_test_runner conformance_cpp)

@ -62,6 +62,7 @@ set(tests_files
${protobuf_test_files}
${compiler_test_files}
${annotation_test_util_srcs}
${editions_test_files}
${io_test_files}
${util_test_files}
${stubs_test_files}

@ -130,6 +130,7 @@ internal_ruby_proto_library(
cc_library(
name = "conformance_test",
testonly = 1,
srcs = [
"conformance_test.cc",
"conformance_test_runner.cc",
@ -151,12 +152,16 @@ cc_library(
cc_library(
name = "binary_json_conformance_suite",
testonly = 1,
srcs = ["binary_json_conformance_suite.cc"],
hdrs = ["binary_json_conformance_suite.h"],
deps = [
":conformance_test",
":test_messages_proto2_proto_cc",
":test_messages_proto3_proto_cc",
"//src/google/protobuf/editions:test_messages_proto2_editions_cc_proto",
"//src/google/protobuf/editions:test_messages_proto3_editions_cc_proto",
"@com_google_absl//absl/log:die_if_null",
"@com_google_absl//absl/status",
"@jsoncpp",
],
@ -164,17 +169,24 @@ cc_library(
cc_library(
name = "text_format_conformance_suite",
testonly = 1,
srcs = ["text_format_conformance_suite.cc"],
hdrs = ["text_format_conformance_suite.h"],
deps = [
":conformance_test",
":test_messages_proto2_proto_cc",
":test_messages_proto3_proto_cc",
"//src/google/protobuf/editions:test_messages_proto2_editions_cc_proto",
"//src/google/protobuf/editions:test_messages_proto3_editions_cc_proto",
"@com_google_absl//absl/log:absl_log",
"@com_google_absl//absl/log:die_if_null",
"@com_google_absl//absl/strings",
],
)
cc_binary(
name = "conformance_test_runner",
testonly = 1,
srcs = ["conformance_test_main.cc"],
visibility = ["//visibility:public"],
deps = [
@ -195,6 +207,8 @@ cc_binary(
"//:protobuf",
"//:test_messages_proto2_cc_proto",
"//:test_messages_proto3_cc_proto",
"//src/google/protobuf/editions:test_messages_proto2_editions_cc_proto",
"//src/google/protobuf/editions:test_messages_proto3_editions_cc_proto",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
],

@ -28,9 +28,10 @@ else
fi
# --- end runfiles.bash initialization ---
TESTEE=unset
FAILURE_LIST=unset
TEXT_FORMAT_FAILURE_LIST=unset
TESTEE=
FAILURE_LIST=
TEXT_FORMAT_FAILURE_LIST=
MAXIMUM_EDITION=
while [[ -n "$@" ]]; do
arg="$1"; shift
@ -39,6 +40,7 @@ while [[ -n "$@" ]]; do
"--testee") TESTEE="$val" ;;
"--failure_list") FAILURE_LIST="$val" ;;
"--text_format_failure_list") TEXT_FORMAT_FAILURE_LIST="$val" ;;
"--maximum_edition") MAXIMUM_EDITION="$val" ;;
*) echo "Flag $arg is not recognized." && exit 1 ;;
esac
done
@ -57,4 +59,8 @@ if [ -n "$text_format_failure_list" ]; then
args+=(--text_format_failure_list $text_format_failure_list)
fi
if [ -n "$MAXIMUM_EDITION" ]; then
args+=(--maximum_edition $MAXIMUM_EDITION)
fi
$conformance_test_runner "${args[@]}" $conformance_testee

File diff suppressed because it is too large Load Diff

@ -8,19 +8,58 @@
#ifndef CONFORMANCE_BINARY_JSON_CONFORMANCE_SUITE_H
#define CONFORMANCE_BINARY_JSON_CONFORMANCE_SUITE_H
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "json/json.h"
#include "conformance_test.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/util/type_resolver.h"
namespace google {
namespace protobuf {
class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
public:
BinaryAndJsonConformanceSuite() {}
BinaryAndJsonConformanceSuite() = default;
private:
void RunSuiteImpl() override;
bool ParseJsonResponse(const conformance::ConformanceResponse& response,
Message* test_message);
bool ParseResponse(const conformance::ConformanceResponse& response,
const ConformanceRequestSetting& setting,
Message* test_message) override;
void SetTypeUrl(absl::string_view type_url) {
type_url_ = std::string(type_url);
}
template <typename MessageType>
friend class BinaryAndJsonConformanceSuiteImpl;
std::unique_ptr<google::protobuf::util::TypeResolver> type_resolver_;
std::string type_url_;
};
template <typename MessageType>
class BinaryAndJsonConformanceSuiteImpl {
public:
explicit BinaryAndJsonConformanceSuiteImpl(
BinaryAndJsonConformanceSuite* suite, bool run_proto3_tests);
private:
using ConformanceRequestSetting =
BinaryAndJsonConformanceSuite::ConformanceRequestSetting;
using ConformanceLevel = BinaryAndJsonConformanceSuite::ConformanceLevel;
constexpr static ConformanceLevel RECOMMENDED = ConformanceLevel::RECOMMENDED;
constexpr static ConformanceLevel REQUIRED = ConformanceLevel::REQUIRED;
void RunAllTests();
void RunBinaryPerformanceTests();
void RunJsonPerformanceTests();
void RunJsonTests();
@ -37,10 +76,6 @@ class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
void RunValidJsonTest(const std::string& test_name, ConformanceLevel level,
const std::string& input_json,
const std::string& equivalent_text_format);
void RunValidJsonTest(const std::string& test_name, ConformanceLevel level,
const std::string& input_json,
const std::string& equivalent_text_format,
bool is_proto3);
void RunValidJsonTestWithMessage(const std::string& test_name,
ConformanceLevel level,
const std::string& input_json,
@ -48,8 +83,7 @@ class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
const Message& prototype);
void RunValidJsonTestWithProtobufInput(
const std::string& test_name, ConformanceLevel level,
const protobuf_test_messages::proto3::TestAllTypesProto3& input,
const std::string& equivalent_text_format);
const MessageType& input, const std::string& equivalent_text_format);
void RunValidJsonIgnoreUnknownTest(const std::string& test_name,
ConformanceLevel level,
const std::string& input_json,
@ -57,38 +91,26 @@ class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
void RunValidProtobufTest(const std::string& test_name,
ConformanceLevel level,
const std::string& input_protobuf,
const std::string& equivalent_text_format,
bool is_proto3);
const std::string& equivalent_text_format);
void RunValidBinaryProtobufTest(const std::string& test_name,
ConformanceLevel level,
const std::string& input_protobuf,
bool is_proto3);
const std::string& input_protobuf);
void RunValidBinaryProtobufTest(const std::string& test_name,
ConformanceLevel level,
const std::string& input_protobuf,
const std::string& expected_protobuf,
bool is_proto3);
void RunBinaryPerformanceMergeMessageWithField(const std::string& test_name,
const std::string& field_proto,
bool is_proto3);
const std::string& expected_protobuf);
void RunBinaryPerformanceMergeMessageWithField(
const std::string& test_name, const std::string& field_proto);
void RunValidProtobufTestWithMessage(
const std::string& test_name, ConformanceLevel level,
const Message* input, const std::string& equivalent_text_format,
bool is_proto3);
bool ParseJsonResponse(const conformance::ConformanceResponse& response,
Message* test_message);
bool ParseResponse(const conformance::ConformanceResponse& response,
const ConformanceRequestSetting& setting,
Message* test_message) override;
const Message* input, const std::string& equivalent_text_format);
typedef std::function<bool(const Json::Value&)> Validator;
void RunValidJsonTestWithValidator(const std::string& test_name,
ConformanceLevel level,
const std::string& input_json,
const Validator& validator,
bool is_proto3);
const Validator& validator);
void ExpectParseFailureForJson(const std::string& test_name,
ConformanceLevel level,
const std::string& input_json);
@ -97,8 +119,7 @@ class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
const std::string& text_format);
void ExpectParseFailureForProtoWithProtoVersion(const std::string& proto,
const std::string& test_name,
ConformanceLevel level,
bool is_proto3);
ConformanceLevel level);
void ExpectParseFailureForProto(const std::string& proto,
const std::string& test_name,
ConformanceLevel level);
@ -107,10 +128,8 @@ class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
ConformanceLevel level);
void TestPrematureEOFForType(google::protobuf::FieldDescriptor::Type type);
void TestIllegalTags();
template <class MessageType>
void TestOneofMessage(MessageType& message, bool is_proto3);
template <class MessageType>
void TestUnknownMessage(MessageType& message, bool is_proto3);
void TestOneofMessage();
void TestUnknownMessage();
void TestValidDataForType(
google::protobuf::FieldDescriptor::Type,
std::vector<std::pair<std::string, std::string>> values);
@ -128,8 +147,22 @@ class BinaryAndJsonConformanceSuite : public ConformanceTestSuite {
void TestJsonPerformanceMergeMessageWithRepeatedFieldForType(
google::protobuf::FieldDescriptor::Type, std::string field_value);
std::unique_ptr<google::protobuf::util::TypeResolver> type_resolver_;
std::string type_url_;
enum class Packed {
kUnspecified = 0,
kTrue = 1,
kFalse = 2,
};
const FieldDescriptor* GetFieldForType(
FieldDescriptor::Type type, bool repeated,
Packed packed = Packed::kUnspecified) const;
const FieldDescriptor* GetFieldForMapType(
FieldDescriptor::Type key_type, FieldDescriptor::Type value_type) const;
const FieldDescriptor* GetFieldForOneofType(FieldDescriptor::Type type,
bool exclusive = false) const;
std::string SyntaxIdentifier() const;
BinaryAndJsonConformanceSuite& suite_;
bool run_proto3_tests_;
};
} // namespace protobuf

@ -21,6 +21,8 @@
#include "absl/status/statusor.h"
#include "conformance/conformance.pb.h"
#include "conformance/conformance.pb.h"
#include "google/protobuf/editions/golden/test_messages_proto2_editions.pb.h"
#include "google/protobuf/editions/golden/test_messages_proto3_editions.pb.h"
#include "google/protobuf/endian.h"
#include "google/protobuf/message.h"
#include "google/protobuf/test_messages_proto2.pb.h"
@ -45,6 +47,10 @@ using ::google::protobuf::util::NewTypeResolverForDescriptorPool;
using ::google::protobuf::util::TypeResolver;
using ::protobuf_test_messages::proto2::TestAllTypesProto2;
using ::protobuf_test_messages::proto3::TestAllTypesProto3;
using TestAllTypesProto2Editions =
::protobuf_test_messages::editions::proto2::TestAllTypesProto2;
using TestAllTypesProto3Editions =
::protobuf_test_messages::editions::proto3::TestAllTypesProto3;
absl::Status ReadFd(int fd, char* buf, size_t len) {
while (len > 0) {
@ -76,6 +82,8 @@ class Harness {
Harness() {
google::protobuf::LinkMessageReflection<TestAllTypesProto2>();
google::protobuf::LinkMessageReflection<TestAllTypesProto3>();
google::protobuf::LinkMessageReflection<TestAllTypesProto2Editions>();
google::protobuf::LinkMessageReflection<TestAllTypesProto3Editions>();
resolver_.reset(NewTypeResolverForDescriptorPool(
"type.googleapis.com", DescriptorPool::generated_pool()));

@ -9,12 +9,15 @@
#include <stdarg.h>
#include <cstddef>
#include <cstdint>
#include <fstream>
#include <memory>
#include <string>
#include "google/protobuf/util/field_comparator.h"
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/message_differencer.h"
#include "absl/log/absl_check.h"
#include "absl/log/absl_log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
@ -28,15 +31,14 @@
using conformance::ConformanceRequest;
using conformance::ConformanceResponse;
using conformance::WireFormat;
using google::protobuf::TextFormat;
using google::protobuf::util::DefaultFieldComparator;
using google::protobuf::util::MessageDifferencer;
using std::string;
namespace {
static string ToOctString(const string& binary_string) {
string oct_string;
static std::string ToOctString(const std::string& binary_string) {
std::string oct_string;
for (size_t i = 0; i < binary_string.size(); i++) {
uint8_t c = binary_string.at(i);
uint8_t high = c / 64;
@ -96,7 +98,7 @@ ConformanceTestSuite::ConformanceRequestSetting::ConformanceRequestSetting(
ConformanceLevel level, conformance::WireFormat input_format,
conformance::WireFormat output_format,
conformance::TestCategory test_category, const Message& prototype_message,
const string& test_name, const string& input)
const std::string& test_name, const std::string& input)
: level_(level),
input_format_(input_format),
output_format_(output_format),
@ -139,26 +141,37 @@ ConformanceTestSuite::ConformanceRequestSetting::NewTestMessage() const {
return std::unique_ptr<Message>(prototype_message_for_compare_->New());
}
string ConformanceTestSuite::ConformanceRequestSetting::GetTestName() const {
string rname;
std::string
ConformanceTestSuite::ConformanceRequestSetting::GetSyntaxIdentifier() const {
switch (FileDescriptorLegacy(prototype_message_.GetDescriptor()->file())
.syntax()) {
case FileDescriptorLegacy::Syntax::SYNTAX_PROTO3:
rname = ".Proto3.";
break;
return "Proto3";
case FileDescriptorLegacy::Syntax::SYNTAX_PROTO2:
rname = ".Proto2.";
break;
return "Proto2";
case FileDescriptorLegacy::Syntax::SYNTAX_EDITIONS: {
std::string id = "Editions";
if (prototype_message_.GetDescriptor()->name() == "TestAllTypesProto2") {
absl::StrAppend(&id, "_Proto2");
} else if (prototype_message_.GetDescriptor()->name() ==
"TestAllTypesProto3") {
absl::StrAppend(&id, "_Proto3");
}
return id;
}
default:
break;
return "Unknown";
}
}
return absl::StrCat(ConformanceLevelToString(level_), rname,
string ConformanceTestSuite::ConformanceRequestSetting::GetTestName() const {
return absl::StrCat(ConformanceLevelToString(level_), ".",
GetSyntaxIdentifier(), ".",
InputFormatString(input_format_), ".", test_name_, ".",
OutputFormatString(output_format_));
}
string
std::string
ConformanceTestSuite::ConformanceRequestSetting::ConformanceLevelToString(
ConformanceLevel level) const {
switch (level) {
@ -171,7 +184,7 @@ ConformanceTestSuite::ConformanceRequestSetting::ConformanceLevelToString(
return "";
}
string ConformanceTestSuite::ConformanceRequestSetting::InputFormatString(
std::string ConformanceTestSuite::ConformanceRequestSetting::InputFormatString(
conformance::WireFormat format) const {
switch (format) {
case conformance::PROTOBUF:
@ -186,7 +199,7 @@ string ConformanceTestSuite::ConformanceRequestSetting::InputFormatString(
return "";
}
string ConformanceTestSuite::ConformanceRequestSetting::OutputFormatString(
std::string ConformanceTestSuite::ConformanceRequestSetting::OutputFormatString(
conformance::WireFormat format) const {
switch (format) {
case conformance::PROTOBUF:
@ -208,7 +221,7 @@ void ConformanceTestSuite::TruncateDebugPayload(string* payload) {
}
}
const ConformanceRequest ConformanceTestSuite::TruncateRequest(
ConformanceRequest ConformanceTestSuite::TruncateRequest(
const ConformanceRequest& request) {
ConformanceRequest debug_request(request);
switch (debug_request.payload_case()) {
@ -231,7 +244,7 @@ const ConformanceRequest ConformanceTestSuite::TruncateRequest(
return debug_request;
}
const ConformanceResponse ConformanceTestSuite::TruncateResponse(
ConformanceResponse ConformanceTestSuite::TruncateResponse(
const ConformanceResponse& response) {
ConformanceResponse debug_response(response);
switch (debug_response.result_case()) {
@ -254,7 +267,7 @@ const ConformanceResponse ConformanceTestSuite::TruncateResponse(
return debug_response;
}
void ConformanceTestSuite::ReportSuccess(const string& test_name) {
void ConformanceTestSuite::ReportSuccess(const std::string& test_name) {
if (expected_to_fail_.erase(test_name) != 0) {
absl::StrAppendFormat(
&output_,
@ -266,7 +279,7 @@ void ConformanceTestSuite::ReportSuccess(const string& test_name) {
successes_++;
}
void ConformanceTestSuite::ReportFailure(const string& test_name,
void ConformanceTestSuite::ReportFailure(const std::string& test_name,
ConformanceLevel level,
const ConformanceRequest& request,
const ConformanceResponse& response,
@ -286,7 +299,7 @@ void ConformanceTestSuite::ReportFailure(const string& test_name,
TruncateResponse(response).ShortDebugString());
}
void ConformanceTestSuite::ReportSkip(const string& test_name,
void ConformanceTestSuite::ReportSkip(const std::string& test_name,
const ConformanceRequest& request,
const ConformanceResponse& response) {
if (verbose_) {
@ -299,19 +312,20 @@ void ConformanceTestSuite::ReportSkip(const string& test_name,
void ConformanceTestSuite::RunValidInputTest(
const ConformanceRequestSetting& setting,
const string& equivalent_text_format) {
const std::string& equivalent_text_format) {
std::unique_ptr<Message> reference_message(setting.NewTestMessage());
ABSL_CHECK(TextFormat::ParseFromString(equivalent_text_format,
reference_message.get()))
<< "Failed to parse data for test case: " << setting.GetTestName()
<< ", data: " << equivalent_text_format;
const string equivalent_wire_format = reference_message->SerializeAsString();
const std::string equivalent_wire_format =
reference_message->SerializeAsString();
RunValidBinaryInputTest(setting, equivalent_wire_format);
}
void ConformanceTestSuite::RunValidBinaryInputTest(
const ConformanceRequestSetting& setting,
const string& equivalent_wire_format, bool require_same_wire_format) {
const std::string& equivalent_wire_format, bool require_same_wire_format) {
const ConformanceRequest& request = setting.GetRequest();
ConformanceResponse response;
RunTest(setting.GetTestName(), request, &response);
@ -321,11 +335,12 @@ void ConformanceTestSuite::RunValidBinaryInputTest(
void ConformanceTestSuite::VerifyResponse(
const ConformanceRequestSetting& setting,
const string& equivalent_wire_format, const ConformanceResponse& response,
bool need_report_success, bool require_same_wire_format) {
const std::string& equivalent_wire_format,
const ConformanceResponse& response, bool need_report_success,
bool require_same_wire_format) {
std::unique_ptr<Message> test_message(setting.NewTestMessage());
const ConformanceRequest& request = setting.GetRequest();
const string& test_name = setting.GetTestName();
const std::string& test_name = setting.GetTestName();
ConformanceLevel level = setting.GetLevel();
std::unique_ptr<Message> reference_message = setting.NewTestMessage();
@ -358,7 +373,7 @@ void ConformanceTestSuite::VerifyResponse(
DefaultFieldComparator field_comparator;
field_comparator.set_treat_nan_as_equal(true);
differencer.set_field_comparator(&field_comparator);
string differences;
std::string differences;
differencer.ReportDifferencesToString(&differences);
bool check = false;
@ -366,7 +381,7 @@ void ConformanceTestSuite::VerifyResponse(
if (require_same_wire_format) {
ABSL_DCHECK_EQ(response.result_case(),
ConformanceResponse::kProtobufPayload);
const string& protobuf_payload = response.protobuf_payload();
const std::string& protobuf_payload = response.protobuf_payload();
check = equivalent_wire_format == protobuf_payload;
differences = absl::StrCat("Expect: ", ToOctString(equivalent_wire_format),
", but got: ", ToOctString(protobuf_payload));
@ -386,15 +401,15 @@ void ConformanceTestSuite::VerifyResponse(
}
}
void ConformanceTestSuite::RunTest(const string& test_name,
void ConformanceTestSuite::RunTest(const std::string& test_name,
const ConformanceRequest& request,
ConformanceResponse* response) {
if (test_names_.insert(test_name).second == false) {
ABSL_LOG(FATAL) << "Duplicated test name: " << test_name;
}
string serialized_request;
string serialized_response;
std::string serialized_request;
std::string serialized_response;
request.SerializeToString(&serialized_request);
runner_->RunTest(test_name, serialized_request, &serialized_response);
@ -412,7 +427,7 @@ void ConformanceTestSuite::RunTest(const string& test_name,
}
}
string ConformanceTestSuite::WireFormatToString(WireFormat wire_format) {
std::string ConformanceTestSuite::WireFormatToString(WireFormat wire_format) {
switch (wire_format) {
case conformance::PROTOBUF:
return "PROTOBUF";
@ -435,7 +450,8 @@ void ConformanceTestSuite::AddExpectedFailedTest(const std::string& test_name) {
}
bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
std::string* output, const string& filename,
std::string* output,
const std::string& filename,
conformance::FailureSet* failure_list) {
runner_ = runner;
successes_ = 0;
@ -449,7 +465,7 @@ bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
failure_list_filename_ = filename;
expected_to_fail_.clear();
for (const string& failure : failure_list->failure()) {
for (const std::string& failure : failure_list->failure()) {
AddExpectedFailedTest(failure);
}
RunSuiteImpl();

@ -14,13 +14,16 @@
#ifndef CONFORMANCE_CONFORMANCE_TEST_H
#define CONFORMANCE_CONFORMANCE_TEST_H
#include <functional>
#include <cstddef>
#include <memory>
#include <string>
#include <vector>
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/util/type_resolver.h"
#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_set.h"
#include "absl/strings/string_view.h"
#include "conformance/conformance.pb.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/wire_format_lite.h"
@ -129,6 +132,7 @@ class ConformanceTestSuite {
: verbose_(false),
performance_(false),
enforce_recommended_(false),
maximum_edition_(Edition::EDITION_PROTO3),
failure_list_flag_name_("--failure_list") {}
virtual ~ConformanceTestSuite() {}
@ -145,6 +149,9 @@ class ConformanceTestSuite {
// difference between REQUIRED and RECOMMENDED test cases.
void SetEnforceRecommended(bool value) { enforce_recommended_ = value; }
// Sets the maximum edition (inclusive) that should be tests for conformance.
void SetMaximumEdition(Edition edition) { maximum_edition_ = edition; }
// Gets the flag name to the failure list file.
// By default, this would return --failure_list
std::string GetFailureListFlagName() { return failure_list_flag_name_; }
@ -198,13 +205,15 @@ class ConformanceTestSuite {
std::unique_ptr<Message> NewTestMessage() const;
std::string GetSyntaxIdentifier() const;
std::string GetTestName() const;
const conformance::ConformanceRequest& GetRequest() const {
return request_;
}
const ConformanceLevel GetLevel() const { return level_; }
ConformanceLevel GetLevel() const { return level_; }
std::string ConformanceLevelToString(ConformanceLevel level) const;
@ -245,9 +254,9 @@ class ConformanceTestSuite {
bool need_report_success, bool require_same_wire_format);
void TruncateDebugPayload(std::string* payload);
const conformance::ConformanceRequest TruncateRequest(
conformance::ConformanceRequest TruncateRequest(
const conformance::ConformanceRequest& request);
const conformance::ConformanceResponse TruncateResponse(
conformance::ConformanceResponse TruncateResponse(
const conformance::ConformanceResponse& response);
void ReportSuccess(const std::string& test_name);
@ -279,6 +288,7 @@ class ConformanceTestSuite {
bool verbose_;
bool performance_;
bool enforce_recommended_;
Edition maximum_edition_;
std::string output_;
std::string output_dir_;
std::string failure_list_flag_name_;

@ -37,12 +37,15 @@
#include <unistd.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <future>
#include <vector>
#include "absl/log/absl_log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "conformance/conformance.pb.h"
#include "conformance_test.h"
@ -119,6 +122,11 @@ void UsageError() {
fprintf(stderr,
" strictly conforming to protobuf\n");
fprintf(stderr, " spec.\n");
fprintf(stderr,
" --maximum edition Only run conformance tests up to \n");
fprintf(stderr,
" and including the specified\n");
fprintf(stderr, " edition.\n");
fprintf(stderr,
" --output_dir <dirname> Directory to write\n"
" output files.\n");
@ -200,6 +208,14 @@ int ForkPipeRunner::Run(int argc, char *argv[],
suite->SetVerbose(true);
} else if (strcmp(argv[arg], "--enforce_recommended") == 0) {
suite->SetEnforceRecommended(true);
} else if (strcmp(argv[arg], "--maximum_edition") == 0) {
if (++arg == argc) UsageError();
Edition edition = EDITION_UNKNOWN;
if (!Edition_Parse(absl::StrCat("EDITION_", argv[arg]), &edition)) {
fprintf(stderr, "Unknown edition: %s\n", argv[arg]);
UsageError();
}
suite->SetMaximumEdition(edition);
} else if (strcmp(argv[arg], "--output_dir") == 0) {
if (++arg == argc) UsageError();
suite->SetOutputDir(argv[arg]);

@ -8,6 +8,7 @@ def conformance_test(
testee,
failure_list = None,
text_format_failure_list = None,
maximum_edition = None,
**kwargs):
"""Conformance test runner.
@ -27,6 +28,8 @@ def conformance_test(
if text_format_failure_list:
args = args + ["--text_format_failure_list %s" % _strip_bazel(text_format_failure_list)]
failure_lists = failure_lists + [text_format_failure_list]
if maximum_edition:
args = args + ["--maximum_edition %s" % maximum_edition]
native.sh_test(
name = name,

@ -7,30 +7,57 @@
# TODO: insert links to corresponding bugs tracking the issue.
# Should we use GitHub issues or the Google-internal bug tracker?
Recommended.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Proto3.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.Editions_Proto3.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.Proto3.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.Editions_Proto3.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.Proto3.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Editions_Proto3.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Proto3.JsonInput.BoolFieldDoubleQuotedFalse
Recommended.Editions_Proto3.JsonInput.BoolFieldDoubleQuotedFalse
Recommended.Proto3.JsonInput.BoolFieldDoubleQuotedTrue
Recommended.Editions_Proto3.JsonInput.BoolFieldDoubleQuotedTrue
Recommended.Proto3.JsonInput.FieldMaskInvalidCharacter
Recommended.Editions_Proto3.JsonInput.FieldMaskInvalidCharacter
Recommended.Proto3.JsonInput.FieldNameDuplicate
Recommended.Editions_Proto3.JsonInput.FieldNameDuplicate
Recommended.Proto3.JsonInput.FieldNameDuplicateDifferentCasing1
Recommended.Editions_Proto3.JsonInput.FieldNameDuplicateDifferentCasing1
Recommended.Proto3.JsonInput.FieldNameDuplicateDifferentCasing2
Recommended.Editions_Proto3.JsonInput.FieldNameDuplicateDifferentCasing2
Recommended.Proto3.JsonInput.FieldNameNotQuoted
Recommended.Editions_Proto3.JsonInput.FieldNameNotQuoted
Recommended.Proto3.JsonInput.IgnoreUnknownEnumStringValueInMapValue.ProtobufOutput
Recommended.Editions_Proto3.JsonInput.IgnoreUnknownEnumStringValueInMapValue.ProtobufOutput
Recommended.Proto3.JsonInput.MapFieldValueIsNull
Recommended.Editions_Proto3.JsonInput.MapFieldValueIsNull
Recommended.Proto3.JsonInput.RepeatedFieldMessageElementIsNull
Recommended.Editions_Proto3.JsonInput.RepeatedFieldMessageElementIsNull
Recommended.Proto3.JsonInput.RepeatedFieldPrimitiveElementIsNull
Recommended.Editions_Proto3.JsonInput.RepeatedFieldPrimitiveElementIsNull
Recommended.Proto3.JsonInput.RepeatedFieldTrailingComma
Recommended.Editions_Proto3.JsonInput.RepeatedFieldTrailingComma
Recommended.Proto3.JsonInput.RepeatedFieldTrailingCommaWithNewlines
Recommended.Editions_Proto3.JsonInput.RepeatedFieldTrailingCommaWithNewlines
Recommended.Proto3.JsonInput.RepeatedFieldTrailingCommaWithSpace
Recommended.Editions_Proto3.JsonInput.RepeatedFieldTrailingCommaWithSpace
Recommended.Proto3.JsonInput.RepeatedFieldTrailingCommaWithSpaceCommaSpace
Recommended.Editions_Proto3.JsonInput.RepeatedFieldTrailingCommaWithSpaceCommaSpace
Recommended.Proto3.JsonInput.StringFieldSingleQuoteBoth
Recommended.Editions_Proto3.JsonInput.StringFieldSingleQuoteBoth
Recommended.Proto3.JsonInput.StringFieldSingleQuoteKey
Recommended.Editions_Proto3.JsonInput.StringFieldSingleQuoteKey
Recommended.Proto3.JsonInput.StringFieldSingleQuoteValue
Recommended.Editions_Proto3.JsonInput.StringFieldSingleQuoteValue
Recommended.Proto3.JsonInput.StringFieldUppercaseEscapeLetter
Recommended.Editions_Proto3.JsonInput.StringFieldUppercaseEscapeLetter
Recommended.Proto3.JsonInput.TrailingCommaInAnObject
Recommended.Editions_Proto3.JsonInput.TrailingCommaInAnObject
Recommended.Proto3.JsonInput.TrailingCommaInAnObjectWithNewlines
Recommended.Editions_Proto3.JsonInput.TrailingCommaInAnObjectWithNewlines
Recommended.Proto3.JsonInput.TrailingCommaInAnObjectWithSpace
Recommended.Editions_Proto3.JsonInput.TrailingCommaInAnObjectWithSpace
Recommended.Proto3.JsonInput.TrailingCommaInAnObjectWithSpaceCommaSpace
Recommended.Editions_Proto3.JsonInput.TrailingCommaInAnObjectWithSpaceCommaSpace
Recommended.Proto2.JsonInput.FieldNameExtension.Validator
Recommended.Editions_Proto2.JsonInput.FieldNameExtension.Validator

@ -5,5 +5,5 @@ Required.Proto3.JsonInput.OneofFieldNullFirst.JsonOutput
Required.Proto3.JsonInput.OneofFieldNullFirst.ProtobufOutput
Required.Proto3.JsonInput.OneofFieldNullSecond.JsonOutput
Required.Proto3.JsonInput.OneofFieldNullSecond.ProtobufOutput
Recommended.ValueRejectInfNumberValue.JsonOutput
Recommended.ValueRejectNanNumberValue.JsonOutput
Recommended.Proto3.ValueRejectInfNumberValue.JsonOutput
Recommended.Proto3.ValueRejectNanNumberValue.JsonOutput

@ -4,9 +4,9 @@
# By listing them here we can keep tabs on which ones are failing and be sure
# that we don't introduce regressions in other tests.
Recommended.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Proto3.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.Proto3.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.Proto3.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Proto3.JsonInput.BoolFieldAllCapitalFalse
Recommended.Proto3.JsonInput.BoolFieldAllCapitalTrue
Recommended.Proto3.JsonInput.BoolFieldCamelCaseFalse

@ -1,6 +1,6 @@
Recommended.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Proto3.FieldMaskNumbersDontRoundTrip.JsonOutput
Recommended.Proto3.FieldMaskPathsDontRoundTrip.JsonOutput
Recommended.Proto3.FieldMaskTooManyUnderscore.JsonOutput
Recommended.Proto2.JsonInput.FieldNameExtension.Validator
Recommended.Proto3.JsonInput.BoolFieldAllCapitalFalse
Recommended.Proto3.JsonInput.BoolFieldAllCapitalTrue

@ -7,25 +7,30 @@
#include "text_format_conformance_suite.h"
#include "google/protobuf/any.pb.h"
#include <cstddef>
#include <string>
#include <vector>
#include "absl/log/absl_log.h"
#include "absl/log/die_if_null.h"
#include "absl/strings/str_cat.h"
#include "conformance_test.h"
#include "google/protobuf/editions/golden/test_messages_proto2_editions.pb.h"
#include "google/protobuf/editions/golden/test_messages_proto3_editions.pb.h"
#include "google/protobuf/test_messages_proto2.pb.h"
#include "google/protobuf/test_messages_proto3.pb.h"
#include "google/protobuf/text_format.h"
namespace proto2_messages = protobuf_test_messages::proto2;
using conformance::ConformanceRequest;
using conformance::ConformanceResponse;
using conformance::WireFormat;
using google::protobuf::Message;
using google::protobuf::TextFormat;
using proto2_messages::TestAllTypesProto2;
using proto2_messages::UnknownToTestAllTypes;
using protobuf_test_messages::proto2::TestAllTypesProto2;
using protobuf_test_messages::proto2::UnknownToTestAllTypes;
using protobuf_test_messages::proto3::TestAllTypesProto3;
using std::string;
using TestAllTypesProto2Editions =
protobuf_test_messages::editions::proto2::TestAllTypesProto2;
using TestAllTypesProto3Editions =
protobuf_test_messages::editions::proto3::TestAllTypesProto3;
namespace google {
namespace protobuf {
@ -61,7 +66,7 @@ bool TextFormatConformanceTestSuite::ParseResponse(
const ConformanceRequestSetting& setting, Message* test_message) {
const ConformanceRequest& request = setting.GetRequest();
WireFormat requested_output = request.requested_output_format();
const string& test_name = setting.GetTestName();
const std::string& test_name = setting.GetTestName();
ConformanceLevel level = setting.GetLevel();
switch (response.result_case()) {
@ -111,10 +116,39 @@ bool TextFormatConformanceTestSuite::ParseResponse(
return true;
}
void TextFormatConformanceTestSuite::ExpectParseFailure(const string& test_name,
ConformanceLevel level,
const string& input) {
TestAllTypesProto3 prototype;
void TextFormatConformanceTestSuite::RunSuiteImpl() {
TextFormatConformanceTestSuiteImpl<TestAllTypesProto2>(this);
TextFormatConformanceTestSuiteImpl<TestAllTypesProto3>(this);
if (maximum_edition_ >= Edition::EDITION_2023) {
TextFormatConformanceTestSuiteImpl<TestAllTypesProto2Editions>(this);
TextFormatConformanceTestSuiteImpl<TestAllTypesProto3Editions>(this);
}
}
template <typename MessageType>
TextFormatConformanceTestSuiteImpl<MessageType>::
TextFormatConformanceTestSuiteImpl(TextFormatConformanceTestSuite* suite)
: suite_(*ABSL_DIE_IF_NULL(suite)) {
// Flag control performance tests to keep them internal and opt-in only
if (suite_.performance_) {
RunTextFormatPerformanceTests();
} else {
if (MessageType::GetDescriptor()->name() == "TestAllTypesProto2") {
RunGroupTests();
}
if (MessageType::GetDescriptor()->name() == "TestAllTypesProto3") {
RunAnyTests();
// TODO Run these over proto2 also.
RunAllTests();
}
}
}
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::ExpectParseFailure(
const std::string& test_name, ConformanceLevel level,
const std::string& input) {
MessageType prototype;
// We don't expect output, but if the program erroneously accepts the protobuf
// we let it send its response as this. We must not leave it unspecified.
ConformanceRequestSetting setting(
@ -122,86 +156,75 @@ void TextFormatConformanceTestSuite::ExpectParseFailure(const string& test_name,
conformance::TEXT_FORMAT_TEST, prototype, test_name, input);
const ConformanceRequest& request = setting.GetRequest();
ConformanceResponse response;
string effective_test_name =
absl::StrCat(setting.ConformanceLevelToString(level),
".Proto3.TextFormatInput.", test_name);
std::string effective_test_name = absl::StrCat(
setting.ConformanceLevelToString(level), ".",
setting.GetSyntaxIdentifier(), ".TextFormatInput.", test_name);
RunTest(effective_test_name, request, &response);
suite_.RunTest(effective_test_name, request, &response);
if (response.result_case() == ConformanceResponse::kParseError) {
ReportSuccess(effective_test_name);
suite_.ReportSuccess(effective_test_name);
} else if (response.result_case() == ConformanceResponse::kSkipped) {
ReportSkip(effective_test_name, request, response);
suite_.ReportSkip(effective_test_name, request, response);
} else {
ReportFailure(effective_test_name, level, request, response,
suite_.ReportFailure(effective_test_name, level, request, response,
"Should have failed to parse, but didn't.");
}
}
void TextFormatConformanceTestSuite::RunValidTextFormatTest(
const string& test_name, ConformanceLevel level, const string& input_text) {
TestAllTypesProto3 prototype;
RunValidTextFormatTestWithMessage(test_name, level, input_text, prototype);
}
void TextFormatConformanceTestSuite::RunValidTextFormatTestProto2(
const string& test_name, ConformanceLevel level, const string& input_text) {
TestAllTypesProto2 prototype;
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::RunValidTextFormatTest(
const std::string& test_name, ConformanceLevel level,
const std::string& input_text) {
MessageType prototype;
RunValidTextFormatTestWithMessage(test_name, level, input_text, prototype);
}
void TextFormatConformanceTestSuite::RunValidTextFormatTestWithExpected(
const string& test_name, ConformanceLevel level, const string& input_text,
const string& expected_text) {
TestAllTypesProto3 prototype;
RunValidTextFormatTestWithMessage(test_name, level, input_text, expected_text,
prototype);
}
void TextFormatConformanceTestSuite::RunValidTextFormatTestProto2WithExpected(
const string& test_name, ConformanceLevel level, const string& input_text,
const string& expected_text) {
TestAllTypesProto2 prototype;
RunValidTextFormatTestWithMessage(test_name, level, input_text, expected_text,
prototype);
}
void TextFormatConformanceTestSuite::RunValidTextFormatTestWithMessage(
const string& test_name, ConformanceLevel level, const string& input_text,
const Message& prototype) {
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::
RunValidTextFormatTestWithMessage(const std::string& test_name,
ConformanceLevel level,
const std::string& input_text,
const Message& message) {
ConformanceRequestSetting setting1(
level, conformance::TEXT_FORMAT, conformance::PROTOBUF,
conformance::TEXT_FORMAT_TEST, prototype, test_name, input_text);
RunValidInputTest(setting1, input_text);
conformance::TEXT_FORMAT_TEST, message, test_name, input_text);
suite_.RunValidInputTest(setting1, input_text);
ConformanceRequestSetting setting2(
level, conformance::TEXT_FORMAT, conformance::TEXT_FORMAT,
conformance::TEXT_FORMAT_TEST, prototype, test_name, input_text);
RunValidInputTest(setting2, input_text);
conformance::TEXT_FORMAT_TEST, message, test_name, input_text);
suite_.RunValidInputTest(setting2, input_text);
}
void TextFormatConformanceTestSuite::RunValidTextFormatTestWithMessage(
const string& test_name, ConformanceLevel level, const string& input_text,
const string& expected_text, const Message& prototype) {
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::
RunValidTextFormatTestWithExpected(const std::string& test_name,
ConformanceLevel level,
const std::string& input_text,
const std::string& expected_text) {
MessageType prototype;
ConformanceRequestSetting setting1(
level, conformance::TEXT_FORMAT, conformance::PROTOBUF,
conformance::TEXT_FORMAT_TEST, prototype, test_name, input_text);
RunValidInputTest(setting1, expected_text);
suite_.RunValidInputTest(setting1, expected_text);
ConformanceRequestSetting setting2(
level, conformance::TEXT_FORMAT, conformance::TEXT_FORMAT,
conformance::TEXT_FORMAT_TEST, prototype, test_name, input_text);
RunValidInputTest(setting2, expected_text);
suite_.RunValidInputTest(setting2, expected_text);
}
void TextFormatConformanceTestSuite::RunValidUnknownTextFormatTest(
const string& test_name, const Message& message) {
string serialized_input;
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<
MessageType>::RunValidUnknownTextFormatTest(const std::string& test_name,
const Message& message) {
std::string serialized_input;
message.SerializeToString(&serialized_input);
TestAllTypesProto3 prototype;
MessageType prototype;
ConformanceRequestSetting setting1(
RECOMMENDED, conformance::PROTOBUF, conformance::TEXT_FORMAT,
conformance::TEXT_FORMAT_TEST, prototype,
absl::StrCat(test_name, "_Drop"), serialized_input);
setting1.SetPrototypeMessageForCompare(message);
RunValidBinaryInputTest(setting1, "");
suite_.RunValidBinaryInputTest(setting1, "");
ConformanceRequestSetting setting2(
RECOMMENDED, conformance::PROTOBUF, conformance::TEXT_FORMAT,
@ -209,11 +232,20 @@ void TextFormatConformanceTestSuite::RunValidUnknownTextFormatTest(
absl::StrCat(test_name, "_Print"), serialized_input);
setting2.SetPrototypeMessageForCompare(message);
setting2.SetPrintUnknownFields(true);
RunValidBinaryInputTest(setting2, serialized_input);
suite_.RunValidBinaryInputTest(setting2, serialized_input);
}
void TextFormatConformanceTestSuite::RunSuiteImpl() {
if (!performance_) {
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::RunGroupTests() {
RunValidTextFormatTest("GroupFieldNoColon", REQUIRED,
"Data { group_int32: 1 }");
RunValidTextFormatTest("GroupFieldWithColon", REQUIRED,
"Data: { group_int32: 1 }");
RunValidTextFormatTest("GroupFieldEmpty", REQUIRED, "Data {}");
}
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::RunAllTests() {
RunValidTextFormatTest("HelloWorld", REQUIRED,
"optional_string: 'Hello, World!'");
// Integer fields.
@ -252,8 +284,7 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
"optional_float: 3.4028235e+38");
RunValidTextFormatTest("FloatFieldMinValue", REQUIRED,
"optional_float: 1.17549e-38");
RunValidTextFormatTest("FloatFieldNaNValue", REQUIRED,
"optional_float: NaN");
RunValidTextFormatTest("FloatFieldNaNValue", REQUIRED, "optional_float: NaN");
RunValidTextFormatTest("FloatFieldPosInfValue", REQUIRED,
"optional_float: inf");
RunValidTextFormatTest("FloatFieldNegInfValue", REQUIRED,
@ -282,8 +313,8 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
RunValidTextFormatTest(
absl::StrCat("StringLiteralOctalEscapes", field_type), REQUIRED,
absl::StrCat(field_name, ": '\\341\\210\\264'"));
RunValidTextFormatTest(
absl::StrCat("StringLiteralHexEscapes", field_type), REQUIRED,
RunValidTextFormatTest(absl::StrCat("StringLiteralHexEscapes", field_type),
REQUIRED,
absl::StrCat(field_name, ": '\\xe1\\x88\\xb4'"));
RunValidTextFormatTest(
absl::StrCat("StringLiteralShortUnicodeEscape", field_type),
@ -292,8 +323,8 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
absl::StrCat("StringLiteralLongUnicodeEscapes", field_type),
RECOMMENDED, absl::StrCat(field_name, ": '\\U00001234\\U00010437'"));
// String literals don't include line feeds.
ExpectParseFailure(
absl::StrCat("StringLiteralIncludesLF", field_type), REQUIRED,
ExpectParseFailure(absl::StrCat("StringLiteralIncludesLF", field_type),
REQUIRED,
absl::StrCat(field_name, ": 'first line\nsecond line'"));
// Unicode escapes don't include code points that lie beyond the planes
// (> 0x10ffff).
@ -322,8 +353,7 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
field_type),
RECOMMENDED, absl::StrCat(field_name, ": '\\U0000dc00'"));
ExpectParseFailure(
absl::StrCat("StringLiteralLongUnicodeEscapeSurrogatePair",
field_type),
absl::StrCat("StringLiteralLongUnicodeEscapeSurrogatePair", field_type),
RECOMMENDED, absl::StrCat(field_name, ": '\\U0000d801\\U00000dc37'"));
ExpectParseFailure(
absl::StrCat("StringLiteralUnicodeEscapeSurrogatePairLongShort",
@ -338,23 +368,16 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
// validation.
const auto test_method =
field_type == "String"
? &TextFormatConformanceTestSuite::ExpectParseFailure
: &TextFormatConformanceTestSuite::RunValidTextFormatTest;
? &TextFormatConformanceTestSuiteImpl::ExpectParseFailure
: &TextFormatConformanceTestSuiteImpl::RunValidTextFormatTest;
// String fields reject invalid UTF-8 byte sequences; bytes fields don't.
(this->*test_method)(absl::StrCat(field_type, "FieldBadUTF8Octal"),
REQUIRED, absl::StrCat(field_name, ": '\\300'"));
(this->*test_method)(absl::StrCat(field_type, "FieldBadUTF8Hex"),
REQUIRED, absl::StrCat(field_name, ": '\\xc0'"));
(this->*test_method)(absl::StrCat(field_type, "FieldBadUTF8Hex"), REQUIRED,
absl::StrCat(field_name, ": '\\xc0'"));
}
// Group fields
RunValidTextFormatTestProto2("GroupFieldNoColon", REQUIRED,
"Data { group_int32: 1 }");
RunValidTextFormatTestProto2("GroupFieldWithColon", REQUIRED,
"Data: { group_int32: 1 }");
RunValidTextFormatTestProto2("GroupFieldEmpty", REQUIRED, "Data {}");
// Unknown Fields
UnknownToTestAllTypes message;
// Unable to print unknown Fixed32/Fixed64 fields as if they are known.
@ -377,33 +400,8 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
message.add_repeated_int32(3);
RunValidUnknownTextFormatTest("RepeatedUnknownFields", message);
// Any fields
RunValidTextFormatTest("AnyField", REQUIRED,
R"(
optional_any: {
[type.googleapis.com/protobuf_test_messages.proto3.TestAllTypesProto3] {
optional_int32: 12345
}
}
)");
RunValidTextFormatTest("AnyFieldWithRawBytes", REQUIRED,
R"(
optional_any: {
type_url: "type.googleapis.com/protobuf_test_messages.proto3.TestAllTypesProto3"
value: "\b\271`"
}
)");
ExpectParseFailure("AnyFieldWithInvalidType", REQUIRED,
R"(
optional_any: {
[type.googleapis.com/unknown] {
optional_int32: 12345
}
}
)");
// Map fields
TestAllTypesProto3 prototype;
MessageType prototype;
(*prototype.mutable_map_string_string())["c"] = "value";
(*prototype.mutable_map_string_string())["b"] = "value";
(*prototype.mutable_map_string_string())["a"] = "value";
@ -429,8 +427,7 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
(*prototype.mutable_map_int32_int32())[3] = 0;
(*prototype.mutable_map_int32_int32())[2] = 0;
(*prototype.mutable_map_int32_int32())[1] = 0;
RunValidTextFormatTestWithMessage("AlphabeticallySortedMapIntKeys",
REQUIRED,
RunValidTextFormatTestWithMessage("AlphabeticallySortedMapIntKeys", REQUIRED,
R"(
map_int32_int32 {
key: 1
@ -450,8 +447,7 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
prototype.Clear();
(*prototype.mutable_map_bool_bool())[true] = false;
(*prototype.mutable_map_bool_bool())[false] = false;
RunValidTextFormatTestWithMessage("AlphabeticallySortedMapBoolKeys",
REQUIRED,
RunValidTextFormatTestWithMessage("AlphabeticallySortedMapBoolKeys", REQUIRED,
R"(
map_bool_bool {
key: false
@ -478,20 +474,46 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
}
)");
// The last-specified value will be retained in a parsed map
RunValidInputTest(setting_map, R"(
suite_.RunValidInputTest(setting_map, R"(
map_string_nested_message {
key: "duplicate"
value: { corecursive: {} }
}
)");
}
// Flag control performance tests to keep them internal and opt-in only
if (performance_) {
RunTextFormatPerformanceTests();
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::RunAnyTests() {
// Any fields
RunValidTextFormatTest("AnyField", REQUIRED,
R"(
optional_any: {
[type.googleapis.com/protobuf_test_messages.proto3.TestAllTypesProto3]
{ optional_int32: 12345
}
}
)");
RunValidTextFormatTest("AnyFieldWithRawBytes", REQUIRED,
R"(
optional_any: {
type_url:
"type.googleapis.com/protobuf_test_messages.proto3.TestAllTypesProto3" value:
"\b\271`"
}
)");
ExpectParseFailure("AnyFieldWithInvalidType", REQUIRED,
R"(
optional_any: {
[type.googleapis.com/unknown] {
optional_int32: 12345
}
}
)");
}
void TextFormatConformanceTestSuite::RunTextFormatPerformanceTests() {
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<
MessageType>::RunTextFormatPerformanceTests() {
TestTextFormatPerformanceMergeMessageWithRepeatedField("Bool",
"repeated_bool: true");
TestTextFormatPerformanceMergeMessageWithRepeatedField(
@ -507,30 +529,27 @@ void TextFormatConformanceTestSuite::RunTextFormatPerformanceTests() {
}
// This is currently considered valid input by some languages but not others
void TextFormatConformanceTestSuite::
template <typename MessageType>
void TextFormatConformanceTestSuiteImpl<MessageType>::
TestTextFormatPerformanceMergeMessageWithRepeatedField(
const string& test_type_name, const string& message_field) {
string recursive_message =
const std::string& test_type_name, const std::string& message_field) {
std::string recursive_message =
absl::StrCat("recursive_message { ", message_field, " }");
string input;
std::string input;
for (size_t i = 0; i < kPerformanceRepeatCount; i++) {
absl::StrAppend(&input, recursive_message);
}
string expected = "recursive_message { ";
std::string expected = "recursive_message { ";
for (size_t i = 0; i < kPerformanceRepeatCount; i++) {
absl::StrAppend(&expected, message_field, " ");
}
absl::StrAppend(&expected, "}");
RunValidTextFormatTestProto2WithExpected(
absl::StrCat("TestTextFormatPerformanceMergeMessageWithRepeatedField",
test_type_name, "Proto2"),
RECOMMENDED, input, expected);
RunValidTextFormatTestWithExpected(
absl::StrCat("TestTextFormatPerformanceMergeMessageWithRepeatedField",
test_type_name, "Proto3"),
test_type_name),
RECOMMENDED, input, expected);
}

@ -8,7 +8,10 @@
#ifndef TEXT_FORMAT_CONFORMANCE_SUITE_H_
#define TEXT_FORMAT_CONFORMANCE_SUITE_H_
#include <string>
#include "conformance_test.h"
#include "google/protobuf/message.h"
namespace google {
namespace protobuf {
@ -19,41 +22,55 @@ class TextFormatConformanceTestSuite : public ConformanceTestSuite {
private:
void RunSuiteImpl() override;
bool ParseTextFormatResponse(const conformance::ConformanceResponse& response,
const ConformanceRequestSetting& setting,
Message* test_message);
bool ParseResponse(const conformance::ConformanceResponse& response,
const ConformanceRequestSetting& setting,
Message* test_message) override;
template <typename MessageType>
friend class TextFormatConformanceTestSuiteImpl;
};
template <typename MessageType>
class TextFormatConformanceTestSuiteImpl {
public:
explicit TextFormatConformanceTestSuiteImpl(
TextFormatConformanceTestSuite* suite);
private:
using ConformanceRequestSetting =
TextFormatConformanceTestSuite::ConformanceRequestSetting;
using ConformanceLevel = TextFormatConformanceTestSuite::ConformanceLevel;
constexpr static ConformanceLevel RECOMMENDED = ConformanceLevel::RECOMMENDED;
constexpr static ConformanceLevel REQUIRED = ConformanceLevel::REQUIRED;
void RunAllTests();
void RunGroupTests();
void RunAnyTests();
void RunTextFormatPerformanceTests();
void RunValidTextFormatTest(const std::string& test_name,
ConformanceLevel level, const std::string& input);
void RunValidTextFormatTestProto2(const std::string& test_name,
ConformanceLevel level,
const std::string& input);
void RunValidTextFormatTestWithExpected(const std::string& test_name,
ConformanceLevel level,
const std::string& input,
const std::string& expected);
void RunValidTextFormatTestProto2WithExpected(const std::string& test_name,
ConformanceLevel level,
const std::string& input,
const std::string& expected);
void RunValidTextFormatTestWithMessage(const std::string& test_name,
ConformanceLevel level,
const std::string& input_text,
const Message& prototype);
const std::string& expected_text);
void RunValidUnknownTextFormatTest(const std::string& test_name,
const Message& message);
void RunValidTextFormatTestWithMessage(const std::string& test_name,
ConformanceLevel level,
const std::string& input_text,
const std::string& expected_text,
const Message& prototype);
void RunValidUnknownTextFormatTest(const std::string& test_name,
const Message& message);
void ExpectParseFailure(const std::string& test_name, ConformanceLevel level,
const std::string& input);
bool ParseTextFormatResponse(const conformance::ConformanceResponse& response,
const ConformanceRequestSetting& setting,
Message* test_message);
bool ParseResponse(const conformance::ConformanceResponse& response,
const ConformanceRequestSetting& setting,
Message* test_message) override;
void TestTextFormatPerformanceMergeMessageWithRepeatedField(
const std::string& test_type_name, const std::string& message_field);
TextFormatConformanceTestSuite& suite_;
};
} // namespace protobuf

@ -1,20 +1,40 @@
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString
Recommended.Editions_Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString
Required.Proto3.TextFormatInput.StringFieldBadUTF8Hex
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Hex
Required.Proto3.TextFormatInput.StringFieldBadUTF8Octal
Required.Editions_Proto3.TextFormatInput.StringFieldBadUTF8Octal
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes
Required.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeBytes
Required.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString
Required.Editions_Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeTooLargeString

@ -3,31 +3,6 @@
# TODO: These should be fixed.
Required.Proto3.TextFormatInput.FloatFieldMaxValue.ProtobufOutput
Required.Proto3.TextFormatInput.FloatFieldMaxValue.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesBytes.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesBytes.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesString.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesString.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeBytes.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeBytes.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeString.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeString.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput

@ -1,27 +1,3 @@
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateFirstOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogatePairString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapeSurrogateSecondOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesBytes.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesBytes.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesString.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralLongUnicodeEscapesString.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeBytes.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeBytes.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeString.ProtobufOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeString.TextFormatOutput
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateFirstOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogatePairString
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyBytes
Recommended.Proto3.TextFormatInput.StringLiteralShortUnicodeEscapeSurrogateSecondOnlyString
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortBytes
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairLongShortString
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongBytes
Recommended.Proto3.TextFormatInput.StringLiteralUnicodeEscapeSurrogatePairShortLongString
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.ProtobufOutput
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesBytes.TextFormatOutput
Required.Proto3.TextFormatInput.StringLiteralBasicEscapesString.ProtobufOutput

@ -24,6 +24,7 @@ The following topics are in this repository:
* [Protobuf Editions Design: Features](protobuf-editions-design-features.md)
* [Editions: Life of a Featureset](editions-life-of-a-featureset.md)
* [Edition Zero Features](edition-zero-features.md)
* [Minimum Required Edition](minimum-required-edition.md)
* [Protobuf Editions for Schema Producers](protobuf-editions-for-schema-producers.md)
* [Stricter Schemas with Editions](stricter-schemas-with-editions.md)
* [Edition Zero: JSON Handling](edition-zero-json-handling.md)
@ -34,3 +35,5 @@ The following topics are in this repository:
* [Edition Evolution](edition-evolution.md)
* [Edition Naming](edition-naming.md)
* [Editions Feature Visibility](editions-feature-visibility.md)
* [Legacy Syntax Editions](legacy-syntax-editions.md)
* [Editions: Feature Extension Layout](editions-feature-extension-layout.md)

@ -0,0 +1,150 @@
# Editions: Feature Extension Layout
**Author:** [@mkruskal-google](https://github.com/mkruskal-google),
[@zhangskz](https://github.com/zhangskz)
**Approved:** 2023-08-23
## Background
"[What are Protobuf Editions](what-are-protobuf-editions.md)" lays out a plan
for allowing for more targeted features not owned by the protobuf team. It uses
extensions of the global features proto to implement this. One thing that was
left a bit ambiguous was *who* should own these extensions. Language, code
generator, and runtime implementations are all similar but not identical
distinctions.
"Editions Zero Feature: utf8_validation" (not available externally, though a
later version,
"[Editions Zero: utf8_validation Without Problematic Options](editions-zero-utf8_validation.md)"
is) is a recent plan to add a new set of generator features for utf8 validation.
While the sole feature we had originally created (`legacy_closed_enum` in Java
and C++) didn't have any ambiguity here, this one did. Specifically in Python,
the current behaviors across proto2/proto3 are distinct for all 3
implementations: pure python, Python/C++, Python/upb.
## Overview
In meetings, we've discussed various alternatives, captured below. The original
plan was to make feature extensions runtime implementation-specific (e.g. C++,
Java, Python, upb). There are some notable complications that came up though:
1. **Polyglot** - it's not clear how upb or C++ runtimes should behave in
multi-language situations. Which feature sets do they consider for runtime
behaviors? *Note: this is already a serious issue today, where all proto2
strings and many proto3 strings are completely unsafe across languages.*
2. **Shared Implementations** - Runtimes like upb and C++ are used as backing
implementations of multiple other languages (e.g. Python, Rust, Ruby, PHP).
If we have a single set of `upb` or `cpp` features, migrating to those
shared implementations would be more difficult (since there's no independent
switches per-language). *Note: this is already the situation we're in today,
where switching the runtime implementation can cause subtle and dangerous
behavior changes.*
Given that we only have two behaviors, and one of them is unambiguous, it seems
reasonable to punt on this decision until we have more information. We may
encounter more edge cases that require feature extensions (and give us more
information) during the rollout of edition zero. We also have a lot of freedom
to re-model features in later editions, so keeping the initial implementation as
simple as possible seems best (i.e. Alternative 2).
## Alternatives
### Alternative 1: Runtime Implementation Features
Features would be per-runtime implementation as originally described in
"Editions Zero Feature: utf8_validation." For example, Protobuf Python users
would set different features depending on the backing implementation (e.g.
`features.(pb.cpp).<feature>`, `features.(pb.upb).<feature>`).
#### Pros
* Most consistent with range of behaviors expressible pre-Editions
#### Cons
* Implementation may / should not be obvious to users.
* Lack of levers specifically for language / implementation combos. For
example, there is no way to set Python-C++ behavior independently of C++
behavior which may make migration harder from other Python implementations.
### Alternative 2: Generator Features
Features would be per-generator only (i.e. each protoc plugin would own one set
of features). This was the second decision we made in later discussions, and
while very similar to the above alternative, it's more inline with our goal of
making features primarily for codegen.
For example, all Python implementations would share the same set of features
(e.g. `features.(pb.python).<feature>`). However, certain features could be
targeted to specific implementations (e.g.
`features.(pb.python).upb_utf8_validation` would only be used by Python/upb).
#### Pros
* Allows independent controls of shared implementations in different target
languages (e.g. Python's upb feature won't affect PHP).
#### Cons
* Possible complexity in upb to understand which language's features to
respect. UPB is not currently aware of what language it is being used for.
* Limits in-process sharing across languages with shared implementations (e.g.
Python upb, PHP upb) in the case of conflicting behaviors.
* Additional checks may be needed.
### Alternative 3: Migrate to bytes
Since this whole discussion revolves around the utf8 validation feature, one
option would be to just remove it from edition zero. Instead of adding a new
toggle for UTF8 behavior, we could simply migrate everyone who doesn't enforce
utf8 today to `bytes`. This would likely need another new *codegen* feature for
generating byte getters/setters as strings, but that wouldn't have any of the
ambiguity we're seeing today.
Unfortunately, this doesn't seem feasible because of all the different behaviors
laid out in "Editions Zero Feature: utf8_validation." UTF8 validation isn't
really a binary on/off decision, and it can vary widely between languages. There
are many cases where UTF8 is validated in **some** languages but not others, and
there's also the C++ "hint" behavior that logs errors but allows invalid UTF8.
**Note:** This could still be partially done in a follow-up LSC by targeting
specific combinations of the new feature that disable validation in all relevant
languages.
#### Pros
* Punts on the issue, we wouldn't need any upb features and C++ features would
all be code-gen only
* Simplifies the situation, avoids adding a very complicated feature in
edition zero
#### Cons
* Not really possible given the current complexity
* There are O(10M) proto2 string fields that would be blindly changed to bytes
### Alternative 4: Nested Features
Another option is to allow for shared feature set messages. For example, upb
would define a feature message, but *not* make it an extension of the global
`FeatureSet`. Instead, languages with upb implementations would have a field of
this type to allow for finer-grained controls. C++ would both extend the global
`FeatureSet` and also be allowed as a field in other languages.
For example, python utf8 validation could be specified as:
We could have checks during feature validation that enforce that impossible
combinations aren't specified. For example, with our current implementation
`features.(pb.python).cpp` should always be identical to `features.(pb.cpp)`,
since we don't have any mechanism for distinguishing them.
#### Pros
* Much more explicit than options 1 and 2
#### Cons
* Maybe too explicit? Proto owners would be forced to duplicate a lot of
features

@ -0,0 +1,144 @@
# Legacy Syntax Editions
**Author:** [@mkruskal-google](https://github.com/mkruskal-google)
**Approved:** 2023-09-08
Should proto2/proto3 be treated as editions?
## Background
[Edition Zero Features](edition-zero-features.md) lays out our plan for edition
2023, which will unify proto2 and proto3. Since early in the design process,
we've discussed the possibility of making proto2 and proto3 "special" editions,
but never laid out what exactly it would look like or determined if it was
necessary.
We recently redesigned editions to be represented as enums
([Edition Naming](edition-naming.md)), and also how edition defaults are
propagated to generators and runtimes
([Editions: Life of a FeatureSet](editions-life-of-a-featureset.md)). With these
changes, there could be an opportunity to special-case proto2 and proto3 in a
beneficial way.
## Problem Description
While the original plan was to keep editions and syntax orthogonal, that naively
means we'd be supporting two very different codebases. This has some serious
maintenance costs though, especially when it comes to test coverage. We could
expect to have sub-optimal test coverage of editions initially, which would
gradually become poor coverage of syntax later. Since we need to support both
syntax and editions long-term, this isn't ideal.
In the implementation of editions in C++, we decided to unify a lot of the
infrastructure to avoid this issue. We define global feature sets for proto2 and
proto3, and try to use those internally instead of checking syntax directly. By
pushing the syntax/editions branch earlier in the stack, it gives us a lot of
indirect test coverage for editions much earlier.
A separate issue is how Prototiller will support the conversion of syntax to
edition 2023. For features it knows about, we can hardcode defaults into the
transforms. However, third party feature owners will have no way of signaling
what the old proto2/proto3 behavior was, so Prototiller won't be able to provide
any transformations by default. They'd need to provide custom Prototiller
transforms hardcoding all of their features.
## Recommended Solution
We recommend adding two new special editions to our current set:
```
enum Edition {
EDITION_UNKNOWN = 0;
EDITION_PROTO2 = 998;
EDITION_PROTO3 = 999;
EDITION_2023 = 1000;
}
```
These will be treated the same as any other edition, except in our parser which
will reject `edition = "proto2"` and `edition = "proto3"` in proto files. The
real benefit here is that this allows features to specify what their
proto2/proto3 defaults are, making it easier for Prototiller to handle
migration. It also allows generators and runtimes to unify their internals more
completely, treating proto2/proto3 files exactly the same as editions.
### Serialized Descriptors
As we now know, there are a lot of serialized `descriptor.proto` descriptor sets
out there that need to continue working for O(months). In order to avoid
blocking edition zero for that long, we may need fallbacks in protoc for the
case where feature resolution *fails*. If the file is proto2/proto3, failure
should result in a fallback to the existing hardcoded defaults. We can remove
these later once we're willing to break stale `descriptor.proto` snapshots that
predate the changes in this doc.
### Bootstrapping
In order to get feature resolution running in proto2 and proto3, we need to be
able to support bootstrapped protos. For these builds, we can't use any
reflection without deadlocking, which means feature defaults can't be compiled
during runtime. We would have had to solve this problem anyway when it came time
to migrate these protos to editions, but this proposal forces our hand early.
Luckily, "Editions: Life of a FeatureSet" already set us up for this scenario,
and we have Blaze rules for embedding these defaults into code. For C++
specifically, this will need to be checked in alongside the other bootstrapped
protos. Other languages will be able to do this more dynamically via genrules.
### Feature Inference
While we can calculate defaults using the same logic as in editions, actually
inferring "features" from proto2/proto3 needs some custom code. For example:
* The `required` keyword sets `LEGACY_REQUIRED` feature
* The `optional` keyword in proto3 sets `EXPLICIT` presence
* The `group` keyword implies `DELIMITED` encoding
* The `enforce_utf8` options flips between `PACKED` and `EXPANDED` encoding
This logic needs to be written in code, and will need to be duplicated in every
language we support. Any language-specific feature transformations will also
need to be included in that language. To make this as portable as possible, we
will define functions like:
Each type of descriptor will have its own set of transformations that should be
applied to its features for legacy editions.
#### Pros
* Makes it clearer that proto2/proto3 are "like" editions
* Gives Prototiller a little more information in the transformation from
proto2/proto3 to editions (not necessarily 2023)
* Allows proto2/proto3 defaults to be specified in a single location
* Makes unification of syntax/edition code easier to implement in runtimes
* Allows cross-language proto2/proto3 testing with the conformance framework
mentioned in "Editions: Life of a FeatureSet"
#### Cons
* Adds special-case legacy editions, which may be somewhat confusing
* We will need to port feature inference logic across all languages. This is
arguably cheaper than maintaining branched proto2/proto3 code in all
languages though
## Considered Alternatives
### Do Nothing
If we do nothing, there will be no built-in unification of syntax and editions.
Runtimes could choose any point to split the logic.
#### Pros
* Requires no changes to editions code
#### Cons
* Likely results in lower test coverage
* May hide issues until we start rolling out edition 2023
* Prototiller would have to hard-code proto2/proto3 defaults of features it
knows, and couldn't even try to migrate runtimes it doesn't

@ -0,0 +1,139 @@
# Minimum Required Edition
**Author:** [@mcy](https://github.com/mcy)
**Approved:** 2022-11-15
A versioning mechanism for descriptors that ensures old runtimes do not load
descriptors that are "too new."
## Background
Suppose we decide to add a novel definition like
```
const int32 MY_CONSTANT = 42;
```
to the Protobuf language. This would entail a descriptor change to track the
values of constants, but they would not be loaded properly by older runtimes.
This document describes an addition to `descriptor.proto` that prevents this
version mismatch issue.
[Protobuf Editions](what-are-protobuf-editions.md) intends to add the concept of
an edition to Protobuf, which will be an approximately-annually incrementing
value. Because of their annual nature, and because runtimes need to be updated
to handle new features they implement regardless, we can use them as a poison
pill for old runtimes that try to load descriptors that are "too new."
## Overview
We propose adding a new field to `FileDescriptorProto`:
```
optional string minimum_required_edition = ...;
```
This field would exist alongside the `edition` field, and would have the
following semantics:
Every Protobuf runtime implementation must specify the newest edition whose
constructs it can handle (at a particular rev of that implementation). If that
edition is less than `minimum_required_edition`, loading the descriptor must
fail.
"Less than" is defined per the edition total order given in
[Life of an Edition](life-of-an-edition.md). To restate it, it is the following
algorithm:
```
def edition_less_than(a, b):
parts_a = a.split(".")
parts_b = b.split(".")
for i in range(0, min(len(parts_a), len(parts_b))):
if int(parts_a[i]) < int(parts_b[i]): return True
return len(a) < len(b)
```
`protoc` should keep track of which constructions require which minimum edition.
For example, if constants are introduced in edition 2025, but they are not
present in a file, `protoc` should not require that runtimes understand
constants by picking a lower edition, like 2023 (assuming no other construct
requires a higher edition).
In particular, the following changes should keep the minimum edition constant,
with all other things unchanged:
* An upgrade of the proto compiler.
* Upgrading the specified edition of a file via Prototiller.
### Bootstrapping Concerns
"Epochs for `descriptor.proto`" (not available externally) describes a potential
issue with bootstrapping. It is not the case here: minimum edition is only
incremented once a particular file uses a new feature. Since `descriptor.proto`
and other schemas used by `protoc` and the backends would not use new features
immediately, introducing a new feature does not immediately stop the compiler
from being able to compile itself.
### Concerns for Schema Producers
Schema producers should consider changes to their schemas that increase the
minimum required edition to be breaking changes, since it will stop compiled
descriptors from being loaded at runtime.
## Recommendation
We recommend adding the aforementioned minimum required edition field, along
with the semantics it entails. This logic should be implemented entirely in the
protoc frontend.
## Alternatives
### Use a Non-Editions Version Number
Rather than using the editions value, use some other version number. This number
would be incremented rarely (3-5 year horizon). This is the approach proposed
in "Epochs for `descriptor.proto`."
#### Pros
* Does not re-use the editions value for a semantically-different meaning; the
edition remains being "just" a key into a table of features defaults.
#### Cons
* Introduces another version number to Protobuf that increments at its own
cadence.
* Could potentially be confused with the edition value, even though they serve
distinct purposes.
### Minimum Required Edition Should Not Be Minimal
The proto compiler should not guarantee that the minimum required edition is as
small as it could possibly be.
#### Pros
* Reduces implementation burden.
#### Cons
* This introduces situations where an upgrade of the proto compiler, or an
innocuous change to a schema, can lead the the minimum required edition
being incremented. This is a problem for schema producers.
### Do Nothing
#### Pros
* Reduces churn in runtimes, since they do not need to implement new handling
for new *editions* (as contrasted to just *features)* regularly.
* Avoids a situation where old software cannot load new descriptors at
runtime.
#### Cons
* Commits us to never changing the descriptor wire format in
backwards-incompatible ways, which has far-reaching effects on evolution.
These consequences are discussed in "Epochs for `descriptor.proto`."

@ -486,3 +486,8 @@ with info about your project (name and website) so we can add an entry for you.
* Website: https://square.github.io/wire/
* Extensions: 1185
1. Protons
* Website: https://github.com/ipfs/protons
* Extensions: 1186

@ -31,6 +31,8 @@ These are projects we know about implementing Protocol Buffers for other program
* Clojure: http://github.com/ninjudd/clojure-protobuf
* Clojure: https://github.com/clojusc/protobuf
* Clojure: https://protojure.readthedocs.io
* Clojure: https://github.com/s-expresso/clojobuf
* ClojureScript: https://github.com/s-expresso/clojobuf
* Common Lisp: http://github.com/brown/protobuf
* Common Lisp: http://github.com/qitab/cl-protobuf
* D: https://github.com/dcarp/protobuf-d
@ -233,3 +235,8 @@ There are miscellaneous other things you may find useful as a Protocol Buffers d
* [protobuf-decoder-explainer - Web page which decodes and explains pasted
binary Protocol
Buffers](https://github.com/jamesdbrock/protobuf-decoder-explainer)
* [rubberbuf - dynamically transpile protobuf into AST](https://github.com/s-expresso/rubberbuf)
`clojure` `clojurescript`
* [clojobuf-serdes - low level serialize/deserialize library for protobuf
binary format](https://github.com/s-expresso/clojobuf-codec) `clojure`
`clojurescript`

@ -9,6 +9,7 @@ package com.google.protobuf;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import com.google.protobuf.Descriptors.Descriptor;
import com.google.protobuf.Descriptors.FieldDescriptor;
@ -213,28 +214,28 @@ public class GeneratedMessageTest {
@Test
public void testGetExtensionFieldOutOfBound() {
TestAllExtensions.Builder builder = TestAllExtensions.newBuilder();
try {
builder.getRepeatedField(UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0);
assertWithMessage("Expected IndexOutOfBoundsException to be thrown").fail();
} catch (IndexOutOfBoundsException expected) {
}
try {
builder.getExtension(UnittestProto.repeatedNestedMessageExtension, 0);
assertWithMessage("Expected IndexOutOfBoundsException to be thrown").fail();
} catch (IndexOutOfBoundsException expected) {
}
assertThrows(
"Expected IndexOutOfBoundsException to be thrown",
IndexOutOfBoundsException.class,
() ->
builder.getRepeatedField(
UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0));
assertThrows(
"Expected IndexOutOfBoundsException to be thrown",
IndexOutOfBoundsException.class,
() -> builder.getExtension(UnittestProto.repeatedNestedMessageExtension, 0));
TestAllExtensions extensionsMessage = builder.build();
try {
assertThrows(
"Expected IndexOutOfBoundsException to be thrown",
IndexOutOfBoundsException.class,
() ->
extensionsMessage.getRepeatedField(
UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0);
assertWithMessage("Expected IndexOutOfBoundsException to be thrown").fail();
} catch (IndexOutOfBoundsException expected) {
}
try {
extensionsMessage.getExtension(UnittestProto.repeatedNestedMessageExtension, 0);
assertWithMessage("Expected IndexOutOfBoundsException to be thrown").fail();
} catch (IndexOutOfBoundsException expected) {
}
UnittestProto.repeatedNestedMessageExtension.getDescriptor(), 0));
assertThrows(
"Expected IndexOutOfBoundsException to be thrown",
IndexOutOfBoundsException.class,
() -> extensionsMessage.getExtension(UnittestProto.repeatedNestedMessageExtension, 0));
}
@Test
@ -365,78 +366,51 @@ public class GeneratedMessageTest {
if (list == Collections.emptyList()) {
// OKAY -- Need to check this b/c EmptyList allows you to call clear.
} else {
try {
list.clear();
assertWithMessage("List wasn't immutable").fail();
} catch (UnsupportedOperationException e) {
// good
}
assertThrows(
"List wasn't immutable", UnsupportedOperationException.class, () -> list.clear());
}
}
@Test
public void testSettersRejectNull() throws Exception {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
try {
builder.setOptionalString(null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.setOptionalBytes(null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.setOptionalNestedMessage((TestAllTypes.NestedMessage) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.setOptionalNestedMessage((TestAllTypes.NestedMessage.Builder) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.setOptionalNestedEnum(null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addRepeatedString(null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addRepeatedBytes(null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addRepeatedNestedMessage((TestAllTypes.NestedMessage) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addRepeatedNestedMessage((TestAllTypes.NestedMessage.Builder) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addRepeatedNestedEnum(null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setOptionalString(null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setOptionalNestedMessage((TestAllTypes.NestedMessage) null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setOptionalNestedMessage((TestAllTypes.NestedMessage.Builder) null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setOptionalNestedEnum(null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addRepeatedString(null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addRepeatedBytes(null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addRepeatedNestedMessage((TestAllTypes.NestedMessage) null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addRepeatedNestedMessage((TestAllTypes.NestedMessage.Builder) null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addRepeatedNestedEnum(null));
}
@Test
@ -454,45 +428,35 @@ public class GeneratedMessageTest {
builder.addRepeatedString("one");
builder.addRepeatedString("two");
try {
builder.setRepeatedString(1, null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setRepeatedString(1, null));
builder.addRepeatedBytes(TestUtil.toBytes("one"));
builder.addRepeatedBytes(TestUtil.toBytes("two"));
try {
builder.setRepeatedBytes(1, null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setRepeatedBytes(1, null));
builder.addRepeatedNestedMessage(TestAllTypes.NestedMessage.newBuilder().setBb(218).build());
builder.addRepeatedNestedMessage(TestAllTypes.NestedMessage.newBuilder().setBb(456).build());
try {
builder.setRepeatedNestedMessage(1, (TestAllTypes.NestedMessage) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.setRepeatedNestedMessage(1, (TestAllTypes.NestedMessage.Builder) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setRepeatedNestedMessage(1, (TestAllTypes.NestedMessage) null));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setRepeatedNestedMessage(1, (TestAllTypes.NestedMessage.Builder) null));
builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.FOO);
builder.addRepeatedNestedEnum(TestAllTypes.NestedEnum.BAR);
try {
builder.setRepeatedNestedEnum(1, null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.setRepeatedNestedEnum(1, null));
}
@Test
@ -518,33 +482,24 @@ public class GeneratedMessageTest {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
ForeignMessage foreignMessage = ForeignMessage.newBuilder().setC(12).build();
try {
builder.addAllRepeatedForeignMessage(Arrays.asList(foreignMessage, (ForeignMessage) null));
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addAllRepeatedForeignEnum(Arrays.asList(ForeignEnum.FOREIGN_BAZ, null));
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addAllRepeatedString(Arrays.asList("one", null));
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
try {
builder.addAllRepeatedBytes(Arrays.asList(TestUtil.toBytes("one"), null));
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() ->
builder.addAllRepeatedForeignMessage(
Arrays.asList(foreignMessage, (ForeignMessage) null)));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addAllRepeatedForeignEnum(Arrays.asList(ForeignEnum.FOREIGN_BAZ, null)));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addAllRepeatedString(Arrays.asList("one", null)));
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.addAllRepeatedBytes(Arrays.asList(TestUtil.toBytes("one"), null)));
}
@Test
@ -570,23 +525,19 @@ public class GeneratedMessageTest {
assertThat(builder.getRepeatedString(1)).isEqualTo("two");
assertThat(builder.getRepeatedString(2)).isEqualTo("three");
try {
builder.addAllRepeatedString(stringIterable);
assertWithMessage("Exception was not thrown").fail();
} catch (IllegalStateException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
IllegalStateException.class,
() -> builder.addAllRepeatedString(stringIterable));
}
@Test
public void testMergeFromOtherRejectsNull() throws Exception {
try {
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
builder.mergeFrom((TestAllTypes) null);
assertWithMessage("Exception was not thrown").fail();
} catch (NullPointerException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
NullPointerException.class,
() -> builder.mergeFrom((TestAllTypes) null));
}
@Test
@ -781,13 +732,11 @@ public class GeneratedMessageTest {
@Test
public void testGetBuilderForNonMessageExtensionField() {
TestAllExtensions.Builder builder = TestAllExtensions.newBuilder();
try {
// This should throw an exception because the extension field is not a message.
builder.newBuilderForField(UnittestProto.optionalInt32Extension.getDescriptor());
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// This exception is expected.
}
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.newBuilderForField(UnittestProto.optionalInt32Extension.getDescriptor()));
}
@Test
@ -1388,36 +1337,26 @@ public class GeneratedMessageTest {
public void testGetFieldBuilderNotSupportedException() {
Descriptor descriptor = TestAllTypes.getDescriptor();
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
try {
builder.getFieldBuilder(descriptor.findFieldByName("optional_int32"));
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getFieldBuilder(descriptor.findFieldByName("optional_nested_enum"));
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getFieldBuilder(descriptor.findFieldByName("repeated_int32"));
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getFieldBuilder(descriptor.findFieldByName("repeated_nested_enum"));
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getFieldBuilder(descriptor.findFieldByName("repeated_nested_message"));
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getFieldBuilder(descriptor.findFieldByName("optional_int32")));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getFieldBuilder(descriptor.findFieldByName("optional_nested_enum")));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getFieldBuilder(descriptor.findFieldByName("repeated_int32")));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getFieldBuilder(descriptor.findFieldByName("repeated_nested_enum")));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getFieldBuilder(descriptor.findFieldByName("repeated_nested_message")));
}
// Test that when the default outer class name conflicts with another type
@ -1873,42 +1812,38 @@ public class GeneratedMessageTest {
public void testGetRepeatedFieldBuilderNotSupportedException() {
Descriptor descriptor = TestAllTypes.getDescriptor();
TestAllTypes.Builder builder = TestAllTypes.newBuilder();
try {
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("repeated_int32"), 0);
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("repeated_nested_enum"), 0);
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("optional_int32"), 0);
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("optional_nested_enum"), 0);
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
try {
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("optional_nested_message"), 0);
assertWithMessage("Exception was not thrown").fail();
} catch (UnsupportedOperationException e) {
// We expect this exception.
}
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getRepeatedFieldBuilder(descriptor.findFieldByName("repeated_int32"), 0));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() ->
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("repeated_nested_enum"), 0));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() -> builder.getRepeatedFieldBuilder(descriptor.findFieldByName("optional_int32"), 0));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() ->
builder.getRepeatedFieldBuilder(descriptor.findFieldByName("optional_nested_enum"), 0));
assertThrows(
"Exception was not thrown",
UnsupportedOperationException.class,
() ->
builder.getRepeatedFieldBuilder(
descriptor.findFieldByName("optional_nested_message"), 0));
}
private static final FieldDescriptor OPTIONAL_NESTED_MESSAGE_EXTENSION =
UnittestProto.getDescriptor().findExtensionByName("optional_nested_message_extension");
private static final FieldDescriptor REPEATED_NESTED_MESSAGE_EXTENSION =
UnittestProto.getDescriptor().findExtensionByName("repeated_nested_message_extension");
// A compile-time check that TestAllExtensions.Builder does in fact extend
// GeneratedMessageV3.ExtendableBuilder. The tests below assume that it does.
static {

@ -5803,8 +5803,10 @@ bool upb_Arena_Fuse(upb_Arena* a1, upb_Arena* a2) {
}
}
void upb_Arena_IncRefFor(upb_Arena* arena, const void* owner) {
bool upb_Arena_IncRefFor(upb_Arena* arena, const void* owner) {
_upb_ArenaRoot r;
if (upb_Arena_HasInitialBlock(arena)) return false;
retry:
r = _upb_Arena_FindRoot(arena);
if (upb_Atomic_CompareExchangeWeak(
@ -5813,7 +5815,7 @@ retry:
_upb_Arena_RefCountFromTagged(r.tagged_count) + 1),
memory_order_release, memory_order_acquire)) {
// We incremented it successfully, so we are done.
return;
return true;
}
// We failed update due to parent switching on the arena.
goto retry;

@ -561,7 +561,7 @@ UPB_API upb_Arena* upb_Arena_Init(void* mem, size_t n, upb_alloc* alloc);
UPB_API void upb_Arena_Free(upb_Arena* a);
UPB_API bool upb_Arena_Fuse(upb_Arena* a, upb_Arena* b);
void upb_Arena_IncRefFor(upb_Arena* arena, const void* owner);
bool upb_Arena_IncRefFor(upb_Arena* arena, const void* owner);
void upb_Arena_DecRefFor(upb_Arena* arena, const void* owner);
void* _upb_Arena_SlowMalloc(upb_Arena* a, size_t size);

@ -2014,4 +2014,56 @@ class Message
}
return $size;
}
public function __debugInfo()
{
if (is_a($this, 'Google\Protobuf\FieldMask')) {
return ['paths' => $this->getPaths()->__debugInfo()];
}
if (is_a($this, 'Google\Protobuf\Value')) {
switch ($this->getKind()) {
case 'null_value':
return ['nullValue' => $this->getNullValue()];
case 'number_value':
return ['numberValue' => $this->getNumberValue()];
case 'string_value':
return ['stringValue' => $this->getStringValue()];
case 'bool_value':
return ['boolValue' => $this->getBoolValue()];
case 'struct_value':
return ['structValue' => $this->getStructValue()->__debugInfo()];
case 'list_value':
return ['listValue' => $this->getListValue()->__debugInfo()];
}
return [];
}
if (is_a($this, 'Google\Protobuf\BoolValue')
|| is_a($this, 'Google\Protobuf\BytesValue')
|| is_a($this, 'Google\Protobuf\DoubleValue')
|| is_a($this, 'Google\Protobuf\FloatValue')
|| is_a($this, 'Google\Protobuf\StringValue')
|| is_a($this, 'Google\Protobuf\Int32Value')
|| is_a($this, 'Google\Protobuf\Int64Value')
|| is_a($this, 'Google\Protobuf\UInt32Value')
|| is_a($this, 'Google\Protobuf\UInt64Value')
) {
return [
'value' => json_decode($this->serializeToJsonString(), true),
];
}
if (
is_a($this, 'Google\Protobuf\Duration')
|| is_a($this, 'Google\Protobuf\Timestamp')
) {
return [
'seconds' => $this->getSeconds(),
'nanos' => $this->getNanos(),
];
}
return json_decode($this->serializeToJsonString(), true);
}
}

@ -238,4 +238,17 @@ class RepeatedField implements \ArrayAccess, \IteratorAggregate, \Countable
{
return count($this->container);
}
public function __debugInfo()
{
return array_map(
function ($item) {
if ($item instanceof Message || $item instanceof RepeatedField) {
return $item->__debugInfo();
}
return $item;
},
iterator_to_array($this)
);
}
}

@ -0,0 +1,597 @@
<?php
require_once('test_base.php');
require_once('test_util.php');
use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBType;
use Foo\EmptyAnySerialization;
use Foo\TestInt32Value;
use Foo\TestStringValue;
use Foo\TestAny;
use Foo\TestEnum;
use Foo\TestLargeFieldNumber;
use Foo\TestMessage;
use Foo\TestMessage\Sub;
use Foo\TestPackedMessage;
use Foo\TestRandomFieldOrder;
use Foo\TestUnpackedMessage;
use Google\Protobuf\Any;
use Google\Protobuf\DoubleValue;
use Google\Protobuf\FieldMask;
use Google\Protobuf\FloatValue;
use Google\Protobuf\Int32Value;
use Google\Protobuf\UInt32Value;
use Google\Protobuf\Int64Value;
use Google\Protobuf\UInt64Value;
use Google\Protobuf\BoolValue;
use Google\Protobuf\StringValue;
use Google\Protobuf\BytesValue;
use Google\Protobuf\Value;
use Google\Protobuf\ListValue;
use Google\Protobuf\Struct;
use Google\Protobuf\GPBEmpty;
class DebugInfoTest extends TestBase
{
public function setUp(): void
{
if (extension_loaded('protobuf')) {
$this->markTestSkipped('__debugInfo is not supported for the protobuf extension');
}
}
public function testVarDumpOutput()
{
$m = new DoubleValue();
$m->setValue(1.5);
var_dump($m);
$regex = <<<EOL
object(Google\Protobuf\DoubleValue)#%s (1) {
["value"]=>
float(1.5)
}
EOL;
$this->expectOutputRegex('/' . sprintf(preg_quote($regex), '\d+') . '/');
}
public function testTopLevelDoubleValue()
{
$m = new DoubleValue();
$m->setValue(1.5);
$this->assertSame(['value' => 1.5], $m->__debugInfo());
}
public function testTopLevelFloatValue()
{
$m = new FloatValue();
$m->setValue(1.5);
$this->assertSame(['value' => 1.5], $m->__debugInfo());
}
public function testTopLevelInt32Value()
{
$m = new Int32Value();
$m->setValue(1);
$this->assertSame(['value' => 1], $m->__debugInfo());
}
public function testTopLevelUInt32Value()
{
$m = new UInt32Value();
$m->setValue(1);
$this->assertSame(['value' => 1], $m->__debugInfo());
}
public function testTopLevelInt64Value()
{
$m = new Int64Value();
$m->setValue(1);
$this->assertSame(['value' => '1'], $m->__debugInfo());
}
public function testTopLevelUInt64Value()
{
$m = new UInt64Value();
$m->setValue(1);
$this->assertSame(['value' => '1'], $m->__debugInfo());
}
public function testTopLevelStringValue()
{
$m = new StringValue();
$m->setValue("a");
$this->assertSame(['value' => 'a'], $m->__debugInfo());
}
public function testTopLevelBoolValue()
{
$m = new BoolValue();
$m->setValue(true);
$this->assertSame(['value' => true], $m->__debugInfo());
}
public function testTopLevelBytesValue()
{
$m = new BytesValue();
$m->setValue("a");
$this->assertSame(['value' => 'YQ=='], $m->__debugInfo());
}
public function testTopLevelLongBytesValue()
{
$m = new BytesValue();
$data = $this->generateRandomString(12007);
$m->setValue($data);
$expected = base64_encode($data);
$this->assertSame(['value' => $expected], $m->__debugInfo());
}
public function testJsonEncodeNullSubMessage()
{
$from = new TestMessage();
$from->setOptionalMessage(null);
$data = $from->__debugInfo();
$this->assertEquals([], $data);
}
public function testDuration()
{
$m = new Google\Protobuf\Duration();
$m->setSeconds(1234);
$m->setNanos(999999999);
$this->assertEquals([
'seconds' => 1234,
'nanos' => 999999999,
], $m->__debugInfo());
}
public function testTimestamp()
{
$m = new Google\Protobuf\Timestamp();
$m->setSeconds(946684800);
$m->setNanos(123456789);
$this->assertEquals([
'seconds' => 946684800,
'nanos' => 123456789,
], $m->__debugInfo());
}
public function testTopLevelValue()
{
$m = new Value();
$m->setStringValue("a");
$this->assertSame(['stringValue' => 'a'], $m->__debugInfo());
$m = new Value();
$m->setNumberValue(1.5);
$this->assertSame(['numberValue' => 1.5], $m->__debugInfo());
$m = new Value();
$m->setBoolValue(true);
$this->assertSame(['boolValue' => true], $m->__debugInfo());
$m = new Value();
$m->setNullValue(\Google\Protobuf\NullValue::NULL_VALUE);
$this->assertSame(['nullValue' => 0], $m->__debugInfo());
$m = new Value();
$struct = new Struct();
$map = $struct->getFields();
$sub = new Value();
$sub->setNumberValue(1.5);
$map["a"] = $sub;
$m->setStructValue($struct);
$this->assertSame(['structValue' => ['a' => 1.5]], $m->__debugInfo());
$m = new Value();
$list = new ListValue();
$arr = $list->getValues();
$sub = new Value();
$sub->setNumberValue(1.5);
$arr[] = $sub;
$m->setListValue($list);
$this->assertSame(['listValue' => [1.5]], $m->__debugInfo());
}
public function testTopLevelListValue()
{
$m = new ListValue();
$arr = $m->getValues();
$sub = new Value();
$sub->setNumberValue(1.5);
$arr[] = $sub;
$this->assertSame([1.5], $m->__debugInfo());
}
public function testEmptyListValue()
{
$m = new Struct();
$m->setFields(['test' => (new Value())->setListValue(new ListValue())]);
$this->assertSame(['test' => []], $m->__debugInfo());
}
public function testTopLevelStruct()
{
$m = new Struct();
$map = $m->getFields();
$sub = new Value();
$sub->setNumberValue(1.5);
$map["a"] = $sub;
$this->assertSame(['a' => 1.5], $m->__debugInfo());
}
public function testEmptyStruct()
{
$m = new Struct();
$m->setFields(['test' => (new Value())->setStructValue(new Struct())]);
$this->assertSame(['test' => []], $m->__debugInfo());
}
public function testEmptyValue()
{
$m = new Value();
$this->assertSame([], $m->__debugInfo());
}
public function testTopLevelAny()
{
// Test a normal message.
$packed = new TestMessage();
$packed->setOptionalInt32(123);
$packed->setOptionalString("abc");
$m = new Any();
$m->pack($packed);
$expected = [
'@type' => 'type.googleapis.com/foo.TestMessage',
'optionalInt32' => 123,
'optionalString' => 'abc',
];
$result = $m->__debugInfo();
$this->assertSame($expected, $result);
// Test a well known message.
$packed = new Int32Value();
$packed->setValue(123);
$m = new Any();
$m->pack($packed);
$this->assertSame([
'@type' => 'type.googleapis.com/google.protobuf.Int32Value',
'value' => 123
], $m->__debugInfo());
// Test an Any message.
$outer = new Any();
$outer->pack($m);
$this->assertSame([
'@type' => 'type.googleapis.com/google.protobuf.Any',
'value' => [
'@type' => 'type.googleapis.com/google.protobuf.Int32Value',
'value' => 123
],
], $outer->__debugInfo());
// Test a Timestamp message.
$packed = new Google\Protobuf\Timestamp();
$packed->setSeconds(946684800);
$packed->setNanos(123456789);
$m = new Any();
$m->pack($packed);
$this->assertSame([
'@type' => 'type.googleapis.com/google.protobuf.Timestamp',
'value' => '2000-01-01T00:00:00.123456789Z',
], $m->__debugInfo());
}
public function testAnyWithDefaultWrapperMessagePacked()
{
$any = new Any();
$any->pack(new TestInt32Value([
'field' => new Int32Value(['value' => 0]),
]));
$this->assertSame(
['@type' => 'type.googleapis.com/foo.TestInt32Value', 'field' => 0],
$any->__debugInfo()
);
}
public function testTopLevelFieldMask()
{
$m = new FieldMask();
$m->setPaths(["foo.bar_baz", "qux"]);
$this->assertSame(['paths' => ['foo.bar_baz', 'qux']], $m->__debugInfo());
}
public function testEmptyAnySerialization()
{
$m = new EmptyAnySerialization();
$any = new Any();
$any->pack($m);
$data = $any->__debugInfo();
$this->assertEquals(['@type' => 'type.googleapis.com/foo.EmptyAnySerialization'], $data);
}
public function testRepeatedStringValue()
{
$m = new TestStringValue();
$r = [new StringValue(['value' => 'a'])];
$m->setRepeatedField($r);
$this->assertSame(['repeatedField' => ['a']], $m->__debugInfo());
}
public function testMapStringValue()
{
$m = new TestStringValue();
$m->mergeFromJsonString("{\"map_field\":{\"1\": \"a\"}}");
$this->assertSame(['mapField' => [1 => 'a']], $m->__debugInfo());
}
public function testNestedAny()
{
// Make sure packed message has been created at least once.
$m = new TestAny();
$m->mergeFromJsonString(
"{\"any\":{\"optionalInt32\": 1, " .
"\"@type\":\"type.googleapis.com/foo.TestMessage\", " .
"\"optionalInt64\": 2}}");
$this->assertSame([
'any' => [
'@type' => 'type.googleapis.com/foo.TestMessage',
'optionalInt32' => 1,
'optionalInt64' => '2',
]
], $m->__debugInfo());
}
public function testEnum()
{
$m = new TestMessage();
$m->setOneofEnum(TestEnum::ZERO);
$this->assertSame(['oneofEnum' => 'ZERO'], $m->__debugInfo());
}
public function testTopLevelRepeatedField()
{
$r1 = new RepeatedField(GPBType::class);
$r1[] = 'a';
$this->assertSame(['a'], $r1->__debugInfo());
$r2 = new RepeatedField(TestMessage::class);
$r2[] = new TestMessage(['optional_int32' => -42]);
$r2[] = new TestMessage(['optional_int64' => 43]);
$this->assertSame([
['optionalInt32' => -42],
['optionalInt64' => '43'],
], $r2->__debugInfo());
$r3 = new RepeatedField(RepeatedField::class);
$r3[] = $r1;
$r3[] = $r2;
$this->assertSame([
['a'],
[
['optionalInt32' => -42],
['optionalInt64' => '43'],
],
], $r3->__debugInfo());
}
public function testEverything()
{
$m = new TestMessage([
'optional_int32' => -42,
'optional_int64' => -43,
'optional_uint32' => 42,
'optional_uint64' => 43,
'optional_sint32' => -44,
'optional_sint64' => -45,
'optional_fixed32' => 46,
'optional_fixed64' => 47,
'optional_sfixed32' => -46,
'optional_sfixed64' => -47,
'optional_float' => 1.5,
'optional_double' => 1.6,
'optional_bool' => true,
'optional_string' => 'a',
'optional_bytes' => 'bbbb',
'optional_enum' => TestEnum::ONE,
'optional_message' => new Sub([
'a' => 33
]),
'repeated_int32' => [-42, -52],
'repeated_int64' => [-43, -53],
'repeated_uint32' => [42, 52],
'repeated_uint64' => [43, 53],
'repeated_sint32' => [-44, -54],
'repeated_sint64' => [-45, -55],
'repeated_fixed32' => [46, 56],
'repeated_fixed64' => [47, 57],
'repeated_sfixed32' => [-46, -56],
'repeated_sfixed64' => [-47, -57],
'repeated_float' => [1.5, 2.5],
'repeated_double' => [1.6, 2.6],
'repeated_bool' => [true, false],
'repeated_string' => ['a', 'c'],
'repeated_bytes' => ['bbbb', 'dddd'],
'repeated_enum' => [TestEnum::ZERO, TestEnum::ONE],
'repeated_message' => [new Sub(['a' => 34]),
new Sub(['a' => 35])],
'map_int32_int32' => [-62 => -62],
'map_int64_int64' => [-63 => -63],
'map_uint32_uint32' => [62 => 62],
'map_uint64_uint64' => [63 => 63],
'map_sint32_sint32' => [-64 => -64],
'map_sint64_sint64' => [-65 => -65],
'map_fixed32_fixed32' => [66 => 66],
'map_fixed64_fixed64' => [67 => 67],
'map_sfixed32_sfixed32' => [-68 => -68],
'map_sfixed64_sfixed64' => [-69 => -69],
'map_int32_float' => [1 => 3.5],
'map_int32_double' => [1 => 3.6],
'map_bool_bool' => [true => true],
'map_string_string' => ['e' => 'e'],
'map_int32_bytes' => [1 => 'ffff'],
'map_int32_enum' => [1 => TestEnum::ONE],
'map_int32_message' => [1 => new Sub(['a' => 36])],
]);
$this->assertSame([
'optionalInt32' => -42,
'optionalInt64' => '-43',
'optionalUint32' => 42,
'optionalUint64' => '43',
'optionalSint32' => -44,
'optionalSint64' => '-45',
'optionalFixed32' => 46,
'optionalFixed64' => '47',
'optionalSfixed32' => -46,
'optionalSfixed64' => '-47',
'optionalFloat' => 1.5,
'optionalDouble' => 1.6,
'optionalBool' => true,
'optionalString' => 'a',
'optionalBytes' => 'YmJiYg==',
'optionalEnum' => 'ONE',
'optionalMessage' => [
'a' => 33,
],
'repeatedInt32' => [
-42,
-52,
],
'repeatedInt64' => [
'-43',
'-53',
],
'repeatedUint32' => [
42,
52,
],
'repeatedUint64' => [
'43',
'53',
],
'repeatedSint32' => [
-44,
-54,
],
'repeatedSint64' => [
'-45',
'-55',
],
'repeatedFixed32' => [
46,
56,
],
'repeatedFixed64' => [
'47',
'57',
],
'repeatedSfixed32' => [
-46,
-56,
],
'repeatedSfixed64' => [
'-47',
'-57',
],
'repeatedFloat' => [
1.5,
2.5,
],
'repeatedDouble' => [
1.6,
2.6,
],
'repeatedBool' => [
true,
false,
],
'repeatedString' => [
'a',
'c',
],
'repeatedBytes' => [
'YmJiYg==',
'ZGRkZA==',
],
'repeatedEnum' => [
'ZERO',
'ONE',
],
'repeatedMessage' => [
[
'a' => 34,
],
[
'a' => 35,
],
],
'mapInt32Int32' => [
-62 => -62,
],
'mapInt64Int64' => [
-63 => '-63',
],
'mapUint32Uint32' => [
62 => 62,
],
'mapUint64Uint64' => [
63 => '63',
],
'mapSint32Sint32' => [
-64 => -64,
],
'mapSint64Sint64' => [
-65 => '-65',
],
'mapFixed32Fixed32' => [
66 => 66,
],
'mapFixed64Fixed64' => [
67 => '67',
],
'mapSfixed32Sfixed32' => [
-68 => -68,
],
'mapSfixed64Sfixed64' => [
-69 => '-69',
],
'mapInt32Float' => [
1 => 3.5,
],
'mapInt32Double' => [
1 => 3.6,
],
'mapBoolBool' => [
'true' => true,
],
'mapStringString' => [
'e' => 'e',
],
'mapInt32Bytes' => [
1 => 'ZmZmZg==',
],
'mapInt32Enum' => [
1 => 'ONE',
],
'mapInt32Message' => [
1 => ['a' => 36],
],
], $m->__debugInfo());
}
private function generateRandomString($length = 10)
{
$randomString = str_repeat("+", $length);
for ($i = 0; $i < $length; $i++) {
$randomString[$i] = chr(rand(0, 255));
}
return $randomString;
}
}

@ -170,12 +170,12 @@ def _cc_file_list_aspect_impl(target, ctx):
return [CcFileList(
hdrs = _get_transitive_sources(
_flatten_target_files(rule_attr.hdrs).to_list(),
_flatten_target_files(getattr(rule_attr, "hdrs", [])).to_list(),
"hdrs",
rule_attr.deps,
),
textual_hdrs = _get_transitive_sources(
_flatten_target_files(rule_attr.textual_hdrs).to_list(),
_flatten_target_files(getattr(rule_attr, "textual_hdrs", [])).to_list(),
"textual_hdrs",
rule_attr.deps,
),

@ -11,7 +11,7 @@ load(
)
load(
"//bazel:upb_proto_library.bzl",
"upb_proto_library",
"upb_c_proto_library",
)
load(
"//protos/bazel:upb_cc_proto_library.bzl",
@ -60,7 +60,7 @@ proto_library(
deps = [":no_package_proto"],
)
upb_proto_library(
upb_c_proto_library(
name = "test_model_upb_proto",
visibility = [
"//protos:__pkg__",

@ -114,7 +114,7 @@ TEST(CppGeneratedCode, ScalarInt32) {
// Test int32 defaults.
EXPECT_EQ(testModel.value(), 0);
EXPECT_FALSE(testModel.has_value());
// Floating point defautls.
// Floating point defaults.
EXPECT_EQ(std::numeric_limits<float>::infinity(),
testModel.float_value_with_default());
EXPECT_EQ(-std::numeric_limits<double>::infinity(),

@ -246,6 +246,7 @@ pkg_files(
"//upb/mini_descriptor:source_files",
"//upb/mini_table:source_files",
"//upb/port:source_files",
"//upb/reflection:source_files",
"//upb/text:source_files",
"//upb/util:source_files",
"//upb/wire:source_files",

@ -13,6 +13,8 @@ import os
import sys
import warnings
_GOOGLE3_PYTHON_UPB_DEFAULT = False
def _ApiVersionToImplementationType(api_version):
if api_version == 2:

@ -12,13 +12,15 @@ __author__ = 'robinson@google.com (Will Robinson)'
import unittest
import warnings
from google.protobuf import descriptor_pb2
from google.protobuf.internal import api_implementation
from google.protobuf.internal import test_util
from google.protobuf import descriptor
from google.protobuf import descriptor_pb2
from google.protobuf import descriptor_pool
from google.protobuf import symbol_database
from google.protobuf import text_format
from google.protobuf.internal import api_implementation
from google.protobuf.internal import test_util
from google.protobuf.internal import _parameterized
from google.protobuf import unittest_custom_options_pb2
from google.protobuf import unittest_import_pb2
from google.protobuf import unittest_pb2
@ -1169,7 +1171,6 @@ class MakeDescriptorTest(unittest.TestCase):
self.assertEqual(result.fields[0].cpp_type,
descriptor.FieldDescriptor.CPPTYPE_UINT64)
def testMakeDescriptorWithOptions(self):
descriptor_proto = descriptor_pb2.DescriptorProto()
aggregate_message = unittest_custom_options_pb2.AggregateMessage
@ -1214,5 +1215,63 @@ class MakeDescriptorTest(unittest.TestCase):
json_names[index])
class FeaturesTest(_parameterized.TestCase):
# TODO Add _features for upb and C++.
@_parameterized.named_parameters([
('File', lambda: descriptor_pb2.DESCRIPTOR),
('Message', lambda: descriptor_pb2.FeatureSet.DESCRIPTOR),
(
'Enum',
lambda: descriptor_pb2.FeatureSet.FieldPresence.DESCRIPTOR,
),
(
'Field',
lambda: descriptor_pb2.FeatureSet.DESCRIPTOR.fields_by_name[
'enum_type'
],
),
])
@unittest.skipIf(
api_implementation.Type() != 'python',
'Features field is only available with the pure python implementation',
)
def testDescriptorProtoDefaultFeatures(self, desc):
self.assertEqual(
desc()._features.field_presence,
descriptor_pb2.FeatureSet.FieldPresence.EXPLICIT,
)
self.assertEqual(
desc()._features.enum_type,
descriptor_pb2.FeatureSet.EnumType.CLOSED,
)
self.assertEqual(
desc()._features.repeated_field_encoding,
descriptor_pb2.FeatureSet.RepeatedFieldEncoding.EXPANDED,
)
# TODO Add _features for upb and C++.
@unittest.skipIf(
api_implementation.Type() != 'python',
'Features field is only available with the pure python implementation',
)
def testDescriptorProtoOverrideFeatures(self):
desc = descriptor_pb2.SourceCodeInfo.Location.DESCRIPTOR.fields_by_name[
'path'
]
self.assertEqual(
desc._features.field_presence,
descriptor_pb2.FeatureSet.FieldPresence.EXPLICIT,
)
self.assertEqual(
desc._features.enum_type,
descriptor_pb2.FeatureSet.EnumType.CLOSED,
)
self.assertEqual(
desc._features.repeated_field_encoding,
descriptor_pb2.FeatureSet.RepeatedFieldEncoding.PACKED,
)
if __name__ == '__main__':
unittest.main()

@ -1462,6 +1462,7 @@ class Proto2ReflectionTest(unittest.TestCase):
if api_implementation.Type() != 'python':
return
file = descriptor.FileDescriptor(name='foo.proto', package='')
FieldDescriptor = descriptor.FieldDescriptor
foo_field_descriptor = FieldDescriptor(
name='foo_field', full_name='MyProto.foo_field',
@ -1470,7 +1471,7 @@ class Proto2ReflectionTest(unittest.TestCase):
label=FieldDescriptor.LABEL_OPTIONAL, default_value=0,
containing_type=None, message_type=None, enum_type=None,
is_extension=False, extension_scope=None,
options=descriptor_pb2.FieldOptions(),
options=descriptor_pb2.FieldOptions(), file=file,
# pylint: disable=protected-access
create_key=descriptor._internal_create_key)
mydescriptor = descriptor.Descriptor(
@ -1478,6 +1479,7 @@ class Proto2ReflectionTest(unittest.TestCase):
containing_type=None, nested_types=[], enum_types=[],
fields=[foo_field_descriptor], extensions=[],
options=descriptor_pb2.MessageOptions(),
file=file,
# pylint: disable=protected-access
create_key=descriptor._internal_create_key)

@ -854,10 +854,11 @@ class TextFormatParserTests(TextFormatBase):
# itself for string fields. It also demonstrates escaped binary data.
# The ur"" string prefix is unfortunately missing from Python 3
# so we resort to double escaping our \s so that they come through.
_UNICODE_SAMPLE = u"""
_UNICODE_SAMPLE = """
optional_bytes: 'Á short desçription'
optional_string: 'Á short desçription'
repeated_bytes: '\\303\\201 short des\\303\\247ription'
repeated_bytes: '\\u00c1 short des\\u00e7ription'
repeated_bytes: '\\x12\\x34\\x56\\x78\\x90\\xab\\xcd\\xef'
repeated_string: '\\xd0\\x9f\\xd1\\x80\\xd0\\xb8\\xd0\\xb2\\xd0\\xb5\\xd1\\x82'
"""
@ -873,8 +874,9 @@ class TextFormatParserTests(TextFormatBase):
self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES)
self.assertEqual(m.optional_string, self._GOLDEN_UNICODE)
self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES)
# repeated_bytes[1] contained simple \ escaped non-UTF-8 raw binary data.
self.assertEqual(m.repeated_bytes[1], self._GOLDEN_BYTES_1)
self.assertEqual(m.repeated_bytes[1], self._GOLDEN_BYTES)
# repeated_bytes[2] contained simple \ escaped non-UTF-8 raw binary data.
self.assertEqual(m.repeated_bytes[2], self._GOLDEN_BYTES_1)
# repeated_string[0] contained \ escaped data representing the UTF-8
# representation of _GOLDEN_STR_0 - it needs to decode as such.
self.assertEqual(m.repeated_string[0], self._GOLDEN_STR_0)
@ -885,8 +887,9 @@ class TextFormatParserTests(TextFormatBase):
self.assertEqual(m.optional_bytes, self._GOLDEN_BYTES)
self.assertEqual(m.optional_string, self._GOLDEN_UNICODE)
self.assertEqual(m.repeated_bytes[0], self._GOLDEN_BYTES)
self.assertEqual(m.repeated_bytes[1], self._GOLDEN_BYTES)
# repeated_bytes[1] contained simple \ escaped non-UTF-8 raw binary data.
self.assertEqual(m.repeated_bytes[1], self._GOLDEN_BYTES_1)
self.assertEqual(m.repeated_bytes[2], self._GOLDEN_BYTES_1)
# repeated_string[0] contained \ escaped data representing the UTF-8
# representation of _GOLDEN_STR_0 - it needs to decode as such.
self.assertEqual(m.repeated_string[0], self._GOLDEN_STR_0)

@ -79,7 +79,10 @@ def CUnescape(text: str) -> bytes:
# allow single-digit hex escapes (like '\xf').
result = _CUNESCAPE_HEX.sub(ReplaceHex, text)
return (result.encode('utf-8') # Make it bytes to allow decode.
.decode('unicode_escape')
# Make it bytes again to return the proper type.
.encode('raw_unicode_escape'))
# Replaces Unicode escape sequences with their character equivalents.
result = result.encode('raw_unicode_escape').decode('raw_unicode_escape')
# Encode Unicode characters as UTF-8, then decode to Latin-1 escaping
# unprintable characters.
result = result.encode('utf-8').decode('unicode_escape')
# Convert Latin-1 text back to a byte string (latin-1 codec also works here).
return result.encode('latin-1')

@ -5318,8 +5318,10 @@ bool upb_Arena_Fuse(upb_Arena* a1, upb_Arena* a2) {
}
}
void upb_Arena_IncRefFor(upb_Arena* arena, const void* owner) {
bool upb_Arena_IncRefFor(upb_Arena* arena, const void* owner) {
_upb_ArenaRoot r;
if (upb_Arena_HasInitialBlock(arena)) return false;
retry:
r = _upb_Arena_FindRoot(arena);
if (upb_Atomic_CompareExchangeWeak(
@ -5328,7 +5330,7 @@ retry:
_upb_Arena_RefCountFromTagged(r.tagged_count) + 1),
memory_order_release, memory_order_acquire)) {
// We incremented it successfully, so we are done.
return;
return true;
}
// We failed update due to parent switching on the arena.
goto retry;

@ -563,7 +563,7 @@ UPB_API upb_Arena* upb_Arena_Init(void* mem, size_t n, upb_alloc* alloc);
UPB_API void upb_Arena_Free(upb_Arena* a);
UPB_API bool upb_Arena_Fuse(upb_Arena* a, upb_Arena* b);
void upb_Arena_IncRefFor(upb_Arena* arena, const void* owner);
bool upb_Arena_IncRefFor(upb_Arena* arena, const void* owner);
void upb_Arena_DecRefFor(upb_Arena* arena, const void* owner);
void* _upb_Arena_SlowMalloc(upb_Arena* a, size_t size);

@ -52,6 +52,7 @@ PROTOBUF_SHARED = [
"optional.rs",
"primitive.rs",
"proxied.rs",
"repeated.rs",
"shared.rs",
"string.rs",
"vtable.rs",
@ -92,8 +93,14 @@ rust_library(
name = "protobuf_cpp",
srcs = PROTOBUF_SHARED + ["cpp.rs"],
crate_root = "shared.rs",
proc_macro_deps = [
"@crate_index//:paste",
],
rustc_flags = ["--cfg=cpp_kernel"],
deps = [":utf8"],
deps = [
":utf8",
"//rust/cpp_kernel:cpp_api",
],
)
rust_test(

@ -8,7 +8,6 @@ load("@rules_rust//rust/private:providers.bzl", "CrateInfo", "DepInfo", "DepVari
# buildifier: disable=bzl-visibility
load("@rules_rust//rust/private:rustc.bzl", "rustc_compile_action")
load("@rules_rust//rust:defs.bzl", "rust_common")
load("//bazel:upb_proto_library.bzl", "UpbWrappedCcInfo", "upb_proto_library_aspect")
load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
@ -173,7 +172,7 @@ def _compile_rust(ctx, attr, src, extra_srcs, deps):
ctx = ctx,
attr = attr,
toolchain = toolchain,
crate_info = rust_common.create_crate_info(
crate_info_dict = dict(
name = crate_name,
type = "rlib",
root = src,

@ -7,7 +7,8 @@
// Rust Protobuf runtime using the C++ kernel.
use crate::__internal::{Private, RawArena, RawMessage};
use crate::__internal::{Private, RawArena, RawMessage, RawRepeatedField};
use paste::paste;
use std::alloc::Layout;
use std::cell::UnsafeCell;
use std::fmt;
@ -35,6 +36,7 @@ pub struct Arena {
impl Arena {
/// Allocates a fresh arena.
#[inline]
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self { ptr: NonNull::dangling(), _not_sync: PhantomData }
}
@ -182,6 +184,133 @@ pub fn copy_bytes_in_arena_if_needed_by_runtime<'a>(
val
}
/// RepeatedField impls delegate out to `extern "C"` functions exposed by
/// `cpp_api.h` and store either a RepeatedField* or a RepeatedPtrField*
/// depending on the type.
///
/// Note: even though this type is `Copy`, it should only be copied by
/// protobuf internals that can maintain mutation invariants:
///
/// - No concurrent mutation for any two fields in a message: this means
/// mutators cannot be `Send` but are `Sync`.
/// - If there are multiple accessible `Mut` to a single message at a time, they
/// must be different fields, and not be in the same oneof. As such, a `Mut`
/// cannot be `Clone` but *can* reborrow itself with `.as_mut()`, which
/// converts `&'b mut Mut<'a, T>` to `Mut<'b, T>`.
#[derive(Debug)]
pub struct RepeatedField<'msg, T: ?Sized> {
inner: RepeatedFieldInner<'msg>,
_phantom: PhantomData<&'msg mut T>,
}
/// CPP runtime-specific arguments for initializing a RepeatedField.
/// See RepeatedField comment about mutation invariants for when this type can
/// be copied.
#[derive(Clone, Copy, Debug)]
pub struct RepeatedFieldInner<'msg> {
pub raw: RawRepeatedField,
pub _phantom: PhantomData<&'msg ()>,
}
impl<'msg, T: ?Sized> RepeatedField<'msg, T> {
pub fn from_inner(_private: Private, inner: RepeatedFieldInner<'msg>) -> Self {
RepeatedField { inner, _phantom: PhantomData }
}
}
// These use manual impls instead of derives to avoid unnecessary bounds on `T`.
// This problem is referred to as "perfect derive".
// https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/
impl<'msg, T: ?Sized> Copy for RepeatedField<'msg, T> {}
impl<'msg, T: ?Sized> Clone for RepeatedField<'msg, T> {
fn clone(&self) -> RepeatedField<'msg, T> {
*self
}
}
pub trait RepeatedScalarOps {
fn new_repeated_field() -> RawRepeatedField;
fn push(f: RawRepeatedField, v: Self);
fn len(f: RawRepeatedField) -> usize;
fn get(f: RawRepeatedField, i: usize) -> Self;
fn set(f: RawRepeatedField, i: usize, v: Self);
fn copy_from(src: RawRepeatedField, dst: RawRepeatedField);
}
macro_rules! impl_repeated_scalar_ops {
($($t: ty),*) => {
paste! { $(
extern "C" {
fn [< __pb_rust_RepeatedField_ $t _new >]() -> RawRepeatedField;
fn [< __pb_rust_RepeatedField_ $t _add >](f: RawRepeatedField, v: $t);
fn [< __pb_rust_RepeatedField_ $t _size >](f: RawRepeatedField) -> usize;
fn [< __pb_rust_RepeatedField_ $t _get >](f: RawRepeatedField, i: usize) -> $t;
fn [< __pb_rust_RepeatedField_ $t _set >](f: RawRepeatedField, i: usize, v: $t);
fn [< __pb_rust_RepeatedField_ $t _copy_from >](src: RawRepeatedField, dst: RawRepeatedField);
}
impl RepeatedScalarOps for $t {
fn new_repeated_field() -> RawRepeatedField {
unsafe { [< __pb_rust_RepeatedField_ $t _new >]() }
}
fn push(f: RawRepeatedField, v: Self) {
unsafe { [< __pb_rust_RepeatedField_ $t _add >](f, v) }
}
fn len(f: RawRepeatedField) -> usize {
unsafe { [< __pb_rust_RepeatedField_ $t _size >](f) }
}
fn get(f: RawRepeatedField, i: usize) -> Self {
unsafe { [< __pb_rust_RepeatedField_ $t _get >](f, i) }
}
fn set(f: RawRepeatedField, i: usize, v: Self) {
unsafe { [< __pb_rust_RepeatedField_ $t _set >](f, i, v) }
}
fn copy_from(src: RawRepeatedField, dst: RawRepeatedField) {
unsafe { [< __pb_rust_RepeatedField_ $t _copy_from >](src, dst) }
}
}
)* }
};
}
impl_repeated_scalar_ops!(i32, u32, i64, u64, f32, f64, bool);
impl<'msg, T: RepeatedScalarOps> RepeatedField<'msg, T> {
#[allow(clippy::new_without_default, dead_code)]
/// new() is not currently used in our normal pathways, it is only used
/// for testing. Existing `RepeatedField<>`s are owned by, and retrieved
/// from, the containing `Message`.
pub fn new() -> Self {
Self::from_inner(
Private,
RepeatedFieldInner::<'msg> { raw: T::new_repeated_field(), _phantom: PhantomData },
)
}
pub fn push(&mut self, val: T) {
T::push(self.inner.raw, val)
}
pub fn len(&self) -> usize {
T::len(self.inner.raw)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<T> {
if index >= self.len() {
return None;
}
Some(T::get(self.inner.raw, index))
}
pub fn set(&mut self, index: usize, val: T) {
if index >= self.len() {
return;
}
T::set(self.inner.raw, index, val)
}
pub fn copy_from(&mut self, src: &RepeatedField<'_, T>) {
T::copy_from(src.inner.raw, self.inner.raw)
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -201,4 +330,27 @@ mod tests {
let serialized_data = SerializedData { data: NonNull::new(ptr).unwrap(), len: len };
assert_eq!(&*serialized_data, b"Hello world");
}
#[test]
fn repeated_field() {
let mut r = RepeatedField::<i32>::new();
assert_eq!(r.len(), 0);
r.push(32);
assert_eq!(r.get(0), Some(32));
let mut r = RepeatedField::<u32>::new();
assert_eq!(r.len(), 0);
r.push(32);
assert_eq!(r.get(0), Some(32));
let mut r = RepeatedField::<f64>::new();
assert_eq!(r.len(), 0);
r.push(0.1234f64);
assert_eq!(r.get(0), Some(0.1234));
let mut r = RepeatedField::<bool>::new();
assert_eq!(r.len(), 0);
r.push(true);
assert_eq!(r.get(0), Some(true));
}
}

@ -4,13 +4,14 @@ load("@rules_rust//rust:defs.bzl", "rust_library")
cc_library(
name = "cpp_api",
srcs = ["cpp_api.cc"],
hdrs = ["cpp_api.h"],
visibility = [
"//src/google/protobuf:__subpackages__",
"//rust:__subpackages__",
],
deps = [
":rust_alloc_for_cpp_api",
":rust_alloc_for_cpp_api", # buildcleaner: keep
"//:protobuf_nowkt",
],
)

@ -0,0 +1,39 @@
#include "google/protobuf/repeated_field.h"
extern "C" {
#define expose_repeated_field_methods(ty, rust_ty) \
google::protobuf::RepeatedField<ty>* __pb_rust_RepeatedField_##rust_ty##_new() { \
return new google::protobuf::RepeatedField<ty>(); \
} \
void __pb_rust_RepeatedField_##rust_ty##_add(google::protobuf::RepeatedField<ty>* r, \
ty val) { \
r->Add(val); \
} \
size_t __pb_rust_RepeatedField_##rust_ty##_size( \
google::protobuf::RepeatedField<ty>* r) { \
return r->size(); \
} \
ty __pb_rust_RepeatedField_##rust_ty##_get(google::protobuf::RepeatedField<ty>* r, \
size_t index) { \
return r->Get(index); \
} \
void __pb_rust_RepeatedField_##rust_ty##_set(google::protobuf::RepeatedField<ty>* r, \
size_t index, ty val) { \
return r->Set(index, val); \
} \
void __pb_rust_RepeatedField_##rust_ty##_copy_from( \
google::protobuf::RepeatedField<ty> const& src, google::protobuf::RepeatedField<ty>& dst) { \
dst.CopyFrom(src); \
}
expose_repeated_field_methods(int32_t, i32);
expose_repeated_field_methods(uint32_t, u32);
expose_repeated_field_methods(float, f32);
expose_repeated_field_methods(double, f64);
expose_repeated_field_methods(bool, bool);
expose_repeated_field_methods(uint64_t, u64);
expose_repeated_field_methods(int64_t, i64);
#undef expose_repeated_field_methods
}

@ -10,8 +10,8 @@
//! generated code.
pub use crate::vtable::{
new_vtable_field_entry, BytesMutVTable, BytesOptionalMutVTable, PrimitiveVTable,
RawVTableMutator,
new_vtable_field_entry, BytesMutVTable, BytesOptionalMutVTable, PrimitiveOptionalMutVTable,
PrimitiveVTable, RawVTableMutator,
};
use std::ptr::NonNull;
use std::slice;
@ -51,6 +51,17 @@ mod _opaque_pointees {
_data: [u8; 0],
_marker: std::marker::PhantomData<(*mut u8, ::std::marker::PhantomPinned)>,
}
/// Opaque pointee for [`RawRepeatedField`]
///
/// This type is not meant to be dereferenced in Rust code.
/// It is only meant to provide type safety for raw pointers
/// which are manipulated behind FFI.
#[repr(C)]
pub struct RawRepeatedFieldData {
_data: [u8; 0],
_marker: std::marker::PhantomData<(*mut u8, ::std::marker::PhantomPinned)>,
}
}
/// A raw pointer to the underlying message for this runtime.
@ -59,6 +70,9 @@ pub type RawMessage = NonNull<_opaque_pointees::RawMessageData>;
/// A raw pointer to the underlying arena for this runtime.
pub type RawArena = NonNull<_opaque_pointees::RawArenaData>;
/// A raw pointer to the underlying repeated field container for this runtime.
pub type RawRepeatedField = NonNull<_opaque_pointees::RawRepeatedFieldData>;
/// Represents an ABI-stable version of `NonNull<[u8]>`/`string_view` (a
/// borrowed slice of bytes) for FFI use only.
///

@ -7,22 +7,39 @@
use crate::__internal::Private;
use crate::__runtime::InnerPrimitiveMut;
use crate::vtable::{PrimitiveVTable, ProxiedWithRawVTable};
use crate::{Mut, MutProxy, Proxied, SettableValue, View, ViewProxy};
use crate::repeated::RepeatedMut;
use crate::vtable::{
PrimitiveOptionalMutVTable, PrimitiveVTable, ProxiedWithRawOptionalVTable,
ProxiedWithRawVTable, RawVTableOptionalMutatorData,
};
use crate::{Mut, MutProxy, Proxied, ProxiedWithPresence, SettableValue, View, ViewProxy};
#[derive(Debug)]
pub struct PrimitiveMut<'a, T: ProxiedWithRawVTable> {
pub struct SingularPrimitiveMut<'a, T: ProxiedWithRawVTable> {
inner: InnerPrimitiveMut<'a, T>,
}
#[derive(Debug)]
pub enum PrimitiveMut<'a, T: ProxiedWithRawVTable> {
Singular(SingularPrimitiveMut<'a, T>),
Repeated(RepeatedMut<'a, T>, usize),
}
impl<'a, T: ProxiedWithRawVTable> PrimitiveMut<'a, T> {
#[doc(hidden)]
pub fn from_singular(_private: Private, inner: InnerPrimitiveMut<'a, T>) -> Self {
PrimitiveMut::Singular(SingularPrimitiveMut::from_inner(_private, inner))
}
}
impl<'a, T: ProxiedWithRawVTable> SingularPrimitiveMut<'a, T> {
#[doc(hidden)]
pub fn from_inner(_private: Private, inner: InnerPrimitiveMut<'a, T>) -> Self {
Self { inner }
}
}
unsafe impl<'a, T: ProxiedWithRawVTable> Sync for PrimitiveMut<'a, T> {}
unsafe impl<'a, T: ProxiedWithRawVTable> Sync for SingularPrimitiveMut<'a, T> {}
macro_rules! impl_singular_primitives {
($($t:ty),*) => {
@ -44,6 +61,29 @@ macro_rules! impl_singular_primitives {
}
}
impl<'a> PrimitiveMut<'a, $t> {
pub fn get(&self) -> View<'_, $t> {
match self {
PrimitiveMut::Singular(s) => {
s.get()
}
PrimitiveMut::Repeated(r, i) => {
r.get().get(*i).unwrap()
}
}
}
pub fn set(&mut self, val: impl SettableValue<$t>) {
val.set_on(Private, self.as_mut());
}
pub fn clear(&mut self) {
// The default value for a boolean field is false and 0 for numerical types. It
// matches the Rust default values for corresponding types. Let's use this fact.
SettableValue::<$t>::set_on(<$t>::default(), Private, MutProxy::as_mut(self));
}
}
impl<'a> ViewProxy<'a> for PrimitiveMut<'a, $t> {
type Proxied = $t;
@ -58,7 +98,14 @@ macro_rules! impl_singular_primitives {
impl<'a> MutProxy<'a> for PrimitiveMut<'a, $t> {
fn as_mut(&mut self) -> Mut<'_, Self::Proxied> {
PrimitiveMut::from_inner(Private, self.inner)
match self {
PrimitiveMut::Singular(s) => {
PrimitiveMut::Singular(s.as_mut())
}
PrimitiveMut::Repeated(r, i) => {
PrimitiveMut::Repeated(r.as_mut(), *i)
}
}
}
fn into_mut<'shorter>(self) -> Mut<'shorter, Self::Proxied>
@ -70,23 +117,23 @@ macro_rules! impl_singular_primitives {
impl SettableValue<$t> for $t {
fn set_on(self, _private: Private, mutator: Mut<'_, $t>) {
unsafe { (mutator.inner).set(self) };
match mutator {
PrimitiveMut::Singular(s) => {
unsafe { (s.inner).set(self) };
}
PrimitiveMut::Repeated(mut r, i) => {
r.set(i, self);
}
}
}
impl<'a> PrimitiveMut<'a, $t> {
pub fn set(&mut self, val: impl SettableValue<$t>) {
val.set_on(Private, self.as_mut());
}
impl<'a> SingularPrimitiveMut<'a, $t> {
pub fn get(&self) -> $t {
self.inner.get()
}
pub fn clear(&mut self) {
// The default value for a boolean field is false and 0 for numerical types. It
// matches the Rust default values for corresponding types. Let's use this fact.
SettableValue::<$t>::set_on(<$t>::default(), Private, MutProxy::as_mut(self));
pub fn as_mut(&mut self) -> SingularPrimitiveMut<'_, $t> {
SingularPrimitiveMut::from_inner(Private, self.inner)
}
}
@ -101,7 +148,35 @@ macro_rules! impl_singular_primitives {
}
fn make_mut(_private: Private, inner: InnerPrimitiveMut<'_, Self>) -> Mut<'_, Self> {
PrimitiveMut::from_inner(Private, inner)
PrimitiveMut::Singular(SingularPrimitiveMut::from_inner(Private, inner))
}
}
impl ProxiedWithPresence for $t {
type PresentMutData<'a> = RawVTableOptionalMutatorData<'a, $t>;
type AbsentMutData<'a> = RawVTableOptionalMutatorData<'a, $t>;
fn clear_present_field(
present_mutator: Self::PresentMutData<'_>,
) -> Self::AbsentMutData<'_> {
present_mutator.clear()
}
fn set_absent_to_default(
absent_mutator: Self::AbsentMutData<'_>,
) -> Self::PresentMutData<'_> {
absent_mutator.set_absent_to_default()
}
}
impl ProxiedWithRawOptionalVTable for $t {
type OptionalVTable = PrimitiveOptionalMutVTable<$t>;
fn upcast_vtable(
_private: Private,
optional_vtable: &'static Self::OptionalVTable,
) -> &'static Self::VTable {
&optional_vtable.base
}
}
)*

@ -0,0 +1,233 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
/// Repeated scalar fields are implemented around the runtime-specific
/// `RepeatedField` struct. `RepeatedField` stores an opaque pointer to the
/// runtime-specific representation of a repeated scalar (`upb_Array*` on upb,
/// and `RepeatedField<T>*` on cpp).
use std::marker::PhantomData;
use crate::{
Mut, MutProxy, Proxied, SettableValue, View, ViewProxy,
__internal::{Private, RawRepeatedField},
__runtime::{RepeatedField, RepeatedFieldInner},
primitive::PrimitiveMut,
vtable::ProxiedWithRawVTable,
};
#[derive(Clone, Copy)]
pub struct RepeatedFieldRef<'a> {
pub repeated_field: RawRepeatedField,
pub _phantom: PhantomData<&'a mut ()>,
}
unsafe impl<'a> Send for RepeatedFieldRef<'a> {}
unsafe impl<'a> Sync for RepeatedFieldRef<'a> {}
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct RepeatedView<'a, T: ?Sized> {
inner: RepeatedField<'a, T>,
}
unsafe impl<'a, T: ProxiedWithRawVTable> Sync for RepeatedView<'a, T> {}
unsafe impl<'a, T: ProxiedWithRawVTable> Send for RepeatedView<'a, T> {}
impl<'msg, T: ?Sized> RepeatedView<'msg, T> {
pub fn from_inner(_private: Private, inner: RepeatedFieldInner<'msg>) -> Self {
Self { inner: RepeatedField::<'msg>::from_inner(_private, inner) }
}
}
pub struct RepeatedFieldIter<'a, T> {
inner: RepeatedField<'a, T>,
current_index: usize,
}
impl<'a, T> std::fmt::Debug for RepeatedView<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("RepeatedView").finish()
}
}
#[repr(transparent)]
#[derive(Debug)]
pub struct RepeatedMut<'a, T: ?Sized> {
inner: RepeatedField<'a, T>,
}
unsafe impl<'a, T: ProxiedWithRawVTable> Sync for RepeatedMut<'a, T> {}
impl<'msg, T: ?Sized> RepeatedMut<'msg, T> {
pub fn from_inner(_private: Private, inner: RepeatedFieldInner<'msg>) -> Self {
Self { inner: RepeatedField::from_inner(_private, inner) }
}
pub fn as_mut(&self) -> RepeatedMut<'_, T> {
Self { inner: self.inner }
}
}
impl<'a, T> std::ops::Deref for RepeatedMut<'a, T> {
type Target = RepeatedView<'a, T>;
fn deref(&self) -> &Self::Target {
// SAFETY:
// - `Repeated{View,Mut}<'a, T>` are both `#[repr(transparent)]` over
// `RepeatedField<'a, T>`.
// - `RepeatedField` is a type alias for `NonNull`.
unsafe { &*(self as *const Self as *const RepeatedView<'a, T>) }
}
}
pub struct RepeatedFieldIterMut<'a, T> {
inner: RepeatedMut<'a, T>,
current_index: usize,
}
pub struct Repeated<T>(PhantomData<T>);
macro_rules! impl_repeated_primitives {
($($t:ty),*) => {
$(
impl Proxied for Repeated<$t> {
type View<'a> = RepeatedView<'a, $t>;
type Mut<'a> = RepeatedMut<'a, $t>;
}
impl<'a> ViewProxy<'a> for RepeatedView<'a, $t> {
type Proxied = Repeated<$t>;
fn as_view(&self) -> View<'_, Self::Proxied> {
*self
}
fn into_view<'shorter>(self) -> View<'shorter, Self::Proxied>
where 'a: 'shorter,
{
RepeatedView { inner: self.inner }
}
}
impl<'a> ViewProxy<'a> for RepeatedMut<'a, $t> {
type Proxied = Repeated<$t>;
fn as_view(&self) -> View<'_, Self::Proxied> {
**self
}
fn into_view<'shorter>(self) -> View<'shorter, Self::Proxied>
where 'a: 'shorter,
{
*self.into_mut::<'shorter>()
}
}
impl<'a> MutProxy<'a> for RepeatedMut<'a, $t> {
fn as_mut(&mut self) -> Mut<'_, Self::Proxied> {
RepeatedMut { inner: self.inner }
}
fn into_mut<'shorter>(self) -> Mut<'shorter, Self::Proxied>
where 'a: 'shorter,
{
RepeatedMut { inner: self.inner }
}
}
impl <'a> SettableValue<Repeated<$t>> for RepeatedView<'a, $t> {
fn set_on(self, _private: Private, mut mutator: Mut<'_, Repeated<$t>>) {
mutator.copy_from(self);
}
}
impl<'a> RepeatedView<'a, $t> {
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: usize) -> Option<$t> {
self.inner.get(index)
}
pub fn iter(&self) -> RepeatedFieldIter<'_, $t> {
(*self).into_iter()
}
}
impl<'a> RepeatedMut<'a, $t> {
pub fn push(&mut self, val: $t) {
self.inner.push(val)
}
pub fn set(&mut self, index: usize, val: $t) {
self.inner.set(index, val)
}
pub fn get_mut(&mut self, index: usize) -> Option<Mut<'_, $t>> {
if index >= self.len() {
return None;
}
Some(PrimitiveMut::Repeated(self.as_mut(), index))
}
pub fn iter(&self) -> RepeatedFieldIter<'_, $t> {
self.as_view().into_iter()
}
pub fn iter_mut(&mut self) -> RepeatedFieldIterMut<'_, $t> {
self.as_mut().into_iter()
}
pub fn copy_from(&mut self, src: RepeatedView<'_, $t>) {
self.inner.copy_from(&src.inner);
}
}
impl<'a> std::iter::Iterator for RepeatedFieldIter<'a, $t> {
type Item = $t;
fn next(&mut self) -> Option<Self::Item> {
let val = self.inner.get(self.current_index);
if val.is_some() {
self.current_index += 1;
}
val
}
}
impl<'a> std::iter::IntoIterator for RepeatedView<'a, $t> {
type Item = $t;
type IntoIter = RepeatedFieldIter<'a, $t>;
fn into_iter(self) -> Self::IntoIter {
RepeatedFieldIter { inner: self.inner, current_index: 0 }
}
}
impl <'a> std::iter::Iterator for RepeatedFieldIterMut<'a, $t> {
type Item = Mut<'a, $t>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_index >= self.inner.len() {
return None;
}
let elem = PrimitiveMut::Repeated(
// While this appears to allow mutable aliasing
// (multiple `Self::Item`s can co-exist), each `Item`
// only references a specific unique index.
RepeatedMut{ inner: self.inner.inner },
self.current_index,
);
self.current_index += 1;
Some(elem)
}
}
impl<'a> std::iter::IntoIterator for RepeatedMut<'a, $t> {
type Item = Mut<'a, $t>;
type IntoIter = RepeatedFieldIterMut<'a, $t>;
fn into_iter(self) -> Self::IntoIter {
RepeatedFieldIterMut { inner: self, current_index: 0 }
}
}
)*
}
}
impl_repeated_primitives!(i32, u32, bool, f32, f64, i64, u64);

@ -18,10 +18,11 @@ use std::fmt;
#[doc(hidden)]
pub mod __public {
pub use crate::optional::{AbsentField, FieldEntry, Optional, PresentField};
pub use crate::primitive::PrimitiveMut;
pub use crate::primitive::{PrimitiveMut, SingularPrimitiveMut};
pub use crate::proxied::{
Mut, MutProxy, Proxied, ProxiedWithPresence, SettableValue, View, ViewProxy,
};
pub use crate::repeated::{RepeatedFieldRef, RepeatedMut, RepeatedView};
pub use crate::string::{BytesMut, ProtoStr, ProtoStrMut};
}
pub use __public::*;
@ -46,6 +47,7 @@ mod macros;
mod optional;
mod primitive;
mod proxied;
mod repeated;
mod string;
mod vtable;

@ -8,7 +8,7 @@
#include <cstddef>
#include "absl/strings/string_view.h"
#include "google/protobuf/rust/cpp_kernel/cpp_api.h"
#include "rust/cpp_kernel/cpp_api.h"
#include "google/protobuf/unittest.pb.h"
extern "C" void MutateTestAllTypes(protobuf_unittest::TestAllTypes* msg) {

@ -151,6 +151,9 @@ rust_test(
"//rust:protobuf_cpp": "protobuf",
"//rust/test/shared:matchers_cpp": "matchers",
},
proc_macro_deps = [
"@crate_index//:paste",
],
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
@ -170,6 +173,9 @@ rust_test(
"//rust:protobuf_upb": "protobuf",
"//rust/test/shared:matchers_upb": "matchers",
},
proc_macro_deps = [
"@crate_index//:paste",
],
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
@ -187,15 +193,18 @@ rust_test(
srcs = ["accessors_proto3_test.rs"],
aliases = {
"//rust:protobuf_cpp": "protobuf",
"//rust/test/shared:matchers_cpp": "matchers",
},
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
],
deps = [
"//third_party/gtest_rust/googletest",
"//rust:protobuf_cpp",
"//rust/test:unittest_proto3_cc_rust_proto",
"//rust/test:unittest_proto3_optional_cc_rust_proto",
"//rust/test/shared:matchers_cpp",
],
)
@ -204,15 +213,18 @@ rust_test(
srcs = ["accessors_proto3_test.rs"],
aliases = {
"//rust:protobuf_upb": "protobuf",
"//rust/test/shared:matchers_upb": "matchers",
},
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
],
deps = [
"//third_party/gtest_rust/googletest",
"//rust:protobuf_upb",
"//rust/test:unittest_proto3_optional_upb_rust_proto",
"//rust/test:unittest_proto3_upb_rust_proto",
"//rust/test/shared:matchers_upb",
],
)
@ -245,11 +257,19 @@ rust_test(
rust_test(
name = "simple_nested_cpp_test",
srcs = ["simple_nested_test.rs"],
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
],
deps = ["//rust/test:nested_cc_rust_proto"],
)
rust_test(
name = "simple_nested_upb_test",
srcs = ["simple_nested_test.rs"],
tags = [
# TODO: Enable testing on arm once we support sanitizers for Rust on Arm.
"not_build:arm",
],
deps = ["//rust/test:nested_upb_rust_proto"],
)

@ -7,6 +7,8 @@
/// Tests covering accessors for singular bool, int32, int64, and bytes fields
/// on proto3.
use googletest::prelude::*;
use matchers::{is_set, is_unset};
use protobuf::Optional;
use unittest_proto3::proto3_unittest::{TestAllTypes, TestAllTypes_};
use unittest_proto3_optional::proto2_unittest::TestProto3Optional;
@ -14,31 +16,31 @@ use unittest_proto3_optional::proto2_unittest::TestProto3Optional;
#[test]
fn test_fixed32_accessors() {
let mut msg = TestAllTypes::new();
assert_eq!(msg.optional_fixed32(), 0);
assert_eq!(msg.optional_fixed32_mut().get(), 0);
assert_that!(msg.optional_fixed32(), eq(0));
assert_that!(msg.optional_fixed32_mut().get(), eq(0));
msg.optional_fixed32_mut().set(42);
assert_eq!(msg.optional_fixed32_mut().get(), 42);
assert_eq!(msg.optional_fixed32(), 42);
assert_that!(msg.optional_fixed32_mut().get(), eq(42));
assert_that!(msg.optional_fixed32(), eq(42));
msg.optional_fixed32_mut().clear();
assert_eq!(msg.optional_fixed32(), 0);
assert_eq!(msg.optional_fixed32_mut().get(), 0);
assert_that!(msg.optional_fixed32(), eq(0));
assert_that!(msg.optional_fixed32_mut().get(), eq(0));
}
#[test]
fn test_bool_accessors() {
let mut msg = TestAllTypes::new();
assert!(!msg.optional_bool());
assert!(!msg.optional_bool_mut().get());
assert_that!(msg.optional_bool(), eq(false));
assert_that!(msg.optional_bool_mut().get(), eq(false));
msg.optional_bool_mut().set(true);
assert!(msg.optional_bool());
assert!(msg.optional_bool_mut().get());
assert_that!(msg.optional_bool(), eq(true));
assert_that!(msg.optional_bool_mut().get(), eq(true));
msg.optional_bool_mut().clear();
assert!(!msg.optional_bool());
assert!(!msg.optional_bool_mut().get());
assert_that!(msg.optional_bool(), eq(false));
assert_that!(msg.optional_bool_mut().get(), eq(false));
}
#[test]
@ -46,73 +48,73 @@ fn test_bytes_accessors() {
let mut msg = TestAllTypes::new();
// Note: even though it's named 'optional_bytes', the field is actually not
// proto3 optional, so it does not support presence.
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_mut().get(), b"");
assert_that!(*msg.optional_bytes(), empty());
assert_that!(*msg.optional_bytes_mut().get(), empty());
msg.optional_bytes_mut().set(b"accessors_test");
assert_eq!(msg.optional_bytes(), b"accessors_test");
assert_eq!(msg.optional_bytes_mut().get(), b"accessors_test");
assert_that!(msg.optional_bytes(), eq(b"accessors_test"));
assert_that!(msg.optional_bytes_mut().get(), eq(b"accessors_test"));
{
let s = Vec::from(&b"hello world"[..]);
msg.optional_bytes_mut().set(&s[..]);
}
assert_eq!(msg.optional_bytes(), b"hello world");
assert_eq!(msg.optional_bytes_mut().get(), b"hello world");
assert_that!(msg.optional_bytes(), eq(b"hello world"));
assert_that!(msg.optional_bytes_mut().get(), eq(b"hello world"));
msg.optional_bytes_mut().clear();
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_mut().get(), b"");
assert_that!(*msg.optional_bytes(), empty());
assert_that!(*msg.optional_bytes_mut().get(), empty());
msg.optional_bytes_mut().set(b"");
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_mut().get(), b"");
assert_that!(*msg.optional_bytes(), empty());
assert_that!(*msg.optional_bytes_mut().get(), empty());
}
#[test]
fn test_optional_bytes_accessors() {
let mut msg = TestProto3Optional::new();
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_opt(), Optional::Unset(&b""[..]));
assert_eq!(msg.optional_bytes_mut().get(), b"");
assert!(msg.optional_bytes_mut().is_unset());
assert_that!(*msg.optional_bytes(), empty());
assert_that!(msg.optional_bytes_opt(), eq(Optional::Unset(&b""[..])));
assert_that!(*msg.optional_bytes_mut().get(), empty());
assert_that!(msg.optional_bytes_mut(), is_unset());
{
let s = Vec::from(&b"hello world"[..]);
msg.optional_bytes_mut().set(&s[..]);
}
assert_eq!(msg.optional_bytes(), b"hello world");
assert_eq!(msg.optional_bytes_opt(), Optional::Set(&b"hello world"[..]));
assert!(msg.optional_bytes_mut().is_set());
assert_eq!(msg.optional_bytes_mut().get(), b"hello world");
assert_that!(msg.optional_bytes(), eq(b"hello world"));
assert_that!(msg.optional_bytes_opt(), eq(Optional::Set(&b"hello world"[..])));
assert_that!(msg.optional_bytes_mut(), is_set());
assert_that!(msg.optional_bytes_mut().get(), eq(b"hello world"));
msg.optional_bytes_mut().or_default().set(b"accessors_test");
assert_eq!(msg.optional_bytes(), b"accessors_test");
assert_eq!(msg.optional_bytes_opt(), Optional::Set(&b"accessors_test"[..]));
assert!(msg.optional_bytes_mut().is_set());
assert_eq!(msg.optional_bytes_mut().get(), b"accessors_test");
assert_eq!(msg.optional_bytes_mut().or_default().get(), b"accessors_test");
assert_that!(msg.optional_bytes(), eq(b"accessors_test"));
assert_that!(msg.optional_bytes_opt(), eq(Optional::Set(&b"accessors_test"[..])));
assert_that!(msg.optional_bytes_mut(), is_set());
assert_that!(msg.optional_bytes_mut().get(), eq(b"accessors_test"));
assert_that!(msg.optional_bytes_mut().or_default().get(), eq(b"accessors_test"));
msg.optional_bytes_mut().clear();
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_opt(), Optional::Unset(&b""[..]));
assert!(msg.optional_bytes_mut().is_unset());
assert_that!(*msg.optional_bytes(), empty());
assert_that!(msg.optional_bytes_opt(), eq(Optional::Unset(&b""[..])));
assert_that!(msg.optional_bytes_mut(), is_unset());
msg.optional_bytes_mut().set(b"");
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_opt(), Optional::Set(&b""[..]));
assert_that!(*msg.optional_bytes(), empty());
assert_that!(msg.optional_bytes_opt(), eq(Optional::Set(&b""[..])));
msg.optional_bytes_mut().clear();
msg.optional_bytes_mut().or_default();
assert_eq!(msg.optional_bytes(), b"");
assert_eq!(msg.optional_bytes_opt(), Optional::Set(&b""[..]));
assert_that!(*msg.optional_bytes(), empty());
assert_that!(msg.optional_bytes_opt(), eq(Optional::Set(&b""[..])));
msg.optional_bytes_mut().or_default().set(b"\xffbinary\x85non-utf8");
assert_eq!(msg.optional_bytes(), b"\xffbinary\x85non-utf8");
assert_eq!(msg.optional_bytes_opt(), Optional::Set(&b"\xffbinary\x85non-utf8"[..]));
assert!(msg.optional_bytes_mut().is_set());
assert_eq!(msg.optional_bytes_mut().get(), b"\xffbinary\x85non-utf8");
assert_eq!(msg.optional_bytes_mut().or_default().get(), b"\xffbinary\x85non-utf8");
assert_that!(msg.optional_bytes(), eq(b"\xffbinary\x85non-utf8"));
assert_that!(msg.optional_bytes_opt(), eq(Optional::Set(&b"\xffbinary\x85non-utf8"[..])));
assert_that!(msg.optional_bytes_mut(), is_set());
assert_that!(msg.optional_bytes_mut().get(), eq(b"\xffbinary\x85non-utf8"));
assert_that!(msg.optional_bytes_mut().or_default().get(), eq(b"\xffbinary\x85non-utf8"));
}
#[test]
@ -149,7 +151,7 @@ fn test_optional_string_accessors() {
assert_eq!(msg.optional_string(), "");
assert_eq!(msg.optional_string_opt(), Optional::Unset("".into()));
assert_eq!(msg.optional_string_mut().get(), "");
assert!(msg.optional_string_mut().is_unset());
assert_that!(msg.optional_string_mut(), is_unset());
{
let s = String::from("hello world");
@ -157,20 +159,20 @@ fn test_optional_string_accessors() {
}
assert_eq!(msg.optional_string(), "hello world");
assert_eq!(msg.optional_string_opt(), Optional::Set("hello world".into()));
assert!(msg.optional_string_mut().is_set());
assert_that!(msg.optional_string_mut(), is_set());
assert_eq!(msg.optional_string_mut().get(), "hello world");
msg.optional_string_mut().or_default().set("accessors_test");
assert_eq!(msg.optional_string(), "accessors_test");
assert_eq!(msg.optional_string_opt(), Optional::Set("accessors_test".into()));
assert!(msg.optional_string_mut().is_set());
assert_that!(msg.optional_string_mut(), is_set());
assert_eq!(msg.optional_string_mut().get(), "accessors_test");
assert_eq!(msg.optional_string_mut().or_default().get(), "accessors_test");
msg.optional_string_mut().clear();
assert_eq!(msg.optional_string(), "");
assert_eq!(msg.optional_string_opt(), Optional::Unset("".into()));
assert!(msg.optional_string_mut().is_unset());
assert_that!(msg.optional_string_mut(), is_unset());
msg.optional_string_mut().set("");
assert_eq!(msg.optional_string(), "");
@ -187,20 +189,19 @@ fn test_oneof_accessors() {
use TestAllTypes_::OneofField::*;
let mut msg = TestAllTypes::new();
assert!(matches!(msg.oneof_field(), not_set(_)));
assert_that!(msg.oneof_field(), matches_pattern!(not_set(_)));
msg.oneof_uint32_set(Some(7));
assert_eq!(msg.oneof_uint32_opt(), Optional::Set(7));
assert!(matches!(msg.oneof_field(), OneofUint32(7)));
assert_that!(msg.oneof_uint32_opt(), eq(Optional::Set(7)));
assert_that!(msg.oneof_field(), matches_pattern!(OneofUint32(eq(7))));
msg.oneof_uint32_set(None);
assert_eq!(msg.oneof_uint32_opt(), Optional::Unset(0));
assert!(matches!(msg.oneof_field(), not_set(_)));
assert_that!(msg.oneof_uint32_opt(), eq(Optional::Unset(0)));
assert_that!(msg.oneof_field(), matches_pattern!(not_set(_)));
msg.oneof_uint32_set(Some(7));
msg.oneof_bytes_mut().set(b"");
assert_eq!(msg.oneof_uint32_opt(), Optional::Unset(0));
msg.oneof_bytes_mut().set(b"123");
assert_that!(msg.oneof_uint32_opt(), eq(Optional::Unset(0)));
// This should show it set to the OneofBytes but its not supported yet.
assert!(matches!(msg.oneof_field(), not_set(_)));
assert_that!(msg.oneof_field(), matches_pattern!(OneofBytes(eq(b"123"))));
}

@ -9,6 +9,7 @@
use googletest::prelude::*;
use matchers::{is_set, is_unset};
use paste::paste;
use protobuf::Optional;
use unittest_proto::proto2_unittest::{TestAllTypes, TestAllTypes_};
@ -50,6 +51,33 @@ fn test_optional_fixed32_accessors() {
assert_that!(msg.optional_fixed32(), eq(0));
}
#[test]
fn test_default_fixed32_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_fixed32(), eq(47));
assert_that!(msg.default_fixed32_mut().get(), eq(47));
assert_that!(msg.default_fixed32_mut().is_set(), eq(false));
assert_that!(msg.default_fixed32_opt(), eq(Optional::Unset(47)));
msg.default_fixed32_mut().set(999);
assert_that!(msg.default_fixed32(), eq(999));
assert_that!(msg.default_fixed32_mut().get(), eq(999));
assert_that!(msg.default_fixed32_mut().is_set(), eq(true));
assert_that!(msg.default_fixed32_opt(), eq(Optional::Set(999)));
msg.default_fixed32_mut().clear();
assert_that!(msg.default_fixed32(), eq(47));
assert_that!(msg.default_fixed32_mut().get(), eq(47));
assert_that!(msg.default_fixed32_mut().is_set(), eq(false));
assert_that!(msg.default_fixed32_opt(), eq(Optional::Unset(47)));
msg.default_fixed32_mut().or_default();
assert_that!(msg.default_fixed32(), eq(47));
assert_that!(msg.default_fixed32_mut().get(), eq(47));
assert_that!(msg.default_fixed32_mut().is_set(), eq(true));
assert_that!(msg.default_fixed32_opt(), eq(Optional::Set(47)));
}
#[test]
fn test_optional_fixed64_accessors() {
let mut msg = TestAllTypes::new();
@ -65,6 +93,33 @@ fn test_optional_fixed64_accessors() {
assert_that!(msg.optional_fixed64(), eq(0));
}
#[test]
fn test_default_fixed64_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_fixed64(), eq(48));
assert_that!(msg.default_fixed64_mut().get(), eq(48));
assert_that!(msg.default_fixed64_mut().is_set(), eq(false));
assert_that!(msg.default_fixed64_opt(), eq(Optional::Unset(48)));
msg.default_fixed64_mut().set(999);
assert_that!(msg.default_fixed64(), eq(999));
assert_that!(msg.default_fixed64_mut().get(), eq(999));
assert_that!(msg.default_fixed64_mut().is_set(), eq(true));
assert_that!(msg.default_fixed64_opt(), eq(Optional::Set(999)));
msg.default_fixed64_mut().clear();
assert_that!(msg.default_fixed64(), eq(48));
assert_that!(msg.default_fixed64_mut().get(), eq(48));
assert_that!(msg.default_fixed64_mut().is_set(), eq(false));
assert_that!(msg.default_fixed64_opt(), eq(Optional::Unset(48)));
msg.default_fixed64_mut().or_default();
assert_that!(msg.default_fixed64(), eq(48));
assert_that!(msg.default_fixed64_mut().get(), eq(48));
assert_that!(msg.default_fixed64_mut().is_set(), eq(true));
assert_that!(msg.default_fixed64_opt(), eq(Optional::Set(48)));
}
#[test]
fn test_optional_int32_accessors() {
let mut msg = TestAllTypes::new();
@ -80,6 +135,33 @@ fn test_optional_int32_accessors() {
assert_that!(msg.optional_int32(), eq(0));
}
#[test]
fn test_default_int32_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_int32(), eq(41));
assert_that!(msg.default_int32_mut().get(), eq(41));
assert_that!(msg.default_int32_mut().is_set(), eq(false));
assert_that!(msg.default_int32_opt(), eq(Optional::Unset(41)));
msg.default_int32_mut().set(999);
assert_that!(msg.default_int32(), eq(999));
assert_that!(msg.default_int32_mut().get(), eq(999));
assert_that!(msg.default_int32_mut().is_set(), eq(true));
assert_that!(msg.default_int32_opt(), eq(Optional::Set(999)));
msg.default_int32_mut().clear();
assert_that!(msg.default_int32(), eq(41));
assert_that!(msg.default_int32_mut().get(), eq(41));
assert_that!(msg.default_int32_mut().is_set(), eq(false));
assert_that!(msg.default_int32_opt(), eq(Optional::Unset(41)));
msg.default_int32_mut().or_default();
assert_that!(msg.default_int32(), eq(41));
assert_that!(msg.default_int32_mut().get(), eq(41));
assert_that!(msg.default_int32_mut().is_set(), eq(true));
assert_that!(msg.default_int32_opt(), eq(Optional::Set(41)));
}
#[test]
fn test_optional_int64_accessors() {
let mut msg = TestAllTypes::new();
@ -95,6 +177,33 @@ fn test_optional_int64_accessors() {
assert_that!(msg.optional_int64(), eq(0));
}
#[test]
fn test_default_int64_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_int64(), eq(42));
assert_that!(msg.default_int64_mut().get(), eq(42));
assert_that!(msg.default_int64_mut().is_set(), eq(false));
assert_that!(msg.default_int64_opt(), eq(Optional::Unset(42)));
msg.default_int64_mut().set(999);
assert_that!(msg.default_int64(), eq(999));
assert_that!(msg.default_int64_mut().get(), eq(999));
assert_that!(msg.default_int64_mut().is_set(), eq(true));
assert_that!(msg.default_int64_opt(), eq(Optional::Set(999)));
msg.default_int64_mut().clear();
assert_that!(msg.default_int64(), eq(42));
assert_that!(msg.default_int64_mut().get(), eq(42));
assert_that!(msg.default_int64_mut().is_set(), eq(false));
assert_that!(msg.default_int64_opt(), eq(Optional::Unset(42)));
msg.default_int64_mut().or_default();
assert_that!(msg.default_int64(), eq(42));
assert_that!(msg.default_int64_mut().get(), eq(42));
assert_that!(msg.default_int64_mut().is_set(), eq(true));
assert_that!(msg.default_int64_opt(), eq(Optional::Set(42)));
}
#[test]
fn test_optional_sint32_accessors() {
let mut msg = TestAllTypes::new();
@ -110,6 +219,33 @@ fn test_optional_sint32_accessors() {
assert_that!(msg.optional_sint32(), eq(0));
}
#[test]
fn test_default_sint32_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_sint32(), eq(-45));
assert_that!(msg.default_sint32_mut().get(), eq(-45));
assert_that!(msg.default_sint32_mut().is_set(), eq(false));
assert_that!(msg.default_sint32_opt(), eq(Optional::Unset(-45)));
msg.default_sint32_mut().set(999);
assert_that!(msg.default_sint32(), eq(999));
assert_that!(msg.default_sint32_mut().get(), eq(999));
assert_that!(msg.default_sint32_mut().is_set(), eq(true));
assert_that!(msg.default_sint32_opt(), eq(Optional::Set(999)));
msg.default_sint32_mut().clear();
assert_that!(msg.default_sint32(), eq(-45));
assert_that!(msg.default_sint32_mut().get(), eq(-45));
assert_that!(msg.default_sint32_mut().is_set(), eq(false));
assert_that!(msg.default_sint32_opt(), eq(Optional::Unset(-45)));
msg.default_sint32_mut().or_default();
assert_that!(msg.default_sint32(), eq(-45));
assert_that!(msg.default_sint32_mut().get(), eq(-45));
assert_that!(msg.default_sint32_mut().is_set(), eq(true));
assert_that!(msg.default_sint32_opt(), eq(Optional::Set(-45)));
}
#[test]
fn test_optional_sint64_accessors() {
let mut msg = TestAllTypes::new();
@ -125,6 +261,33 @@ fn test_optional_sint64_accessors() {
assert_that!(msg.optional_sint64(), eq(0));
}
#[test]
fn test_default_sint64_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_sint64(), eq(46));
assert_that!(msg.default_sint64_mut().get(), eq(46));
assert_that!(msg.default_sint64_mut().is_set(), eq(false));
assert_that!(msg.default_sint64_opt(), eq(Optional::Unset(46)));
msg.default_sint64_mut().set(999);
assert_that!(msg.default_sint64(), eq(999));
assert_that!(msg.default_sint64_mut().get(), eq(999));
assert_that!(msg.default_sint64_mut().is_set(), eq(true));
assert_that!(msg.default_sint64_opt(), eq(Optional::Set(999)));
msg.default_sint64_mut().clear();
assert_that!(msg.default_sint64(), eq(46));
assert_that!(msg.default_sint64_mut().get(), eq(46));
assert_that!(msg.default_sint64_mut().is_set(), eq(false));
assert_that!(msg.default_sint64_opt(), eq(Optional::Unset(46)));
msg.default_sint64_mut().or_default();
assert_that!(msg.default_sint64(), eq(46));
assert_that!(msg.default_sint64_mut().get(), eq(46));
assert_that!(msg.default_sint64_mut().is_set(), eq(true));
assert_that!(msg.default_sint64_opt(), eq(Optional::Set(46)));
}
#[test]
fn test_optional_uint32_accessors() {
let mut msg = TestAllTypes::new();
@ -140,6 +303,33 @@ fn test_optional_uint32_accessors() {
assert_that!(msg.optional_uint32(), eq(0));
}
#[test]
fn test_default_uint32_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_uint32(), eq(43));
assert_that!(msg.default_uint32_mut().get(), eq(43));
assert_that!(msg.default_uint32_mut().is_set(), eq(false));
assert_that!(msg.default_uint32_opt(), eq(Optional::Unset(43)));
msg.default_uint32_mut().set(999);
assert_that!(msg.default_uint32(), eq(999));
assert_that!(msg.default_uint32_mut().get(), eq(999));
assert_that!(msg.default_uint32_mut().is_set(), eq(true));
assert_that!(msg.default_uint32_opt(), eq(Optional::Set(999)));
msg.default_uint32_mut().clear();
assert_that!(msg.default_uint32(), eq(43));
assert_that!(msg.default_uint32_mut().get(), eq(43));
assert_that!(msg.default_uint32_mut().is_set(), eq(false));
assert_that!(msg.default_uint32_opt(), eq(Optional::Unset(43)));
msg.default_uint32_mut().or_default();
assert_that!(msg.default_uint32(), eq(43));
assert_that!(msg.default_uint32_mut().get(), eq(43));
assert_that!(msg.default_uint32_mut().is_set(), eq(true));
assert_that!(msg.default_uint32_opt(), eq(Optional::Set(43)));
}
#[test]
fn test_optional_uint64_accessors() {
let mut msg = TestAllTypes::new();
@ -155,6 +345,33 @@ fn test_optional_uint64_accessors() {
assert_that!(msg.optional_uint64(), eq(0));
}
#[test]
fn test_default_uint64_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_uint64(), eq(44));
assert_that!(msg.default_uint64_mut().get(), eq(44));
assert_that!(msg.default_uint64_mut().is_set(), eq(false));
assert_that!(msg.default_uint64_opt(), eq(Optional::Unset(44)));
msg.default_uint64_mut().set(999);
assert_that!(msg.default_uint64(), eq(999));
assert_that!(msg.default_uint64_mut().get(), eq(999));
assert_that!(msg.default_uint64_mut().is_set(), eq(true));
assert_that!(msg.default_uint64_opt(), eq(Optional::Set(999)));
msg.default_uint64_mut().clear();
assert_that!(msg.default_uint64(), eq(44));
assert_that!(msg.default_uint64_mut().get(), eq(44));
assert_that!(msg.default_uint64_mut().is_set(), eq(false));
assert_that!(msg.default_uint64_opt(), eq(Optional::Unset(44)));
msg.default_uint64_mut().or_default();
assert_that!(msg.default_uint64(), eq(44));
assert_that!(msg.default_uint64_mut().get(), eq(44));
assert_that!(msg.default_uint64_mut().is_set(), eq(true));
assert_that!(msg.default_uint64_opt(), eq(Optional::Set(44)));
}
#[test]
fn test_optional_float_accessors() {
let mut msg = TestAllTypes::new();
@ -170,6 +387,33 @@ fn test_optional_float_accessors() {
assert_that!(msg.optional_float(), eq(0.0));
}
#[test]
fn test_default_float_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_float(), eq(51.5));
assert_that!(msg.default_float_mut().get(), eq(51.5));
assert_that!(msg.default_float_mut().is_set(), eq(false));
assert_that!(msg.default_float_opt(), eq(Optional::Unset(51.5)));
msg.default_float_mut().set(999.9);
assert_that!(msg.default_float(), eq(999.9));
assert_that!(msg.default_float_mut().get(), eq(999.9));
assert_that!(msg.default_float_mut().is_set(), eq(true));
assert_that!(msg.default_float_opt(), eq(Optional::Set(999.9)));
msg.default_float_mut().clear();
assert_that!(msg.default_float(), eq(51.5));
assert_that!(msg.default_float_mut().get(), eq(51.5));
assert_that!(msg.default_float_mut().is_set(), eq(false));
assert_that!(msg.default_float_opt(), eq(Optional::Unset(51.5)));
msg.default_float_mut().or_default();
assert_that!(msg.default_float(), eq(51.5));
assert_that!(msg.default_float_mut().get(), eq(51.5));
assert_that!(msg.default_float_mut().is_set(), eq(true));
assert_that!(msg.default_float_opt(), eq(Optional::Set(51.5)));
}
#[test]
fn test_optional_double_accessors() {
let mut msg = TestAllTypes::new();
@ -185,6 +429,33 @@ fn test_optional_double_accessors() {
assert_that!(msg.optional_double(), eq(0.0));
}
#[test]
fn test_default_double_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_double(), eq(52e3));
assert_that!(msg.default_double_mut().get(), eq(52e3));
assert_that!(msg.default_double_mut().is_set(), eq(false));
assert_that!(msg.default_double_opt(), eq(Optional::Unset(52e3)));
msg.default_double_mut().set(999.9);
assert_that!(msg.default_double(), eq(999.9));
assert_that!(msg.default_double_mut().get(), eq(999.9));
assert_that!(msg.default_double_mut().is_set(), eq(true));
assert_that!(msg.default_double_opt(), eq(Optional::Set(999.9)));
msg.default_double_mut().clear();
assert_that!(msg.default_double(), eq(52e3));
assert_that!(msg.default_double_mut().get(), eq(52e3));
assert_that!(msg.default_double_mut().is_set(), eq(false));
assert_that!(msg.default_double_opt(), eq(Optional::Unset(52e3)));
msg.default_double_mut().or_default();
assert_that!(msg.default_double(), eq(52e3));
assert_that!(msg.default_double_mut().get(), eq(52e3));
assert_that!(msg.default_double_mut().is_set(), eq(true));
assert_that!(msg.default_double_opt(), eq(Optional::Set(52e3)));
}
#[test]
fn test_optional_bool_accessors() {
let mut msg = TestAllTypes::new();
@ -197,6 +468,33 @@ fn test_optional_bool_accessors() {
assert_that!(msg.optional_bool_opt(), eq(Optional::Unset(false)));
}
#[test]
fn test_default_bool_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.default_bool(), eq(true));
assert_that!(msg.default_bool_mut().get(), eq(true));
assert_that!(msg.default_bool_mut().is_set(), eq(false));
assert_that!(msg.default_bool_opt(), eq(Optional::Unset(true)));
msg.default_bool_mut().set(false);
assert_that!(msg.default_bool(), eq(false));
assert_that!(msg.default_bool_mut().get(), eq(false));
assert_that!(msg.default_bool_mut().is_set(), eq(true));
assert_that!(msg.default_bool_opt(), eq(Optional::Set(false)));
msg.default_bool_mut().clear();
assert_that!(msg.default_bool(), eq(true));
assert_that!(msg.default_bool_mut().get(), eq(true));
assert_that!(msg.default_bool_mut().is_set(), eq(false));
assert_that!(msg.default_bool_opt(), eq(Optional::Unset(true)));
msg.default_bool_mut().or_default();
assert_that!(msg.default_bool(), eq(true));
assert_that!(msg.default_bool_mut().get(), eq(true));
assert_that!(msg.default_bool_mut().is_set(), eq(true));
assert_that!(msg.default_bool_opt(), eq(Optional::Set(true)));
}
#[test]
fn test_optional_bytes_accessors() {
let mut msg = TestAllTypes::new();
@ -392,9 +690,125 @@ fn test_oneof_accessors() {
assert_that!(msg.oneof_field(), matches_pattern!(not_set(_)));
msg.oneof_uint32_set(Some(7));
msg.oneof_bytes_mut().set(b"");
msg.oneof_bytes_mut().set(b"123");
assert_that!(msg.oneof_uint32_opt(), eq(Optional::Unset(0)));
// This should show it set to the OneofBytes but its not supported yet.
assert_that!(msg.oneof_field(), matches_pattern!(not_set(_)));
assert_that!(msg.oneof_field(), matches_pattern!(OneofBytes(eq(b"123"))));
}
macro_rules! generate_repeated_numeric_test {
($(($t: ty, $field: ident)),*) => {
paste! { $(
#[test]
fn [< test_repeated_ $field _accessors >]() {
let mut msg = TestAllTypes::new();
assert_that!(msg.[< repeated_ $field >]().len(), eq(0));
assert_that!(msg.[<repeated_ $field >]().get(0), none());
let mut mutator = msg.[<repeated_ $field _mut >]();
mutator.push(1 as $t);
assert_that!(mutator.len(), eq(1));
assert_that!(mutator.get(0), some(eq(1 as $t)));
mutator.set(0, 2 as $t);
assert_that!(mutator.get(0), some(eq(2 as $t)));
mutator.push(1 as $t);
mutator.push(3 as $t);
assert_that!(mutator.get_mut(2).is_some(), eq(true));
let mut mut_elem = mutator.get_mut(2).unwrap();
mut_elem.set(4 as $t);
assert_that!(mut_elem.get(), eq(4 as $t));
mut_elem.clear();
assert_that!(mut_elem.get(), eq(0 as $t));
assert_that!(
mutator.iter().collect::<Vec<_>>(),
eq(vec![2 as $t, 1 as $t, 0 as $t])
);
assert_that!(
(*mutator).into_iter().collect::<Vec<_>>(),
eq(vec![2 as $t, 1 as $t, 0 as $t])
);
for mut mutable_elem in msg.[<repeated_ $field _mut >]() {
mutable_elem.set(0 as $t);
}
assert_that!(
msg.[<repeated_ $field _mut >]().iter().all(|v| v == (0 as $t)),
eq(true)
);
}
#[test]
fn [< test_repeated_ $field _set >]() {
let mut msg = TestAllTypes::new();
let mut mutator = msg.[<repeated_ $field _mut>]();
let mut msg2 = TestAllTypes::new();
let mut mutator2 = msg2.[<repeated_ $field _mut>]();
for i in 0..5 {
mutator2.push(i as $t);
}
protobuf::MutProxy::set(&mut mutator, *mutator2);
assert_that!(
mutator.iter().collect::<Vec<_>>(),
eq(mutator2.iter().collect::<Vec<_>>())
);
}
)* }
};
}
generate_repeated_numeric_test!(
(i32, int32),
(u32, uint32),
(i64, int64),
(u64, uint64),
(f32, float),
(f64, double)
);
#[test]
fn test_repeated_bool_accessors() {
let mut msg = TestAllTypes::new();
assert_that!(msg.repeated_bool().len(), eq(0));
assert_that!(msg.repeated_bool().get(0), none());
let mut mutator = msg.repeated_bool_mut();
mutator.push(true);
assert_that!(mutator.len(), eq(1));
assert_that!(mutator.get(0), some(eq(true)));
mutator.set(0, false);
assert_that!(mutator.get(0), some(eq(false)));
mutator.push(true);
mutator.push(false);
assert_that!(mutator.get_mut(2), pat!(Some(_)));
let mut mut_elem = mutator.get_mut(2).unwrap();
mut_elem.set(true);
assert_that!(mut_elem.get(), eq(true));
mut_elem.clear();
assert_that!(mut_elem.get(), eq(false));
assert_that!(mutator.iter().collect::<Vec<_>>(), eq(vec![false, true, false]));
assert_that!((*mutator).into_iter().collect::<Vec<_>>(), eq(vec![false, true, false]));
for mut mutable_elem in msg.repeated_bool_mut() {
mutable_elem.set(false);
}
assert_that!(msg.repeated_bool().iter().all(|v| v), eq(false));
}
#[test]
fn test_repeated_bool_set() {
let mut msg = TestAllTypes::new();
let mut mutator = msg.repeated_bool_mut();
let mut msg2 = TestAllTypes::new();
let mut mutator2 = msg2.repeated_bool_mut();
for _ in 0..5 {
mutator2.push(true);
}
protobuf::MutProxy::set(&mut mutator, *mutator2);
assert_that!(mutator.iter().collect::<Vec<_>>(), eq(mutator2.iter().collect::<Vec<_>>()));
}

@ -7,7 +7,7 @@
//! UPB FFI wrapper code for use by Rust Protobuf.
use crate::__internal::{Private, RawArena, RawMessage};
use crate::__internal::{Private, PtrAndLen, RawArena, RawMessage, RawRepeatedField};
use std::alloc;
use std::alloc::Layout;
use std::cell::UnsafeCell;
@ -150,7 +150,7 @@ const UPB_SCRATCH_SPACE_BYTES: usize = 64_000;
/// with readonly access.
pub struct ScratchSpace;
impl ScratchSpace {
pub fn zeroed_block() -> RawMessage {
pub fn zeroed_block(_private: Private) -> RawMessage {
unsafe {
INIT.call_once(|| {
let layout =
@ -284,6 +284,173 @@ pub fn copy_bytes_in_arena_if_needed_by_runtime<'a>(
}
}
/// RepeatedFieldInner contains a `upb_Array*` as well as a reference to an
/// `Arena`, most likely that of the containing `Message`. upb requires an Arena
/// to perform mutations on a repeated field.
#[derive(Clone, Copy, Debug)]
pub struct RepeatedFieldInner<'msg> {
pub raw: RawRepeatedField,
pub arena: &'msg Arena,
}
#[derive(Debug)]
pub struct RepeatedField<'msg, T: ?Sized> {
inner: RepeatedFieldInner<'msg>,
_phantom: PhantomData<&'msg mut T>,
}
// These use manual impls instead of derives to avoid unnecessary bounds on `T`.
// This problem is referred to as "perfect derive".
// https://smallcultfollowing.com/babysteps/blog/2022/04/12/implied-bounds-and-perfect-derive/
impl<'msg, T: ?Sized> Copy for RepeatedField<'msg, T> {}
impl<'msg, T: ?Sized> Clone for RepeatedField<'msg, T> {
fn clone(&self) -> RepeatedField<'msg, T> {
*self
}
}
impl<'msg, T: ?Sized> RepeatedField<'msg, T> {
pub fn len(&self) -> usize {
unsafe { upb_Array_Size(self.inner.raw) }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn from_inner(_private: Private, inner: RepeatedFieldInner<'msg>) -> Self {
Self { inner, _phantom: PhantomData }
}
}
// Transcribed from google3/third_party/upb/upb/message/value.h
#[repr(C)]
#[derive(Clone, Copy)]
union upb_MessageValue {
bool_val: bool,
float_val: std::ffi::c_float,
double_val: std::ffi::c_double,
uint32_val: u32,
int32_val: i32,
uint64_val: u64,
int64_val: i64,
array_val: *const std::ffi::c_void,
map_val: *const std::ffi::c_void,
msg_val: *const std::ffi::c_void,
str_val: PtrAndLen,
}
// Transcribed from google3/third_party/upb/upb/base/descriptor_constants.h
#[repr(C)]
#[allow(dead_code)]
enum UpbCType {
Bool = 1,
Float = 2,
Int32 = 3,
UInt32 = 4,
Enum = 5,
Message = 6,
Double = 7,
Int64 = 8,
UInt64 = 9,
String = 10,
Bytes = 11,
}
extern "C" {
#[allow(dead_code)]
fn upb_Array_New(a: RawArena, r#type: std::ffi::c_int) -> RawRepeatedField;
fn upb_Array_Size(arr: RawRepeatedField) -> usize;
fn upb_Array_Set(arr: RawRepeatedField, i: usize, val: upb_MessageValue);
fn upb_Array_Get(arr: RawRepeatedField, i: usize) -> upb_MessageValue;
fn upb_Array_Append(arr: RawRepeatedField, val: upb_MessageValue, arena: RawArena);
fn upb_Array_Resize(arr: RawRepeatedField, size: usize, arena: RawArena);
}
macro_rules! impl_repeated_primitives {
($(($rs_type:ty, $union_field:ident, $upb_tag:expr)),*) => {
$(
impl<'msg> RepeatedField<'msg, $rs_type> {
#[allow(dead_code)]
fn new(arena: &'msg Arena) -> Self {
Self {
inner: RepeatedFieldInner {
raw: unsafe { upb_Array_New(arena.raw, $upb_tag as std::ffi::c_int) },
arena,
},
_phantom: PhantomData,
}
}
pub fn push(&mut self, val: $rs_type) {
unsafe { upb_Array_Append(
self.inner.raw,
upb_MessageValue { $union_field: val },
self.inner.arena.raw(),
) }
}
pub fn get(&self, i: usize) -> Option<$rs_type> {
if i >= self.len() {
None
} else {
unsafe { Some(upb_Array_Get(self.inner.raw, i).$union_field) }
}
}
pub fn set(&self, i: usize, val: $rs_type) {
if i >= self.len() {
return;
}
unsafe { upb_Array_Set(
self.inner.raw,
i,
upb_MessageValue { $union_field: val },
) }
}
pub fn copy_from(&mut self, src: &RepeatedField<'_, $rs_type>) {
// TODO: Optimize this copy_from implementation using memcopy.
// NOTE: `src` cannot be `self` because this would violate borrowing rules.
unsafe { upb_Array_Resize(self.inner.raw, 0, self.inner.arena.raw()) };
// `upb_Array_DeepClone` is not used here because it returns
// a new `upb_Array*`. The contained `RawRepeatedField` must
// then be set to this new pointer, but other copies of this
// pointer may exist because of re-borrowed `RepeatedMut`s.
// Alternatively, a `clone_into` method could be exposed by upb.
for i in 0..src.len() {
self.push(src.get(i).unwrap());
}
}
}
)*
}
}
impl_repeated_primitives!(
(bool, bool_val, UpbCType::Bool),
(f32, float_val, UpbCType::Float),
(f64, double_val, UpbCType::Double),
(i32, int32_val, UpbCType::Int32),
(u32, uint32_val, UpbCType::UInt32),
(i64, int64_val, UpbCType::Int64),
(u64, uint64_val, UpbCType::UInt64)
);
/// Returns a static thread-local empty RepeatedFieldInner for use in a
/// RepeatedView.
///
/// # Safety
/// TODO: Split RepeatedFieldInner into mut and const variants to
/// enforce safety. The returned array must never be mutated.
pub unsafe fn empty_array() -> RepeatedFieldInner<'static> {
// TODO: Consider creating empty array in C.
fn new_repeated_field_inner() -> RepeatedFieldInner<'static> {
let arena = Box::leak::<'static>(Box::new(Arena::new()));
// Provide `i32` as a placeholder type.
RepeatedField::<'static, i32>::new(arena).inner
}
thread_local! {
static REPEATED_FIELD: RepeatedFieldInner<'static> = new_repeated_field_inner();
}
REPEATED_FIELD.with(|inner| *inner)
}
#[cfg(test)]
mod tests {
use super::*;
@ -309,4 +476,35 @@ mod tests {
};
assert_eq!(&*serialized_data, b"Hello world");
}
#[test]
fn i32_array() {
let arena = Arena::new();
let mut arr = RepeatedField::<i32>::new(&arena);
assert_eq!(arr.len(), 0);
arr.push(1);
assert_eq!(arr.get(0), Some(1));
assert_eq!(arr.len(), 1);
arr.set(0, 3);
assert_eq!(arr.get(0), Some(3));
for i in 0..2048 {
arr.push(i);
assert_eq!(arr.get(arr.len() - 1), Some(i));
}
}
#[test]
fn u32_array() {
let mut arena = Arena::new();
let mut arr = RepeatedField::<u32>::new(&mut arena);
assert_eq!(arr.len(), 0);
arr.push(1);
assert_eq!(arr.get(0), Some(1));
assert_eq!(arr.len(), 1);
arr.set(0, 3);
assert_eq!(arr.get(0), Some(3));
for i in 0..2048 {
arr.push(i);
assert_eq!(arr.get(arr.len() - 1), Some(i));
}
}
}

@ -8,6 +8,7 @@ cc_library(
"//rust:__subpackages__",
],
deps = [
"//upb:collections",
"//upb:mem",
],
)

@ -8,4 +8,5 @@
#define UPB_BUILD_API
#include "upb/collections/array.h" // IWYU pragma: keep
#include "upb/mem/arena.h" // IWYU pragma: keep

@ -241,7 +241,7 @@ impl<'a> Iterator for Utf8Chunks<'a> {
/// # Safety
/// `index` must be in-bounds for `x`
unsafe fn split_at_unchecked(x: &[u8], index: usize) -> (&[u8], &[u8]) {
// SAFTEY: in-bounds as promised by the caller
// SAFETY: in-bounds as promised by the caller
unsafe { (x.get_unchecked(..index), x.get_unchecked(index..)) }
}

@ -258,6 +258,15 @@ pub struct PrimitiveVTable<T> {
pub(crate) getter: unsafe extern "C" fn(msg: RawMessage) -> T,
}
#[doc(hidden)]
#[derive(Debug)]
/// A generic thunk vtable for mutating an `optional` primitive field.
pub struct PrimitiveOptionalMutVTable<T> {
pub(crate) base: PrimitiveVTable<T>,
pub(crate) clearer: unsafe extern "C" fn(msg: RawMessage),
pub(crate) default: T,
}
impl<T> PrimitiveVTable<T> {
#[doc(hidden)]
pub const fn new(
@ -269,6 +278,19 @@ impl<T> PrimitiveVTable<T> {
}
}
impl<T> PrimitiveOptionalMutVTable<T> {
#[doc(hidden)]
pub const fn new(
_private: Private,
getter: unsafe extern "C" fn(msg: RawMessage) -> T,
setter: unsafe extern "C" fn(msg: RawMessage, val: T),
clearer: unsafe extern "C" fn(msg: RawMessage),
default: T,
) -> Self {
Self { base: PrimitiveVTable { getter, setter }, clearer, default }
}
}
macro_rules! impl_raw_vtable_mutator_get_set {
($($t:ty),*) => {
$(
@ -398,3 +420,36 @@ impl<'msg> RawVTableOptionalMutatorData<'msg, [u8]> {
self
}
}
macro_rules! impl_raw_vtable_optional_mutator_data {
($($t:ty),*) => {
$(
impl<'msg> RawVTableOptionalMutatorData<'msg, $t> {
pub(crate) fn set_absent_to_default(self) -> Self {
// SAFETY:
// - `msg_ref` is valid for the lifetime of `RawVTableOptionalMutatorData` as
// promised by the caller of `new`.
self.set(self.vtable.default)
}
pub(crate) fn set(self, val: $t) -> Self {
// SAFETY:
// - `msg_ref` is valid for the lifetime of `RawVTableOptionalMutatorData` as
// promised by the caller of `new`.
unsafe { (self.vtable.base.setter)(self.msg_ref.msg(), val.into()) }
self
}
pub(crate) fn clear(self) -> Self {
// SAFETY:
// - `msg_ref` is valid for the lifetime of `RawVTableOptionalMutatorData` as
// promised by the caller of `new`.
unsafe { (self.vtable.clearer)(self.msg_ref.msg()) }
self
}
}
)*
}
}
impl_raw_vtable_optional_mutator_data!(bool, f32, f64, i32, i64, u32, u64);

@ -38,6 +38,7 @@ pkg_filegroup(
conformance_test(
name = "conformance_test",
failure_list = "//conformance:failure_list_cpp.txt",
maximum_edition = "2023",
testee = "//conformance:conformance_cpp",
text_format_failure_list = "//conformance:text_format_failure_list_cpp.txt",
)

@ -289,7 +289,6 @@ set(libprotoc_srcs
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/allowlists/empty_package.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/allowlists/open_enum.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/allowlists/unused_imports.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/allowlists/weak_imports.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/code_generator.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/command_line_interface.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/cpp/enum.cc
@ -384,6 +383,7 @@ set(libprotoc_srcs
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/retention.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/ruby/ruby_generator.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/accessors.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/repeated_scalar.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/singular_message.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/singular_scalar.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/rust/accessors/singular_string.cc

@ -162,6 +162,7 @@ cc_library(
copts = COPTS,
includes = ["wkt"],
linkopts = LINK_OPTS,
strip_include_prefix = "/src",
visibility = ["//pkg:__pkg__"],
deps = [":protobuf_nowkt"],
)
@ -198,7 +199,7 @@ cc_library(
"port_def.inc",
"port_undef.inc",
],
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -212,7 +213,7 @@ cc_library(
name = "varint_shuffle",
hdrs = ["varint_shuffle.h"],
copts = COPTS,
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -248,7 +249,7 @@ cc_library(
name = "arena_align",
srcs = ["arena_align.cc"],
hdrs = ["arena_align.h"],
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -264,7 +265,7 @@ cc_library(
cc_library(
name = "arena_cleanup",
hdrs = ["arena_cleanup.h"],
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -278,7 +279,7 @@ cc_library(
cc_library(
name = "arena_allocation_policy",
hdrs = ["arena_allocation_policy.h"],
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -291,7 +292,7 @@ cc_library(
cc_library(
name = "string_block",
hdrs = ["string_block.h"],
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
deps = [
":arena_align",
"@com_google_absl//absl/base:core_headers",
@ -320,7 +321,7 @@ cc_library(
"serial_arena.h",
"thread_safe_arena.h",
],
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -343,7 +344,7 @@ cc_library(
name = "internal_visibility",
hdrs = ["internal_visibility.h"],
copts = COPTS,
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -355,7 +356,7 @@ cc_library(
testonly = 1,
hdrs = ["internal_visibility_for_testing.h"],
copts = COPTS,
include_prefix = "google/protobuf",
strip_include_prefix = "/src",
visibility = [
"//:__subpackages__",
"//src/google/protobuf:__subpackages__",
@ -388,6 +389,7 @@ cc_library(
"arena.h",
"arenastring.h",
"arenaz_sampler.h",
"descriptor_lite.h",
"endian.h",
"explicitly_constructed.h",
"extension_set.h",
@ -419,8 +421,8 @@ cc_library(
"-Wno-error",
],
}),
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
strip_include_prefix = "/src",
visibility = [
"//:__pkg__",
"//pkg:__pkg__",
@ -506,8 +508,8 @@ cc_library(
"wire_format.h",
],
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
strip_include_prefix = "/src",
visibility = [
"//:__pkg__",
"//pkg:__pkg__",
@ -549,8 +551,8 @@ cc_test(
cc_library(
name = "protobuf",
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
strip_include_prefix = "/src",
visibility = [
"//:__pkg__",
"//pkg:__pkg__",
@ -573,14 +575,15 @@ cc_library(
"**/*.h",
"**/*.inc",
]),
strip_include_prefix = "/src",
)
cc_library(
name = "descriptor_legacy",
hdrs = ["descriptor_legacy.h"],
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
strip_include_prefix = "/src",
visibility = ["//:__subpackages__"],
deps = [
":port_def",
@ -593,8 +596,8 @@ cc_library(
name = "descriptor_visitor",
hdrs = ["descriptor_visitor.h"],
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
strip_include_prefix = "/src",
visibility = ["//:__subpackages__"],
deps = [
":port_def",
@ -749,6 +752,22 @@ proto_library(
],
)
proto_library(
name = "unittest_features_proto",
srcs = ["unittest_features.proto"],
strip_import_prefix = "/src",
visibility = ["//src/google/protobuf:__subpackages__"],
deps = [
":descriptor_proto",
],
)
cc_proto_library(
name = "unittest_features_cc_proto",
visibility = ["//src/google/protobuf:__subpackages__"],
deps = [":unittest_features_proto"],
)
proto_library(
name = "generic_test_protos",
srcs = [":test_proto_srcs"],

@ -58,7 +58,8 @@ static constexpr const ::_pb::EnumDescriptor**
file_level_enum_descriptors_google_2fprotobuf_2fany_2eproto = nullptr;
static constexpr const ::_pb::ServiceDescriptor**
file_level_service_descriptors_google_2fprotobuf_2fany_2eproto = nullptr;
const ::uint32_t TableStruct_google_2fprotobuf_2fany_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(
const ::uint32_t
TableStruct_google_2fprotobuf_2fany_2eproto::offsets[] ABSL_ATTRIBUTE_SECTION_VARIABLE(
protodesc_cold) = {
~0u, // no _has_bits_
PROTOBUF_FIELD_OFFSET(::google::protobuf::Any, _internal_metadata_),
@ -73,14 +74,15 @@ const ::uint32_t TableStruct_google_2fprotobuf_2fany_2eproto::offsets[] PROTOBUF
};
static const ::_pbi::MigrationSchema
schemas[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = {
{0, -1, -1, sizeof(::google::protobuf::Any)},
};
static const ::_pb::Message* const file_default_instances[] = {
&::google::protobuf::_Any_default_instance_._instance,
};
const char descriptor_table_protodef_google_2fprotobuf_2fany_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
const char descriptor_table_protodef_google_2fprotobuf_2fany_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE(
protodesc_cold) = {
"\n\031google/protobuf/any.proto\022\017google.prot"
"obuf\"&\n\003Any\022\020\n\010type_url\030\001 \001(\t\022\r\n\005value\030\002"
" \001(\014Bv\n\023com.google.protobufB\010AnyProtoP\001Z"
@ -193,6 +195,16 @@ inline void Any::SharedDtor() {
_impl_.~Impl_();
}
const ::google::protobuf::MessageLite::ClassData*
Any::GetClassData() const {
PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite::ClassData
data = {
Any::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
&::google::protobuf::Message::kDescriptorMethods,
};
return &data;
}
PROTOBUF_NOINLINE void Any::Clear() {
// @@protoc_insertion_point(message_clear_start:google.protobuf.Any)
PROTOBUF_TSAN_WRITE(&_impl_._tsan_detect_race);
@ -304,13 +316,6 @@ const ::_pbi::TcParseTable<1, 2, 0, 36, 2> Any::_table_ = {
return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
}
const ::google::protobuf::Message::ClassData Any::_class_data_ = {
Any::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
};
const ::google::protobuf::Message::ClassData* Any::GetClassData() const {
return &_class_data_;
}
void Any::MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg) {
auto* const _this = static_cast<Any*>(&to_msg);

@ -203,7 +203,7 @@ class PROTOBUF_EXPORT Any final :
private:
static void MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg);
public:
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
ABSL_ATTRIBUTE_REINITIALIZES void Clear() final;
bool IsInitialized() const final;
::size_t ByteSizeLong() const final;
@ -226,11 +226,9 @@ class PROTOBUF_EXPORT Any final :
protected:
explicit Any(::google::protobuf::Arena* arena);
Any(::google::protobuf::Arena* arena, const Any& from);
const ::google::protobuf::MessageLite::ClassData* GetClassData() const final;
public:
static const ClassData _class_data_;
const ::google::protobuf::Message::ClassData*GetClassData() const final;
::google::protobuf::Metadata GetMetadata() const final;
// nested types ----------------------------------------------------

@ -113,7 +113,8 @@ static constexpr const ::_pb::EnumDescriptor**
file_level_enum_descriptors_google_2fprotobuf_2fapi_2eproto = nullptr;
static constexpr const ::_pb::ServiceDescriptor**
file_level_service_descriptors_google_2fprotobuf_2fapi_2eproto = nullptr;
const ::uint32_t TableStruct_google_2fprotobuf_2fapi_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(
const ::uint32_t
TableStruct_google_2fprotobuf_2fapi_2eproto::offsets[] ABSL_ATTRIBUTE_SECTION_VARIABLE(
protodesc_cold) = {
PROTOBUF_FIELD_OFFSET(::google::protobuf::Api, _impl_._has_bits_),
PROTOBUF_FIELD_OFFSET(::google::protobuf::Api, _internal_metadata_),
@ -165,7 +166,7 @@ const ::uint32_t TableStruct_google_2fprotobuf_2fapi_2eproto::offsets[] PROTOBUF
};
static const ::_pbi::MigrationSchema
schemas[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
schemas[] ABSL_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = {
{0, 15, -1, sizeof(::google::protobuf::Api)},
{22, -1, -1, sizeof(::google::protobuf::Method)},
{37, -1, -1, sizeof(::google::protobuf::Mixin)},
@ -176,7 +177,8 @@ static const ::_pb::Message* const file_default_instances[] = {
&::google::protobuf::_Method_default_instance_._instance,
&::google::protobuf::_Mixin_default_instance_._instance,
};
const char descriptor_table_protodef_google_2fprotobuf_2fapi_2eproto[] PROTOBUF_SECTION_VARIABLE(protodesc_cold) = {
const char descriptor_table_protodef_google_2fprotobuf_2fapi_2eproto[] ABSL_ATTRIBUTE_SECTION_VARIABLE(
protodesc_cold) = {
"\n\031google/protobuf/api.proto\022\017google.prot"
"obuf\032$google/protobuf/source_context.pro"
"to\032\032google/protobuf/type.proto\"\201\002\n\003Api\022\014"
@ -330,6 +332,16 @@ inline void Api::SharedDtor() {
_impl_.~Impl_();
}
const ::google::protobuf::MessageLite::ClassData*
Api::GetClassData() const {
PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite::ClassData
data = {
Api::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
&::google::protobuf::Message::kDescriptorMethods,
};
return &data;
}
PROTOBUF_NOINLINE void Api::Clear() {
// @@protoc_insertion_point(message_clear_start:google.protobuf.Api)
PROTOBUF_TSAN_WRITE(&_impl_._tsan_detect_race);
@ -558,13 +570,6 @@ const ::_pbi::TcParseTable<3, 7, 4, 39, 2> Api::_table_ = {
return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
}
const ::google::protobuf::Message::ClassData Api::_class_data_ = {
Api::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
};
const ::google::protobuf::Message::ClassData* Api::GetClassData() const {
return &_class_data_;
}
void Api::MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg) {
auto* const _this = static_cast<Api*>(&to_msg);
@ -708,6 +713,16 @@ inline void Method::SharedDtor() {
_impl_.~Impl_();
}
const ::google::protobuf::MessageLite::ClassData*
Method::GetClassData() const {
PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite::ClassData
data = {
Method::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
&::google::protobuf::Message::kDescriptorMethods,
};
return &data;
}
PROTOBUF_NOINLINE void Method::Clear() {
// @@protoc_insertion_point(message_clear_start:google.protobuf.Method)
PROTOBUF_TSAN_WRITE(&_impl_._tsan_detect_race);
@ -924,13 +939,6 @@ const ::_pbi::TcParseTable<3, 7, 1, 68, 2> Method::_table_ = {
return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
}
const ::google::protobuf::Message::ClassData Method::_class_data_ = {
Method::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
};
const ::google::protobuf::Message::ClassData* Method::GetClassData() const {
return &_class_data_;
}
void Method::MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg) {
auto* const _this = static_cast<Method*>(&to_msg);
@ -1051,6 +1059,16 @@ inline void Mixin::SharedDtor() {
_impl_.~Impl_();
}
const ::google::protobuf::MessageLite::ClassData*
Mixin::GetClassData() const {
PROTOBUF_CONSTINIT static const ::google::protobuf::MessageLite::ClassData
data = {
Mixin::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
&::google::protobuf::Message::kDescriptorMethods,
};
return &data;
}
PROTOBUF_NOINLINE void Mixin::Clear() {
// @@protoc_insertion_point(message_clear_start:google.protobuf.Mixin)
PROTOBUF_TSAN_WRITE(&_impl_._tsan_detect_race);
@ -1165,13 +1183,6 @@ const ::_pbi::TcParseTable<1, 2, 0, 38, 2> Mixin::_table_ = {
return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_);
}
const ::google::protobuf::Message::ClassData Mixin::_class_data_ = {
Mixin::MergeImpl,
nullptr, // OnDemandRegisterArenaDtor
};
const ::google::protobuf::Message::ClassData* Mixin::GetClassData() const {
return &_class_data_;
}
void Mixin::MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg) {
auto* const _this = static_cast<Mixin*>(&to_msg);

@ -176,7 +176,7 @@ class PROTOBUF_EXPORT Mixin final :
private:
static void MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg);
public:
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
ABSL_ATTRIBUTE_REINITIALIZES void Clear() final;
bool IsInitialized() const final;
::size_t ByteSizeLong() const final;
@ -199,11 +199,9 @@ class PROTOBUF_EXPORT Mixin final :
protected:
explicit Mixin(::google::protobuf::Arena* arena);
Mixin(::google::protobuf::Arena* arena, const Mixin& from);
const ::google::protobuf::MessageLite::ClassData* GetClassData() const final;
public:
static const ClassData _class_data_;
const ::google::protobuf::Message::ClassData*GetClassData() const final;
::google::protobuf::Metadata GetMetadata() const final;
// nested types ----------------------------------------------------
@ -375,7 +373,7 @@ class PROTOBUF_EXPORT Method final :
private:
static void MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg);
public:
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
ABSL_ATTRIBUTE_REINITIALIZES void Clear() final;
bool IsInitialized() const final;
::size_t ByteSizeLong() const final;
@ -398,11 +396,9 @@ class PROTOBUF_EXPORT Method final :
protected:
explicit Method(::google::protobuf::Arena* arena);
Method(::google::protobuf::Arena* arena, const Method& from);
const ::google::protobuf::MessageLite::ClassData* GetClassData() const final;
public:
static const ClassData _class_data_;
const ::google::protobuf::Message::ClassData*GetClassData() const final;
::google::protobuf::Metadata GetMetadata() const final;
// nested types ----------------------------------------------------
@ -648,7 +644,7 @@ class PROTOBUF_EXPORT Api final :
private:
static void MergeImpl(::google::protobuf::Message& to_msg, const ::google::protobuf::Message& from_msg);
public:
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
ABSL_ATTRIBUTE_REINITIALIZES void Clear() final;
bool IsInitialized() const final;
::size_t ByteSizeLong() const final;
@ -671,11 +667,9 @@ class PROTOBUF_EXPORT Api final :
protected:
explicit Api(::google::protobuf::Arena* arena);
Api(::google::protobuf::Arena* arena, const Api& from);
const ::google::protobuf::MessageLite::ClassData* GetClassData() const final;
public:
static const ClassData _class_data_;
const ::google::protobuf::Message::ClassData*GetClassData() const final;
::google::protobuf::Metadata GetMetadata() const final;
// nested types ----------------------------------------------------

@ -261,7 +261,7 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final {
// again.
template <typename T>
PROTOBUF_ALWAYS_INLINE static void Destroy(T* obj) {
if (InternalGetOwningArena(obj) == nullptr) delete obj;
if (InternalGetArena(obj) == nullptr) delete obj;
}
// Allocates memory with the specific size and alignment.
@ -392,21 +392,6 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final {
struct Rank1 {};
struct Rank0 : Rank1 {};
static Arena* GetOwningArena(const T* p) {
return GetOwningArena(Rank0{}, p);
}
template <typename U>
static auto GetOwningArena(Rank0, const U* p)
-> EnableIfArena<decltype(p->GetOwningArena())> {
return p->GetOwningArena();
}
template <typename U>
static Arena* GetOwningArena(Rank1, const U*) {
return nullptr;
}
static void InternalSwap(T* a, T* b) { a->InternalSwap(b); }
static Arena* GetArena(T* p) { return GetArena(Rank0{}, p); }
@ -458,28 +443,10 @@ class PROTOBUF_EXPORT PROTOBUF_ALIGNAS(8) Arena final {
friend class TestUtil::ReflectionTester;
};
// Provides access to protected GetOwningArena to generated messages. For
// internal use only.
template <typename T>
static Arena* InternalGetOwningArena(T* p) {
ABSL_DCHECK_EQ(InternalHelper<T>::GetOwningArena(p),
InternalHelper<T>::GetArena(p));
return InternalHelper<T>::GetOwningArena(p);
}
// Wraps InternalGetArena() and will be removed soon.
// For internal use only.
template <typename T>
static Arena* InternalGetArenaForAllocation(T* p) {
return InternalHelper<T>::GetArena(p);
}
// Provides access to protected GetArena to generated messages.
// For internal use only.
template <typename T>
static Arena* InternalGetArena(T* p) {
ABSL_DCHECK_EQ(InternalHelper<T>::GetOwningArena(p),
InternalHelper<T>::GetArena(p));
return InternalHelper<T>::GetArena(p);
}

@ -561,16 +561,6 @@ TEST(ArenaTest, UnsafeArenaSwap) {
TestUtil::ExpectAllFieldsSet(*message2);
}
TEST(ArenaTest, GetOwningArena) {
Arena arena;
auto* m1 = Arena::CreateMessage<TestAllTypes>(&arena);
EXPECT_EQ(Arena::InternalGetOwningArena(m1), &arena);
EXPECT_EQ(&arena, Arena::InternalGetOwningArena(
m1->mutable_repeated_foreign_message()));
EXPECT_EQ(&arena,
Arena::InternalGetOwningArena(m1->mutable_repeated_int32()));
}
TEST(ArenaTest, SwapBetweenArenasUsingReflection) {
Arena arena1;
TestAllTypes* arena1_message = Arena::CreateMessage<TestAllTypes>(&arena1);

@ -337,7 +337,7 @@ struct PROTOBUF_EXPORT ArenaStringPtr {
// Returns a pointer to the stored contents for this instance.
// This method is for internal debugging and tracking purposes only.
PROTOBUF_NDEBUG_INLINE const std::string* UnsafeGetPointer() const
PROTOBUF_RETURNS_NONNULL {
ABSL_ATTRIBUTE_RETURNS_NONNULL {
return tagged_ptr_.Get();
}
@ -383,7 +383,7 @@ struct PROTOBUF_EXPORT ArenaStringPtr {
// Generated code only! An optimization, in certain cases the generated
// code is certain we can obtain a std::string with no default checks and
// tag tests.
std::string* UnsafeMutablePointer() PROTOBUF_RETURNS_NONNULL;
std::string* UnsafeMutablePointer() ABSL_ATTRIBUTE_RETURNS_NONNULL;
// Returns true if this instances holds an immutable default value.
inline bool IsDefault() const { return tagged_ptr_.IsDefault(); }

@ -401,6 +401,7 @@ TEST(ThreadSafeArenazSamplerTest, InitialBlockReportsZeroUsedAndWasted) {
for (int i = 0; i < 10; ++i) {
char block[kSize];
google::protobuf::Arena arena(/*initial_block=*/block, /*initial_block_size=*/kSize);
benchmark::DoNotOptimize(&arena);
sampler.Iterate([&](const ThreadSafeArenaStats& h) {
const auto& histbin =
h.block_histogram[ThreadSafeArenaStats::FindBin(kSize)];

@ -42,7 +42,7 @@ cc_library(
"parser.h",
],
copts = COPTS,
include_prefix = "google/protobuf/compiler",
strip_include_prefix = "/src",
visibility = ["//visibility:public"],
deps = [
"//src/google/protobuf:protobuf_nowkt",
@ -66,7 +66,7 @@ cc_library(
"scc.h",
],
copts = COPTS,
include_prefix = "google/protobuf/compiler",
strip_include_prefix = "/src",
visibility = ["//visibility:public"],
deps = [
"//src/google/protobuf:protobuf_nowkt",
@ -87,7 +87,7 @@ cc_library(
"versions_suffix.h",
],
copts = COPTS,
include_prefix = "google/protobuf/compiler",
strip_include_prefix = "/src",
visibility = [
"//src/google/protobuf/compiler:__subpackages__",
],
@ -107,7 +107,7 @@ cc_library(
"zip_writer.h",
],
copts = COPTS,
include_prefix = "google/protobuf/compiler",
strip_include_prefix = "/src",
visibility = ["//visibility:public"],
deps = [
":code_generator",
@ -133,6 +133,7 @@ cc_library(
"main.cc",
],
copts = COPTS,
strip_include_prefix = "/src",
visibility = [
"//:__pkg__",
"//pkg:__pkg__",
@ -154,6 +155,23 @@ cc_library(
],
)
# This is a build of the protobuf compiler without code generators.
cc_binary(
name = "protoc_minimal",
srcs = [
"main_no_generators.cc",
],
copts = COPTS,
visibility = [
"//src/google/protobuf:__subpackages__",
],
deps = [
":command_line_interface",
"//src/google/protobuf:port_def",
"@com_google_absl//absl/log:initialize",
],
)
# Note: this is an alias for now. In the future, this rule will become the
# cc_binary for protoc, and //:protoc will become an alias.
alias(
@ -397,7 +415,7 @@ cc_library(
name = "retention",
srcs = ["retention.cc"],
hdrs = ["retention.h"],
include_prefix = "google/protobuf/compiler",
strip_include_prefix = "/src",
visibility = ["//src/google/protobuf:__subpackages__"],
deps = [
"//src/google/protobuf:protobuf_nowkt",

@ -7,11 +7,12 @@ cc_library(
name = "allowlist",
hdrs = ["allowlist.h"],
copts = COPTS,
include_prefix = "google/protobuf/compiler/allowlists",
strip_include_prefix = "/src",
deps = [
"//src/google/protobuf/stubs",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:span",
],
@ -24,11 +25,10 @@ cc_library(
"empty_package.cc",
"open_enum.cc",
"unused_imports.cc",
"weak_imports.cc",
],
hdrs = ["allowlists.h"],
copts = COPTS,
include_prefix = "google/protobuf/compiler/allowlists",
strip_include_prefix = "/src",
visibility = ["//src/google/protobuf:__subpackages__"],
deps = [
":allowlist",

@ -10,10 +10,12 @@
#include <cstddef>
#include <cstring>
#include <string>
#include <type_traits>
#include "absl/algorithm/container.h"
#include "google/protobuf/stubs/common.h"
#include "absl/container/flat_hash_set.h"
#include "absl/strings/match.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
@ -29,8 +31,10 @@ enum AllowlistFlags : unsigned int {
kNone = 0,
kMatchPrefix = 1 << 1,
kAllowAllInOss = 1 << 2,
kAllowAllWhenEmpty = 1 << 3,
};
#if !defined(__GNUC__) || defined(__clang__) || PROTOBUF_GNUC_MIN(9, 1)
using maybe_string_view = absl::string_view;
#else

@ -1,29 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "google/protobuf/compiler/allowlists/allowlists.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/compiler/allowlists/allowlist.h"
namespace google {
namespace protobuf {
namespace compiler {
// NOTE: Allowlists in this file are not accepting new entries unless otherwise
// specified.
static constexpr auto kWeakImports = internal::MakeAllowlist({
// Intentionally left blank.
});
bool IsWeakImportFile(absl::string_view file) {
return kWeakImports.Allows(file);
}
} // namespace compiler
} // namespace protobuf
} // namespace google

@ -23,6 +23,9 @@
#include "google/protobuf/descriptor.h"
#include "google/protobuf/feature_resolver.h"
// Must be included last.
#include "google/protobuf/port_def.inc"
namespace google {
namespace protobuf {
namespace compiler {
@ -57,6 +60,14 @@ bool CodeGenerator::GenerateAll(const std::vector<const FileDescriptor*>& files,
absl::StatusOr<FeatureSetDefaults> CodeGenerator::BuildFeatureSetDefaults()
const {
if ((GetSupportedFeatures() & FEATURE_SUPPORTS_EDITIONS) == 0) {
// For generators that don't fully support editions yet, provide an
// optimistic set of defaults. Protoc will check this condition later
// anyway.
return FeatureResolver::CompileDefaults(
FeatureSet::descriptor(), GetFeatureExtensions(),
PROTOBUF_MINIMUM_EDITION, PROTOBUF_MAXIMUM_EDITION);
}
return FeatureResolver::CompileDefaults(
FeatureSet::descriptor(), GetFeatureExtensions(), GetMinimumEdition(),
GetMaximumEdition());
@ -131,3 +142,5 @@ bool IsKnownFeatureProto(absl::string_view filename) {
} // namespace compiler
} // namespace protobuf
} // namespace google
#include "google/protobuf/port_undef.inc"

@ -135,9 +135,9 @@ class PROTOC_EXPORT CodeGenerator {
protected:
// Retrieves the resolved source features for a given descriptor. All the
// features that are imported (from the proto file) and linked in (from the
// callers binary) will be fully resolved. These should be used to make any
// feature-based decisions during code generation.
// global features and language features returned by GetFeatureExtensions will
// be fully resolved. These should be used to make any feature-based decisions
// during code generation.
template <typename DescriptorT>
static const FeatureSet& GetResolvedSourceFeatures(const DescriptorT& desc) {
return ::google::protobuf::internal::InternalFeatureHelper::GetFeatures(desc);

@ -7,6 +7,7 @@
#include "google/protobuf/compiler/code_generator.h"
#include <cstdint>
#include <string>
#include <vector>
@ -43,6 +44,9 @@ class TestGenerator : public CodeGenerator {
return true;
}
uint64_t GetSupportedFeatures() const override { return features_; }
void set_supported_features(uint64_t features) { features_ = features; }
std::vector<const FieldDescriptor*> GetFeatureExtensions() const override {
return feature_extensions_;
}
@ -65,6 +69,7 @@ class TestGenerator : public CodeGenerator {
using CodeGenerator::GetUnresolvedSourceFeatures;
private:
uint64_t features_ = CodeGenerator::Feature::FEATURE_SUPPORTS_EDITIONS;
Edition minimum_edition_ = PROTOBUF_MINIMUM_EDITION;
Edition maximum_edition_ = PROTOBUF_MAXIMUM_EDITION;
std::vector<const FieldDescriptor*> feature_extensions_ = {
@ -301,6 +306,19 @@ TEST_F(CodeGeneratorTest, BuildFeatureSetDefaults) {
)pb")));
}
TEST_F(CodeGeneratorTest, BuildFeatureSetDefaultsUnsupported) {
TestGenerator generator;
generator.set_supported_features(0);
generator.set_feature_extensions({});
generator.set_minimum_edition(EDITION_99997_TEST_ONLY);
generator.set_maximum_edition(EDITION_99999_TEST_ONLY);
auto result = generator.BuildFeatureSetDefaults();
ASSERT_TRUE(result.ok()) << result.status().message();
EXPECT_EQ(result->minimum_edition(), PROTOBUF_MINIMUM_EDITION);
EXPECT_EQ(result->maximum_edition(), PROTOBUF_MAXIMUM_EDITION);
}
#include "google/protobuf/port_undef.inc"
} // namespace

@ -11,6 +11,7 @@
#include "google/protobuf/compiler/command_line_interface.h"
#include <cstdint>
#include <cstdlib>
#include "absl/algorithm/container.h"
@ -295,28 +296,6 @@ bool GetBootstrapParam(const std::string& parameter) {
}
bool EnforceEditionsSupport(
const std::string& codegen_name, uint64_t supported_features,
const std::vector<const FileDescriptor*>& parsed_files) {
if ((supported_features & CodeGenerator::FEATURE_SUPPORTS_EDITIONS) == 0) {
for (const auto fd : parsed_files) {
if (FileDescriptorLegacy(fd).syntax() ==
FileDescriptorLegacy::SYNTAX_EDITIONS) {
std::cerr
<< fd->name() << ": is an editions file, but code generator "
<< codegen_name
<< " hasn't been updated to support editions yet. Please ask "
"the owner of this code generator to add support or "
"switch back to proto2/proto3.\n\nSee "
"https://protobuf.dev/editions/overview/ for more information."
<< std::endl;
return false;
}
}
}
return true;
}
} // namespace
void CommandLineInterface::GetTransitiveDependencies(
@ -1538,25 +1517,27 @@ bool CommandLineInterface::SetupFeatureResolution(DescriptorPool& pool) {
for (const auto& output : output_directives_) {
if (output.generator == nullptr) continue;
if ((output.generator->GetSupportedFeatures() &
CodeGenerator::FEATURE_SUPPORTS_EDITIONS) == 0) {
continue;
}
if (output.generator->GetMinimumEdition() != PROTOBUF_MINIMUM_EDITION) {
CodeGenerator::FEATURE_SUPPORTS_EDITIONS) != 0) {
// Only validate min/max edition on generators that advertise editions
// support. Generators still under development will always use the
// correct values.
if (output.generator->GetMinimumEdition() != minimum_edition) {
ABSL_LOG(ERROR) << "Built-in generator " << output.name
<< " specifies a minimum edition "
<< output.generator->GetMinimumEdition()
<< " which is not the protoc minimum "
<< PROTOBUF_MINIMUM_EDITION << ".";
<< minimum_edition << ".";
return false;
}
if (output.generator->GetMaximumEdition() != PROTOBUF_MAXIMUM_EDITION) {
if (output.generator->GetMaximumEdition() != maximum_edition) {
ABSL_LOG(ERROR) << "Built-in generator " << output.name
<< " specifies a maximum edition "
<< output.generator->GetMaximumEdition()
<< " which is not the protoc maximum "
<< PROTOBUF_MAXIMUM_EDITION << ".";
<< maximum_edition << ".";
return false;
}
}
for (const FieldDescriptor* ext :
output.generator->GetFeatureExtensions()) {
if (ext == nullptr) {
@ -1899,7 +1880,7 @@ CommandLineInterface::ParseArgumentStatus CommandLineInterface::ParseArguments(
break; // only for --decode_raw
}
// --decode (not raw) is handled the same way as the rest of the modes.
PROTOBUF_FALLTHROUGH_INTENDED;
ABSL_FALLTHROUGH_INTENDED;
case MODE_ENCODE:
case MODE_PRINT:
missing_proto_definitions =
@ -2211,7 +2192,9 @@ CommandLineInterface::InterpretArgument(const std::string& name,
if (!version_info_.empty()) {
std::cout << version_info_ << std::endl;
}
std::cout << "libprotoc " << internal::ProtocVersionString(PROTOBUF_VERSION)
std::cout << "libprotoc "
<< ::google::protobuf::internal::ProtocVersionString(
PROTOBUF_VERSION)
<< PROTOBUF_VERSION_SUFFIX << std::endl;
return PARSE_ARGUMENT_DONE_AND_EXIT; // Exit without running compiler.
@ -2564,6 +2547,37 @@ bool CommandLineInterface::EnforceProto3OptionalSupport(
return true;
}
bool CommandLineInterface::EnforceEditionsSupport(
const std::string& codegen_name, uint64_t supported_features,
const std::vector<const FileDescriptor*>& parsed_files) const {
if (supported_features & CodeGenerator::FEATURE_SUPPORTS_EDITIONS) {
// This generator explicitly supports editions.
return true;
}
if (experimental_editions_) {
// The user has explicitly specified the experimental flag.
return true;
}
for (const auto* fd : parsed_files) {
// Skip enforcement for allowlisted files.
if (IsEarlyEditionsFile(fd->name())) continue;
if (FileDescriptorLegacy(fd).syntax() ==
FileDescriptorLegacy::SYNTAX_EDITIONS) {
std::cerr
<< fd->name() << ": is an editions file, but code generator "
<< codegen_name
<< " hasn't been updated to support editions yet. Please ask "
"the owner of this code generator to add support or "
"switch back to proto2/proto3.\n\nSee "
"https://protobuf.dev/editions/overview/ for more information."
<< std::endl;
return false;
}
}
return true;
}
bool CommandLineInterface::GenerateOutput(
const std::vector<const FileDescriptor*>& parsed_files,
const OutputDirective& output_directive,

@ -222,6 +222,10 @@ class PROTOC_EXPORT CommandLineInterface {
const std::string& codegen_name, uint64_t supported_features,
const std::vector<const FileDescriptor*>& parsed_files) const;
bool EnforceEditionsSupport(
const std::string& codegen_name, uint64_t supported_features,
const std::vector<const FileDescriptor*>& parsed_files) const;
// Return status for ParseArguments() and InterpretArgument().
enum ParseArgumentStatus {

@ -1354,16 +1354,14 @@ TEST_F(CommandLineInterfaceTest, AllowServicesHasService) {
ExpectGenerated("test_generator", "", "foo.proto", "Foo");
}
TEST_F(CommandLineInterfaceTest, EditionsAreNotAllowed) {
TEST_F(CommandLineInterfaceTest, NonExperimentalEditions) {
CreateTempFile("foo.proto",
"edition = \"2023\";\n"
"message FooRequest {}\n");
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
ExpectErrorSubstring(
"This file uses editions, but --experimental_editions has not been "
"enabled.");
ExpectErrorSubstring("--experimental_editions has not been enabled");
}
TEST_F(CommandLineInterfaceTest, EditionsFlagExplicitTrue) {
@ -1713,12 +1711,10 @@ TEST_F(CommandLineInterfaceTest, GeneratorNoEditionsSupport) {
"Doesn't support editions",
CodeGenerator::FEATURE_SUPPORTS_EDITIONS);
Run("protocol_compiler --experimental_editions "
Run("protocol_compiler "
"--proto_path=$tmpdir foo.proto --no_editions_out=$tmpdir");
ExpectErrorSubstring(
"code generator --no_editions_out hasn't been updated to support "
"editions");
ExpectErrorSubstring("--experimental_editions has not been enabled");
}
TEST_F(CommandLineInterfaceTest, PluginNoEditionsSupport) {
@ -1730,11 +1726,10 @@ TEST_F(CommandLineInterfaceTest, PluginNoEditionsSupport) {
)schema");
SetMockGeneratorTestCase("no_editions");
Run("protocol_compiler --experimental_editions "
Run("protocol_compiler "
"--proto_path=$tmpdir foo.proto --plug_out=$tmpdir");
ExpectErrorSubstring(
"code generator prefix-gen-plug hasn't been updated to support editions");
ExpectErrorSubstring("--experimental_editions has not been enabled");
}
TEST_F(CommandLineInterfaceTest, EditionDefaults) {

@ -11,7 +11,7 @@ cc_library(
name = "names",
hdrs = ["names.h"],
copts = COPTS,
include_prefix = "google/protobuf/compiler/cpp",
strip_include_prefix = "/src",
visibility = ["//visibility:public"],
deps = [
":names_internal",
@ -32,7 +32,7 @@ cc_library(
"options.h",
],
copts = COPTS,
include_prefix = "google/protobuf/compiler/cpp",
strip_include_prefix = "/src",
visibility = [
"//pkg:__pkg__",
"//src/google/protobuf/compiler/rust:__subpackages__",
@ -79,7 +79,7 @@ cc_library(
"tracker.h",
],
copts = COPTS,
include_prefix = "google/protobuf/compiler/cpp",
strip_include_prefix = "/src",
visibility = [
"//pkg:__pkg__",
"//src/google/protobuf/compiler:__pkg__",

@ -12,6 +12,7 @@
#include "google/protobuf/compiler/cpp/enum.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <string>

@ -65,9 +65,6 @@ class CordFieldGenerator : public FieldGeneratorBase {
void GenerateMergingCode(io::Printer* printer) const override;
void GenerateSwappingCode(io::Printer* printer) const override;
void GenerateConstructorCode(io::Printer* printer) const override;
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
void GenerateDestructorCode(io::Printer* printer) const override;
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void GenerateArenaDestructorCode(io::Printer* printer) const override;
void GenerateSerializeWithCachedSizesToArray(
io::Printer* printer) const override;
@ -244,17 +241,6 @@ void CordFieldGenerator::GenerateConstructorCode(io::Printer* printer) const {
}
}
#ifndef PROTOBUF_EXPLICIT_CONSTRUCTORS
void CordFieldGenerator::GenerateDestructorCode(io::Printer* printer) const {
Formatter format(printer, variables_);
if (should_split()) {
// A cord field in the `Split` struct is automatically destroyed when the
// split pointer is deleted and should not be explicitly destroyed here.
return;
}
format("$field$.~Cord();\n");
}
#endif // !PROTOBUF_EXPLICIT_CONSTRUCTORS
void CordFieldGenerator::GenerateArenaDestructorCode(
io::Printer* printer) const {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save