Skip to content

MetadataColumnInt16 reads unsigned Flags columns as signed, sign-extending FieldAttributes and MethodAttributes #74

Description

@gfraiteur

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.csGetValue

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions