From e1e40816d133148741f2b9f991d2669ec1bc4640 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 26 Feb 2011 18:54:13 +0100 Subject: [PATCH 1/3] display IL instructions as links to MSDN and display a tooltip with information. --- .../Disassembler/DisassemblerHelpers.cs | 2 +- ILSpy/MainWindow.xaml.cs | 7 ++ ILSpy/TextView/DecompilerTextView.cs | 103 +++++++++++++++++- ILSpy/TextView/ReferenceElementGenerator.cs | 9 +- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs index 0d7c3f220..7cbbb7d3b 100644 --- a/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs +++ b/ICSharpCode.Decompiler/Disassembler/DisassemblerHelpers.cs @@ -57,7 +57,7 @@ namespace ICSharpCode.Decompiler.Disassembler { writer.WriteDefinition(CecilExtensions.OffsetToString(instruction.Offset), instruction); writer.Write(": "); - writer.Write(instruction.OpCode.Name); + writer.WriteReference(instruction.OpCode.Name, instruction.OpCode); if(null != instruction.Operand) { writer.Write(' '); WriteOperand(writer, instruction.Operand); diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 002edb75d..22af195cd 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -276,6 +276,13 @@ namespace ICSharpCode.ILSpy SelectNode(assemblyListTreeNode.FindEventNode(((EventReference)reference).Resolve())); } else if (reference is AssemblyDefinition) { SelectNode(assemblyListTreeNode.FindAssemblyNode((AssemblyDefinition)reference)); + } else if (reference is Mono.Cecil.Cil.OpCode) { + string link = "http://msdn.microsoft.com/library/system.reflection.emit.opcodes." + ((Mono.Cecil.Cil.OpCode)reference).Code.ToString().ToLowerInvariant() + ".aspx"; + try { + Process.Start(link); + } catch { + + } } } #endregion diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index f3d0ccb0f..df2c1429f 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -19,8 +19,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -30,12 +32,13 @@ using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Threading; using System.Xml; - +using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Highlighting.Xshd; using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.NRefactory.Documentation; using Microsoft.Win32; using Mono.Cecil; @@ -69,11 +72,99 @@ namespace ICSharpCode.ILSpy.TextView }); InitializeComponent(); - this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference); + this.referenceElementGenerator = new ReferenceElementGenerator(this.JumpToReference, this.IsLink); textEditor.TextArea.TextView.ElementGenerators.Add(referenceElementGenerator); this.uiElementGenerator = new UIElementGenerator(); textEditor.TextArea.TextView.ElementGenerators.Add(uiElementGenerator); textEditor.Options.RequireControlModifierForHyperlinkClick = false; + textEditor.TextArea.TextView.MouseHover += TextViewMouseHover; + textEditor.TextArea.TextView.MouseHoverStopped += TextViewMouseHoverStopped; + } + #endregion + + #region Tooltip support + ToolTip tooltip; + + void TextViewMouseHoverStopped(object sender, MouseEventArgs e) + { + if (tooltip != null) + tooltip.IsOpen = false; + } + + void TextViewMouseHover(object sender, MouseEventArgs e) + { + TextViewPosition? position = textEditor.TextArea.TextView.GetPosition(e.GetPosition(textEditor.TextArea.TextView) + textEditor.TextArea.TextView.ScrollOffset); + if (position == null) + return; + int offset = textEditor.Document.GetOffset(position.Value); + ReferenceSegment seg = referenceElementGenerator.References.FindSegmentsContaining(offset).FirstOrDefault(); + if (seg == null) + return; + object content = GenerateTooltip(seg); + if (tooltip != null) + tooltip.IsOpen = false; + if (content != null) + tooltip = new ToolTip() { Content = content, IsOpen = true }; + } + + object GenerateTooltip(ReferenceSegment segment) + { + if (segment.Reference is Mono.Cecil.Cil.OpCode) { + Mono.Cecil.Cil.OpCode code = (Mono.Cecil.Cil.OpCode)segment.Reference; + string encodedName = code.Code.ToString(); + string opCodeHex = code.Size > 1 ? string.Format("0x{0:x2}{1:x2}", code.Op1, code.Op2) : string.Format("0x{0:x2}", code.Op2); + string documentationFile = FindDocumentation("mscorlib.xml"); + string text = ""; + if (documentationFile != null){ + XmlDocumentationProvider provider = new XmlDocumentationProvider(documentationFile); + string documentation = provider.GetDocumentation("F:System.Reflection.Emit.OpCodes." + encodedName); + if (documentation != null) + text = StripXml(documentation); + } + return string.Format("{0} ({1}): {2}", code.Name, opCodeHex, text); + } + + return null; + } + + string StripXml(string xml) + { + return Regex.Replace(xml, "", "").Trim(); + } + + string FindDocumentation(string fileName) + { + string path = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); + List names = new List(); + EnumerateCultures(CultureInfo.CurrentCulture, names); + names.Add("en"); + names.Add("en-US"); + names.Add("en-GB"); + + foreach (string name in names) { + string location = Path.Combine(path, name, fileName); + if (File.Exists(location)) + return location; + } + + path = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)") ?? Environment.GetEnvironmentVariable("PROGRAMFILES"), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0"); + + string loc = Path.Combine(path, fileName); + + if (File.Exists(loc)) + return loc; + + return null; + } + + void EnumerateCultures(CultureInfo info, List names) + { + while (info != null) { + names.Add(info.Name); + info = info.Parent; + if (info == info.Parent) + return; + } } #endregion @@ -361,6 +452,14 @@ namespace ICSharpCode.ILSpy.TextView } mainWindow.JumpToReference(reference); } + + /// + /// Filters all ReferenceSegments that are no real links. + /// + bool IsLink(ReferenceSegment referenceSegment) + { + return true; + } #endregion #region SaveToDisk diff --git a/ILSpy/TextView/ReferenceElementGenerator.cs b/ILSpy/TextView/ReferenceElementGenerator.cs index 09e0e00a6..e83d89f64 100644 --- a/ILSpy/TextView/ReferenceElementGenerator.cs +++ b/ILSpy/TextView/ReferenceElementGenerator.cs @@ -29,17 +29,21 @@ namespace ICSharpCode.ILSpy.TextView sealed class ReferenceElementGenerator : VisualLineElementGenerator { Action referenceClicked; + Predicate isLink; /// /// The collection of references (hyperlinks). /// public TextSegmentCollection References { get; set; } - public ReferenceElementGenerator(Action referenceClicked) + public ReferenceElementGenerator(Action referenceClicked, Predicate isLink) { if (referenceClicked == null) throw new ArgumentNullException("referenceClicked"); + if (isLink == null) + throw new ArgumentNullException("isLink"); this.referenceClicked = referenceClicked; + this.isLink = isLink; } public override int GetFirstInterestedOffset(int startOffset) @@ -56,6 +60,9 @@ namespace ICSharpCode.ILSpy.TextView if (this.References == null) return null; foreach (var segment in this.References.FindSegmentsContaining(offset)) { + // skip all non-links + if (!isLink(segment)) + continue; // ensure that hyperlinks don't span several lines (VisualLineElements can't contain line breaks) int endOffset = Math.Min(segment.EndOffset, CurrentContext.VisualLine.LastDocumentLine.EndOffset); // don't create hyperlinks with length 0 From 194238586d478cca34524d669ef6370b8597ada0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Sat, 26 Feb 2011 13:40:43 +0000 Subject: [PATCH 2/3] Slightly reduce the number of generated basic blocks --- ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index f2e64e438..912292375 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -12,6 +12,7 @@ namespace Decompiler.ControlFlow public enum ILAstOptimizationStep { SplitToMovableBlocks, + ShortCircuits, FindLoops, FindConditions, FlattenNestedMovableBlocks, @@ -33,6 +34,7 @@ namespace Decompiler.ControlFlow SplitToBasicBlocks(block); } + if (abortBeforeStep == ILAstOptimizationStep.ShortCircuits) return; OptimizeShortCircuits(method); if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; @@ -100,24 +102,23 @@ namespace Decompiler.ControlFlow ILNode lastNode = block.Body[i - 1]; ILNode currNode = block.Body[i]; - bool added = false; - // Insert split if (currNode is ILLabel || lastNode is ILTryCatchBlock || currNode is ILTryCatchBlock || (lastNode is ILExpression) && ((ILExpression)lastNode).IsBranch() || - (currNode is ILExpression) && (((ILExpression)currNode).IsBranch() && basicBlock.Body.Count > 0)) + (currNode is ILExpression) && (((ILExpression)currNode).IsBranch() && ((ILExpression)currNode).Code.CanFallThough() && basicBlock.Body.Count > 0)) { ILBasicBlock lastBlock = basicBlock; basicBlock = new ILBasicBlock(); basicBlocks.Add(basicBlock); + if (currNode is ILLabel) { - // Reuse the first label + // Insert as entry label basicBlock.EntryLabel = (ILLabel)currNode; - added = true; } else { basicBlock.EntryLabel = new ILLabel() { Name = "Block_" + (nextBlockIndex++) }; + basicBlock.Body.Add(currNode); } // Explicit branch from one block to other @@ -125,10 +126,9 @@ namespace Decompiler.ControlFlow if (!(lastNode is ILExpression) || ((ILExpression)lastNode).Code.CanFallThough()) { lastBlock.FallthoughGoto = new ILExpression(ILCode.Br, basicBlock.EntryLabel); } + } else { + basicBlock.Body.Add(currNode); } - - if (!added) - basicBlock.Body.Add(currNode); } } From 05b0b427d6e79bdff3eaaaedb13c217f3165a1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Srbeck=C3=BD?= Date: Sat, 26 Feb 2011 19:02:41 +0000 Subject: [PATCH 3/3] Peephole detection of the ternary operator (?:) --- .../Ast/AstMethodBodyBuilder.cs | 6 + .../ILAst/ILAstOptimizer.cs | 132 ++++++++++++++---- ICSharpCode.Decompiler/ILAst/ILCodes.cs | 1 + ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs | 8 ++ 4 files changed, 123 insertions(+), 24 deletions(-) diff --git a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs index d08c4972a..20f8eed57 100644 --- a/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs +++ b/ICSharpCode.Decompiler/Ast/AstMethodBodyBuilder.cs @@ -285,6 +285,12 @@ namespace Decompiler new Ast.GotoStatement(((ILLabel)byteCode.Operand).Name) } }; + case ILCode.TernaryOp: + return new Ast.ConditionalExpression() { + Condition = MakeBranchCondition(byteCode.Arguments[0]), + TrueExpression = (Expression)TransformExpression(byteCode.Arguments[1]), + FalseExpression = (Expression)TransformExpression(byteCode.Arguments[2]), + }; } List args = TransformExpressionArguments(byteCode); diff --git a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs index 912292375..6fa5b760e 100644 --- a/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs +++ b/ICSharpCode.Decompiler/ILAst/ILAstOptimizer.cs @@ -12,7 +12,7 @@ namespace Decompiler.ControlFlow public enum ILAstOptimizationStep { SplitToMovableBlocks, - ShortCircuits, + PeepholeOptimizations, FindLoops, FindConditions, FlattenNestedMovableBlocks, @@ -34,8 +34,11 @@ namespace Decompiler.ControlFlow SplitToBasicBlocks(block); } - if (abortBeforeStep == ILAstOptimizationStep.ShortCircuits) return; - OptimizeShortCircuits(method); + if (abortBeforeStep == ILAstOptimizationStep.PeepholeOptimizations) return; + AnalyseLabels(method); + foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { + PeepholeOptimizations(block); + } if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { @@ -132,29 +135,21 @@ namespace Decompiler.ControlFlow } } + foreach (ILBasicBlock bb in basicBlocks) { + if (bb.Body.Count > 0 && + bb.Body.Last() is ILExpression && + ((ILExpression)bb.Body.Last()).Code == ILCode.Br) + { + Debug.Assert(bb.FallthoughGoto == null); + bb.FallthoughGoto = (ILExpression)bb.Body.Last(); + bb.Body.RemoveAt(bb.Body.Count - 1); + } + } + block.Body = basicBlocks; return; } - void OptimizeShortCircuits(ILBlock method) - { - AnalyseLabels(method); - - foreach(ILBlock block in method.GetSelfAndChildrenRecursive().ToList()) { - bool modified; - do { - modified = false; - for (int i = 0; i < block.Body.Count;) { - if (TrySimplifyShortCircuit(block.Body, (ILBasicBlock)block.Body[i])) { - modified = true; - } else { - i++; - } - } - } while(modified); - } - } - Dictionary labelGlobalRefCount; Dictionary labelToBasicBlock; @@ -175,11 +170,34 @@ namespace Decompiler.ControlFlow } } + void PeepholeOptimizations(ILBlock block) + { + bool modified; + do { + modified = false; + for (int i = 0; i < block.Body.Count;) { + if (TrySimplifyShortCircuit(block.Body, (ILBasicBlock)block.Body[i])) { + modified = true; + continue; + } + if (TrySimplifyTernaryOperator(block.Body, (ILBasicBlock)block.Body[i])) { + modified = true; + continue; + } + i++; + } + } while(modified); + } + bool IsConditionalBranch(ILBasicBlock bb, ref ILExpression branchExpr, ref ILLabel trueLabel, ref ILLabel falseLabel) { if (bb.Body.Count == 1) { branchExpr = bb.Body[0] as ILExpression; - if (branchExpr != null && branchExpr.Operand is ILLabel && branchExpr.Arguments.Count > 0) { + if (branchExpr != null && + branchExpr.Operand is ILLabel && + branchExpr.Arguments.Count > 0 && + branchExpr.Prefixes == null) + { trueLabel = (ILLabel)branchExpr.Operand; falseLabel = (ILLabel)((ILExpression)bb.FallthoughGoto).Operand; return true; @@ -188,6 +206,71 @@ namespace Decompiler.ControlFlow return false; } + bool IsStloc(ILBasicBlock bb, ref ILVariable locVar, ref ILExpression val, ref ILLabel fallLabel) + { + if (bb.Body.Count == 1) { + ILExpression expr = bb.Body[0] as ILExpression; + if (expr != null && + expr.Code == ILCode.Stloc && + expr.Prefixes == null) + { + locVar = (ILVariable)expr.Operand; + val = expr.Arguments[0]; + fallLabel = (ILLabel)bb.FallthoughGoto.Operand; + return true; + } + } + return false; + } + + // scope is modified if successful + bool TrySimplifyTernaryOperator(List scope, ILBasicBlock head) + { + Debug.Assert(scope.Contains(head)); + + ILExpression branchExpr = null; + ILLabel trueLabel = null; + ILLabel falseLabel = null; + ILVariable trueLocVar = null; + ILExpression trueExpr = null; + ILLabel trueFall = null; + ILVariable falseLocVar = null; + ILExpression falseExpr = null; + ILLabel falseFall = null; + + if(IsConditionalBranch(head, ref branchExpr, ref trueLabel, ref falseLabel) && + labelGlobalRefCount[trueLabel] == 1 && + labelGlobalRefCount[falseLabel] == 1 && + IsStloc(labelToBasicBlock[trueLabel], ref trueLocVar, ref trueExpr, ref trueFall) && + IsStloc(labelToBasicBlock[falseLabel], ref falseLocVar, ref falseExpr, ref falseFall) && + trueLocVar == falseLocVar && + trueFall == falseFall) + { + // Create the ternary expression + head.Body = new List() { + new ILExpression(ILCode.Stloc, trueLocVar, + new ILExpression(ILCode.TernaryOp, null, + new ILExpression(branchExpr.Code, null, branchExpr.Arguments.ToArray()), + trueExpr, + falseExpr + ) + ) + }; + head.FallthoughGoto = new ILExpression(ILCode.Br, trueFall); + + // Remove the old basic blocks + scope.Remove(labelToBasicBlock[trueLabel]); + scope.Remove(labelToBasicBlock[falseLabel]); + labelToBasicBlock.Remove(trueLabel); + labelToBasicBlock.Remove(falseLabel); + labelGlobalRefCount.Remove(trueLabel); + labelGlobalRefCount.Remove(falseLabel); + + return true; + } + return false; + } + // scope is modified if successful bool TrySimplifyShortCircuit(List scope, ILBasicBlock head) { @@ -228,7 +311,8 @@ namespace Decompiler.ControlFlow head.FallthoughGoto = new ILExpression(ILCode.Br, nextFalseLabel); // Remove the inlined branch from scope - labelGlobalRefCount[nextBasicBlock.EntryLabel] = 0; + labelGlobalRefCount.Remove(nextBasicBlock.EntryLabel); + labelToBasicBlock.Remove(nextBasicBlock.EntryLabel); if (!scope.Remove(nextBasicBlock)) throw new Exception("Element not found"); diff --git a/ICSharpCode.Decompiler/ILAst/ILCodes.cs b/ICSharpCode.Decompiler/ILAst/ILCodes.cs index 8010821ba..75f9b1239 100644 --- a/ICSharpCode.Decompiler/ILAst/ILCodes.cs +++ b/ICSharpCode.Decompiler/ILAst/ILCodes.cs @@ -259,6 +259,7 @@ namespace Decompiler BrLogicAnd, BrLogicOr, InitArray, // Array Initializer + TernaryOp, // ?: Pattern // used for ILAst pattern nodes } diff --git a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs index 1cbc9d441..14a4c346e 100644 --- a/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs +++ b/ICSharpCode.Decompiler/ILAst/TypeAnalysis.cs @@ -119,6 +119,14 @@ namespace Decompiler InferTypeForExpression(expr.Arguments[1], typeSystem.Boolean); } return null; + case ILCode.TernaryOp: + if (forceInferChildren) { + InferTypeForExpression(expr.Arguments[0], typeSystem.Boolean); + } + return TypeWithMoreInformation( + InferTypeForExpression(expr.Arguments[1], expectedType, forceInferChildren), + InferTypeForExpression(expr.Arguments[2], expectedType, forceInferChildren) + ); #endregion #region Variable load/store case ILCode.Stloc: