Browse Source

implement proper decompilation of event handlers in XAML;

API changes: Language API now uses LoadedAssembly
pull/155/head
Siegfried Pammer 14 years ago
parent
commit
60c93bda40
  1. 60
      ILSpy/BamlDecompiler.cs
  2. 23
      ILSpy/CSharpLanguage.cs
  3. 2
      ILSpy/Commands.cs
  4. 124
      ILSpy/ConnectMethodDecompiler.cs
  5. 6
      ILSpy/ILLanguage.cs
  6. 1
      ILSpy/ILSpy.csproj
  7. 6
      ILSpy/Language.cs
  8. 2
      ILSpy/TreeNodes/AssemblyTreeNode.cs

60
ILSpy/BamlDecompiler.cs

@ -37,7 +37,7 @@ namespace ICSharpCode.ILSpy.Baml
[Conditional("DEBUG")] [Conditional("DEBUG")]
static void Log(string format, params object[] args) static void Log(string format, params object[] args)
{ {
//Debug.WriteLine(format, args); Debug.WriteLine(format, args);
} }
sealed class XamlObjectNode : XamlNode sealed class XamlObjectNode : XamlNode
@ -241,8 +241,23 @@ namespace ICSharpCode.ILSpy.Baml
} }
} }
public string DecompileBaml(MemoryStream bamlCode, string containingAssemblyFile) AssemblyResolver asmResolver;
Assembly AssemblyResolve(object sender, ResolveEventArgs args)
{ {
string path = asmResolver.FindAssembly(args.Name);
if (path == null)
return null;
return Assembly.LoadFile(path);
}
public string DecompileBaml(MemoryStream bamlCode, string containingAssemblyFile, ConnectMethodDecompiler connectMethodDecompiler, AssemblyResolver asmResolver)
{
this.asmResolver = asmResolver;
AppDomain.CurrentDomain.AssemblyResolve += AssemblyResolve;
bamlCode.Position = 0; bamlCode.Position = 0;
TextWriter w = new StringWriter(); TextWriter w = new StringWriter();
@ -250,8 +265,13 @@ namespace ICSharpCode.ILSpy.Baml
Baml2006Reader reader = new Baml2006Reader(bamlCode, new XamlReaderSettings() { ValuesMustBeString = true, LocalAssembly = assembly }); Baml2006Reader reader = new Baml2006Reader(bamlCode, new XamlReaderSettings() { ValuesMustBeString = true, LocalAssembly = assembly });
var xamlDocument = Parse(reader); var xamlDocument = Parse(reader);
string bamlTypeName = xamlDocument.OfType<XamlObjectNode>().First().Type.UnderlyingType.FullName;
var eventMappings = connectMethodDecompiler.DecompileEventMappings(bamlTypeName);
foreach (var xamlNode in xamlDocument) { foreach (var xamlNode in xamlDocument) {
RemoveConnectionIds(xamlNode, eventMappings);
AvoidContentProperties(xamlNode); AvoidContentProperties(xamlNode);
MoveXKeyToFront(xamlNode); MoveXKeyToFront(xamlNode);
} }
@ -277,6 +297,36 @@ namespace ICSharpCode.ILSpy.Baml
return doc.ToString(); return doc.ToString();
} }
void RemoveConnectionIds(XamlNode node, Dictionary<int, EventRegistration[]> eventMappings)
{
foreach (XamlNode child in node.Children)
RemoveConnectionIds(child, eventMappings);
XamlObjectNode obj = node as XamlObjectNode;
if (obj != null && obj.Children.Count > 0) {
var removableNodes = new List<XamlMemberNode>();
var addableNodes = new List<XamlMemberNode>();
foreach (XamlMemberNode memberNode in obj.Children.OfType<XamlMemberNode>()) {
if (memberNode.Member == XamlLanguage.ConnectionId && memberNode.Children.Single() is XamlValueNode) {
var value = memberNode.Children.Single() as XamlValueNode;
int id;
if (value.Value is string && int.TryParse(value.Value as string, out id) && eventMappings.ContainsKey(id)) {
var map = eventMappings[id];
foreach (var entry in map) {
var member = new XamlMemberNode(obj.Type.GetMember(entry.EventName));
member.Children.Add(new XamlValueNode(entry.MethodName));
addableNodes.Add(member);
}
removableNodes.Add(memberNode);
}
}
}
foreach (var rnode in removableNodes)
node.Children.Remove(rnode);
node.Children.InsertRange(node.Children.Count > 1 ? node.Children.Count - 1 : 0, addableNodes);
}
}
/// <summary> /// <summary>
/// Changes all references from oldNamespace to newNamespace in the document. /// Changes all references from oldNamespace to newNamespace in the document.
/// </summary> /// </summary>
@ -345,7 +395,7 @@ namespace ICSharpCode.ILSpy.Baml
data.Position = 0; data.Position = 0;
data.CopyTo(bamlStream); data.CopyTo(bamlStream);
output.Write(decompiler.DecompileBaml(bamlStream, asm.FileName)); output.Write(decompiler.DecompileBaml(bamlStream, asm.FileName, new ConnectMethodDecompiler(asm), new AssemblyResolver(asm)));
return true; return true;
} finally { } finally {
if (bamlDecompilerAppDomain != null) if (bamlDecompilerAppDomain != null)
@ -358,7 +408,7 @@ namespace ICSharpCode.ILSpy.Baml
if (appDomain == null) { if (appDomain == null) {
// Construct and initialize settings for a second AppDomain. // Construct and initialize settings for a second AppDomain.
AppDomainSetup bamlDecompilerAppDomainSetup = new AppDomainSetup(); AppDomainSetup bamlDecompilerAppDomainSetup = new AppDomainSetup();
bamlDecompilerAppDomainSetup.ApplicationBase = "file:///" + Path.GetDirectoryName(assemblyFileName); // bamlDecompilerAppDomainSetup.ApplicationBase = "file:///" + Path.GetDirectoryName(assemblyFileName);
bamlDecompilerAppDomainSetup.DisallowBindingRedirects = false; bamlDecompilerAppDomainSetup.DisallowBindingRedirects = false;
bamlDecompilerAppDomainSetup.DisallowCodeDownload = true; bamlDecompilerAppDomainSetup.DisallowCodeDownload = true;
bamlDecompilerAppDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; bamlDecompilerAppDomainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
@ -366,7 +416,7 @@ namespace ICSharpCode.ILSpy.Baml
// Create the second AppDomain. // Create the second AppDomain.
appDomain = AppDomain.CreateDomain("BamlDecompiler AD", null, bamlDecompilerAppDomainSetup); appDomain = AppDomain.CreateDomain("BamlDecompiler AD", null, bamlDecompilerAppDomainSetup);
} }
return (BamlDecompiler)appDomain.CreateInstanceFromAndUnwrap(typeof(BamlDecompiler).Assembly.Location, typeof(BamlDecompiler).FullName); return (BamlDecompiler)appDomain.CreateInstanceAndUnwrap(typeof(BamlDecompiler).Assembly.FullName, typeof(BamlDecompiler).FullName);
} }
} }
} }

23
ILSpy/CSharpLanguage.cs

@ -31,6 +31,7 @@ using System.Xml;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Ast; using ICSharpCode.Decompiler.Ast;
using ICSharpCode.Decompiler.Ast.Transforms; using ICSharpCode.Decompiler.Ast.Transforms;
using ICSharpCode.ILSpy.Baml;
using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp;
using Mono.Cecil; using Mono.Cecil;
@ -127,19 +128,19 @@ namespace ICSharpCode.ILSpy
codeDomBuilder.GenerateCode(output); codeDomBuilder.GenerateCode(output);
} }
public override void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options) public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{ {
if (options.FullDecompilation && options.SaveAsProjectDirectory != null) { if (options.FullDecompilation && options.SaveAsProjectDirectory != null) {
HashSet<string> directories = new HashSet<string>(StringComparer.OrdinalIgnoreCase); HashSet<string> directories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var files = WriteCodeFilesInProject(assembly, options, directories).ToList(); var files = WriteCodeFilesInProject(assembly.AssemblyDefinition, options, directories).ToList();
files.AddRange(WriteResourceFilesInProject(assembly, fileName, options, directories)); files.AddRange(WriteResourceFilesInProject(assembly, options, directories));
WriteProjectFile(new TextOutputWriter(output), files, assembly.MainModule); WriteProjectFile(new TextOutputWriter(output), files, assembly.AssemblyDefinition.MainModule);
} else { } else {
base.DecompileAssembly(assembly, fileName, output, options); base.DecompileAssembly(assembly, output, options);
// don't automatically load additional assemblies when an assembly node is selected in the tree view // don't automatically load additional assemblies when an assembly node is selected in the tree view
using (options.FullDecompilation ? null : LoadedAssembly.DisableAssemblyLoad()) { using (options.FullDecompilation ? null : LoadedAssembly.DisableAssemblyLoad()) {
AstBuilder codeDomBuilder = CreateAstBuilder(options, currentModule: assembly.MainModule); AstBuilder codeDomBuilder = CreateAstBuilder(options, currentModule: assembly.AssemblyDefinition.MainModule);
codeDomBuilder.AddAssembly(assembly, onlyAssemblyLevel: !options.FullDecompilation); codeDomBuilder.AddAssembly(assembly.AssemblyDefinition, onlyAssemblyLevel: !options.FullDecompilation);
codeDomBuilder.RunTransformations(transformAbortCondition); codeDomBuilder.RunTransformations(transformAbortCondition);
codeDomBuilder.GenerateCode(output); codeDomBuilder.GenerateCode(output);
} }
@ -316,11 +317,11 @@ namespace ICSharpCode.ILSpy
#endregion #endregion
#region WriteResourceFilesInProject #region WriteResourceFilesInProject
IEnumerable<Tuple<string, string>> WriteResourceFilesInProject(AssemblyDefinition assembly, string assemblyFileName, DecompilationOptions options, HashSet<string> directories) IEnumerable<Tuple<string, string>> WriteResourceFilesInProject(LoadedAssembly assembly, DecompilationOptions options, HashSet<string> directories)
{ {
AppDomain bamlDecompilerAppDomain = null; AppDomain bamlDecompilerAppDomain = null;
try { try {
foreach (EmbeddedResource r in assembly.MainModule.Resources.OfType<EmbeddedResource>()) { foreach (EmbeddedResource r in assembly.AssemblyDefinition.MainModule.Resources.OfType<EmbeddedResource>()) {
string fileName; string fileName;
Stream s = r.GetResourceStream(); Stream s = r.GetResourceStream();
s.Position = 0; s.Position = 0;
@ -342,10 +343,10 @@ namespace ICSharpCode.ILSpy
if (fileName.EndsWith(".baml", StringComparison.OrdinalIgnoreCase)) { if (fileName.EndsWith(".baml", StringComparison.OrdinalIgnoreCase)) {
MemoryStream ms = new MemoryStream(); MemoryStream ms = new MemoryStream();
entryStream.CopyTo(ms); entryStream.CopyTo(ms);
var decompiler = Baml.BamlResourceEntryNode.CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, assemblyFileName); var decompiler = Baml.BamlResourceEntryNode.CreateBamlDecompilerInAppDomain(ref bamlDecompilerAppDomain, assembly.FileName);
string xaml = null; string xaml = null;
try { try {
xaml = decompiler.DecompileBaml(ms, assemblyFileName); xaml = decompiler.DecompileBaml(ms, assembly.FileName, new ConnectMethodDecompiler(assembly), new AssemblyResolver(assembly));
} catch (XamlXmlWriterException) {} // ignore XAML writer exceptions } catch (XamlXmlWriterException) {} // ignore XAML writer exceptions
if (xaml != null) { if (xaml != null) {
File.WriteAllText(Path.Combine(options.SaveAsProjectDirectory, Path.ChangeExtension(fileName, ".xaml")), xaml); File.WriteAllText(Path.Combine(options.SaveAsProjectDirectory, Path.ChangeExtension(fileName, ".xaml")), xaml);

2
ILSpy/Commands.cs

@ -87,7 +87,7 @@ namespace ICSharpCode.ILSpy
using (var writer = new System.IO.StreamWriter("c:\\temp\\decompiled\\" + asm.ShortName + ".cs")) { using (var writer = new System.IO.StreamWriter("c:\\temp\\decompiled\\" + asm.ShortName + ".cs")) {
try { try {
new CSharpLanguage().DecompileAssembly( new CSharpLanguage().DecompileAssembly(
asm.AssemblyDefinition, asm.FileName, new Decompiler.PlainTextOutput(writer), asm, new Decompiler.PlainTextOutput(writer),
new DecompilationOptions { FullDecompilation = true, CancellationToken = ct }); new DecompilationOptions { FullDecompilation = true, CancellationToken = ct });
} catch (Exception ex) { } catch (Exception ex) {
writer.WriteLine(ex.ToString()); writer.WriteLine(ex.ToString());

124
ILSpy/ConnectMethodDecompiler.cs

@ -0,0 +1,124 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.ILAst;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace ICSharpCode.ILSpy.Baml
{
[Serializable]
sealed class EventRegistration
{
public string EventName, MethodName;
}
/// <summary>
/// Description of ConnectMethodDecompiler.
/// </summary>
sealed class ConnectMethodDecompiler : MarshalByRefObject
{
LoadedAssembly assembly;
public ConnectMethodDecompiler(LoadedAssembly assembly)
{
this.assembly = assembly;
}
public Dictionary<int, EventRegistration[]> DecompileEventMappings(string fullTypeName)
{
var result = new Dictionary<int, EventRegistration[]>();
TypeDefinition type = this.assembly.AssemblyDefinition.MainModule.GetType(fullTypeName);
if (type == null)
return result;
MethodDefinition def = null;
foreach (var method in type.Methods) {
if (method.Name == "System.Windows.Markup.IComponentConnector.Connect") {
def = method;
break;
}
}
if (def == null)
return result;
// decompile method and optimize the switch
ILBlock ilMethod = new ILBlock();
ILAstBuilder astBuilder = new ILAstBuilder();
ilMethod.Body = astBuilder.Build(def, true);
ILAstOptimizer optimizer = new ILAstOptimizer();
var context = new DecompilerContext(type.Module) { CurrentMethod = def, CurrentType = type };
optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.RemoveRedundantCode3);
var cases = ilMethod.Body.OfType<ILSwitch>().First().CaseBlocks;
foreach (var caseBlock in cases) {
if (caseBlock.Values == null)
continue;
var events = new List<EventRegistration>();
foreach (var node in caseBlock.Body) {
var expr = node as ILExpression;
string eventName, handlerName;
if (IsAddEvent(expr, out eventName, out handlerName))
events.Add(new EventRegistration() { EventName = eventName, MethodName = handlerName });
}
foreach (int id in caseBlock.Values)
result.Add(id, events.ToArray());
}
return result;
}
bool IsAddEvent(ILExpression expr, out string eventName, out string handlerName)
{
eventName = "";
handlerName = "";
if (expr == null || !(expr.Code == ILCode.Callvirt || expr.Code == ILCode.Call))
return false;
if (expr.Operand is MethodReference && expr.Arguments.Count == 2) {
var addMethod = expr.Operand as MethodReference;
if (addMethod.Name.StartsWith("add_") && addMethod.Parameters.Count == 1)
eventName = addMethod.Name.Substring("add_".Length);
var arg = expr.Arguments[1];
if (arg.Code != ILCode.Newobj && arg.Arguments.Count != 2)
return false;
var arg1 = arg.Arguments[1];
if (arg1.Code != ILCode.Ldftn && arg1.Code != ILCode.Ldvirtftn)
return false;
if (arg1.Operand is MethodReference) {
var m = arg1.Operand as MethodReference;
handlerName = m.Name;
return true;
}
}
return false;
}
}
sealed class AssemblyResolver : MarshalByRefObject
{
LoadedAssembly assembly;
public AssemblyResolver(LoadedAssembly assembly)
{
this.assembly = assembly;
}
public string FindAssembly(string name)
{
var asm = assembly.LookupReferencedAssembly(name);
return asm == null ? null : asm.FileName;
}
}
}

6
ILSpy/ILLanguage.cs

@ -80,12 +80,12 @@ namespace ICSharpCode.ILSpy
new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleNamespace(nameSpace, types); new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).DisassembleNamespace(nameSpace, types);
} }
public override void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options) public override void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{ {
output.WriteLine("// " + fileName); output.WriteLine("// " + assembly.FileName);
output.WriteLine(); output.WriteLine();
new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).WriteAssemblyHeader(assembly); new ReflectionDisassembler(output, detectControlStructure, options.CancellationToken).WriteAssemblyHeader(assembly.AssemblyDefinition);
} }
public override string TypeToString(TypeReference t, bool includeNamespace, ICustomAttributeProvider attributeProvider) public override string TypeToString(TypeReference t, bool includeNamespace, ICustomAttributeProvider attributeProvider)

1
ILSpy/ILSpy.csproj

@ -93,6 +93,7 @@
<Compile Include="BamlDecompiler.cs" /> <Compile Include="BamlDecompiler.cs" />
<Compile Include="CommandLineArguments.cs" /> <Compile Include="CommandLineArguments.cs" />
<Compile Include="Commands.cs" /> <Compile Include="Commands.cs" />
<Compile Include="ConnectMethodDecompiler.cs" />
<Compile Include="DecompilerSettingsPanel.xaml.cs"> <Compile Include="DecompilerSettingsPanel.xaml.cs">
<DependentUpon>DecompilerSettingsPanel.xaml</DependentUpon> <DependentUpon>DecompilerSettingsPanel.xaml</DependentUpon>
<SubType>Code</SubType> <SubType>Code</SubType>

6
ILSpy/Language.cs

@ -84,10 +84,10 @@ namespace ICSharpCode.ILSpy
WriteCommentLine(output, nameSpace); WriteCommentLine(output, nameSpace);
} }
public virtual void DecompileAssembly(AssemblyDefinition assembly, string fileName, ITextOutput output, DecompilationOptions options) public virtual void DecompileAssembly(LoadedAssembly assembly, ITextOutput output, DecompilationOptions options)
{ {
WriteCommentLine(output, fileName); WriteCommentLine(output, assembly.FileName);
WriteCommentLine(output, assembly.Name.FullName); WriteCommentLine(output, assembly.AssemblyDefinition.FullName);
} }
public virtual void WriteCommentLine(ITextOutput output, string comment) public virtual void WriteCommentLine(ITextOutput output, string comment)

2
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -186,7 +186,7 @@ namespace ICSharpCode.ILSpy.TreeNodes
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options) public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{ {
assembly.WaitUntilLoaded(); // necessary so that load errors are passed on to the caller assembly.WaitUntilLoaded(); // necessary so that load errors are passed on to the caller
language.DecompileAssembly(assembly.AssemblyDefinition, assembly.FileName, output, options); language.DecompileAssembly(assembly, output, options);
} }
public override bool Save(DecompilerTextView textView) public override bool Save(DecompilerTextView textView)

Loading…
Cancel
Save