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.

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions