Skip to content

Commit 1847e86

Browse files
authored
Update WriteConsole to not use stackalloc for buffer with too large size (PowerShell#18084)
1 parent 1a82574 commit 1847e86

File tree

1 file changed

+38
-26
lines changed

1 file changed

+38
-26
lines changed

src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
// On the use of DangerousGetHandle: If the handle has been invalidated, then the API we pass it to will return an error. These
1111
// handles should not be exposed to recycling attacks (because they are not exposed at all), but if they were, the worse they
1212
// could do is diddle with the console buffer.
13-
#pragma warning disable 1634, 1691
1413

1514
using System;
15+
using System.Buffers;
1616
using System.Text;
1717
using System.Runtime.InteropServices;
1818
using System.Management.Automation;
@@ -1479,9 +1479,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat
14791479
bSize.X++;
14801480
SMALL_RECT wRegion = writeRegion;
14811481
wRegion.Right++;
1482-
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
1483-
// get the error code.
1484-
#pragma warning disable 56523
1482+
14851483
result = NativeMethods.WriteConsoleOutput(
14861484
consoleHandle.DangerousGetHandle(),
14871485
characterBuffer,
@@ -1491,9 +1489,6 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat
14911489
}
14921490
else
14931491
{
1494-
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
1495-
// get the error code.
1496-
#pragma warning disable 56523
14971492
result = NativeMethods.WriteConsoleOutput(
14981493
consoleHandle.DangerousGetHandle(),
14991494
characterBuffer,
@@ -1796,9 +1791,7 @@ private static bool ReadConsoleOutputCJKSmall
17961791
readRegion.Top = (short)origin.Y;
17971792
readRegion.Right = (short)(origin.X + bufferSize.X - 1);
17981793
readRegion.Bottom = (short)(origin.Y + bufferSize.Y - 1);
1799-
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
1800-
// get the error code.
1801-
#pragma warning disable 56523
1794+
18021795
bool result = NativeMethods.ReadConsoleOutput(
18031796
consoleHandle.DangerousGetHandle(),
18041797
characterBuffer,
@@ -2474,9 +2467,6 @@ internal static string GetConsoleWindowTitle()
24742467
DWORD result;
24752468
StringBuilder consoleTitle = new StringBuilder((int)bufferSize);
24762469

2477-
// Suppress the PreFAST warning about not using Marshal.GetLastWin32Error() to
2478-
// get the error code.
2479-
#pragma warning disable 56523
24802470
result = NativeMethods.GetConsoleTitle(consoleTitle, bufferSize);
24812471
// If the result is zero, it may mean and error but it may also mean
24822472
// that the window title has been set to null. Since we can't tell the
@@ -2561,12 +2551,16 @@ internal static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan<char
25612551
return;
25622552
}
25632553

2564-
// Native WriteConsole doesn't support output buffer longer than 64K.
2565-
// We need to chop the output string if it is too long.
2566-
int cursor = 0; // This records the chopping position in output string
2567-
const int MaxBufferSize = 16383; // this is 64K/4 - 1 to account for possible width of each character.
2554+
// Native WriteConsole doesn't support output buffer longer than 64K, so we need to chop the output string if it is too long.
2555+
// This records the chopping position in output string.
2556+
int cursor = 0;
2557+
// This is 64K/4 - 1 to account for possible width of each character.
2558+
const int MaxBufferSize = 16383;
2559+
const int MaxStackAllocSize = 512;
25682560
ReadOnlySpan<char> outBuffer;
25692561

2562+
// In case that a new line is required, we try to write out the last chunk and the new-line string together,
2563+
// to avoid one extra call to 'WriteConsole' just for a new line string.
25702564
while (cursor + MaxBufferSize < output.Length)
25712565
{
25722566
outBuffer = output.Slice(cursor, MaxBufferSize);
@@ -2575,19 +2569,37 @@ internal static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan<char
25752569
}
25762570

25772571
outBuffer = output.Slice(cursor);
2572+
if (!newLine)
2573+
{
2574+
WriteConsole(consoleHandle, outBuffer);
2575+
return;
2576+
}
2577+
2578+
char[] rentedArray = null;
2579+
string lineEnding = Environment.NewLine;
2580+
int size = outBuffer.Length + lineEnding.Length;
2581+
2582+
// We expect the 'size' will often be small, and thus optimize that case with 'stackalloc'.
2583+
Span<char> buffer = size <= MaxStackAllocSize ? stackalloc char[size] : default;
25782584

2579-
if (newLine)
2585+
try
25802586
{
2581-
var endOfLine = Environment.NewLine.AsSpan();
2582-
var endOfLineLength = endOfLine.Length;
2583-
Span<char> outBufferLine = stackalloc char[outBuffer.Length + endOfLineLength];
2584-
outBuffer.CopyTo(outBufferLine);
2585-
endOfLine.CopyTo(outBufferLine.Slice(outBufferLine.Length - endOfLineLength));
2586-
WriteConsole(consoleHandle, outBufferLine);
2587+
if (buffer.IsEmpty)
2588+
{
2589+
rentedArray = ArrayPool<char>.Shared.Rent(size);
2590+
buffer = rentedArray.AsSpan().Slice(0, size);
2591+
}
2592+
2593+
outBuffer.CopyTo(buffer);
2594+
lineEnding.CopyTo(buffer.Slice(outBuffer.Length));
2595+
WriteConsole(consoleHandle, buffer);
25872596
}
2588-
else
2597+
finally
25892598
{
2590-
WriteConsole(consoleHandle, outBuffer);
2599+
if (rentedArray is not null)
2600+
{
2601+
ArrayPool<char>.Shared.Return(rentedArray);
2602+
}
25912603
}
25922604
}
25932605

0 commit comments

Comments
 (0)