Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ public void WriteHtmlLiteral_WithinMaxSize_WritesSingleLiteral()
using var context = TestCodeRenderingContext.CreateRuntime();

// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "Hello");
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "Hello".AsMemory());

// Assert
var csharp = context.CodeWriter.GetText().ToString();
Expand All @@ -426,7 +426,7 @@ public void WriteHtmlLiteral_GreaterThanMaxSize_WritesMultipleLiterals()
using var context = TestCodeRenderingContext.CreateRuntime();

// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "Hello World");
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "Hello World".AsMemory());

// Assert
var csharp = context.CodeWriter.GetText().ToString();
Expand All @@ -446,7 +446,7 @@ public void WriteHtmlLiteral_GreaterThanMaxSize_SingleEmojisSplit()
using var context = TestCodeRenderingContext.CreateRuntime();

// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 2, " 👦");
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 2, " 👦".AsMemory());

// Assert
var csharp = context.CodeWriter.GetText().ToString();
Expand All @@ -466,7 +466,7 @@ public void WriteHtmlLiteral_GreaterThanMaxSize_SequencedZeroWithJoinedEmojisSpl
using var context = TestCodeRenderingContext.CreateRuntime();

// Act
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "👩‍👩‍👧‍👧👩‍👩‍👧‍👧");
writer.WriteHtmlLiteral(context, maxStringLiteralLength: 6, "👩‍👩‍👧‍👧👩‍👩‍👧‍👧".AsMemory());

// Assert
var csharp = context.CodeWriter.GetText().ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.PooledObjects;

namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration;

Expand Down Expand Up @@ -251,62 +251,55 @@ public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentI
{
const int MaxStringLiteralLength = 1024;

var builder = new StringBuilder();
for (var i = 0; i < node.Children.Count; i++)
using var htmlContentBuilder = new PooledArrayBuilder<ReadOnlyMemory<char>>();

var length = 0;
foreach (var child in node.Children)
{
if (node.Children[i] is IntermediateToken token && token.IsHtml)
if (child is IntermediateToken token && token.IsHtml)
{
builder.Append(token.Content);
var htmlContent = token.Content.AsMemory();

htmlContentBuilder.Add(htmlContent);
length += htmlContent.Length;
}
}

var content = builder.ToString();
// Can't use a pooled builder here as the memory will be stored in the context.
var content = new char[length];
var contentIndex = 0;
foreach (var htmlContent in htmlContentBuilder)
{
htmlContent.Span.CopyTo(content.AsSpan(contentIndex));
contentIndex += htmlContent.Length;
}

WriteHtmlLiteral(context, MaxStringLiteralLength, content);
WriteHtmlLiteral(context, MaxStringLiteralLength, content.AsMemory());
}

// Internal for testing
internal void WriteHtmlLiteral(CodeRenderingContext context, int maxStringLiteralLength, string literal)
internal void WriteHtmlLiteral(CodeRenderingContext context, int maxStringLiteralLength, ReadOnlyMemory<char> literal)
{
if (literal.Length <= maxStringLiteralLength)
while (literal.Length > maxStringLiteralLength)
{
WriteLiteral(literal);
return;
}
// String is too large, render the string in pieces to avoid Roslyn OOM exceptions at compile time: https://github.com/aspnet/External/issues/54
var lastCharBeforeSplit = literal.Span[maxStringLiteralLength - 1];

// String is too large, render the string in pieces to avoid Roslyn OOM exceptions at compile time: https://github.com/aspnet/External/issues/54
var charactersConsumed = 0;
do
{
var charactersRemaining = literal.Length - charactersConsumed;
var charactersToSubstring = Math.Min(maxStringLiteralLength, charactersRemaining);
var lastCharBeforeSplitIndex = charactersConsumed + charactersToSubstring - 1;
var lastCharBeforeSplit = literal[lastCharBeforeSplitIndex];
// If character at splitting point is a high surrogate, take one less character this iteration
// as we're attempting to split a surrogate pair. This can happen when something like an
// emoji sits on the barrier between splits; if we were to split the emoji we'd end up with
// invalid bytes in our output.
var renderCharCount = char.IsHighSurrogate(lastCharBeforeSplit) ? maxStringLiteralLength - 1 : maxStringLiteralLength;

if (char.IsHighSurrogate(lastCharBeforeSplit))
{
if (charactersRemaining > 1)
{
// Take one less character this iteration. We're attempting to split inbetween a surrogate pair.
// This can happen when something like an emoji sits on the barrier between splits; if we were to
// split the emoji we'd end up with invalid bytes in our output.
charactersToSubstring--;
}
else
{
// The user has an invalid file with a partial surrogate a the splitting point.
// We'll let the invalid character flow but we'll explode later on.
}
}
WriteLiteral(literal[..renderCharCount]);

var textToRender = literal.Substring(charactersConsumed, charactersToSubstring);

WriteLiteral(textToRender);
literal = literal[renderCharCount..];
}

charactersConsumed += textToRender.Length;
} while (charactersConsumed < literal.Length);
WriteLiteral(literal);
return;

void WriteLiteral(string content)
void WriteLiteral(ReadOnlyMemory<char> content)
{
context.CodeWriter
.WriteStartMethodInvocation(WriteHtmlContentMethod)
Expand Down