From 22462b0c529b444d065679885a6aaf5cb2514e05 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 3 Nov 2021 10:50:09 +1300 Subject: [PATCH] Fix trim warnings --- Makefile.am | 4 + .../Google.Protobuf.Test.TestProtos.csproj | 4 +- .../Google.Protobuf.Test.csproj | 2 +- .../Google.Protobuf.Test/JsonParserTest.cs | 132 +++++++++++++++++- .../DynamicallyAccessedMemberTypes.cs | 127 +++++++++++++++++ .../DynamicallyAccessedMembersAttribute.cs | 83 +++++++++++ .../RequiresUnreferencedCodeAttribute.cs | 72 ++++++++++ .../Compatibility/TypeExtensions.cs | 13 +- .../UnconditionalSuppressMessageAttribute.cs | 117 ++++++++++++++++ .../Google.Protobuf/Google.Protobuf.csproj | 1 + csharp/src/Google.Protobuf/JsonFormatter.cs | 27 ++-- .../Reflection/CustomOptions.cs | 20 +++ .../Reflection/FieldDescriptor.cs | 2 +- .../Reflection/GeneratedClrTypeInfo.cs | 22 ++- .../Reflection/MessageDescriptor.cs | 2 + .../Reflection/ReflectionUtil.cs | 14 +- .../Reflection/SingleFieldAccessor.cs | 54 +++++-- .../WellKnownTypes/FieldMaskPartial.cs | 13 +- 18 files changed, 660 insertions(+), 49 deletions(-) create mode 100644 csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMemberTypes.cs create mode 100644 csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMembersAttribute.cs create mode 100644 csharp/src/Google.Protobuf/Compatibility/RequiresUnreferencedCodeAttribute.cs create mode 100644 csharp/src/Google.Protobuf/Compatibility/UnconditionalSuppressMessageAttribute.cs diff --git a/Makefile.am b/Makefile.am index a83f9aaf4118..23022f7cad37 100644 --- a/Makefile.am +++ b/Makefile.am @@ -182,10 +182,14 @@ csharp_EXTRA_DIST= \ csharp/src/Google.Protobuf/Collections/ProtobufEqualityComparers.cs \ csharp/src/Google.Protobuf/Collections/ReadOnlyDictionary.cs \ csharp/src/Google.Protobuf/Collections/RepeatedField.cs \ + csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMembersAttribute.cs \ + csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMemberTypes.cs \ csharp/src/Google.Protobuf/Compatibility/MethodInfoExtensions.cs \ csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs \ + csharp/src/Google.Protobuf/Compatibility/RequiresUnreferencedCodeAttribute.cs \ csharp/src/Google.Protobuf/Compatibility/StreamExtensions.cs \ csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs \ + csharp/src/Google.Protobuf/Compatibility/UnconditionalSuppressMessageAttribute.cs \ csharp/src/Google.Protobuf/Extension.cs \ csharp/src/Google.Protobuf/ExtensionRegistry.cs \ csharp/src/Google.Protobuf/ExtensionSet.cs \ diff --git a/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj b/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj index 9f2ba6b0deeb..5030043d760b 100644 --- a/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj +++ b/csharp/src/Google.Protobuf.Test.TestProtos/Google.Protobuf.Test.TestProtos.csproj @@ -1,4 +1,4 @@ - + - + diff --git a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj index 2665cc3d357a..deb17e9f52e4 100644 --- a/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj +++ b/csharp/src/Google.Protobuf.Test/Google.Protobuf.Test.csproj @@ -21,7 +21,7 @@ - + diff --git a/csharp/src/Google.Protobuf.Test/JsonParserTest.cs b/csharp/src/Google.Protobuf.Test/JsonParserTest.cs index e170fcc5a09c..eb8996e620f6 100644 --- a/csharp/src/Google.Protobuf.Test/JsonParserTest.cs +++ b/csharp/src/Google.Protobuf.Test/JsonParserTest.cs @@ -35,6 +35,7 @@ using Google.Protobuf.WellKnownTypes; using NUnit.Framework; using ProtobufTestMessages.Proto2; +using ProtobufTestMessages.Proto3; using System; using UnitTest.Issues.TestProtos; @@ -918,10 +919,10 @@ public void Bytes_InvalidBase64(string badBase64) } [Test] - [TestCase("\"FOREIGN_BAR\"", ForeignEnum.ForeignBar)] - [TestCase("5", ForeignEnum.ForeignBar)] - [TestCase("100", (ForeignEnum)100)] - public void EnumValid(string value, ForeignEnum expectedValue) + [TestCase("\"FOREIGN_BAR\"", TestProtos.ForeignEnum.ForeignBar)] + [TestCase("5", TestProtos.ForeignEnum.ForeignBar)] + [TestCase("100", (TestProtos.ForeignEnum)100)] + public void EnumValid(string value, TestProtos.ForeignEnum expectedValue) { string json = "{ \"singleForeignEnum\": " + value + " }"; var parsed = TestAllTypes.Parser.ParseJson(json); @@ -1021,5 +1022,128 @@ internal static string WrapInQuotes(string text) { return '"' + text + '"'; } + + [Test] + public void ParseAllNullValues() + { + string json = @"{ + ""optionalInt32"": null, + ""optionalInt64"": null, + ""optionalUint32"": null, + ""optionalUint64"": null, + ""optionalSint32"": null, + ""optionalSint64"": null, + ""optionalFixed32"": null, + ""optionalFixed64"": null, + ""optionalSfixed32"": null, + ""optionalSfixed64"": null, + ""optionalFloat"": null, + ""optionalDouble"": null, + ""optionalBool"": null, + ""optionalString"": null, + ""optionalBytes"": null, + ""optionalNestedEnum"": null, + ""optionalNestedMessage"": null, + ""repeatedInt32"": null, + ""repeatedInt64"": null, + ""repeatedUint32"": null, + ""repeatedUint64"": null, + ""repeatedSint32"": null, + ""repeatedSint64"": null, + ""repeatedFixed32"": null, + ""repeatedFixed64"": null, + ""repeatedSfixed32"": null, + ""repeatedSfixed64"": null, + ""repeatedFloat"": null, + ""repeatedDouble"": null, + ""repeatedBool"": null, + ""repeatedString"": null, + ""repeatedBytes"": null, + ""repeatedNestedEnum"": null, + ""repeatedNestedMessage"": null, + ""mapInt32Int32"": null, + ""mapBoolBool"": null, + ""mapStringNestedMessage"": null +}"; + + TestAllTypesProto3 message = new TestAllTypesProto3(); + + message.OptionalInt32 = 1; + message.OptionalInt64 = 1; + message.OptionalUint32 = 1; + message.OptionalUint64 = 1; + message.OptionalSint32 = 1; + message.OptionalSint64 = 1; + message.OptionalFixed32 = 1; + message.OptionalFixed64 = 1; + message.OptionalSfixed32 = 1; + message.OptionalSfixed64 = 1; + message.OptionalFloat = 1; + message.OptionalDouble = 1; + message.OptionalBool = true; + message.OptionalString = "1"; + message.OptionalBytes = ByteString.CopyFrom(new byte[] { 1 }); + message.OptionalNestedEnum = TestAllTypesProto3.Types.NestedEnum.Bar; + message.OptionalNestedMessage = new TestAllTypesProto3.Types.NestedMessage(); + message.RepeatedInt32.Add(1); + message.RepeatedInt64.Add(1); + message.RepeatedUint32.Add(1); + message.RepeatedUint64.Add(1); + message.RepeatedSint32.Add(1); + message.RepeatedSint64.Add(1); + message.RepeatedFixed32.Add(1); + message.RepeatedFixed64.Add(1); + message.RepeatedSfixed32.Add(1); + message.RepeatedSfixed64.Add(1); + message.RepeatedFloat.Add(1); + message.RepeatedDouble.Add(1); + message.RepeatedBool.Add(true); + message.RepeatedString.Add("1"); + message.RepeatedBytes.Add(ByteString.CopyFrom(new byte[] { 1 })); + message.RepeatedNestedEnum.Add(TestAllTypesProto3.Types.NestedEnum.Bar); + message.RepeatedNestedMessage.Add(new TestAllTypesProto3.Types.NestedMessage()); + message.MapInt32Int32.Add(1, 1); + message.MapBoolBool.Add(true, true); + message.MapStringNestedMessage.Add(" ", new TestAllTypesProto3.Types.NestedMessage()); + + JsonParser.Default.Merge(message, json); + + Assert.AreEqual(0, message.OptionalInt32); + Assert.AreEqual(0, message.OptionalInt64); + Assert.AreEqual(0, message.OptionalUint32); + Assert.AreEqual(0, message.OptionalUint64); + Assert.AreEqual(0, message.OptionalSint32); + Assert.AreEqual(0, message.OptionalSint64); + Assert.AreEqual(0, message.OptionalFixed32); + Assert.AreEqual(0, message.OptionalFixed64); + Assert.AreEqual(0, message.OptionalSfixed32); + Assert.AreEqual(0, message.OptionalSfixed64); + Assert.AreEqual(0, message.OptionalFloat); + Assert.AreEqual(0, message.OptionalDouble); + Assert.AreEqual(false, message.OptionalBool); + Assert.AreEqual("", message.OptionalString); + Assert.AreEqual(ByteString.Empty, message.OptionalBytes); + Assert.AreEqual(TestAllTypesProto3.Types.NestedEnum.Foo, message.OptionalNestedEnum); + Assert.AreEqual(null, message.OptionalNestedMessage); + Assert.AreEqual(0, message.RepeatedInt32.Count); + Assert.AreEqual(0, message.RepeatedInt64.Count); + Assert.AreEqual(0, message.RepeatedUint32.Count); + Assert.AreEqual(0, message.RepeatedUint64.Count); + Assert.AreEqual(0, message.RepeatedSint32.Count); + Assert.AreEqual(0, message.RepeatedSint64.Count); + Assert.AreEqual(0, message.RepeatedFixed32.Count); + Assert.AreEqual(0, message.RepeatedFixed64.Count); + Assert.AreEqual(0, message.RepeatedSfixed32.Count); + Assert.AreEqual(0, message.RepeatedFloat.Count); + Assert.AreEqual(0, message.RepeatedDouble.Count); + Assert.AreEqual(0, message.RepeatedBool.Count); + Assert.AreEqual(0, message.RepeatedString.Count); + Assert.AreEqual(0, message.RepeatedBytes.Count); + Assert.AreEqual(0, message.RepeatedNestedEnum.Count); + Assert.AreEqual(0, message.RepeatedNestedMessage.Count); + Assert.AreEqual(0, message.MapInt32Int32.Count); + Assert.AreEqual(0, message.MapBoolBool.Count); + Assert.AreEqual(0, message.MapStringNestedMessage.Count); + } } } \ No newline at end of file diff --git a/csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMemberTypes.cs b/csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMemberTypes.cs new file mode 100644 index 000000000000..a4d739d80298 --- /dev/null +++ b/csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMemberTypes.cs @@ -0,0 +1,127 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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 + +#if !NET5_0_OR_GREATER +// Copied with permission from https://github.com/dotnet/runtime/tree/8fbf206d0e518b45ca855832e8bfb391afa85972/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None + } +} +#endif diff --git a/csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMembersAttribute.cs b/csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMembersAttribute.cs new file mode 100644 index 000000000000..ae0927623474 --- /dev/null +++ b/csharp/src/Google.Protobuf/Compatibility/DynamicallyAccessedMembersAttribute.cs @@ -0,0 +1,83 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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 + +#if !NET5_0_OR_GREATER +// Copied with permission from https://github.com/dotnet/runtime/tree/8fbf206d0e518b45ca855832e8bfb391afa85972/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// When this attribute is applied to a class, interface, or struct, the members specified + /// can be accessed dynamically on instances returned from calling + /// on instances of that class, interface, or struct. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } +} +#endif diff --git a/csharp/src/Google.Protobuf/Compatibility/RequiresUnreferencedCodeAttribute.cs b/csharp/src/Google.Protobuf/Compatibility/RequiresUnreferencedCodeAttribute.cs new file mode 100644 index 000000000000..1fc8e66d49b1 --- /dev/null +++ b/csharp/src/Google.Protobuf/Compatibility/RequiresUnreferencedCodeAttribute.cs @@ -0,0 +1,72 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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 + +#if !NET5_0_OR_GREATER +// Copied with permission from https://github.com/dotnet/runtime/tree/8fbf206d0e518b45ca855832e8bfb391afa85972/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that the specified method requires dynamic access to code that is not referenced + /// statically, for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when removing unreferenced + /// code from an application. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] + internal sealed class RequiresUnreferencedCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of unreferenced code. + /// + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with it. + /// + public string Url { get; set; } + } +} +#endif diff --git a/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs b/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs index 2f23713819f6..b3acda2da766 100644 --- a/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs +++ b/csharp/src/Google.Protobuf/Compatibility/TypeExtensions.cs @@ -31,6 +31,7 @@ #endregion using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; #if !NET35 @@ -59,7 +60,11 @@ internal static bool IsAssignableFrom(this Type target, Type c) /// including inherited properties or null if there is no such public property. /// Here, "public property" means a property where either the getter, or the setter, or both, is public. /// - internal static PropertyInfo GetProperty(this Type target, string name) + [UnconditionalSuppressMessage("Trimming", "IL2072", + Justification = "The BaseType of the target will have all properties because of the annotation.")] + internal static PropertyInfo GetProperty( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + this Type target, string name) { // GetDeclaredProperty only returns properties declared in the given type, so we need to recurse. while (target != null) @@ -86,7 +91,11 @@ internal static PropertyInfo GetProperty(this Type target, string name) /// class Child : Base declares public void Foo(long)). /// /// One type in the hierarchy declared more than one method with the same name - internal static MethodInfo GetMethod(this Type target, string name) + [UnconditionalSuppressMessage("Trimming", "IL2072", + Justification = "The BaseType of the target will have all properties because of the annotation.")] + internal static MethodInfo GetMethod( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] + this Type target, string name) { // GetDeclaredMethod only returns methods declared in the given type, so we need to recurse. while (target != null) diff --git a/csharp/src/Google.Protobuf/Compatibility/UnconditionalSuppressMessageAttribute.cs b/csharp/src/Google.Protobuf/Compatibility/UnconditionalSuppressMessageAttribute.cs new file mode 100644 index 000000000000..a02a1453ebb3 --- /dev/null +++ b/csharp/src/Google.Protobuf/Compatibility/UnconditionalSuppressMessageAttribute.cs @@ -0,0 +1,117 @@ +#region Copyright notice and license +// Protocol Buffers - Google's data interchange format +// Copyright 2015 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 + +#if !NET5_0_OR_GREATER +// Copied with permission from https://github.com/dotnet/runtime/tree/8fbf206d0e518b45ca855832e8bfb391afa85972/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Suppresses reporting of a specific rule violation, allowing multiple suppressions on a + /// single code artifact. + /// + /// + /// is different than + /// in that it doesn't have a + /// . So it is always preserved in the compiled assembly. + /// + [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] + internal sealed class UnconditionalSuppressMessageAttribute : Attribute + { + /// + /// Initializes a new instance of the + /// class, specifying the category of the tool and the identifier for an analysis rule. + /// + /// The category for the attribute. + /// The identifier of the analysis rule the attribute applies to. + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + /// + /// The property describes the tool or tool analysis category + /// for which a message suppression attribute applies. + /// + public string Category { get; } + + /// + /// Gets the identifier of the analysis tool rule to be suppressed. + /// + /// + /// Concatenated together, the and + /// properties form a unique check identifier. + /// + public string CheckId { get; } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + /// + /// The Scope property is an optional argument that specifies the metadata scope for which + /// the attribute is relevant. + /// + public string Scope { get; set; } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + /// + /// The property is an optional argument identifying the analysis target + /// of the attribute. An example value is "System.IO.Stream.ctor():System.Void". + /// Because it is fully qualified, it can be long, particularly for targets such as parameters. + /// The analysis tool user interface should be capable of automatically formatting the parameter. + /// + public string Target { get; set; } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + /// + /// The property is an optional argument that specifies additional + /// exclusion where the literal metadata target is not sufficiently precise. For example, + /// the cannot be applied within a method, + /// and it may be desirable to suppress a violation against a statement in the method that will + /// give a rule violation, but not against all statements in the method. + /// + public string MessageId { get; set; } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string Justification { get; set; } + } +} +#endif diff --git a/csharp/src/Google.Protobuf/Google.Protobuf.csproj b/csharp/src/Google.Protobuf/Google.Protobuf.csproj index f40e444de9d5..928915085630 100644 --- a/csharp/src/Google.Protobuf/Google.Protobuf.csproj +++ b/csharp/src/Google.Protobuf/Google.Protobuf.csproj @@ -22,6 +22,7 @@ true $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + true diff --git a/csharp/src/Google.Protobuf/JsonFormatter.cs b/csharp/src/Google.Protobuf/JsonFormatter.cs index 4bffd58c1f40..17fdc7f73866 100644 --- a/csharp/src/Google.Protobuf/JsonFormatter.cs +++ b/csharp/src/Google.Protobuf/JsonFormatter.cs @@ -40,6 +40,7 @@ using System.Linq; using System.Collections.Generic; using System.Reflection; +using System.Diagnostics.CodeAnalysis; namespace Google.Protobuf { @@ -879,6 +880,8 @@ private static class OriginalEnumValueHelper private static readonly Dictionary> dictionaries = new Dictionary>(); + [UnconditionalSuppressMessage("Trimming", "IL2072", + Justification = "The field for the value must still be present. It will be returned by reflection, will be in this collection, and its name can be resolved.")] internal static string GetOriginalName(object value) { var enumType = value.GetType(); @@ -898,21 +901,13 @@ internal static string GetOriginalName(object value) return originalName; } -#if NET35 - // TODO: Consider adding functionality to TypeExtensions to avoid this difference. - private static Dictionary GetNameMapping(System.Type enumType) => - enumType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static) - .Where(f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false) - .FirstOrDefault() as OriginalNameAttribute) - ?.PreferredAlias ?? true) - .ToDictionary(f => f.GetValue(null), - f => (f.GetCustomAttributes(typeof(OriginalNameAttribute), false) - .FirstOrDefault() as OriginalNameAttribute) - // If the attribute hasn't been applied, fall back to the name of the field. - ?.Name ?? f.Name); -#else - private static Dictionary GetNameMapping(System.Type enumType) => - enumType.GetTypeInfo().DeclaredFields + private static Dictionary GetNameMapping( + [DynamicallyAccessedMembers( + DynamicallyAccessedMemberTypes.PublicFields | + DynamicallyAccessedMemberTypes.NonPublicFields)] + System.Type enumType) + { + return enumType.GetTypeInfo().DeclaredFields .Where(f => f.IsStatic) .Where(f => f.GetCustomAttributes() .FirstOrDefault()?.PreferredAlias ?? true) @@ -921,7 +916,7 @@ internal static string GetOriginalName(object value) .FirstOrDefault() // If the attribute hasn't been applied, fall back to the name of the field. ?.Name ?? f.Name); -#endif + } } } } diff --git a/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs b/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs index 9fc3766889b8..f6fa1522ba6c 100644 --- a/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs +++ b/csharp/src/Google.Protobuf/Reflection/CustomOptions.cs @@ -34,6 +34,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; @@ -63,6 +64,8 @@ namespace Google.Protobuf.Reflection /// public sealed class CustomOptions { + private const string UnreferencedCodeMessage = "CustomOptions is incompatible with trimming."; + private static readonly object[] EmptyParameters = new object[0]; private readonly IDictionary values; @@ -77,6 +80,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetBool(int field, out bool value) => TryGetPrimitiveValue(field, out value); /// @@ -85,6 +89,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetInt32(int field, out int value) => TryGetPrimitiveValue(field, out value); /// @@ -93,6 +98,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetInt64(int field, out long value) => TryGetPrimitiveValue(field, out value); /// @@ -102,6 +108,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetFixed32(int field, out uint value) => TryGetUInt32(field, out value); /// @@ -111,6 +118,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetFixed64(int field, out ulong value) => TryGetUInt64(field, out value); /// @@ -120,6 +128,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetSFixed32(int field, out int value) => TryGetInt32(field, out value); /// @@ -129,6 +138,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetSFixed64(int field, out long value) => TryGetInt64(field, out value); /// @@ -138,6 +148,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetSInt32(int field, out int value) => TryGetPrimitiveValue(field, out value); /// @@ -147,6 +158,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetSInt64(int field, out long value) => TryGetPrimitiveValue(field, out value); /// @@ -155,6 +167,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetUInt32(int field, out uint value) => TryGetPrimitiveValue(field, out value); /// @@ -163,6 +176,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetUInt64(int field, out ulong value) => TryGetPrimitiveValue(field, out value); /// @@ -171,6 +185,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetFloat(int field, out float value) => TryGetPrimitiveValue(field, out value); /// @@ -179,6 +194,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetDouble(int field, out double value) => TryGetPrimitiveValue(field, out value); /// @@ -187,6 +203,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetString(int field, out string value) => TryGetPrimitiveValue(field, out value); /// @@ -195,6 +212,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetBytes(int field, out ByteString value) => TryGetPrimitiveValue(field, out value); /// @@ -203,6 +221,7 @@ internal CustomOptions(IDictionary values) /// The field to fetch the value for. /// The output variable to populate. /// true if a suitable value for the field was found; false otherwise. + [RequiresUnreferencedCode(UnreferencedCodeMessage)] public bool TryGetMessage(int field, out T value) where T : class, IMessage, new() { if (values == null) @@ -240,6 +259,7 @@ public bool TryGetMessage(int field, out T value) where T : class, IMessage, return false; } + [RequiresUnreferencedCode(UnreferencedCodeMessage)] private bool TryGetPrimitiveValue(int field, out T value) { if (values == null) diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs index 0ba16cf96f20..f9b90619adb5 100644 --- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs @@ -452,7 +452,7 @@ private IFieldAccessor CreateAccessor() } return IsMap ? new MapFieldAccessor(property, this) : IsRepeated ? new RepeatedFieldAccessor(property, this) - : (IFieldAccessor) new SingleFieldAccessor(property, this); + : (IFieldAccessor) new SingleFieldAccessor(ContainingType.ClrType, property, this); } } } diff --git a/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs b/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs index 479e177130fc..d0a495b851b5 100644 --- a/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs +++ b/csharp/src/Google.Protobuf/Reflection/GeneratedClrTypeInfo.cs @@ -30,6 +30,7 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #endregion using System; +using System.Diagnostics.CodeAnalysis; namespace Google.Protobuf.Reflection { @@ -43,10 +44,19 @@ public sealed class GeneratedClrTypeInfo private static readonly string[] EmptyNames = new string[0]; private static readonly GeneratedClrTypeInfo[] EmptyCodeInfo = new GeneratedClrTypeInfo[0]; private static readonly Extension[] EmptyExtensions = new Extension[0]; + internal const DynamicallyAccessedMemberTypes MessageAccessibility = + // Creating types + DynamicallyAccessedMemberTypes.PublicConstructors | + // Getting and setting properties + DynamicallyAccessedMemberTypes.PublicProperties | + DynamicallyAccessedMemberTypes.NonPublicProperties | + // Calling presence methods + DynamicallyAccessedMemberTypes.PublicMethods; /// /// Irrelevant for file descriptors; the CLR type for the message for message descriptors. /// + [DynamicallyAccessedMembers(MessageAccessibility)] public Type ClrType { get; private set; } /// @@ -88,7 +98,11 @@ public sealed class GeneratedClrTypeInfo /// Each array parameter may be null, to indicate a lack of values. /// The parameter order is designed to make it feasible to format the generated code readably. /// - public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, Extension[] extensions, GeneratedClrTypeInfo[] nestedTypes) + public GeneratedClrTypeInfo( + // Preserve all public members on message types when trimming is enabled. + // This ensures that members used by reflection, e.g. JSON serialization, are preserved. + [DynamicallyAccessedMembers(MessageAccessibility)] + Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, Extension[] extensions, GeneratedClrTypeInfo[] nestedTypes) { NestedTypes = nestedTypes ?? EmptyCodeInfo; NestedEnums = nestedEnums ?? ReflectionUtil.EmptyTypes; @@ -104,7 +118,11 @@ public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propert /// Each array parameter may be null, to indicate a lack of values. /// The parameter order is designed to make it feasible to format the generated code readably. /// - public GeneratedClrTypeInfo(Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, GeneratedClrTypeInfo[] nestedTypes) + public GeneratedClrTypeInfo( + // Preserve all public members on message types when trimming is enabled. + // This ensures that members used by reflection, e.g. JSON serialization, are preserved. + [DynamicallyAccessedMembers(MessageAccessibility)] + Type clrType, MessageParser parser, string[] propertyNames, string[] oneofNames, Type[] nestedEnums, GeneratedClrTypeInfo[] nestedTypes) : this(clrType, parser, propertyNames, oneofNames, nestedEnums, null, nestedTypes) { } diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs index 7b5ab2fb48e7..f56dd893b8b2 100644 --- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs +++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs @@ -33,6 +33,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; #if NET35 @@ -182,6 +183,7 @@ internal bool IsExtensionsInitialized(IMessage message) /// a wrapper type, and handle the result appropriately. /// /// + [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)] public Type ClrType { get; } /// diff --git a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs index fd1ad4e3e086..73efcc256621 100644 --- a/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs +++ b/csharp/src/Google.Protobuf/Reflection/ReflectionUtil.cs @@ -32,6 +32,7 @@ using Google.Protobuf.Compatibility; using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace Google.Protobuf.Reflection @@ -115,13 +116,15 @@ static ReflectionUtil() internal static Func CreateFuncIMessageBool(MethodInfo method) => GetReflectionHelper(method.DeclaringType, method.ReturnType).CreateFuncIMessageBool(method); - internal static Func CreateIsInitializedCaller(Type msg) => + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] + internal static Func CreateIsInitializedCaller([DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)]Type msg) => ((IExtensionSetReflector)Activator.CreateInstance(typeof(ExtensionSetReflector<>).MakeGenericType(msg))).CreateIsInitializedCaller(); /// /// Creates a delegate which will execute the given method after casting the first argument to /// the type that declares the method, and the second argument to the first parameter type of the method. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] internal static IExtensionReflectionHelper CreateExtensionHelper(Extension extension) => (IExtensionReflectionHelper)Activator.CreateInstance(typeof(ExtensionReflectionHelper<,>).MakeGenericType(extension.TargetType, extension.GetType().GenericTypeArguments[1]), extension); @@ -131,6 +134,7 @@ static ReflectionUtil() /// they can be garbage collected. We could cache them by type if that proves to be important, but creating /// an object is pretty cheap. /// + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Type parameter members are preserved with DynamicallyAccessedMembers on GeneratedClrTypeInfo.ctor clrType parameter.")] private static IReflectionHelper GetReflectionHelper(Type t1, Type t2) => (IReflectionHelper) Activator.CreateInstance(typeof(ReflectionHelper<,>).MakeGenericType(t1, t2)); @@ -308,16 +312,14 @@ public void ClearExtension(IMessage message) } } - private class ExtensionSetReflector : IExtensionSetReflector where T1 : IExtendableMessage + private class ExtensionSetReflector< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] + T1> : IExtensionSetReflector where T1 : IExtendableMessage { public Func CreateIsInitializedCaller() { var prop = typeof(T1).GetTypeInfo().GetDeclaredProperty("_Extensions"); -#if NET35 - var getFunc = (Func>)prop.GetGetMethod(true).CreateDelegate(typeof(Func>)); -#else var getFunc = (Func>)prop.GetMethod.CreateDelegate(typeof(Func>)); -#endif var initializedFunc = (Func, bool>) typeof(ExtensionSet) .GetTypeInfo() diff --git a/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs b/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs index 07d84d7fb914..ac35e729f7b3 100644 --- a/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs +++ b/csharp/src/Google.Protobuf/Reflection/SingleFieldAccessor.cs @@ -31,6 +31,8 @@ #endregion using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Google.Protobuf.Compatibility; @@ -50,7 +52,9 @@ internal sealed class SingleFieldAccessor : FieldAccessorBase private readonly Action clearDelegate; private readonly Func hasDelegate; - internal SingleFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor) + internal SingleFieldAccessor( + [DynamicallyAccessedMembers(GeneratedClrTypeInfo.MessageAccessibility)] + Type messageType, PropertyInfo property, FieldDescriptor descriptor) : base(property, descriptor) { if (!property.CanWrite) { @@ -87,13 +91,13 @@ internal SingleFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) // Primitive fields always support presence in proto2, and support presence in proto3 for optional fields. else if (descriptor.File.Syntax == Syntax.Proto2 || descriptor.Proto.Proto3Optional) { - MethodInfo hasMethod = property.DeclaringType.GetRuntimeProperty("Has" + property.Name).GetMethod; + MethodInfo hasMethod = messageType.GetRuntimeProperty("Has" + property.Name).GetMethod; if (hasMethod == null) { throw new ArgumentException("Not all required properties/methods are available"); } hasDelegate = ReflectionUtil.CreateFuncIMessageBool(hasMethod); - MethodInfo clearMethod = property.DeclaringType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes); + MethodInfo clearMethod = messageType.GetRuntimeMethod("Clear" + property.Name, ReflectionUtil.EmptyTypes); if (clearMethod == null) { throw new ArgumentException("Not all required properties/methods are available"); @@ -107,16 +111,48 @@ internal SingleFieldAccessor(PropertyInfo property, FieldDescriptor descriptor) hasDelegate = message => { throw new InvalidOperationException("Presence is not implemented for this field"); }; // While presence isn't supported, clearing still is; it's just setting to a default value. - var clrType = property.PropertyType; - - object defaultValue = - clrType == typeof(string) ? "" - : clrType == typeof(ByteString) ? ByteString.Empty - : Activator.CreateInstance(clrType); + object defaultValue = GetDefaultValue(descriptor); clearDelegate = message => SetValue(message, defaultValue); } } + private static object GetDefaultValue(FieldDescriptor descriptor) + { + switch (descriptor.FieldType) + { + case FieldType.Bool: + return false; + case FieldType.Bytes: + return ByteString.Empty; + case FieldType.String: + return ""; + case FieldType.Double: + return 0.0; + case FieldType.SInt32: + case FieldType.Int32: + case FieldType.SFixed32: + case FieldType.Enum: + return 0; + case FieldType.Fixed32: + case FieldType.UInt32: + return (uint)0; + case FieldType.Fixed64: + case FieldType.UInt64: + return 0UL; + case FieldType.SFixed64: + case FieldType.Int64: + case FieldType.SInt64: + return 0L; + case FieldType.Float: + return 0f; + case FieldType.Message: + case FieldType.Group: // Never expect to get this, but... + return null; + default: + throw new ArgumentException("Invalid field type"); + } + } + public override void Clear(IMessage message) => clearDelegate(message); public override bool HasValue(IMessage message) => hasDelegate(message); public override void SetValue(IMessage message, object value) => setValueDelegate(message, value); diff --git a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs index 91d2ee9094ab..58a33cb6d44f 100644 --- a/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs +++ b/csharp/src/Google.Protobuf/WellKnownTypes/FieldMaskPartial.cs @@ -33,6 +33,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Google.Protobuf.Reflection; @@ -115,7 +116,7 @@ public static FieldMask FromString(string value) /// Parses from a string to a FieldMask and validates all field paths. /// /// The type to validate the field paths against. - public static FieldMask FromString(string value) where T : IMessage + public static FieldMask FromString<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(string value) where T : IMessage { return FromStringEnumerable(new List(value.Split(FIELD_PATH_SEPARATOR))); } @@ -124,7 +125,7 @@ public static FieldMask FromString(string value) /// Constructs a FieldMask for a list of field paths in a certain type. /// /// The type to validate the field paths against. - public static FieldMask FromStringEnumerable(IEnumerable paths) where T : IMessage + public static FieldMask FromStringEnumerable<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(IEnumerable paths) where T : IMessage { var mask = new FieldMask(); foreach (var path in paths) @@ -151,7 +152,7 @@ public static FieldMask FromString(string value) /// Constructs a FieldMask from the passed field numbers. /// /// The type to validate the field paths against. - public static FieldMask FromFieldNumbers(params int[] fieldNumbers) where T : IMessage + public static FieldMask FromFieldNumbers<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(params int[] fieldNumbers) where T : IMessage { return FromFieldNumbers((IEnumerable)fieldNumbers); } @@ -160,7 +161,7 @@ public static FieldMask FromString(string value) /// Constructs a FieldMask from the passed field numbers. /// /// The type to validate the field paths against. - public static FieldMask FromFieldNumbers(IEnumerable fieldNumbers) where T : IMessage + public static FieldMask FromFieldNumbers<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(IEnumerable fieldNumbers) where T : IMessage { var descriptor = Activator.CreateInstance().Descriptor; @@ -208,7 +209,7 @@ private static bool IsPathValid(string input) /// Checks whether paths in a given fields mask are valid. /// /// The type to validate the field paths against. - public static bool IsValid(FieldMask fieldMask) where T : IMessage + public static bool IsValid<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(FieldMask fieldMask) where T : IMessage { var descriptor = Activator.CreateInstance().Descriptor; @@ -235,7 +236,7 @@ public static bool IsValid(MessageDescriptor descriptor, FieldMask fieldMask) /// Checks whether a given field path is valid. /// /// The type to validate the field paths against. - public static bool IsValid(string path) where T : IMessage + public static bool IsValid<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>(string path) where T : IMessage { var descriptor = Activator.CreateInstance().Descriptor;