Add pseudo custom attribute lowering to managed ilasm - #131510
Add pseudo custom attribute lowering to managed ilasm#131510jkoritzinsky wants to merge 4 commits into
Conversation
|
Azure Pipelines: 16 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/jit-contrib |
|
Azure Pipelines: 16 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Adds a pseudo custom attribute lowering pass to managed ilasm so that well-known attributes (e.g., DllImportAttribute, MarshalAsAttribute, MethodImplAttribute, SpecialNameAttribute, StructLayoutAttribute, etc.) are translated into the corresponding metadata flags / auxiliary tables, matching native ilasm emission behavior and validation.
Changes:
- Introduces a new
PseudoCustomAttributes.Lower(...)pass that runs before metadata emission and can dropCustomAttributerows after lowering. - Adds parsing/validation + lowering implementations for a set of known pseudo custom attributes (interop, layout, security) and new diagnostics for invalid targets/values/blobs.
- Expands
ILAssembler.Testscoverage to assert correct flag/table emission and “attribute dropped vs kept” behavior.
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/tools/ilasm/tests/ILAssembler.Tests/PropertyEventTests.cs | Adds tests for SpecialNameAttribute lowering on properties/events. |
| src/tools/ilasm/tests/ILAssembler.Tests/ParameterTests.cs | Adds tests for In/Out/Optional parameter attribute lowering. |
| src/tools/ilasm/tests/ILAssembler.Tests/MethodTests.cs | Adds tests for SpecialNameAttribute and MethodImplAttribute lowering + diagnostics. |
| src/tools/ilasm/tests/ILAssembler.Tests/InteropTests.cs | Adds tests for PreserveSig, DllImport, and MarshalAs lowering/validation behaviors. |
| src/tools/ilasm/tests/ILAssembler.Tests/FieldTests.cs | Adds tests for field pseudo attributes (NonSerialized, SpecialName, FieldOffset). |
| src/tools/ilasm/tests/ILAssembler.Tests/CustomAttributeTests.cs | Adds broad pseudo-CA coverage: type flags, StructLayout, security, ordering, malformed blob rules. |
| src/tools/ilasm/tests/ILAssembler.Tests/AssemblyTests.cs | Adds assembly-level pseudo-CA coverage (TypeLibVersion, etc.) and validation. |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Security.cs | Implements special handling for the two security-name-based attributes. |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.MarshalAs.cs | Implements MarshalAs decoding and FieldMarshal descriptor emission (incl. property fan-out). |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.KnownAttributes.cs | Defines the known pseudo-CA table (targets, fixed args, named arg descriptors). |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Identity.cs | Adds logic to identify attribute types and resolve local owners pre-emission. |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Decoding.cs | Adds CA blob decoding matching native parsing behavior (incl. Everett quirks). |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.cs | Adds the lowering entry point, context, and attribute-row removal orchestration. |
| src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Application.cs | Applies decoded pseudo-CA effects to entities (flags, layouts, imports) + validation. |
| src/tools/ilasm/src/ILAssembler/MetadataExtensions.cs | Adds helper constants/masks used by lowering (e.g., miUserMask, marshalling constants). |
| src/tools/ilasm/src/ILAssembler/GrammarVisitor.cs | Hooks the lowering pass into compilation and records .custom source locations. |
| src/tools/ilasm/src/ILAssembler/EntityRegistry.cs | Adds pre-emission local type-def lookup and supports removing lowered CA rows. |
| src/tools/ilasm/src/ILAssembler/Diagnostic.cs | Adds new diagnostic IDs/messages for pseudo-CA validation failures. |
b1089a0 to
1549a04
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.MarshalAs.cs:164
- TryGetSignatureParameterCount reads the first byte but doesn't account for generic method signatures, where the generic parameter count appears before the parameter count. Handling the generic case avoids misidentifying the setter parameter count for signatures with the generic bit set.
private static unsafe bool TryGetSignatureParameterCount(BlobBuilder? signature, out int count)
{
count = 0;
if (signature is null)
{
return false;
}
byte[] signatureBytes = signature.ToArray();
fixed (byte* signaturePointer = signatureBytes)
{
var reader = new BlobReader(signaturePointer, signatureBytes.Length);
try
{
_ = reader.ReadByte();
return reader.TryReadCompressedInteger(out count);
}
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Application.cs:290
- The DllImportAttribute CallingConvention named argument should fall back to WinApi for unrecognized values (including 0/1), matching the default behavior for DllImport and avoiding emitting a P/Invoke map with no calling convention bits set.
MethodImportAttributes flags = MethodImportAttributes.None;
if (FindNamedArgument(arguments, DllImportCallingConvention) is { } callingConventionArgument)
{
flags = GetUInt32(callingConventionArgument.Value) switch
1549a04 to
9174dea
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Application.cs:102
- In ApplySpecialName, the default branch reports InvalidValue, but reaching the default means the attribute was applied to an unsupported target type (i.e., an invalid target), not an invalid argument value. This would produce the wrong diagnostic if this path is ever hit.
@event.Attributes |= EventAttributes.SpecialName;
return true;
default:
return context.InvalidValue();
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/tools/ilasm/src/ILAssembler/EntityRegistry.cs:844
- FindLocalTypeDefinition treats both ModuleEntity and ModuleReferenceEntity as local. A TypeRef with ResolutionScope=ModuleRef refers to a different module, so resolving it to a local TypeDef can cause pseudo-attribute lowering to target the wrong entity (and potentially drop the attribute row) when the TypeRef is actually external.
case ModuleEntity or ModuleReferenceEntity:
return FindTypeDefinition(null, typeReference.Namespace, typeReference.Name);
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Application.cs:102
- ApplySpecialName reports InvalidValue for unsupported owners, but this condition is about an invalid application target rather than an invalid argument value. Using InvalidTarget here keeps diagnostics consistent with the other pseudo-attribute validations.
return true;
default:
return context.InvalidValue();
}
7f4db0a to
3fe6951
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.Application.cs:102
- In ApplySpecialName, the default branch reports an invalid value, but any non-supported owner kind is an invalid target (consistent with how other pseudo custom attributes validate targets). This affects which diagnostic ID/message users get if an unexpected target slips through.
default:
return context.InvalidValue();
}
5f30a8e to
2b2c3a9
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.cs:140
- There are multiple consecutive blank lines at the end of the file, which adds whitespace noise in a newly introduced source file.
src/tools/ilasm/tests/ILAssembler.Tests/CustomAttributeTests.cs:825 - This
MemberDatasource property allocates a newTheoryDatainstance each time it's accessed. Making it an initialized static property avoids repeated allocations and ensures a single stable instance is used by xUnit.
public static TheoryData<string, TypeAttributes> PseudoAttributeTypeFlagData => new()
| KnownAttributeKind.TypeLibVersion or KnownAttributeKind.ComCompatibleVersion => | ||
| ValidateNonNegative(context, arguments.FixedArguments), | ||
| KnownAttributeKind.SpecialName => ApplySpecialName(context), | ||
| KnownAttributeKind.AllowPartiallyTrustedCallers => true, |
There was a problem hiding this comment.
Do we need to handle AllowPartiallyTrustedCallers given that it does nothing interesting?
| LoweringContext context, | ||
| CustomAttributeValue<SerializationTypeCode> arguments) | ||
| { | ||
| // The value is only validated; the attribute itself is still emitted. |
There was a problem hiding this comment.
I do not think we need bug-for-bug compatibility here and we can drop these validations. I think it is a bug in ilasm that it does not allow you to emit Guid attribute with invalid Guid format.
| string.Format(DiagnosticMessageTemplates.PseudoCustomAttributeRepeatedArgument, AttributeName, argumentName)); | ||
| } | ||
|
|
||
|
|
It is bug-for-bug clone of what the current unmanaged ilasm does. It tries really hard to match every non-sensical corner case. I am not sure whether it is good idea to go that far. |
|
Is the CI expected to pass on each of the sub-PRs? |
It consulted the support from native ilasm as that's our compatibility target. |
Generally yes, but as I just got CI functioning for stacked PRs with the most recent rebase, there's some failures I hadn't validated yet (that I expected CI to catch). I'll move a few of these back to draft for now. |
|
I see -- so what's the review guidance here? Should I have both implementations side-by-side as I read through the code? |
2b2c3a9 to
6ec46a4
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
src/tools/ilasm/src/ILAssembler/PseudoCustomAttributes.MarshalAs.cs:164
- TryGetSignatureParameterCount reads a method signature as: 1 byte calling-convention + 1 compressed int parameter count. For generic methods, ECMA-335 inserts a compressed generic-parameter-count between those, so this will return the wrong parameter count (and can cause MarshalAs-on-property to target the wrong parameter). Parse the signature header and skip the generic-parameter-count when present.
private static unsafe bool TryGetSignatureParameterCount(BlobBuilder? signature, out int count)
{
count = 0;
if (signature is null)
{
return false;
}
byte[] signatureBytes = signature.ToArray();
fixed (byte* signaturePointer = signatureBytes)
{
var reader = new BlobReader(signaturePointer, signatureBytes.Length);
try
{
_ = reader.ReadByte();
return reader.TryReadCompressedInteger(out count);
}
catch (BadImageFormatException)
{
return false;
}
}
}
src/tools/ilasm/src/ILAssembler/Diagnostic.cs:61
- This PR adds new public API surface by introducing additional public DiagnosticIds constants (ILA0034-ILA0039). There is no linked
api-approvedissue/proposal in the PR description, which is normally required for new public API additions. Either link an approved issue/proposal, or make these IDs non-public (or otherwise document/justify why this tool assembly's public surface is exempt).
public const string MissingExportedTypeImplementation = "ILA0031";
public const string KeyFileError = "ILA0032";
public const string TooManyGenericParameters = "ILA0033";
public const string PseudoCustomAttributeInvalidTarget = "ILA0034";
public const string PseudoCustomAttributeInvalidValue = "ILA0035";
public const string PseudoCustomAttributeInvalidBlob = "ILA0036";
public const string PseudoCustomAttributeInvalidGuid = "ILA0037";
public const string PseudoCustomAttributeUnknownArgument = "ILA0038";
public const string PseudoCustomAttributeRepeatedArgument = "ILA0039";
Match native ilasm metadata emission for known pseudo custom attributes, including flag lowering, layout, P/Invoke, marshalling, and validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c177aade-9940-4e79-a9df-6747dcc22bb0
Use a reference type for LoweringContext and remove unnecessary readonly-reference parameter modifiers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: c177aade-9940-4e79-a9df-6747dcc22bb0
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
6ec46a4 to
4d599c4
Compare
| public TypeDefinitionEntity? FindLocalTypeDefinition(TypeReferenceEntity typeReference) | ||
| { | ||
| switch (typeReference.ResolutionScope) | ||
| { | ||
| case TypeReferenceEntity enclosing: |
Match native ilasm metadata emission for known pseudo custom attributes, including flag lowering, layout, P/Invoke, marshalling, and validation.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com
Copilot-Session: c177aade-9940-4e79-a9df-6747dcc22bb0
Stack created with GitHub Stacks CLI • Give Feedback 💬
Note
This PR description was generated with GitHub Copilot.