Skip to content
Closed
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 @@ -131,7 +131,14 @@ private static ProcessModuleCollection GetModules(int processId, bool firstModul

var modules = new ProcessModuleCollection(firstModuleOnly ? 1 : modulesCount);

char[] chars = ArrayPool<char>.Shared.Rent(1024);
#if RELEASE
int minimumLength = Interop.Kernel32.MAX_PATH;
#else
// use a smaller value to ensure that at least for DEBUG builds
// we test the code path that rents a bigger array from ArrayPool
int minimumLength = Interop.Kernel32.MAX_PATH / 4;
#endif
char[]? chars = ArrayPool<char>.Shared.Rent(minimumLength);
try
{
for (int i = 0; i < modulesCount; i++)
Expand Down Expand Up @@ -171,14 +178,29 @@ private static ProcessModuleCollection GetModules(int processId, bool firstModul

module.ModuleName = new string(chars, 0, length);

length = Interop.Kernel32.GetModuleFileNameEx(processHandle, moduleHandle, chars, chars.Length);
while (true)
{
length = Interop.Kernel32.GetModuleFileNameEx(processHandle, moduleHandle, chars, chars.Length);
if (length == chars.Length)
{
minimumLength = chars.Length * 2;
char[] toReturn = chars;
chars = null;
ArrayPool<char>.Shared.Return(toReturn);
Comment on lines +187 to +189
Copy link
Member

Choose a reason for hiding this comment

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

what is the purpose of toReturn - can't you do:

                               ArrayPool<char>.Shared.Return(chars);
                                minimumLength = Math.Min(minimumLength * 2, short.MaxValue);
                                chars = ArrayPool<char>.Shared.Rent(minimumLength);

Copy link
Member

Choose a reason for hiding this comment

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

@danmosemsft This would call Return multiple timeswhenRent` throws an exception.

It would be fine to write it this way if the Return is not inside finally block (ie we let GC take care of collecting the buffer when exception is thrown).

Copy link
Member

Choose a reason for hiding this comment

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

ah yes - tricky. might be worth a comment.

chars = ArrayPool<char>.Shared.Rent(minimumLength);
continue;
}

break;
}

if (length == 0)
{
HandleLastWin32Error();
continue;
}

module.FileName = (length >= 4 && chars[0] == '\\' && chars[1] == '\\' && chars[2] == '?' && chars[3] == '\\') ?
module.FileName = chars.AsSpan().StartsWith(@"\\?\") ?
new string(chars, 4, length - 4) :
new string(chars, 0, length);

Expand All @@ -187,7 +209,10 @@ private static ProcessModuleCollection GetModules(int processId, bool firstModul
}
finally
{
ArrayPool<char>.Shared.Return(chars);
if (chars != null)
{
ArrayPool<char>.Shared.Return(chars);
}
}

return modules;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,32 +92,36 @@ public void ModulesAreDisposedWhenProcessIsDisposed()
Assert.Equal(expectedCount, disposedCount);
}

[ActiveIssue("https://github.com/dotnet/runtime/pull/335059")]
[SkipOnMono("Assembly.LoadFile used the way this test is implemented fails on Mono")]
[ConditionalFact(typeof(PathFeatures), nameof(PathFeatures.AreAllLongPathsAvailable))]
[PlatformSpecific(TestPlatforms.Windows)]
public void LongModuleFileNamesAreSupported()
{
// To be able to test Long Path support for ProcessModule.FileName we need a .dll that has a path >= 260 chars.
// To be able to test Long Path support for ProcessModule.FileName we need a .dll that has a path > 260 chars.
// Since Long Paths support can be disabled (see the ConditionalFact attribute usage above),
// we just copy "LongName.dll" from bin to a temp directory with a long name and load it from there.
// Loading from new path is possible because the type exposed by the assembly is not referenced in any explicit way.
const string libraryName = "LongPath.dll";
const int minPathLength = 261;

string testBinPath = Path.GetDirectoryName(typeof(ProcessModuleTests).Assembly.Location);
string libraryToCopy = Path.Combine(testBinPath, libraryName);
Assert.True(File.Exists(libraryToCopy), $"{libraryName} was not present in bin folder '{testBinPath}'");

string directoryWithLongName = Path.Combine(TestDirectory, new string('a', Math.Max(1, 261 - TestDirectory.Length)));
string directoryWithLongName = Path.Combine(TestDirectory, new string('a', Math.Max(1, minPathLength - TestDirectory.Length)));
Directory.CreateDirectory(directoryWithLongName);

string longNamePath = Path.Combine(directoryWithLongName, libraryName);
Assert.True(longNamePath.Length > 260);
Assert.True(longNamePath.Length > minPathLength);

File.Copy(libraryToCopy, longNamePath);
Assert.True(File.Exists(longNamePath));

Assembly loaded = Assembly.LoadFile(longNamePath);
Assert.Equal(longNamePath, loaded.Location);

Assert.Contains(Process.GetCurrentProcess().Modules.Cast<ProcessModule>(), module => module.FileName == longNamePath);
ProcessModule[] longPathModules = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().Where(module => module.FileName.Contains(libraryName)).ToArray();
Assert.Contains(longPathModules, module => module.FileName == longNamePath);
}
}
}