diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index d584666ab62..39d0f31a16f 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -3657,6 +3657,26 @@ internal static class MemberInvocationLoggingOps ); #endif + private const int MaxLoggedArgumentStringLength = 4096; + + private static string LimitLoggedArgumentString(string value) + { + if (value is null || value.Length <= MaxLoggedArgumentStringLength) + { + return value; + } + + string originalLength = value.Length.ToString(CultureInfo.InvariantCulture); + string truncationMarker = string.Concat( + "...".AsSpan()); + int prefixLength = MaxLoggedArgumentStringLength - truncationMarker.Length; + return string.Concat( + value.AsSpan(0, prefixLength), + truncationMarker.AsSpan()); + } + private static string ArgumentToString(object arg) { object baseObj = PSObject.Base(arg); @@ -3669,7 +3689,7 @@ private static string ArgumentToString(object arg) // The comparisons below are ordered by the likelihood of arguments being of those types. if (baseObj is string str) { - return str; + return LimitLoggedArgumentString(str); } // Special case some types to call 'ToString' on the object. For the rest, we return its @@ -3683,7 +3703,7 @@ private static string ArgumentToString(object arg) || baseType == typeof(BigInteger) || baseType == typeof(decimal)) { - return baseObj.ToString(); + return LimitLoggedArgumentString(baseObj.ToString()); } return baseType.FullName; diff --git a/test/powershell/engine/Logging/MemberInvocationLogging.Tests.ps1 b/test/powershell/engine/Logging/MemberInvocationLogging.Tests.ps1 new file mode 100644 index 00000000000..5a788b160ed --- /dev/null +++ b/test/powershell/engine/Logging/MemberInvocationLogging.Tests.ps1 @@ -0,0 +1,63 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Member invocation logging' -Tags 'CI' { + BeforeAll { + $type = [psobject].Assembly.GetType('System.Management.Automation.MemberInvocationLoggingOps') + $argumentToString = $type.GetMethod( + 'ArgumentToString', + [System.Reflection.BindingFlags]'NonPublic, Static', + $null, + [type[]]@([object]), + $null) + $maxLoggedArgumentStringLength = [int]$type.GetField( + 'MaxLoggedArgumentStringLength', + [System.Reflection.BindingFlags]'NonPublic, Static').GetRawConstantValue() + } + + It 'Keeps short string arguments unchanged' { + $value = 'short argument' + + $argumentToString.Invoke($null, [object[]]@($value)) | Should -BeExactly $value + } + + It 'Keeps string arguments at the maximum length unchanged' { + $value = 'a' * $maxLoggedArgumentStringLength + + $argumentToString.Invoke($null, [object[]]@($value)) | Should -BeExactly $value + } + + It 'Limits long string arguments' { + $originalLength = $maxLoggedArgumentStringLength + 904 + $value = 'a' * $originalLength + + $result = $argumentToString.Invoke($null, [object[]]@($value)) + $truncationMarker = "..." + $expectedPrefixLength = $maxLoggedArgumentStringLength - $truncationMarker.Length + + $result.Length | Should -Be $maxLoggedArgumentStringLength + $result.StartsWith(('a' * $expectedPrefixLength), [System.StringComparison]::Ordinal) | Should -BeTrue + $result.EndsWith($truncationMarker, [System.StringComparison]::Ordinal) | Should -BeTrue + } + + It 'Limits string arguments just over the maximum length' { + $originalLength = $maxLoggedArgumentStringLength + 1 + $value = 'a' * $originalLength + + $result = $argumentToString.Invoke($null, [object[]]@($value)) + + $result.Length | Should -Be $maxLoggedArgumentStringLength + $result.Length | Should -BeLessThan $value.Length + $result.EndsWith("...", [System.StringComparison]::Ordinal) | Should -BeTrue + } + + It 'Limits long non-string special-cased arguments' { + $originalLength = $maxLoggedArgumentStringLength + 101 + $value = [System.Numerics.BigInteger]::Pow([System.Numerics.BigInteger]10, $originalLength - 1) + + $result = $argumentToString.Invoke($null, [object[]]@($value)) + + $result.Length | Should -Be $maxLoggedArgumentStringLength + $result.EndsWith("...", [System.StringComparison]::Ordinal) | Should -BeTrue + } +}