Skip to content

Commit

Permalink
Annotate System.Data.Common with RequiresDynamicCode (#73199)
Browse files Browse the repository at this point in the history
Annotate System.Data.Common for AOT
Add AOT safe path for nullable field execution
Add additional test for non nullable enum
  • Loading branch information
tlakollo authored Aug 4, 2022
1 parent 6c6d5d7 commit dd735c0
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<NoWarn>$(NoWarn);SYSLIB0038</NoWarn>
<EnableAOTAnalyzer>true</EnableAOTAnalyzer>
</PropertyGroup>
<ItemGroup>
<Compile Include="System.Data.Common.TypeForwards.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

namespace System.Data
{
Expand Down Expand Up @@ -141,31 +142,56 @@ public static void SetField<T>(this DataRow row, DataColumn column, T? value)

private static class UnboxT<T>
{
internal static readonly Converter<object, T?> s_unbox = Create();
internal static readonly Func<object, T?> s_unbox = Create();

private static Converter<object, T?> Create()
private static Func<object, T?> Create()
{
if (typeof(T).IsValueType)
if (typeof(T).IsValueType && default(T) == null)
{
return typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>)
? (Converter<object, T>)Delegate.CreateDelegate(
typeof(Converter<object, T>),
typeof(UnboxT<T>)
.GetMethod("NullableField", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!
.MakeGenericMethod(typeof(T).GetGenericArguments()[0]))
: ValueField;
if (!RuntimeFeature.IsDynamicCodeSupported)
return NullableFieldUsingReflection;

#pragma warning disable IL3050 // There is a path that is safe for AOT executed when IsDynamicCodeSupported is false.
return typeof(UnboxT<T>)
.GetMethod("NullableField", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)!
.MakeGenericMethod(Nullable.GetUnderlyingType(typeof(T))!)
.CreateDelegate<Func<object, T>>();
#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
}
return NonNullableField;
}

return ReferenceField;
private static T? NonNullableField(object value)
{
if (value == DBNull.Value)
{
if (default(T) is null)
return default;
throw DataSetUtil.InvalidCast(SR.Format(SR.DataSetLinq_NonNullableCast, typeof(T)));
}
return (T)value;
}

private static T? ReferenceField(object value)
=> value == DBNull.Value ? default : (T)value;
private static T? NullableFieldUsingReflection(object value)
{
if (value == DBNull.Value)
return default;

private static T ValueField(object value)
=> value == DBNull.Value
? throw DataSetUtil.InvalidCast(SR.Format(SR.DataSetLinq_NonNullableCast, typeof(T)))
: (T)value;
// Try regular cast first
if (value is T t)
return t;

Type valueType = value.GetType();
Type nullableType = Nullable.GetUnderlyingType(typeof(T))!;

// Convert does all sorts of conversions. We are only interested in conversions for enums.
Type fromType = valueType.IsEnum ? Enum.GetUnderlyingType(valueType) : valueType;
Type toType = nullableType.IsEnum ? Enum.GetUnderlyingType(nullableType) : nullableType;
if (fromType == toType)
value = nullableType.IsEnum ? Enum.ToObject(nullableType, value) : Convert.ChangeType(value, nullableType, null);

return (T)value;
}

private static Nullable<TElem> NullableField<TElem>(object value) where TElem : struct
=> value == DBNull.Value ? default : new Nullable<TElem>((TElem)value);
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Data.Common/src/System/Data/DataTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3516,6 +3516,8 @@ record = NewRecord(-1);

// Prevent inlining so that reflection calls are not moved to caller that may be in a different assembly that may have a different grant set.
[MethodImpl(MethodImplOptions.NoInlining)]
[UnconditionalSuppressMessage("AOT analysis", "IL3050:RequiresDynamicCode",
Justification = "Array.CreateInstance operates over a reference type making the call safe for AOT")]
protected internal DataRow[] NewRowArray(int size)
{
if (IsTypedDataTable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@ public void Field_ColumnVersion_NullColumnThrows()
AssertExtensions.Throws<ArgumentNullException>("column", () => DataRowExtensions.Field<int>(row, column: null, version: DataRowVersion.Default));
}

[Fact]
public void Field_NonNullable_Enum()
{
DataTable table = new DataTable("test");
table.Columns.Add(new DataColumn("col", typeof(int)));
DataRow row = table.NewRow();
row["col"] = 0;
table.Rows.Add(row);

Assert.Equal(SomeEnum.Foo, table.Rows[0].Field<SomeEnum>("col"));
}

[Fact]
public void Field_Nullable_Enum()
{
Expand Down

0 comments on commit dd735c0

Please sign in to comment.