Skip to content

Commit

Permalink
Merge pull request #2802 from andrewcrawley/pdbgen-progress-reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
siegfriedpammer authored Oct 21, 2022
2 parents 6e70db7 + 4619b99 commit e0c5b20
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public void Report(DecompilationProgress value)
lock (syncObject)
{
completed++;
progress = new ProgressRecord(1, "Decompiling " + fileName, $"Completed {completed} of {value.TotalNumberOfFiles}: {value.Status}") {
PercentComplete = (int)(completed * 100.0 / value.TotalNumberOfFiles)
progress = new ProgressRecord(1, "Decompiling " + fileName, $"Completed {completed} of {value.TotalUnits}: {value.Status}") {
PercentComplete = (int)(completed * 100.0 / value.TotalUnits)
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@
</ItemGroup>

<ItemGroup>
<Content Include="TestCases\PdbGen\ProgressReporting.xml" />
<Content Include="TestCases\PdbGen\ForLoopTests.xml" />
<Content Include="TestCases\PdbGen\CustomPdbId.xml" />
<Content Include="TestCases\PdbGen\HelloWorld.xml" />
Expand Down
58 changes: 54 additions & 4 deletions ICSharpCode.Decompiler.Tests/PdbGenerationTestRunner.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
Expand All @@ -9,13 +8,10 @@
using System.Xml.Linq;

using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Tests.Helpers;
using ICSharpCode.Decompiler.TypeSystem;

using Microsoft.CodeAnalysis.CSharp;
using Microsoft.DiaSymReader.Tools;

using NUnit.Framework;
Expand Down Expand Up @@ -72,6 +68,60 @@ public void CustomPdbId()
}
}

[Test]
public void ProgressReporting()
{
// Generate a PDB for an assembly and validate that the progress reporter is called with reasonable values
(string peFileName, string pdbFileName) = CompileTestCase(nameof(ProgressReporting));

var moduleDefinition = new PEFile(peFileName);
var resolver = new UniversalAssemblyResolver(peFileName, false, moduleDefinition.Metadata.DetectTargetFrameworkId(), null, PEStreamOptions.PrefetchEntireImage);
var decompiler = new CSharpDecompiler(moduleDefinition, resolver, new DecompilerSettings());

var lastFilesWritten = 0;
var totalFiles = -1;

Action<DecompilationProgress> reportFunc = progress => {
if (totalFiles == -1)
{
// Initialize value on first call
totalFiles = progress.TotalUnits;
}

Assert.AreEqual(progress.TotalUnits, totalFiles);
Assert.AreEqual(progress.UnitsCompleted, lastFilesWritten + 1);

lastFilesWritten = progress.UnitsCompleted;
};

using (FileStream pdbStream = File.Open(Path.Combine(TestCasePath, nameof(ProgressReporting) + ".pdb"), FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
pdbStream.SetLength(0);
PortablePdbWriter.WritePdb(moduleDefinition, decompiler, new DecompilerSettings(), pdbStream, noLogo: true, progress: new TestProgressReporter(reportFunc));

pdbStream.Position = 0;
var metadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader();
var generatedPdbId = new BlobContentId(metadataReader.DebugMetadataHeader.Id);
}

Assert.AreEqual(totalFiles, lastFilesWritten);
}

private class TestProgressReporter : IProgress<DecompilationProgress>
{
private Action<DecompilationProgress> reportFunc;

public TestProgressReporter(Action<DecompilationProgress> reportFunc)
{
this.reportFunc = reportFunc;
}

public void Report(DecompilationProgress value)
{
reportFunc(value);
}
}

private void TestGeneratePdb([CallerMemberName] string testName = null)
{
const PdbToXmlOptions options = PdbToXmlOptions.IncludeEmbeddedSources | PdbToXmlOptions.ThrowOnError | PdbToXmlOptions.IncludeTokens | PdbToXmlOptions.ResolveTokens | PdbToXmlOptions.IncludeMethodSpans;
Expand Down
4 changes: 2 additions & 2 deletions ICSharpCode.Decompiler.Tests/TestCases/PdbGen/CustomPdbId.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?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\CustomPdbId.cs" language="C#" checksumAlgorithm="SHA256"><![CDATA[using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.PdbGen;
public class HelloWorld
public class CustomPdbId
{
public static void Main(string[] args)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<symbols>
<files>
<file id="1" name="ICSharpCode.Decompiler.Tests.TestCases.PdbGen\ProgressReporting.cs" language="C#" checksumAlgorithm="SHA256">
<![CDATA[using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.PdbGen;
public class ProgressReporting
{
public static void Main(string[] args)
{
Console.ReadKey();
Console.WriteLine("Hello World!");
Console.ReadKey();
}
}
public class Class1
{
public static void Test()
{
Console.WriteLine("Class1");
}
}
public class Class2
{
public static void Test()
{
Console.WriteLine("Class2");
}
}
public class Class3
{
public static void Test()
{
Console.WriteLine("Class3");
}
}
]]></file>
</files>
</symbols>
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts)
return Path.Combine(dir, file);
}
}, StringComparer.OrdinalIgnoreCase).ToList();
int total = files.Count;
var progress = ProgressIndicator;
var progressReporter = ProgressIndicator;
var progress = new DecompilationProgress { TotalUnits = files.Count, Title = "Exporting project..." };
DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, Settings);
Parallel.ForEach(
Partitioner.Create(files, loadBalance: true),
Expand Down Expand Up @@ -253,7 +253,9 @@ CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts)
throw new DecompilerException(module, $"Error decompiling for '{file.Key}'", innerException);
}
}
progress?.Report(new DecompilationProgress(total, file.Key));
progress.Status = file.Key;
Interlocked.Increment(ref progress.UnitsCompleted);
progressReporter?.Report(progress);
});
return files.Select(f => ("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken));
}
Expand Down Expand Up @@ -705,16 +707,4 @@ public static bool CanUseSdkStyleProjectFormat(PEFile module)
return TargetServices.DetectTargetFramework(module).Moniker != null;
}
}

public readonly struct DecompilationProgress
{
public readonly int TotalNumberOfFiles;
public readonly string Status;

public DecompilationProgress(int total, string status = null)
{
this.TotalNumberOfFiles = total;
this.Status = status ?? "";
}
}
}
26 changes: 24 additions & 2 deletions ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
using System.Reflection.PortableExecutable;
using System.Security.Cryptography;
using System.Text;
using System.Threading;

using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
Expand All @@ -49,7 +50,14 @@ public static bool HasCodeViewDebugDirectoryEntry(PEFile file)
return file.Reader.ReadDebugDirectory().Any(entry => entry.Type == DebugDirectoryEntryType.CodeView);
}

public static void WritePdb(PEFile file, CSharpDecompiler decompiler, DecompilerSettings settings, Stream targetStream, bool noLogo = false, BlobContentId? pdbId = null)
public static void WritePdb(
PEFile file,
CSharpDecompiler decompiler,
DecompilerSettings settings,
Stream targetStream,
bool noLogo = false,
BlobContentId? pdbId = null,
IProgress<DecompilationProgress> progress = null)
{
MetadataBuilder metadata = new MetadataBuilder();
MetadataReader reader = file.Metadata;
Expand All @@ -72,10 +80,24 @@ string BuildFileNameFromTypeName(TypeDefinitionHandle handle)
return Path.Combine(ns, WholeProjectDecompiler.CleanUpFileName(typeName.Name) + ".cs");
}

foreach (var sourceFile in reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName))
var sourceFiles = reader.GetTopLevelTypeDefinitions().GroupBy(BuildFileNameFromTypeName).ToList();
DecompilationProgress currentProgress = new() {
TotalUnits = sourceFiles.Count,
UnitsCompleted = 0,
Title = "Generating portable PDB..."
};

foreach (var sourceFile in sourceFiles)
{
// Generate syntax tree
var syntaxTree = decompiler.DecompileTypes(sourceFile);

if (progress != null)
{
currentProgress.UnitsCompleted++;
progress.Report(currentProgress);
}

if (!syntaxTree.HasChildren)
continue;

Expand Down
48 changes: 48 additions & 0 deletions ICSharpCode.Decompiler/DecompilationProgress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#nullable enable

namespace ICSharpCode.Decompiler
{
/// <summary>
/// Information used for (optional) progress reporting by the decompiler.
/// </summary>
public struct DecompilationProgress
{
/// <summary>
/// The total number of units to process. If set to a value &lt;= 0, an indeterminate progress bar is displayed.
/// </summary>
public int TotalUnits;

/// <summary>
/// The number of units currently completed. Should be a positive number.
/// </summary>
public int UnitsCompleted;

/// <summary>
/// Optional information displayed alongside the progress bar.
/// </summary>
public string? Status;

/// <summary>
/// Optional custom title for the operation.
/// </summary>
public string? Title;
}
}
1 change: 1 addition & 0 deletions ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
<Compile Include="CSharp\Annotations.cs" />
<Compile Include="CSharp\CallBuilder.cs" />
<Compile Include="CSharp\CSharpLanguageVersion.cs" />
<Compile Include="DecompilationProgress.cs" />
<Compile Include="NRTAttributes.cs" />
<Compile Include="PartialTypeInfo.cs" />
<Compile Include="CSharp\ProjectDecompiler\IProjectFileWriter.cs" />
Expand Down
4 changes: 3 additions & 1 deletion ILSpy/Commands/GeneratePdbContextMenuEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ internal static void GeneratePdbForAssembly(LoadedAssembly assembly)
Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task<AvalonEditTextOutput>.Factory.StartNew(() => {
AvalonEditTextOutput output = new AvalonEditTextOutput();
Stopwatch stopwatch = Stopwatch.StartNew();
options.CancellationToken = ct;
using (FileStream stream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write))
{
try
{
var decompiler = new CSharpDecompiler(file, assembly.GetAssemblyResolver(), options.DecompilerSettings);
PortablePdbWriter.WritePdb(file, decompiler, options.DecompilerSettings, stream);
decompiler.CancellationToken = ct;
PortablePdbWriter.WritePdb(file, decompiler, options.DecompilerSettings, stream, progress: options.Progress);
}
catch (OperationCanceledException)
{
Expand Down
9 changes: 9 additions & 0 deletions ILSpy/DecompilationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System;
using System.Threading;

using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX;

Expand Down Expand Up @@ -55,6 +56,14 @@ public class DecompilationOptions
/// </remarks>
public CancellationToken CancellationToken { get; set; }

/// <summary>
/// Gets the progress reporter.
/// </summary>
/// <remarks>
/// If decompilers do not implement progress reporting, an indeterminate wait bar is displayed.
/// </remarks>
public IProgress<DecompilationProgress> Progress { get; set; }

/// <summary>
/// Gets the settings for the decompiler.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions ILSpy/Languages/CSharpLanguage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput
options.DecompilerSettings.UseSdkStyleProjectFormat = false;
}
var decompiler = new ILSpyWholeProjectDecompiler(assembly, options);
decompiler.ProgressIndicator = options.Progress;
return decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken);
}
else
Expand Down
6 changes: 5 additions & 1 deletion ILSpy/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ public SearchPane SearchPane {

public DisplaySettings CurrentDisplaySettings { get; internal set; }

public DecompilationOptions CreateDecompilationOptions() => new DecompilationOptions(CurrentLanguageVersion, CurrentDecompilerSettings, CurrentDisplaySettings);
public DecompilationOptions CreateDecompilationOptions()
{
var decompilerView = DockWorkspace.Instance.ActiveTabPage.Content as IProgress<DecompilationProgress>;
return new DecompilationOptions(CurrentLanguageVersion, CurrentDecompilerSettings, CurrentDisplaySettings) { Progress = decompilerView };
}

public MainWindow()
{
Expand Down
Loading

0 comments on commit e0c5b20

Please sign in to comment.