Skip to content

Source generator formats integral enum underlying values with ambient culture (non-deterministic generated source) #459

Description

@eiriktsarpalis

Summary

In the source generator, integral enum underlying values are formatted into generated source using the ambient culture. EnumValueToString pins InvariantCulture for the float/double cases but falls back to a culture-sensitive object.ToString() for the integral cases. The result is written verbatim into the generated C# source, so on a build host configured with a non-invariant culture the generated output can differ for negative or large enum values (e.g. a non-ASCII negative sign), undermining deterministic / reproducible builds.

Root cause

src/PolyType.SourceGenerator/Parser/Parser.ModelMapper.cs:791-797

private static string EnumValueToString(object underlyingValue)
    => underlyingValue switch
    {
        float f => f.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
        double d => d.ToString("R", System.Globalization.CultureInfo.InvariantCulture),
        _ => underlyingValue.ToString(),   // ambient culture for byte/sbyte/short/ushort/int/uint/long/ulong
    };

The boxed integral value's ToString() uses CultureInfo.CurrentCulture's NumberFormatInfo, whose negative sign (and other formatting) is not guaranteed to be ASCII/invariant.

This value flows into the emitted model and is written directly into source:

  • Constructed at Parser.ModelMapper.cs:36
    Members = enumModel.Members.ToImmutableEquatableDictionary(m => m.Key, m => EnumValueToString(m.Value)),
  • Emitted at src/PolyType.SourceGenerator/SourceFormatter/SourceFormatter.Enum.cs:57
    writer.WriteLine($"""["{member.Key}"] = {member.Value},""");

Impact

  • Reproducibility / determinism: generated source for an enum with negative or large underlying values can vary by build-host culture. This is a (minor, low-likelihood) violation of the generator's "deterministic, culture-independent generation" property.
  • Not a runtime-data / untrusted-input concern — the enum metadata is control-plane.

Repro sketch

Build a project containing an enum with a negative underlying value (e.g. enum E : int { A = -1 }) on a host with a culture whose NumberFormatInfo.NegativeSign is non-ASCII (some ar-* cultures). The generated member dictionary entry can contain a non-ASCII negative sign instead of -.

Suggested fix

Format the integral fallback with InvariantCulture as well, e.g.:

_ => ((IFormattable)underlyingValue).ToString(null, System.Globalization.CultureInfo.InvariantCulture),

(all the boxed integral primitives implement IFormattable). A regression test that formats a negative-valued enum under a non-invariant CultureInfo.CurrentCulture and asserts ASCII output would lock this down.


Found during a threat-model review of the core programming model and the two built-in shape providers.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions