// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. namespace System.Text { public partial class StringBuilder { /// /// Calculate the new length for array allocation when marshalling StringBuilder in interop /// while taking current capacity into account /// This is needed to ensure compat with desktop CLR behavior /// internal int GetAllocationLength(int requiredLength) { int currentLength = Capacity; if (currentLength < requiredLength) { // round the current length to the nearest multiple of 2 // that is >= the required length return (requiredLength + 1) & ~1; } return currentLength; } /// /// Throw away the current contents in this StringBuffer and replace it with a new char[] /// NOTE: The buffer in StringBuilder is *not* NULL-terminated. /// This is only called from MCG code /// internal unsafe void ReplaceBuffer(char* newBuffer) { int len = string.wcslen(newBuffer); // the '+1' is for back-compat with desktop CLR in terms of length calculation because desktop // CLR had '\0' char[] chunkChars = new char[GetAllocationLength(len + 1)]; ThreadSafeCopy(newBuffer, chunkChars, 0, len); ReplaceBufferInternal(chunkChars, len); } /// /// Throw away the current contents in this StringBuffer and replace it with a new char[] /// NOTE: The buffer in StringBuilder is *not* NULL-terminated. /// This is only called from MCG code /// internal void ReplaceBuffer(char[] chunkCharsCandidate) { int len = chunkCharsCandidate.Length; int newLen = GetAllocationLength(len + 1); if (len == newLen) { ReplaceBufferInternal(chunkCharsCandidate, len); } else { char[] chunkChars = new char[GetAllocationLength(len + 1)]; ThreadSafeCopy(chunkCharsCandidate, 0, chunkChars, 0, len); ReplaceBufferInternal(chunkChars, len); } } /// /// Replace the internal buffer with the specified length /// This is only called from MCG code /// private void ReplaceBufferInternal(char[] chunkChars, int length) { m_ChunkChars = chunkChars; m_ChunkOffset = 0; m_ChunkLength = length; m_ChunkPrevious = null; } /// /// Return buffer if single chunk, for MCG interop /// internal char[] GetBuffer(out int len) { len = 0; if (m_ChunkOffset == 0) { len = Length; return m_ChunkChars; } else { return null; } } /// /// Copy StringBuilder's contents to the char*, and appending a '\0' /// The destination buffer must be big enough, and include space for '\0' /// NOTE: There is no guarantee the destination pointer has enough size, but we have no choice /// because the pointer might come from native code. /// internal unsafe void UnsafeCopyTo(char* destination) { if (destination == null) { throw new ArgumentNullException(nameof(destination)); } int count = Length; destination[count] = '\0'; StringBuilder chunk = this; int sourceEndIndex = count; int curDestIndex = count; while (count > 0) { int chunkEndIndex = sourceEndIndex - chunk.m_ChunkOffset; if (chunkEndIndex >= 0) { if (chunkEndIndex > chunk.m_ChunkLength) chunkEndIndex = chunk.m_ChunkLength; int chunkCount = count; int chunkStartIndex = chunkEndIndex - count; if (chunkStartIndex < 0) { chunkCount += chunkStartIndex; chunkStartIndex = 0; } curDestIndex -= chunkCount; count -= chunkCount; // SafeCritical: we ensure that chunkStartIndex + chunkCount are within range of m_chunkChars // as well as ensuring that curDestIndex + chunkCount are within range of destination ThreadSafeCopy(chunk.m_ChunkChars, chunkStartIndex, destination + curDestIndex, chunkCount); } chunk = chunk.m_ChunkPrevious; } } unsafe private static void ThreadSafeCopy(char[] source, int sourceIndex, char* destinationPtr, int count) { if (count > 0) { if ((uint)sourceIndex <= (uint)source.Length && (sourceIndex + count) <= source.Length) { unsafe { fixed (char* sourcePtr = &source[sourceIndex]) string.wstrcpy(destinationPtr, sourcePtr, count); } } else { throw new ArgumentOutOfRangeException(nameof(sourceIndex), SR.ArgumentOutOfRange_Index); } } } } }