diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CSharpCodeWriterTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CSharpCodeWriterTest.cs index 2c503f89f8f..8d7c6ff83c3 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CSharpCodeWriterTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/CSharpCodeWriterTest.cs @@ -32,7 +32,7 @@ public static IEnumerable NewLines public void CSharpCodeWriter_TracksPosition_WithWrite() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234"); @@ -48,7 +48,7 @@ public void CSharpCodeWriter_TracksPosition_WithWrite() public void CSharpCodeWriter_TracksPosition_WithIndent() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteLine(); @@ -65,7 +65,7 @@ public void CSharpCodeWriter_TracksPosition_WithIndent() public void CSharpCodeWriter_TracksPosition_WithWriteLine() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteLine("1234"); @@ -83,7 +83,7 @@ public void CSharpCodeWriter_TracksPosition_WithWriteLine() public void CSharpCodeWriter_TracksPosition_WithWriteLine_WithNewLineInContent(string newLine) { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteLine("1234" + newLine + "12"); @@ -104,7 +104,7 @@ public void CSharpCodeWriter_TracksPosition_WithWriteLine_WithNewLineInContent(s public void CSharpCodeWriter_TracksPosition_WithWrite_WithNewlineInContent(string newLine) { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234" + newLine + "123" + newLine + "12"); @@ -124,7 +124,7 @@ public void CSharpCodeWriter_TracksPosition_WithWrite_WithNewlineInContent(strin public void CSharpCodeWriter_TracksPosition_WithWrite_WithNewlineInContent_RepeatedN() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234\n\n123"); @@ -144,7 +144,7 @@ public void CSharpCodeWriter_TracksPosition_WithWrite_WithNewlineInContent_Repea public void CSharpCodeWriter_TracksPosition_WithWrite_WithMixedNewlineInContent() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234\r123\r\n12\n1"); @@ -164,7 +164,7 @@ public void CSharpCodeWriter_TracksPosition_WithWrite_WithMixedNewlineInContent( public void CSharpCodeWriter_TracksPosition_WithNewline_SplitAcrossWrites() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234\r"); @@ -185,7 +185,7 @@ public void CSharpCodeWriter_TracksPosition_WithNewline_SplitAcrossWrites() public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_R() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234\r"); @@ -206,7 +206,7 @@ public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_R() public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_N() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234\n"); @@ -227,7 +227,7 @@ public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_N() public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_Reversed() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("1234\n"); @@ -248,7 +248,7 @@ public void CSharpCodeWriter_TracksPosition_WithTwoNewline_SplitAcrossWrites_Rev public void CSharpCodeWriter_TracksPosition_WithNewline_SplitAcrossWrites_AtBeginning() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("\r"); @@ -269,7 +269,7 @@ public void CSharpCodeWriter_TracksPosition_WithNewline_SplitAcrossWrites_AtBegi public void CSharpCodeWriter_LinesBreaksOutsideOfContentAreNotCounted() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.Write("\r\nHello\r\nWorld\r\n", startIndex: 2, count: 12); @@ -287,7 +287,7 @@ public void WriteLineNumberDirective_UsesFilePath_FromSourceLocation() var filePath = "some-path"; var mappingLocation = new SourceSpan(filePath, 10, 4, 3, 9); - var writer = new CodeWriter(); + using var writer = new CodeWriter(); var expected = $"#line 5 \"{filePath}\"" + writer.NewLine; // Act @@ -302,7 +302,7 @@ public void WriteLineNumberDirective_UsesFilePath_FromSourceLocation() public void WriteField_WritesFieldDeclaration() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteField(Array.Empty(), new[] { "private" }, "global::System.String", "_myString"); @@ -319,7 +319,7 @@ public void WriteField_WritesFieldDeclaration() public void WriteField_WithModifiers_WritesFieldDeclaration() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteField(Array.Empty(), new[] { "private", "readonly", "static" }, "global::System.String", "_myString"); @@ -336,7 +336,7 @@ public void WriteField_WithModifiers_WritesFieldDeclaration() public void WriteField_WithModifiersAndSupressions_WritesFieldDeclaration() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteField( @@ -362,7 +362,7 @@ public void WriteField_WithModifiersAndSupressions_WritesFieldDeclaration() public void WriteAutoPropertyDeclaration_WritesPropertyDeclaration() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteAutoPropertyDeclaration(new[] { "public" }, "global::System.String", "MyString"); @@ -379,7 +379,7 @@ public void WriteAutoPropertyDeclaration_WritesPropertyDeclaration() public void WriteAutoPropertyDeclaration_WithModifiers_WritesPropertyDeclaration() { // Arrange - var writer = new CodeWriter(); + using var writer = new CodeWriter(); // Act writer.WriteAutoPropertyDeclaration(new[] { "public", "static" }, "global::System.String", "MyString"); @@ -402,7 +402,7 @@ public void CSharpCodeWriter_RespectTabSetting() o.IndentSize = 4; }); - var writer = new CodeWriter(Environment.NewLine, options); + using var writer = new CodeWriter(Environment.NewLine, options); // Act writer.BuildClassDeclaration(Array.Empty(), "C", "", Array.Empty(), Array.Empty(), context: null); @@ -428,7 +428,7 @@ public void CSharpCodeWriter_RespectSpaceSetting() o.IndentSize = 4; }); - var writer = new CodeWriter(Environment.NewLine, options); + using var writer = new CodeWriter(Environment.NewLine, options); // Act writer.BuildClassDeclaration(Array.Empty(), "C", "", Array.Empty(), Array.Empty(), context: null); diff --git a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs index b5f5aa95765..1724a5ad318 100644 --- a/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs +++ b/src/Compiler/Microsoft.AspNetCore.Razor.Language/test/CodeGeneration/DesignTimeNodeWriterTest.cs @@ -506,7 +506,7 @@ Render Children public void LinePragma_Is_Adjusted_On_Windows(string fileName, string expectedFileName) { var writer = new DesignTimeNodeWriter(); - var context = TestCodeRenderingContext.CreateDesignTime(); + using var context = TestCodeRenderingContext.CreateDesignTime(); Assert.True(context.Options.RemapLinePragmaPathsOnWindows); @@ -553,7 +553,7 @@ public void LinePragma_Is_Adjusted_On_Windows(string fileName, string expectedFi public void LinePragma_Enhanced_Is_Adjusted_On_Windows(string fileName, string expectedFileName) { var writer = new RuntimeNodeWriter(); - var context = TestCodeRenderingContext.CreateDesignTime(source: RazorSourceDocument.Create("", fileName)); + using var context = TestCodeRenderingContext.CreateDesignTime(source: RazorSourceDocument.Create("", fileName)); Assert.True(context.Options.RemapLinePragmaPathsOnWindows); Assert.True(context.Options.UseEnhancedLinePragma); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs index 6f850bd120b..b0d4dcae50a 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/CodeWriter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -10,13 +11,13 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration; -public sealed partial class CodeWriter +public sealed partial class CodeWriter : IDisposable { // This is the size of each "page", which are arrays of ReadOnlyMemory. // This number was chosen arbitrarily as a "best guess". If changed, care should be // taken to ensure that pages are not allocated on the LOH. ReadOnlyMemory // takes up 16 bytes, so a page size of 1000 is 16k. - private const int PageSize = 1000; + private const int MinimumPageSize = 1000; // Rather than using a StringBuilder, we maintain a linked list of pages, which are arrays // of "chunks of text", represented by ReadOnlyMemory. This avoids copying strings @@ -65,9 +66,17 @@ private void AddTextChunk(ReadOnlyMemory value) } // If we're at the start of a page, we need to add the page first. - var lastPage = _pageOffset == 0 - ? _pages.AddLast(new ReadOnlyMemory[PageSize]).Value - : _pages.Last!.Value; + ReadOnlyMemory[] lastPage; + + if (_pageOffset == 0) + { + lastPage = ArrayPool>.Shared.Rent(MinimumPageSize); + _pages.AddLast(lastPage); + } + else + { + lastPage = _pages.Last!.Value; + } // Add our chunk of text (the ReadOnlyMemory) and increment the offset. lastPage[_pageOffset] = value; @@ -75,7 +84,9 @@ private void AddTextChunk(ReadOnlyMemory value) // We've reached the end of a page, so we reset the offset to 0. // This will cause a new page to be added next time. - if (_pageOffset == PageSize) + // _pageOffset is checked against the lastPage.Length as the Rent call that + // return that array may return an array longer that MinimumPageSize. + if (_pageOffset == lastPage.Length) { _pageOffset = 0; } @@ -370,4 +381,14 @@ public string GenerateCode() return result; } } + + public void Dispose() + { + foreach (var page in _pages) + { + ArrayPool>.Shared.Return(page, clearArray: true); + } + + _pages.Clear(); + } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultCodeRenderingContext.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultCodeRenderingContext.cs index 60fb31990bd..109ab30dd6f 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultCodeRenderingContext.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultCodeRenderingContext.cs @@ -21,17 +21,11 @@ internal class DefaultCodeRenderingContext : CodeRenderingContext private readonly PooledObject.Builder> _sourceMappingsBuilder; public DefaultCodeRenderingContext( - CodeWriter codeWriter, IntermediateNodeWriter nodeWriter, RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode, RazorCodeGenerationOptions options) { - if (codeWriter == null) - { - throw new ArgumentNullException(nameof(codeWriter)); - } - if (nodeWriter == null) { throw new ArgumentNullException(nameof(nodeWriter)); @@ -52,7 +46,6 @@ public DefaultCodeRenderingContext( throw new ArgumentNullException(nameof(options)); } - CodeWriter = codeWriter; _codeDocument = codeDocument; _documentNode = documentNode; Options = options; @@ -69,12 +62,9 @@ public DefaultCodeRenderingContext( Diagnostics.Add(diagnostics[i]); } - var newLineString = codeDocument.Items[NewLineString]; - if (newLineString != null) - { - // Set new line character to a specific string regardless of platform, for testing purposes. - codeWriter.NewLine = (string)newLineString; - } + // Set new line character to a specific string regardless of platform, for testing purposes. + var newLineString = codeDocument.Items[NewLineString] as string ?? Environment.NewLine; + CodeWriter = new CodeWriter(newLineString, options); Items[NewLineString] = codeDocument.Items[NewLineString]; Items[SuppressUniqueIds] = codeDocument.Items[SuppressUniqueIds] ?? options.SuppressUniqueIds; @@ -220,6 +210,7 @@ public override void AddLinePragma(LinePragma linePragma) public override void Dispose() { _sourceMappingsBuilder.Dispose(); + CodeWriter.Dispose(); } private struct ScopeInternal diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultDocumentWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultDocumentWriter.cs index 3b976d4ea4c..72c3b39d0f1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultDocumentWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/CodeGeneration/DefaultDocumentWriter.cs @@ -34,7 +34,6 @@ public override RazorCSharpDocument WriteDocument(RazorCodeDocument codeDocument } using var context = new DefaultCodeRenderingContext( - new CodeWriter(Environment.NewLine, _options), _codeTarget.CreateNodeWriter(), codeDocument, documentNode, diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorHtmlWriter.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorHtmlWriter.cs index aa062c94ca0..39586c97e5f 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorHtmlWriter.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorHtmlWriter.cs @@ -276,5 +276,6 @@ private void WriteNode(TNode node, bool isHtml, Action handler) wh public void Dispose() { _sourceMappingsBuilder.Dispose(); + Builder.Dispose(); } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/CodeGeneration/TestCodeRenderingContext.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/CodeGeneration/TestCodeRenderingContext.cs index 28c534b3234..e4f695cc133 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/CodeGeneration/TestCodeRenderingContext.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/CodeGeneration/TestCodeRenderingContext.cs @@ -15,7 +15,6 @@ public static CodeRenderingContext CreateDesignTime( RazorSourceDocument source = null, IntermediateNodeWriter nodeWriter = null) { - var codeWriter = new CodeWriter(); var documentNode = new DocumentIntermediateNode(); var options = RazorCodeGenerationOptions.CreateDesignTimeDefault(); @@ -40,7 +39,7 @@ public static CodeRenderingContext CreateDesignTime( nodeWriter = new DesignTimeNodeWriter(); } - var context = new DefaultCodeRenderingContext(codeWriter, nodeWriter, codeDocument, documentNode, options); + var context = new DefaultCodeRenderingContext(nodeWriter, codeDocument, documentNode, options); context.Visitor = new RenderChildrenVisitor(context); return context; @@ -52,7 +51,6 @@ public static CodeRenderingContext CreateRuntime( RazorSourceDocument source = null, IntermediateNodeWriter nodeWriter = null) { - var codeWriter = new CodeWriter(); var documentNode = new DocumentIntermediateNode(); var options = RazorCodeGenerationOptions.CreateDefault(); @@ -77,7 +75,7 @@ public static CodeRenderingContext CreateRuntime( nodeWriter = new RuntimeNodeWriter(); } - var context = new DefaultCodeRenderingContext(codeWriter, nodeWriter, codeDocument, documentNode, options); + var context = new DefaultCodeRenderingContext(nodeWriter, codeDocument, documentNode, options); context.Visitor = new RenderChildrenVisitor(context); return context;