Skip to content

Commit 67a48ec

Browse files
[release/6.0-rc2] XmlSerializer support for IsDynamicCodeSupported=false (#59507)
* 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 * dotnet/runtimelab#593 * dotnet/runtimelab#600 Fix #59167 * Fix a bug in XmlSerializer.CanDeserialize when in ReflectionOnly mode. * Port UAP code for CanDeserialize * PR feedback * Add a linker test to ensure linker option '--enable-opt sealer' works when IsDynamicCodeSupported==false. Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
1 parent 157d591 commit 67a48ec

File tree

3 files changed

+72
-2
lines changed

3 files changed

+72
-2
lines changed

src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,10 @@ public XmlSerializer(XmlTypeMapping xmlTypeMapping)
194194
if (xmlTypeMapping == null)
195195
throw new ArgumentNullException(nameof(xmlTypeMapping));
196196

197-
_tempAssembly = GenerateTempAssembly(xmlTypeMapping);
197+
if (Mode != SerializationMode.ReflectionOnly)
198+
{
199+
_tempAssembly = GenerateTempAssembly(xmlTypeMapping);
200+
}
198201
_mapping = xmlTypeMapping;
199202
}
200203

@@ -218,6 +221,12 @@ public XmlSerializer(Type type, string? defaultNamespace)
218221
_primitiveType = type;
219222
return;
220223
}
224+
225+
if (Mode == SerializationMode.ReflectionOnly)
226+
{
227+
return;
228+
}
229+
221230
_tempAssembly = s_cache[defaultNamespace, type];
222231
if (_tempAssembly == null)
223232
{
@@ -270,7 +279,10 @@ public XmlSerializer(Type type, XmlAttributeOverrides? overrides, Type[]? extraT
270279
DefaultNamespace = defaultNamespace;
271280
_rootType = type;
272281
_mapping = GenerateXmlTypeMapping(type, overrides, extraTypes, root, defaultNamespace);
273-
_tempAssembly = GenerateTempAssembly(_mapping, type, defaultNamespace, location);
282+
if (Mode != SerializationMode.ReflectionOnly)
283+
{
284+
_tempAssembly = GenerateTempAssembly(_mapping, type, defaultNamespace, location);
285+
}
274286
}
275287

276288
[RequiresUnreferencedCode("calls ImportTypeMapping")]
@@ -530,6 +542,14 @@ public virtual bool CanDeserialize(XmlReader xmlReader)
530542
TypeDesc typeDesc = (TypeDesc)TypeScope.PrimtiveTypes[_primitiveType]!;
531543
return xmlReader.IsStartElement(typeDesc.DataType!.Name!, string.Empty);
532544
}
545+
else if (ShouldUseReflectionBasedSerialization(_mapping) || _isReflectionBasedSerializer)
546+
{
547+
// If we should use reflection, we will try to do reflection-based deserialization, without fallback.
548+
// Don't check xmlReader.IsStartElement to avoid having to duplicate SOAP deserialization logic here.
549+
// It is better to return an incorrect 'true', which will throw during Deserialize than to return an
550+
// incorrect 'false', and the caller won't even try to Deserialize when it would succeed.
551+
return true;
552+
}
533553
else if (_tempAssembly != null)
534554
{
535555
return _tempAssembly.CanRead(_mapping, xmlReader);

src/libraries/System.Private.Xml/tests/TrimmingTests/System.Private.Xml.TrimmingTests.proj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
<ItemGroup>
55
<TestConsoleAppSourceFiles Include="XmlSchema.Write.cs" />
66
<TestConsoleAppSourceFiles Include="XmlSerializer.Deserialize.cs" />
7+
<TestConsoleAppSourceFiles Include="XmlSerializer.Deserialize.SealerOpt.cs"
8+
ExtraTrimmerArgs="--enable-opt sealer" />
79
<TestConsoleAppSourceFiles Include="XmlSerializer.Serialize.cs" />
810
<TestConsoleAppSourceFiles Include="XslCompiledTransformTests.cs" />
911
</ItemGroup>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.IO;
6+
using System.Reflection;
7+
8+
namespace System.Xml.Serialization.TrimmingTests
9+
{
10+
/// <summary>
11+
/// Tests that using XmlSerializer with linker option '--enable-opt sealer' works
12+
/// when IsDynamicCodeSupported==false.
13+
/// </summary>
14+
internal class Program
15+
{
16+
// Preserve these types until XmlSerializer is fully trim-safe.
17+
// see https://github.com/dotnet/runtime/issues/44768
18+
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(Response))]
19+
public static int Main()
20+
{
21+
// simulate IsDynamicCodeSupported==false by setting the SerializationMode to ReflectionOnly
22+
const int ReflectionOnly = 1;
23+
typeof(XmlSerializer).GetField("s_mode", BindingFlags.NonPublic | BindingFlags.Static)
24+
.SetValue(null, ReflectionOnly);
25+
26+
using StringReader stringReader = new StringReader(@"<?xml version=""1.0"" encoding=""UTF-8""?>
27+
<Response DataType=""Data"">
28+
</Response>");
29+
30+
Response obj = (Response)new XmlSerializer(typeof(Response)).Deserialize(stringReader);
31+
if (obj.DataType == "Data")
32+
{
33+
return 100;
34+
}
35+
36+
return -1;
37+
}
38+
}
39+
40+
[Serializable]
41+
[XmlType(AnonymousType = true)]
42+
[XmlRoot(Namespace = "", IsNullable = false)]
43+
public class Response
44+
{
45+
[XmlAttribute]
46+
public string DataType { get; set; }
47+
}
48+
}

0 commit comments

Comments
 (0)