Skip to content

Parse custom attribute value blobs for dependencies, rewrite type name strings in ILTrim, track corrupted attribute blobs, and simplify rewrite type handling#127596

Draft
Copilot wants to merge 18 commits intomainfrom
copilot/fix-custom-attribute-todos
Draft

Parse custom attribute value blobs for dependencies, rewrite type name strings in ILTrim, track corrupted attribute blobs, and simplify rewrite type handling#127596
Copilot wants to merge 18 commits intomainfrom
copilot/fix-custom-attribute-todos

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 30, 2026

Description

CustomAttributeNode had two TODOs: it wasn't parsing the custom attribute value blob for dependencies, and it wasn't rewriting type name strings that could become stale after trimming removes type forwarders.

Dependency analysis (GetStaticDependencies)

Decodes the custom attribute blob via CustomAttributeTypeProvider and reports dependencies for:

  • typeof() arguments
  • Enum types (needed for boxing)
  • Property setters and fields referenced in named arguments
  • Non-primitive array element types

Follows the same pattern as CustomAttributeBasedDependencyAlgorithm in ILCompiler. All dependency helper methods accept a DependencyList parameter and use .Add() directly, avoiding yield return and IEnumerable overhead. GetStaticDependencies creates and returns the populated DependencyList. The unnecessary customAttribute.Constructor.IsNil guard has been removed since custom attributes always have a constructor. Dependency blob parsing and named argument handling are inlined directly into GetStaticDependencies rather than delegated to standalone methods. Constructor resolution uses TryGetMethod (backed by GetObject(handle, NotFoundBehavior.ReturnNull)) instead of try/catch, with a null constructor triggering an early return before named argument processing. GetDependenciesFromPropertySetter uses GetTypeDefinition() to correctly resolve property setters on generic attribute types.

Additionally, dependency decode failures now set an instance _isCorrupted flag so rewrite can avoid re-processing known-bad blobs.

Blob rewriting (WriteInternal)

RewriteCustomAttributeBlob no longer uses customAttribute.DecodeValue for rewriting. Instead, it reads the custom attribute blob manually with BlobReader, writes into writeContext.GetSharedBlobBuilder(), and rewrites only serialized type-name strings where needed (System.Type payloads and enum type names).

This avoids losing declared named-argument type information (especially object/tagged object cases), preserves malformed/edge metadata behavior by falling back to the original blob on decode/format failures, and still normalizes type names after trimming/type-forwarder changes (for example, rewriting System.Object, System.Runtime to System.Object, System.Private.CoreLib via CustomAttributeTypeNameFormatter).

Rewrite now takes advantage of corruption tracking:

  • early-outs to copying the original blob when _isCorrupted is set
  • simplifies rewrite-time branching by relying on dependency-time validation for valid-path assumptions
  • retains fallback behavior for malformed/unresolvable cases during rewrite

Latest follow-up feedback updates also:

  • switched named-argument/value dispatch in rewrite parsing to switch-based control flow
  • removed redundant invalid-sentinel handling in the validated-blob path
  • fixed enum fixed-argument typing by using type.UnderlyingType in fixed-argument type-code inference
  • removed dead enum/fallback branches in fixed-argument type-code inference
  • removed premature enum rewrite optimization (always writes the rewritten serialized enum type name)
  • removed CustomAttributeTypeProvider indirection from rewrite helpers by using direct EcmaModule lookup for serialized type names and underlying enum primitive mapping logic
  • converted rewrite blob helper methods to instance methods so _module is no longer threaded through helper parameters
  • changed primitive serialization-type mapping to a switch expression
  • updated grouped primitive serialization cases to use C# or patterns
  • combined _isCorrupted and constructor resolution into a single early-out guard in rewrite
  • simplified serialized string rewrite to direct read/rewrite/write flow and removed the premature byte-copy optimization for serialized strings

Code style

  • Methods use cascading if/else if/else instead of early return statements.
  • EncodeElementType switches on type.Category and handles IsEnum and System.Type in the default case, rather than checking them before the switch.
  • Exception handling uses catch (Exception ex) when (ex is TypeSystemException or BadImageFormatException) to collapse multiple catch blocks.
  • Rewrite parser type dispatch uses switch statements per reviewer guidance.
  • Removed inaccurate decode-failure commentary and removed an unnecessary type is null check in dependency argument handling.

Test results

25 previously-expected-failing tests now pass and have been removed from ILTrimExpectedFailures.txt (including Attributes.GenericAttributes).

Validation run for this follow-up update:

  • ./build.sh clr+libs -rc release
  • dotnet build src/coreclr/tools/ILTrim.Core/ILTrim.Core.csproj
  • dotnet test src/coreclr/tools/ILTrim.Tests/ILTrim.Tests.csproj --filter "FullyQualifiedName~AttributesTests"

Copilot AI and others added 2 commits April 30, 2026 04:17
…ame strings

Implement custom attribute blob parsing in CustomAttributeNode:
1. GetStaticDependencies now decodes the custom attribute value blob and
   reports dependencies for typeof() arguments, enum types, property
   setters, and fields referenced in the blob.
2. WriteInternal now re-encodes the blob with type name strings resolved
   to their definitions, handling cases where trimming drops type
   forwarders (e.g. re-encoding "System.Object, System.Runtime" to
   "System.Object, System.Private.CoreLib").

Remove 24 tests from ILTrimExpectedFailures.txt that now pass.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f4173cc7-fd45-4a62-9a1c-1a31c06cc85b

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
… remove unused parameter

- Catch TypeSystemException and BadImageFormatException instead of base Exception
- Add DependencyNode type alias to reduce verbose fully-qualified type names
- Remove unused isFixedArg parameter from WriteArgumentValue
- Hoist constructor resolution out of named argument loop
- Fix duplicate CustomAttributeTypeNameFormatter allocations

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/f4173cc7-fd45-4a62-9a1c-1a31c06cc85b

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
@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.

… BlobBuilder

Use BlobEncoder.CustomAttributeSignature with FixedArgumentsEncoder and
CustomAttributeNamedArgumentsEncoder instead of manual byte writing.
Use writeContext.GetSharedBlobBuilder() following established patterns.
Removed byte[] materialization - writes directly to shared BlobBuilder.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/d078b767-9d76-41e5-9168-8f6651786108

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot April 30, 2026 09:04
Copilot AI requested a review from MichalStrehovsky April 30, 2026 09:05
…bute blobs

Remove CheckArgumentsForRewrite/CheckArgumentValueForRewrite that skipped
rewriting when no typeof() references were found. Always rewrite to exercise
the encoding logic and surface bugs.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/46e2e54d-5313-4538-8274-c5cfe241e547

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
…pe.Category

- Replace early returns with cascading if/else if/else in EncodeNamedArgumentType,
  WriteLiteralValue, and WriteScalarValue.
- Switch on type.Category instead of type.UnderlyingType.Category in
  EncodeElementType, handling IsEnum and System.Type in the default case.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/683a21bb-4e07-4219-81e8-b0a320967bd3

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 1, 2026 11:23
Copilot AI review requested due to automatic review settings May 11, 2026 08:00
…parser

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e7baf5db-dc38-4019-b7c6-c66d7a5cbb73

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 11, 2026 08:02
…ying

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e7baf5db-dc38-4019-b7c6-c66d7a5cbb73

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 11, 2026 08:03
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e7baf5db-dc38-4019-b7c6-c66d7a5cbb73

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 11, 2026 08:05
Copilot AI changed the title Parse custom attribute value blobs for dependencies and rewrite type name strings in ILTrim Parse custom attribute value blobs for dependencies, rewrite type name strings in ILTrim, and track corrupted attribute blobs May 11, 2026
Copilot AI requested a review from MichalStrehovsky May 11, 2026 08:07
Copy link
Copy Markdown
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e83d165b-d5bc-4be9-b1db-5069b96d2c27

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 11, 2026 08:18
Copilot AI requested a review from MichalStrehovsky May 11, 2026 08:20
Copy link
Copy Markdown
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/4fe77ae2-b014-4433-9586-1722a52da603

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot May 11, 2026 09:39
Copilot AI changed the title Parse custom attribute value blobs for dependencies, rewrite type name strings in ILTrim, and track corrupted attribute blobs Parse custom attribute value blobs for dependencies, rewrite type name strings in ILTrim, track corrupted attribute blobs, and simplify rewrite type handling May 11, 2026
Copy link
Copy Markdown
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

MethodSignature constructorSig = constructor.Signature;
for (int i = 0; i < constructorSig.Length; i++)
{
CopyFixedArgument(ref valueReader, blobBuilder, originalBlob, constructorSig[i], _module, formatter);
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.

Do not pass _module to the various helpers here. Make the helpers instance methods instead.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in d4b6451. I converted the rewrite helpers to instance methods and removed _module from helper parameter lists.

Comment on lines +375 to +377
case SerializationTypeCode.Boolean:
case SerializationTypeCode.Byte:
case SerializationTypeCode.SByte:
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.

Suggested change
case SerializationTypeCode.Boolean:
case SerializationTypeCode.Byte:
case SerializationTypeCode.SByte:
case SerializationTypeCode.Boolean or SerializationTypeCode.Byte or SerializationTypeCode.SByte:

Also same below.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in d4b6451. I switched grouped primitive cases to or patterns in CopySerializedValue (including the lower grouped cases).

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/6129cc2c-9d66-4f42-876a-056fcef7f711

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Copy link
Copy Markdown
Member

@MichalStrehovsky MichalStrehovsky left a comment

Choose a reason for hiding this comment

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

Comment on lines +84 to +85
// Attribute ctor doesn't resolve, typeof() refers to something that can't be loaded,
// attribute refers to a non-existing field, malformed blob, etc.
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.

This comment doesn't seem true.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in ef95ab1. I removed that inaccurate comment and replaced it with a concise decode-failure comment.

Comment on lines +115 to +116
if (type is null)
return;
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.

When can type be null?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in ef95ab1. type cannot be null there, so I removed the null check.

private void RewriteCustomAttributeBlob(CustomAttribute customAttribute, BlobBuilder blobBuilder)
{
byte[] originalBlob = _module.MetadataReader.GetBlobBytes(customAttribute.Value);
if (_isCorrupted)
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.

Combine with the subsequent if:

Suggested change
if (_isCorrupted)
if (_isCorrupted || _module.TryGetMethod(customAttribute.Constructor) is not MethodDesc constructor)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in ef95ab1. I combined the checks into a single early-out using _isCorrupted || _module.TryGetMethod(...) is not MethodDesc constructor.

Comment on lines +269 to +287
int startOffset = valueReader.Offset;
string? original = valueReader.ReadSerializedString();
int endOffset = valueReader.Offset;

if (rewriteTypeName && original is not null)
{
TypeDesc resolved = _module.GetTypeByCustomAttributeTypeName(original);
if (resolved is not null)
{
string rewritten = formatter.FormatName(resolved, true);
if (rewritten != original)
{
blobBuilder.WriteSerializedString(rewritten);
return;
}
}
}

blobBuilder.WriteBytes(originalBlob, startOffset, endOffset - startOffset);
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.

Since we know the blob is valid, we don't need error handling.

The extra optimization is also premature.

Suggested change
int startOffset = valueReader.Offset;
string? original = valueReader.ReadSerializedString();
int endOffset = valueReader.Offset;
if (rewriteTypeName && original is not null)
{
TypeDesc resolved = _module.GetTypeByCustomAttributeTypeName(original);
if (resolved is not null)
{
string rewritten = formatter.FormatName(resolved, true);
if (rewritten != original)
{
blobBuilder.WriteSerializedString(rewritten);
return;
}
}
}
blobBuilder.WriteBytes(originalBlob, startOffset, endOffset - startOffset);
string? s = valueReader.ReadSerializedString();
if (rewriteTypeName)
{
TypeDesc resolved = _module.GetTypeByCustomAttributeTypeName(s);
s = formatter.FormatName(resolved, true);
}
blobBuilder.WriteSerializedString(s);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Applied in ef95ab1. CopySerializedString now directly reads/writes serialized strings and rewrites type names without the previous byte-copy optimization path.

Comment on lines +300 to +304
TypeDesc underlyingType = type.UnderlyingType;

if (underlyingType.IsPrimitive)
{
valueTypeCode = GetPrimitiveSerializationTypeCode(underlyingType);
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.

Suggested change
TypeDesc underlyingType = type.UnderlyingType;
if (underlyingType.IsPrimitive)
{
valueTypeCode = GetPrimitiveSerializationTypeCode(underlyingType);
if (type.IsPrimitive || type.IsEnum)
{
valueTypeCode = GetPrimitiveSerializationTypeCode(type.UnderlyingType);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated in ef95ab1. GetFixedArgumentTypeCodes now handles primitives/enums as if (type.IsPrimitive || type.IsEnum) and maps via type.UnderlyingType.

Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/20d7918c-7976-4a3d-882f-8aac8f4ade8b

Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants