Browse Source

Fix #2038: Add proper support for Long paths on Windows 10 and limit path segments to 255 characters (with long path support) or 30 characters. If a PathTooLongException is thrown display a better error message on project export.

pull/2431/head
Siegfried Pammer 4 years ago
parent
commit
07199e2ecf
  1. 2
      ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml
  2. 2
      ICSharpCode.Decompiler.Tests/TestCases/PdbGen/HelloWorld.xml
  3. 2
      ICSharpCode.Decompiler.Tests/TestCases/PdbGen/LambdaCapturing.xml
  4. 89
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  5. 2
      ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
  6. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  7. 1
      ILSpy/ILSpy.csproj
  8. 11
      ILSpy/Properties/Resources.Designer.cs
  9. 5
      ILSpy/Properties/Resources.resx
  10. 7
      ILSpy/SolutionWriter.cs
  11. 26
      ILSpy/TextView/DecompilerTextView.cs
  12. 4
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  13. 2
      ILSpy/app.manifest

2
ICSharpCode.Decompiler.Tests/TestCases/PdbGen/ForLoopTests.xml

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<symbols>
<files>
<file id="1" name="ICSharpCode.Decompiler.Tests.TestCases.PdbGen\ForLoopTests.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
<file id="1" name="ICSharpCode\Decompiler\Tests\TestCases\PdbGen\ForLoopTests.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.PdbGen
{

2
ICSharpCode.Decompiler.Tests/TestCases/PdbGen/HelloWorld.xml

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<symbols>
<files>
<file id="1" name="ICSharpCode.Decompiler.Tests.TestCases.PdbGen\HelloWorld.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
<file id="1" name="ICSharpCode\Decompiler\Tests\TestCases\PdbGen\HelloWorld.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.PdbGen
{

2
ICSharpCode.Decompiler.Tests/TestCases/PdbGen/LambdaCapturing.xml

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<symbols>
<files>
<file id="1" name="ICSharpCode.Decompiler.Tests.TestCases.PdbGen\LambdaCapturing.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
<file id="1" name="ICSharpCode\Decompiler\Tests\TestCases\PdbGen\LambdaCapturing.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.PdbGen
{

89
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -36,6 +36,8 @@ using ICSharpCode.Decompiler.Solution; @@ -36,6 +36,8 @@ using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using Microsoft.Win32;
using static ICSharpCode.Decompiler.Metadata.MetadataExtensions;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
@ -126,7 +128,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -126,7 +128,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
// per-run members
HashSet<string> directories = new HashSet<string>(Platform.FileNameComparer);
readonly IProjectFileWriter projectWriter;
public void DecompileProject(PEFile moduleDefinition, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken))
@ -252,7 +253,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -252,7 +253,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
#region WriteResourceFilesInProject
protected virtual IEnumerable<(string itemType, string fileName)> WriteResourceFilesInProject(Metadata.PEFile module)
{
foreach (var r in module.Resources.Where(r => r.ResourceType == Metadata.ResourceType.Embedded))
foreach (var r in module.Resources.Where(r => r.ResourceType == ResourceType.Embedded))
{
Stream stream = r.TryOpenStream();
stream.Position = 0;
@ -268,7 +269,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -268,7 +269,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
foreach (var (name, value) in resourcesFile)
{
string fileName = Path.Combine(name.Split('/').Select(p => CleanUpFileName(p)).ToArray());
string fileName = CleanUpFileName(name)
.Replace('/', Path.DirectorySeparatorChar);
string dirName = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName))
{
@ -359,8 +361,15 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -359,8 +361,15 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
string GetFileNameForResource(string fullName)
{
string[] splitName = fullName.Split('.');
string fileName = CleanUpFileName(fullName);
// Clean up the name first and ensure the length does not exceed the maximum length
// supported by the OS.
fullName = CleanUpDirectoryName(fullName);
// The purpose of the below algorithm is to "maximize" the directory name and "minimize" the file name.
// That is, a full name of the form "Namespace1.Namespace2{...}.NamespaceN.ResourceName" is split such that
// the directory part Namespace1\Namespace2\... reuses as many existing directories as
// possible, and only the remaining name parts are used as prefix for the filename.
string[] splitName = fullName.Split(Path.DirectorySeparatorChar);
string fileName = fullName;
string separator = Path.DirectorySeparatorChar.ToString();
for (int i = splitName.Length - 1; i > 0; i--)
{
@ -368,7 +377,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -368,7 +377,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
if (directories.Contains(ns))
{
string name = string.Join(".", splitName, i, splitName.Length - i);
fileName = Path.Combine(ns, CleanUpFileName(name));
fileName = Path.Combine(ns, name);
break;
}
}
@ -526,10 +535,50 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -526,10 +535,50 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
#endregion
static readonly Lazy<(bool longPathsEnabled, int maxPathLength, int maxSegmentLength)> longPathSupport =
new Lazy<(bool longPathsEnabled, int maxPathLength, int maxSegmentLength)>(GetLongPathSupport, isThreadSafe: true);
static (bool longPathsEnabled, int maxPathLength, int maxSegmentLength) GetLongPathSupport()
{
try
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.MacOSX:
case PlatformID.Unix:
return (true, int.MaxValue, int.MaxValue);
case PlatformID.Win32NT:
const string key = @"SYSTEM\CurrentControlSet\Control\FileSystem";
var fileSystem = Registry.LocalMachine.OpenSubKey(key);
var value = (int?)fileSystem.GetValue("LongPathsEnabled");
if (value == 1)
{
return (true, int.MaxValue, 255);
}
return (false, 200, 30);
default:
return (false, 200, 30);
}
}
catch
{
return (false, 200, 30);
}
}
/// <summary>
/// Cleans up a node name for use as a file name.
/// </summary>
public static string CleanUpFileName(string text)
{
return CleanUpFileName(text, separateAtDots: false);
}
/// <summary>
/// Cleans up a node name for use as a file system name. If <paramref name="separateAtDots"/> is active,
/// dots are seen as segment separators. Each segment is limited to 255 characters.
/// </summary>
static string CleanUpFileName(string text, bool separateAtDots)
{
int pos = text.IndexOf(':');
if (pos > 0)
@ -540,16 +589,38 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -540,16 +589,38 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
text = text.Trim();
// Whitelist allowed characters, replace everything else:
StringBuilder b = new StringBuilder(text.Length);
int currentSegmentLength = 0;
var (supportsLongPaths, maxPathLength, maxSegmentLength) = longPathSupport.Value;
foreach (var c in text)
{
currentSegmentLength++;
if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
{
// if the current segment exceeds 255 characters,
// skip until the end of the segment.
if (currentSegmentLength <= maxSegmentLength)
b.Append(c);
}
else if (c == '.' && b.Length > 0 && b[b.Length - 1] != '.')
{
// if the current segment exceeds 255 characters,
// skip until the end of the segment.
if (separateAtDots || currentSegmentLength <= maxSegmentLength)
b.Append('.'); // allow dot, but never two in a row
// Reset length at end of segment.
if (separateAtDots)
currentSegmentLength = 0;
}
else
{
// if the current segment exceeds 255 characters,
// skip until the end of the segment.
if (currentSegmentLength <= maxSegmentLength)
b.Append('-');
if (b.Length >= 200)
break; // limit to 200 chars
}
if (b.Length >= maxPathLength && !supportsLongPaths)
break; // limit to 200 chars, if long paths are not supported.
}
if (b.Length == 0)
b.Append('-');
@ -567,7 +638,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -567,7 +638,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
public static string CleanUpDirectoryName(string text)
{
return CleanUpFileName(text).Replace('.', Path.DirectorySeparatorChar);
return CleanUpFileName(text, separateAtDots: true).Replace('.', Path.DirectorySeparatorChar);
}
static bool IsReservedFileSystemName(string name)

2
ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs

@ -66,7 +66,7 @@ namespace ICSharpCode.Decompiler.DebugInfo @@ -66,7 +66,7 @@ namespace ICSharpCode.Decompiler.DebugInfo
string BuildFileNameFromTypeName(TypeDefinitionHandle handle)
{
var typeName = handle.GetFullTypeName(reader).TopLevelTypeName;
return Path.Combine(WholeProjectDecompiler.CleanUpFileName(typeName.Namespace), WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs");
return Path.Combine(WholeProjectDecompiler.CleanUpDirectoryName(typeName.Namespace), WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs");
}
foreach (var sourceFile in reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName))

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -53,6 +53,7 @@ @@ -53,6 +53,7 @@
<Import Project="..\packages.props" />
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutableVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataVersion)" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">

1
ILSpy/ILSpy.csproj

@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<ApplicationIcon>Images\ILSpy-Large.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<SignAssembly>True</SignAssembly>
<AssemblyOriginatorKeyFile>..\ICSharpCode.Decompiler\ICSharpCode.Decompiler.snk</AssemblyOriginatorKeyFile>

11
ILSpy/Properties/Resources.Designer.cs generated

@ -2010,6 +2010,17 @@ namespace ICSharpCode.ILSpy.Properties { @@ -2010,6 +2010,17 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Failed to decompile the assemblies {0} because the namespace directory structure is nested too deep.
///
///If you are using Windows 10.0.14393 (Windows 10 version 1607) or later, you can enable &quot;Long path support&quot; by creating a REG_DWORD registry key named &quot;LongPathsEnabled&quot; with value 0x1 at &quot;HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem&quot; (see https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation for more information)..
/// </summary>
public static string ProjectExportPathTooLong {
get {
return ResourceManager.GetString("ProjectExportPathTooLong", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to for ex. property getter/setter access. To get optimal decompilation results, please manually add the missing references to the list of loaded assemblies..
/// </summary>

5
ILSpy/Properties/Resources.resx

@ -691,6 +691,11 @@ Please disable all filters that might hide the item (i.e. activate "View &gt; Sh @@ -691,6 +691,11 @@ Please disable all filters that might hide the item (i.e. activate "View &gt; Sh
<data name="ProjectExportFormatSDKHint" xml:space="preserve">
<value>A SDK-style project was generated. Learn more at https://docs.microsoft.com/en-us/nuget/resources/check-project-format.</value>
</data>
<data name="ProjectExportPathTooLong" xml:space="preserve">
<value>Failed to decompile the assemblies {0} because the namespace directory structure is nested too deep.
If you are using Windows 10.0.14393 (Windows 10 version 1607) or later, you can enable "Long path support" by creating a REG_DWORD registry key named "LongPathsEnabled" with value 0x1 at "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem" (see https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation for more information).</value>
</data>
<data name="PropertyManuallyMissingReferencesListLoadedAssemblies" xml:space="preserve">
<value>for ex. property getter/setter access. To get optimal decompilation results, please manually add the missing references to the list of loaded assemblies.</value>
</data>

7
ILSpy/SolutionWriter.cs

@ -229,6 +229,13 @@ namespace ICSharpCode.ILSpy @@ -229,6 +229,13 @@ namespace ICSharpCode.ILSpy
statusOutput.Add("-------------");
statusOutput.Add($"Failed to decompile the assembly '{loadedAssembly.FileName}':{Environment.NewLine}{e.Message}");
}
catch (PathTooLongException e)
{
statusOutput.Add("-------------");
statusOutput.Add(string.Format(Properties.Resources.ProjectExportPathTooLong, loadedAssembly.FileName)
+ Environment.NewLine + Environment.NewLine
+ e.ToString());
}
catch (Exception e) when (!(e is OperationCanceledException))
{
statusOutput.Add("-------------");

26
ILSpy/TextView/DecompilerTextView.cs

@ -1079,8 +1079,14 @@ namespace ICSharpCode.ILSpy.TextView @@ -1079,8 +1079,14 @@ namespace ICSharpCode.ILSpy.TextView
{
bool originalProjectFormatSetting = context.Options.DecompilerSettings.UseSdkStyleProjectFormat;
context.Options.EscapeInvalidIdentifiers = true;
AvalonEditTextOutput output = new AvalonEditTextOutput {
EnableHyperlinks = true,
Title = string.Join(", ", context.TreeNodes.Select(n => n.Text))
};
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
try
{
using (StreamWriter w = new StreamWriter(fileName))
{
try
@ -1093,12 +1099,20 @@ namespace ICSharpCode.ILSpy.TextView @@ -1093,12 +1099,20 @@ namespace ICSharpCode.ILSpy.TextView
w.WriteLine(Properties.Resources.DecompilationWasCancelled);
throw;
}
catch (PathTooLongException pathTooLong) when (context.Options.SaveAsProjectDirectory != null)
{
output.WriteLine(Properties.Resources.ProjectExportPathTooLong, string.Join(", ", context.TreeNodes.Select(n => n.Text)));
output.WriteLine();
output.WriteLine(pathTooLong.ToString());
tcs.SetResult(output);
return;
}
}
}
finally
{
stopwatch.Stop();
AvalonEditTextOutput output = new AvalonEditTextOutput {
EnableHyperlinks = true,
Title = string.Join(", ", context.TreeNodes.Select(n => n.Text))
};
}
output.WriteLine(Properties.Resources.DecompilationCompleteInF1Seconds, stopwatch.Elapsed.TotalSeconds);
if (context.Options.SaveAsProjectDirectory != null)
@ -1106,9 +1120,13 @@ namespace ICSharpCode.ILSpy.TextView @@ -1106,9 +1120,13 @@ namespace ICSharpCode.ILSpy.TextView
output.WriteLine();
bool useSdkStyleProjectFormat = context.Options.DecompilerSettings.UseSdkStyleProjectFormat;
if (useSdkStyleProjectFormat)
{
output.WriteLine(Properties.Resources.ProjectExportFormatSDKHint);
}
else
{
output.WriteLine(Properties.Resources.ProjectExportFormatNonSDKHint);
}
output.WriteLine(Properties.Resources.ProjectExportFormatChangeSettingHint);
if (originalProjectFormatSetting != useSdkStyleProjectFormat)
{

4
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -342,11 +342,11 @@ namespace ICSharpCode.ILSpy.TreeNodes @@ -342,11 +342,11 @@ namespace ICSharpCode.ILSpy.TreeNodes
{
HandleException(badImage, "This file does not contain a managed assembly.");
}
catch (FileNotFoundException fileNotFound)
catch (FileNotFoundException fileNotFound) when (options.SaveAsProjectDirectory == null)
{
HandleException(fileNotFound, "The file was not found.");
}
catch (DirectoryNotFoundException dirNotFound)
catch (DirectoryNotFoundException dirNotFound) when (options.SaveAsProjectDirectory == null)
{
HandleException(dirNotFound, "The directory was not found.");
}

2
ILSpy/app.manifest

@ -56,7 +56,7 @@ @@ -56,7 +56,7 @@
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True</dpiAware>
<!-- Enable long path support: https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/ -->
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">True</longPathAware>
</windowsSettings>
</application>

Loading…
Cancel
Save