Skip to content

Commit 0faf2a2

Browse files
committed
Phase 2a: Hot path items annotated.
1 parent 821ff00 commit 0faf2a2

10 files changed

Lines changed: 187 additions & 21 deletions

src/Autofac/Builder/ConcreteReflectionActivatorData.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Autofac.Core;
55
using Autofac.Core.Activators.Reflection;
6+
using Autofac.Util;
67

78
namespace Autofac.Builder;
89

@@ -15,7 +16,7 @@ public class ConcreteReflectionActivatorData : ReflectionActivatorData, IConcret
1516
/// Initializes a new instance of the <see cref="ConcreteReflectionActivatorData"/> class.
1617
/// </summary>
1718
/// <param name="implementer">Type that will be activated.</param>
18-
public ConcreteReflectionActivatorData(Type implementer)
19+
public ConcreteReflectionActivatorData([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer)
1920
: base(implementer)
2021
{
2122
}

src/Autofac/Builder/ReflectionActivatorData.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Autofac.Core;
55
using Autofac.Core.Activators.Reflection;
6+
using Autofac.Util;
67

78
namespace Autofac.Builder;
89

@@ -14,6 +15,7 @@ public class ReflectionActivatorData
1415
private static readonly IConstructorFinder _defaultConstructorFinder = new DefaultConstructorFinder();
1516
private static readonly IConstructorSelector _defaultConstructorSelector = new MostParametersConstructorSelector();
1617

18+
[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)]
1719
private Type _implementer = default!;
1820
private IConstructorFinder _constructorFinder;
1921
private IConstructorSelector _constructorSelector;
@@ -22,7 +24,7 @@ public class ReflectionActivatorData
2224
/// Initializes a new instance of the <see cref="ReflectionActivatorData"/> class.
2325
/// </summary>
2426
/// <param name="implementer">Type that will be activated.</param>
25-
public ReflectionActivatorData(Type implementer)
27+
public ReflectionActivatorData([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer)
2628
{
2729
ImplementationType = implementer;
2830

@@ -33,6 +35,7 @@ public ReflectionActivatorData(Type implementer)
3335
/// <summary>
3436
/// Gets or sets the implementation type.
3537
/// </summary>
38+
[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)]
3639
public Type ImplementationType
3740
{
3841
get

src/Autofac/Builder/RegistrationBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static IRegistrationBuilder<object, SimpleActivatorData, SingleRegistrati
5959
/// </summary>
6060
/// <typeparam name="TImplementer">Implementation type to register.</typeparam>
6161
/// <returns>A registration builder.</returns>
62-
public static IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData, SingleRegistrationStyle> ForType<TImplementer>()
62+
public static IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData, SingleRegistrationStyle> ForType<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TImplementer>()
6363
where TImplementer : notnull
6464
{
6565
// Open generics can't be generic type parameters so we don't have to check for that here.
@@ -79,7 +79,7 @@ public static IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData
7979
/// </summary>
8080
/// <param name="implementationType">Implementation type to register.</param>
8181
/// <returns>A registration builder.</returns>
82-
public static IRegistrationBuilder<object, ConcreteReflectionActivatorData, SingleRegistrationStyle> ForType(Type implementationType)
82+
public static IRegistrationBuilder<object, ConcreteReflectionActivatorData, SingleRegistrationStyle> ForType([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType)
8383
{
8484
if (implementationType is null)
8585
{

src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace Autofac.Core.Activators.Reflection;
1515
/// </summary>
1616
public class ReflectionActivator : InstanceActivator, IInstanceActivator
1717
{
18+
[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)]
1819
private readonly Type _implementationType;
1920
private readonly Parameter[] _configuredProperties;
2021
private readonly Parameter[] _defaultParameters;
@@ -33,7 +34,7 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator
3334
/// <param name="configuredParameters">Parameters configured explicitly for this instance.</param>
3435
/// <param name="configuredProperties">Properties configured explicitly for this instance.</param>
3536
public ReflectionActivator(
36-
Type implementationType,
37+
[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType,
3738
IConstructorFinder constructorFinder,
3839
IConstructorSelector constructorSelector,
3940
IEnumerable<Parameter> configuredParameters,
@@ -51,9 +52,14 @@ public ReflectionActivator(
5152
}
5253

5354
_implementationType = implementationType;
55+
56+
// The cache key is the (already annotated) implementation type; the factory
57+
// ignores the dictionary's unannotated key parameter and reads the annotated
58+
// local instead, so the [DynamicallyAccessedMembers] contract flows correctly
59+
// into UsesServiceKeyAttribute.
5460
_requiresServiceKeyParameter = ReflectionCacheSet.Shared.Internal.ServiceKeyUsageByType.GetOrAdd(
5561
_implementationType,
56-
static t => UsesServiceKeyAttribute(t));
62+
_ => UsesServiceKeyAttribute(implementationType));
5763
ConstructorFinder = constructorFinder ?? throw new ArgumentNullException(nameof(constructorFinder));
5864
ConstructorSelector = constructorSelector ?? throw new ArgumentNullException(nameof(constructorSelector));
5965
_configuredProperties = configuredProperties.ToArray();
@@ -123,7 +129,11 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
123129
/// </summary>
124130
/// <param name="implementationType">The type to inspect.</param>
125131
/// <returns><see langword="true"/> if the type uses the <see cref="ServiceKeyAttribute"/>; otherwise, <see langword="false"/>.</returns>
126-
private static bool UsesServiceKeyAttribute(Type implementationType)
132+
[UnconditionalSuppressMessage(
133+
"Trimming",
134+
"IL2070:UnrecognizedReflectionPattern",
135+
Justification = "This is a best-effort scan for [ServiceKey] across all constructors and properties, including non-public ones. Autofac's trim/AOT contract preserves only public constructors and properties (see ActivatorMemberTypes); if a non-public member is trimmed away, it simply is not found here, which matches the documented behavior that non-public activation is not trim/AOT-safe. No public member that drives activation is missed.")]
136+
private static bool UsesServiceKeyAttribute([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType)
127137
{
128138
// Intentionally not picky about _which_ constructor or property has the
129139
// attribute. If a different constructor is picked via constructor
@@ -347,6 +357,10 @@ private bool HasAnyRequiredMembers()
347357
});
348358
}
349359

360+
[UnconditionalSuppressMessage(
361+
"Trimming",
362+
"IL2077:UnrecognizedReflectionPattern",
363+
Justification = "GetRuntimeProperties enumerates non-public properties as well, but Autofac's trim/AOT contract preserves only public properties (see ActivatorMemberTypes). The injectable set is filtered down to required members and explicitly-configured properties; non-public properties that the trimmer removes are not part of the supported (public) property-injection surface.")]
350364
private void InitializeInjectablePropertySet()
351365
{
352366
if (!_anyRequiredMembers && _configuredProperties.Length == 0)

src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,9 +390,14 @@ private void VerifyPhase(PipelinePhase middlewarePhase)
390390
{
391391
static string DescribeValidEnumRange(PipelinePhase start, PipelinePhase end)
392392
{
393+
#if NET5_0_OR_GREATER
394+
var enumValues = Enum.GetValues<PipelinePhase>()
395+
.Where(value => value >= start && value <= end);
396+
#else
393397
var enumValues = Enum.GetValues(typeof(PipelinePhase))
394398
.Cast<PipelinePhase>()
395399
.Where(value => value >= start && value <= end);
400+
#endif
396401

397402
return string.Join(", ", enumValues);
398403
}

src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static void MiddlewareStart(this DiagnosticListener diagnosticSource, Res
2222
{
2323
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart))
2424
{
25-
diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware));
25+
Write(diagnosticSource, DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware));
2626
}
2727
}
2828

@@ -36,7 +36,7 @@ public static void MiddlewareFailure(this DiagnosticListener diagnosticSource, R
3636
{
3737
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure))
3838
{
39-
diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware));
39+
Write(diagnosticSource, DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware));
4040
}
4141
}
4242

@@ -50,7 +50,7 @@ public static void MiddlewareSuccess(this DiagnosticListener diagnosticSource, R
5050
{
5151
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess))
5252
{
53-
diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware));
53+
Write(diagnosticSource, DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware));
5454
}
5555
}
5656

@@ -64,7 +64,7 @@ public static void OperationStart(this DiagnosticListener diagnosticSource, IRes
6464
{
6565
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart))
6666
{
67-
diagnosticSource.Write(DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest));
67+
Write(diagnosticSource, DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest));
6868
}
6969
}
7070

@@ -78,7 +78,7 @@ public static void OperationFailure(this DiagnosticListener diagnosticSource, IR
7878
{
7979
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure))
8080
{
81-
diagnosticSource.Write(DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException));
81+
Write(diagnosticSource, DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException));
8282
}
8383
}
8484

@@ -92,7 +92,7 @@ public static void OperationSuccess(this DiagnosticListener diagnosticSource, IR
9292
{
9393
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess))
9494
{
95-
diagnosticSource.Write(DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance));
95+
Write(diagnosticSource, DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance));
9696
}
9797
}
9898

@@ -106,7 +106,7 @@ public static void RequestStart(this DiagnosticListener diagnosticSource, IResol
106106
{
107107
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart))
108108
{
109-
diagnosticSource.Write(DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext));
109+
Write(diagnosticSource, DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext));
110110
}
111111
}
112112

@@ -121,7 +121,7 @@ public static void RequestFailure(this DiagnosticListener diagnosticSource, IRes
121121
{
122122
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure))
123123
{
124-
diagnosticSource.Write(DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException));
124+
Write(diagnosticSource, DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException));
125125
}
126126
}
127127

@@ -135,7 +135,31 @@ public static void RequestSuccess(this DiagnosticListener diagnosticSource, IRes
135135
{
136136
if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess))
137137
{
138-
diagnosticSource.Write(DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext));
138+
Write(diagnosticSource, DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext));
139139
}
140140
}
141+
142+
/// <summary>
143+
/// Centralizes the <see cref="DiagnosticSource.Write(string, object?)"/> call so the
144+
/// trimming/AOT suppression for it lives in exactly one place.
145+
/// </summary>
146+
/// <remarks>
147+
/// <see cref="DiagnosticSource.Write(string, object?)"/> is annotated
148+
/// <c>[RequiresUnreferencedCode]</c> because a listener may reflect over the
149+
/// anonymously-typed payload. Autofac's diagnostics are opt-in: this code path only
150+
/// runs when a caller has explicitly attached a <see cref="DiagnosticListener"/>
151+
/// subscriber, which is a development/observability scenario rather than part of the
152+
/// core resolve behavior. The payload types are concrete, internal
153+
/// <c>*DiagnosticData</c> classes that are always referenced from this assembly, so they
154+
/// are not trimmed away. Suppressing here keeps the resolve pipeline (which calls these
155+
/// methods) free of trim warnings.
156+
/// </remarks>
157+
[UnconditionalSuppressMessage(
158+
"Trimming",
159+
"IL2026:RequiresUnreferencedCode",
160+
Justification = "Diagnostics are opt-in and only run when a DiagnosticListener subscriber is attached. Payload types are concrete internal types referenced from this assembly and are not trimmed.")]
161+
private static void Write(DiagnosticListener diagnosticSource, string name, object value)
162+
{
163+
diagnosticSource.Write(name, value);
164+
}
141165
}

src/Autofac/RegistrationExtensions.Composite.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Autofac.Builder;
66
using Autofac.Core;
77
using Autofac.Core.Registration;
8+
using Autofac.Util;
89

910
namespace Autofac;
1011

@@ -31,7 +32,7 @@ public static partial class RegistrationExtensions
3132
/// <typeparam name="TService">Service type to provide a composite for.</typeparam>
3233
/// <param name="builder">Container builder.</param>
3334
/// <returns>The composite registration for continued configuration.</returns>
34-
public static IRegistrationBuilder<TComposite, ConcreteReflectionActivatorData, SingleRegistrationStyle> RegisterComposite<TComposite, TService>(this ContainerBuilder builder)
35+
public static IRegistrationBuilder<TComposite, ConcreteReflectionActivatorData, SingleRegistrationStyle> RegisterComposite<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TComposite, TService>(this ContainerBuilder builder)
3536
where TComposite : notnull, TService
3637
where TService : notnull
3738
{
@@ -66,7 +67,7 @@ public static IRegistrationBuilder<TComposite, ConcreteReflectionActivatorData,
6667
/// <returns>The composite registration for continued configuration.</returns>
6768
public static IRegistrationBuilder<object, ConcreteReflectionActivatorData, SingleRegistrationStyle> RegisterComposite(
6869
this ContainerBuilder builder,
69-
Type compositeType,
70+
[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type compositeType,
7071
Type serviceType)
7172
{
7273
if (builder == null)

src/Autofac/RegistrationExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static IRegistrationBuilder<T, SimpleActivatorData, SingleRegistrationSty
9595
/// <param name="builder">Container builder.</param>
9696
/// <returns>Registration builder allowing the registration to be configured.</returns>
9797
public static IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData, SingleRegistrationStyle>
98-
RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementer>(this ContainerBuilder builder)
98+
RegisterType<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TImplementer>(this ContainerBuilder builder)
9999
where TImplementer : notnull
100100
{
101101
if (builder == null)
@@ -117,7 +117,7 @@ public static IRegistrationBuilder<TImplementer, ConcreteReflectionActivatorData
117117
/// <param name="implementationType">The type of the component implementation.</param>
118118
/// <returns>Registration builder allowing the registration to be configured.</returns>
119119
public static IRegistrationBuilder<object, ConcreteReflectionActivatorData, SingleRegistrationStyle>
120-
RegisterType(this ContainerBuilder builder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType)
120+
RegisterType(this ContainerBuilder builder, [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType)
121121
{
122122
if (builder == null)
123123
{
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) Autofac Project. All rights reserved.
2+
// Licensed under the MIT License. See LICENSE in the project root for license information.
3+
4+
namespace Autofac.Util;
5+
6+
/// <summary>
7+
/// Centralizes the <see cref="DynamicallyAccessedMemberTypes"/> contract for implementation
8+
/// types that Autofac activates through reflection, so that every annotation across the
9+
/// codebase advertises an identical, trim/AOT-consistent member set.
10+
/// </summary>
11+
/// <remarks>
12+
/// <para>
13+
/// The contract is <see cref="DynamicallyAccessedMemberTypes.PublicConstructors"/> plus
14+
/// <see cref="DynamicallyAccessedMemberTypes.PublicProperties"/>:
15+
/// </para>
16+
/// <list type="bullet">
17+
/// <item>
18+
/// Public constructors back the default activation path (the
19+
/// <see cref="Core.Activators.Reflection.DefaultConstructorFinder"/>), so they must be
20+
/// preserved.
21+
/// </item>
22+
/// <item>
23+
/// Public properties back the default property-injection path
24+
/// (<see cref="Core.DefaultPropertySelector"/> only injects public setters), a
25+
/// first-class Autofac feature that must keep working under trimming and native AOT.
26+
/// </item>
27+
/// </list>
28+
/// <para>
29+
/// Non-public constructors (reachable only via a custom
30+
/// <see cref="Core.Activators.Reflection.IConstructorFinder"/>) and non-public properties
31+
/// (reachable only via a custom <see cref="Core.IPropertySelector"/>) are
32+
/// intentionally excluded: those paths are documented as not trim/AOT-safe, and the
33+
/// custom-selector registration APIs already carry <c>[RequiresUnreferencedCode]</c>.
34+
/// </para>
35+
/// </remarks>
36+
internal static class ActivatorMemberTypes
37+
{
38+
/// <summary>
39+
/// The member set preserved for reflection-activated implementation types:
40+
/// <see cref="DynamicallyAccessedMemberTypes.PublicConstructors"/> |
41+
/// <see cref="DynamicallyAccessedMemberTypes.PublicProperties"/>.
42+
/// </summary>
43+
public const DynamicallyAccessedMemberTypes ActivatedType =
44+
DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties;
45+
}

0 commit comments

Comments
 (0)