diff --git a/.gitignore b/.gitignore
index d8b0bd3842..cc881f3f2c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,23 @@ m4/ltversion.m4
m4/lt~obsolete.m4
autom4te.cache
+# CMake-generated files
+.ninja_deps
+.ninja_logs
+cmake/protobuf/*.cmake
+cmake_install.cmake
+CMakeCache.txt
+CTestTestfile.cmake
+CMakeFiles/*
+Testing/Temporary/*
+
+/core
+/protoc
+/test_plugin
+/tests
+/lite-test
+/protoc-*.*
+
# downloaded files
/gmock
@@ -41,6 +58,7 @@ stamp-h1
*.la
src/.libs
*.so
+*.a
.dirstamp
diff --git a/CHANGES.txt b/CHANGES.txt
index c49a53a502..a8e7fd19a9 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
2022-07-01 Unreleased version
+
C++
* Reduced .pb.o object file size slightly by explicitly instantiating
InternalMetadata templates in the runtime.
@@ -14,6 +15,12 @@
* Performance improvement for repeated use of FieldMaskUtil#merge by caching
constructed FieldMaskTrees.
+2022-07-25 version 21.4 (C++/Java/Python/PHP/Objective-C/C#/Ruby)
+
+ C++
+ * Reduce the required alignment of ArenaString from 8 to 4 (#10298)
+
+
2022-07-19 version 21.3 (C++/Java/Python/PHP/Objective-C/C#/Ruby)
C++
* Add header search paths to Protobuf-C++.podspec (#10024)
diff --git a/Protobuf.podspec b/Protobuf.podspec
index 140a142ef5..e41f39a704 100644
--- a/Protobuf.podspec
+++ b/Protobuf.podspec
@@ -5,7 +5,7 @@
# dependent projects use the :git notation to refer to the library.
Pod::Spec.new do |s|
s.name = 'Protobuf'
- s.version = '3.21.3'
+ s.version = '3.21.4'
s.summary = 'Protocol Buffers v.3 runtime library for Objective-C.'
s.homepage = 'https://github.com/protocolbuffers/protobuf'
s.license = 'BSD-3-Clause'
diff --git a/csharp/Google.Protobuf.Tools.nuspec b/csharp/Google.Protobuf.Tools.nuspec
index 3c2f345431..90033cfae8 100644
--- a/csharp/Google.Protobuf.Tools.nuspec
+++ b/csharp/Google.Protobuf.Tools.nuspec
@@ -5,7 +5,7 @@
Google Protocol Buffers tools
Tools for Protocol Buffers - Google's data interchange format.
See project site for more info.
- 3.21.3
+ 3.21.4
Google Inc.
protobuf-packages
https://github.com/protocolbuffers/protobuf/blob/main/LICENSE
diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs
new file mode 100644
index 0000000000..f7ea97c135
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/JsonFormatterSettingsTest.cs
@@ -0,0 +1,111 @@
+#region Copyright notice and license
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 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.
+
+#endregion
+
+using Google.Protobuf.Reflection;
+using NUnit.Framework;
+
+// For WrapInQuotes
+
+namespace Google.Protobuf
+{
+ public class JsonFormatterSettingsTest
+ {
+ [Test]
+ public void WithIndentation()
+ {
+ var settings = JsonFormatter.Settings.Default.WithIndentation("\t");
+ Assert.AreEqual("\t", settings.Indentation);
+ }
+
+ [Test]
+ public void WithTypeRegistry()
+ {
+ var typeRegistry = TypeRegistry.Empty;
+ var settings = JsonFormatter.Settings.Default.WithTypeRegistry(typeRegistry);
+ Assert.AreEqual(typeRegistry, settings.TypeRegistry);
+ }
+
+ [Test]
+ public void WithFormatDefaultValues()
+ {
+ var settingsWith = JsonFormatter.Settings.Default.WithFormatDefaultValues(true);
+ Assert.AreEqual(true, settingsWith.FormatDefaultValues);
+
+ var settingsWithout = JsonFormatter.Settings.Default.WithFormatDefaultValues(false);
+ Assert.AreEqual(false, settingsWithout.FormatDefaultValues);
+ }
+
+ [Test]
+ public void WithFormatEnumsAsIntegers()
+ {
+ var settingsWith = JsonFormatter.Settings.Default.WithFormatEnumsAsIntegers(true);
+ Assert.AreEqual(true, settingsWith.FormatEnumsAsIntegers);
+
+ var settingsWithout = JsonFormatter.Settings.Default.WithFormatEnumsAsIntegers(false);
+ Assert.AreEqual(false, settingsWithout.FormatEnumsAsIntegers);
+ }
+
+ [Test]
+ public void WithMethodsPreserveExistingSettings()
+ {
+ var typeRegistry = TypeRegistry.Empty;
+ var baseSettings = JsonFormatter.Settings.Default
+ .WithIndentation("\t")
+ .WithFormatDefaultValues(true)
+ .WithFormatEnumsAsIntegers(true)
+ .WithTypeRegistry(typeRegistry)
+ .WithPreserveProtoFieldNames(true);
+
+ var settings1 = baseSettings.WithIndentation("\t");
+ var settings2 = baseSettings.WithFormatDefaultValues(true);
+ var settings3 = baseSettings.WithFormatEnumsAsIntegers(true);
+ var settings4 = baseSettings.WithTypeRegistry(typeRegistry);
+ var settings5 = baseSettings.WithPreserveProtoFieldNames(true);
+
+ AssertAreEqual(baseSettings, settings1);
+ AssertAreEqual(baseSettings, settings2);
+ AssertAreEqual(baseSettings, settings3);
+ AssertAreEqual(baseSettings, settings4);
+ AssertAreEqual(baseSettings, settings5);
+ }
+
+ private static void AssertAreEqual(JsonFormatter.Settings settings, JsonFormatter.Settings other)
+ {
+ Assert.AreEqual(settings.Indentation, other.Indentation);
+ Assert.AreEqual(settings.FormatDefaultValues, other.FormatDefaultValues);
+ Assert.AreEqual(settings.FormatEnumsAsIntegers, other.FormatEnumsAsIntegers);
+ Assert.AreEqual(settings.TypeRegistry, other.TypeRegistry);
+ }
+ }
+}
diff --git a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
index 714c78cbeb..f4dfde2435 100644
--- a/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
+++ b/csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
@@ -674,6 +674,200 @@ namespace Google.Protobuf
AssertWriteValue(value, "[ 1, 2, 3 ]");
}
+ [Test]
+ public void WriteValueWithIndentation_EmptyMessage()
+ {
+ var value = new TestEmptyMessage();
+
+ AssertWriteValue(value, "{}", JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_NestedTestAllTypes()
+ {
+ var value = new NestedTestAllTypes
+ {
+ Payload = new TestAllTypes
+ {
+ SingleBool = true,
+ SingleInt32 = 100,
+ SingleString = "multiple fields",
+ RepeatedString = { "string1", "string2" },
+ },
+ Child = new NestedTestAllTypes
+ {
+ Payload = new TestAllTypes
+ {
+ SingleString = "single field",
+ },
+ },
+ RepeatedChild =
+ {
+ new NestedTestAllTypes { Payload = new TestAllTypes { SingleString = "child 1", RepeatedString = { "string" } } },
+ new NestedTestAllTypes { Payload = new TestAllTypes { SingleString = "child 2" } },
+ },
+ };
+
+ const string expectedJson = @"
+{
+ 'child': {
+ 'payload': {
+ 'singleString': 'single field'
+ }
+ },
+ 'payload': {
+ 'singleInt32': 100,
+ 'singleBool': true,
+ 'singleString': 'multiple fields',
+ 'repeatedString': [
+ 'string1',
+ 'string2'
+ ]
+ },
+ 'repeatedChild': [
+ {
+ 'payload': {
+ 'singleString': 'child 1',
+ 'repeatedString': [
+ 'string'
+ ]
+ }
+ },
+ {
+ 'payload': {
+ 'singleString': 'child 2'
+ }
+ }
+ ]
+}";
+ AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_WellKnownTypes()
+ {
+ var value = new TestWellKnownTypes
+ {
+ StructField = new Struct
+ {
+ Fields =
+ {
+ { "string", Value.ForString("foo") },
+ { "numbers", Value.ForList(Value.ForNumber(1), Value.ForNumber(2), Value.ForNumber(3)) },
+ { "emptyList", Value.ForList() },
+ { "emptyStruct", Value.ForStruct(new Struct()) },
+ },
+ },
+ };
+
+ const string expectedJson = @"
+{
+ 'structField': {
+ 'string': 'foo',
+ 'numbers': [
+ 1,
+ 2,
+ 3
+ ],
+ 'emptyList': [],
+ 'emptyStruct': {}
+ }
+}";
+ AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_StructSingleField()
+ {
+ var value = new Struct { Fields = { { "structField1", Value.ForString("structFieldValue1") } } };
+
+ const string expectedJson = @"
+{
+ 'structField1': 'structFieldValue1'
+}";
+ AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_StructMultipleFields()
+ {
+ var value = new Struct
+ {
+ Fields =
+ {
+ { "structField1", Value.ForString("structFieldValue1") },
+ { "structField2", Value.ForString("structFieldValue2") },
+ { "structField3", Value.ForString("structFieldValue3") },
+ },
+ };
+
+ const string expectedJson = @"
+{
+ 'structField1': 'structFieldValue1',
+ 'structField2': 'structFieldValue2',
+ 'structField3': 'structFieldValue3'
+}";
+ AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void FormatWithIndentation_EmbeddedMessage()
+ {
+ var value = new TestAllTypes { SingleInt32 = 100, SingleInt64 = 3210987654321L };
+ var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithIndentation());
+ var valueJson = formatter.Format(value, indentationLevel: 1);
+
+ var actualJson = $@"
+{{
+ ""data"": {valueJson}
+}}";
+ const string expectedJson = @"
+{
+ 'data': {
+ 'singleInt32': 100,
+ 'singleInt64': '3210987654321'
+ }
+}";
+ AssertJson(expectedJson, actualJson.Trim());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_Map()
+ {
+ var value = new TestMap
+ {
+ MapStringString =
+ {
+ { "key1", "value1" },
+ { "key2", "value2" },
+ },
+ };
+
+ const string expectedJson = @"
+{
+ 'mapStringString': {
+ 'key1': 'value1',
+ 'key2': 'value2'
+ }
+}";
+
+ AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_List()
+ {
+ var value = new RepeatedField { 1, 2, 3 };
+ AssertWriteValue(value, "[\n 1,\n 2,\n 3\n]", JsonFormatter.Settings.Default.WithIndentation());
+ }
+
+ [Test]
+ public void WriteValueWithIndentation_CustomIndentation()
+ {
+ var value = new RepeatedField { 1, 2, 3 };
+ AssertWriteValue(value, "[\n\t1,\n\t2,\n\t3\n]", JsonFormatter.Settings.Default.WithIndentation("\t"));
+ }
+
[Test]
public void Proto2_DefaultValuesWritten()
{
@@ -683,7 +877,7 @@ namespace Google.Protobuf
private static void AssertWriteValue(object value, string expectedJson, JsonFormatter.Settings settings = null)
{
- var writer = new StringWriter();
+ var writer = new StringWriter { NewLine = "\n" };
new JsonFormatter(settings ?? JsonFormatter.Settings.Default).WriteValue(writer, value);
string actual = writer.ToString();
AssertJson(expectedJson, actual);
@@ -691,13 +885,17 @@ namespace Google.Protobuf
///
/// Checks that the actual JSON is the same as the expected JSON - but after replacing
- /// all apostrophes in the expected JSON with double quotes. This basically makes the tests easier
- /// to read.
+ /// all apostrophes in the expected JSON with double quotes, trimming leading whitespace and normalizing new lines.
+ /// This basically makes the tests easier to read.
///
+ ///
+ /// Line endings are normalized because indented JSON strings are generated with system-specific line endings,
+ /// while line endings in the test cases are hard-coded, but may be converted during source checkout, depending
+ /// on git settings, causing unpredictability in the test results otherwise.
private static void AssertJson(string expectedJsonWithApostrophes, string actualJson)
{
- var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"");
- Assert.AreEqual(expectedJson, actualJson);
+ var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"").Replace("\r\n", "\n").TrimStart();
+ Assert.AreEqual(expectedJson, actualJson.Replace("\r\n", "\n"));
}
}
}
diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
index 3efeabba67..c95ecad94e 100644
--- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj
+++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj
@@ -4,7 +4,7 @@
C# runtime library for Protocol Buffers - Google's data interchange format.
Copyright 2015, Google Inc.
Google Protocol Buffers
- 3.21.3
+ 3.21.4
10.0
Google Inc.
netstandard1.1;netstandard2.0;net45;net50
diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs
index 2ef10ee7e9..4482c8790b 100644
--- a/csharp/src/Google.Protobuf/JsonFormatter.cs
+++ b/csharp/src/Google.Protobuf/JsonFormatter.cs
@@ -63,7 +63,12 @@ namespace Google.Protobuf
internal const string AnyDiagnosticValueField = "@value";
internal const string AnyWellKnownTypeValueField = "value";
private const string NameValueSeparator = ": ";
- private const string PropertySeparator = ", ";
+ private const string ValueSeparator = ", ";
+ private const string MultilineValueSeparator = ",";
+ private const char ObjectOpenBracket = '{';
+ private const char ObjectCloseBracket = '}';
+ private const char ListBracketOpen = '[';
+ private const char ListBracketClose = ']';
///
/// Returns a formatter using the default settings.
@@ -140,11 +145,26 @@ namespace Google.Protobuf
/// Formats the specified message as JSON.
///
/// The message to format.
+ /// This method delegates to Format(IMessage, int) with indentationLevel = 0.
/// The formatted message.
- public string Format(IMessage message)
+ public string Format(IMessage message) => Format(message, indentationLevel: 0);
+
+ ///
+ /// Formats the specified message as JSON.
+ ///
+ /// The message to format.
+ /// Indentation level to start at.
+ /// To keep consistent indentation when embedding a message inside another JSON string, set . E.g:
+ ///
+ /// var response = $@"{{
+ /// ""data"": { Format(message, indentationLevel: 1) }
+ /// }}"
+ ///
+ /// The formatted message.
+ public string Format(IMessage message, int indentationLevel)
{
var writer = new StringWriter();
- Format(message, writer);
+ Format(message, writer, indentationLevel);
return writer.ToString();
}
@@ -153,19 +173,29 @@ namespace Google.Protobuf
///
/// The message to format.
/// The TextWriter to write the formatted message to.
+ /// This method delegates to Format(IMessage, TextWriter, int) with indentationLevel = 0.
/// The formatted message.
- public void Format(IMessage message, TextWriter writer)
+ public void Format(IMessage message, TextWriter writer) => Format(message, writer, indentationLevel: 0);
+
+ ///
+ /// Formats the specified message as JSON. When is not null, start indenting at the specified .
+ ///
+ /// The message to format.
+ /// The TextWriter to write the formatted message to.
+ /// Indentation level to start at.
+ /// To keep consistent indentation when embedding a message inside another JSON string, set .
+ public void Format(IMessage message, TextWriter writer, int indentationLevel)
{
ProtoPreconditions.CheckNotNull(message, nameof(message));
ProtoPreconditions.CheckNotNull(writer, nameof(writer));
if (message.Descriptor.IsWellKnownType)
{
- WriteWellKnownTypeValue(writer, message.Descriptor, message);
+ WriteWellKnownTypeValue(writer, message.Descriptor, message, indentationLevel);
}
else
{
- WriteMessage(writer, message);
+ WriteMessage(writer, message, indentationLevel);
}
}
@@ -192,7 +222,7 @@ namespace Google.Protobuf
return diagnosticFormatter.Format(message);
}
- private void WriteMessage(TextWriter writer, IMessage message)
+ private void WriteMessage(TextWriter writer, IMessage message, int indentationLevel)
{
if (message == null)
{
@@ -207,12 +237,13 @@ namespace Google.Protobuf
return;
}
}
- writer.Write("{ ");
- bool writtenFields = WriteMessageFields(writer, message, false);
- writer.Write(writtenFields ? " }" : "}");
+
+ WriteBracketOpen(writer, ObjectOpenBracket);
+ bool writtenFields = WriteMessageFields(writer, message, false, indentationLevel + 1);
+ WriteBracketClose(writer, ObjectCloseBracket, writtenFields, indentationLevel);
}
- private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten)
+ private bool WriteMessageFields(TextWriter writer, IMessage message, bool assumeFirstFieldWritten, int indentationLevel)
{
var fields = message.Descriptor.Fields;
bool first = !assumeFirstFieldWritten;
@@ -226,10 +257,8 @@ namespace Google.Protobuf
continue;
}
- if (!first)
- {
- writer.Write(PropertySeparator);
- }
+ MaybeWriteValueSeparator(writer, first);
+ MaybeWriteValueWhitespace(writer, indentationLevel);
if (settings.PreserveProtoFieldNames)
{
@@ -240,13 +269,23 @@ namespace Google.Protobuf
WriteString(writer, accessor.Descriptor.JsonName);
}
writer.Write(NameValueSeparator);
- WriteValue(writer, value);
+ WriteValue(writer, value, indentationLevel);
first = false;
}
return !first;
}
+ private void MaybeWriteValueSeparator(TextWriter writer, bool first)
+ {
+ if (first)
+ {
+ return;
+ }
+
+ writer.Write(settings.Indentation == null ? ValueSeparator : MultilineValueSeparator);
+ }
+
///
/// Determines whether or not a field value should be serialized according to the field,
/// its value in the message, and the settings of this formatter.
@@ -342,7 +381,19 @@ namespace Google.Protobuf
///
/// The writer to write the value to. Must not be null.
/// The value to write. May be null.
- public void WriteValue(TextWriter writer, object value)
+ /// Delegates to WriteValue(TextWriter, object, int) with indentationLevel = 0.
+ public void WriteValue(TextWriter writer, object value) => WriteValue(writer, value, 0);
+
+ ///
+ /// Writes a single value to the given writer as JSON. Only types understood by
+ /// Protocol Buffers can be written in this way. This method is only exposed for
+ /// advanced use cases; most users should be using
+ /// or .
+ ///
+ /// The writer to write the value to. Must not be null.
+ /// The value to write. May be null.
+ /// The current indentationLevel. Not used when is null.
+ public void WriteValue(TextWriter writer, object value, int indentationLevel)
{
if (value == null || value is NullValue)
{
@@ -365,11 +416,11 @@ namespace Google.Protobuf
}
else if (value is IDictionary dictionary)
{
- WriteDictionary(writer, dictionary);
+ WriteDictionary(writer, dictionary, indentationLevel);
}
else if (value is IList list)
{
- WriteList(writer, list);
+ WriteList(writer, list, indentationLevel);
}
else if (value is int || value is uint)
{
@@ -418,7 +469,7 @@ namespace Google.Protobuf
}
else if (value is IMessage message)
{
- Format(message, writer);
+ Format(message, writer, indentationLevel);
}
else
{
@@ -432,7 +483,7 @@ namespace Google.Protobuf
/// values are using the embedded well-known types, in order to allow for dynamic messages
/// in the future.
///
- private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value)
+ private void WriteWellKnownTypeValue(TextWriter writer, MessageDescriptor descriptor, object value, int indentationLevel)
{
// Currently, we can never actually get here, because null values are always handled by the caller. But if we *could*,
// this would do the right thing.
@@ -472,26 +523,26 @@ namespace Google.Protobuf
}
if (descriptor.FullName == Struct.Descriptor.FullName)
{
- WriteStruct(writer, (IMessage)value);
+ WriteStruct(writer, (IMessage)value, indentationLevel);
return;
}
if (descriptor.FullName == ListValue.Descriptor.FullName)
{
var fieldAccessor = descriptor.Fields[ListValue.ValuesFieldNumber].Accessor;
- WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value));
+ WriteList(writer, (IList)fieldAccessor.GetValue((IMessage)value), indentationLevel);
return;
}
if (descriptor.FullName == Value.Descriptor.FullName)
{
- WriteStructFieldValue(writer, (IMessage)value);
+ WriteStructFieldValue(writer, (IMessage)value, indentationLevel);
return;
}
if (descriptor.FullName == Any.Descriptor.FullName)
{
- WriteAny(writer, (IMessage)value);
+ WriteAny(writer, (IMessage)value, indentationLevel);
return;
}
- WriteMessage(writer, (IMessage)value);
+ WriteMessage(writer, (IMessage)value, indentationLevel);
}
private void WriteTimestamp(TextWriter writer, IMessage value)
@@ -519,7 +570,7 @@ namespace Google.Protobuf
writer.Write(FieldMask.ToJson(paths, DiagnosticOnly));
}
- private void WriteAny(TextWriter writer, IMessage value)
+ private void WriteAny(TextWriter writer, IMessage value, int indentationLevel)
{
if (DiagnosticOnly)
{
@@ -536,23 +587,23 @@ namespace Google.Protobuf
throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'");
}
IMessage message = descriptor.Parser.ParseFrom(data);
- writer.Write("{ ");
+ WriteBracketOpen(writer, ObjectOpenBracket);
WriteString(writer, AnyTypeUrlField);
writer.Write(NameValueSeparator);
WriteString(writer, typeUrl);
if (descriptor.IsWellKnownType)
{
- writer.Write(PropertySeparator);
+ writer.Write(ValueSeparator);
WriteString(writer, AnyWellKnownTypeValueField);
writer.Write(NameValueSeparator);
- WriteWellKnownTypeValue(writer, descriptor, message);
+ WriteWellKnownTypeValue(writer, descriptor, message, indentationLevel);
}
else
{
- WriteMessageFields(writer, message, true);
+ WriteMessageFields(writer, message, true, indentationLevel);
}
- writer.Write(" }");
+ WriteBracketClose(writer, ObjectCloseBracket, true, indentationLevel);
}
private void WriteDiagnosticOnlyAny(TextWriter writer, IMessage value)
@@ -563,7 +614,7 @@ namespace Google.Protobuf
WriteString(writer, AnyTypeUrlField);
writer.Write(NameValueSeparator);
WriteString(writer, typeUrl);
- writer.Write(PropertySeparator);
+ writer.Write(ValueSeparator);
WriteString(writer, AnyDiagnosticValueField);
writer.Write(NameValueSeparator);
writer.Write('"');
@@ -572,9 +623,9 @@ namespace Google.Protobuf
writer.Write(" }");
}
- private void WriteStruct(TextWriter writer, IMessage message)
+ private void WriteStruct(TextWriter writer, IMessage message, int indentationLevel)
{
- writer.Write("{ ");
+ WriteBracketOpen(writer, ObjectOpenBracket);
IDictionary fields = (IDictionary) message.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(message);
bool first = true;
foreach (DictionaryEntry entry in fields)
@@ -586,19 +637,17 @@ namespace Google.Protobuf
throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
}
- if (!first)
- {
- writer.Write(PropertySeparator);
- }
+ MaybeWriteValueSeparator(writer, first);
+ MaybeWriteValueWhitespace(writer, indentationLevel + 1);
WriteString(writer, key);
writer.Write(NameValueSeparator);
- WriteStructFieldValue(writer, value);
+ WriteStructFieldValue(writer, value, indentationLevel + 1);
first = false;
}
- writer.Write(first ? "}" : " }");
+ WriteBracketClose(writer, ObjectCloseBracket, !first, indentationLevel);
}
- private void WriteStructFieldValue(TextWriter writer, IMessage message)
+ private void WriteStructFieldValue(TextWriter writer, IMessage message, int indentationLevel)
{
var specifiedField = message.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(message);
if (specifiedField == null)
@@ -619,7 +668,7 @@ namespace Google.Protobuf
case Value.ListValueFieldNumber:
// Structs and ListValues are nested messages, and already well-known types.
var nestedMessage = (IMessage) specifiedField.Accessor.GetValue(message);
- WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage);
+ WriteWellKnownTypeValue(writer, nestedMessage.Descriptor, nestedMessage, indentationLevel);
return;
case Value.NullValueFieldNumber:
WriteNull(writer);
@@ -629,33 +678,30 @@ namespace Google.Protobuf
}
}
- internal void WriteList(TextWriter writer, IList list)
+ internal void WriteList(TextWriter writer, IList list, int indentationLevel = 0)
{
- writer.Write("[ ");
+ WriteBracketOpen(writer, ListBracketOpen);
+
bool first = true;
foreach (var value in list)
{
- if (!first)
- {
- writer.Write(PropertySeparator);
- }
- WriteValue(writer, value);
+ MaybeWriteValueSeparator(writer, first);
+ MaybeWriteValueWhitespace(writer, indentationLevel + 1);
+ WriteValue(writer, value, indentationLevel + 1);
first = false;
}
- writer.Write(first ? "]" : " ]");
+
+ WriteBracketClose(writer, ListBracketClose, !first, indentationLevel);
}
- internal void WriteDictionary(TextWriter writer, IDictionary dictionary)
+ internal void WriteDictionary(TextWriter writer, IDictionary dictionary, int indentationLevel = 0)
{
- writer.Write("{ ");
+ WriteBracketOpen(writer, ObjectOpenBracket);
+
bool first = true;
// This will box each pair. Could use IDictionaryEnumerator, but that's ugly in terms of disposal.
foreach (DictionaryEntry pair in dictionary)
{
- if (!first)
- {
- writer.Write(PropertySeparator);
- }
string keyText;
if (pair.Key is string s)
{
@@ -677,12 +723,16 @@ namespace Google.Protobuf
}
throw new ArgumentException("Unhandled dictionary key type: " + pair.Key.GetType());
}
+
+ MaybeWriteValueSeparator(writer, first);
+ MaybeWriteValueWhitespace(writer, indentationLevel + 1);
WriteString(writer, keyText);
writer.Write(NameValueSeparator);
WriteValue(writer, pair.Value);
first = false;
}
- writer.Write(first ? "}" : " }");
+
+ WriteBracketClose(writer, ObjectCloseBracket, !first, indentationLevel);
}
///
@@ -766,6 +816,49 @@ namespace Google.Protobuf
writer.Write(Hex[(c >> 0) & 0xf]);
}
+ private void WriteBracketOpen(TextWriter writer, char openChar)
+ {
+ writer.Write(openChar);
+ if (settings.Indentation == null)
+ {
+ writer.Write(' ');
+ }
+ }
+
+ private void WriteBracketClose(TextWriter writer, char closeChar, bool hasFields, int indentationLevel)
+ {
+ if (hasFields)
+ {
+ if (settings.Indentation != null)
+ {
+ writer.WriteLine();
+ WriteIndentation(writer, indentationLevel);
+ }
+ else
+ {
+ writer.Write(" ");
+ }
+ }
+
+ writer.Write(closeChar);
+ }
+
+ private void MaybeWriteValueWhitespace(TextWriter writer, int indentationLevel)
+ {
+ if (settings.Indentation != null) {
+ writer.WriteLine();
+ WriteIndentation(writer, indentationLevel);
+ }
+ }
+
+ private void WriteIndentation(TextWriter writer, int indentationLevel)
+ {
+ for (int i = 0; i < indentationLevel; i++)
+ {
+ writer.Write(settings.Indentation);
+ }
+ }
+
///
/// Settings controlling JSON formatting.
///
@@ -806,6 +899,10 @@ namespace Google.Protobuf
///
public bool PreserveProtoFieldNames { get; }
+ ///
+ /// Indentation string, used for formatting. Setting null disables indentation.
+ ///
+ public string Indentation { get; }
///
/// Creates a new object with the specified formatting of default values
@@ -833,40 +930,54 @@ namespace Google.Protobuf
/// The to use when formatting messages. TypeRegistry.Empty will be used if it is null.
/// true to format the enums as integers; false to format enums as enum names.
/// true to preserve proto field names; false to convert them to lowerCamelCase.
+ /// The indentation string to use for multi-line formatting. null to disable multi-line format.
private Settings(bool formatDefaultValues,
TypeRegistry typeRegistry,
bool formatEnumsAsIntegers,
- bool preserveProtoFieldNames)
+ bool preserveProtoFieldNames,
+ string indentation = null)
{
FormatDefaultValues = formatDefaultValues;
TypeRegistry = typeRegistry ?? TypeRegistry.Empty;
FormatEnumsAsIntegers = formatEnumsAsIntegers;
PreserveProtoFieldNames = preserveProtoFieldNames;
+ Indentation = indentation;
}
///
/// Creates a new object with the specified formatting of default values and the current settings.
///
/// true if default values (0, empty strings etc) should be formatted; false otherwise.
- public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames);
+ public Settings WithFormatDefaultValues(bool formatDefaultValues) => new Settings(formatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames, Indentation);
///
/// Creates a new object with the specified type registry and the current settings.
///
/// The to use when formatting messages.
- public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames);
+ public Settings WithTypeRegistry(TypeRegistry typeRegistry) => new Settings(FormatDefaultValues, typeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames, Indentation);
///
/// Creates a new object with the specified enums formatting option and the current settings.
///
/// true to format the enums as integers; false to format enums as enum names.
- public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers, PreserveProtoFieldNames);
+ public Settings WithFormatEnumsAsIntegers(bool formatEnumsAsIntegers) => new Settings(FormatDefaultValues, TypeRegistry, formatEnumsAsIntegers, PreserveProtoFieldNames, Indentation);
///
/// Creates a new object with the specified field name formatting option and the current settings.
///
/// true to preserve proto field names; false to convert them to lowerCamelCase.
- public Settings WithPreserveProtoFieldNames(bool preserveProtoFieldNames) => new Settings(FormatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, preserveProtoFieldNames);
+ public Settings WithPreserveProtoFieldNames(bool preserveProtoFieldNames) => new Settings(FormatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, preserveProtoFieldNames, Indentation);
+
+ ///
+ /// Creates a new object with the specified indentation and the current settings.
+ ///
+ /// The string to output for each level of indentation (nesting). The default is two spaces per level. Use null to disable indentation entirely.
+ /// A non-null value for will insert additional line-breaks to the JSON output.
+ /// Each line will contain either a single value, or braces. The default line-break is determined by ,
+ /// which is "\n" on Unix platforms, and "\r\n" on Windows. If seems to produce empty lines,
+ /// you need to pass a that uses a "\n" newline. See .
+ ///
+ public Settings WithIndentation(string indentation = " ") => new Settings(FormatDefaultValues, TypeRegistry, FormatEnumsAsIntegers, PreserveProtoFieldNames, indentation);
}
// Effectively a cache of mapping from enum values to the original name as specified in the proto file,
diff --git a/java/README.md b/java/README.md
index 3465f3aa23..5e3ded418f 100644
--- a/java/README.md
+++ b/java/README.md
@@ -23,7 +23,7 @@ If you are using Maven, use the following:
com.google.protobuf
protobuf-java
- 3.21.3
+ 3.21.4
```
@@ -37,7 +37,7 @@ protobuf-java-util package:
com.google.protobuf
protobuf-java-util
- 3.21.3
+ 3.21.4
```
@@ -45,7 +45,7 @@ protobuf-java-util package:
If you are using Gradle, add the following to your `build.gradle` file's dependencies:
```
- implementation 'com.google.protobuf:protobuf-java:3.21.3'
+ implementation 'com.google.protobuf:protobuf-java:3.21.4'
```
Again, be sure to check that the version number matches (or is newer than) the version number of protoc that you are using.
diff --git a/java/bom/pom.xml b/java/bom/pom.xml
index 130c967f56..4cccfb3545 100644
--- a/java/bom/pom.xml
+++ b/java/bom/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-bom
- 3.21.3
+ 3.21.4
pom
Protocol Buffers [BOM]
diff --git a/java/core/pom.xml b/java/core/pom.xml
index 5a36e6ff0f..d5eacd6a10 100644
--- a/java/core/pom.xml
+++ b/java/core/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-parent
- 3.21.3
+ 3.21.4
protobuf-java
diff --git a/java/kotlin-lite/pom.xml b/java/kotlin-lite/pom.xml
index feb51224c2..2dc430907e 100644
--- a/java/kotlin-lite/pom.xml
+++ b/java/kotlin-lite/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-parent
- 3.21.3
+ 3.21.4
protobuf-kotlin-lite
diff --git a/java/kotlin/pom.xml b/java/kotlin/pom.xml
index f8a8efc700..7a350f7853 100644
--- a/java/kotlin/pom.xml
+++ b/java/kotlin/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-parent
- 3.21.3
+ 3.21.4
protobuf-kotlin
diff --git a/java/lite.md b/java/lite.md
index 7243d3aabc..d4b4fc87b2 100644
--- a/java/lite.md
+++ b/java/lite.md
@@ -29,7 +29,7 @@ protobuf Java Lite runtime. If you are using Maven, include the following:
com.google.protobuf
protobuf-javalite
- 3.21.3
+ 3.21.4
```
diff --git a/java/lite/pom.xml b/java/lite/pom.xml
index d22d62f620..20381c65ba 100644
--- a/java/lite/pom.xml
+++ b/java/lite/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-parent
- 3.21.3
+ 3.21.4
protobuf-javalite
diff --git a/java/pom.xml b/java/pom.xml
index 9f38fd0f95..99abcf86a9 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-parent
- 3.21.3
+ 3.21.4
pom
Protocol Buffers [Parent]
diff --git a/java/util/pom.xml b/java/util/pom.xml
index cb19a74e89..f634047c6d 100644
--- a/java/util/pom.xml
+++ b/java/util/pom.xml
@@ -4,7 +4,7 @@
com.google.protobuf
protobuf-parent
- 3.21.3
+ 3.21.4
protobuf-java-util
diff --git a/kokoro/linux/cmake/build.sh b/kokoro/linux/cmake/build.sh
new file mode 100755
index 0000000000..1b0ebfc5fa
--- /dev/null
+++ b/kokoro/linux/cmake/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Build file to set up and run tests based on distribution archive
+
+set -eux
+
+# Change to repo root
+cd $(dirname $0)/../../..
+GIT_REPO_ROOT=`pwd`
+
+CONTAINER_IMAGE=gcr.io/protobuf-build/cmake/linux@sha256:79e6ed9d7f3f8e56167a3309a521e5b7e6a212bfb19855c65ee1cbb6f9099671
+
+# Update git submodules
+git submodule update --init --recursive
+
+tmpfile=$(mktemp -u)
+
+docker run \
+ --cidfile $tmpfile \
+ -v $GIT_REPO_ROOT:/workspace \
+ $CONTAINER_IMAGE \
+ /test.sh -Dprotobuf_BUILD_CONFORMANCE=ON
+
+# Save logs for Kokoro
+docker cp \
+ `cat $tmpfile`:/workspace/logs $KOKORO_ARTIFACTS_DIR
diff --git a/kokoro/linux/cmake/continuous.cfg b/kokoro/linux/cmake/continuous.cfg
new file mode 100644
index 0000000000..f03bd3945f
--- /dev/null
+++ b/kokoro/linux/cmake/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake/build.sh"
+timeout_mins: 1440
+
+action {
+ define_artifacts {
+ regex: "**/sponge_log.*"
+ }
+}
diff --git a/kokoro/linux/cmake/presubmit.cfg b/kokoro/linux/cmake/presubmit.cfg
new file mode 100644
index 0000000000..f03bd3945f
--- /dev/null
+++ b/kokoro/linux/cmake/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake/build.sh"
+timeout_mins: 1440
+
+action {
+ define_artifacts {
+ regex: "**/sponge_log.*"
+ }
+}
diff --git a/kokoro/linux/cmake_install/build.sh b/kokoro/linux/cmake_install/build.sh
new file mode 100755
index 0000000000..6fdafa557e
--- /dev/null
+++ b/kokoro/linux/cmake_install/build.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+#
+# Build file to set up and run tests based on distribution archive
+
+set -eux
+
+# Change to repo root
+cd $(dirname $0)/../../..
+GIT_REPO_ROOT=`pwd`
+
+CONTAINER_IMAGE=gcr.io/protobuf-build/cmake/linux@sha256:79e6ed9d7f3f8e56167a3309a521e5b7e6a212bfb19855c65ee1cbb6f9099671
+
+# Update git submodules
+git submodule update --init --recursive
+
+tmpfile=$(mktemp -u)
+
+docker run \
+ --cidfile $tmpfile \
+ -v $GIT_REPO_ROOT:/workspace \
+ $CONTAINER_IMAGE \
+ "/install.sh && /test.sh \
+ -Dprotobuf_REMOVE_INSTALLED_HEADERS=ON \
+ -Dprotobuf_BUILD_PROTOBUF_BINARIES=OFF \
+ -Dprotobuf_BUILD_CONFORMANCE=ON"
+
+
+# Save logs for Kokoro
+docker cp \
+ `cat $tmpfile`:/workspace/logs $KOKORO_ARTIFACTS_DIR
diff --git a/kokoro/linux/cmake_install/continuous.cfg b/kokoro/linux/cmake_install/continuous.cfg
new file mode 100644
index 0000000000..f1ae0b351f
--- /dev/null
+++ b/kokoro/linux/cmake_install/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_install/build.sh"
+timeout_mins: 1440
+
+action {
+ define_artifacts {
+ regex: "**/sponge_log.*"
+ }
+}
diff --git a/kokoro/linux/cmake_install/presubmit.cfg b/kokoro/linux/cmake_install/presubmit.cfg
new file mode 100644
index 0000000000..f1ae0b351f
--- /dev/null
+++ b/kokoro/linux/cmake_install/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_install/build.sh"
+timeout_mins: 1440
+
+action {
+ define_artifacts {
+ regex: "**/sponge_log.*"
+ }
+}
diff --git a/kokoro/linux/cmake_ninja/build.sh b/kokoro/linux/cmake_ninja/build.sh
new file mode 100755
index 0000000000..d3a281f9ec
--- /dev/null
+++ b/kokoro/linux/cmake_ninja/build.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# Build file to set up and run tests based on distribution archive
+
+set -eux
+
+# Change to repo root
+cd $(dirname $0)/../../..
+GIT_REPO_ROOT=`pwd`
+
+CONTAINER_IMAGE=gcr.io/protobuf-build/cmake/linux@sha256:79e6ed9d7f3f8e56167a3309a521e5b7e6a212bfb19855c65ee1cbb6f9099671
+
+# Update git submodules
+git submodule update --init --recursive
+
+tmpfile=$(mktemp -u)
+
+docker run \
+ --cidfile $tmpfile \
+ -v $GIT_REPO_ROOT:/workspace \
+ $CONTAINER_IMAGE \
+ /test.sh -G Ninja -Dprotobuf_BUILD_CONFORMANCE=ON
+
+# Save logs for Kokoro
+docker cp \
+ `cat $tmpfile`:/workspace/logs $KOKORO_ARTIFACTS_DIR
diff --git a/kokoro/linux/cmake_ninja/continuous.cfg b/kokoro/linux/cmake_ninja/continuous.cfg
new file mode 100644
index 0000000000..144fc90a82
--- /dev/null
+++ b/kokoro/linux/cmake_ninja/continuous.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_ninja/build.sh"
+timeout_mins: 1440
+
+action {
+ define_artifacts {
+ regex: "**/sponge_log.*"
+ }
+}
diff --git a/kokoro/linux/cmake_ninja/presubmit.cfg b/kokoro/linux/cmake_ninja/presubmit.cfg
new file mode 100644
index 0000000000..144fc90a82
--- /dev/null
+++ b/kokoro/linux/cmake_ninja/presubmit.cfg
@@ -0,0 +1,11 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/linux/cmake_ninja/build.sh"
+timeout_mins: 1440
+
+action {
+ define_artifacts {
+ regex: "**/sponge_log.*"
+ }
+}
diff --git a/kokoro/windows/cmake/build.bat b/kokoro/windows/cmake/build.bat
new file mode 100644
index 0000000000..52b83f4666
--- /dev/null
+++ b/kokoro/windows/cmake/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/windows/cmake/continuous.cfg b/kokoro/windows/cmake/continuous.cfg
new file mode 100644
index 0000000000..37e89e068b
--- /dev/null
+++ b/kokoro/windows/cmake/continuous.cfg
@@ -0,0 +1,5 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
+timeout_mins: 1440
diff --git a/kokoro/windows/cmake/presubmit.cfg b/kokoro/windows/cmake/presubmit.cfg
new file mode 100644
index 0000000000..37e89e068b
--- /dev/null
+++ b/kokoro/windows/cmake/presubmit.cfg
@@ -0,0 +1,5 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/windows/cmake/build.bat"
+timeout_mins: 1440
diff --git a/kokoro/windows/cmake_install/build.bat b/kokoro/windows/cmake_install/build.bat
new file mode 100644
index 0000000000..52b83f4666
--- /dev/null
+++ b/kokoro/windows/cmake_install/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/windows/cmake_install/continuous.cfg b/kokoro/windows/cmake_install/continuous.cfg
new file mode 100644
index 0000000000..2efc0dced2
--- /dev/null
+++ b/kokoro/windows/cmake_install/continuous.cfg
@@ -0,0 +1,5 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/windows/cmake_install/build.bat"
+timeout_mins: 1440
diff --git a/kokoro/windows/cmake_install/presubmit.cfg b/kokoro/windows/cmake_install/presubmit.cfg
new file mode 100644
index 0000000000..2efc0dced2
--- /dev/null
+++ b/kokoro/windows/cmake_install/presubmit.cfg
@@ -0,0 +1,5 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/windows/cmake_install/build.bat"
+timeout_mins: 1440
diff --git a/kokoro/windows/cmake_nmake/build.bat b/kokoro/windows/cmake_nmake/build.bat
new file mode 100644
index 0000000000..52b83f4666
--- /dev/null
+++ b/kokoro/windows/cmake_nmake/build.bat
@@ -0,0 +1,4 @@
+@rem enter repo root
+cd /d %~dp0\..\..\..
+
+@rem TODO(mkruskal) Implement tests
diff --git a/kokoro/windows/cmake_nmake/continuous.cfg b/kokoro/windows/cmake_nmake/continuous.cfg
new file mode 100644
index 0000000000..3c279fe421
--- /dev/null
+++ b/kokoro/windows/cmake_nmake/continuous.cfg
@@ -0,0 +1,5 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/windows/cmake_nmake/build.bat"
+timeout_mins: 1440
diff --git a/kokoro/windows/cmake_nmake/presubmit.cfg b/kokoro/windows/cmake_nmake/presubmit.cfg
new file mode 100644
index 0000000000..3c279fe421
--- /dev/null
+++ b/kokoro/windows/cmake_nmake/presubmit.cfg
@@ -0,0 +1,5 @@
+# Config file for running tests in Kokoro
+
+# Location of the build script in repository
+build_file: "protobuf/kokoro/windows/cmake_nmake/build.bat"
+timeout_mins: 1440
diff --git a/php/ext/google/protobuf/package.xml b/php/ext/google/protobuf/package.xml
index 7b99d916f5..c291714265 100644
--- a/php/ext/google/protobuf/package.xml
+++ b/php/ext/google/protobuf/package.xml
@@ -10,11 +10,11 @@
protobuf-packages@google.com
yes
- 2022-07-21
-
+ 2022-07-25
+
- 3.21.3
- 3.21.3
+ 3.21.4
+ 3.21.4
stable
@@ -1373,5 +1373,20 @@ G A release.
+
+
+ 3.21.4
+ 3.21.4
+
+
+ stable
+ stable
+
+ 2022-07-25
+
+ BSD-3-Clause
+
+
+
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index 0ecfb6f726..e63e9e8303 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -127,7 +127,7 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_setter, 0, 0, 1)
ZEND_ARG_INFO(0, value)
ZEND_END_ARG_INFO()
-#define PHP_PROTOBUF_VERSION "3.21.3"
+#define PHP_PROTOBUF_VERSION "3.21.4"
// ptr -> PHP object cache. This is a weak map that caches lazily-created
// wrapper objects around upb types:
diff --git a/protobuf_version.bzl b/protobuf_version.bzl
index c5d90299eb..edac50366c 100644
--- a/protobuf_version.bzl
+++ b/protobuf_version.bzl
@@ -1,3 +1,3 @@
-PROTOC_VERSION = '21.3'
-PROTOBUF_JAVA_VERSION = '3.21.3'
-PROTOBUF_PYTHON_VERSION = '4.21.3'
+PROTOC_VERSION = '21.4'
+PROTOBUF_JAVA_VERSION = '3.21.4'
+PROTOBUF_PYTHON_VERSION = '4.21.4'
diff --git a/protoc-artifacts/pom.xml b/protoc-artifacts/pom.xml
index 4b462e2494..d816174f08 100644
--- a/protoc-artifacts/pom.xml
+++ b/protoc-artifacts/pom.xml
@@ -8,7 +8,7 @@
com.google.protobuf
protoc
- 3.21.3
+ 3.21.4
pom
Protobuf Compiler
diff --git a/python/release.sh b/python/release.sh
index 15a70db31f..87fcf8cfee 100755
--- a/python/release.sh
+++ b/python/release.sh
@@ -19,11 +19,24 @@ function run_install_test() {
chmod +x test-venv/bin/protoc
source test-venv/bin/activate
- pip install -i ${PYPI} protobuf==${VERSION} --no-cache-dir
+ (pip install -i ${PYPI} protobuf==${VERSION} --no-cache-dir) || (retry_pip_install ${PYPI} ${VERSION})
deactivate
rm -fr test-venv
}
+function retry_pip_install() {
+ local PYPI=$1
+ local VERSION=$2
+
+ read -p "pip install failed, possibly due to delay between upload and availability on pip. Retry? [y/n]" -r
+ echo
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ exit 1
+ fi
+
+ (pip install -i ${PYPI} protobuf==${VERSION} --no-cache-dir) || (retry_pip_install ${PYPI} ${VERSION})
+}
+
[ $# -lt 1 ] && {
echo "Usage: $0 VERSION ["
@@ -86,13 +99,16 @@ python3 setup.py test
python3 setup.py sdist
twine upload --skip-existing -r testpypi -u protobuf-wheel-test dist/*
-# Test locally with different python versions.
+# Sleep to allow time for distribution to be available on pip.
+sleep 5m
+
+# Test locally.
run_install_test ${TESTING_VERSION} python3 https://test.pypi.org/simple
# Deploy egg/wheel packages to testing PyPI and test again.
python3 setup.py clean build bdist_wheel
twine upload --skip-existing -r testpypi -u protobuf-wheel-test dist/*
-
+sleep 5m
run_install_test ${TESTING_VERSION} python3 https://test.pypi.org/simple
echo "All install tests have passed using testing PyPI."
diff --git a/ruby/google-protobuf.gemspec b/ruby/google-protobuf.gemspec
index 1688735f45..e43665d447 100644
--- a/ruby/google-protobuf.gemspec
+++ b/ruby/google-protobuf.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = "google-protobuf"
- s.version = "3.21.3"
+ s.version = "3.21.4"
git_tag = "v#{s.version.to_s.sub('.rc.', '-rc')}" # Converts X.Y.Z.rc.N to vX.Y.Z-rcN, used for the git tag
s.licenses = ["BSD-3-Clause"]
s.summary = "Protocol Buffers"
diff --git a/ruby/pom.xml b/ruby/pom.xml
index f1e4bef009..4264172772 100644
--- a/ruby/pom.xml
+++ b/ruby/pom.xml
@@ -9,7 +9,7 @@
com.google.protobuf.jruby
protobuf-jruby
- 3.21.3
+ 3.21.4
Protocol Buffer JRuby native extension
Protocol Buffers are a way of encoding structured data in an efficient yet
@@ -76,7 +76,7 @@
com.google.protobuf
protobuf-java-util
- 3.21.3
+ 3.21.4
org.jruby
diff --git a/src/Makefile.am b/src/Makefile.am
index 9875b1c575..4f4fc808a6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -774,6 +774,7 @@ protobuf_test_SOURCES = \
google/protobuf/compiler/csharp/csharp_generator_unittest.cc \
google/protobuf/compiler/importer_unittest.cc \
google/protobuf/compiler/java/doc_comment_unittest.cc \
+ google/protobuf/compiler/java/message_serialization_unittest.cc \
google/protobuf/compiler/java/plugin_unittest.cc \
google/protobuf/compiler/mock_code_generator.cc \
google/protobuf/compiler/mock_code_generator.h \
diff --git a/src/file_lists.cmake b/src/file_lists.cmake
index 32f10b2705..67092cc39e 100644
--- a/src/file_lists.cmake
+++ b/src/file_lists.cmake
@@ -771,6 +771,7 @@ set(compiler_test_files
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/csharp/csharp_generator_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/importer_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/java/doc_comment_unittest.cc
+ ${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/java/message_serialization_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/java/plugin_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/objectivec/objectivec_helpers_unittest.cc
${protobuf_SOURCE_DIR}/src/google/protobuf/compiler/parser_unittest.cc
diff --git a/src/google/protobuf/arena_unittest.cc b/src/google/protobuf/arena_unittest.cc
index 0987c9929e..1466376ed7 100644
--- a/src/google/protobuf/arena_unittest.cc
+++ b/src/google/protobuf/arena_unittest.cc
@@ -1419,7 +1419,7 @@ TEST(ArenaTest, BlockSizeDoubling) {
ASSERT_GT(arena.SpaceAllocated(), first_block_size);
auto second_block_size = (arena.SpaceAllocated() - first_block_size);
- EXPECT_EQ(second_block_size, 2*first_block_size);
+ EXPECT_GE(second_block_size, 2*first_block_size);
}
TEST(ArenaTest, Alignment) {
diff --git a/src/google/protobuf/compiler/command_line_interface.cc b/src/google/protobuf/compiler/command_line_interface.cc
index 4dc5f96cf4..74910461ec 100644
--- a/src/google/protobuf/compiler/command_line_interface.cc
+++ b/src/google/protobuf/compiler/command_line_interface.cc
@@ -339,9 +339,12 @@ class CommandLineInterface::ErrorPrinter
void AddErrorOrWarning(const std::string& filename, int line, int column,
const std::string& message, const std::string& type,
std::ostream& out) {
- // Print full path when running under MSVS
std::string dfile;
- if (format_ == CommandLineInterface::ERROR_FORMAT_MSVS &&
+ if (
+#ifndef PROTOBUF_OPENSOURCE
+ // Print full path when running under MSVS
+ format_ == CommandLineInterface::ERROR_FORMAT_MSVS &&
+#endif // !PROTOBUF_OPENSOURCE
tree_ != nullptr && tree_->VirtualFileToDiskFile(filename, &dfile)) {
out << dfile;
} else {
diff --git a/src/google/protobuf/compiler/cpp/field.cc b/src/google/protobuf/compiler/cpp/field.cc
index cf4f14e8bf..90d20848b3 100644
--- a/src/google/protobuf/compiler/cpp/field.cc
+++ b/src/google/protobuf/compiler/cpp/field.cc
@@ -330,7 +330,6 @@ void FieldGenerator::GenerateCopyConstructorCode(io::Printer* printer) const {
}
}
-
void SetCommonOneofFieldVariables(
const FieldDescriptor* descriptor,
std::map* variables) {
diff --git a/src/google/protobuf/compiler/cpp/field.h b/src/google/protobuf/compiler/cpp/field.h
index 3903e79862..3fcbda371c 100644
--- a/src/google/protobuf/compiler/cpp/field.h
+++ b/src/google/protobuf/compiler/cpp/field.h
@@ -208,7 +208,6 @@ class FieldGenerator {
virtual bool IsInlined() const { return false; }
-
virtual ArenaDtorNeeds NeedsArenaDestructor() const {
return ArenaDtorNeeds::kNone;
}
diff --git a/src/google/protobuf/compiler/cpp/file.cc b/src/google/protobuf/compiler/cpp/file.cc
index 838e0ab9b4..502d8c007e 100644
--- a/src/google/protobuf/compiler/cpp/file.cc
+++ b/src/google/protobuf/compiler/cpp/file.cc
@@ -495,12 +495,10 @@ void FileGenerator::GenerateSourceDefaultInstance(int idx,
generator->GenerateInitDefaultSplitInstance(printer);
format(
"} {}\n"
- " ~$1$() {}\n"
" union {\n"
- " $2$ _instance;\n"
+ " $1$ _instance;\n"
" };\n"
"};\n",
- DefaultInstanceType(generator->descriptor_, options_, /*split=*/true),
StrCat(generator->classname_, "::Impl_::Split"));
// NO_DESTROY is not necessary for correctness. The empty destructor is
// enough. However, the empty destructor fails to be elided in some
@@ -508,7 +506,7 @@ void FileGenerator::GenerateSourceDefaultInstance(int idx,
// there just to improve performance and binary size in these builds.
format(
"PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT "
- "PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 $1$ $2$;\n",
+ "PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 const $1$ $2$;\n",
DefaultInstanceType(generator->descriptor_, options_, /*split=*/true),
DefaultInstanceName(generator->descriptor_, options_, /*split=*/true));
}
@@ -999,7 +997,7 @@ class FileGenerator::ForwardDeclarations {
const Descriptor* class_desc = p.second;
format(
"struct $1$;\n"
- "$dllexport_decl $extern $1$ $2$;\n",
+ "$dllexport_decl $extern const $1$ $2$;\n",
DefaultInstanceType(class_desc, options, /*split=*/true),
DefaultInstanceName(class_desc, options, /*split=*/true));
}
diff --git a/src/google/protobuf/compiler/cpp/helpers.cc b/src/google/protobuf/compiler/cpp/helpers.cc
index 4939aa533f..d182c8607f 100644
--- a/src/google/protobuf/compiler/cpp/helpers.cc
+++ b/src/google/protobuf/compiler/cpp/helpers.cc
@@ -176,7 +176,6 @@ static const char* const kKeywordList[] = {
#endif // !PROTOBUF_FUTURE_BREAKING_CHANGES
};
-
static std::unordered_set* MakeKeywordsMap() {
auto* result = new std::unordered_set();
for (const auto keyword : kKeywordList) {
@@ -525,7 +524,6 @@ std::string FieldName(const FieldDescriptor* field) {
return result;
}
-
std::string FieldMemberName(const FieldDescriptor* field, bool split) {
StringPiece prefix =
IsMapEntryMessage(field->containing_type()) ? "" : "_impl_.";
@@ -876,8 +874,6 @@ std::string SafeFunctionName(const Descriptor* descriptor,
bool IsProfileDriven(const Options& options) {
return options.access_info_map != nullptr;
}
-
-
bool IsStringInlined(const FieldDescriptor* descriptor,
const Options& options) {
(void)descriptor;
diff --git a/src/google/protobuf/compiler/cpp/message.cc b/src/google/protobuf/compiler/cpp/message.cc
index 3713b9cf2c..51a3e652f1 100644
--- a/src/google/protobuf/compiler/cpp/message.cc
+++ b/src/google/protobuf/compiler/cpp/message.cc
@@ -828,7 +828,6 @@ void MessageGenerator::GenerateFieldAccessorDeclarations(io::Printer* printer) {
// Generate type-specific accessor declarations.
field_generators_.get(field).GenerateAccessorDeclarations(printer);
-
format("\n");
}
@@ -1238,41 +1237,41 @@ void MessageGenerator::GenerateFieldAccessorDefinitions(io::Printer* printer) {
Formatter::SaveState saver(&format);
format.AddMap(vars);
- // Generate has_$name$() or $name$_size().
- if (field->is_repeated()) {
- if (IsFieldStripped(field, options_)) {
- format(
- "inline int $classname$::$name$_size() const { "
- "__builtin_trap(); }\n");
- } else {
- format(
- "inline int $classname$::_internal_$name$_size() const {\n"
- " return $field$$1$.size();\n"
- "}\n"
- "inline int $classname$::$name$_size() const {\n"
- "$annotate_size$"
- " return _internal_$name$_size();\n"
- "}\n",
- IsImplicitWeakField(field, options_, scc_analyzer_) &&
- field->message_type()
- ? ".weak"
- : "");
- }
- } else if (field->real_containing_oneof()) {
- format.Set("field_name", UnderscoresToCamelCase(field->name(), true));
- format.Set("oneof_name", field->containing_oneof()->name());
- format.Set("oneof_index",
- StrCat(field->containing_oneof()->index()));
- GenerateOneofMemberHasBits(field, format);
+
+ // Generate has_$name$() or $name$_size().
+ if (field->is_repeated()) {
+ if (IsFieldStripped(field, options_)) {
+ format(
+ "inline int $classname$::$name$_size() const { "
+ "__builtin_trap(); }\n");
} else {
- // Singular field.
- GenerateSingularFieldHasBits(field, format);
+ format(
+ "inline int $classname$::_internal_$name$_size() const {\n"
+ " return $field$$1$.size();\n"
+ "}\n"
+ "inline int $classname$::$name$_size() const {\n"
+ "$annotate_size$"
+ " return _internal_$name$_size();\n"
+ "}\n",
+ IsImplicitWeakField(field, options_, scc_analyzer_) &&
+ field->message_type()
+ ? ".weak"
+ : "");
}
+ } else if (field->real_containing_oneof()) {
+ format.Set("field_name", UnderscoresToCamelCase(field->name(), true));
+ format.Set("oneof_name", field->containing_oneof()->name());
+ format.Set("oneof_index",
+ StrCat(field->containing_oneof()->index()));
+ GenerateOneofMemberHasBits(field, format);
+ } else {
+ // Singular field.
+ GenerateSingularFieldHasBits(field, format);
+ }
if (!IsCrossFileMaybeMap(field)) {
GenerateFieldClear(field, true, format);
}
-
// Generate type-specific accessors.
if (!IsFieldStripped(field, options_)) {
field_generators_.get(field).GenerateInlineAccessorDefinitions(printer);
@@ -1760,7 +1759,7 @@ void MessageGenerator::GenerateClassDefinition(io::Printer* printer) {
format(
"private:\n"
"inline bool IsSplitMessageDefault() const {\n"
- " return $split$ == reinterpret_cast(&$1$);\n"
+ " return $split$ == reinterpret_cast(&$1$);\n"
"}\n"
"PROTOBUF_NOINLINE void PrepareSplitMessageForWrite();\n"
"public:\n",
@@ -2423,8 +2422,15 @@ void MessageGenerator::GenerateSharedConstructorCode(io::Printer* printer) {
}
if (ShouldSplit(descriptor_, options_)) {
put_sep();
- format("decltype($split$){reinterpret_cast(&$1$)}",
- DefaultInstanceName(descriptor_, options_, /*split=*/true));
+ // We can't assign the default split to this->split without the const_cast
+ // because the former is a const. The const_cast is safe because we don't
+ // intend to modify the default split through this pointer, and we also
+ // expect the default split to be in the rodata section which is protected
+ // from mutation.
+ format(
+ "decltype($split$){const_cast"
+ "(reinterpret_cast(&$1$))}",
+ DefaultInstanceName(descriptor_, options_, /*split=*/true));
}
for (auto oneof : OneOfRange(descriptor_)) {
put_sep();
@@ -2683,7 +2689,7 @@ void MessageGenerator::GenerateConstexprConstructor(io::Printer* printer) {
}
if (ShouldSplit(descriptor_, options_)) {
put_sep();
- format("/*decltype($split$)*/&$1$._instance",
+ format("/*decltype($split$)*/const_cast(&$1$._instance)",
DefaultInstanceName(descriptor_, options_, /*split=*/true));
}
@@ -2868,8 +2874,10 @@ void MessageGenerator::GenerateStructors(io::Printer* printer) {
}
if (ShouldSplit(descriptor_, options_)) {
put_sep();
- format("decltype($split$){reinterpret_cast(&$1$)}",
- DefaultInstanceName(descriptor_, options_, /*split=*/true));
+ format(
+ "decltype($split$){const_cast"
+ "(reinterpret_cast(&$1$))}",
+ DefaultInstanceName(descriptor_, options_, /*split=*/true));
}
for (auto oneof : OneOfRange(descriptor_)) {
put_sep();
diff --git a/src/google/protobuf/compiler/java/message_serialization.h b/src/google/protobuf/compiler/java/message_serialization.h
index 6145392f81..15dc515a85 100644
--- a/src/google/protobuf/compiler/java/message_serialization.h
+++ b/src/google/protobuf/compiler/java/message_serialization.h
@@ -32,6 +32,7 @@
#define GOOGLE_PROTOBUF_COMPILER_JAVA_MESSAGE_SERIALIZATION_H__
#include
+#include
#include
#include
@@ -66,20 +67,31 @@ void GenerateSerializeFieldsAndExtensions(
std::sort(sorted_extensions.begin(), sorted_extensions.end(),
ExtensionRangeOrdering());
+ std::size_t range_idx = 0;
+
// Merge the fields and the extension ranges, both sorted by field number.
- for (int i = 0, j = 0;
- i < descriptor->field_count() || j < sorted_extensions.size();) {
- if (i == descriptor->field_count()) {
- GenerateSerializeExtensionRange(printer, sorted_extensions[j++]);
- } else if (j == sorted_extensions.size()) {
- field_generators.get(sorted_fields[i++])
- .GenerateSerializationCode(printer);
- } else if (sorted_fields[i]->number() < sorted_extensions[j]->start) {
- field_generators.get(sorted_fields[i++])
- .GenerateSerializationCode(printer);
- } else {
- GenerateSerializeExtensionRange(printer, sorted_extensions[j++]);
+ for (int i = 0; i < descriptor->field_count(); ++i) {
+ const FieldDescriptor* field = sorted_fields[i];
+
+ // Collapse all extension ranges up until the next field. This leads to
+ // shorter and more efficient codegen for messages containing a large
+ // number of extension ranges without fields in between them.
+ const Descriptor::ExtensionRange* range = nullptr;
+ while (range_idx < sorted_extensions.size() &&
+ sorted_extensions[range_idx]->end <= field->number()) {
+ range = sorted_extensions[range_idx++];
+ }
+
+ if (range != nullptr) {
+ GenerateSerializeExtensionRange(printer, range);
}
+ field_generators.get(field).GenerateSerializationCode(printer);
+ }
+
+ // After serializing all fields, serialize any remaining extensions via a
+ // single writeUntil call.
+ if (range_idx < sorted_extensions.size()) {
+ GenerateSerializeExtensionRange(printer, sorted_extensions.back());
}
}
diff --git a/src/google/protobuf/compiler/java/message_serialization_unittest.cc b/src/google/protobuf/compiler/java/message_serialization_unittest.cc
new file mode 100644
index 0000000000..b2e1fcaed8
--- /dev/null
+++ b/src/google/protobuf/compiler/java/message_serialization_unittest.cc
@@ -0,0 +1,124 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 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.
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace java {
+namespace {
+
+using ::testing::ElementsAre;
+
+// Generates Java code for the specified Java proto, returning the compiler's
+// exit status.
+int CompileJavaProto(std::string proto_file_name) {
+ JavaGenerator java_generator;
+
+ CommandLineInterface cli;
+ cli.RegisterGenerator("--java_out", &java_generator, /*help_text=*/"");
+
+ std::string proto_path = StrCat(
+ "--proto_path=",
+ TestUtil::GetTestDataPath("third_party/protobuf/compiler/java"));
+ std::string java_out = StrCat("--java_out=", TestTempDir());
+
+ const char* argv[] = {
+ "protoc",
+ proto_path.c_str(),
+ java_out.c_str(),
+ proto_file_name.c_str(),
+ };
+
+ // Open-source codebase does not support ABSL_ARRAYSIZE.
+ return cli.Run(sizeof(argv) / sizeof(*argv), argv);
+}
+
+TEST(MessageSerializationTest, CollapseAdjacentExtensionRanges) {
+ GOOGLE_CHECK_EQ(CompileJavaProto("message_serialization_unittest.proto"), 0);
+
+ std::string java_source;
+ GOOGLE_CHECK_OK(File::GetContents(
+ // Open-source codebase does not support file::JoinPath, so we manually
+ // concatenate instead.
+ StrCat(TestTempDir(),
+ "/TestMessageWithManyExtensionRanges.java"),
+ &java_source, true));
+
+ // Open-source codebase does not support constexpr StringPiece.
+ static constexpr const char kWriteUntilCall[] = "extensionWriter.writeUntil(";
+
+ std::vector range_ends;
+
+ // Open-source codebase does not have Split overload taking a single
+ // char delimiter.
+ //
+ // NOLINTNEXTLINE(abseil-faster-strsplit-delimiter)
+ for (const auto& line : Split(java_source, "\n")) {
+ // Extract end position from writeUntil call. (Open-source codebase does not
+ // support RE2.)
+ std::size_t write_until_pos = line.find(kWriteUntilCall);
+ if (write_until_pos == std::string::npos) {
+ continue;
+ }
+ write_until_pos += (sizeof(kWriteUntilCall) - 1);
+
+ std::size_t comma_pos = line.find(',', write_until_pos);
+ if (comma_pos == std::string::npos) {
+ continue;
+ }
+
+ range_ends.push_back(
+ std::string(line.substr(write_until_pos, comma_pos - write_until_pos)));
+ }
+
+ EXPECT_THAT(range_ends, ElementsAre("3", "13", "43"));
+}
+
+} // namespace
+} // namespace java
+} // namespace compiler
+} // namespace protobuf
+} // namespace google
diff --git a/src/google/protobuf/compiler/java/message_serialization_unittest.proto b/src/google/protobuf/compiler/java/message_serialization_unittest.proto
new file mode 100644
index 0000000000..9cfdf42b32
--- /dev/null
+++ b/src/google/protobuf/compiler/java/message_serialization_unittest.proto
@@ -0,0 +1,56 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 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.
+
+syntax = "proto2";
+
+package protobuf_unittest;
+
+option java_multiple_files = true;
+option java_package = "";
+
+// Each batch of extension ranges not separated by a non-extension field should
+// be serialized using a single ExtensionWriter#writeUntil call.
+message TestMessageWithManyExtensionRanges {
+ // First extension range: ends at field number 3 (exclusive)
+ extensions 1 to 2;
+
+ optional int32 foo = 3;
+ optional int32 bar = 5;
+
+ // Second extension range: ends at field number 13 (exclusive)
+ extensions 6;
+ extensions 8;
+ extensions 10 to 12;
+
+ optional int32 baz = 23;
+
+ // Third extension range: ends at field number 43 (exclusive)
+ extensions 42;
+}
diff --git a/src/google/protobuf/compiler/python/pyi_generator.cc b/src/google/protobuf/compiler/python/pyi_generator.cc
index 0a28102a70..e954fd317f 100644
--- a/src/google/protobuf/compiler/python/pyi_generator.cc
+++ b/src/google/protobuf/compiler/python/pyi_generator.cc
@@ -44,25 +44,10 @@ namespace protobuf {
namespace compiler {
namespace python {
-template
-struct SortByName {
- bool operator()(const DescriptorT* l, const DescriptorT* r) const {
- return l->name() < r->name();
- }
-};
-
PyiGenerator::PyiGenerator() : file_(nullptr) {}
PyiGenerator::~PyiGenerator() {}
-void PyiGenerator::PrintItemMap(
- const std::map& item_map) const {
- for (const auto& entry : item_map) {
- printer_->Print("$key$: $value$\n", "key", entry.first, "value",
- entry.second);
- }
-}
-
template
std::string PyiGenerator::ModuleLevelName(const DescriptorT& descriptor) const {
std::string name = NamePrefixedWithNestedTypes(descriptor, ".");
@@ -177,8 +162,7 @@ void PyiGenerator::PrintImportForDescriptor(
seen_aliases->insert(alias);
}
-void PyiGenerator::PrintImports(
- std::map* item_map) const {
+void PyiGenerator::PrintImports() const {
// Prints imported dependent _pb2 files.
std::set seen_aliases;
for (int i = 0; i < file_->dependency_count(); ++i) {
@@ -250,7 +234,7 @@ void PyiGenerator::PrintImports(
if (import_modules.has_union) {
printer_->Print(", Union as _Union");
}
- printer_->Print("\n\n");
+ printer_->Print("\n");
// Public imports
for (int i = 0; i < file_->public_dependency_count(); ++i) {
@@ -268,17 +252,8 @@ void PyiGenerator::PrintImports(
module_name, "enum_class",
public_dep->enum_type(i)->name());
}
- // Enum values for public imports
- for (int i = 0; i < public_dep->enum_type_count(); ++i) {
- const EnumDescriptor* enum_descriptor = public_dep->enum_type(i);
- for (int j = 0; j < enum_descriptor->value_count(); ++j) {
- (*item_map)[enum_descriptor->value(j)->name()] =
- ModuleLevelName(*enum_descriptor);
- }
- }
- // Top level extensions for public imports
- AddExtensions(*public_dep, item_map);
}
+printer_->Print("\n");
}
void PyiGenerator::PrintEnum(const EnumDescriptor& enum_descriptor) const {
@@ -289,19 +264,18 @@ void PyiGenerator::PrintEnum(const EnumDescriptor& enum_descriptor) const {
"enum_name", enum_name);
}
-// Adds enum value to item map which will be ordered and printed later.
-void PyiGenerator::AddEnumValue(
- const EnumDescriptor& enum_descriptor,
- std::map* item_map) const {
+void PyiGenerator::PrintEnumValues(
+ const EnumDescriptor& enum_descriptor) const {
// enum values
std::string module_enum_name = ModuleLevelName(enum_descriptor);
for (int j = 0; j < enum_descriptor.value_count(); ++j) {
const EnumValueDescriptor* value_descriptor = enum_descriptor.value(j);
- (*item_map)[value_descriptor->name()] = module_enum_name;
+ printer_->Print("$name$: $module_enum_name$\n",
+ "name", value_descriptor->name(),
+ "module_enum_name", module_enum_name);
}
}
-// Prints top level enums
void PyiGenerator::PrintTopLevelEnums() const {
for (int i = 0; i < file_->enum_type_count(); ++i) {
printer_->Print("\n");
@@ -309,18 +283,16 @@ void PyiGenerator::PrintTopLevelEnums() const {
}
}
-// Add top level extensions to item_map which will be ordered and
-// printed later.
template
-void PyiGenerator::AddExtensions(
- const DescriptorT& descriptor,
- std::map* item_map) const {
+void PyiGenerator::PrintExtensions(const DescriptorT& descriptor) const {
for (int i = 0; i < descriptor.extension_count(); ++i) {
const FieldDescriptor* extension_field = descriptor.extension(i);
std::string constant_name = extension_field->name() + "_FIELD_NUMBER";
ToUpper(&constant_name);
- (*item_map)[constant_name] = "_ClassVar[int]";
- (*item_map)[extension_field->name()] = "_descriptor.FieldDescriptor";
+ printer_->Print("$constant_name$: _ClassVar[int]\n",
+ "constant_name", constant_name);
+ printer_->Print("$name$: _descriptor.FieldDescriptor\n",
+ "name", extension_field->name());
}
}
@@ -383,17 +355,11 @@ void PyiGenerator::PrintMessage(
printer_->Indent();
printer_->Indent();
- std::vector fields;
- fields.reserve(message_descriptor.field_count());
- for (int i = 0; i < message_descriptor.field_count(); ++i) {
- fields.push_back(message_descriptor.field(i));
- }
- std::sort(fields.begin(), fields.end(), SortByName());
-
// Prints slots
printer_->Print("__slots__ = [", "class_name", class_name);
bool first_item = true;
- for (const auto& field_des : fields) {
+ for (int i = 0; i < message_descriptor.field_count(); ++i) {
+ const FieldDescriptor* field_des = message_descriptor.field(i);
if (IsPythonKeyword(field_des->name())) {
continue;
}
@@ -406,48 +372,34 @@ void PyiGenerator::PrintMessage(
}
printer_->Print("]\n");
- std::map item_map;
// Prints Extensions for extendable messages
if (message_descriptor.extension_range_count() > 0) {
- item_map["Extensions"] = "_python_message._ExtensionDict";
+ printer_->Print("Extensions: _python_message._ExtensionDict\n");
}
// Prints nested enums
- std::vector nested_enums;
- nested_enums.reserve(message_descriptor.enum_type_count());
for (int i = 0; i < message_descriptor.enum_type_count(); ++i) {
- nested_enums.push_back(message_descriptor.enum_type(i));
- }
- std::sort(nested_enums.begin(), nested_enums.end(),
- SortByName());
-
- for (const auto& entry : nested_enums) {
- PrintEnum(*entry);
- // Adds enum value to item_map which will be ordered and printed later
- AddEnumValue(*entry, &item_map);
+ PrintEnum(*message_descriptor.enum_type(i));
+ PrintEnumValues(*message_descriptor.enum_type(i));
}
// Prints nested messages
- std::vector nested_messages;
- nested_messages.reserve(message_descriptor.nested_type_count());
for (int i = 0; i < message_descriptor.nested_type_count(); ++i) {
- nested_messages.push_back(message_descriptor.nested_type(i));
+ PrintMessage(*message_descriptor.nested_type(i), true);
}
- std::sort(nested_messages.begin(), nested_messages.end(),
- SortByName());
- for (const auto& entry : nested_messages) {
- PrintMessage(*entry, true);
- }
+ PrintExtensions(message_descriptor);
- // Adds extensions to item_map which will be ordered and printed later
- AddExtensions(message_descriptor, &item_map);
-
- // Adds field number and field descriptor to item_map
+ // Prints field number
+ for (int i = 0; i < message_descriptor.field_count(); ++i) {
+ const FieldDescriptor& field_des = *message_descriptor.field(i);
+ printer_->Print(
+ "$field_number_name$: _ClassVar[int]\n", "field_number_name",
+ ToUpper(field_des.name()) + "_FIELD_NUMBER");
+ }
+ // Prints field name and type
for (int i = 0; i < message_descriptor.field_count(); ++i) {
const FieldDescriptor& field_des = *message_descriptor.field(i);
- item_map[ToUpper(field_des.name()) + "_FIELD_NUMBER"] =
- "_ClassVar[int]";
if (IsPythonKeyword(field_des.name())) {
continue;
}
@@ -473,12 +425,10 @@ void PyiGenerator::PrintMessage(
if (field_des.is_repeated()) {
field_type += "]";
}
- item_map[field_des.name()] = field_type;
+ printer_->Print("$name$: $type$\n",
+ "name", field_des.name(), "type", field_type);
}
- // Prints all items in item_map
- PrintItemMap(item_map);
-
// Prints __init__
printer_->Print("def __init__(self");
bool has_key_words = false;
@@ -548,33 +498,19 @@ void PyiGenerator::PrintMessage(
void PyiGenerator::PrintMessages() const {
// Deterministically order the descriptors.
- std::vector messages;
- messages.reserve(file_->message_type_count());
for (int i = 0; i < file_->message_type_count(); ++i) {
- messages.push_back(file_->message_type(i));
- }
- std::sort(messages.begin(), messages.end(), SortByName());
-
- for (const auto& entry : messages) {
- PrintMessage(*entry, false);
+ PrintMessage(*file_->message_type(i), false);
}
}
void PyiGenerator::PrintServices() const {
- std::vector services;
- services.reserve(file_->service_count());
- for (int i = 0; i < file_->service_count(); ++i) {
- services.push_back(file_->service(i));
- }
- std::sort(services.begin(), services.end(), SortByName());
-
// Prints $Service$ and $Service$_Stub classes
- for (const auto& entry : services) {
+ for (int i = 0; i < file_->service_count(); ++i) {
printer_->Print("\n");
printer_->Print(
"class $service_name$(_service.service): ...\n\n"
"class $service_name$_Stub($service_name$): ...\n",
- "service_name", entry->name());
+ "service_name", file_->service(i)->name());
}
}
@@ -594,25 +530,28 @@ bool PyiGenerator::Generate(const FileDescriptor* file,
io::Printer printer(output.get(), '$');
printer_ = &printer;
- // item map will store "DESCRIPTOR", top level extensions, top level enum
- // values. The items will be sorted and printed later.
- std::map item_map;
+ PrintImports();
+ printer_->Print("DESCRIPTOR: _descriptor.FileDescriptor\n");
- // Adds "DESCRIPTOR" into item_map.
- item_map["DESCRIPTOR"] = "_descriptor.FileDescriptor";
+ // Prints extensions and enums from imports.
+ for (int i = 0; i < file_->public_dependency_count(); ++i) {
+ const FileDescriptor* public_dep = file_->public_dependency(i);
+ PrintExtensions(*public_dep);
+ for (int i = 0; i < public_dep->enum_type_count(); ++i) {
+ const EnumDescriptor* enum_descriptor = public_dep->enum_type(i);
+ PrintEnumValues(*enum_descriptor);
+ }
+ }
- PrintImports(&item_map);
- // Adds top level enum values to item_map.
+ PrintTopLevelEnums();
+ // Prints top level enum values
for (int i = 0; i < file_->enum_type_count(); ++i) {
- AddEnumValue(*file_->enum_type(i), &item_map);
+ PrintEnumValues(*file_->enum_type(i));
}
- // Adds top level extensions to item_map.
- AddExtensions(*file_, &item_map);
- // Prints item map
- PrintItemMap(item_map);
-
+ // Prints top level Extensions
+ PrintExtensions(*file_);
PrintMessages();
- PrintTopLevelEnums();
+
if (HasGenericServices(file)) {
PrintServices();
}
diff --git a/src/google/protobuf/compiler/python/pyi_generator.h b/src/google/protobuf/compiler/python/pyi_generator.h
index 6265593f11..40741e1e78 100644
--- a/src/google/protobuf/compiler/python/pyi_generator.h
+++ b/src/google/protobuf/compiler/python/pyi_generator.h
@@ -77,18 +77,15 @@ class PROTOC_EXPORT PyiGenerator : public google::protobuf::compiler::CodeGenera
private:
void PrintImportForDescriptor(const FileDescriptor& desc,
std::set* seen_aliases) const;
- void PrintImports(std::map* item_map) const;
- void PrintEnum(const EnumDescriptor& enum_descriptor) const;
- void AddEnumValue(const EnumDescriptor& enum_descriptor,
- std::map* item_map) const;
+ void PrintImports() const;
void PrintTopLevelEnums() const;
+ void PrintEnum(const EnumDescriptor& enum_descriptor) const;
+ void PrintEnumValues(const EnumDescriptor& enum_descriptor) const;
template
- void AddExtensions(const DescriptorT& descriptor,
- std::map* item_map) const;
+ void PrintExtensions(const DescriptorT& descriptor) const;
void PrintMessages() const;
void PrintMessage(const Descriptor& message_descriptor, bool is_nested) const;
void PrintServices() const;
- void PrintItemMap(const std::map& item_map) const;
std::string GetFieldType(
const FieldDescriptor& field_des, const Descriptor& containing_des) const;
template
diff --git a/src/google/protobuf/generated_message_tctable_impl.h b/src/google/protobuf/generated_message_tctable_impl.h
index 11ac5baf35..3fad18c7f7 100644
--- a/src/google/protobuf/generated_message_tctable_impl.h
+++ b/src/google/protobuf/generated_message_tctable_impl.h
@@ -38,8 +38,6 @@
#include
#include
#include
-#include
-#include
#include
#include
#include
diff --git a/src/google/protobuf/io/printer.h b/src/google/protobuf/io/printer.h
index e30bfa6908..aa507cb014 100644
--- a/src/google/protobuf/io/printer.h
+++ b/src/google/protobuf/io/printer.h
@@ -251,7 +251,8 @@ class PROTOBUF_EXPORT Printer {
template
void Print(const char* text, const Args&... args) {
std::map vars;
- PrintInternal(&vars, text, args...);
+ FillMap(&vars, args...);
+ Print(vars, text);
}
// Indent text by two spaces. After calling Indent(), two spaces will be
@@ -299,18 +300,13 @@ class PROTOBUF_EXPORT Printer {
void Annotate(const char* begin_varname, const char* end_varname,
const std::string& file_path, const std::vector& path);
- // Base case
- void PrintInternal(std::map* vars,
- const char* text) {
- Print(*vars, text);
- }
+ void FillMap(std::map* vars) {}
template
- void PrintInternal(std::map* vars, const char* text,
- const char* key, const std::string& value,
- const Args&... args) {
+ void FillMap(std::map* vars, const std::string& key,
+ const std::string& value, const Args&... args) {
(*vars)[key] = value;
- PrintInternal(vars, text, args...);
+ FillMap(vars, args...);
}
// Copy size worth of bytes from data to buffer_.
diff --git a/src/google/protobuf/map.h b/src/google/protobuf/map.h
index d0aa010816..a3f79f1545 100644
--- a/src/google/protobuf/map.h
+++ b/src/google/protobuf/map.h
@@ -333,6 +333,11 @@ inline size_t SpaceUsedInValues(const void*) { return 0; }
} // namespace internal
+#ifdef PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
+// This is the class for Map's internal value_type.
+template
+using MapPair = std::pair;
+#else
// This is the class for Map's internal value_type. Instead of using
// std::pair as value_type, we use this class which provides us more control of
// its process of construction and destruction.
@@ -363,6 +368,7 @@ struct PROTOBUF_ATTRIBUTE_STANDALONE_DEBUG MapPair {
friend class Arena;
friend class Map;
};
+#endif
// Map is an associative container type used to store protobuf map
// fields. Each Map instance may or may not use a different hash function, a
diff --git a/src/google/protobuf/map_test.cc b/src/google/protobuf/map_test.cc
index f7c024cd16..fd895246b5 100644
--- a/src/google/protobuf/map_test.cc
+++ b/src/google/protobuf/map_test.cc
@@ -84,3 +84,5 @@ TEST(MapTest, Aligned8OnArena) { MapTest_Aligned(); }
} // namespace internal
} // namespace protobuf
} // namespace google
+
+#include
diff --git a/src/google/protobuf/map_test.inc b/src/google/protobuf/map_test.inc
index 041ac824d4..1d55dd57e6 100644
--- a/src/google/protobuf/map_test.inc
+++ b/src/google/protobuf/map_test.inc
@@ -810,6 +810,8 @@ TEST_F(MapImplTest, Emplace) {
m, UnorderedElementsAre(Pair(1, "one"), Pair(2, "two"), Pair(42, "aaa")));
}
+#ifndef PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
+
TEST_F(MapImplTest, EmplaceKeyOnly) {
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
@@ -824,6 +826,43 @@ TEST_F(MapImplTest, EmplaceKeyOnly) {
EXPECT_THAT(m, UnorderedElementsAre(Pair(1, ""), Pair(42, "")));
}
+#else
+
+TEST_F(MapImplTest, ValueTypeNoImplicitConversion) {
+ using vt = typename Map::value_type;
+
+ EXPECT_FALSE((std::is_convertible<
+ vt, std::pair>>::value));
+}
+
+enum class ConstructorType {
+ kDefault,
+ kCopy,
+ kMove,
+};
+
+struct ConstructorTag {
+ ConstructorTag() : invoked_constructor(ConstructorType::kDefault) {}
+ ConstructorTag(const ConstructorTag&)
+ : invoked_constructor(ConstructorType::kCopy) {}
+ ConstructorTag(ConstructorTag&&)
+ : invoked_constructor(ConstructorType::kMove) {}
+
+ ConstructorType invoked_constructor;
+};
+
+TEST_F(MapImplTest, ValueTypeHasMoveConstructor) {
+ using vt = typename Map::value_type;
+ ConstructorTag l, r;
+
+ vt pair(l, std::move(r));
+
+ EXPECT_EQ(pair.first.invoked_constructor, ConstructorType::kCopy);
+ EXPECT_EQ(pair.second.invoked_constructor, ConstructorType::kMove);
+}
+
+#endif // !PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
+
struct CountedInstance {
CountedInstance() { ++num_created; }
CountedInstance(const CountedInstance&) : CountedInstance() {}
diff --git a/src/google/protobuf/message.cc b/src/google/protobuf/message.cc
index 724a6220f0..1279164619 100644
--- a/src/google/protobuf/message.cc
+++ b/src/google/protobuf/message.cc
@@ -214,7 +214,7 @@ uint64_t Message::GetInvariantPerBuild(uint64_t salt) {
}
namespace internal {
-void* CreateSplitMessageGeneric(Arena* arena, void* default_split,
+void* CreateSplitMessageGeneric(Arena* arena, const void* default_split,
size_t size) {
void* split =
(arena == nullptr) ? ::operator new(size) : arena->AllocateAligned(size);
diff --git a/src/google/protobuf/message.h b/src/google/protobuf/message.h
index 7042a13342..b61fafb333 100644
--- a/src/google/protobuf/message.h
+++ b/src/google/protobuf/message.h
@@ -411,7 +411,8 @@ class PROTOBUF_EXPORT Message : public MessageLite {
namespace internal {
// Creates and returns an allocation for a split message.
-void* CreateSplitMessageGeneric(Arena* arena, void* default_split, size_t size);
+void* CreateSplitMessageGeneric(Arena* arena, const void* default_split,
+ size_t size);
// Forward-declare interfaces used to implement RepeatedFieldRef.
// These are protobuf internals that users shouldn't care about.
diff --git a/src/google/protobuf/port_def.inc b/src/google/protobuf/port_def.inc
index 2805a92aba..94d299d3f0 100644
--- a/src/google/protobuf/port_def.inc
+++ b/src/google/protobuf/port_def.inc
@@ -181,11 +181,15 @@
// Future versions of protobuf will include breaking changes to some APIs.
// This macro can be set to enable these API changes ahead of time, so that
// user code can be updated before upgrading versions of protobuf.
-// PROTOBUF_FUTURE_FINAL is used on classes that are historically not marked as
-// final, but that may be marked final in future (breaking) releases.
#ifdef PROTOBUF_FUTURE_BREAKING_CHANGES
-// Used on classes that are historically not marked as final.
+
+// Used to upgrade google::protobuf::MapPair to std::pair.
+// Owner: mordberg@
+#define PROTOBUF_FUTURE_MAP_PAIR_UPGRADE 1
+
+// Used on classes that are historically not marked as final, but that may be
+// marked final in future (breaking) releases.
// Owner: kfm@
#define PROTOBUF_FUTURE_FINAL final
diff --git a/src/google/protobuf/port_undef.inc b/src/google/protobuf/port_undef.inc
index 92ca8050c1..23eb789d1e 100644
--- a/src/google/protobuf/port_undef.inc
+++ b/src/google/protobuf/port_undef.inc
@@ -113,6 +113,7 @@
#ifdef PROTOBUF_FUTURE_BREAKING_CHANGES
#undef PROTOBUF_FUTURE_BREAKING_CHANGES
+#undef PROTOBUF_FUTURE_MAP_PAIR_UPGRADE
#undef PROTOBUF_FUTURE_REMOVE_DEFAULT_FIELD_COMPARATOR
#undef PROTOBUF_FUTURE_REMOVE_CLEARED_API
#endif
diff --git a/src/google/protobuf/repeated_field.h b/src/google/protobuf/repeated_field.h
index e504274778..b4e441681f 100644
--- a/src/google/protobuf/repeated_field.h
+++ b/src/google/protobuf/repeated_field.h
@@ -233,12 +233,6 @@ class RepeatedField final {
// copies data between each other.
void Swap(RepeatedField* other);
- // Swaps entire contents with "other". Should be called only if the caller can
- // guarantee that both repeated fields are on the same arena or are on the
- // heap. Swapping between different arenas is disallowed and caught by a
- // GOOGLE_DCHECK (see API docs for details).
- void UnsafeArenaSwap(RepeatedField* other);
-
// Swaps two elements.
void SwapElements(int index1, int index2);
@@ -321,6 +315,12 @@ class RepeatedField final {
: rep()->arena;
}
+ // Swaps entire contents with "other". Should be called only if the caller can
+ // guarantee that both repeated fields are on the same arena or are on the
+ // heap. Swapping between different arenas is disallowed and caught by a
+ // GOOGLE_DCHECK (see API docs for details).
+ void UnsafeArenaSwap(RepeatedField* other);
+
static constexpr int kInitialSize = 0;
// A note on the representation here (see also comment below for
// RepeatedPtrFieldBase's struct Rep):
diff --git a/src/google/protobuf/util/json_util_test.cc b/src/google/protobuf/util/json_util_test.cc
index ebe1b604d1..ca9fe731dd 100644
--- a/src/google/protobuf/util/json_util_test.cc
+++ b/src/google/protobuf/util/json_util_test.cc
@@ -307,6 +307,15 @@ TEST_P(JsonTest, Camels) {
EXPECT_THAT(ToJson(m), IsOkAndHolds(R"({"StringField":"sTRINGfIELD"})"));
}
+TEST_P(JsonTest, EvilString) {
+ auto m = ToProto(R"json(
+ {"string_value": ")json"
+ "\n\r\b\f\1\2\3"
+ "\"}");
+ ASSERT_OK(m);
+ EXPECT_EQ(m->string_value(), "\n\r\b\f\1\2\3");
+}
+
TEST_P(JsonTest, TestAlwaysPrintEnumsAsInts) {
TestMessage orig;
orig.set_enum_value(proto3::BAR);
@@ -399,6 +408,7 @@ TEST_P(JsonTest, ParseMessage) {
"repeatedEnumValue": [1, "FOO"],
"repeatedMessageValue": [
{"value": 40},
+ {},
{"value": 96}
]
}
@@ -427,9 +437,10 @@ TEST_P(JsonTest, ParseMessage) {
EXPECT_THAT(m->repeated_string_value(), ElementsAre("foo", "bar ", ""));
EXPECT_THAT(m->repeated_enum_value(), ElementsAre(proto3::BAR, proto3::FOO));
- ASSERT_THAT(m->repeated_message_value(), SizeIs(2));
+ ASSERT_THAT(m->repeated_message_value(), SizeIs(3));
EXPECT_EQ(m->repeated_message_value(0).value(), 40);
- EXPECT_EQ(m->repeated_message_value(1).value(), 96);
+ EXPECT_EQ(m->repeated_message_value(1).value(), 0);
+ EXPECT_EQ(m->repeated_message_value(2).value(), 96);
EXPECT_THAT(
ToJson(*m),
@@ -440,7 +451,7 @@ TEST_P(JsonTest, ParseMessage) {
R"("messageValue":{"value":2048},"repeatedBoolValue":[true],"repeatedInt32Value":[0,-42])"
R"(,"repeatedUint64Value":["1","2"],"repeatedDoubleValue":[1.5,-2],)"
R"("repeatedStringValue":["foo","bar ",""],"repeatedEnumValue":["BAR","FOO"],)"
- R"("repeatedMessageValue":[{"value":40},{"value":96}]})"));
+ R"("repeatedMessageValue":[{"value":40},{},{"value":96}]})"));
}
TEST_P(JsonTest, CurseOfAtob) {
@@ -761,6 +772,24 @@ TEST_P(JsonTest, TestFlatList) {
)json");
ASSERT_OK(m);
EXPECT_THAT(m->repeated_int32_value(), ElementsAre(5, 6));
+
+ // The above flatteing behavior is supressed for google::protobuf::ListValue.
+ auto m2 = ToProto(R"json(
+ {
+ "repeatedInt32Value": [[[5]], [6]]
+ }
+ )json");
+ ASSERT_OK(m2);
+ auto fields = m2->struct_value().fields();
+ auto list = fields["repeatedInt32Value"].list_value();
+ EXPECT_EQ(list.values(0)
+ .list_value()
+ .values(0)
+ .list_value()
+ .values(0)
+ .number_value(),
+ 5);
+ EXPECT_EQ(list.values(1).list_value().values(0).number_value(), 6);
}
TEST_P(JsonTest, ParseWrappers) {
@@ -1095,6 +1124,33 @@ TEST_P(JsonTest, TestLegalNullsInArray) {
ASSERT_THAT(m2->repeated_value(), SizeIs(1));
EXPECT_TRUE(m2->repeated_value(0).has_null_value());
+
+ m2->Clear();
+ m2->mutable_repeated_value(); // Materialize an empty singular Value.
+ m2->add_repeated_value();
+ m2->add_repeated_value()->set_string_value("solitude");
+ m2->add_repeated_value();
+ EXPECT_THAT(ToJson(*m2), IsOkAndHolds(R"({"repeatedValue":["solitude"]})"));
+}
+
+TEST_P(JsonTest, ListList) {
+ auto m = ToProto(R"json({
+ "repeated_value": [["ayy", "lmao"]]
+ })json");
+ ASSERT_OK(m);
+
+ EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy");
+ EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao");
+
+ m = ToProto(R"json({
+ "repeated_value": [{
+ "values": ["ayy", "lmao"]
+ }]
+ })json");
+ ASSERT_OK(m);
+
+ EXPECT_EQ(m->repeated_value(0).values(0).string_value(), "ayy");
+ EXPECT_EQ(m->repeated_value(0).values(1).string_value(), "lmao");
}
TEST_P(JsonTest, HtmlEscape) {