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

[AOT] Add RequiresDynamicCode annotation to Microsoft.AspNetCore.Routing #45580

Merged
merged 4 commits into from
Jan 3, 2023
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
4 changes: 4 additions & 0 deletions src/Components/Components/src/Reflection/PropertySetter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ internal sealed class PropertySetter
"ReflectionAnalysis",
"IL2060:MakeGenericMethod",
Justification = "The referenced methods don't have any DynamicallyAccessedMembers annotations. See https://github.com/mono/linker/issues/1727")]
[SuppressMessage(
"AOT",
"IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
Justification = "The referenced methods are AOT safe with reference types.")]
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this suppression is valid. We won't ever call this class with a property of type int, double, or Guid, or any other Value Type?

Instead, if we want to make this AOT-compatible, we will need to do something like this:

https://github.com/dotnet/runtime/blob/66b11d7f2c2b25fc3e6685b02f016ef2f9f8c64f/src/libraries/System.Diagnostics.DiagnosticSource/src/System/Diagnostics/DiagnosticSourceEventSource.cs#L1378-L1391

Basically - check if !RuntimeFeature.IsDynamicCodeSupported. When it isn't, and one of the types is a ValueType, then fallback to "slower" reflection based property setting that boxes.

public PropertySetter(Type targetType, PropertyInfo property)
{
if (property.SetMethod == null)
Expand Down
3 changes: 3 additions & 0 deletions src/Http/Routing/src/Matching/ILEmitTrieFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#nullable disable

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
Expand All @@ -12,6 +13,7 @@

namespace Microsoft.AspNetCore.Routing.Matching;

[RequiresDynamicCode("ILEmitTrieFactory uses runtime IL generation.")]
internal static class ILEmitTrieFactory
{
// The algorthm we use only works for ASCII text. If we find non-ASCII text in the input
Expand Down Expand Up @@ -477,6 +479,7 @@ private sealed class Labels
public Label ReturnNotAscii { get; set; }
}

[RequiresDynamicCode("ILEmitTrieFactory uses runtime IL generation.")]
private sealed class Methods
{
// Caching because the methods won't change, if we're being called once we're likely to
Expand Down
3 changes: 3 additions & 0 deletions src/Http/Routing/src/Matching/ILEmitTrieJumpTable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@

#nullable disable

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.AspNetCore.Routing.Matching;

// Uses generated IL to implement the JumpTable contract. This approach requires
// a fallback jump table for two reasons:
// 1. We compute the IL lazily to avoid taking up significant time when processing a request
// 2. The generated IL only supports ASCII in the URL path
[RequiresDynamicCode("ILEmitTrieJumpTable uses runtime IL generation.")]
internal sealed class ILEmitTrieJumpTable : JumpTable
{
private readonly int _defaultDestination;
Expand Down
2 changes: 2 additions & 0 deletions src/Http/Routing/src/Matching/JumpTableBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ public static JumpTable Build(int defaultDestination, int exitDestination, (stri
// Use the ILEmitTrieJumpTable if the IL is going to be compiled (not interpreted)
if (RuntimeFeature.IsDynamicCodeCompiled)
{
#pragma warning disable IL3050 // See https://github.com/dotnet/linker/issues/2715.
return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
#pragma warning restore IL3050
}

return fallback;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ private static (MethodInfo, Type[]) GetCachedMethodInfo(AssemblyKey assemblyKey,
"ReflectionAnalysis",
"IL2060:MakeGenericMethod",
Justification = "https://github.com/mono/linker/issues/1727")]
[SuppressMessage("AOT",
"IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
Justification = "Methods being referenced do not have DynamicallyAccessedMembers.")]
Copy link
Member

Choose a reason for hiding this comment

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

  1. This isn't a valid justification for suppressing this warning.
  2. I don't believe this warning can be safely suppressed. From looking at the only callsite, this is used to convert a ValueTask<T> to a Task<T>. If the T is a ValueType, this code isn't guaranteed to work in Native AOT, since CreateValueTaskConverter<T> might not be generated ahead of time for that specific ValueType.

Copy link
Member

Choose a reason for hiding this comment

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

Further, how important is it for Microsoft.JSInterop to be AOT-compatible in .NET 8? Isn't it only used for WASM, which doesn't use Native AOT?

Copy link
Member Author

@Nick-Stanton Nick-Stanton Dec 21, 2022

Choose a reason for hiding this comment

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

It is used across WASM, Server and WebView. You've raised important points about these suppressions, and I will either pursue a fallback like you describe here or document my findings in an issue report for us to milestone and discuss like I did with BindConverter (#45578).

In the meantime, I have reverted my changes in JSInterop since @JamesNK's PR already disables analysis on the project.

Copy link
Member Author

Choose a reason for hiding this comment

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

Documented in #45795

private static Task GetTaskByType(Type type, object obj)
{
var converterDelegate = _cachedConvertToTaskByType.GetOrAdd(type, (Type t, MethodInfo taskConverterMethodInfo) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public override bool CanConvert(Type typeToConvert)
}

[UnconditionalSuppressMessage("Trimming", "IL2055", Justification = "We expect that types used with DotNetObjectReference are retained.")]
[SuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "MakeGenericType is AOT safe for reference types.")]
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions jsonSerializerOptions)
{
// System.Text.Json handles caching the converters per type on our behalf. No caching is required here.
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.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

namespace Microsoft.JSInterop.Infrastructure;
Expand All @@ -23,6 +24,9 @@ public static void SetTaskCompletionSourceException(object taskCompletionSource,
public static Type GetTaskCompletionSourceResultType(object taskCompletionSource)
=> CreateResultSetter(taskCompletionSource).ResultType;

[SuppressMessage("AOT",
"IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
Justification = "MakeGenericType is AOT safe for reference types.")]
public static object? GetTaskResult(Task task)
{
var getter = _cachedResultGetters.GetOrAdd(task.GetType(), taskInstanceType =>
Expand Down Expand Up @@ -101,6 +105,9 @@ public void SetException(object tcs, Exception exception)
}
}

[SuppressMessage("AOT",
"IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.",
Justification = "MakeGenericType is AOT safe for reference types.")]
private static ITcsResultSetter CreateResultSetter(object taskCompletionSource)
{
return _cachedResultSetters.GetOrAdd(taskCompletionSource.GetType(), tcsType =>
Expand Down
9 changes: 9 additions & 0 deletions src/Shared/PropertyHelper/PropertyHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public static PropertyHelper[] GetProperties(
/// A cached array of all public properties of the specified type.
/// </returns>
[RequiresUnreferencedCode("This API is not trim safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static PropertyHelper[] GetVisibleProperties(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] Type type)
{
Expand All @@ -167,6 +168,7 @@ public static PropertyHelper[] GetVisibleProperties(
/// same speed.
/// </remarks>
[RequiresUnreferencedCode("This API is not trimmer safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static Func<object, object?> MakeFastPropertyGetter(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null);
Expand All @@ -187,6 +189,7 @@ public static PropertyHelper[] GetVisibleProperties(
/// same speed.
/// </remarks>
[RequiresUnreferencedCode("This API is not trimmer safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static Func<object, object?> MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null);
Expand All @@ -198,6 +201,7 @@ public static PropertyHelper[] GetVisibleProperties(
}

[RequiresUnreferencedCode("This API is not trimmer safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
private static Func<object, object?> MakeFastPropertyGetter(
PropertyInfo propertyInfo,
MethodInfo propertyGetterWrapperMethod,
Expand Down Expand Up @@ -242,6 +246,7 @@ public static PropertyHelper[] GetVisibleProperties(
}

[RequiresUnreferencedCode("This API is not trimmer safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
private static Func<object, object?> MakeFastPropertyGetter(
Type openGenericDelegateType,
MethodInfo propertyGetMethod,
Expand Down Expand Up @@ -271,6 +276,7 @@ public static PropertyHelper[] GetVisibleProperties(
/// same speed. This only works for reference types.
/// </remarks>
[RequiresUnreferencedCode("This API is not trimmer safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static Action<object, object?> MakeFastPropertySetter(PropertyInfo propertyInfo)
{
Debug.Assert(propertyInfo != null);
Expand Down Expand Up @@ -313,6 +319,7 @@ public static PropertyHelper[] GetVisibleProperties(
/// faster when the same type is used multiple times with ObjectToDictionary.
/// </remarks>
[RequiresUnreferencedCode("Method uses reflection to generate the dictionary.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static IDictionary<string, object?> ObjectToDictionary(object? value)
{
if (value is IDictionary<string, object?> dictionary)
Expand Down Expand Up @@ -402,6 +409,7 @@ private static void CallPropertySetter<TDeclaringType, TValue>(
/// A cached array of all public properties of the specified type.
/// </returns>
[RequiresUnreferencedCode("This API is not trim safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static PropertyHelper[] GetVisibleProperties(
Type type,
ConcurrentDictionary<Type, PropertyHelper[]>? allPropertiesCache,
Expand Down Expand Up @@ -483,6 +491,7 @@ public static PropertyHelper[] GetVisibleProperties(
/// </returns>
// There isn't a way to represent trimmability requirements since for type since we unwrap nullable types.
[RequiresUnreferencedCode("This API is not trim safe.")]
[RequiresDynamicCode("This API is not trim safe.")]
public static PropertyHelper[] GetProperties(
Type type,
ConcurrentDictionary<Type, PropertyHelper[]>? cache)
Expand Down