Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature switch to disable DataSet XML serialization #107713

Merged
merged 2 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/workflow/trimming/feature-switches.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| VerifyDependencyInjectionOpenGenericServiceTrimmability | Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability | When set to true, DependencyInjection will verify trimming annotations applied to open generic services are correct. |
| _AggressiveAttributeTrimming | System.AggressiveAttributeTrimming | When set to true, aggressively trims attributes to allow for the most size savings possible, even if it could result in runtime behavior changes |
| _ComObjectDescriptorSupport | System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported | When set to true, supports creating a TypeDescriptor based view of COM objects. |
| _DataSetXmlSerializationSupport | System.Data.DataSet.XmlSerializationSupport | When set to false, DataSet implementation of IXmlSerializable will throw instead of using trim-incompatible XML serialization. |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have some process to decide if something is a public or non-public switch? The "has been disabled in the app configuration" in the exception message sounds like something that we might want to leave configurable. If someone turns it on, my understanding is they'll get trimming warnings, so it should be fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's true that there will be trim warnings if someone turns it on. I made this one non-public because I don't think there's much value in toggling it in a non-trimmed app - it feels more like a behavior that should be automatically disabled when trimming, but is otherwise not super interesting.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My question is more whether someone could have been using it with TrimMode=partial and wants to re-enable it. (My assumption is that this doesn't reflect on our IsTrimmable assemblies, but on user code.)

We do document BuiltInComInteropSupport and enabling that one is a lot more scarier. (I always wondered why we did that.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider that an unsupported scenario where we provide an undocumented escape hatch - in that case I think it's fine for the escape hatch to be non-public. Maybe BuiltInComInteropSupport should have been non-public too.

Curious what you think - should it be public? Or should the exception message say something else?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm questioning this because this might be the first feature switch PR that I'm tagged on adding a non-public switch. We have others that feel in the same category and are not underscored (JsonSerializerIsReflectionEnabledByDefault comes to mind). Enabling them with full trimming will likely break the app but the story might be different with partial trimming.

It comes down to whether we would consider this supported with partial trimming (to the extent that we say it's supported for JsonSerializerIsReflectionEnabledByDefault, EnableCppCLIHostActivation, EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization, etc.).

dotnet/docs#41739 (comment) touched on that a bit but I think we'll want to have some better guidelines around this.

I don't feel strongly however. I'm not a fan of warning-disabled partial trimming, but we have appmodels that still set it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bringing it up - I think it warrants further discussion. I left some thoughts in dotnet/docs#41739 (comment).

I'll leave this one non-public here per my comments, but if we reach a different agreement in that discussion I'll go back and make it public.

| _DefaultValueAttributeSupport | System.ComponentModel.DefaultValueAttribute.IsSupported | When set to true, supports creating a DefaultValueAttribute at runtime. |
| _DesignerHostSupport | System.ComponentModel.Design.IDesignerHost.IsSupported | When set to true, supports creating design components at runtime. |
| _EnableConsumingManagedCodeFromNativeHosting | System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting | Getting a managed function from native hosting is disabled when set to false and related functionality can be trimmed. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,4 +527,5 @@
<data name="DataSetLinq_CannotCompareDeletedRow" xml:space="preserve"><value>The DataRowComparer does not work with DataRows that have been deleted since it only compares current values.</value></data>
<data name="DataSetLinq_CannotLoadDeletedRow" xml:space="preserve"><value>The source contains a deleted DataRow that cannot be copied to the DataTable.</value></data>
<data name="DataSetLinq_NonNullableCast" xml:space="preserve"><value>Cannot cast DBNull. Value to type '{0}'. Please use a nullable type.</value></data>
<data name="DataSet_XmlSerializationUnsupported" xml:space="preserve"><value>DataSet implementation of IXmlSerializable is not trim compatible and has been disabled in the app configuration.</value></data>
</root>
27 changes: 21 additions & 6 deletions src/libraries/System.Data.Common/src/System/Data/DataSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ public class DataSet : MarshalByValueComponent, IListSource, IXmlSerializable, I
internal bool _useDataSetSchemaOnly; // UseDataSetSchemaOnly , for YUKON
internal bool _udtIsWrapped; // if UDT is wrapped , for YUKON

[FeatureSwitchDefinition("System.Data.DataSet.XmlSerializationSupport")]
[FeatureGuard(typeof(RequiresUnreferencedCodeAttribute))]
#pragma warning disable IL4000
internal static bool XmlSerializationSupport => AppContext.TryGetSwitch("System.Data.DataSet.XmlSerializationSupport", out bool isSupported) ? isSupported : true;
sbomer marked this conversation as resolved.
Show resolved Hide resolved
#pragma warning restore IL4000

/// <summary>
/// Initializes a new instance of the <see cref='System.Data.DataSet'/> class.
/// </summary>
Expand Down Expand Up @@ -3459,6 +3465,11 @@ public static XmlSchemaComplexType GetDataSetSchema(XmlSchemaSet? schemaSet)

XmlSchema? IXmlSerializable.GetSchema()
{
if (!XmlSerializationSupport)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}

if (GetType() == typeof(DataSet))
{
return null;
Expand All @@ -3469,9 +3480,7 @@ public static XmlSchemaComplexType GetDataSetSchema(XmlSchemaSet? schemaSet)
XmlWriter writer = new XmlTextWriter(stream, null);
if (writer != null)
{
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
sbomer marked this conversation as resolved.
Show resolved Hide resolved
WriteXmlSchema(this, writer);
#pragma warning restore IL2026
}
stream.Position = 0;
return XmlSchema.Read(new XmlTextReader(stream), null);
Expand All @@ -3485,6 +3494,11 @@ private static void WriteXmlSchema(DataSet ds, XmlWriter writer)

void IXmlSerializable.ReadXml(XmlReader reader)
{
if (!XmlSerializationSupport)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}

bool fNormalization = true;
XmlTextReader? xmlTextReader = null;
IXmlTextParser? xmlTextParser = reader as IXmlTextParser;
Expand All @@ -3503,9 +3517,7 @@ void IXmlSerializable.ReadXml(XmlReader reader)
}
}

#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
ReadXmlSerializableInternal(reader);
#pragma warning restore IL2026

if (xmlTextParser != null)
{
Expand All @@ -3525,9 +3537,12 @@ private void ReadXmlSerializableInternal(XmlReader reader)

void IXmlSerializable.WriteXml(XmlWriter writer)
{
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
if (!XmlSerializationSupport)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}

WriteXmlInternal(writer);
#pragma warning restore IL2026
}

[RequiresUnreferencedCode("DataSet.WriteXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")]
Expand Down
26 changes: 19 additions & 7 deletions src/libraries/System.Data.Common/src/System/Data/DataTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6664,9 +6664,15 @@ public static XmlSchemaComplexType GetDataTableSchema(XmlSchemaSet? schemaSet)
return type;
}

#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
XmlSchema? IXmlSerializable.GetSchema() => GetXmlSchema();
#pragma warning restore IL2026
XmlSchema? IXmlSerializable.GetSchema()
{
if (!DataSet.XmlSerializationSupport)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}

return GetXmlSchema();
}

[RequiresUnreferencedCode(DataSet.RequiresUnreferencedCodeMessage)]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2046:UnrecognizedReflectionPattern",
Expand All @@ -6693,16 +6699,19 @@ public static XmlSchemaComplexType GetDataTableSchema(XmlSchemaSet? schemaSet)

void IXmlSerializable.ReadXml(XmlReader reader)
{
if (!DataSet.XmlSerializationSupport)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}

IXmlTextParser? textReader = reader as IXmlTextParser;
bool fNormalization = true;
if (textReader != null)
{
fNormalization = textReader.Normalized;
textReader.Normalized = false;
}
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
ReadXmlSerializableInternal(reader);
#pragma warning restore IL2026

if (textReader != null)
{
Expand All @@ -6718,9 +6727,12 @@ private void ReadXmlSerializableInternal(XmlReader reader)

void IXmlSerializable.WriteXml(XmlWriter writer)
{
#pragma warning disable IL2026 // suppressed in ILLink.Suppressions.LibraryBuild.xml
if (!DataSet.XmlSerializationSupport)
{
throw new NotSupportedException(SR.DataSet_XmlSerializationUnsupported);
}

WriteXmlInternal(writer);
#pragma warning restore IL2026
}

[RequiresUnreferencedCode("DataTable.WriteXml uses XmlSerialization underneath which is not trimming safe. Members from serialized types may be trimmed if not referenced directly.")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Data;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace DataSetXmlSerializationTrimmingTests
{
class Program
{
static int Main(string[] args)
{
IXmlSerializable xmlSerializable = new DataSet();

// Calling GetSchema should throw NotSupportedException
try
{
xmlSerializable.GetSchema();
return -1;
}
catch (NotSupportedException)
{
}

// Calling ReadXml should throw NotSupportedException
try
{
xmlSerializable.ReadXml(null);
return -2;
}
catch (NotSupportedException)
{
}

// Calling WriteXml should throw NotSupportedException
try
{
xmlSerializable.WriteXml(null);
return -3;
}
catch (NotSupportedException)
{
}

xmlSerializable = new DataTable();

// Calling GetSchema should throw NotSupportedException
try
{
xmlSerializable.GetSchema();
return -4;
}
catch (NotSupportedException)
{
}

// Calling ReadXml should throw NotSupportedException
try
{
xmlSerializable.ReadXml(null);
return -5;
}
catch (NotSupportedException)
{
}

// Calling WriteXml should throw NotSupportedException
try
{
xmlSerializable.WriteXml(null);
return -6;
}
catch (NotSupportedException)
{
}

return 100;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
<ItemGroup>
<TestConsoleAppSourceFiles Include="CreateSqlXmlReader.cs" />
<TestConsoleAppSourceFiles Include="DbConnectionStringBuilder.cs" />
<TestConsoleAppSourceFiles Include="DataSetXmlSerialization.cs" >
<DisabledFeatureSwitches>System.Data.DataSet.XmlSerializationSupport</DisabledFeatureSwitches>
</TestConsoleAppSourceFiles>
</ItemGroup>

<Import Project="$([MSBuild]::GetPathOfFileAbove(Directory.Build.targets))" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Copyright (c) .NET Foundation. All rights reserved.
<_DefaultValueAttributeSupport Condition="'$(_DefaultValueAttributeSupport)' == ''">false</_DefaultValueAttributeSupport>
<DynamicCodeSupport Condition="'$(DynamicCodeSupport)' == ''">true</DynamicCodeSupport>
<UseSystemResourceKeys Condition="'$(UseSystemResourceKeys)' == ''">false</UseSystemResourceKeys>
<_DataSetXmlSerializationSupport Condition="'$(_DataSetXmlSerializationSupport)' == ''">false</_DataSetXmlSerializationSupport>
</PropertyGroup>


Expand Down
Loading