Conversation
…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>
|
Tagging subscribers to this area: @agocke, @dotnet/illink |
… 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>
…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>
…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>
…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>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e7baf5db-dc38-4019-b7c6-c66d7a5cbb73 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/e83d165b-d5bc-4be9-b1db-5069b96d2c27 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/4fe77ae2-b014-4433-9586-1722a52da603 Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com>
| MethodSignature constructorSig = constructor.Signature; | ||
| for (int i = 0; i < constructorSig.Length; i++) | ||
| { | ||
| CopyFixedArgument(ref valueReader, blobBuilder, originalBlob, constructorSig[i], _module, formatter); |
There was a problem hiding this comment.
Do not pass _module to the various helpers here. Make the helpers instance methods instead.
There was a problem hiding this comment.
Done in d4b6451. I converted the rewrite helpers to instance methods and removed _module from helper parameter lists.
| case SerializationTypeCode.Boolean: | ||
| case SerializationTypeCode.Byte: | ||
| case SerializationTypeCode.SByte: |
There was a problem hiding this comment.
| case SerializationTypeCode.Boolean: | |
| case SerializationTypeCode.Byte: | |
| case SerializationTypeCode.SByte: | |
| case SerializationTypeCode.Boolean or SerializationTypeCode.Byte or SerializationTypeCode.SByte: |
Also same below.
There was a problem hiding this comment.
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>
| // Attribute ctor doesn't resolve, typeof() refers to something that can't be loaded, | ||
| // attribute refers to a non-existing field, malformed blob, etc. |
There was a problem hiding this comment.
This comment doesn't seem true.
There was a problem hiding this comment.
Updated in ef95ab1. I removed that inaccurate comment and replaced it with a concise decode-failure comment.
| if (type is null) | ||
| return; |
There was a problem hiding this comment.
When can type be null?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Combine with the subsequent if:
| if (_isCorrupted) | |
| if (_isCorrupted || _module.TryGetMethod(customAttribute.Constructor) is not MethodDesc constructor) |
There was a problem hiding this comment.
Done in ef95ab1. I combined the checks into a single early-out using _isCorrupted || _module.TryGetMethod(...) is not MethodDesc constructor.
| 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); |
There was a problem hiding this comment.
Since we know the blob is valid, we don't need error handling.
The extra optimization is also premature.
| 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); |
There was a problem hiding this comment.
Applied in ef95ab1. CopySerializedString now directly reads/writes serialized strings and rewrites type names without the previous byte-copy optimization path.
| TypeDesc underlyingType = type.UnderlyingType; | ||
|
|
||
| if (underlyingType.IsPrimitive) | ||
| { | ||
| valueTypeCode = GetPrimitiveSerializationTypeCode(underlyingType); |
There was a problem hiding this comment.
| TypeDesc underlyingType = type.UnderlyingType; | |
| if (underlyingType.IsPrimitive) | |
| { | |
| valueTypeCode = GetPrimitiveSerializationTypeCode(underlyingType); | |
| if (type.IsPrimitive || type.IsEnum) | |
| { | |
| valueTypeCode = GetPrimitiveSerializationTypeCode(type.UnderlyingType); |
There was a problem hiding this comment.
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>
Description
CustomAttributeNodehad 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
CustomAttributeTypeProviderand reports dependencies for:typeof()argumentsFollows the same pattern as
CustomAttributeBasedDependencyAlgorithmin ILCompiler. All dependency helper methods accept aDependencyListparameter and use.Add()directly, avoidingyield returnandIEnumerableoverhead.GetStaticDependenciescreates and returns the populatedDependencyList. The unnecessarycustomAttribute.Constructor.IsNilguard has been removed since custom attributes always have a constructor. Dependency blob parsing and named argument handling are inlined directly intoGetStaticDependenciesrather than delegated to standalone methods. Constructor resolution usesTryGetMethod(backed byGetObject(handle, NotFoundBehavior.ReturnNull)) instead of try/catch, with a null constructor triggering an early return before named argument processing.GetDependenciesFromPropertySetterusesGetTypeDefinition()to correctly resolve property setters on generic attribute types.Additionally, dependency decode failures now set an instance
_isCorruptedflag so rewrite can avoid re-processing known-bad blobs.Blob rewriting (
WriteInternal)RewriteCustomAttributeBlobno longer usescustomAttribute.DecodeValuefor rewriting. Instead, it reads the custom attribute blob manually withBlobReader, writes intowriteContext.GetSharedBlobBuilder(), and rewrites only serialized type-name strings where needed (System.Typepayloads 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, rewritingSystem.Object, System.RuntimetoSystem.Object, System.Private.CoreLibviaCustomAttributeTypeNameFormatter).Rewrite now takes advantage of corruption tracking:
_isCorruptedis setLatest follow-up feedback updates also:
switch-based control flowtype.UnderlyingTypein fixed-argument type-code inferenceCustomAttributeTypeProviderindirection from rewrite helpers by using directEcmaModulelookup for serialized type names and underlying enum primitive mapping logic_moduleis no longer threaded through helper parametersorpatterns_isCorruptedand constructor resolution into a single early-out guard in rewriteCode style
if/else if/elseinstead of earlyreturnstatements.EncodeElementTypeswitches ontype.Categoryand handlesIsEnumandSystem.Typein thedefaultcase, rather than checking them before the switch.catch (Exception ex) when (ex is TypeSystemException or BadImageFormatException)to collapse multiple catch blocks.switchstatements per reviewer guidance.type is nullcheck in dependency argument handling.Test results
25 previously-expected-failing tests now pass and have been removed from
ILTrimExpectedFailures.txt(includingAttributes.GenericAttributes).Validation run for this follow-up update:
./build.sh clr+libs -rc releasedotnet build src/coreclr/tools/ILTrim.Core/ILTrim.Core.csprojdotnet test src/coreclr/tools/ILTrim.Tests/ILTrim.Tests.csproj --filter "FullyQualifiedName~AttributesTests"