Skip to content

Fix x:Key values not escaped in source-generated C# string literals#34727

Merged
jfversluis merged 1 commit intomainfrom
fix/34726-xkey-escape-sourcegen
Apr 2, 2026
Merged

Fix x:Key values not escaped in source-generated C# string literals#34727
jfversluis merged 1 commit intomainfrom
fix/34726-xkey-escape-sourcegen

Conversation

@StephaneDelcroix
Copy link
Copy Markdown
Contributor

Description

Fixes #34726

The XAML source generator interpolates x:Key values directly into generated C# string literals without escaping special characters. If an x:Key contains a double quote ("), backslash (\), or control character, the generated C# is syntactically invalid.

Changes

  • SetPropertyHelpers.cs — Escape the x:Key value via CSharpExpressionHelpers.EscapeForString() before interpolating into the generated resources["..."] = ... assignment.
  • KnownMarkups.cs — Same fix for DynamicResource key emission (new DynamicResource("...")).
  • CSharpExpressionHelpers.cs — Changed EscapeForString from private static to internal static so it can be reused from SetPropertyHelpers and KnownMarkups.

Test

Added Maui34726.xaml / .xaml.cs XAML unit test with x:Key values containing double quotes and backslashes:

  • SourceGen path: Verifies the generated code compiles without diagnostics
  • Runtime/XamlC paths: Verifies the resources are accessible by their original (unescaped) key names

Escape x:Key values using CSharpExpressionHelpers.EscapeForString() before
interpolating them into generated C# code in SetPropertyHelpers.AddToResourceDictionary.

Previously, keys containing double quotes, backslashes, or control characters
would produce syntactically invalid generated C#.

Fixes #34726

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 29, 2026 17:47
@github-actions
Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34727

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34727"

Copy link
Copy Markdown
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 an XAML source generator correctness/security issue where x:Key values were emitted into generated C# string literals without escaping, which could produce syntactically invalid generated code for keys containing quotes, backslashes, or control characters.

Changes:

  • Escape x:Key values when emitting ResourceDictionary indexer assignments in sourcegen.
  • Escape DynamicResource keys when emitting new DynamicResource("...") in sourcegen.
  • Reuse the escaping helper by widening EscapeForString visibility and add a regression XAML unit test for quote/backslash keys.

Reviewed changes

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

Show a summary per file
File Description
src/Controls/src/SourceGen/SetPropertyHelpers.cs Escapes resource dictionary keys before writing them into generated C# string literals.
src/Controls/src/SourceGen/KnownMarkups.cs Escapes DynamicResource keys before emitting generated constructor code.
src/Controls/src/SourceGen/CSharpExpressionHelpers.cs Makes EscapeForString reusable outside the defining type.
src/Controls/tests/Xaml.UnitTests/Issues/Maui34726.xaml Adds a repro XAML with x:Key values containing quotes/backslashes.
src/Controls/tests/Xaml.UnitTests/Issues/Maui34726.xaml.cs Adds a regression test covering SourceGen + Runtime/XamlC verification for special-character keys.

Comment on lines +769 to 796
internal static string EscapeForString(string value)
{
var sb = new StringBuilder();
foreach (var c in value)
{
switch (c)
{
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
default: sb.Append(c); break;
case '"':
sb.Append("\\\"");
break;
case '\\':
sb.Append("\\\\");
break;
case '\n':
sb.Append("\\n");
break;
case '\r':
sb.Append("\\r");
break;
case '\t':
sb.Append("\\t");
break;
default:
sb.Append(c);
break;
}
}
return sb.ToString();
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

EscapeForString currently only handles ", \, \n, \r, and \t. Other valid control characters (e.g., \0, \b, \f, \v, or arbitrary chars where char.IsControl(c) is true) will still be emitted verbatim into generated source, which can produce invalid/fragile C# and leaves some of the injection surface described in the issue. Consider escaping all control chars (e.g., use SymbolDisplay.FormatLiteral(value, quote: false) or fall back to \uXXXX for remaining controls).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I do not think these are a problem in C# string literals.

@github-actions
Copy link
Copy Markdown
Contributor

🧪 PR Test Evaluation

Overall Verdict: ⚠️ Tests need improvement

The XAML test covers the ResourceDictionary key setter fix well but leaves the {DynamicResource} key escaping fix (KnownMarkups.cs) without direct coverage — reverting that fix would not cause the test to fail.

👍 / 👎 — Was this evaluation helpful? React to let us know!

📊 Expand Full Evaluation

PR Test Evaluation Report

PR: #34727 — Fix x:Key values not escaped in source-generated C# string literals
Test files evaluated: 2 (Maui34726.xaml, Maui34726.xaml.cs)
Fix files: 3 (CSharpExpressionHelpers.cs, KnownMarkups.cs, SetPropertyHelpers.cs)


Overview of the Fix

The PR applies EscapeForString() in two distinct call sites:

  1. SetPropertyHelpers.cs — when emitting rd["key"] = value for ResourceDictionary entries with x:Key
  2. KnownMarkups.cs — when emitting new DynamicResource("key") for {DynamicResource ...} references

Additionally, EscapeForString was made internal in CSharpExpressionHelpers.cs to allow cross-class access.


1. Fix Coverage — ⚠️

The test XAML defines three ResourceDictionary entries with special-character keys (Key"Quote, Key\Backslash, SimpleKey). This directly exercises the SetPropertyHelpers.cs fix.

However, the XAML contains no {DynamicResource ...} references, so the KnownMarkups.cs fix (escaping the key in new DynamicResource("key")) is never triggered by this test. Reverting only that fix would not cause the test to fail.

The SourceGen path only checks Assert.Empty(result.Diagnostics) — which confirms the generated code compiles, but since no {DynamicResource Key"Quote} element exists in the XAML, the KnownMarkups code path is never generated.

2. Edge Cases & Gaps — ⚠️

Covered:

  • Double-quote (") in x:Key — tested via Key"Quote
  • Backslash (\) in x:Key — tested via Key\Backslash
  • Simple/unescaped key — tested via SimpleKey (regression guard)
  • Both runtime/XamlC and SourceGen inflator paths

Missing:

  • {DynamicResource Key"Quote} (or {DynamicResource Key\Backslash}) — this would directly cover the KnownMarkups.cs fix. Adding (Label TextColor="{DynamicResource Key&quot;Quote}" /) to the XAML and asserting the resulting property value would close this gap.
  • Other escape sequences in keys (\n, \r, \t) — low priority given uncommon usage, but EscapeForString handles them and they could be added as additional entries.

3. Test Type Appropriateness — ✅

XAML unit test is the correct and lightest test type. This fix is entirely in the source generator — no platform context, no UI interaction, no Appium needed.

4. Convention Compliance — ✅

Uses [Collection("Issue")] + [Theory] + [XamlInflatorData] — consistent with the most recent XAML test additions (e.g., Maui34713.xaml.cs). The automated script warning about the naming pattern is a false positive; Maui34726 correctly follows MauiXXXXX naming.

5. Flakiness Risk — ✅ Low

In-process XAML unit test with no device, Appium, or async dependencies. No flakiness risk.

6. Duplicate Coverage — ✅ No duplicates

No existing tests exercise special-character escaping in x:Key source generation.

7. Platform Scope — ✅

Source generator runs cross-platform; XAML unit tests execute on all Helix queues. Appropriate coverage.

8. Assertion Quality — ⚠️

For runtime/XamlC inflators:

Assert.True(page.Resources.ContainsKey("Key\"Quote"));   // ✅ specific
Assert.True(page.Resources.ContainsKey("Key\\Backslash")); // ✅ specific

Good — these assertions would fail if the keys were mis-stored.

For SourceGen inflator:

Assert.Empty(result.Diagnostics);  // ⚠️ confirms compile success, not correctness

This is sufficient to catch the original bug (invalid C# string literal would fail compilation), but it does not verify that the values stored at those keys are correct. A stronger SourceGen assertion could instantiate the generated class and verify Resources.ContainsKey("Key\"Quote") at runtime.

9. Fix-Test Alignment — ⚠️

Fix site What it does Tested?
SetPropertyHelpers.cs Escapes key in rd["key"] = value ✅ Directly tested
KnownMarkups.cs Escapes key in new DynamicResource("key") ❌ Not tested
CSharpExpressionHelpers.cs Made EscapeForString internal ✅ (implicitly, as both fixes call it)

Recommendations

  1. Add a {DynamicResource} reference with a special-char key — Add e.g. (Label TextColor="{DynamicResource Key&quot;Quote}" /) to Maui34726.xaml and assert that the resulting TextColor equals Colors.Red. This directly covers the KnownMarkups.cs fix and ensures it can't be silently reverted.

  2. Strengthen the SourceGen assertion — After Assert.Empty(result.Diagnostics), optionally add Assert.Contains("\\\"Key\\\\\\\"Quote\\\"\\\"", result.GeneratedInitializeComponent()) or similar to verify the escaped string literal appears in the generated code (though this is optional given the compile-check is already a meaningful guard).

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • dc.services.visualstudio.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "dc.services.visualstudio.com"

See Network Configuration for more information.

Note

🔒 Integrity filtering filtered 2 items

Integrity filtering activated and filtered the following items during workflow execution.
This happens when a tool call accesses a resource that does not meet the required integrity or secrecy level of the workflow.

🧪 Test evaluation by Evaluate PR Tests

@StephaneDelcroix
Copy link
Copy Markdown
Contributor Author

/azp run maui-pr-uitests, maui-pr-devicetests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 2 pipeline(s).

@jfversluis jfversluis merged commit 148b9aa into main Apr 2, 2026
165 of 174 checks passed
@jfversluis jfversluis deleted the fix/34726-xkey-escape-sourcegen branch April 2, 2026 12:41
PureWeen added a commit that referenced this pull request Apr 2, 2026
The EscapeForString fix from PR #34727 only escaped x:Key in the
resources["..."] path but missed the AddFactory("...") path,
causing generated C# with unescaped quotes/backslashes in keys.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PureWeen pushed a commit that referenced this pull request Apr 2, 2026
- Replace non-existent PR numbers (#34000, #33500, #33000, #34100)
  with real merged PRs (#34024, #34727, #31202, #28713, #34723)
- Add "in dotnet/maui" to all prompts to prevent agent asking for repo
- All PRs verified as real merged PRs with actual code changes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

xsg Xaml sourceGen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Source Generator: x:Key values not escaped in generated C# string literals

4 participants