Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 2, 2025

Fix STJ source generator producing non-compilable code on some locales

This PR addresses an issue where the System.Text.Json source generator produces non-compilable code when using JsonPropertyOrder with negative values on certain locales (e.g., fi_FI.UTF-8).

Changes Made

  • Explore the repository structure and understand the issue
  • Build and test baseline to verify current state
  • Write a test that reproduces the culture-specific issue
  • Fix the issue by wrapping Emit invocation with InvariantCulture
  • Verify the fix with tests (all 152 unit tests pass)
  • Address feedback about comprehensive culture handling
  • Address code review feedback: clean up formatting and use ThreadCultureChange helper

Technical Details

The issue was in JsonSourceGenerator.Emitter.cs where numeric values were being directly interpolated into generated C# code without specifying culture. On locales like fi_FI, negative numbers use the Unicode minus sign (U+2212) instead of the ASCII hyphen-minus, making the generated code invalid.

Fix:
Wrapped the invocation of emitter.Emit() with code that sets CultureInfo.CurrentCulture to InvariantCulture and restores it in a finally block. This comprehensive approach:

  • Ensures all numeric formatting uses invariant culture throughout the entire emission process
  • Is more future-proof than fixing individual interpolations
  • Properly handles the RS1035 analyzer rule by placing the culture-setting code in the generator invocation files (JsonSourceGenerator.Roslyn4.0.cs and JsonSourceGenerator.Roslyn3.11.cs) with appropriate pragma suppressions

Files Modified:

  1. JsonSourceGenerator.Roslyn4.0.cs: Added try/finally wrapper around Emit invocation with RS1035 pragma suppressions
  2. JsonSourceGenerator.Roslyn3.11.cs: Added try/finally wrapper around Emit invocation (leverages existing file-level RS1035 suppression)
  3. JsonSourceGenerator.Emitter.cs: Removed unnecessary using System.Globalization; since culture is now managed in calling code
  4. JsonSourceGeneratorTests.cs: Added test using ThreadCultureChange helper for proper culture management, fixed issue number (121277)

Tests:

  • Added new unit test NegativeJsonPropertyOrderGeneratesValidCode that tests negative JsonPropertyOrder with fi_FI culture using ThreadCultureChange helper
  • All existing tests continue to pass (152 unit tests)

Fixes #121277

Original prompt

This section details on the original issue you should resolve

<issue_title>STJ source generator produces noncompilable code on some locales</issue_title>
<issue_description>### Description

If JsonPropertyOrder is used with a negative value, and STJ source generation is used, the order number is written to the generated code file using the current culture. Depending on the active locale, this can make the code uncompilable.

For example, on locale fi_FI.UTF-8, negative numbers use the U+2212 minus sign instead of the normal hyphen character. This is not valid C# syntax and therefore the code won't compile.

I ran into this when trying to build ASP.NET Core on my own machine. The ProblemDetails class uses negative JsonPropertyOrder values there.

Similar issue from regex code generation: #113077

Reproduction Steps

  1. Set up this Program.cs file:
using System.Text.Json;
using System.Text.Json.Serialization;

public static class Program
{
    public static void Main()
    {
        var foo = new Foo { Bar = 123 };
        var ser = JsonSerializer.Serialize(foo, FooJsonContext.Default.Foo);
        Console.WriteLine(ser);
    }
}

public class Foo
{
    [JsonPropertyOrder(-1)]
    public int Bar { get; set; }
}

[JsonSerializable(typeof(Foo))]
public partial class FooJsonContext : JsonSerializerContext;
  1. Verify that build works on locale en_US.UTF-8
export LANG="en_US.UTF-8"
dotnet build-server shutdown
dotnet build
  1. Try to build on locale fi_FI.UTF-8, and see the build fail:
export LANG="fi_FI.UTF-8"
dotnet build-server shutdown
dotnet build

Expected behavior

The generated code is valid C# code and compiles on all locales.

Actual behavior

The generated code contains a minus sign (U+2212) character that's not a valid token in C#. This makes the generated code not compile.

FooJsonContext.Foo.g.cs(68,31): error CS1525: Invalid expression term ''
FooJsonContext.Foo.g.cs(68,31): error CS1002: ; expected
FooJsonContext.Foo.g.cs(68,31): error CS1056: Unexpected character '−'
FooJsonContext.Foo.g.cs(68,32): error CS0201: Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement

Regression?

No response

Known Workarounds

No response

Configuration

  • .NET 9.0.9, SDK 9.0.110
  • Arch Linux, kernel 6.17.5-arch1-1, x64

Other information

STJ code generator should probably use CultureInfo.InvariantCulture here:

writer.WriteLine($"properties[{i}].Order = {property.Order};");
</issue_description>

<agent_instructions>First validate there's an issue here by writing a test that fails, then fix the issue and validate that the unmodified test passes. </agent_instructions>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@github-actions github-actions bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Nov 2, 2025
Copilot AI changed the title [WIP] Fix STJ source generator to produce compilable code for locales Fix STJ source generator emitting culture-dependent minus signs for negative JsonPropertyOrder Nov 2, 2025
Copilot AI requested a review from stephentoub November 2, 2025 16:37
Copilot finished work on behalf of stephentoub November 2, 2025 16:37
@stephentoub
Copy link
Member

@copilot, are there any other cases of this in the STJ emitter? Places where something like a number or DateTime or something else that's culture-sensitive may end up rendering with the current culture inappropriately? I'm wondering whether a more encompassing fix might be to instead surround the emitter with setting CultureInfo.CurrentCulture to InvariantCulture (and then setting it back in a finally).

@stephentoub stephentoub marked this pull request as ready for review November 3, 2025 01:20
Copilot AI review requested due to automatic review settings November 3, 2025 01:20
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes a culture-specific bug in the JSON source generator where negative JsonPropertyOrder values could generate invalid C# code in locales that use non-ASCII minus signs (e.g., Finnish locale uses U+2212 instead of U+002D).

Key changes:

  • Modified the source generator to use culture-invariant formatting when emitting property order values
  • Added a comprehensive unit test that validates the fix by testing code generation under the Finnish locale

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs Fixed numeric formatting to use InvariantCulture when generating code for property.Order values
src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs Added test to verify negative JsonPropertyOrder values generate valid code across different locales

Copy link
Contributor Author

Copilot AI commented Nov 3, 2025

@copilot, are there any other cases of this in the STJ emitter? Places where something like a number or DateTime or something else that's culture-sensitive may end up rendering with the current cultur...

I searched through the emitter and found:

  1. Negative numbers: Only property.Order can be negative (already fixed on line 709)
  2. Positive integers: Array indices (i), counts (properties.Count), parameter indices - these are culture-safe since positive ints don't vary
  3. Configuration values: DefaultBufferSize, MaxDepth, IndentSize - all non-negative by design

However, wrapping the entire Emit method with InvariantCulture is better because:

  • More future-proof against new numeric properties
  • Clearer intent that we're generating invariant C# code
  • Simpler than auditing every interpolation

I'll implement that approach - setting CultureInfo.CurrentCulture to InvariantCulture at the start of the Emit method and restoring it in a finally block.

Copilot finished work on behalf of stephentoub November 3, 2025 02:00
@stephentoub
Copy link
Member

I'll implement that approach - setting CultureInfo.CurrentCulture to InvariantCulture at the start of the Emit method and restoring it in a finally block.

@copilot, you didn't make the changes. Please wrap the invocation of the emitter with such a try/finally.

Copy link
Contributor Author

Copilot AI commented Nov 3, 2025

I'll implement that approach - setting CultureInfo.CurrentCulture to InvariantCulture at the start of the Emit method and restoring it in a finally block.

@copilot, you didn't make the changes. P...

I've implemented the try/finally wrapper approach. The Emit invocation is now wrapped with code that sets CultureInfo.CurrentCulture to InvariantCulture in both JsonSourceGenerator.Roslyn4.0.cs and JsonSourceGenerator.Roslyn3.11.cs. This ensures all numeric formatting throughout the emission process uses invariant culture. Fixed in commit a574a56.

Copilot finished work on behalf of stephentoub November 3, 2025 02:32
…atting, use ThreadCultureChange helper

Co-authored-by: stephentoub <[email protected]>
Copilot finished work on behalf of stephentoub November 3, 2025 03:01
Copilot AI requested a review from stephentoub November 3, 2025 03:01
@stephentoub stephentoub added area-System.Text.Json and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Nov 3, 2025
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-text-json, @gregsdennis
See info in area-owners.md if you want to be subscribed.

// This prevents issues such as locale-specific negative signs (e.g., U+2212 in fi-FI)
// from being written to generated source files.
// Note: RS1035 is already disabled at the file level for this Roslyn version.
CultureInfo originalCulture = CultureInfo.CurrentCulture;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephentoub this seems a bit heavy handed to me and I could see this regressing accidentally in the future. Shouldn't we instead fix the individual callsites hitting culture sensitive APIs?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it seem heavy handed? It's making it so that everything is done culture-invariant, which is what we want. Is there ever a situation where we'd want something the generator to be culture-aware to the build machine that ran the generator? Assuming not, the only thing we'd get from doing it per call-site is greater risk of a bug tail and more expensive formatting.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

STJ source generator produces noncompilable code on some locales

4 participants