PROTOBUF_SYNC_PIPER
pull/10636/head
Mike Kruskal 2 years ago
parent b971bea8aa
commit c3e751aac6
  1. 7
      conformance/BUILD.bazel
  2. 9
      conformance/binary_json_conformance_suite.cc
  3. 80
      conformance/conformance_test.cc
  4. 2
      conformance/conformance_test.h
  5. 8
      conformance/conformance_test_runner.cc
  6. 13
      conformance/text_format_conformance_suite.cc
  7. 2
      java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java
  8. 3
      java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto
  9. 5
      src/file_lists.cmake
  10. 16
      src/google/protobuf/BUILD.bazel
  11. 523
      src/google/protobuf/arena.cc
  12. 202
      src/google/protobuf/arena_impl.h
  13. 29
      src/google/protobuf/arena_unittest.cc
  14. 3
      src/google/protobuf/compiler/BUILD.bazel
  15. 10
      src/google/protobuf/compiler/command_line_interface.cc
  16. 9
      src/google/protobuf/compiler/command_line_interface_unittest.cc
  17. 28
      src/google/protobuf/compiler/cpp/file.cc
  18. 71
      src/google/protobuf/compiler/cpp/message.cc
  19. 2
      src/google/protobuf/compiler/cpp/service.cc
  20. 38
      src/google/protobuf/compiler/java/enum_field_lite.cc
  21. 2
      src/google/protobuf/compiler/java/generator.cc
  22. 4
      src/google/protobuf/compiler/java/helpers.cc
  23. 2
      src/google/protobuf/compiler/java/map_field_lite.cc
  24. 28
      src/google/protobuf/compiler/java/message_builder_lite.cc
  25. 23
      src/google/protobuf/compiler/java/message_field_lite.cc
  26. 63
      src/google/protobuf/compiler/java/message_lite.cc
  27. 18
      src/google/protobuf/compiler/java/primitive_field_lite.cc
  28. 30
      src/google/protobuf/compiler/java/string_field_lite.cc
  29. 4
      src/google/protobuf/compiler/python/generator.cc
  30. 3
      src/google/protobuf/descriptor.cc
  31. 4
      src/google/protobuf/descriptor_unittest.cc
  32. 1
      src/google/protobuf/io/BUILD.bazel
  33. 78
      src/google/protobuf/io/printer.cc
  34. 169
      src/google/protobuf/io/printer.h
  35. 70
      src/google/protobuf/io/printer_unittest.cc
  36. 6
      src/google/protobuf/io/tokenizer.cc
  37. 1
      src/google/protobuf/map_field_test.cc
  38. 9
      src/google/protobuf/port_def.inc
  39. 2
      src/google/protobuf/port_undef.inc
  40. 4
      src/google/protobuf/stubs/BUILD.bazel
  41. 176
      src/google/protobuf/stubs/stringprintf.cc
  42. 87
      src/google/protobuf/stubs/stringprintf.h
  43. 157
      src/google/protobuf/stubs/stringprintf_unittest.cc
  44. 3
      src/google/protobuf/unittest.proto
  45. 2
      src/google/protobuf/util/internal/proto_writer.h
  46. 8
      src/google/protobuf/util/internal/protostream_objectsource.cc
  47. 2
      src/google/protobuf/util/internal/protostream_objectwriter.h
  48. 2
      src/google/protobuf/util/internal/protostream_objectwriter_test.cc
  49. 2
      src/google/protobuf/util/json_util.h
  50. 4
      src/google/protobuf/util/message_differencer.cc
  51. 8
      src/google/protobuf/util/time_util.cc
  52. 6
      src/google/protobuf/util/type_resolver_util.h
  53. 2
      src/google/protobuf/wire_format_lite.cc

@ -143,7 +143,11 @@ cc_library(
"conformance_test.h",
],
includes = ["."],
deps = [":conformance_cc_proto"],
deps = [
":conformance_cc_proto",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
],
)
cc_library(
@ -178,6 +182,7 @@ cc_binary(
":binary_json_conformance_suite",
":conformance_test",
":text_format_conformance_suite",
"@com_google_absl//absl/strings:str_format",
],
)

@ -35,6 +35,7 @@
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/type_resolver_util.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "third_party/jsoncpp/json.h"
#include "conformance_test.h"
#include "google/protobuf/test_messages_proto2.pb.h"
@ -584,16 +585,16 @@ void BinaryAndJsonConformanceSuite::RunValidJsonTestWithValidator(
if (response.result_case() != ConformanceResponse::kJsonPayload) {
ReportFailure(effective_test_name, level, request, response,
"Expected JSON payload but got type %d.",
response.result_case());
absl::StrCat("Expected JSON payload but got type ",
response.result_case()));
return;
}
Json::Reader reader;
Json::Value value;
if (!reader.parse(response.json_payload(), value)) {
ReportFailure(effective_test_name, level, request, response,
"JSON payload cannot be parsed as valid JSON: %s",
reader.getFormattedErrorMessages().c_str());
absl::StrCat("JSON payload cannot be parsed as valid JSON: ",
reader.getFormattedErrorMessages()));
return;
}
if (!validator(value)) {

@ -36,12 +36,13 @@
#include <set>
#include <string>
#include "google/protobuf/stubs/stringprintf.h"
#include "google/protobuf/message.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/util/field_comparator.h"
#include "google/protobuf/util/json_util.h"
#include "google/protobuf/util/message_differencer.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "conformance/conformance.pb.h"
using conformance::ConformanceRequest;
@ -177,10 +178,11 @@ string ConformanceTestSuite::ConformanceRequestSetting::
void ConformanceTestSuite::ReportSuccess(const string& test_name) {
if (expected_to_fail_.erase(test_name) != 0) {
StringAppendF(&output_,
"ERROR: test %s is in the failure list, but test succeeded. "
"Remove it from the failure list.\n",
test_name.c_str());
absl::StrAppendFormat(
&output_,
"ERROR: test %s is in the failure list, but test succeeded. "
"Remove it from the failure list.\n",
test_name);
unexpected_succeeding_tests_.insert(test_name);
}
successes_++;
@ -190,33 +192,29 @@ void ConformanceTestSuite::ReportFailure(const string& test_name,
ConformanceLevel level,
const ConformanceRequest& request,
const ConformanceResponse& response,
const char* fmt, ...) {
absl::string_view message) {
if (expected_to_fail_.erase(test_name) == 1) {
expected_failures_++;
if (!verbose_)
return;
} else if (level == RECOMMENDED && !enforce_recommended_) {
StringAppendF(&output_, "WARNING, test=%s: ", test_name.c_str());
absl::StrAppendFormat(&output_, "WARNING, test=%s: ", test_name);
} else {
StringAppendF(&output_, "ERROR, test=%s: ", test_name.c_str());
absl::StrAppendFormat(&output_, "ERROR, test=%s: ", test_name);
unexpected_failing_tests_.insert(test_name);
}
va_list args;
va_start(args, fmt);
StringAppendV(&output_, fmt, args);
va_end(args);
StringAppendF(&output_, " request=%s, response=%s\n",
request.ShortDebugString().c_str(),
response.ShortDebugString().c_str());
absl::StrAppendFormat(&output_, "%s request=%s, response=%s\n", message,
request.ShortDebugString(),
response.ShortDebugString());
}
void ConformanceTestSuite::ReportSkip(const string& test_name,
const ConformanceRequest& request,
const ConformanceResponse& response) {
if (verbose_) {
StringAppendF(&output_, "SKIPPED, test=%s request=%s, response=%s\n",
test_name.c_str(), request.ShortDebugString().c_str(),
response.ShortDebugString().c_str());
absl::StrAppendFormat(
&output_, "SKIPPED, test=%s request=%s, response=%s\n", test_name,
request.ShortDebugString(), response.ShortDebugString());
}
skipped_.insert(test_name);
}
@ -301,9 +299,10 @@ void ConformanceTestSuite::VerifyResponse(
ReportSuccess(test_name);
}
} else {
ReportFailure(test_name, level, request, response,
"Output was not equivalent to reference message: %s.",
differences.c_str());
ReportFailure(
test_name, level, request, response,
absl::StrCat("Output was not equivalent to reference message: ",
differences));
}
}
@ -326,11 +325,9 @@ void ConformanceTestSuite::RunTest(const string& test_name,
}
if (verbose_) {
StringAppendF(&output_,
"conformance test: name=%s, request=%s, response=%s\n",
test_name.c_str(),
request.ShortDebugString().c_str(),
response->ShortDebugString().c_str());
absl::StrAppendFormat(
&output_, "conformance test: name=%s, request=%s, response=%s\n",
test_name, request.ShortDebugString(), response->ShortDebugString());
}
}
@ -341,13 +338,12 @@ bool ConformanceTestSuite::CheckSetEmpty(
if (set_to_check.empty()) {
return true;
} else {
StringAppendF(&output_, "\n");
StringAppendF(&output_, "%s\n\n", msg.c_str());
for (std::set<string>::const_iterator iter = set_to_check.begin();
iter != set_to_check.end(); ++iter) {
StringAppendF(&output_, " %s\n", iter->c_str());
absl::StrAppendFormat(&output_, "\n");
absl::StrAppendFormat(&output_, "%s\n\n", msg);
for (absl::string_view v : set_to_check) {
absl::StrAppendFormat(&output_, " %s\n", v);
}
StringAppendF(&output_, "\n");
absl::StrAppendFormat(&output_, "\n");
if (!write_to_file.empty()) {
std::string full_filename;
@ -362,13 +358,11 @@ bool ConformanceTestSuite::CheckSetEmpty(
}
std::ofstream os(*filename);
if (os) {
for (std::set<string>::const_iterator iter = set_to_check.begin();
iter != set_to_check.end(); ++iter) {
os << *iter << "\n";
for (absl::string_view v : set_to_check) {
os << v << "\n";
}
} else {
StringAppendF(&output_, "Failed to open file: %s\n",
filename->c_str());
absl::StrAppendFormat(&output_, "Failed to open file: %s\n", *filename);
}
}
@ -452,12 +446,12 @@ bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
"features is not implemented)");
}
StringAppendF(&output_,
"CONFORMANCE SUITE %s: %d successes, %zu skipped, "
"%d expected failures, %zu unexpected failures.\n",
ok ? "PASSED" : "FAILED", successes_, skipped_.size(),
expected_failures_, unexpected_failing_tests_.size());
StringAppendF(&output_, "\n");
absl::StrAppendFormat(&output_,
"CONFORMANCE SUITE %s: %d successes, %zu skipped, "
"%d expected failures, %zu unexpected failures.\n",
ok ? "PASSED" : "FAILED", successes_, skipped_.size(),
expected_failures_, unexpected_failing_tests_.size());
absl::StrAppendFormat(&output_, "\n");
output->assign(output_);

@ -276,7 +276,7 @@ class ConformanceTestSuite {
void ReportFailure(const std::string& test_name, ConformanceLevel level,
const conformance::ConformanceRequest& request,
const conformance::ConformanceResponse& response,
const char* fmt, ...);
absl::string_view message);
void ReportSkip(const std::string& test_name,
const conformance::ConformanceRequest& request,
const conformance::ConformanceResponse& response);

@ -62,7 +62,7 @@
#include <fstream>
#include <vector>
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "conformance/conformance.pb.h"
#include "conformance_test.h"
@ -165,9 +165,11 @@ void ForkPipeRunner::RunTest(const std::string &test_name,
string error_msg;
if (WIFEXITED(status)) {
StringAppendF(&error_msg, "child exited, status=%d", WEXITSTATUS(status));
absl::StrAppendFormat(&error_msg, "child exited, status=%d",
WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
StringAppendF(&error_msg, "child killed by signal %d", WTERMSIG(status));
absl::StrAppendFormat(&error_msg, "child killed by signal %d",
WTERMSIG(status));
}
GOOGLE_LOG(INFO) << error_msg;
child_pid_ = -1;

@ -87,8 +87,7 @@ bool TextFormatConformanceTestSuite::ParseResponse(
ReportFailure(test_name, level, request, response,
absl::StrCat("Test was asked for ",
WireFormatToString(requested_output),
" output but provided PROTOBUF instead.")
.c_str());
" output but provided PROTOBUF instead."));
return false;
}
@ -103,11 +102,11 @@ bool TextFormatConformanceTestSuite::ParseResponse(
case ConformanceResponse::kTextPayload: {
if (requested_output != conformance::TEXT_FORMAT) {
ReportFailure(test_name, level, request, response,
absl::StrCat("Test was asked for ",
WireFormatToString(requested_output),
" output but provided TEXT_FORMAT instead.")
.c_str());
ReportFailure(
test_name, level, request, response,
absl::StrCat("Test was asked for ",
WireFormatToString(requested_output),
" output but provided TEXT_FORMAT instead."));
return false;
}

@ -129,6 +129,7 @@ public abstract class GeneratedMessageV3 extends AbstractMessage implements Seri
return internalGetFieldAccessorTable().descriptor;
}
// TODO(gberg): remove this. Have to leave it for now to support old gencode.
protected void mergeFromAndMakeImmutableInternal(
CodedInputStream input, ExtensionRegistryLite extensionRegistry)
throws InvalidProtocolBufferException {
@ -747,7 +748,6 @@ public abstract class GeneratedMessageV3 extends AbstractMessage implements Seri
UnknownFieldSet.newBuilder(this.unknownFields).mergeFrom(unknownFields).build());
}
@Override
public boolean isInitialized() {
for (final FieldDescriptor field : getDescriptorForType().getFields()) {

@ -31,7 +31,10 @@
syntax = "proto2";
package map_for_proto2_lite_test;
option java_outer_classname = "MapForProto2TestProto";
option optimize_for = LITE_RUNTIME;
option java_package = "map_lite_test";
message TestMap {
message MessageValue {

@ -65,7 +65,6 @@ set(libprotobuf_srcs
${protobuf_SOURCE_DIR}/src/google/protobuf/service.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/bytestream.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/common.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stringprintf.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/structurally_valid.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/strutil.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/text_format.cc
@ -174,7 +173,6 @@ set(libprotobuf_hdrs
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/port.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/status_macros.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stl_util.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stringprintf.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/strutil.h
${protobuf_SOURCE_DIR}/src/google/protobuf/text_format.h
${protobuf_SOURCE_DIR}/src/google/protobuf/unknown_field_set.h
@ -233,7 +231,6 @@ set(libprotobuf_lite_srcs
${protobuf_SOURCE_DIR}/src/google/protobuf/repeated_ptr_field.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/bytestream.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/common.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stringprintf.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/structurally_valid.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/strutil.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/wire_format_lite.cc
@ -282,7 +279,6 @@ set(libprotobuf_lite_hdrs
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/port.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/status_macros.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stl_util.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stringprintf.h
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/strutil.h
${protobuf_SOURCE_DIR}/src/google/protobuf/wire_format_lite.h
)
@ -745,7 +741,6 @@ set(util_test_protos_files
set(stubs_test_files
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/bytestream_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/common_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/stringprintf_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/structurally_valid_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/stubs/strutil_unittest.cc
)

@ -108,7 +108,6 @@ WELL_KNOWN_TYPES = [
genrule(
name = "gen_wkt_cc_sources",
srcs = [wkt + ".proto" for wkt in WELL_KNOWN_TYPES],
exec_tools = ["//src/google/protobuf/compiler:protoc_nowkt"],
outs =
[wkt + ".pb.h" for wkt in WELL_KNOWN_TYPES] +
[wkt + ".pb.cc" for wkt in WELL_KNOWN_TYPES],
@ -118,6 +117,7 @@ genrule(
--proto_path=$$(dirname $$(dirname $$(dirname $(location any.proto)))) \
$(SRCS)
""",
exec_tools = ["//src/google/protobuf/compiler:protoc_nowkt"],
visibility = ["//visibility:private"],
)
@ -125,11 +125,11 @@ cc_library(
name = "wkt_cc_proto",
srcs = [wkt + ".pb.cc" for wkt in WELL_KNOWN_TYPES],
hdrs = [wkt + ".pb.h" for wkt in WELL_KNOWN_TYPES],
deps = [":protobuf_nowkt"],
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
visibility = ["//pkg:__pkg__"],
deps = [":protobuf_nowkt"],
)
# Built-in runtime types
@ -313,7 +313,7 @@ cc_library(
"//src/google/protobuf:__subpackages__",
],
deps = [
":protobuf_lite",
":protobuf_lite",
"//src/google/protobuf/io",
"//src/google/protobuf/io:gzip_stream",
"//src/google/protobuf/io:printer",
@ -332,10 +332,6 @@ cc_library(
cc_library(
name = "protobuf",
deps = [
":protobuf_nowkt",
":wkt_cc_proto",
],
copts = COPTS,
include_prefix = "google/protobuf",
linkopts = LINK_OPTS,
@ -344,6 +340,10 @@ cc_library(
"//pkg:__pkg__",
"//src/google/protobuf:__subpackages__",
],
deps = [
":protobuf_nowkt",
":wkt_cc_proto",
],
)
# This provides just the header files for use in projects that need to build
@ -739,6 +739,7 @@ cc_test(
":protobuf",
"//src/google/protobuf/compiler:importer",
"//src/google/protobuf/testing",
"@com_google_absl//absl/strings:str_format",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],
@ -885,6 +886,7 @@ cc_test(
":protobuf",
":test_util",
"//src/google/protobuf/stubs",
"@com_google_absl//absl/strings:str_format",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],

@ -105,11 +105,42 @@ class GetDeallocator {
size_t* space_allocated_;
};
constexpr ArenaBlock SerialArena::kSentryBlock;
// It is guaranteed that this is constructed in `b`. IOW, this is not the first
// arena and `b` cannot be sentry.
SerialArena::SerialArena(ArenaBlock* b, ThreadSafeArena& parent)
: parent_(parent), space_allocated_(b->size()) {
: ptr_{b->Pointer(kBlockHeaderSize + ThreadSafeArena::kSerialArenaSize)},
limit_{b->Limit()},
head_{b},
space_allocated_{b->size()},
parent_{parent} {
GOOGLE_DCHECK(!b->IsSentry());
}
// It is guaranteed that this is the first SerialArena. Use sentry block.
SerialArena::SerialArena(ThreadSafeArena& parent)
: head_{SentryBlock()}, parent_{parent} {}
// It is guaranteed that this is the first SerialArena but `b` may be user
// provided or newly allocated to store AllocationPolicy.
SerialArena::SerialArena(FirstSerialArena, ArenaBlock* b,
ThreadSafeArena& parent)
: head_{b}, space_allocated_{b->size()}, parent_{parent} {
if (b->IsSentry()) return;
set_ptr(b->Pointer(kBlockHeaderSize));
limit_ = b->Limit();
}
void SerialArena::Init(ArenaBlock* b, size_t offset) {
set_ptr(b->Pointer(offset));
limit_ = b->Limit();
set_head(b);
set_ptr(b->Pointer(kBlockHeaderSize + ThreadSafeArena::kSerialArenaSize));
limit_ = b->Pointer(b->size() & static_cast<size_t>(-8));
space_used_.relaxed_set(0);
space_allocated_.relaxed_set(b->size());
cached_block_length_ = 0;
cached_blocks_ = nullptr;
}
SerialArena* SerialArena::New(Memory mem, ThreadSafeArena& parent) {
@ -155,33 +186,36 @@ void SerialArena::AddCleanupFallback(void* elem, void (*destructor)(void*)) {
}
void SerialArena::AllocateNewBlock(size_t n) {
// Sync limit to block
head()->cleanup_nodes = limit_;
size_t used = 0;
size_t wasted = 0;
ArenaBlock* old_head = head();
if (!old_head->IsSentry()) {
// Sync limit to block
old_head->cleanup_nodes = limit_;
// Record how much used in this block.
size_t used = static_cast<size_t>(ptr() - head()->Pointer(kBlockHeaderSize));
size_t wasted = head()->size() - used;
space_used_.store(space_used_.load(std::memory_order_relaxed) + used,
std::memory_order_relaxed);
// Record how much used in this block.
used = static_cast<size_t>(ptr() - old_head->Pointer(kBlockHeaderSize));
wasted = old_head->size() - used;
space_used_.relaxed_set(space_used_.relaxed_get() + used);
}
// TODO(sbenza): Evaluate if pushing unused space into the cached blocks is a
// win. In preliminary testing showed increased memory savings as expected,
// but with a CPU regression. The regression might have been an artifact of
// the microbenchmark.
auto mem = AllocateMemory(parent_.AllocPolicy(), head()->size(), n);
auto mem = AllocateMemory(parent_.AllocPolicy(), old_head->size(), n);
// We don't want to emit an expensive RMW instruction that requires
// exclusive access to a cacheline. Hence we write it in terms of a
// regular add.
space_allocated_.store(
space_allocated_.load(std::memory_order_relaxed) + mem.size,
std::memory_order_relaxed);
space_allocated_.relaxed_set(space_allocated_.relaxed_get() + mem.size);
ThreadSafeArenaStats::RecordAllocateStats(parent_.arena_stats_.MutableStats(),
/*used=*/used,
/*allocated=*/mem.size, wasted);
set_head(new (mem.ptr) ArenaBlock{head(), mem.size});
set_ptr(head()->Pointer(kBlockHeaderSize));
limit_ = head()->Pointer(head()->size());
auto* new_head = new (mem.ptr) ArenaBlock{old_head, mem.size};
set_head(new_head);
set_ptr(new_head->Pointer(kBlockHeaderSize));
limit_ = new_head->Limit();
#ifdef ADDRESS_SANITIZER
ASAN_POISON_MEMORY_REGION(ptr(), limit_ - ptr());
@ -196,24 +230,26 @@ uint64_t SerialArena::SpaceUsed() const {
// usage of the *current* block.
// TODO(mkruskal) Consider eliminating this race in exchange for a possible
// performance hit on ARM (see cl/455186837).
const uint64_t current_block_size = head()->size();
uint64_t space_used = std::min(
const ArenaBlock* h = head();
if (h->IsSentry()) return 0;
const uint64_t current_block_size = h->size();
uint64_t current_space_used = std::min(
static_cast<uint64_t>(
ptr() - const_cast<ArenaBlock*>(head())->Pointer(kBlockHeaderSize)),
ptr() - const_cast<ArenaBlock*>(h)->Pointer(kBlockHeaderSize)),
current_block_size);
space_used += space_used_.load(std::memory_order_relaxed);
// Remove the overhead of the SerialArena itself.
space_used -= ThreadSafeArena::kSerialArenaSize;
return space_used;
return current_space_used + space_used_.relaxed_get();
}
void SerialArena::CleanupList() {
ArenaBlock* b = head();
if (b->IsSentry()) return;
b->cleanup_nodes = limit_;
do {
char* limit = reinterpret_cast<char*>(
b->Pointer(b->size() & static_cast<size_t>(-8)));
char* limit = b->Limit();
char* it = reinterpret_cast<char*>(b->cleanup_nodes);
GOOGLE_DCHECK(!b->IsSentry() || it == limit);
if (it < limit) {
// A prefetch distance of 8 here was chosen arbitrarily. It makes the
// pending nodes fill a cacheline which seemed nice.
@ -260,86 +296,79 @@ void SerialArena::CleanupList() {
// Uses absl::container_internal::Layout to emulate the following:
//
// struct SerialArenaChunk {
// SerialArenaChunk* next_chunk;
// const uint32_t capacity;
// std::atomic<uint32_t> size;
// std::atomic<void*> ids[];
// std::atomic<SerialArena*> arenas[];
// struct SerialArenaChunkHeader {
// SerialArenaChunk* next_chunk;
// uint32_t capacity;
// Atomic<uint32_t> size;
// } header;
// Atomic<void*> ids[];
// Atomic<SerialArena*> arenas[];
// };
//
// where the size of "ids" and "arenas" is determined at runtime; hence the use
// of Layout.
class ThreadSafeArena::SerialArenaChunk {
public:
explicit SerialArenaChunk(uint32_t capacity) {
set_next(nullptr);
set_capacity(capacity);
new (&size()) std::atomic<uint32_t>{0};
struct SerialArenaChunkHeader {
constexpr SerialArenaChunkHeader(uint32_t capacity, uint32_t size)
: next_chunk(nullptr), capacity(capacity), size(size) {}
for (unsigned i = 0; i < capacity; ++i) {
new (&id(i)) std::atomic<void*>{nullptr};
}
for (unsigned i = 0; i < capacity; ++i) {
new (&arena(i)) std::atomic<void*>{nullptr};
}
}
ThreadSafeArena::SerialArenaChunk* next_chunk;
uint32_t capacity;
Atomic<uint32_t> size;
};
class ThreadSafeArena::SerialArenaChunk {
public:
SerialArenaChunk(uint32_t capacity, void* me, SerialArena* serial) {
set_next(nullptr);
set_capacity(capacity);
new (&size()) std::atomic<uint32_t>{1};
new (&header()) SerialArenaChunkHeader{capacity, 1};
new (&id(0)) std::atomic<void*>{me};
for (unsigned i = 1; i < capacity; ++i) {
new (&id(i)) std::atomic<void*>{nullptr};
new (&id(0)) Atomic<void*>{me};
for (uint32_t i = 1; i < capacity; ++i) {
new (&id(i)) Atomic<void*>{nullptr};
}
new (&arena(0)) std::atomic<SerialArena*>{serial};
for (unsigned i = 1; i < capacity; ++i) {
new (&arena(i)) std::atomic<void*>{nullptr};
new (&arena(0)) Atomic<SerialArena*>{serial};
for (uint32_t i = 1; i < capacity; ++i) {
new (&arena(i)) Atomic<void*>{nullptr};
}
}
bool IsSentry() const { return capacity() == 0; }
// next_chunk
const SerialArenaChunk* next_chunk() const {
return *layout_type::Partial().Pointer<kNextChunk>(ptr());
}
SerialArenaChunk* next_chunk() {
return *layout_type::Partial().Pointer<kNextChunk>(ptr());
}
const SerialArenaChunk* next_chunk() const { return header().next_chunk; }
SerialArenaChunk* next_chunk() { return header().next_chunk; }
void set_next(SerialArenaChunk* next_chunk) {
*layout_type::Partial().Pointer<kNextChunk>(ptr()) = next_chunk;
header().next_chunk = next_chunk;
}
// capacity
uint32_t capacity() const {
return *layout_type::Partial(1u).Pointer<kCapacity>(ptr());
}
void set_capacity(uint32_t capacity) {
*layout_type::Partial(1u).Pointer<kCapacity>(ptr()) = capacity;
}
uint32_t capacity() const { return header().capacity; }
void set_capacity(uint32_t capacity) { header().capacity = capacity; }
// ids: returns up to size().
absl::Span<const std::atomic<void*>> ids() const {
absl::Span<const Atomic<void*>> ids() const {
return Layout(capacity()).Slice<kIds>(ptr()).first(safe_size());
}
absl::Span<std::atomic<void*>> ids() {
absl::Span<Atomic<void*>> ids() {
return Layout(capacity()).Slice<kIds>(ptr()).first(safe_size());
}
std::atomic<void*>& id(unsigned i) {
Atomic<void*>& id(uint32_t i) {
GOOGLE_DCHECK_LT(i, capacity());
return Layout(capacity()).Pointer<kIds>(ptr())[i];
}
// arenas: returns up to size().
absl::Span<const std::atomic<SerialArena*>> arenas() const {
absl::Span<const Atomic<SerialArena*>> arenas() const {
return Layout(capacity()).Slice<kArenas>(ptr()).first(safe_size());
}
absl::Span<std::atomic<SerialArena*>> arenas() {
absl::Span<Atomic<SerialArena*>> arenas() {
return Layout(capacity()).Slice<kArenas>(ptr()).first(safe_size());
}
std::atomic<SerialArena*>& arena(unsigned i) {
const Atomic<SerialArena*>& arena(uint32_t i) const {
GOOGLE_DCHECK_LT(i, capacity());
return Layout(capacity()).Pointer<kArenas>(ptr())[i];
}
Atomic<SerialArena*>& arena(uint32_t i) {
GOOGLE_DCHECK_LT(i, capacity());
return Layout(capacity()).Pointer<kArenas>(ptr())[i];
}
@ -353,59 +382,68 @@ class ThreadSafeArena::SerialArenaChunk {
// other paths, either race is not possible (GetSerialArenaFallback) or must
// be prevented by users (CleanupList, Free).
bool insert(void* me, SerialArena* serial) {
uint32_t idx = size().fetch_add(1, std::memory_order_relaxed);
uint32_t idx = size().relaxed_fetch_add(1);
// Bail out if this chunk is full.
if (idx >= capacity()) {
// Write old value back to avoid potential overflow.
size().store(capacity(), std::memory_order_relaxed);
size().relaxed_set(capacity());
return false;
}
id(idx).store(me, std::memory_order_relaxed);
arena(idx).store(serial, std::memory_order_relaxed);
id(idx).relaxed_set(me);
arena(idx).relaxed_set(serial);
return true;
}
constexpr static size_t AllocSize(size_t n) { return Layout(n).AllocSize(); }
private:
constexpr static int kNextChunk = 0;
constexpr static int kCapacity = 1;
constexpr static int kSize = 2;
constexpr static int kIds = 3;
constexpr static int kArenas = 4;
constexpr static int kHeader = 0;
constexpr static int kIds = 1;
constexpr static int kArenas = 2;
using layout_type = absl::container_internal::Layout<
SerialArenaChunk*, uint32_t, std::atomic<uint32_t>, std::atomic<void*>,
std::atomic<SerialArena*>>;
using layout_type =
absl::container_internal::Layout<SerialArenaChunkHeader, Atomic<void*>,
Atomic<SerialArena*>>;
const char* ptr() const { return reinterpret_cast<const char*>(this); }
char* ptr() { return reinterpret_cast<char*>(this); }
std::atomic<uint32_t>& size() {
return *layout_type::Partial(1u, 1u).Pointer<kSize>(ptr());
SerialArenaChunkHeader& header() {
return *layout_type::Partial().Pointer<kHeader>(ptr());
}
const std::atomic<uint32_t>& size() const {
return *layout_type::Partial(1u, 1u).Pointer<kSize>(ptr());
const SerialArenaChunkHeader& header() const {
return *layout_type::Partial().Pointer<kHeader>(ptr());
}
Atomic<uint32_t>& size() { return header().size; }
const Atomic<uint32_t>& size() const { return header().size; }
// Returns the size capped by the capacity as fetch_add may result in a size
// greater than capacity.
uint32_t safe_size() const {
return std::min(capacity(), size().load(std::memory_order_relaxed));
return std::min(capacity(), size().relaxed_get());
}
constexpr static layout_type Layout(size_t n) {
return layout_type(
/*next_chunk*/ 1,
/*capacity*/ 1,
/*size*/ 1,
/*header*/ 1,
/*ids*/ n,
/*arenas*/ n);
}
};
constexpr SerialArenaChunkHeader kSentryArenaChunk = {0, 0};
ThreadSafeArena::SerialArenaChunk* ThreadSafeArena::SentrySerialArenaChunk() {
// const_cast is okay because the sentry chunk is never mutated. Also,
// reinterpret_cast is acceptable here as it should be identical to
// SerialArenaChunk with zero payload. This is a necessary trick to
// constexpr initialize kSentryArenaChunk.
return reinterpret_cast<SerialArenaChunk*>(
const_cast<SerialArenaChunkHeader*>(&kSentryArenaChunk));
}
ThreadSafeArena::CacheAlignedLifecycleIdGenerator
ThreadSafeArena::lifecycle_id_generator_;
@ -427,59 +465,71 @@ PROTOBUF_THREAD_LOCAL ThreadSafeArena::ThreadCache
nullptr};
#endif
void ThreadSafeArena::InitializeFrom(void* mem, size_t size) {
GOOGLE_DCHECK_EQ(reinterpret_cast<uintptr_t>(mem) & 7, 0u);
GOOGLE_DCHECK(!AllocPolicy()); // Reset should call InitializeWithPolicy instead.
ThreadSafeArena::ThreadSafeArena() : first_arena_(*this) { Init(); }
// Constructor solely used by message-owned arena.
ThreadSafeArena::ThreadSafeArena(internal::MessageOwned)
: tag_and_id_(kMessageOwnedArena), first_arena_(*this) {
Init();
}
ThreadSafeArena::ThreadSafeArena(char* mem, size_t size)
: first_arena_(FirstSerialArena{}, FirstBlock(mem, size), *this) {
Init();
}
ThreadSafeArena::ThreadSafeArena(void* mem, size_t size,
const AllocationPolicy& policy)
: first_arena_(FirstSerialArena{}, FirstBlock(mem, size, policy), *this) {
InitializeWithPolicy(policy);
}
// Ignore initial block if it is too small.
if (mem != nullptr && size >= kBlockHeaderSize + kSerialArenaSize) {
ArenaBlock* ThreadSafeArena::FirstBlock(void* buf, size_t size) {
GOOGLE_DCHECK_EQ(reinterpret_cast<uintptr_t>(buf) & 7, 0u);
if (buf == nullptr || size <= kBlockHeaderSize) {
return SerialArena::SentryBlock();
}
// Record user-owned block.
alloc_policy_.set_is_user_owned_initial_block(true);
return new (buf) ArenaBlock{nullptr, size};
}
ArenaBlock* ThreadSafeArena::FirstBlock(void* buf, size_t size,
const AllocationPolicy& policy) {
if (policy.IsDefault()) return FirstBlock(buf, size);
GOOGLE_DCHECK_EQ(reinterpret_cast<uintptr_t>(buf) & 7, 0u);
SerialArena::Memory mem;
if (buf == nullptr || size < kBlockHeaderSize + kAllocPolicySize) {
mem = AllocateMemory(&policy, 0, kAllocPolicySize);
} else {
mem = {buf, size};
// Record user-owned block.
alloc_policy_.set_is_user_owned_initial_block(true);
SetInitialBlock(mem, size);
}
return new (mem.ptr) ArenaBlock{nullptr, mem.size};
}
void ThreadSafeArena::InitializeWithPolicy(void* mem, size_t size,
AllocationPolicy policy) {
void ThreadSafeArena::InitializeWithPolicy(const AllocationPolicy& policy) {
Init();
if (policy.IsDefault()) return;
#ifndef NDEBUG
const uint64_t old_alloc_policy = alloc_policy_.get_raw();
// If there was a policy (e.g., in Reset()), make sure flags were preserved.
#define GOOGLE_DCHECK_POLICY_FLAGS_() \
if (old_alloc_policy > 3) \
GOOGLE_CHECK_EQ(old_alloc_policy & 3, alloc_policy_.get_raw() & 3)
GOOGLE_CHECK_EQ(old_alloc_policy & 3, alloc_policy_.get_raw() & 3)
#else
#define GOOGLE_DCHECK_POLICY_FLAGS_()
#endif // NDEBUG
if (policy.IsDefault()) {
// Legacy code doesn't use the API above, but provides the initial block
// through ArenaOptions. I suspect most do not touch the allocation
// policy parameters.
InitializeFrom(mem, size);
GOOGLE_DCHECK_POLICY_FLAGS_();
return;
}
GOOGLE_DCHECK_EQ(reinterpret_cast<uintptr_t>(mem) & 7, 0u);
Init();
// Ignore initial block if it is too small. We include an optional
// AllocationPolicy in this check, so that this can be allocated on the
// first block.
constexpr size_t kAPSize = internal::AlignUpTo8(sizeof(AllocationPolicy));
constexpr size_t kMinimumSize = kBlockHeaderSize + kSerialArenaSize + kAPSize;
// Make sure we have an initial block to store the AllocationPolicy.
if (mem != nullptr && size >= kMinimumSize) {
alloc_policy_.set_is_user_owned_initial_block(true);
} else {
auto tmp = AllocateMemory(&policy, 0, kMinimumSize);
mem = tmp.ptr;
size = tmp.size;
}
SerialArena* sa = SetInitialBlock(mem, size);
// We ensured enough space so this cannot fail.
void* p;
if (!sa || !sa->MaybeAllocateAligned(kAPSize, &p)) {
if (!first_arena_.MaybeAllocateAligned(kAllocPolicySize, &p)) {
GOOGLE_LOG(FATAL) << "MaybeAllocateAligned cannot fail here.";
return;
}
@ -500,11 +550,10 @@ uint64_t ThreadSafeArena::GetNextLifeCycleId() {
constexpr uint64_t kDelta = 2;
constexpr uint64_t kInc = ThreadCache::kPerThreadIds * kDelta;
if (PROTOBUF_PREDICT_FALSE((id & (kInc - 1)) == 0)) {
constexpr auto relaxed = std::memory_order_relaxed;
// On platforms that don't support uint64_t atomics we can certainly not
// afford to increment by large intervals and expect uniqueness due to
// wrapping, hence we only add by 1.
id = lifecycle_id_generator_.id.fetch_add(1, relaxed) * kInc;
id = lifecycle_id_generator_.id.relaxed_fetch_add(1) * kInc;
}
tc.next_lifecycle_id = id + kDelta;
return id;
@ -532,9 +581,6 @@ ThreadSafeArena::SerialArenaChunk* ThreadSafeArena::NewSerialArenaChunk(
next_bytes = SerialArenaChunk::AllocSize(next_capacity);
void* mem;
mem = ::operator new(next_bytes);
if (serial == nullptr) {
return new (mem) SerialArenaChunk{next_capacity};
}
return new (mem) SerialArenaChunk{next_capacity, id, serial};
}
@ -542,10 +588,9 @@ ThreadSafeArena::SerialArenaChunk* ThreadSafeArena::NewSerialArenaChunk(
// Tries to reserve an entry by atomic fetch_add. If the head chunk is already
// full (size >= capacity), acquires the mutex and adds a new head.
void ThreadSafeArena::AddSerialArena(void* id, SerialArena* serial) {
SerialArenaChunk* head = head_.load(std::memory_order_acquire);
GOOGLE_DCHECK_NE(head, nullptr);
SerialArenaChunk* head = head_.atomic_get();
// Fast path without acquiring mutex.
if (head->insert(id, serial)) {
if (!head->IsSentry() && head->insert(id, serial)) {
return;
}
@ -553,7 +598,7 @@ void ThreadSafeArena::AddSerialArena(void* id, SerialArena* serial) {
absl::MutexLock lock(&mutex_);
// Refetch and if someone else installed a new head, try allocating on that!
SerialArenaChunk* new_head = head_.load(std::memory_order_acquire);
SerialArenaChunk* new_head = head_.atomic_get();
if (new_head != head) {
if (new_head->insert(id, serial)) return;
// Update head to link to the latest one.
@ -565,7 +610,7 @@ void ThreadSafeArena::AddSerialArena(void* id, SerialArena* serial) {
// Use "std::memory_order_release" to make sure prior stores are visible after
// this one.
head_.store(new_head, std::memory_order_release);
head_.atomic_set(new_head);
}
void ThreadSafeArena::Init() {
@ -576,17 +621,20 @@ void ThreadSafeArena::Init() {
} else {
GOOGLE_DCHECK_EQ(tag_and_id_, kMessageOwnedArena);
}
auto* empty_chunk = NewSerialArenaChunk(0, nullptr, nullptr);
head_.store(empty_chunk, std::memory_order_relaxed);
GOOGLE_DCHECK_EQ(message_owned, IsMessageOwned());
arena_stats_ = Sample();
}
head_.relaxed_set(SentrySerialArenaChunk());
GOOGLE_DCHECK_EQ(message_owned, IsMessageOwned());
first_owner_ = &thread_cache();
SerialArena* ThreadSafeArena::SetInitialBlock(void* mem, size_t size) {
SerialArena* serial = SerialArena::New({mem, size}, *this);
AddSerialArena(&thread_cache(), serial);
CacheSerialArena(serial);
return serial;
// Record allocation for the first block that was either user-provided or
// newly allocated.
ThreadSafeArenaStats::RecordAllocateStats(
arena_stats_.MutableStats(),
/*used=*/0,
/*allocated=*/first_arena_.SpaceAllocated(),
/*wasted=*/0);
CacheSerialArena(&first_arena_);
}
ThreadSafeArena::~ThreadSafeArena() {
@ -602,27 +650,35 @@ ThreadSafeArena::~ThreadSafeArena() {
ASAN_UNPOISON_MEMORY_REGION(mem.ptr, mem.size);
#endif // ADDRESS_SANITIZER
space_allocated += mem.size;
} else {
} else if (mem.size > 0) {
GetDeallocator(alloc_policy_.get(), &space_allocated)(mem);
}
}
SerialArena::Memory ThreadSafeArena::Free(size_t* space_allocated) {
SerialArena::Memory mem = {nullptr, 0};
auto deallocator = GetDeallocator(alloc_policy_.get(), space_allocated);
PerSerialArena([deallocator, &mem](SerialArena* a) {
if (mem.ptr) deallocator(mem);
mem = a->Free(deallocator);
});
// Free chunks that stored SerialArena.
SerialArenaChunk* chunk = head_.load(std::memory_order_relaxed);
while (chunk != nullptr) {
SerialArenaChunk* next_chunk = chunk->next_chunk();
WalkSerialArenaChunk([deallocator](SerialArenaChunk* chunk) {
absl::Span<Atomic<SerialArena*>> span = chunk->arenas();
// Walks arenas backward to handle the first serial arena the last. Freeing
// in reverse-order to the order in which objects were created may not be
// necessary to Free and we should revisit this. (b/247560530)
for (auto it = span.rbegin(); it != span.rend(); ++it) {
SerialArena* serial = it->relaxed_get();
GOOGLE_DCHECK_NE(serial, nullptr);
// Always frees the first block of "serial" as it cannot be user-provided.
SerialArena::Memory mem = serial->Free(deallocator);
GOOGLE_DCHECK_NE(mem.ptr, nullptr);
deallocator(mem);
}
// Delete the chunk as we're done with it.
internal::SizedDelete(chunk,
SerialArenaChunk::AllocSize(chunk->capacity()));
chunk = next_chunk;
}
return mem;
});
// The first block of the first arena is special and let the caller handle it.
return first_arena_.Free(deallocator);
}
uint64_t ThreadSafeArena::Reset() {
@ -630,32 +686,29 @@ uint64_t ThreadSafeArena::Reset() {
// refer to memory in other blocks.
CleanupList();
// Discard all blocks except the special block (if present).
// Discard all blocks except the first one. Whether it is user-provided or
// allocated, always reuse the first block for the first arena.
size_t space_allocated = 0;
auto mem = Free(&space_allocated);
AllocationPolicy* policy = alloc_policy_.get();
if (policy) {
auto saved_policy = *policy;
if (alloc_policy_.is_user_owned_initial_block()) {
space_allocated += mem.size;
} else {
GetDeallocator(alloc_policy_.get(), &space_allocated)(mem);
mem.ptr = nullptr;
mem.size = 0;
}
InitializeWithPolicy(mem.ptr, mem.size, saved_policy);
space_allocated += mem.size;
// Reset the first arena with the first block. This avoids redundant
// free / allocation and re-allocating for AllocationPolicy. Adjust offset if
// we need to preserve alloc_policy_.
if (alloc_policy_.is_user_owned_initial_block() ||
alloc_policy_.get() != nullptr) {
size_t offset = alloc_policy_.get() == nullptr
? kBlockHeaderSize
: kBlockHeaderSize + kAllocPolicySize;
first_arena_.Init(new (mem.ptr) ArenaBlock{nullptr, mem.size}, offset);
} else {
// Nullptr policy
if (alloc_policy_.is_user_owned_initial_block()) {
space_allocated += mem.size;
InitializeFrom(mem.ptr, mem.size);
} else {
GetDeallocator(alloc_policy_.get(), &space_allocated)(mem);
Init();
}
first_arena_.Init(SerialArena::SentryBlock(), 0);
}
// Since the first block and potential alloc_policy on the first block is
// preserved, this can be initialized by Init().
Init();
return space_allocated;
}
@ -685,51 +738,62 @@ void* ThreadSafeArena::AllocateAlignedWithCleanupFallback(
}
template <typename Functor>
void ThreadSafeArena::PerConstSerialArena(Functor fn) const {
const SerialArenaChunk* chunk = head_.load(std::memory_order_acquire);
for (; chunk; chunk = chunk->next_chunk()) {
absl::Span<const std::atomic<SerialArena*>> span = chunk->arenas();
// Walks arenas backward to handle the first serial arena the last. This is
// necessary to special-case the initial block.
for (auto it = span.crbegin(); it != span.crend(); ++it) {
const SerialArena* serial = it->load(std::memory_order_relaxed);
// It is possible that newly added SerialArena is not updated although
// size was. This is acceptable for SpaceAllocated and SpaceUsed.
if (serial == nullptr) continue;
fn(serial);
}
void ThreadSafeArena::WalkConstSerialArenaChunk(Functor fn) const {
const SerialArenaChunk* chunk = head_.atomic_get();
for (; !chunk->IsSentry(); chunk = chunk->next_chunk()) {
fn(chunk);
}
}
template <typename Functor>
void ThreadSafeArena::PerSerialArena(Functor fn) {
void ThreadSafeArena::WalkSerialArenaChunk(Functor fn) {
// By omitting an Acquire barrier we help the sanitizer that any user code
// that doesn't properly synchronize Reset() or the destructor will throw a
// TSAN warning.
SerialArenaChunk* chunk = head_.load(std::memory_order_relaxed);
SerialArenaChunk* chunk = head_.relaxed_get();
for (; chunk; chunk = chunk->next_chunk()) {
absl::Span<std::atomic<SerialArena*>> span = chunk->arenas();
// Walks arenas backward to handle the first serial arena the last. This is
// necessary to special-case the initial block.
for (auto it = span.rbegin(); it != span.rend(); ++it) {
SerialArena* serial = it->load(std::memory_order_relaxed);
GOOGLE_DCHECK_NE(serial, nullptr);
while (!chunk->IsSentry()) {
// Cache next chunk in case this chunk is destroyed.
SerialArenaChunk* next_chunk = chunk->next_chunk();
fn(chunk);
chunk = next_chunk;
}
}
template <typename Functor>
void ThreadSafeArena::PerConstSerialArenaInChunk(Functor fn) const {
WalkConstSerialArenaChunk([&fn](const SerialArenaChunk* chunk) {
for (const auto& each : chunk->arenas()) {
const SerialArena* serial = each.relaxed_get();
// It is possible that newly added SerialArena is not updated although
// size was. This is acceptable for SpaceAllocated and SpaceUsed.
if (serial == nullptr) continue;
fn(serial);
}
}
});
}
uint64_t ThreadSafeArena::SpaceAllocated() const {
uint64_t space_allocated = 0;
PerConstSerialArena([&space_allocated](const SerialArena* serial) {
uint64_t space_allocated = first_arena_.SpaceAllocated();
PerConstSerialArenaInChunk([&space_allocated](const SerialArena* serial) {
space_allocated += serial->SpaceAllocated();
});
return space_allocated;
}
uint64_t ThreadSafeArena::SpaceUsed() const {
// First arena is inlined to ThreadSafeArena and the first block's overhead is
// smaller than others that contain SerialArena.
uint64_t space_used = first_arena_.SpaceUsed();
PerConstSerialArenaInChunk([&space_used](const SerialArena* serial) {
// SerialArena on chunks directly allocated from the block and needs to be
// subtracted from SpaceUsed.
space_used += serial->SpaceUsed() - kSerialArenaSize;
});
return space_used - (alloc_policy_.get() ? sizeof(AllocationPolicy) : 0);
}
template <AllocationClient alloc_client>
PROTOBUF_NOINLINE void* ThreadSafeArena::AllocateAlignedFallback(size_t n) {
return GetSerialArenaFallback()->AllocateAligned<alloc_client>(n);
@ -740,35 +804,42 @@ template void* ThreadSafeArena::AllocateAlignedFallback<
template void*
ThreadSafeArena::AllocateAlignedFallback<AllocationClient::kArray>(size_t);
uint64_t ThreadSafeArena::SpaceUsed() const {
uint64_t space_used = 0;
PerConstSerialArena([&space_used](const SerialArena* serial) {
space_used += serial->SpaceUsed();
});
return space_used - (alloc_policy_.get() ? sizeof(AllocationPolicy) : 0);
}
void ThreadSafeArena::CleanupList() {
PerSerialArena([](SerialArena* a) { a->CleanupList(); });
WalkSerialArenaChunk([](SerialArenaChunk* chunk) {
absl::Span<Atomic<SerialArena*>> span = chunk->arenas();
// Walks arenas backward to handle the first serial arena the last.
// Destroying in reverse-order to the construction is often assumed by users
// and required not to break inter-object dependencies. (b/247560530)
for (auto it = span.rbegin(); it != span.rend(); ++it) {
SerialArena* serial = it->relaxed_get();
GOOGLE_DCHECK_NE(serial, nullptr);
serial->CleanupList();
}
});
// First arena must be cleaned up last. (b/247560530)
first_arena_.CleanupList();
}
PROTOBUF_NOINLINE
SerialArena* ThreadSafeArena::GetSerialArenaFallback() {
void* const id = &thread_cache();
SerialArena* serial = nullptr;
if (id == first_owner_) {
CacheSerialArena(&first_arena_);
return &first_arena_;
}
// Search matching SerialArena.
SerialArenaChunk* chunk = head_.load(std::memory_order_acquire);
for (; chunk; chunk = chunk->next_chunk()) {
absl::Span<std::atomic<void*>> ids = chunk->ids();
for (unsigned i = 0; i < ids.size(); ++i) {
if (ids[i].load(std::memory_order_relaxed) == id) {
serial = chunk->arena(i).load(std::memory_order_relaxed);
SerialArena* serial = nullptr;
WalkConstSerialArenaChunk([&serial, id](const SerialArenaChunk* chunk) {
absl::Span<const Atomic<void*>> ids = chunk->ids();
for (uint32_t i = 0; i < ids.size(); ++i) {
if (ids[i].relaxed_get() == id) {
serial = chunk->arena(i).relaxed_get();
GOOGLE_DCHECK_NE(serial, nullptr);
break;
}
}
}
});
if (!serial) {
// This thread doesn't have any SerialArena, which also means it doesn't

@ -36,6 +36,7 @@
#include <atomic>
#include <limits>
#include <string>
#include <type_traits>
#include <typeinfo>
#include "google/protobuf/stubs/common.h"
@ -45,10 +46,6 @@
#include "google/protobuf/port.h"
#ifdef ADDRESS_SANITIZER
#include <sanitizer/asan_interface.h>
#endif // ADDRESS_SANITIZER
#include "google/protobuf/arena_config.h"
#include "google/protobuf/arenaz_sampler.h"
@ -90,26 +87,55 @@ inline PROTOBUF_ALWAYS_INLINE void* AlignTo(void* p, size_t a) {
}
}
// Wraps std::atomic<T> to avoid accidentally accessing the atomic variable via
// a default memory order (std::memory_order_seq_cst).
template <typename T>
struct Atomic {
constexpr explicit Atomic(T v) : val(v) {}
T relaxed_get() const { return val.load(std::memory_order_relaxed); }
T relaxed_get() { return val.load(std::memory_order_relaxed); }
void relaxed_set(T v) { val.store(v, std::memory_order_relaxed); }
T atomic_get() const { return val.load(std::memory_order_acquire); }
T atomic_get() { return val.load(std::memory_order_acquire); }
void atomic_set(T v) { val.store(v, std::memory_order_release); }
T relaxed_fetch_add(T v) {
return val.fetch_add(v, std::memory_order_relaxed);
}
private:
std::atomic<T> val;
};
// Arena blocks are variable length malloc-ed objects. The following structure
// describes the common header for all blocks.
struct ArenaBlock {
// For the sentry block with zero-size where ptr_, limit_, cleanup_nodes all
// point to "this".
constexpr ArenaBlock()
: next(nullptr), cleanup_nodes(this), relaxed_size(0) {}
ArenaBlock(ArenaBlock* next, size_t size)
: next(next), cleanup_nodes(nullptr), relaxed_size(size) {
GOOGLE_DCHECK_GT(size, sizeof(ArenaBlock));
}
char* Pointer(size_t n) {
GOOGLE_DCHECK(n <= size());
GOOGLE_DCHECK_LE(n, size());
return reinterpret_cast<char*>(this) + n;
}
char* Limit() { return Pointer(size() & static_cast<size_t>(-8)); }
size_t size() const { return relaxed_size.load(std::memory_order_relaxed); }
size_t size() const { return relaxed_size.relaxed_get(); }
bool IsSentry() const { return size() == 0; }
ArenaBlock* const next;
void* cleanup_nodes;
private:
const std::atomic<size_t> relaxed_size;
const Atomic<size_t> relaxed_size;
// data follows
};
@ -344,6 +370,11 @@ enum class AllocationClient { kDefault, kArray };
class ThreadSafeArena;
// Tag type used to invoke the constructor of the first SerialArena.
struct FirstSerialArena {
explicit FirstSerialArena() = default;
};
// A simple arena allocator. Calls to allocate functions must be properly
// serialized by the caller, hence this class cannot be used as a general
// purpose allocator in a multi-threaded program. It serves as a building block
@ -363,9 +394,7 @@ class PROTOBUF_EXPORT SerialArena {
};
void CleanupList();
uint64_t SpaceAllocated() const {
return space_allocated_.load(std::memory_order_relaxed);
}
uint64_t SpaceAllocated() const { return space_allocated_.relaxed_get(); }
uint64_t SpaceUsed() const;
bool HasSpace(size_t n) const {
@ -384,9 +413,7 @@ class PROTOBUF_EXPORT SerialArena {
if (cached_head == nullptr) return nullptr;
void* ret = cached_head;
#ifdef ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(ret, size);
#endif // ADDRESS_SANITIZER
PROTOBUF_UNPOISON_MEMORY_REGION(ret, size);
cached_head = cached_head->next;
return ret;
}
@ -417,9 +444,7 @@ class PROTOBUF_EXPORT SerialArena {
private:
void* AllocateFromExisting(size_t n) {
#ifdef ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(ptr(), n);
#endif // ADDRESS_SANITIZER
PROTOBUF_UNPOISON_MEMORY_REGION(ptr(), n);
void* ret = ptr();
set_ptr(static_cast<char*>(ret) + n);
return ret;
@ -451,13 +476,12 @@ class PROTOBUF_EXPORT SerialArena {
std::copy(cached_blocks_, cached_blocks_ + cached_block_length_,
new_list);
#ifdef ADDRESS_SANITIZER
// We need to unpoison this memory before filling it in case it has been
// poisoned by another santizer client.
ASAN_UNPOISON_MEMORY_REGION(
PROTOBUF_UNPOISON_MEMORY_REGION(
new_list + cached_block_length_,
(new_size - cached_block_length_) * sizeof(CachedBlock*));
#endif
std::fill(new_list + cached_block_length_, new_list + new_size, nullptr);
cached_blocks_ = new_list;
@ -473,9 +497,7 @@ class PROTOBUF_EXPORT SerialArena {
auto* new_node = static_cast<CachedBlock*>(p);
new_node->next = cached_head;
cached_head = new_node;
#ifdef ADDRESS_SANITIZER
ASAN_POISON_MEMORY_REGION(p, size);
#endif // ADDRESS_SANITIZER
PROTOBUF_POISON_MEMORY_REGION(p, size);
}
public:
@ -533,9 +555,7 @@ class PROTOBUF_EXPORT SerialArena {
void* AllocateFromExistingWithCleanupFallback(size_t n, size_t align,
void (*destructor)(void*)) {
n = AlignUpTo(n, align);
#ifdef ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(ptr(), n);
#endif // ADDRESS_SANITIZER
PROTOBUF_UNPOISON_MEMORY_REGION(ptr(), n);
void* ret = internal::AlignTo(ptr(), align);
set_ptr(ptr() + n);
GOOGLE_DCHECK_GE(limit_, ptr());
@ -548,9 +568,7 @@ class PROTOBUF_EXPORT SerialArena {
cleanup::Tag tag = cleanup::Type(destructor);
size_t n = cleanup::Size(tag);
#ifdef ADDRESS_SANITIZER
ASAN_UNPOISON_MEMORY_REGION(limit_ - n, n);
#endif // ADDRESS_SANITIZER
PROTOBUF_UNPOISON_MEMORY_REGION(limit_ - n, n);
limit_ -= n;
GOOGLE_DCHECK_GE(limit_, ptr());
cleanup::CreateNode(tag, limit_, elem, destructor);
@ -559,6 +577,13 @@ class PROTOBUF_EXPORT SerialArena {
private:
friend class ThreadSafeArena;
static constexpr ArenaBlock kSentryBlock = {};
static ArenaBlock* SentryBlock() {
// const_cast<> is okay as kSentryBlock will never be mutated.
return const_cast<ArenaBlock*>(&kSentryBlock);
}
// Creates a new SerialArena inside mem using the remaining memory as for
// future allocations.
// The `parent` arena must outlive the serial arena, which is guaranteed
@ -568,30 +593,20 @@ class PROTOBUF_EXPORT SerialArena {
template <typename Deallocator>
Memory Free(Deallocator deallocator);
ThreadSafeArena& parent_;
std::atomic<ArenaBlock*> head_; // Head of linked list of blocks.
std::atomic<size_t> space_used_{0}; // Necessary for metrics.
std::atomic<size_t> space_allocated_;
// Members are declared here to track sizeof(SerialArena) and hotness
// centrally. They are (roughly) laid out in descending order of hotness.
// Next pointer to allocate from. Always 8-byte aligned. Points inside
// head_ (and head_->pos will always be non-canonical). We keep these
// here to reduce indirection.
std::atomic<char*> ptr_;
// Helper getters/setters to handle relaxed operations on atomic variables.
ArenaBlock* head() { return head_.load(std::memory_order_relaxed); }
const ArenaBlock* head() const {
return head_.load(std::memory_order_relaxed);
}
void set_head(ArenaBlock* head) {
return head_.store(head, std::memory_order_relaxed);
}
char* ptr() { return ptr_.load(std::memory_order_relaxed); }
const char* ptr() const { return ptr_.load(std::memory_order_relaxed); }
void set_ptr(char* ptr) { return ptr_.store(ptr, std::memory_order_relaxed); }
Atomic<char*> ptr_{nullptr};
// Limiting address up to which memory can be allocated from the head block.
char* limit_;
char* limit_ = nullptr;
Atomic<ArenaBlock*> head_{nullptr}; // Head of linked list of blocks.
Atomic<size_t> space_used_{0}; // Necessary for metrics.
Atomic<size_t> space_allocated_{0};
ThreadSafeArena& parent_;
// Repeated*Field and Arena play together to reduce memory consumption by
// reusing blocks. Currently, natural growth of the repeated field types makes
@ -607,14 +622,28 @@ class PROTOBUF_EXPORT SerialArena {
uint8_t cached_block_length_ = 0;
CachedBlock** cached_blocks_ = nullptr;
// Helper getters/setters to handle relaxed operations on atomic variables.
ArenaBlock* head() { return head_.relaxed_get(); }
const ArenaBlock* head() const { return head_.relaxed_get(); }
void set_head(ArenaBlock* head) { return head_.relaxed_set(head); }
char* ptr() { return ptr_.relaxed_get(); }
const char* ptr() const { return ptr_.relaxed_get(); }
void set_ptr(char* ptr) { return ptr_.relaxed_set(ptr); }
// Constructor is private as only New() should be used.
inline SerialArena(ArenaBlock* b, ThreadSafeArena& parent);
// Constructors to handle the first SerialArena.
inline explicit SerialArena(ThreadSafeArena& parent);
inline SerialArena(FirstSerialArena, ArenaBlock* b, ThreadSafeArena& parent);
void* AllocateAlignedFallback(size_t n);
void* AllocateAlignedWithCleanupFallback(size_t n, size_t align,
void (*destructor)(void*));
void AddCleanupFallback(void* elem, void (*destructor)(void*));
void AllocateNewBlock(size_t n);
inline void AllocateNewBlock(size_t n);
inline void Init(ArenaBlock* b, size_t offset);
public:
static constexpr size_t kBlockHeaderSize = AlignUpTo8(sizeof(ArenaBlock));
@ -635,19 +664,15 @@ struct MessageOwned {
// use #ifdef the select the best implementation based on hardware / OS.
class PROTOBUF_EXPORT ThreadSafeArena {
public:
ThreadSafeArena() { Init(); }
ThreadSafeArena();
// Constructor solely used by message-owned arena.
ThreadSafeArena(internal::MessageOwned) : tag_and_id_(kMessageOwnedArena) {
Init();
}
explicit ThreadSafeArena(internal::MessageOwned);
ThreadSafeArena(char* mem, size_t size) { InitializeFrom(mem, size); }
ThreadSafeArena(char* mem, size_t size);
explicit ThreadSafeArena(void* mem, size_t size,
const AllocationPolicy& policy) {
InitializeWithPolicy(mem, size, policy);
}
const AllocationPolicy& policy);
// All protos have pointers back to the arena hence Arena must have
// pointer stability.
@ -712,6 +737,7 @@ class PROTOBUF_EXPORT ThreadSafeArena {
friend class ArenaBenchmark;
friend class TcParser;
friend class SerialArena;
friend struct SerialArenaChunkHeader;
static uint64_t GetNextLifeCycleId();
class SerialArenaChunk;
@ -720,33 +746,49 @@ class PROTOBUF_EXPORT ThreadSafeArena {
// grow based on "prev_num_slots".
static SerialArenaChunk* NewSerialArenaChunk(uint32_t prev_capacity, void* id,
SerialArena* serial);
static SerialArenaChunk* SentrySerialArenaChunk();
// Returns the first ArenaBlock* for the first SerialArena. If users provide
// one, use it if it's acceptable. Otherwise returns a sentry block.
ArenaBlock* FirstBlock(void* buf, size_t size);
// Same as the above but returns a valid block if "policy" is not default.
ArenaBlock* FirstBlock(void* buf, size_t size,
const AllocationPolicy& policy);
// Adds SerialArena to the chunked list. May create a new chunk.
void AddSerialArena(void* id, SerialArena* serial);
// Members are declared here to track sizeof(ThreadSafeArena) and hotness
// centrally.
// Unique for each arena. Changes on Reset().
uint64_t tag_and_id_ = 0;
// The LSB of tag_and_id_ indicates if the arena is message-owned.
enum : uint64_t { kMessageOwnedArena = 1 };
TaggedAllocationPolicyPtr alloc_policy_; // Tagged pointer to AllocPolicy.
static_assert(std::is_trivially_destructible<SerialArena>{},
"SerialArena needs to be trivially destructible.");
// Pointer to a linked list of SerialArenaChunk.
std::atomic<SerialArenaChunk*> head_;
ThreadSafeArenaStatsHandle arena_stats_;
// Adding a new chunk to head_ must be protected by mutex_.
absl::Mutex mutex_;
// Pointer to a linked list of SerialArenaChunk.
Atomic<SerialArenaChunk*> head_{nullptr};
void* first_owner_;
// Must be declared after alloc_policy_; otherwise, it may lose info on
// user-provided initial block.
SerialArena first_arena_;
// The LSB of tag_and_id_ indicates if the arena is message-owned.
enum : uint64_t { kMessageOwnedArena = 1 };
static_assert(std::is_trivially_destructible<SerialArena>{},
"SerialArena needs to be trivially destructible.");
const AllocationPolicy* AllocPolicy() const { return alloc_policy_.get(); }
void InitializeFrom(void* mem, size_t size);
void InitializeWithPolicy(void* mem, size_t size, AllocationPolicy policy);
void InitializeWithPolicy(const AllocationPolicy& policy);
void* AllocateAlignedWithCleanupFallback(size_t n, size_t align,
void (*destructor)(void*));
void Init();
SerialArena* SetInitialBlock(void* mem, size_t size);
// Delete or Destruct all objects owned by the arena.
void CleanupList();
@ -776,15 +818,19 @@ class PROTOBUF_EXPORT ThreadSafeArena {
template <AllocationClient alloc_client = AllocationClient::kDefault>
void* AllocateAlignedFallback(size_t n);
// Executes callback function over chunked list of SerialArena in reverse
// chronological order. Passes const SerialArena*.
// Executes callback function over SerialArenaChunk. Passes const
// SerialArenaChunk*.
template <typename Functor>
void WalkConstSerialArenaChunk(Functor fn) const;
// Executes callback function over SerialArenaChunk.
template <typename Functor>
void PerConstSerialArena(Functor fn) const;
void WalkSerialArenaChunk(Functor fn);
// Executes callback function over chunked list of SerialArena in reverse
// chronological order.
// Executes callback function over SerialArena in chunked list in reverse
// chronological order. Passes const SerialArena*.
template <typename Functor>
void PerSerialArena(Functor fn);
void PerConstSerialArenaInChunk(Functor fn) const;
// Releases all memory except the first block which it returns. The first
// block might be owned by the user and thus need some extra checks before
@ -826,7 +872,9 @@ class PROTOBUF_EXPORT ThreadSafeArena {
#pragma warning(disable : 4324)
#endif
struct alignas(kCacheAlignment) CacheAlignedLifecycleIdGenerator {
std::atomic<LifecycleIdAtomic> id;
constexpr CacheAlignedLifecycleIdGenerator() : id{0} {}
Atomic<LifecycleIdAtomic> id;
};
static CacheAlignedLifecycleIdGenerator lifecycle_id_generator_;
#if defined(GOOGLE_PROTOBUF_NO_THREADLOCAL)
@ -842,14 +890,14 @@ class PROTOBUF_EXPORT ThreadSafeArena {
static ThreadCache& thread_cache() { return thread_cache_; }
#endif
ThreadSafeArenaStatsHandle arena_stats_;
public:
// kBlockHeaderSize is sizeof(Block), aligned up to the nearest multiple of 8
// to protect the invariant that pos is always at a multiple of 8.
// kBlockHeaderSize is sizeof(ArenaBlock), aligned up to the nearest multiple
// of 8 to protect the invariant that pos is always at a multiple of 8.
static constexpr size_t kBlockHeaderSize = SerialArena::kBlockHeaderSize;
static constexpr size_t kSerialArenaSize =
(sizeof(SerialArena) + 7) & static_cast<size_t>(-8);
static constexpr size_t kAllocPolicySize =
AlignUpTo8(sizeof(AllocationPolicy));
static_assert(kBlockHeaderSize % 8 == 0,
"kBlockHeaderSize must be a multiple of 8.");
static_assert(kSerialArenaSize % 8 == 0,

@ -46,6 +46,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/strings/string_view.h"
#include "absl/synchronization/barrier.h"
#include "google/protobuf/arena_test_util.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/extension_set.h"
@ -281,7 +282,8 @@ TEST(ArenaTest, CreateWithMoveArguments) {
TEST(ArenaTest, InitialBlockTooSmall) {
// Construct a small blocks of memory to be used by the arena allocator; then,
// allocate an object which will not fit in the initial block.
for (int size = 0; size <= Arena::kBlockOverhead + 32; size++) {
for (uint32_t size = 0; size <= internal::SerialArena::kBlockHeaderSize + 32;
size++) {
std::vector<char> arena_block(size);
ArenaOptions options;
options.initial_block = arena_block.data();
@ -1407,6 +1409,31 @@ TEST(ArenaTest, SpaceAllocated_and_Used) {
EXPECT_EQ(1024, arena_2.Reset());
}
namespace {
void VerifyArenaOverhead(Arena& arena, size_t overhead) {
EXPECT_EQ(0, arena.SpaceAllocated());
// Allocate a tiny block and record the allocation size.
constexpr size_t kTinySize = 8;
Arena::CreateArray<char>(&arena, kTinySize);
uint64_t space_allocated = arena.SpaceAllocated();
// Next allocation expects to fill up the block but no new block.
uint64_t next_size = space_allocated - overhead - kTinySize;
Arena::CreateArray<char>(&arena, next_size);
EXPECT_EQ(space_allocated, arena.SpaceAllocated());
}
} // namespace
TEST(ArenaTest, FirstArenaOverhead) {
Arena arena;
VerifyArenaOverhead(arena, internal::SerialArena::kBlockHeaderSize);
}
TEST(ArenaTest, BlockSizeDoubling) {
Arena arena;
EXPECT_EQ(0, arena.SpaceUsed());

@ -84,6 +84,7 @@ cc_library(
":importer",
"//src/google/protobuf:protobuf_nowkt",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
],
)
@ -126,8 +127,8 @@ cc_library(
"//pkg:__pkg__",
],
deps = [
"//:protobuf",
":protoc_lib_nowkt",
"//:protobuf",
],
)

@ -76,7 +76,7 @@
#include "google/protobuf/compiler/plugin.pb.h"
#include "google/protobuf/stubs/strutil.h"
#include "absl/strings/match.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/substitute.h"
@ -2618,7 +2618,7 @@ void GatherOccupiedFieldRanges(
void FormatFreeFieldNumbers(const std::string& name,
const std::set<FieldRange>& ranges) {
std::string output;
StringAppendF(&output, "%-35s free:", name.c_str());
absl::StrAppendFormat(&output, "%-35s free:", name.c_str());
int next_free_number = 1;
for (std::set<FieldRange>::const_iterator i = ranges.begin();
i != ranges.end(); ++i) {
@ -2629,17 +2629,17 @@ void FormatFreeFieldNumbers(const std::string& name,
if (next_free_number < i->first) {
if (next_free_number + 1 == i->first) {
// Singleton
StringAppendF(&output, " %d", next_free_number);
absl::StrAppendFormat(&output, " %d", next_free_number);
} else {
// Range
StringAppendF(&output, " %d-%d", next_free_number,
absl::StrAppendFormat(&output, " %d-%d", next_free_number,
i->first - 1);
}
}
next_free_number = i->second;
}
if (next_free_number <= FieldDescriptor::kMaxNumber) {
StringAppendF(&output, " %d-INF", next_free_number);
absl::StrAppendFormat(&output, " %d-INF", next_free_number);
}
std::cout << output << std::endl;
}

@ -38,6 +38,8 @@
#include <cstdint>
#include "absl/strings/str_format.h"
#ifndef _MSC_VER
#include <unistd.h>
#endif
@ -45,7 +47,6 @@
#include <string>
#include <vector>
#include "google/protobuf/stubs/stringprintf.h"
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
#include "google/protobuf/testing/file.h"
@ -2259,9 +2260,9 @@ TEST_F(CommandLineInterfaceTest, PluginReceivesCompilerVersion) {
Run("protocol_compiler --plug_out=$tmpdir --proto_path=$tmpdir foo.proto");
ExpectErrorSubstring(StringPrintf("Saw compiler_version: %d %s",
GOOGLE_PROTOBUF_VERSION,
GOOGLE_PROTOBUF_VERSION_SUFFIX));
ExpectErrorSubstring(absl::StrFormat("Saw compiler_version: %d %s",
GOOGLE_PROTOBUF_VERSION,
GOOGLE_PROTOBUF_VERSION_SUFFIX));
}
TEST_F(CommandLineInterfaceTest, GeneratorPluginNotFound) {

@ -41,6 +41,7 @@
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "google/protobuf/compiler/scc.h"
@ -51,7 +52,7 @@
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
@ -860,13 +861,13 @@ void FileGenerator::GenerateSource(io::Printer* p) {
void FileGenerator::GenerateReflectionInitializationCode(io::Printer* p) {
if (!message_generators_.empty()) {
p->Emit({{"len", absl::StrCat(message_generators_.size())}}, R"cc(
p->Emit({{"len", message_generators_.size()}}, R"cc(
static ::_pb::Metadata $file_level_metadata$[$len$];
)cc");
}
if (!enum_generators_.empty()) {
p->Emit({{"len", absl::StrCat(enum_generators_.size())}}, R"cc(
p->Emit({{"len", enum_generators_.size()}}, R"cc(
static const ::_pb::EnumDescriptor* $file_level_enum_descriptors$[$len$];
)cc");
} else {
@ -877,7 +878,7 @@ void FileGenerator::GenerateReflectionInitializationCode(io::Printer* p) {
}
if (HasGenericServices(file_, options_) && file_->service_count() > 0) {
p->Emit({{"len", absl::StrCat(file_->service_count())}}, R"cc(
p->Emit({{"len", file_->service_count()}}, R"cc(
static const ::_pb::ServiceDescriptor*
$file_level_service_descriptors$[$len$];
)cc");
@ -1015,7 +1016,7 @@ void FileGenerator::GenerateReflectionInitializationCode(io::Printer* p) {
if (num_deps > 0) {
p->Emit(
{
{"len", absl::StrCat(num_deps)},
{"len", num_deps},
{"deps",
[&] {
for (auto dep : refs.strong_reflection_files) {
@ -1046,13 +1047,13 @@ void FileGenerator::GenerateReflectionInitializationCode(io::Printer* p) {
p->Emit(
{
{"eager", eager ? "true" : "false"},
{"file_proto_len", absl::StrCat(file_data.size())},
{"file_proto_len", file_data.size()},
{"proto_name", desc_name},
{"deps_ptr", num_deps == 0
? "nullptr"
: absl::StrCat(p->LookupVar("desc_table"), "_deps")},
{"num_deps", absl::StrCat(num_deps)},
{"num_msgs", absl::StrCat(message_generators_.size())},
{"num_deps", num_deps},
{"num_msgs", message_generators_.size()},
{"msgs_ptr", message_generators_.empty()
? "nullptr"
: std::string(p->LookupVar("file_level_metadata"))},
@ -1116,8 +1117,7 @@ class FileGenerator::ForwardDeclarations {
void Print(io::Printer* p, const Options& options) const {
for (const auto& e : enums_) {
auto a = p->WithAnnotations({{"enum", e.second}});
p->Emit({{"enum", e.first}}, R"cc(
p->Emit({{"enum", e.first, e.second}}, R"cc(
enum $enum$ : int;
bool $enum$_IsValid(int value);
)cc");
@ -1125,10 +1125,9 @@ class FileGenerator::ForwardDeclarations {
for (const auto& c : classes_) {
const Descriptor* desc = c.second;
auto a = p->WithAnnotations({{"class", desc}});
p->Emit(
{
{"class", c.first},
{"class", c.first, desc},
{"default_type", DefaultInstanceType(desc, options)},
{"default_name", DefaultInstanceName(desc, options)},
},
@ -1254,9 +1253,8 @@ void FileGenerator::GenerateLibraryIncludes(io::Printer* p) {
IncludeFile("net/proto2/public/port_def.inc", p);
p->Emit(
{
{"min_version",
absl::StrCat(PROTOBUF_MIN_HEADER_VERSION_FOR_PROTOC)},
{"version", absl::StrCat(PROTOBUF_VERSION)},
{"min_version", PROTOBUF_MIN_HEADER_VERSION_FOR_PROTOC},
{"version", PROTOBUF_VERSION},
},
R"(
#if PROTOBUF_VERSION < $min_version$

@ -53,9 +53,10 @@
#include "google/protobuf/map_entry_lite.h"
#include "google/protobuf/wire_format.h"
#include "google/protobuf/stubs/strutil.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
@ -791,38 +792,36 @@ void MessageGenerator::GenerateFieldAccessorDeclarations(io::Printer* printer) {
format.AddMap(vars);
if (field->is_repeated()) {
if (field->is_repeated()) {
format("$deprecated_attr$int ${1$$name$_size$}$() const$2$\n", field,
!IsFieldStripped(field, options_) ? ";" : " {__builtin_trap();}");
if (!IsFieldStripped(field, options_)) {
format(
"$deprecated_attr$int ${1$$name$_size$}$() const$2$\n", field,
!IsFieldStripped(field, options_) ? ";" : " {__builtin_trap();}");
if (!IsFieldStripped(field, options_)) {
format(
"private:\n"
"int ${1$_internal_$name$_size$}$() const;\n"
"public:\n",
field);
}
} else if (HasHasMethod(field)) {
"private:\n"
"int ${1$_internal_$name$_size$}$() const;\n"
"public:\n",
field);
}
} else if (HasHasMethod(field)) {
format("$deprecated_attr$bool ${1$has_$name$$}$() const$2$\n", field,
!IsFieldStripped(field, options_) ? ";" : " {__builtin_trap();}");
if (!IsFieldStripped(field, options_)) {
format(
"$deprecated_attr$bool ${1$has_$name$$}$() const$2$\n", field,
!IsFieldStripped(field, options_) ? ";" : " {__builtin_trap();}");
if (!IsFieldStripped(field, options_)) {
format(
"private:\n"
"bool _internal_has_$name$() const;\n"
"public:\n");
}
} else if (HasPrivateHasMethod(field)) {
if (!IsFieldStripped(field, options_)) {
format(
"private:\n"
"bool ${1$_internal_has_$name$$}$() const;\n"
"public:\n",
field);
}
"private:\n"
"bool _internal_has_$name$() const;\n"
"public:\n");
}
} else if (HasPrivateHasMethod(field)) {
if (!IsFieldStripped(field, options_)) {
format(
"private:\n"
"bool ${1$_internal_has_$name$$}$() const;\n"
"public:\n",
field);
}
format("$deprecated_attr$void ${1$clear_$name$$}$()$2$\n", field,
!IsFieldStripped(field, options_) ? ";" : "{__builtin_trap();}");
}
format("$deprecated_attr$void ${1$clear_$name$$}$()$2$\n", field,
!IsFieldStripped(field, options_) ? ";" : "{__builtin_trap();}");
// Generate type-specific accessor declarations.
field_generators_.get(field).GenerateAccessorDeclarations(printer);
@ -1267,9 +1266,9 @@ void MessageGenerator::GenerateFieldAccessorDefinitions(io::Printer* printer) {
GenerateSingularFieldHasBits(field, format);
}
if (!IsCrossFileMaybeMap(field)) {
GenerateFieldClear(field, true, format);
}
if (!IsCrossFileMaybeMap(field)) {
GenerateFieldClear(field, true, format);
}
// Generate type-specific accessors.
if (!IsFieldStripped(field, options_)) {
field_generators_.get(field).GenerateInlineAccessorDefinitions(printer);
@ -2130,8 +2129,7 @@ void MessageGenerator::GenerateClassMethods(io::Printer* printer) {
format("};\n\n");
for (auto field : FieldRange(descriptor_)) {
if (!IsFieldStripped(field, options_)) {
field_generators_.get(field).GenerateInternalAccessorDefinitions(
printer);
field_generators_.get(field).GenerateInternalAccessorDefinitions(printer);
}
}
@ -2677,8 +2675,7 @@ void MessageGenerator::GenerateConstexprConstructor(io::Printer* printer) {
continue;
}
put_sep();
field_generators_.get(field).GenerateConstexprAggregateInitializer(
printer);
field_generators_.get(field).GenerateConstexprAggregateInitializer(printer);
}
if (ShouldSplit(descriptor_, options_)) {
put_sep();

@ -138,7 +138,7 @@ void ServiceGenerator::GenerateImplementation(io::Printer* printer) {
auto vars = printer->WithVars(&vars_);
printer->Emit(
{
{"index", absl::StrCat(index_in_metadata_)},
{"index", index_in_metadata_},
{"no_impl_methods", [&] { GenerateNotImplementedMethods(printer); }},
{"call_method", [&] { GenerateCallMethod(printer); }},
{"get_request", [&] { GenerateGetPrototype(kRequest, printer); }},

@ -137,6 +137,9 @@ void SetEnumVariables(const FieldDescriptor* descriptor, int messageBitIndex,
// We use `x.getClass()` as a null check because it generates less bytecode
// than an `if (x == null) { throw ... }` statement.
(*variables)["null_check"] = "value.getClass();\n";
// Calls to Annotate() use variable ranges to know which text to annotate.
(*variables)["{"] = "";
(*variables)["}"] = "";
}
} // namespace
@ -165,15 +168,19 @@ void ImmutableEnumFieldLiteGenerator::GenerateInterfaceMembers(
if (HasHazzer(descriptor_)) {
WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
printer->Print(variables_,
"$deprecation$boolean has$capitalized_name$();\n");
"$deprecation$boolean ${$has$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
if (SupportUnknownEnumValue(descriptor_->file())) {
WriteFieldEnumValueAccessorDocComment(printer, descriptor_, GETTER);
printer->Print(variables_,
"$deprecation$int get$capitalized_name$Value();\n");
"$deprecation$int ${$get$capitalized_name$Value$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
WriteFieldAccessorDocComment(printer, descriptor_, GETTER);
printer->Print(variables_, "$deprecation$$type$ get$capitalized_name$();\n");
printer->Print(variables_,
"$deprecation$$type$ ${$get$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
void ImmutableEnumFieldLiteGenerator::GenerateMembers(
@ -550,24 +557,31 @@ int RepeatedImmutableEnumFieldLiteGenerator::GetNumBitsForMessage() const {
void RepeatedImmutableEnumFieldLiteGenerator::GenerateInterfaceMembers(
io::Printer* printer) const {
WriteFieldAccessorDocComment(printer, descriptor_, LIST_GETTER);
printer->Print(
variables_,
"$deprecation$java.util.List<$type$> get$capitalized_name$List();\n");
printer->Print(variables_,
"$deprecation$java.util.List<$type$> "
"${$get$capitalized_name$List$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_COUNT);
printer->Print(variables_,
"$deprecation$int get$capitalized_name$Count();\n");
"$deprecation$int ${$get$capitalized_name$Count$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_GETTER);
printer->Print(variables_,
"$deprecation$$type$ get$capitalized_name$(int index);\n");
printer->Print(
variables_,
"$deprecation$$type$ ${$get$capitalized_name$$}$(int index);\n");
printer->Annotate("{", "}", descriptor_);
if (SupportUnknownEnumValue(descriptor_->file())) {
WriteFieldEnumValueAccessorDocComment(printer, descriptor_, LIST_GETTER);
printer->Print(variables_,
"$deprecation$java.util.List<java.lang.Integer>\n"
"get$capitalized_name$ValueList();\n");
"${$get$capitalized_name$ValueList$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldEnumValueAccessorDocComment(printer, descriptor_,
LIST_INDEXED_GETTER);
printer->Print(variables_,
"$deprecation$int get$capitalized_name$Value(int index);\n");
printer->Print(
variables_,
"$deprecation$int ${$get$capitalized_name$Value$}$(int index);\n");
printer->Annotate("{", "}", descriptor_);
}
}

@ -39,7 +39,7 @@
#include "google/protobuf/io/printer.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "google/protobuf/compiler/java/file.h"
#include "google/protobuf/compiler/java/generator_factory.h"
#include "google/protobuf/compiler/java/helpers.h"

@ -45,7 +45,7 @@
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
@ -1114,7 +1114,7 @@ void EscapeUtf16ToString(uint16_t code, std::string* output) {
} else if (code >= 0x20 && code <= 0x7f) {
output->push_back(static_cast<char>(code));
} else {
output->append(StringPrintf("\\u%04x", code));
output->append(absl::StrFormat("\\u%04x", code));
}
}

@ -178,6 +178,8 @@ void SetMessageVariables(const FieldDescriptor* descriptor, int messageBitIndex,
(*variables)["default_entry"] =
(*variables)["capitalized_name"] + "DefaultEntryHolder.defaultEntry";
// { and } variables are used as delimiters when emitting annotations.
(*variables)["{"] = (*variables)["}"] = "";
}
} // namespace

@ -37,6 +37,7 @@
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "google/protobuf/io/coded_stream.h"
@ -82,21 +83,27 @@ MessageBuilderLiteGenerator::~MessageBuilderLiteGenerator() {}
void MessageBuilderLiteGenerator::Generate(io::Printer* printer) {
WriteMessageDocComment(printer, descriptor_);
std::map<std::string, std::string> vars = {
{"{", ""},
{"}", ""},
{"classname", name_resolver_->GetImmutableClassName(descriptor_)},
{"extra_interfaces", ExtraBuilderInterfaces(descriptor_)},
{"extendible",
descriptor_->extension_range_count() > 0 ? "Extendable" : ""},
};
printer->Print(
"public static final class Builder extends\n"
vars,
"public static final class ${$Builder$}$ extends\n"
" com.google.protobuf.GeneratedMessageLite.$extendible$Builder<\n"
" $classname$, Builder> implements\n"
" $extra_interfaces$\n"
" $classname$OrBuilder {\n",
"classname", name_resolver_->GetImmutableClassName(descriptor_),
"extra_interfaces", ExtraBuilderInterfaces(descriptor_), "extendible",
descriptor_->extension_range_count() > 0 ? "Extendable" : "");
" $classname$OrBuilder {\n");
printer->Annotate("{", "}", descriptor_);
printer->Indent();
GenerateCommonBuilderMethods(printer);
// oneof
std::map<std::string, std::string> vars;
for (auto oneof : oneofs_) {
vars["oneof_name"] = context_->GetOneofGeneratorInfo(oneof)->name;
vars["oneof_capitalized_name"] =
@ -107,16 +114,19 @@ void MessageBuilderLiteGenerator::Generate(io::Printer* printer) {
printer->Print(vars,
"@java.lang.Override\n"
"public $oneof_capitalized_name$Case\n"
" get$oneof_capitalized_name$Case() {\n"
" ${$get$oneof_capitalized_name$Case$}$() {\n"
" return instance.get$oneof_capitalized_name$Case();\n"
"}\n"
"}\n");
printer->Annotate("{", "}", oneof);
printer->Print(vars,
"\n"
"public Builder clear$oneof_capitalized_name$() {\n"
"public Builder ${$clear$oneof_capitalized_name$$}$() {\n"
" copyOnWrite();\n"
" instance.clear$oneof_capitalized_name$();\n"
" return this;\n"
"}\n"
"\n");
printer->Annotate("{", "}", oneof);
}
for (int i = 0; i < descriptor_->field_count(); i++) {

@ -110,6 +110,9 @@ void SetMessageVariables(const FieldDescriptor* descriptor, int messageBitIndex,
// We use `x.getClass()` as a null check because it generates less bytecode
// than an `if (x == null) { throw ... }` statement.
(*variables)["null_check"] = "value.getClass();\n";
// Annotations often use { and } to determine ranges.
(*variables)["{"] = "";
(*variables)["}"] = "";
}
} // namespace
@ -140,9 +143,13 @@ int ImmutableMessageFieldLiteGenerator::GetNumBitsForMessage() const {
void ImmutableMessageFieldLiteGenerator::GenerateInterfaceMembers(
io::Printer* printer) const {
WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
printer->Print(variables_, "$deprecation$boolean has$capitalized_name$();\n");
printer->Print(variables_,
"$deprecation$boolean ${$has$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, GETTER);
printer->Print(variables_, "$deprecation$$type$ get$capitalized_name$();\n");
printer->Print(variables_,
"$deprecation$$type$ ${$get$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
void ImmutableMessageFieldLiteGenerator::GenerateMembers(
@ -523,13 +530,17 @@ void RepeatedImmutableMessageFieldLiteGenerator::GenerateInterfaceMembers(
WriteFieldDocComment(printer, descriptor_);
printer->Print(variables_,
"$deprecation$java.util.List<$type$> \n"
" get$capitalized_name$List();\n");
" ${$get$capitalized_name$List$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldDocComment(printer, descriptor_);
printer->Print(variables_,
"$deprecation$$type$ get$capitalized_name$(int index);\n");
printer->Print(
variables_,
"$deprecation$$type$ ${$get$capitalized_name$$}$(int index);\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldDocComment(printer, descriptor_);
printer->Print(variables_,
"$deprecation$int get$capitalized_name$Count();\n");
"$deprecation$int ${$get$capitalized_name$Count$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
void RepeatedImmutableMessageFieldLiteGenerator::GenerateMembers(

@ -38,6 +38,7 @@
#include <cstdint>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "google/protobuf/io/coded_stream.h"
@ -117,29 +118,32 @@ void ImmutableMessageLiteGenerator::GenerateInterface(io::Printer* printer) {
MaybePrintGeneratedAnnotation(context_, printer, descriptor_,
/* immutable = */ true, "OrBuilder");
std::map<std::string, std::string> variables = {
{"{", ""},
{"}", ""},
{"deprecation",
descriptor_->options().deprecated() ? "@java.lang.Deprecated " : ""},
{"extra_interfaces", ExtraMessageOrBuilderInterfaces(descriptor_)},
{"classname", descriptor_->name()},
};
if (!context_->options().opensource_runtime) {
printer->Print("@com.google.protobuf.Internal.ProtoNonnullApi\n");
}
if (descriptor_->extension_range_count() > 0) {
printer->Print(
variables,
"$deprecation$public interface ${$$classname$OrBuilder$}$ extends \n"
" $extra_interfaces$\n"
" com.google.protobuf.GeneratedMessageLite.\n"
" ExtendableMessageOrBuilder<\n"
" $classname$, $classname$.Builder> {\n",
"deprecation",
descriptor_->options().deprecated() ? "@java.lang.Deprecated " : "",
"extra_interfaces", ExtraMessageOrBuilderInterfaces(descriptor_),
"classname", descriptor_->name(), "{", "", "}", "");
" $classname$, $classname$.Builder> {\n");
} else {
printer->Print(
variables,
"$deprecation$public interface ${$$classname$OrBuilder$}$ extends\n"
" $extra_interfaces$\n"
" com.google.protobuf.MessageLiteOrBuilder {\n",
"deprecation",
descriptor_->options().deprecated() ? "@java.lang.Deprecated " : "",
"extra_interfaces", ExtraMessageOrBuilderInterfaces(descriptor_),
"classname", descriptor_->name(), "{", "", "}", "");
" com.google.protobuf.MessageLiteOrBuilder {\n");
}
printer->Annotate("{", "}", descriptor_);
@ -150,13 +154,15 @@ void ImmutableMessageLiteGenerator::GenerateInterface(io::Printer* printer) {
.GenerateInterfaceMembers(printer);
}
for (auto oneof : oneofs_) {
printer->Print(
"\n"
"public $classname$.$oneof_capitalized_name$Case "
"get$oneof_capitalized_name$Case();\n",
"oneof_capitalized_name",
context_->GetOneofGeneratorInfo(oneof)->capitalized_name, "classname",
context_->GetNameResolver()->GetImmutableClassName(descriptor_));
variables["oneof_capitalized_name"] =
context_->GetOneofGeneratorInfo(oneof)->capitalized_name;
variables["classname"] =
context_->GetNameResolver()->GetImmutableClassName(descriptor_);
printer->Print(variables,
"\n"
"public ${$$classname$.$oneof_capitalized_name$Case$}$ "
"get$oneof_capitalized_name$Case();\n");
printer->Annotate("{", "}", oneof);
}
printer->Outdent();
@ -168,7 +174,7 @@ void ImmutableMessageLiteGenerator::GenerateInterface(io::Printer* printer) {
void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
bool is_own_file = IsOwnFile(descriptor_, /* immutable = */ true);
std::map<std::string, std::string> variables;
std::map<std::string, std::string> variables = {{"{", ""}, {"}", ""}};
variables["static"] = is_own_file ? " " : " static ";
variables["classname"] = descriptor_->name();
variables["extra_interfaces"] = ExtraMessageInterfaces(descriptor_);
@ -185,7 +191,7 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
if (descriptor_->extension_range_count() > 0) {
printer->Print(
variables,
"$deprecation$public $static$final class $classname$ extends\n"
"$deprecation$public $static$final class ${$$classname$$}$ extends\n"
" com.google.protobuf.GeneratedMessageLite.ExtendableMessage<\n"
" $classname$, $classname$.Builder> implements\n"
" $extra_interfaces$\n"
@ -196,7 +202,7 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
} else {
printer->Print(
variables,
"$deprecation$public $static$final class $classname$ extends\n"
"$deprecation$public $static$final class ${$$classname$$}$ extends\n"
" com.google.protobuf.GeneratedMessageLite<\n"
" $classname$, $classname$.Builder> implements\n"
" $extra_interfaces$\n"
@ -204,6 +210,7 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
builder_type = "com.google.protobuf.GeneratedMessageLite.Builder";
}
printer->Annotate("{", "}", descriptor_);
printer->Indent();
GenerateConstructor(printer);
@ -236,7 +243,7 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
}
// oneof
std::map<std::string, std::string> vars;
std::map<std::string, std::string> vars = {{"{", ""}, {"}", ""}};
for (auto oneof : oneofs_) {
vars["oneof_name"] = context_->GetOneofGeneratorInfo(oneof)->name;
vars["oneof_capitalized_name"] =
@ -249,13 +256,15 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
"private java.lang.Object $oneof_name$_;\n");
}
// OneofCase enum
printer->Print(vars, "public enum $oneof_capitalized_name$Case {\n");
printer->Print(vars, "public enum ${$$oneof_capitalized_name$Case$}$ {\n");
printer->Annotate("{", "}", oneof);
printer->Indent();
for (int j = 0; j < (oneof)->field_count(); j++) {
const FieldDescriptor* field = (oneof)->field(j);
printer->Print("$field_name$($field_number$),\n", "field_name",
absl::AsciiStrToUpper(field->name()), "field_number",
absl::StrCat(field->number()));
printer->Annotate("field_name", field);
}
printer->Print("$cap_oneof_name$_NOT_SET(0);\n", "cap_oneof_name",
absl::AsciiStrToUpper(vars["oneof_name"]));
@ -303,16 +312,19 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
printer->Print(vars,
"@java.lang.Override\n"
"public $oneof_capitalized_name$Case\n"
"get$oneof_capitalized_name$Case() {\n"
"${$get$oneof_capitalized_name$Case$}$() {\n"
" return $oneof_capitalized_name$Case.forNumber(\n"
" $oneof_name$Case_);\n"
"}\n"
"}\n");
printer->Annotate("{", "}", oneof);
printer->Print(vars,
"\n"
"private void clear$oneof_capitalized_name$() {\n"
"private void ${$clear$oneof_capitalized_name$$}$() {\n"
" $oneof_name$Case_ = 0;\n"
" $oneof_name$_ = null;\n"
"}\n"
"\n");
printer->Annotate("{", "}", oneof);
}
// Fields
@ -320,6 +332,7 @@ void ImmutableMessageLiteGenerator::Generate(io::Printer* printer) {
printer->Print("public static final int $constant_name$ = $number$;\n",
"constant_name", FieldConstantName(descriptor_->field(i)),
"number", absl::StrCat(descriptor_->field(i)->number()));
printer->Annotate("constant_name", descriptor_->field(i));
field_generators_.get(descriptor_->field(i)).GenerateMembers(printer);
printer->Print("\n");
}

@ -193,6 +193,8 @@ void SetPrimitiveVariables(const FieldDescriptor* descriptor,
GenerateGetBitFromLocal(builderBitIndex);
(*variables)["set_has_field_bit_to_local"] =
GenerateSetBitToLocal(messageBitIndex);
// Annotations often use { and } variables to denote ranges.
(*variables)["{"] = (*variables)["}"] = "";
}
} // namespace
@ -224,7 +226,9 @@ void ImmutablePrimitiveFieldLiteGenerator::GenerateInterfaceMembers(
"$deprecation$boolean has$capitalized_name$();\n");
}
WriteFieldAccessorDocComment(printer, descriptor_, GETTER);
printer->Print(variables_, "$deprecation$$type$ get$capitalized_name$();\n");
printer->Print(variables_,
"$deprecation$$type$ ${$get$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
void ImmutablePrimitiveFieldLiteGenerator::GenerateMembers(
@ -527,13 +531,17 @@ void RepeatedImmutablePrimitiveFieldLiteGenerator::GenerateInterfaceMembers(
WriteFieldAccessorDocComment(printer, descriptor_, LIST_GETTER);
printer->Print(variables_,
"$deprecation$java.util.List<$boxed_type$> "
"get$capitalized_name$List();\n");
"${$get$capitalized_name$List$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_COUNT);
printer->Print(variables_,
"$deprecation$int get$capitalized_name$Count();\n");
"$deprecation$int ${$get$capitalized_name$Count$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_GETTER);
printer->Print(variables_,
"$deprecation$$type$ get$capitalized_name$(int index);\n");
printer->Print(
variables_,
"$deprecation$$type$ ${$get$capitalized_name$$}$(int index);\n");
printer->Annotate("{", "}", descriptor_);
}
void RepeatedImmutablePrimitiveFieldLiteGenerator::GenerateMembers(

@ -129,6 +129,8 @@ void SetPrimitiveVariables(const FieldDescriptor* descriptor,
GenerateGetBitFromLocal(builderBitIndex);
(*variables)["set_has_field_bit_to_local"] =
GenerateSetBitToLocal(messageBitIndex);
// Annotations often use { and } variables to denote text ranges.
(*variables)["{"] = (*variables)["}"] = "";
}
} // namespace
@ -182,15 +184,19 @@ void ImmutableStringFieldLiteGenerator::GenerateInterfaceMembers(
if (HasHazzer(descriptor_)) {
WriteFieldAccessorDocComment(printer, descriptor_, HAZZER);
printer->Print(variables_,
"$deprecation$boolean has$capitalized_name$();\n");
"$deprecation$boolean ${$has$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
WriteFieldAccessorDocComment(printer, descriptor_, GETTER);
printer->Print(variables_,
"$deprecation$java.lang.String get$capitalized_name$();\n");
printer->Print(
variables_,
"$deprecation$java.lang.String ${$get$capitalized_name$$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldStringBytesAccessorDocComment(printer, descriptor_, GETTER);
printer->Print(variables_,
"$deprecation$com.google.protobuf.ByteString\n"
" get$capitalized_name$Bytes();\n");
" ${$get$capitalized_name$Bytes$}$();\n");
printer->Annotate("{", "}", descriptor_);
}
void ImmutableStringFieldLiteGenerator::GenerateMembers(
@ -569,18 +575,22 @@ void RepeatedImmutableStringFieldLiteGenerator::GenerateInterfaceMembers(
WriteFieldAccessorDocComment(printer, descriptor_, LIST_GETTER);
printer->Print(variables_,
"$deprecation$java.util.List<java.lang.String>\n"
" get$capitalized_name$List();\n");
" ${$get$capitalized_name$List$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_COUNT);
printer->Print(variables_,
"$deprecation$int get$capitalized_name$Count();\n");
"$deprecation$int ${$get$capitalized_name$Count$}$();\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_GETTER);
printer->Print(
variables_,
"$deprecation$java.lang.String get$capitalized_name$(int index);\n");
printer->Print(variables_,
"$deprecation$java.lang.String "
"${$get$capitalized_name$$}$(int index);\n");
printer->Annotate("{", "}", descriptor_);
WriteFieldAccessorDocComment(printer, descriptor_, LIST_INDEXED_GETTER);
printer->Print(variables_,
"$deprecation$com.google.protobuf.ByteString\n"
" get$capitalized_name$Bytes(int index);\n");
" ${$get$capitalized_name$Bytes$}$(int index);\n");
printer->Annotate("{", "}", descriptor_);
}
void RepeatedImmutableStringFieldLiteGenerator::GenerateMembers(

@ -58,7 +58,7 @@
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_replace.h"
#include "absl/strings/strip.h"
#include "absl/strings/substitute.h"
@ -1345,7 +1345,7 @@ void Generator::FixOptionsForEnum(const EnumDescriptor& enum_descriptor) const {
OptionsValue(value_descriptor.options().SerializeAsString());
if (value_options != "None") {
PrintDescriptorOptionsFixingCode(
StringPrintf("%s.values_by_name[\"%s\"]", descriptor_name.c_str(),
absl::StrFormat("%s.values_by_name[\"%s\"]", descriptor_name.c_str(),
value_descriptor.name().c_str()),
value_options, printer_);
}

@ -59,7 +59,6 @@
#include "absl/strings/escaping.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/substitute.h"
@ -1634,7 +1633,7 @@ FileDescriptorTables::FindEnumValueByNumberCreatingIfUnknown(
// EnumDescriptor (it's not a part of the enum as originally defined), but
// we do insert it into the table so that we can return the same pointer
// later.
std::string enum_value_name = StringPrintf(
std::string enum_value_name = absl::StrFormat(
"UNKNOWN_ENUM_VALUE_%s_%d", parent->name().c_str(), number);
auto* pool = DescriptorPool::generated_pool();
auto* tables = const_cast<DescriptorPool::Tables*>(pool->tables_.get());

@ -44,9 +44,9 @@
#include "google/protobuf/unittest.pb.h"
#include "google/protobuf/unittest_custom_options.pb.h"
#include "google/protobuf/descriptor.pb.h"
#include "absl/strings/str_format.h"
#include "google/protobuf/stubs/common.h"
#include "google/protobuf/stubs/logging.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "google/protobuf/unittest_lazy_dependencies.pb.h"
#include "google/protobuf/unittest_proto3_arena.pb.h"
#include "google/protobuf/io/tokenizer.h"
@ -591,7 +591,7 @@ class SimpleErrorCollector : public io::ErrorCollector {
public:
// implements ErrorCollector ---------------------------------------
void AddError(int line, int column, const std::string& message) override {
last_error_ = StringPrintf("%d:%d:", line, column) + message;
last_error_ = absl::StrFormat("%d:%d:%s", line, column, message);
}
const std::string& last_error() { return last_error_; }

@ -144,6 +144,7 @@ cc_test(
"//src/google/protobuf/testing",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_googletest//:gtest",
"@com_google_googletest//:gtest_main",
],

@ -46,11 +46,12 @@
#include "google/protobuf/stubs/logging.h"
#include "google/protobuf/stubs/common.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/types/optional.h"
@ -61,9 +62,8 @@ namespace google {
namespace protobuf {
namespace io {
namespace {
// Returns the number of spaces stripped.
size_t ConsumeInitialRawStringIndent(absl::string_view& format) {
size_t len = 0;
// Returns the number of spaces of the first non empty line.
size_t RawStringIndentLen(absl::string_view format) {
// We are processing a call that looks like
//
// p->Emit(R"cc(
@ -72,10 +72,19 @@ size_t ConsumeInitialRawStringIndent(absl::string_view& format) {
// };
// )cc");
//
// We need to discard all leading newlines, then consume all spaces until
// we reach a non-space; this run of spaces is stripped off at the start of
// each line.
// or
//
// p->Emit(R"cc(
//
// class Foo {
// int x, y, z;
// };
// )cc");
//
// To compute the indent, we need to discard all leading newlines, then
// count all spaces until we reach a non-space; this run of spaces is
// stripped off at the start of each line.
size_t len = 0;
while (absl::ConsumePrefix(&format, "\n")) {
}
@ -169,41 +178,17 @@ void Printer::Outdent() {
}
void Printer::Emit(
std::initializer_list<std::pair<
absl::string_view, absl::variant<std::string, std::function<void()>>>>
std::initializer_list<
VarDefinition<absl::string_view, /*allow_callbacks=*/true>>
vars,
absl::string_view format, SourceLocation loc) {
PrintOptions opts;
opts.strip_raw_string_indentation = true;
opts.loc = loc;
absl::flat_hash_map<absl::string_view,
absl::variant<std::string, std::function<void()>>>
map;
map.reserve(vars.size());
for (auto& var : vars) {
auto result = map.insert(var);
GOOGLE_CHECK(result.second) << "repeated variable in Emit() call: \"" << var.first
<< "\"";
}
var_lookups_.emplace_back([&map](absl::string_view var) -> LookupResult {
auto it = map.find(var);
if (it == map.end()) {
return absl::nullopt;
}
if (auto* str = absl::get_if<std::string>(&it->second)) {
return *str;
}
auto* f = absl::get_if<std::function<void()>>(&it->second);
GOOGLE_CHECK(f != nullptr);
return *f;
});
auto defs = WithDefs(vars);
PrintImpl(format, {}, opts);
var_lookups_.pop_back();
}
absl::optional<std::pair<size_t, size_t>> Printer::GetSubstitutionRange(
@ -305,7 +290,7 @@ void Printer::PrintCodegenTrace(absl::optional<SourceLocation> loc) {
sink_.Write("\n");
}
PrintRaw(StringPrintf("%s @%s:%d\n", options_.comment_start,
PrintRaw(absl::StrFormat("%s @%s:%d\n", options_.comment_start,
loc->file_name(), loc->line()));
at_start_of_line_ = true;
}
@ -314,7 +299,7 @@ bool Printer::ValidateIndexLookupInBounds(size_t index,
size_t current_arg_index,
size_t args_len, PrintOptions opts) {
if (!Validate(index < args_len, opts, [this, index] {
return StringPrintf("annotation %c{%d%c is out of bounds",
return absl::StrFormat("annotation %c{%d%c is out of bounds",
options_.variable_delimiter, index + 1,
options_.variable_delimiter);
})) {
@ -322,7 +307,7 @@ bool Printer::ValidateIndexLookupInBounds(size_t index,
}
if (!Validate(
index <= current_arg_index, opts, [this, index, current_arg_index] {
return StringPrintf(
return absl::StrFormat(
"annotation arg must be in correct order as given; expected "
"%c{%d%c but got %c{%d%c",
options_.variable_delimiter, current_arg_index + 1,
@ -352,9 +337,16 @@ void Printer::PrintImpl(absl::string_view format,
substitutions_.clear();
}
size_t raw_string_indent_len = opts.strip_raw_string_indentation
? ConsumeInitialRawStringIndent(format)
: 0;
size_t raw_string_indent_len =
opts.strip_raw_string_indentation ? RawStringIndentLen(format) : 0;
if (opts.strip_raw_string_indentation) {
// We only want to remove a single newline from the input string to allow
// extra newlines at the start to go into the generated code.
absl::ConsumePrefix(&format, "\n");
while (absl::ConsumePrefix(&format, " ")) {
}
}
PrintCodegenTrace(opts.loc);
@ -534,7 +526,7 @@ void Printer::PrintImpl(absl::string_view format,
annot_records.pop_back();
if (!Validate(record_var.first == var, opts, [record_var, var] {
return StringPrintf(
return absl::StrFormat(
"_start and _end variables must match, but got %s and %s, "
"respectively",
record_var.first, var);
@ -659,7 +651,7 @@ void Printer::PrintImpl(absl::string_view format,
Validate(arg_index == args.size(), opts,
[original] { return absl::StrCat("unused args: ", original); });
Validate(annot_stack.empty(), opts, [this, original] {
return StringPrintf(
return absl::StrFormat(
"annotation range was not closed; expected %c}%c: %s",
options_.variable_delimiter, options_.variable_delimiter, original);
});

@ -46,15 +46,16 @@
#include <utility>
#include <vector>
#include "google/protobuf/stubs/logging.h"
#include "google/protobuf/stubs/common.h"
#include "absl/cleanup/cleanup.h"
#include "absl/container/flat_hash_map.h"
#include "absl/functional/function_ref.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "google/protobuf/io/zero_copy_sink.h"
#include "google/protobuf/port.h"
// Must be included last.
@ -163,6 +164,10 @@ class AnnotationProtoCollector : public AnnotationCollector {
// "Thing", rather than " Thing". This helps minimize awkward whitespace in the
// output.
//
// The value may be any type that can be stringified with `absl::StrCat`:
//
// p.Emit({{"num", 5}}, "x = $num$;");
//
// If a variable is referenced in the format string that is missing, the program
// will crash. Callers must statically know that every variable reference is
// valid, and MUST NOT pass user-provided strings directly into Emit().
@ -255,6 +260,19 @@ class AnnotationProtoCollector : public AnnotationCollector {
// The span corresponding to whatever $class_name$ expands to will be annotated
// as having come from message_descriptor.
//
// For convenience, this can be done with a single WithVars(), using the special
// three-argument form:
//
// auto v = p.WithVars({{"class_name", my_class_name, message_descriptor}});
// p.Emit(R"cc(
// class $class_name$ {
// public:
// $class_name$(int x);
// // Etc.
// };
// )cc");
//
//
// Alternatively, a range may be given explicitly:
//
// auto a = p.WithAnnotations({{"my_desc", message_descriptor}});
@ -267,11 +285,11 @@ class AnnotationProtoCollector : public AnnotationCollector {
// )cc");
//
// The special $_start$ and $_end$ variables indicate the start and end of an
// annotated span, which is annotated with the variable that follows.
// annotated span, which is annotated with the variable that follows. This
// form can produce somewhat unreadable format strings and is not recommended.
//
// Note that whitespace after a $_start$ and before an $_end$ is not printed.
//
//
// # Indentation
//
// Printer tracks an indentation amount to add to each new line, independent
@ -407,6 +425,60 @@ class PROTOBUF_EXPORT Printer {
}
};
// Sink type for constructing values to pass to WithVars() and Emit().
template <typename K, bool allow_callbacks>
struct VarDefinition {
using StringOrCallback = absl::variant<std::string, std::function<void()>>;
template <typename Key, typename Value>
VarDefinition(Key&& key, Value&& value)
: key(std::forward<Key>(key)),
value(ToStringOrCallback(std::forward<Value>(value), Rank2{})),
annotation(absl::nullopt) {}
// NOTE: This is an overload rather than taking optional<AnnotationRecord>
// with a default argument of nullopt, because we want to pick up
// AnnotationRecord's user-defined conversions. Because going from
// e.g. Descriptor* -> optional<AnnotationRecord> requires two user-defined
// conversions, this does not work.
template <typename Key, typename Value>
VarDefinition(Key&& key, Value&& value, AnnotationRecord annotation)
: key(std::forward<Key>(key)),
value(ToStringOrCallback(std::forward<Value>(value), Rank2{})),
annotation(std::move(annotation)) {}
K key;
StringOrCallback value;
absl::optional<AnnotationRecord> annotation;
private:
// go/ranked-overloads
struct Rank0 {};
struct Rank1 : Rank0 {};
struct Rank2 : Rank1 {};
// Dummy template for delayed instantiation, which is required for the
// static assert below to kick in only when this function is called when it
// shouldn't.
//
// This is done to produce a better error message than the "candidate does
// not match" SFINAE errors.
template <bool allowed = allow_callbacks>
StringOrCallback ToStringOrCallback(std::function<void()> cb, Rank2) {
static_assert(
allowed, "callback-typed variables are not allowed in this location");
return cb;
}
// Separate from the AlphaNum overload to avoid copies when taking strings
// by value.
StringOrCallback ToStringOrCallback(std::string s, Rank1) { return s; }
StringOrCallback ToStringOrCallback(const absl::AlphaNum& s, Rank0) {
return std::string(s.Piece());
}
};
public:
static constexpr char kDefaultVariableDelimiter = '$';
static constexpr absl::string_view kProtocCodegenTrace =
@ -418,8 +490,8 @@ class PROTOBUF_EXPORT Printer {
Options(const Options&) = default;
Options(Options&&) = default;
Options(char variable_delimiter, AnnotationCollector* annotation_collector)
: variable_delimiter(variable_delimiter),
annotation_collector(annotation_collector) {}
: variable_delimiter(variable_delimiter),
annotation_collector(annotation_collector) {}
// The delimiter for variable substitutions, e.g. $foo$.
char variable_delimiter = kDefaultVariableDelimiter;
@ -495,6 +567,10 @@ class PROTOBUF_EXPORT Printer {
return absl::MakeCleanup([this] { var_lookups_.pop_back(); });
}
auto WithVars(std::initializer_list<
VarDefinition<std::string, /*allow_callbacks=*/false>>
vars);
// Looks up a variable set with WithVars().
//
// Will crash if:
@ -539,7 +615,7 @@ class PROTOBUF_EXPORT Printer {
}
// Increases the indentation by `indent` spaces; when nullopt, increments
// indentation by the configured default spaces_per_indentation.
// indentation by the configured default spaces_per_indent.
//
// Returns an RAII object that removes this indentation.
auto WithIndent(absl::optional<size_t> indent = absl::nullopt) {
@ -562,15 +638,11 @@ class PROTOBUF_EXPORT Printer {
// documentation for more details.
//
// `format` MUST be a string constant.
void Emit(
std::initializer_list<
std::pair<absl::string_view,
// NOTE: This is a std::string, not a string_view, to work
// around an MSVC bug(?) that causes temporary strings
// passed into this function to be destroyed early.
absl::variant<std::string, std::function<void()>>>>
vars,
absl::string_view format, SourceLocation loc = SourceLocation::current());
void Emit(std::initializer_list<
VarDefinition<absl::string_view, /*allow_callbacks=*/true>>
vars,
absl::string_view format,
SourceLocation loc = SourceLocation::current());
// Write a string directly to the underlying output, performing no formatting
// of any sort.
@ -745,6 +817,11 @@ class PROTOBUF_EXPORT Printer {
// Prints a codegen trace, for the given location in the compiler's source.
void PrintCodegenTrace(absl::optional<SourceLocation> loc);
// The core implementation for "fully-elaborated" variable definitions. This
// is a private function to avoid users being able to set `allow_callbacks`.
template <typename K, bool allow_callbacks>
auto WithDefs(std::initializer_list<VarDefinition<K, allow_callbacks>> vars);
// Returns the start and end of the value that was substituted in place of
// the variable `varname` in the last call to PrintImpl() (with
// `use_substitution_map` set), if such a variable was substituted exactly
@ -776,6 +853,66 @@ class PROTOBUF_EXPORT Printer {
// current line.
std::vector<std::string> line_start_variables_;
};
template <typename K, bool allow_callbacks>
auto Printer::WithDefs(
std::initializer_list<VarDefinition<K, allow_callbacks>> vars) {
absl::flat_hash_map<K, absl::variant<std::string, std::function<void()>>>
var_map;
var_map.reserve(vars.size());
absl::flat_hash_map<K, AnnotationRecord> annotation_map;
for (auto& var : vars) {
auto result = var_map.insert({var.key, var.value});
GOOGLE_CHECK(result.second) << "repeated variable in Emit() or WithVars() call: \""
<< var.key << "\"";
if (var.annotation.has_value()) {
annotation_map.insert({var.key, *var.annotation});
}
}
var_lookups_.emplace_back(
[map = std::move(var_map)](absl::string_view var) -> LookupResult {
auto it = map.find(var);
if (it == map.end()) {
return absl::nullopt;
}
if (auto* str = absl::get_if<std::string>(&it->second)) {
return *str;
}
auto* f = absl::get_if<std::function<void()>>(&it->second);
GOOGLE_CHECK(f != nullptr);
return *f;
});
bool has_annotations = !annotation_map.empty();
if (has_annotations) {
annotation_lookups_.emplace_back(
[map = std::move(annotation_map)](
absl::string_view var) -> absl::optional<AnnotationRecord> {
auto it = map.find(var);
if (it == map.end()) {
return absl::nullopt;
}
return it->second;
});
}
return absl::MakeCleanup([this, has_annotations] {
var_lookups_.pop_back();
if (has_annotations) {
annotation_lookups_.pop_back();
}
});
}
inline auto Printer::WithVars(
std::initializer_list<VarDefinition<std::string, /*allow_callbacks=*/false>>
vars) {
return WithDefs(vars);
}
} // namespace io
} // namespace protobuf
} // namespace google

@ -548,20 +548,49 @@ TEST_F(PrinterTest, Emit) {
"}\n");
}
TEST_F(PrinterTest, EmitWithSubs) {
TEST_F(PrinterTest, EmitKeepsExtraLine) {
{
Printer printer(output());
printer.Emit({{"class", "Foo"}, {"f1", "x"}, {"f2", "y"}, {"f3", "z"}},
R"cc(
class $class$ {
int $f1$, $f2$, $f3$;
};
)cc");
printer.Emit(R"cc(
class Foo {
int x, y, z;
};
)cc");
printer.Emit(R"java(
public final class Bar {
Bar() {}
}
)java");
}
EXPECT_EQ(written(),
"\n"
"class Foo {\n"
" int x, y, z;\n"
"};\n"
"\n"
"public final class Bar {\n"
" Bar() {}\n"
"}\n");
}
TEST_F(PrinterTest, EmitWithSubs) {
{
Printer printer(output());
printer.Emit(
{{"class", "Foo"}, {"f1", "x"}, {"f2", "y"}, {"f3", "z"}, {"init", 42}},
R"cc(
class $class$ {
int $f1$, $f2$, $f3$ = $init$;
};
)cc");
}
EXPECT_EQ(written(),
"class Foo {\n"
" int x, y, z = 42;\n"
"};\n");
}
@ -573,17 +602,18 @@ TEST_F(PrinterTest, EmitWithVars) {
{"f1", "x"},
{"f2", "y"},
{"f3", "z"},
{"init", 42},
});
printer.Emit(R"cc(
class $class$ {
int $f1$, $f2$, $f3$;
int $f1$, $f2$, $f3$ = $init$;
};
)cc");
}
EXPECT_EQ(written(),
"class Foo {\n"
" int x, y, z;\n"
" int x, y, z = 42;\n"
"};\n");
}
@ -681,6 +711,28 @@ TEST_F(PrinterTest, EmitSameNameAnnotationFileNameOnly) {
ElementsAre(Annotation(6, 9, "file.proto", IsEmpty())));
}
TEST_F(PrinterTest, EmitThreeArgWithVars) {
FakeAnnotationCollector collector;
{
Printer printer(output(), '$', &collector);
auto v = printer.WithVars({{"class", "Foo", "file.proto"}});
printer.Emit({{"f1", "x"}, {"f2", "y"}, {"f3", "z"}}, R"cc(
class $class$ {
int $f1$, $f2$, $f3$;
};
)cc");
}
EXPECT_EQ(written(),
"class Foo {\n"
" int x, y, z;\n"
"};\n");
EXPECT_THAT(collector.Get(),
ElementsAre(Annotation(6, 9, "file.proto", IsEmpty())));
}
TEST_F(PrinterTest, EmitRangeAnnotation) {
FakeAnnotationCollector collector;
{

@ -93,7 +93,7 @@
#include "google/protobuf/stubs/common.h"
#include "google/protobuf/stubs/logging.h"
#include "absl/strings/escaping.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "google/protobuf/io/strtod.h"
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/stubs/stl_util.h"
@ -708,7 +708,7 @@ bool Tokenizer::Next() {
if (current_char_ & 0x80) {
error_collector_->AddError(
line_, column_,
StringPrintf("Interpreting non ascii codepoint %d.",
absl::StrFormat("Interpreting non ascii codepoint %d.",
static_cast<unsigned char>(current_char_)));
}
NextChar();
@ -1058,7 +1058,7 @@ static void AppendUTF8(uint32_t code_point, std::string* output) {
// Unicode code points end at 0x10FFFF, so this is out-of-range.
// ConsumeString permits hex values up to 0x1FFFFF, and FetchUnicodePoint
// doesn't perform a range check.
StringAppendF(output, "\\U%08x", code_point);
absl::StrAppendFormat(output, "\\U%08x", code_point);
return;
}
tmp = ghtonl(tmp);

@ -42,6 +42,7 @@
#include "google/protobuf/message.h"
#include "google/protobuf/repeated_field.h"
#include <gtest/gtest.h>
#include "absl/strings/str_format.h"
#include "google/protobuf/arena_test_util.h"
#include "google/protobuf/map_test_util.h"

@ -118,6 +118,15 @@
#define PROTOBUF_has_builtin_DEFINED_
#endif
#ifdef ADDRESS_SANITIZER
#include <sanitizer/asan_interface.h>
#define PROTOBUF_POISON_MEMORY_REGION(p, n) ASAN_POISON_MEMORY_REGION(p, n)
#define PROTOBUF_UNPOISON_MEMORY_REGION(p, n) ASAN_UNPOISON_MEMORY_REGION(p, n)
#else // ADDRESS_SANITIZER
#define PROTOBUF_POISON_MEMORY_REGION(p, n)
#define PROTOBUF_UNPOISON_MEMORY_REGION(p, n)
#endif // ADDRESS_SANITIZER
// Portable PROTOBUF_BUILTIN_BSWAPxx definitions
// Code must check for availability, e.g.: `defined(PROTOBUF_BUILTIN_BSWAP32)`
#ifdef PROTOBUF_BUILTIN_BSWAP16

@ -35,6 +35,8 @@
#error "port_undef.inc must be included after port_def.inc"
#endif
#undef PROTOBUF_POISON_MEMORY_REGION
#undef PROTOBUF_UNPOISON_MEMORY_REGION
#undef PROTOBUF_BUILTIN_BSWAP16
#undef PROTOBUF_BUILTIN_BSWAP32
#undef PROTOBUF_BUILTIN_BSWAP64

@ -14,7 +14,6 @@ cc_library(
srcs = [
"bytestream.cc",
"common.cc",
"stringprintf.cc",
"structurally_valid.cc",
"strutil.cc",
],
@ -28,7 +27,6 @@ cc_library(
"port.h",
"status_macros.h",
"stl_util.h",
"stringprintf.h",
"strutil.h",
],
copts = COPTS,
@ -59,7 +57,6 @@ cc_library(
"port.h",
"status_macros.h",
"stl_util.h",
"stringprintf.h",
"strutil.h",
],
deps = [
@ -76,7 +73,6 @@ cc_test(
srcs = [
"bytestream_unittest.cc",
"common_unittest.cc",
"stringprintf_unittest.cc",
"structurally_valid_unittest.cc",
"strutil_unittest.cc",
],

@ -1,176 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2012 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// from google3/base/stringprintf.cc
#include "google/protobuf/stubs/stringprintf.h"
#include <errno.h>
#include <stdarg.h> // For va_list and related operations
#include <stdio.h> // MSVC requires this for _vsnprintf
#include <vector>
#include "google/protobuf/stubs/common.h"
#include "google/protobuf/stubs/logging.h"
namespace google {
namespace protobuf {
#ifdef _MSC_VER
#ifndef va_copy
// Define va_copy for MSVC. This is a hack, assuming va_list is simply a
// pointer into the stack and is safe to copy.
#define va_copy(dest, src) ((dest) = (src))
#endif
#endif
void StringAppendV(std::string* dst, const char* format, va_list ap) {
// First try with a small fixed size buffer
static const int kSpaceLength = 1024;
char space[kSpaceLength];
// It's possible for methods that use a va_list to invalidate
// the data in it upon use. The fix is to make a copy
// of the structure before using it and use that copy instead.
va_list backup_ap;
va_copy(backup_ap, ap);
int result = vsnprintf(space, kSpaceLength, format, backup_ap);
va_end(backup_ap);
if (result < kSpaceLength) {
if (result >= 0) {
// Normal case -- everything fit.
dst->append(space, result);
return;
}
#ifdef _MSC_VER
{
// Error or MSVC running out of space. MSVC 8.0 and higher
// can be asked about space needed with the special idiom below:
va_copy(backup_ap, ap);
result = vsnprintf(nullptr, 0, format, backup_ap);
va_end(backup_ap);
}
#endif
if (result < 0) {
// Just an error.
return;
}
}
// Increase the buffer size to the size requested by vsnprintf,
// plus one for the closing \0.
int length = result+1;
char* buf = new char[length];
// Restore the va_list before we use it again
va_copy(backup_ap, ap);
result = vsnprintf(buf, length, format, backup_ap);
va_end(backup_ap);
if (result >= 0 && result < length) {
// It fit
dst->append(buf, result);
}
delete[] buf;
}
std::string StringPrintf(const char* format, ...) {
va_list ap;
va_start(ap, format);
std::string result;
StringAppendV(&result, format, ap);
va_end(ap);
return result;
}
const std::string& SStringPrintf(std::string* dst, const char* format, ...) {
va_list ap;
va_start(ap, format);
dst->clear();
StringAppendV(dst, format, ap);
va_end(ap);
return *dst;
}
void StringAppendF(std::string* dst, const char* format, ...) {
va_list ap;
va_start(ap, format);
StringAppendV(dst, format, ap);
va_end(ap);
}
// Max arguments supported by StringPrintVector
const int kStringPrintfVectorMaxArgs = 32;
// An empty block of zero for filler arguments. This is const so that if
// printf tries to write to it (via %n) then the program gets a SIGSEGV
// and we can fix the problem or protect against an attack.
static const char string_printf_empty_block[256] = { '\0' };
std::string StringPrintfVector(const char* format,
const std::vector<std::string>& v) {
GOOGLE_CHECK_LE(v.size(), kStringPrintfVectorMaxArgs)
<< "StringPrintfVector currently only supports up to "
<< kStringPrintfVectorMaxArgs << " arguments. "
<< "Feel free to add support for more if you need it.";
// Add filler arguments so that bogus format+args have a harder time
// crashing the program, corrupting the program (%n),
// or displaying random chunks of memory to users.
const char* cstr[kStringPrintfVectorMaxArgs];
for (int i = 0; i < v.size(); ++i) {
cstr[i] = v[i].c_str();
}
for (int i = v.size(); i < kStringPrintfVectorMaxArgs; ++i) {
cstr[i] = &string_printf_empty_block[0];
}
// I do not know any way to pass kStringPrintfVectorMaxArgs arguments,
// or any way to build a va_list by hand, or any API for printf
// that accepts an array of arguments. The best I can do is stick
// this COMPILE_ASSERT right next to the actual statement.
static_assert(kStringPrintfVectorMaxArgs == 32, "arg_count_mismatch");
return StringPrintf(format,
cstr[0], cstr[1], cstr[2], cstr[3], cstr[4],
cstr[5], cstr[6], cstr[7], cstr[8], cstr[9],
cstr[10], cstr[11], cstr[12], cstr[13], cstr[14],
cstr[15], cstr[16], cstr[17], cstr[18], cstr[19],
cstr[20], cstr[21], cstr[22], cstr[23], cstr[24],
cstr[25], cstr[26], cstr[27], cstr[28], cstr[29],
cstr[30], cstr[31]);
}
} // namespace protobuf
} // namespace google

@ -1,87 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2012 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// from google3/base/stringprintf.h
//
// Printf variants that place their output in a C++ string.
//
// Usage:
// string result = StringPrintf("%d %s\n", 10, "hello");
// SStringPrintf(&result, "%d %s\n", 10, "hello");
// StringAppendF(&result, "%d %s\n", 20, "there");
#ifndef GOOGLE_PROTOBUF_STUBS_STRINGPRINTF_H
#define GOOGLE_PROTOBUF_STUBS_STRINGPRINTF_H
#include <stdarg.h>
#include <string>
#include <vector>
#include "google/protobuf/stubs/common.h"
// Must be last.
#include "google/protobuf/port_def.inc" // NOLINT
namespace google {
namespace protobuf {
// Return a C++ string
PROTOBUF_EXPORT extern std::string StringPrintf(const char* format, ...);
// Store result into a supplied string and return it
PROTOBUF_EXPORT extern const std::string& SStringPrintf(std::string* dst,
const char* format,
...);
// Append result to a supplied string
PROTOBUF_EXPORT extern void StringAppendF(std::string* dst, const char* format,
...);
// Lower-level routine that takes a va_list and appends to a specified
// string. All other routines are just convenience wrappers around it.
PROTOBUF_EXPORT extern void StringAppendV(std::string* dst, const char* format,
va_list ap);
// The max arguments supported by StringPrintfVector
PROTOBUF_EXPORT extern const int kStringPrintfVectorMaxArgs;
// You can use this version when all your arguments are strings, but
// you don't know how many arguments you'll have at compile time.
// StringPrintfVector will LOG(FATAL) if v.size() > kStringPrintfVectorMaxArgs
PROTOBUF_EXPORT extern std::string StringPrintfVector(
const char* format, const std::vector<std::string>& v);
} // namespace protobuf
} // namespace google
#include "google/protobuf/port_undef.inc"
#endif // GOOGLE_PROTOBUF_STUBS_STRINGPRINTF_H

@ -1,157 +0,0 @@
// Protocol Buffers - Google's data interchange format
// Copyright 2012 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// from google3/base/stringprintf_unittest.cc
#include "google/protobuf/stubs/stringprintf.h"
#include <gtest/gtest.h>
#include <array>
#include <cerrno>
#include <string>
#include "google/protobuf/testing/googletest.h"
namespace google {
namespace protobuf {
namespace {
TEST(StringPrintfTest, Empty) {
#if 0
// gcc 2.95.3, gcc 4.1.0, and gcc 4.2.2 all warn about this:
// warning: zero-length printf format string.
// so we do not allow them in google3.
EXPECT_EQ("", StringPrintf(""));
#endif
EXPECT_EQ("", StringPrintf("%s", std::string().c_str()));
EXPECT_EQ("", StringPrintf("%s", ""));
}
TEST(StringPrintfTest, Misc) {
// MSVC and mingw does not support $ format specifier.
#if !defined(_MSC_VER) && !defined(__MINGW32__)
EXPECT_EQ("123hello w", StringPrintf("%3$d%2$s %1$c", 'w', "hello", 123));
#endif // !_MSC_VER
}
TEST(StringAppendFTest, Empty) {
std::string value("Hello");
const char* empty = "";
StringAppendF(&value, "%s", empty);
EXPECT_EQ("Hello", value);
}
TEST(StringAppendFTest, EmptyString) {
std::string value("Hello");
StringAppendF(&value, "%s", "");
EXPECT_EQ("Hello", value);
}
TEST(StringAppendFTest, String) {
std::string value("Hello");
StringAppendF(&value, " %s", "World");
EXPECT_EQ("Hello World", value);
}
TEST(StringAppendFTest, Int) {
std::string value("Hello");
StringAppendF(&value, " %d", 123);
EXPECT_EQ("Hello 123", value);
}
TEST(StringPrintfTest, Multibyte) {
// If we are in multibyte mode and feed invalid multibyte sequence,
// StringPrintf should return an empty string instead of running
// out of memory while trying to determine destination buffer size.
// see b/4194543.
char* old_locale_c = setlocale(LC_CTYPE, nullptr);
ASSERT_TRUE(old_locale_c != nullptr);
std::string old_locale = old_locale_c;
// Push locale with multibyte mode
setlocale(LC_CTYPE, "en_US.utf8");
const char kInvalidCodePoint[] = "\375\067s";
std::string value = StringPrintf("%.*s", 3, kInvalidCodePoint);
// In some versions of glibc (e.g. eglibc-2.11.1, aka GRTEv2), snprintf
// returns error given an invalid codepoint. Other versions
// (e.g. eglibc-2.15, aka pre-GRTEv3) emit the codepoint verbatim.
// We test that the output is one of the above.
EXPECT_TRUE(value.empty() || value == kInvalidCodePoint);
// Repeat with longer string, to make sure that the dynamically
// allocated path in StringAppendV is handled correctly.
const size_t n = 2048;
std::array<char, n + 1> buf;
memset(&buf[0], ' ', n - 3);
memcpy(&buf[0] + n - 3, kInvalidCodePoint, 4);
value = StringPrintf("%.*s", n, &buf[0]);
// See GRTEv2 vs. GRTEv3 comment above.
EXPECT_TRUE(value.empty() || value == &buf[0]);
setlocale(LC_CTYPE, old_locale.c_str());
}
TEST(StringPrintfTest, NoMultibyte) {
// No multibyte handling, but the string contains funny chars.
char* old_locale_c = setlocale(LC_CTYPE, nullptr);
ASSERT_TRUE(old_locale_c != nullptr);
std::string old_locale = old_locale_c;
setlocale(LC_CTYPE, "POSIX");
std::string value = StringPrintf("%.*s", 3, "\375\067s");
setlocale(LC_CTYPE, old_locale.c_str());
EXPECT_EQ("\375\067s", value);
}
TEST(StringPrintfTest, DontOverwriteErrno) {
// Check that errno isn't overwritten unless we're printing
// something significantly larger than what people are normally
// printing in their badly written PLOG() statements.
errno = ECHILD;
std::string value = StringPrintf("Hello, %s!", "World");
EXPECT_EQ(ECHILD, errno);
}
TEST(StringPrintfTest, LargeBuf) {
// Check that the large buffer is handled correctly.
int n = 2048;
char* buf = new char[n+1];
memset(buf, ' ', n);
buf[n] = 0;
std::string value = StringPrintf("%s", buf);
EXPECT_EQ(buf, value);
delete[] buf;
}
} // anonymous namespace
} // namespace protobuf
} // namespace google

@ -359,6 +359,9 @@ message TestMixedFieldsAndExtensions {
message TestGroup {
optional group OptionalGroup = 16 {
optional int32 a = 17;
optional int32 zz = 89; // fast table size must be at least 16, for this
// field to be parsed by the fast parser, since
// 89 - 17 = 72 is a multiple of 8.
}
optional ForeignEnum optional_foreign_enum = 22;
}

@ -41,8 +41,8 @@
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/stubs/bytestream.h"
#include "absl/status/status.h"
#include "google/protobuf/stubs/bytestream.h"
#include "google/protobuf/port.h"
#include "google/protobuf/util/internal/datapiece.h"
#include "google/protobuf/util/internal/error_listener.h"

@ -47,7 +47,7 @@
#include "absl/base/casts.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "google/protobuf/util/internal/constants.h"
@ -332,7 +332,7 @@ absl::Status ProtoStreamObjectSource::RenderTimestamp(
absl::Time tm = absl::FromUnixSeconds(seconds);
std::string formatted_seconds =
absl::FormatTime(kRfc3339TimeFormat, tm, absl::UTCTimeZone());
std::string formatted_time = StringPrintf(
std::string formatted_time = absl::StrFormat(
"%s%sZ", formatted_seconds.c_str(),
FormatNanos(
nanos,
@ -374,7 +374,7 @@ absl::Status ProtoStreamObjectSource::RenderDuration(
sign = "-";
nanos = -nanos;
}
std::string formatted_duration = StringPrintf(
std::string formatted_duration = absl::StrFormat(
"%s%lld%ss", sign.c_str(), static_cast<long long>(seconds), // NOLINT
FormatNanos(
nanos,
@ -1111,7 +1111,7 @@ std::string FormatNanos(uint32_t nanos, bool with_trailing_zeros) {
const int precision = (nanos % 1000 != 0) ? 9
: (nanos % 1000000 != 0) ? 6
: 3;
std::string formatted = StringPrintf(
std::string formatted = absl::StrFormat(
"%.*f", precision, static_cast<double>(nanos) / kNanosPerSecond);
// remove the leading 0 before decimal.
return formatted.substr(1);

@ -40,9 +40,9 @@
#include "google/protobuf/io/coded_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/stubs/bytestream.h"
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "google/protobuf/stubs/bytestream.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/port.h"
#include "google/protobuf/util/internal/datapiece.h"

@ -40,8 +40,8 @@
#include "google/protobuf/descriptor.pb.h"
#include "google/protobuf/message.h"
#include "google/protobuf/util/internal/mock_error_listener.h"
#include "google/protobuf/stubs/bytestream.h"
#include <gtest/gtest.h>
#include "google/protobuf/stubs/bytestream.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/dynamic_message.h"

@ -34,8 +34,8 @@
#define GOOGLE_PROTOBUF_UTIL_JSON_UTIL_H__
#include "google/protobuf/stubs/bytestream.h"
#include "absl/status/status.h"
#include "google/protobuf/stubs/bytestream.h"
#include "absl/strings/string_view.h"
#include "google/protobuf/message.h"
#include "google/protobuf/util/type_resolver.h"

@ -59,7 +59,7 @@
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "google/protobuf/util/field_comparator.h"
// Always include as last one, otherwise it can break compilation
@ -2091,7 +2091,7 @@ void MessageDifferencer::StreamReporter::PrintUnknownFieldValue(
"0x", absl::Hex(unknown_field->fixed64(), absl::kZeroPad16));
break;
case UnknownField::TYPE_LENGTH_DELIMITED:
output = StringPrintf(
output = absl::StrFormat(
"\"%s\"", absl::CEscape(unknown_field->length_delimited()).c_str());
break;
case UnknownField::TYPE_GROUP:

@ -37,7 +37,7 @@
#include "google/protobuf/timestamp.pb.h"
#include "absl/numeric/int128.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
@ -127,11 +127,11 @@ Duration CreateNormalized(int64_t seconds, int32_t nanos) {
// precision to represent the exact value.
std::string FormatNanos(int32_t nanos) {
if (nanos % kNanosPerMillisecond == 0) {
return StringPrintf("%03d", nanos / kNanosPerMillisecond);
return absl::StrFormat("%03d", nanos / kNanosPerMillisecond);
} else if (nanos % kNanosPerMicrosecond == 0) {
return StringPrintf("%06d", nanos / kNanosPerMicrosecond);
return absl::StrFormat("%06d", nanos / kNanosPerMicrosecond);
} else {
return StringPrintf("%09d", nanos);
return absl::StrFormat("%09d", nanos);
}
}

@ -35,15 +35,15 @@
#include <string>
// Must be included last.
#include "google/protobuf/port_def.inc"
namespace google {
namespace protobuf {
class DescriptorPool;
namespace util {
class TypeResolver;
// Must be included last.
#include "google/protobuf/port_def.inc"
// Creates a TypeResolver that serves type information in the given descriptor
// pool. Caller takes ownership of the returned TypeResolver.
PROTOBUF_EXPORT TypeResolver* NewTypeResolverForDescriptorPool(

@ -45,7 +45,7 @@
#include "google/protobuf/io/zero_copy_stream.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "absl/strings/str_cat.h"
#include "google/protobuf/stubs/stringprintf.h"
#include "absl/strings/str_format.h"
// Must be included last.

Loading…
Cancel
Save