Skip to content

Custom Attribute blob syntax for System.Type is unclear #116620

Open
@nike4613

Description

@nike4613

Specifically:

ECMA-335 § II.23.3

  • If the parameter kind is System.Type, (also, the middle line in above diagram) its is stored as a SerString (as defined in the previous paragraph), representing its name. The canonical name is its full type name, followed optionally by assembly where it is defined, its version, culture and public-key-token. If the name is omitted, the CLI looks first in the current assembly, and then in the system library (mscorlib); in these two special cases, it is permitted to omit the name, version, culture and public-key-token.

(emphasis mine)

The spec calls out mscorlib here, which was reasonable for .NET Framework. However, in .NET Core and .NET Standard, the corelib is no longer in mscorlib, but rather (in the API contract) System.Runtime or netstandard primarily. CoreCLR does not use any of these, however, and instead uses System.Private.CoreLib as the fallback, despite that being a (supposedly) private implementation detail. This is most notably a problem for System.Uri: it is in the reference corelib (System.Uri) on .NET Core and .NET Standard, but its implementation is in System.Private.Uri, not System.Private.CoreLib, so a CA blob that contains System.Uri will fail to resolve!

More detail on where this was found:

Washi1337/AsmResolver#648

@Windows10CE:

For some additional references for behavior being changed by this PR, all checked 2025-06-12:

ECMA-335 6th Edition, Section II.23.3, bottom of page 268, describes how to do resolution when no assembly spec is given for Type arguments: https://ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf

The current behavior of CoreCLR, which attempts current assembly, then typeof(object).Assembly (System.Private.CoreLib), then the TypeResolve event of the current AssemblyLoadContext:

private Type? GetTypeFromDefaultAssemblies(string typeName, ReadOnlySpan<string> nestedTypeNames, TypeName parsedName)

The current behavior of Roslyn (the C#/VB compiler), which always writes FQNs when there is an assembly being referenced: https://github.com/dotnet/roslyn/blob/2f32bf87e211fe5319c94f442c4ad56254bc75e0/src/Compilers/Core/Portable/PEWriter/TypeNameSerializer.cs#L194

Cecil reading always uses module.TypeSystem.CoreLibrary when it cannot find a type definition (the current behavior of AsmResolver): https://github.com/jbevain/cecil/blob/3136847ea620fb9b4a3ff96bc4f573148e8bd2e4/Mono.Cecil/TypeParser.cs#L356

Cecil writing follows the ECMA section to the letter and only omits it when the assembly being referenced is exactly current module or mscorlib (this behavior is absolutely incorrect and almost certainly has real issues, I just cannot be bothered to synthesize examples of it, this fails when referencing mscorlib for any type that is not today in System.Private.CoreLib and then loading that attribute with CoreCLR): https://github.com/jbevain/cecil/blob/3136847ea620fb9b4a3ff96bc4f573148e8bd2e4/Mono.Cecil/TypeParser.cs#L521

dnLib reading does the same behavior as current AsmResolver and Cecil, attempting current module, then the referenced corlib: https://github.com/0xd4d/dnlib/blob/c78d296c522aae0520df2afd825d48266321cf36/src/DotNet/CustomAttributeReader.cs#L24

dnLib writing has an option flag for whether to enable the same optimization that AsmResolver currently employs: https://github.com/0xd4d/dnlib/blob/c78d296c522aae0520df2afd825d48266321cf36/src/DotNet/Writer/Metadata.cs#L182-L196

Even Cecil is technically wrong here, with respect to what CoreCLR does! The only actually correct thing to do is what Roslyn does, but that poses a problem when trying to load potentially poorly authored assemblies, because there's not a good answer for what to do.

Proposed solution(s)

  1. Formally deprecate (e.g. in an ECMA augment) omitting the assembly for corelib types
  2. Make CoreCLR (and Mono, if necessary) resolve such types through the mscorlib forwarder assembly (and maybe other forwarder assemblies depending on the source assembly's defined targetframework?)
  • .NET Framework assemblies authored according to spec will load properly, though there is a risk that some incorrectly-authored .NET Standard or .NET Core assemblies will fail to resolve properly without checking the defined TargetFramework

Alternatively:

Augment ECMA to specify resolving-through System.Runtime instead of mscorlib, and adjust CoreCLR and Mono appropriately

  • This sucks, because not specifying the assembly sucks in the first place.

A slightly more out-there idea is to rev the CA blob format and deprecate the string form entirely, replacing it with an encoded TypeDef/TypeRef/TypeSpec token and using all of the goodness of the .NET metadata format here, too.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.ReflectiondocumentationDocumentation bug or enhancement, does not impact product or test codeuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions