Repository navigation
XmlSerializer has different behavior when deserializing an empty XML tag to a collection when DynamicCodeSupport is false #107252
Copy link
Copy link
Closed
Labels
area-Serializationin-prThere is an active PR which will close this issue when it is mergedThere is an active PR which will close this issue when it is mergedos-iosApple iOSApple iOS
Milestone
Description
Description
As the title says, deserializing an empty XML tag to a collection gives different behavior when DynamicCodeSupport feature switch is enabled or disabled.
Considering the following code of a dotnet new console app:
using System.Xml.Serialization;
using System.Diagnostics.CodeAnalysis;
Test.TestSerialization("<MyClass></MyClass>");
class Test
{
public static XmlSerializer GetSerializer([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type sourceType)
{
return new XmlSerializer(sourceType);
}
public static void TestSerialization(string input)
{
string msg = string.Empty;
var serializer = GetSerializer(typeof(MyClass));
using (StringReader reader = new StringReader(input))
{
MyClass? result = (MyClass?)serializer.Deserialize(reader);
if (result!.Items is null)
{
msg += "Items is null";
}
else
{
msg += $"Items has {result.Items.Count} elements";
}
}
Console.WriteLine(msg);
}
}
public class MyClass
{
public List<string>? Items { [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(List<>))] get; set; }
}- When built/run with
-p:DynamicCodeSupport=truegives:
ivanpovazan@EONE-MAC-ARM64 ~/tmp/net8/CoreCLRXmlSerializer $ rm -rf bin obj ; dotnet run -p:DynamicCodeSupport=true
Items has 0 elements
- When built/run with
-p:DynamicCodeSupport=falsegives:
ivanpovazan@EONE-MAC-ARM64 ~/tmp/net8/CoreCLRXmlSerializer $ rm -rf bin obj ; dotnet run -p:DynamicCodeSupport=false
Items is null
- Same behavior is observed with NativeAOT (which has
DynamicCodeSupport=falseset by default) giving:
ivanpovazan@EONE-MAC-ARM64 ~/tmp/net8/CoreCLRXmlSerializer $ rm -rf bin obj ; dotnet publish -r osx-arm64 -p:PublishAot=true
Determining projects to restore...
Restored /Users/ivan/tmp/net8/CoreCLRXmlSerializer/CoreCLRXmlSerializer.csproj (in 1.02 sec).
/Users/ivan/tmp/net8/CoreCLRXmlSerializer/Program.cs(20,41): warning IL2026: Using member 'System.Xml.Serialization.XmlSerializer.Deserialize(TextReader)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Members from deserialized types may be trimmed if not referenced directly. [/Users/ivan/tmp/net8/CoreCLRXmlSerializer/CoreCLRXmlSerializer.csproj]
/Users/ivan/tmp/net8/CoreCLRXmlSerializer/Program.cs(11,16): warning IL2026: Using member 'System.Xml.Serialization.XmlSerializer.XmlSerializer(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Members from serialized types may be trimmed if not referenced directly. [/Users/ivan/tmp/net8/CoreCLRXmlSerializer/CoreCLRXmlSerializer.csproj]
CoreCLRXmlSerializer -> /Users/ivan/tmp/net8/CoreCLRXmlSerializer/bin/Release/net8.0/osx-arm64/CoreCLRXmlSerializer.dll
Generating native code
/Users/ivan/tmp/net8/CoreCLRXmlSerializer/Program.cs(20): Trim analysis warning IL2026: Test.TestSerialization(String): Using member 'System.Xml.Serialization.XmlSerializer.Deserialize(TextReader)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Members from deserialized types may be trimmed if not referenced directly. [/Users/ivan/tmp/net8/CoreCLRXmlSerializer/CoreCLRXmlSerializer.csproj]
/Users/ivan/tmp/net8/CoreCLRXmlSerializer/Program.cs(11): Trim analysis warning IL2026: Test.GetSerializer(Type): Using member 'System.Xml.Serialization.XmlSerializer.XmlSerializer(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Members from serialized types may be trimmed if not referenced directly. [/Users/ivan/tmp/net8/CoreCLRXmlSerializer/CoreCLRXmlSerializer.csproj]
/Users/ivan/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.8/framework/System.Private.Xml.dll : warning IL3053: Assembly 'System.Private.Xml' produced AOT analysis warnings. [/Users/ivan/tmp/net8/CoreCLRXmlSerializer/CoreCLRXmlSerializer.csproj]
CoreCLRXmlSerializer -> /Users/ivan/tmp/net8/CoreCLRXmlSerializer/bin/Release/net8.0/osx-arm64/publish/
ivanpovazan@EONE-MAC-ARM64 ~/tmp/net8/CoreCLRXmlSerializer $ bin/Release/net8.0/osx-arm64/publish/CoreCLRXmlSerializer
Items is null
- More importantly, this specifically impacts mobile device scenarios with Mono where:
-
DynamicCodeSupport=true:
- when JIT is available, which covers:
- debug/release builds for .NET Android or .NET MAUI Android apps on both devices and simulators
- when AOT is required and interpreter enabled, which covers:
- debug builds for .NET MAUI iOS apps on both devices and simulators (hot reload enabled by default)
- when JIT is available, which covers:
-
DynamicCodeSupport=false:
- when AOT is required and interpreter disabled, which covers:
- release builds for .NET iOS or .NET MAUI iOS apps on both devices and simulators
- debug builds for .NET iOS apps on both devices and simulators (hot reload disabled by default)
- when AOT is required and interpreter disabled, which covers:
-
Additional notes
Switching DynamicCodeSupport on and off changes the serialization mode, where when:
DynamicCodeSupport=trueserialization mode isReflectionAsBackupDynamicCodeSupport=falseserialization mode isReflectionOnly
This was introduced in: b3ab2eb
I haven't looked deeper than this, but if I am not mistaken ReflectionAsBackup uses RefEmit which could be producing the different result.
Workaround
As mentioned in my comment one workaround could be to add an initalizer on problematic type members.
For reference: MAUI repro for mobile
Click to expand a repro example for a MAUI app
This is observable in .NET9 as well (tested with net9-p7).
- Create a new MAUI app:
- Exchange
MainPage.xaml.cswith:
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
namespace MauiXmlSerial;
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void OnCounterClicked(object sender, EventArgs e)
{
string msg;
string xml = "<MyClass></MyClass>";
XmlSerializer serializer = new XmlSerializer(typeof(MyClass));
using (StringReader reader = new StringReader(xml))
{
MyClass result = (MyClass)serializer.Deserialize(reader);
if (result.Items == null)
{
msg = "Items is null";
}
else
{
msg = $"Items have {result.Items.Count} elements";
}
}
CounterBtn.Text = msg;
SemanticScreenReader.Announce(CounterBtn.Text);
}
}
public class MyClass
{
public List<string> Items { get; set; }
}- Build/run on iOS simulator with:
dotnet build -f net9.0-ios -t:Run
- Click the button on the startup page and observe that it shows:
Items have 0 elements - Build/run on a iOS device with:
dotnet build -c Release -f net9.0-ios -r ios-arm64 -t:Run
- Click the button on the startup page and observe that it shows:
Items is null
Reacted by Sangeetha Sabu and Neil Malcolm
Metadata
Metadata
Assignees
Labels
area-Serializationin-prThere is an active PR which will close this issue when it is mergedThere is an active PR which will close this issue when it is mergedos-iosApple iOSApple iOS
Activity
dotnet-policy-service commentedon Sep 2, 2024
Tagging subscribers to 'os-ios': @vitek-karas, @kotlarmilos, @ivanpovazan, @steveisok, @akoeplinger
See info in area-owners.md if you want to be subscribed.
ivanpovazan commentedon Sep 5, 2024
Just reporting some findings on a possible workaround.
Adding a property initializer like:
makes the behavior on iOS devices match iOS simulators - deserializing into an empty collection.
[-][mono][ios] iOS device and simulator have different behavior deserializing an empty XML tag to a collection in .NET8[/-][+]XmlSerializer has different behavior when deserializing an empty XML tag to a collection when `DynamicCodeSupport` is false[/+]dotnet-policy-service commentedon Sep 6, 2024
Tagging subscribers to this area: @dotnet/area-system-xml
See info in area-owners.md if you want to be subscribed.
ivanpovazan commentedon Sep 6, 2024
/cc: @vitek-karas @MichalStrehovsky
MichalStrehovsky commentedon Sep 6, 2024
Why do we set
DynamicCodeSupport=truein iOS simulator, butDynamicCodeSupport=falsein iOS physical device?18 remaining items
ivanpovazan commentedon Oct 22, 2024
Thank you for checking it out.
The problem is you are using
9.0.100-preview.6.24328.19which unfortunately had an issue with resolvingUseInterpretercorrectly - and got fixed in: dotnet/macios@261a603The fix became available in the next release (preview 7) and that is the reason I couldn't reproduce.
Could you please try updating to a newer .NET version and try again?
PS You might need to add
<SupportedOSPlatformVersion>17.2</SupportedOSPlatformVersion>to your project file once you update to a newer version of .NETarlm commentedon Oct 24, 2024
I installed the
9.0.100-rc.2.24474.11and have tried both<SupportedOSPlatformVersion>17.2</SupportedOSPlatformVersion>and<SupportedOSPlatformVersion>16.0</SupportedOSPlatformVersion>and both worked fine for me.I have noticed that if I start the app by running from the scripts on my reproduction project it works fine, and I have no error both for the serialization and for the deserialization problems. But if I add
<UseInterpreter>true</UseInterpreter>to the project and start it using Visual Studio for Mac, it does not work and both fail. Do you have any idea why?ivanpovazan commentedon Oct 25, 2024
I am glad it worked.
Regarding:
What I think happens is that VS for Mac does not support .NET 9 SDK, and that it picks up your latest .NET 8 installation instead (which is
8.0.204from what you posted above). So I would try installing the latest -8.0.403version and try again.arlm commentedon Oct 25, 2024
I have installed the
8.0.403version and uninstalled all the older versions. I still have the same result when running from Visual Studio.I also added a
global.jsonconfiguration file and tried running the fix with the8.0.403version, and it does not work. Should I expect some kind of fix for that flag on a future version of .NET 8 SDK ?ivanpovazan commentedon Oct 25, 2024
Does it work when built from CLI?
Can you generate binlog and share it https://github.com/xamarin/xamarin-macios/wiki/Diagnosis ?
arlm commentedon Oct 28, 2024
Yes, I changed on the
global.jsonconfiguration file to the8.0.403version and the fix does not work on this circumstance.Here is the binlog, as requested: msbuild.binlog.zip
ivanpovazan commentedon Oct 29, 2024
From the binlog I can see you have an older version of iOS workloads
17.2.8078which is causing the problem.To have all the necessary bits you will need to use newer workloads version.
There are two options:
16.018.0.*which can be accomplished by running:Or if you don't want to upgrade to Xcode
16.015.417.5.*which can be accomplished by running:arlm commentedon Oct 30, 2024
Hi @ivanpovazan,
I have tested the option with Xcode
15.4with the following workloads:And the following
global.jsonfile:{ "sdk": { "version": "8.0.403", "rollForward": "latestFeature" } }It worked perfectly on the deviceby running from the CLI, but Visual Studio could not load them.
ivanpovazan commentedon Oct 30, 2024
I am glad you managed to resolve it.
Regarding the issue on Visual Studio for Mac - the IDE has reached EOL and is no longer maintained or supported (source), so it is possible that new workloads (or SDK) are not compatible. Alternative solutions for developing on Mac is to use VS Code and .NET MAUI extension.
arlm commentedon Oct 30, 2024
OK, I tried that, but as our app is not MAUI I could not use it to debug it.
Now that we have a working workaround, which are the plans to have a fix on .NET 9 that does not need the flag/property to be set to true on the project or on the command-line? Shouldn't it be the default value of this property for native/non-MAUI iOS apps?
ivanpovazan commentedon Oct 30, 2024
I am afraid I cannot answer that, as both of the reported issues (this and #108432) are XmlSerializer-specific and out of my scope. Maybe @HongGit could provide an answer.