Summary
MetadataColumnInt16.GetValue returns a signed short, while the columns it backs are unsigned 2-byte values — the class documents itself as such, DebugTypeName => "USHORT". Bit 15 is meaningful in two of those columns, so the values reach the code model sign-extended, with the upper 16 bits all set.
Diagnosis
public short GetValue( MetadataRow row )
{
...
return *(short*) (row.Address + this.Offset);
}
The column backs Field.Flags, MethodDef.Flags, Param.Flags, Param.Sequence and GenericParam.Flags. Bit 15 is defined in two of them:
FieldAttributes.HasDefault = 0x8000, set on every literal field, i.e. on every const
MethodAttributes.RequireSecObject = 0x8000, set on methods carrying declarative security
ModuleReader casts the result straight to the attribute enumeration:
Attributes = ((FieldAttributes) this.tables.FieldFlagsColumn.GetValue( row )),
A literal field whose flags are 0x8046 is read as -32698 and becomes FieldAttributes 0xFFFF8046.
ReadAssemblyIdentity shows the intended handling for the same class of column, casting explicitly before use:
version = new Version( (ushort) tables.AssemblyMajorVersionColumn.GetValue( assemblyRow ), ... );
Consequence
Masked tests such as (attributes & FieldAttributes.HasDefault) != 0 are unaffected, and the write path truncates back to 16 bits, so the round-trip through PeWriter is lossless. This is why the defect has gone unnoticed.
What is affected is any equality comparison against an Attributes value, and the value handed to aspect authors through the public Attributes properties of FieldDefDeclaration and MethodDefDeclaration and through the reflection wrappers. No concrete breakage has been observed; this is reported as a latent defect rather than as a bug with a known symptom.
Affected source
Core/PostSharp.Compiler.Engine/Sdk/Binary/Metadata/MetadataColumnInt16.cs — GetValue
Reproduction
Emit a module containing a literal field, that is, one whose Flags column has HasDefault, and read it. (int) field.Attributes & ~0xFFFF is -65536 instead of 0.
Proposed fix
Return ushort from GetValue, or have the callers cast, as ReadAssemblyIdentity already does. Note that GetValue is also used by Copy and by Param.Sequence arithmetic, so the change has to be reviewed call site by call site rather than applied blindly.
Environment
- Present on
release/2024.0 and unchanged on release/2026.0.
-- Claude for Gael Fraiteur
Summary
MetadataColumnInt16.GetValuereturns a signedshort, while the columns it backs are unsigned 2-byte values — the class documents itself as such,DebugTypeName => "USHORT". Bit 15 is meaningful in two of those columns, so the values reach the code model sign-extended, with the upper 16 bits all set.Diagnosis
The column backs
Field.Flags,MethodDef.Flags,Param.Flags,Param.SequenceandGenericParam.Flags. Bit 15 is defined in two of them:FieldAttributes.HasDefault=0x8000, set on every literal field, i.e. on everyconstMethodAttributes.RequireSecObject=0x8000, set on methods carrying declarative securityModuleReadercasts the result straight to the attribute enumeration:A literal field whose flags are
0x8046is read as-32698and becomesFieldAttributes0xFFFF8046.ReadAssemblyIdentityshows the intended handling for the same class of column, casting explicitly before use:Consequence
Masked tests such as
(attributes & FieldAttributes.HasDefault) != 0are unaffected, and the write path truncates back to 16 bits, so the round-trip throughPeWriteris lossless. This is why the defect has gone unnoticed.What is affected is any equality comparison against an
Attributesvalue, and the value handed to aspect authors through the publicAttributesproperties ofFieldDefDeclarationandMethodDefDeclarationand through the reflection wrappers. No concrete breakage has been observed; this is reported as a latent defect rather than as a bug with a known symptom.Affected source
Core/PostSharp.Compiler.Engine/Sdk/Binary/Metadata/MetadataColumnInt16.cs—GetValueReproduction
Emit a module containing a literal field, that is, one whose
Flagscolumn hasHasDefault, and read it.(int) field.Attributes & ~0xFFFFis-65536instead of0.Proposed fix
Return
ushortfromGetValue, or have the callers cast, asReadAssemblyIdentityalready does. Note thatGetValueis also used byCopyand byParam.Sequencearithmetic, so the change has to be reviewed call site by call site rather than applied blindly.Environment
release/2024.0and unchanged onrelease/2026.0.-- Claude for Gael Fraiteur