From 9a82ac3acefa89d122a9278f1521e4f3a189c1c5 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Mon, 20 Sep 2021 19:05:28 -0500 Subject: [PATCH 1/5] XmlSerializer support for IsDynamicCodeSupported=false Add more checks to XmlSerializer to check the SerializationMode. Don't try to use Reflection.Emit if the SerializationMode is ReflectionOnly. These changes were ported from * https://github.com/dotnet/runtimelab/pull/593 * https://github.com/dotnet/runtimelab/pull/600 Fix #59167 --- .../System/Xml/Serialization/XmlSerializer.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs index 15d78f2896ae62..3f52bddda86b29 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @@ -194,7 +194,10 @@ public XmlSerializer(XmlTypeMapping xmlTypeMapping) if (xmlTypeMapping == null) throw new ArgumentNullException(nameof(xmlTypeMapping)); - _tempAssembly = GenerateTempAssembly(xmlTypeMapping); + if (Mode != SerializationMode.ReflectionOnly) + { + _tempAssembly = GenerateTempAssembly(xmlTypeMapping); + } _mapping = xmlTypeMapping; } @@ -218,6 +221,12 @@ public XmlSerializer(Type type, string? defaultNamespace) _primitiveType = type; return; } + + if (Mode == SerializationMode.ReflectionOnly) + { + return; + } + _tempAssembly = s_cache[defaultNamespace, type]; if (_tempAssembly == null) { @@ -270,7 +279,10 @@ public XmlSerializer(Type type, XmlAttributeOverrides? overrides, Type[]? extraT DefaultNamespace = defaultNamespace; _rootType = type; _mapping = GenerateXmlTypeMapping(type, overrides, extraTypes, root, defaultNamespace); - _tempAssembly = GenerateTempAssembly(_mapping, type, defaultNamespace, location); + if (Mode != SerializationMode.ReflectionOnly) + { + _tempAssembly = GenerateTempAssembly(_mapping, type, defaultNamespace, location); + } } [RequiresUnreferencedCode("calls ImportTypeMapping")] From ccb3aa055b2b5e22af2cf2929b535bddeeb57f0a Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 21 Sep 2021 15:28:07 -0500 Subject: [PATCH 2/5] Fix a bug in XmlSerializer.CanDeserialize when in ReflectionOnly mode. --- .../src/System/Xml/Serialization/XmlSerializer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs index 3f52bddda86b29..e7f3e244f58554 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @@ -546,6 +546,16 @@ public virtual bool CanDeserialize(XmlReader xmlReader) { return _tempAssembly.CanRead(_mapping, xmlReader); } + else if (ShouldUseReflectionBasedSerialization(_mapping) || _isReflectionBasedSerializer) + { + XmlMapping mapping = GetMapping(); + ElementAccessor element = mapping.Accessor; + string elementNamespace = element.Form == XmlSchemaForm.Qualified ? element.Namespace! : string.Empty; + + return mapping.IsReadable && + mapping.GenerateSerializer && + xmlReader.IsStartElement(element.Name, elementNamespace); + } else { return false; From cdd1d1460a439977bc30c0236bef1ef27e89a04f Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Tue, 21 Sep 2021 18:36:12 -0500 Subject: [PATCH 3/5] Port UAP code for CanDeserialize --- .../src/System/Xml/Serialization/XmlSerializer.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs index e7f3e244f58554..7f8e7cfab522b0 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @@ -548,13 +548,7 @@ public virtual bool CanDeserialize(XmlReader xmlReader) } else if (ShouldUseReflectionBasedSerialization(_mapping) || _isReflectionBasedSerializer) { - XmlMapping mapping = GetMapping(); - ElementAccessor element = mapping.Accessor; - string elementNamespace = element.Form == XmlSchemaForm.Qualified ? element.Namespace! : string.Empty; - - return mapping.IsReadable && - mapping.GenerateSerializer && - xmlReader.IsStartElement(element.Name, elementNamespace); + return ReflectionMethodEnabled; } else { From f1b0cac71f39aa139e69cda3f7dd7fffc32153b7 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 22 Sep 2021 11:41:17 -0500 Subject: [PATCH 4/5] PR feedback --- .../src/System/Xml/Serialization/XmlSerializer.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs index 7f8e7cfab522b0..20f9d18343cbb8 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @@ -542,13 +542,17 @@ public virtual bool CanDeserialize(XmlReader xmlReader) TypeDesc typeDesc = (TypeDesc)TypeScope.PrimtiveTypes[_primitiveType]!; return xmlReader.IsStartElement(typeDesc.DataType!.Name!, string.Empty); } - else if (_tempAssembly != null) + else if (ShouldUseReflectionBasedSerialization(_mapping) || _isReflectionBasedSerializer) { - return _tempAssembly.CanRead(_mapping, xmlReader); + // If we should use reflection, we will try to do reflection-based deserialization, without fallback. + // Don't check xmlReader.IsStartElement to avoid having to duplicate SOAP deserialization logic here. + // It is better to return an incorrect 'true', which will throw during Deserialize than to return an + // incorrect 'false', and the caller won't even try to Deserialize when it would succeed. + return true; } - else if (ShouldUseReflectionBasedSerialization(_mapping) || _isReflectionBasedSerializer) + else if (_tempAssembly != null) { - return ReflectionMethodEnabled; + return _tempAssembly.CanRead(_mapping, xmlReader); } else { From d6fb135e4318302d3d18039a514a73b7ce0bb742 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 22 Sep 2021 11:56:20 -0500 Subject: [PATCH 5/5] Add a linker test to ensure linker option '--enable-opt sealer' works when IsDynamicCodeSupported==false. --- .../System.Private.Xml.TrimmingTests.proj | 2 + .../XmlSerializer.Deserialize.SealerOpt.cs | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/libraries/System.Private.Xml/tests/TrimmingTests/XmlSerializer.Deserialize.SealerOpt.cs diff --git a/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj b/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj index d7a0e73f68f5b8..181be44826a6a6 100644 --- a/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj +++ b/src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj @@ -4,6 +4,8 @@ + diff --git a/src/libraries/System.Private.Xml/tests/TrimmingTests/XmlSerializer.Deserialize.SealerOpt.cs b/src/libraries/System.Private.Xml/tests/TrimmingTests/XmlSerializer.Deserialize.SealerOpt.cs new file mode 100644 index 00000000000000..11a5b5e91827f4 --- /dev/null +++ b/src/libraries/System.Private.Xml/tests/TrimmingTests/XmlSerializer.Deserialize.SealerOpt.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reflection; + +namespace System.Xml.Serialization.TrimmingTests +{ + /// + /// Tests that using XmlSerializer with linker option '--enable-opt sealer' works + /// when IsDynamicCodeSupported==false. + /// + internal class Program + { + // Preserve these types until XmlSerializer is fully trim-safe. + // see https://github.com/dotnet/runtime/issues/44768 + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Response))] + public static int Main() + { + // simulate IsDynamicCodeSupported==false by setting the SerializationMode to ReflectionOnly + const int ReflectionOnly = 1; + typeof(XmlSerializer).GetField("s_mode", BindingFlags.NonPublic | BindingFlags.Static) + .SetValue(null, ReflectionOnly); + + using StringReader stringReader = new StringReader(@" + + "); + + Response obj = (Response)new XmlSerializer(typeof(Response)).Deserialize(stringReader); + if (obj.DataType == "Data") + { + return 100; + } + + return -1; + } + } + + [Serializable] + [XmlType(AnonymousType = true)] + [XmlRoot(Namespace = "", IsNullable = false)] + public class Response + { + [XmlAttribute] + public string DataType { get; set; } + } +}