Rewrite ConvertToDecimal in XslNumber.cs to use safe code with string.Create and for loop#124794
Rewrite ConvertToDecimal in XslNumber.cs to use safe code with string.Create and for loop#124794
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com>
|
@EgorBot -linux_amd -osx_arm64 using System.IO;
using System.Xml;
using System.Xml.Xsl;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);
[MemoryDiagnoser]
public class Bench
{
private XslCompiledTransform _xslNoGrouping = default!;
private XslCompiledTransform _xslWithGrouping = default!;
private XslCompiledTransform _xslWithPaddingAndGrouping = default!;
private XslCompiledTransform _xslNonAsciiDigits = default!;
private string _xml = "<root/>";
[GlobalSetup]
public void Setup()
{
_xslNoGrouping = Compile("""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/"><xsl:number value="1234567" format="1"/></xsl:template>
</xsl:stylesheet>
""");
_xslWithGrouping = Compile("""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/"><xsl:number value="1234567" format="1" grouping-separator="," grouping-size="3"/></xsl:template>
</xsl:stylesheet>
""");
_xslWithPaddingAndGrouping = Compile("""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/"><xsl:number value="42" format="00001" grouping-separator="," grouping-size="3"/></xsl:template>
</xsl:stylesheet>
""");
_xslNonAsciiDigits = Compile("""
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/"><xsl:number value="1234" format="١"/></xsl:template>
</xsl:stylesheet>
""");
}
private static XslCompiledTransform Compile(string xsl)
{
var t = new XslCompiledTransform();
using var r = XmlReader.Create(new StringReader(xsl));
t.Load(r);
return t;
}
[Benchmark]
public string NoGrouping()
{
var sw = new StringWriter();
using var xr = XmlReader.Create(new StringReader(_xml));
_xslNoGrouping.Transform(xr, null, sw);
return sw.ToString();
}
[Benchmark]
public string WithGroupingSeparator()
{
var sw = new StringWriter();
using var xr = XmlReader.Create(new StringReader(_xml));
_xslWithGrouping.Transform(xr, null, sw);
return sw.ToString();
}
[Benchmark]
public string WithPaddingAndGrouping()
{
var sw = new StringWriter();
using var xr = XmlReader.Create(new StringReader(_xml));
_xslWithPaddingAndGrouping.Transform(xr, null, sw);
return sw.ToString();
}
[Benchmark]
public string NonAsciiDigits()
{
var sw = new StringWriter();
using var xr = XmlReader.Create(new StringReader(_xml));
_xslNonAsciiDigits.Transform(xr, null, sw);
return sw.ToString();
}
} |
There was a problem hiding this comment.
Pull request overview
This PR modernizes the ConvertToDecimal method in XslNumber.cs by replacing unsafe pointer arithmetic with safe managed code using string.Create. The refactoring eliminates the unsafe block, stackalloc, and while(true) loop while preserving the exact same behavior and performance characteristics.
Changes:
- Rewrote
ConvertToDecimalto usestring.Createwith a static lambda and state tuple, replacing unsafe pointer-based string construction - Added comprehensive test coverage with 12 test cases covering all code paths (fast paths, grouping separators, padding, and non-ASCII digit systems)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
XslNumber.cs |
Replaced unsafe pointer arithmetic with safe string.Create pattern; changed while(true) loop to bounds-check-friendly for loop; preserved all fast-path optimizations |
XslCompilerTests.cs |
Added XslNumberDecimalFormatting theory test with 12 cases covering no-op, padding-only, grouping separators, combined padding+grouping, and Arabic-Indic digits |
|
PTAL @stephentoub It seems like this function had an unbound stackalloc with a size that could come from user input + general unsafe code removal. JIT removes bounds check for |
Replaces the
unsafeblock inNumberFormatter.ConvertToDecimal(XslNumber.cs) — which usedstackalloc, raw pointer arithmetic, and awhile (true)loop — with safe code usingstring.Createand a bounds-check-friendlyforloop.Changes
XslNumber.cs:ConvertToDecimalrewritten. Key details:string.Create(newLen, (str, shift, zero, separator, groupSize), static (result, state) => ...)writes directly into the result string, eliminating the intermediate stackalloc buffer copyfor (int newPos = result.Length - 1; newPos >= 0; newPos--)replaces thewhile (true)+ pointer-walk; acntcounter drives grouping-separator insertion identically to the original algorithmgroupSize != 0 && cnt == 0guard correctly handles both the grouping path and the pure-shift path (groupSize == 0, shift != 0) without special-casingreturn str,return str.PadLeft(...)) are preservedXslCompilerTests.cs: AddedXslNumberDecimalFormatting[Theory]with 12 cases covering all code paths throughConvertToDecimal: no-op fast path, padding-only fast path, grouping separator, padding + grouping, and non-ASCII digit system (Arabic-Indic,shift != 0)✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.