Skip to content

Support typeof(T).Assembly.GetType(string) in ILLink dataflow analysis#127319

Open
MichalStrehovsky wants to merge 7 commits intodotnet:mainfrom
MichalStrehovsky:assembly-gettype-dataflow
Open

Support typeof(T).Assembly.GetType(string) in ILLink dataflow analysis#127319
MichalStrehovsky wants to merge 7 commits intodotnet:mainfrom
MichalStrehovsky:assembly-gettype-dataflow

Conversation

@MichalStrehovsky
Copy link
Copy Markdown
Member

Teach the trimmer, NativeAOT compiler, and Roslyn analyzer to understand the pattern typeof(SomeType).Assembly.GetType("OtherType"). This allows the analysis to resolve the target type and avoid false warnings.

This is an extremely common pattern in the libraries tests despite me fixing many instances up into Type.GetType. It is somewhat nice to be able to do this though. So I've let copilot do it. We could also use this to implement dotnet/linker#1947 later.

  • Add AssemblyValue (SingleValue holding assembly simple name)
  • Add IntrinsicId.Type_get_Assembly and IntrinsicId.Assembly_GetType
  • Handle Type_get_Assembly: SystemTypeValue -> AssemblyValue
  • Handle Assembly_GetType: resolve type name within the known assembly
  • Implement partials in all three consumers (ILLinker, NativeAOT, Roslyn)
  • Add test coverage in AssemblyGetTypeDataFlow.cs

Teach the trimmer, NativeAOT compiler, and Roslyn analyzer to understand
the pattern typeof(SomeType).Assembly.GetType("OtherType"). This allows
the analysis to resolve the target type and avoid false warnings.

- Add AssemblyValue (SingleValue holding assembly simple name)
- Add IntrinsicId.Type_get_Assembly and IntrinsicId.Assembly_GetType
- Handle Type_get_Assembly: SystemTypeValue -> AssemblyValue
- Handle Assembly_GetType: resolve type name within the known assembly
- Implement partials in all three consumers (ILLinker, NativeAOT, Roslyn)
- Add test coverage in AssemblyGetTypeDataFlow.cs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 23, 2026 11:54
@MichalStrehovsky MichalStrehovsky added the area-Tools-ILLink .NET linker development as well as trimming analyzers label Apr 23, 2026
@dotnet-policy-service dotnet-policy-service Bot added the linkable-framework Issues associated with delivering a linker friendly framework label Apr 23, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/illink
See info in area-owners.md if you want to be subscribed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends ILLink trim analysis (and its NativeAOT + Roslyn analyzer consumers) to understand the pattern typeof(T).Assembly.GetType(string) by modeling Type.Assembly and Assembly.GetType(...) as intrinsics, enabling more precise type resolution and reducing false trimming/dataflow warnings.

Changes:

  • Introduces AssemblyValue and new intrinsics (Type_get_Assembly, Assembly_GetType) to carry a known assembly identity through the dataflow lattice.
  • Implements Type.AssemblyAssemblyValue propagation and Assembly.GetType(...) resolution across ILLinker, NativeAOT, and the Roslyn analyzer.
  • Adds test coverage for the new analysis behavior in the ILLink test suite and wires it into analyzer test execution.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AssemblyGetTypeDataFlow.cs New regression tests covering known/unknown Assembly.GetType patterns and ignoreCase behavior.
src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs Adds a test entry point so the new dataflow case runs in analyzer test suite.
src/tools/illink/src/linker/Linker.Dataflow/HandleCallAction.cs Linker-specific partial implementations for assembly-name extraction and in-assembly type resolution.
src/tools/illink/src/ILLink.Shared/TrimAnalysis/Intrinsics.cs Recognizes Type.get_Assembly and Assembly.GetType(...) as intrinsics.
src/tools/illink/src/ILLink.Shared/TrimAnalysis/IntrinsicId.cs Adds intrinsic IDs for Type_get_Assembly and Assembly_GetType.
src/tools/illink/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs Shared intrinsic behavior: produces AssemblyValue from SystemTypeValue and resolves Assembly.GetType(...).
src/tools/illink/src/ILLink.Shared/TrimAnalysis/AssemblyValue.cs New SingleValue representing a known assembly (by simple name).
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs Adds analyzer-side helper for resolving a metadata name within a specific assembly reference.
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs Analyzer partial implementations using the new resolver helper.
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs NativeAOT partial implementations for assembly-name extraction and in-assembly type resolution.

Comment thread src/tools/illink/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs Outdated
Comment thread src/tools/illink/src/linker/Linker.Dataflow/HandleCallAction.cs Outdated
Comment thread src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs Outdated
Comment thread src/tools/illink/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs Outdated
MichalStrehovsky and others added 3 commits April 24, 2026 09:33
… fallback

- Add dedicated DiagnosticId.AssemblyGetTypeCannotBeAnalyzed (IL2128) for
  unknown assembly receivers instead of reusing IL2057
- Register IL2128 in DynamicallyAccessedMembersAnalyzer
- Add resx strings for IL2128 title and message
- Return annotatedMethodReturnValue (not Top) for non-SystemTypeValue
  in Type_get_Assembly handler
- Remove unused using and unnecessary [Kept] from test
- Update test to expect IL2128 for unknown assembly case

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover the case where Type.Assembly is called on a non-SystemTypeValue
(e.g. a method parameter) and then Assembly.GetType is called on the
result. Verifies IL2128 is produced.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When the assembly receiver is unknown, the type name is unknown, or the
case-insensitive flag is set, fall back to the original
RequiresUnreferencedCode (IL2026) warning instead of producing new
diagnostic IDs. This avoids a breaking change where code suppressing
IL2026 for Assembly.GetType calls would get unsuppressed warnings.

Add ReportRequiresUnreferencedCode partial method implemented in all
three consumers (ILLinker, NativeAOT, Roslyn analyzer) to enable the
shared handler to report the RUC warning. Remove unused IL2128
diagnostic that is no longer needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 24, 2026 03:31
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comment thread src/tools/illink/src/linker/Linker.Dataflow/HandleCallAction.cs Outdated
Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs Outdated
Comment thread src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs Outdated
Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TypeNameResolver.cs Outdated
Comment thread src/tools/illink/src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs
The previous Assembly.GetType data-flow analysis used a separate, less-capable
`TryResolveTypeNameInAssembly` path on the Roslyn analyzer side
(`IAssemblySymbol.GetTypeByMetadataName`) that didn't handle arrays,
pointers, or generic instantiations. The Cecil and AOT backends used the full
TypeName-parsing resolver but had a different gap: they fell back to the core
library for unqualified type names, which over-resolves for Assembly.GetType
(which only searches the receiver assembly at runtime).

Add a `fallbackToCoreLib` flag to all three resolvers and plumb it through
the marker layer. The Assembly.GetType call site now passes
`fallbackToCoreLib: false`; existing callers (Activator.CreateInstance,
custom attribute parser, UnsafeAccessor, LinkContext, test helpers) keep
`fallbackToCoreLib: true`. The Roslyn analyzer's TryResolveTypeNameInAssembly
now parses the input with TypeName.TryParse and dispatches to the existing
private `ResolveTypeName(assembly, typeName)` helper, which already handles
arrays, pointers, and constructed generics.

New tests in AssemblyGetTypeDataFlow exercise array, pointer, and generic
type names. TestTypeOnlyInCoreLib verifies that
`Assembly.GetType("System.Reflection.Assembly")` does not over-resolve to
corelib; if it did, RequiresAll() would mark Assembly.GetTypes() and other
RequiresUnreferencedCode-annotated members and emit IL2026.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@am11
Copy link
Copy Markdown
Member

am11 commented May 4, 2026

Tried the same on JIT side #127697.

MichalStrehovsky and others added 2 commits May 4, 2026 22:39
Restrict Type.Assembly modeling to named types. Function pointer types,
array/pointer/byref wrappers, generic parameters, and System.Array itself
are now rejected, returning null from GetAssemblyName so downstream
Assembly.GetType calls fall back to an IL2026 warning.

Function pointers in particular are a correctness concern: CoreCLR returns
the loader module (effectively random) and NativeAOT returns CoreLib, so
no static modeling is sound.

System.Array is rejected to handle the Cecil linker's IL scanner, which
lowers typeof(SomeType[]) to a TypeDefinition of System.Array. Without
this rejection the linker would model typeof(SomeType[]).Assembly as
CoreLib, which is wrong for non-CoreLib element types. Rejecting
System.Array also covers the (rare) case where user code writes
typeof(System.Array).Assembly directly.

Type_get_Assembly no longer short-circuits on an empty receiver value: an
unknown receiver has an unknown Assembly, and downstream Assembly.GetType
should warn rather than silently widen to Top. The Roslyn analyzer's
SingleValueExtensions.FromTypeSymbol is unchanged; function pointer
typeof produces Top via the existing default arm, which now flows through
to a warning.

Tests cover array, function pointer, generic parameter, and array-of-T
receivers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
At runtime, Assembly.GetType invokes the type name parser with
IsAssemblyGetType = true, which rejects any top-level assembly qualifier
(returns null, or throws Argument_AssemblyGetTypeCannotSpecifyAssembly
when throwOnError is true). The new dataflow analysis previously honored
the qualifier and resolved the type from the named assembly, causing
spurious marking of types and members the runtime would never see.

Reject top-level assembly-qualified names in each of the three resolvers
when called from the Assembly.GetType path:
- Cecil's Mono.Linker.TypeNameResolver: gated on !fallbackToCoreLib.
- AOT's CustomAttributeTypeNameParser: gated on !fallbackToCoreLib.
- Roslyn analyzer's TryResolveTypeNameInAssembly: unconditional (the
  method is exclusive to Assembly.GetType).

Assembly qualifiers on generic arguments are still honored, matching
runtime behavior.

A regression test calls Assembly.GetType("System.Reflection.Assembly,
System.Runtime").RequiresAll(); without the fix, the analyzer over-resolves
to Assembly and RequiresAll marks RUC members like LoadFrom and emits
IL2026.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
{
// An unknown receiver has an unknown Assembly; downstream Assembly.GetType
// should warn rather than silently widen to Top.
returnValue = annotatedMethodReturnValue;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should follow the pattern that other intrinsics use (check IsEmpty first, and if it's empty return Top and break). The IsEmpty case represents "no value", not "unknown value".

For consistency we could also add a case that returns Top on NullValue.Instance.

Mind adding a test for this too? There's an existing test pattern (look for TestNull/TestNoValue tests for the other intrinsics).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Do you mean restoring the:

                case IntrinsicId.Type_get_Assembly:
                    if (instanceValue.IsEmpty())
                    {
                        returnValue = MultiValueLattice.Top;
                        break;
                    }

part that I removed in c1a65d9?

Analysis models function pointer types as Empty so there is the typeof(delegate*...).Assembly.GetType that would not work because we just keep propagating it as Top and no warning is triggered even though we don't know what type we just got.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes - ah then I think that's a bug, and function pointer types should be modeled as "unknown" instead of "empty". I can fix that in a follow-up if you don't want to do so here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Tools-ILLink .NET linker development as well as trimming analyzers linkable-framework Issues associated with delivering a linker friendly framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants