Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CA1838 Avoid 'StringBuilder' parameters for P/Invokes #7186

Merged
merged 25 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
994b696
CA1838 Avoid 'StringBuilder' parameters for P/Invokes
elachlan Dec 30, 2021
4b519a9
revert ruleset change
elachlan Jan 7, 2022
3a88ed9
rebase
elachlan Jan 7, 2022
638775f
Enable warning on CA1838
elachlan Jan 7, 2022
751fea3
Merge branch 'main' into CA1838
elachlan Jan 11, 2022
661c2c8
CA1838 Avoid 'StringBuilder' parameters for P/Invokes. Consider using…
elachlan Jan 11, 2022
4f0424c
trying again
elachlan Jan 12, 2022
e042e66
Changes from review
elachlan Jan 27, 2022
4ddf661
GetGacPath changes
elachlan Jan 27, 2022
8f0c31c
Making sure we are form strings properly
elachlan Jan 28, 2022
04e4a1b
changes from review
elachlan Jan 28, 2022
903f35d
Remove unused field
elachlan Jan 28, 2022
69cf05f
Use ArrayPool in GetRuntimeVersion and fix usage in GetModuleFileName
elachlan Jan 28, 2022
300179b
Refactor GetRuntimeVersion to remove loop and use stackalloc
elachlan Jan 28, 2022
c392277
changes from review
elachlan Jan 31, 2022
de3618c
Fix conflict and xml doc
elachlan Jan 31, 2022
a0178d7
Merge branch 'main' into CA1838
elachlan Jan 31, 2022
54878ac
Fix possible StackOverflow in GetShortPathName/GetLongPathName by mov…
elachlan Feb 1, 2022
567cc08
Merge branch 'CA1838' of github.com:elachlan/msbuild into CA1838
elachlan Feb 1, 2022
8af24e5
remove unneeded unsafe from pinvoke definitions
elachlan Feb 1, 2022
031af51
Added cached GAC Path and changes from review
elachlan Feb 2, 2022
b4f6bf7
Add documentation to Pinvoke
elachlan Feb 3, 2022
b5f60db
Changes from review
elachlan Feb 4, 2022
53d3da4
Change from review
elachlan Feb 8, 2022
0a97bfd
Add VerifyThrow instead of loop condition
elachlan Feb 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Common.globalconfig
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ dotnet_diagnostic.CA1836.severity = warning
dotnet_diagnostic.CA1837.severity = suggestion

# Avoid 'StringBuilder' parameters for P/Invokes
dotnet_diagnostic.CA1838.severity = suggestion
dotnet_diagnostic.CA1838.severity = warning

# Dispose objects before losing scope
dotnet_diagnostic.CA2000.severity = none
Expand Down
50 changes: 13 additions & 37 deletions src/Framework/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ internal static class NativeMethods
internal const int MAX_PATH = 260;

private const string kernel32Dll = "kernel32.dll";
private const string mscoreeDLL = "mscoree.dll";

private const string WINDOWS_FILE_SYSTEM_REGISTRY_KEY = @"SYSTEM\CurrentControlSet\Control\FileSystem";
private const string WINDOWS_LONG_PATHS_ENABLED_VALUE_NAME = "LongPathsEnabled";
Expand Down Expand Up @@ -950,13 +949,13 @@ internal static string GetShortFilePath(string path)

if (length > 0)
{
StringBuilder fullPathBuffer = new StringBuilder(length);
char[] fullPathBuffer = new char[length];
elachlan marked this conversation as resolved.
Show resolved Hide resolved
elachlan marked this conversation as resolved.
Show resolved Hide resolved
elachlan marked this conversation as resolved.
Show resolved Hide resolved
length = GetShortPathName(path, fullPathBuffer, length);
errorCode = Marshal.GetLastWin32Error();

if (length > 0)
{
string fullPath = fullPathBuffer.ToString();
string fullPath = new(fullPathBuffer, 0, length);
path = fullPath;
}
}
Expand Down Expand Up @@ -989,13 +988,13 @@ internal static string GetLongFilePath(string path)

if (length > 0)
{
StringBuilder fullPathBuffer = new StringBuilder(length);
char[] fullPathBuffer = new char[length];
length = GetLongPathName(path, fullPathBuffer, length);
errorCode = Marshal.GetLastWin32Error();

if (length > 0)
{
string fullPath = fullPathBuffer.ToString();
string fullPath = new(fullPathBuffer, 0, length);
path = fullPath;
}
}
Expand Down Expand Up @@ -1084,7 +1083,7 @@ DateTime LastWriteFileUtcTime(string path)

if (success && (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0)
{
long dt = ((long) (data.ftLastWriteTimeHigh) << 32) | ((long) data.ftLastWriteTimeLow);
long dt = ((long)(data.ftLastWriteTimeHigh) << 32) | ((long)data.ftLastWriteTimeLow);
fileModifiedTime = DateTime.FromFileTimeUtc(dt);

// If file is a symlink _and_ we're not instructed to do the wrong thing, get a more accurate timestamp.
Expand Down Expand Up @@ -1468,17 +1467,6 @@ internal static void VerifyThrowWin32Result(int result)
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);

[DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint SearchPath
(
string path,
string fileName,
string extension,
int numBufferChars,
[Out] StringBuilder buffer,
int[] filePart
);

[DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool FreeLibrary([In] IntPtr module);
Expand All @@ -1489,26 +1477,14 @@ int[] filePart
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)]
internal static extern IntPtr LoadLibrary(string fileName);

[DllImport(mscoreeDLL, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint GetRequestedRuntimeInfo(String pExe,
String pwszVersion,
String pConfigurationFile,
uint startupFlags,
uint runtimeInfoFlags,
[Out] StringBuilder pDirectory,
int dwDirectory,
out uint dwDirectoryLength,
[Out] StringBuilder pVersion,
int cchBuffer,
out uint dwlength);

/// <summary>
/// Gets the fully qualified filename of the currently executing .exe
/// Gets the fully qualified filename of the currently executing .exe.
/// </summary>
/// <param name="hModule"><see cref="HandleRef"/> of the module for which we are finding the file name.</param>
/// <param name="buffer">The character buffer used to return the file name.</param>
/// <param name="length">The length of the buffer.</param>
[DllImport(kernel32Dll, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern int GetModuleFileName(
HandleRef hModule,
[Out] StringBuilder buffer, int length);
internal static extern int GetModuleFileName(HandleRef hModule, [Out] char[] buffer, int length);

[DllImport("kernel32.dll")]
internal static extern IntPtr GetStdHandle(int nStdHandle);
Expand Down Expand Up @@ -1557,10 +1533,10 @@ internal static bool SetCurrentDirectory(string path)
private static extern bool GlobalMemoryStatusEx([In, Out] MemoryStatus lpBuffer);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
internal static extern int GetShortPathName(string path, [Out] StringBuilder fullpath, [In] int length);
internal static extern int GetShortPathName(string path, [Out] char[] fullpath, [In] int length);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
internal static extern int GetLongPathName([In] string path, [Out] StringBuilder fullpath, [In] int length);
internal static extern int GetLongPathName([In] string path, [Out] char[] fullpath, [In] int length);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern bool CreatePipe(out SafeFileHandle hReadPipe, out SafeFileHandle hWritePipe, SecurityAttributes lpPipeAttributes, int nSize);
Expand Down Expand Up @@ -1648,7 +1624,7 @@ internal static bool MsgWaitOne(this WaitHandle handle, int timeout)

if (!(returnValue == 0 || ((uint)returnValue == RPC_S_CALLPENDING && timeout != Timeout.Infinite)))
{
throw new InternalErrorException($"Received {returnValue} from CoWaitForMultipleHandles, but expected 0 (S_OK)");
throw new InternalErrorException($"Received {returnValue} from CoWaitForMultipleHandles, but expected 0 (S_OK)");
}

return returnValue == 0;
Expand Down
56 changes: 31 additions & 25 deletions src/Tasks/AssemblyDependency/AssemblyInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,30 +569,36 @@ internal static string GetRuntimeVersion(string path)
#if FEATURE_MSCOREE
if (NativeMethodsShared.IsWindows)
{
StringBuilder runtimeVersion;
uint hresult;
#if DEBUG
// Just to make sure and exercise the code that doubles the size
// every time GetRequestedRuntimeInfo fails due to insufficient buffer size.
// Just to make sure and exercise the code that uses dwLength to allocate the buffer
// when GetRequestedRuntimeInfo fails due to insufficient buffer size.
int bufferLength = 1;
#else
int bufferLength = 11; // 11 is the length of a runtime version and null terminator v2.0.50727/0
elachlan marked this conversation as resolved.
Show resolved Hide resolved
#endif
do
{
runtimeVersion = new StringBuilder(bufferLength);
hresult = NativeMethods.GetFileVersion(path, runtimeVersion, bufferLength, out _);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, I'm not a fan of this code, so I'm glad it's gone 😁

bufferLength *= 2;
} while (hresult == NativeMethodsShared.ERROR_INSUFFICIENT_BUFFER);

if (hresult == NativeMethodsShared.S_OK)
{
return runtimeVersion.ToString();
}
else
unsafe
{
return String.Empty;
}
// Allocate an initial buffer
char* runtimeVersionInitial = stackalloc char[bufferLength];

// Run GetFileVersion, this should succeed using the initial buffer.
// It also returns the dwLength which is used if there is insufficient buffer.
uint hresult = NativeMethods.GetFileVersion(path, runtimeVersionInitial, bufferLength, out int dwLength);

if (hresult == NativeMethodsShared.ERROR_INSUFFICIENT_BUFFER)
{
// Allocate new buffer based on the returned length.
char* runtimeVersion = stackalloc char[dwLength];

// Get the RuntimeVersion in this second call.
bufferLength = dwLength;
hresult = NativeMethods.GetFileVersion(path, runtimeVersion, bufferLength, out dwLength);
return hresult == NativeMethodsShared.S_OK ? new string(runtimeVersion, 0, dwLength - 1) : string.Empty;
elachlan marked this conversation as resolved.
Show resolved Hide resolved
}

return hresult == NativeMethodsShared.S_OK ? new string(runtimeVersionInitial, 0, dwLength - 1) : string.Empty;
}
}
else
{
Expand All @@ -601,14 +607,14 @@ internal static string GetRuntimeVersion(string path)
#else
return ManagedRuntimeVersionReader.GetRuntimeVersion(path);
#endif
}
}


/// <summary>
/// Import assembly dependencies.
/// </summary>
/// <returns>The array of assembly dependencies.</returns>
private AssemblyNameExtension[] ImportAssemblyDependencies()
/// <summary>
/// Import assembly dependencies.
/// </summary>
/// <returns>The array of assembly dependencies.</returns>
private AssemblyNameExtension[] ImportAssemblyDependencies()
elachlan marked this conversation as resolved.
Show resolved Hide resolved
{
#if !FEATURE_ASSEMBLYLOADCONTEXT
var asmRefs = new List<AssemblyNameExtension>();
Expand Down Expand Up @@ -790,7 +796,7 @@ private static AssemblyNameExtension ConstructAssemblyName(IntPtr asmMetaPtr, ch
// Construct the assembly name. (Note asmNameLength should/must be > 0.)
var assemblyName = new AssemblyName
{
Name = new string(asmNameBuf, 0, (int) asmNameLength - 1),
Name = new string(asmNameBuf, 0, (int)asmNameLength - 1),
Version = new Version(
asmMeta.usMajorVersion,
asmMeta.usMinorVersion,
Expand Down Expand Up @@ -911,7 +917,7 @@ public static string GetRuntimeVersion(string path)
// Read the PE header signature

sr.BaseStream.Position = peHeaderOffset;
if (!ReadBytes(sr, (byte) 'P', (byte) 'E', 0, 0))
if (!ReadBytes(sr, (byte)'P', (byte)'E', 0, 0))
{
return string.Empty;
}
Expand Down
14 changes: 8 additions & 6 deletions src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,16 +367,18 @@ bool specificVersion
}

/// <summary>
/// Return the root path of the GAC
/// Return the root path of the GAC.
/// </summary>
internal static string GetGacPath()
{
int gacPathLength = 0;
NativeMethods.GetCachePath(AssemblyCacheFlags.GAC, null, ref gacPathLength);
StringBuilder gacPath = new StringBuilder(gacPathLength);
NativeMethods.GetCachePath(AssemblyCacheFlags.GAC, gacPath, ref gacPathLength);

return gacPath.ToString();
unsafe
{
NativeMethods.GetCachePath(AssemblyCacheFlags.GAC, null, ref gacPathLength);
char* gacPath = stackalloc char[gacPathLength];
NativeMethods.GetCachePath(AssemblyCacheFlags.GAC, gacPath, ref gacPathLength);
return new string(gacPath, 0, gacPathLength - 1);
}
}
}
}
27 changes: 19 additions & 8 deletions src/Tasks/ComReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -407,21 +407,32 @@ internal static string StripTypeLibNumberFromPath(string typeLibPath, FileExists
private static string GetModuleFileName(IntPtr handle)
{
bool success = false;
elachlan marked this conversation as resolved.
Show resolved Hide resolved
var buffer = new StringBuilder();
char[] buffer = null;
string output = string.Empty;

// Try increased buffer sizes if on longpath-enabled Windows
for (int bufferSize = NativeMethodsShared.MAX_PATH; !success && bufferSize <= NativeMethodsShared.MaxPath; bufferSize *= 2)
elachlan marked this conversation as resolved.
Show resolved Hide resolved
{
buffer.EnsureCapacity(bufferSize);

var handleRef = new System.Runtime.InteropServices.HandleRef(buffer, handle);
int pathLength = NativeMethodsShared.GetModuleFileName(handleRef, buffer, buffer.Capacity);
buffer = System.Buffers.ArrayPool<char>.Shared.Rent(bufferSize);
elachlan marked this conversation as resolved.
Show resolved Hide resolved
try
{
var handleRef = new System.Runtime.InteropServices.HandleRef(buffer, handle);
int pathLength = NativeMethodsShared.GetModuleFileName(handleRef, buffer, bufferSize);

bool isBufferTooSmall = ((uint)Marshal.GetLastWin32Error() == NativeMethodsShared.ERROR_INSUFFICIENT_BUFFER);
success = pathLength != 0 && !isBufferTooSmall;
bool isBufferTooSmall = (uint)Marshal.GetLastWin32Error() == NativeMethodsShared.ERROR_INSUFFICIENT_BUFFER;
success = pathLength != 0 && !isBufferTooSmall;
if (success)
{
output = new string(buffer, 0, pathLength);
elachlan marked this conversation as resolved.
Show resolved Hide resolved
}
}
finally
{
System.Buffers.ArrayPool<char>.Shared.Return(buffer);
}
}

return success ? buffer.ToString() : string.Empty;
return output;
}

/// <summary>
Expand Down
17 changes: 12 additions & 5 deletions src/Tasks/LockCheck.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@ internal enum ApplicationType
string[] rgsServiceNames);

[DllImport(RestartManagerDll, CharSet = CharSet.Unicode)]
private static extern int RmStartSession(out uint pSessionHandle,
int dwSessionFlags, StringBuilder strSessionKey);
private static extern unsafe int RmStartSession(
out uint pSessionHandle,
int dwSessionFlags,
char* strSessionKey);

[DllImport(RestartManagerDll)]
private static extern int RmEndSession(uint pSessionHandle);
Expand Down Expand Up @@ -207,11 +209,16 @@ internal static IEnumerable<ProcessInfo> GetLockingProcessInfos(params string[]
}

const int maxRetries = 6;
uint handle;
int res;

// See http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx.
var key = new StringBuilder(new string('\0', CCH_RM_SESSION_KEY + 1));
unsafe
{
// See http://blogs.msdn.com/b/oldnewthing/archive/2012/02/17/10268840.aspx.
char* key = stackalloc char[CCH_RM_SESSION_KEY + 1];
elachlan marked this conversation as resolved.
Show resolved Hide resolved
res = RmStartSession(out handle, 0, key);
}

int res = RmStartSession(out uint handle, 0, key);
if (res != 0)
{
throw GetException(res, "RmStartSession", "Failed to begin restart manager session.");
Expand Down
16 changes: 10 additions & 6 deletions src/Tasks/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1058,8 +1058,12 @@ internal static bool AllDrivesMapped()
/// and then again to pass the client-allocated character buffer. StringBuilder is the most straightforward way
/// to allocate a mutable buffer of characters and pass it around.
/// </summary>
/// <param name="cacheFlags">Value that indicates the source of the cached assembly.</param>
/// <param name="cachePath">The returned pointer to the path.</param>
/// <param name="pcchPath">The requested maximum length of CachePath, and upon return, the actual length of CachePath.</param>
///
[DllImport("fusion.dll", CharSet = CharSet.Unicode)]
internal static extern int GetCachePath(AssemblyCacheFlags cacheFlags, StringBuilder cachePath, ref int pcchPath);
internal static extern unsafe int GetCachePath(AssemblyCacheFlags cacheFlags, [Out] char* cachePath, ref int pcchPath);
#endif

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -1125,15 +1129,15 @@ internal static bool AllDrivesMapped()

#if FEATURE_MSCOREE
/// <summary>
/// Get the runtime version for a given file
/// Get the runtime version for a given file.
/// </summary>
/// <param name="szFullPath">The path of the file to be examined</param>
/// <param name="szFileName">The path of the file to be examined.</param>
/// <param name="szBuffer">The buffer allocated for the version information that is returned.</param>
/// <param name="cchBuffer">The size, in wide characters, of szBuffer</param>
/// <param name="cchBuffer">The size, in wide characters, of szBuffer.</param>
/// <param name="dwLength">The size, in bytes, of the returned szBuffer.</param>
/// <returns>HResult</returns>
/// <returns>HResult.</returns>
elachlan marked this conversation as resolved.
Show resolved Hide resolved
[DllImport(MscoreeDLL, SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern uint GetFileVersion(String szFullPath, StringBuilder szBuffer, int cchBuffer, out uint dwLength);
internal static extern unsafe uint GetFileVersion([MarshalAs(UnmanagedType.LPWStr)] string szFileName, [Out] char* szBuffer, int cchBuffer, out int dwLength);
#endif
#endregion

Expand Down