546 changed files with 250552 additions and 1623 deletions
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.SharpDevelop; |
||||
using ICSharpCode.SharpDevelop.Dom; |
||||
using ICSharpCode.SharpDevelop.Gui; |
||||
|
||||
namespace ICSharpCode.ILSpyAddIn |
||||
{ |
||||
public class NavigateToDecompiledEntityService : INavigateToEntityService, INavigateToMemberService |
||||
{ |
||||
public bool NavigateToEntity(IEntity entity) |
||||
{ |
||||
if (entity == null) |
||||
throw new ArgumentNullException("entity"); |
||||
|
||||
// Get the underlying entity for generic instance members
|
||||
while ((entity is IMember) && ((IMember)entity).GenericMember != null) |
||||
entity = ((IMember)entity).GenericMember; |
||||
|
||||
IClass declaringType = (entity as IClass) ?? entity.DeclaringType; |
||||
if (declaringType == null) |
||||
return false; |
||||
// get the top-level type
|
||||
while (declaringType.DeclaringType != null) |
||||
declaringType = declaringType.DeclaringType; |
||||
|
||||
ReflectionProjectContent rpc = entity.ProjectContent as ReflectionProjectContent; |
||||
if (rpc != null) { |
||||
string assemblyLocation = ILSpyController.GetAssemblyLocation(rpc); |
||||
if (!string.IsNullOrEmpty(assemblyLocation) && File.Exists(assemblyLocation)) { |
||||
NavigateTo(assemblyLocation, declaringType.DotNetName, ((AbstractEntity)entity).DocumentationTag); |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public static void NavigateTo(string assemblyFile, string typeName, string entityTag) |
||||
{ |
||||
if (string.IsNullOrEmpty(assemblyFile)) |
||||
throw new ArgumentException("assemblyFile is null or empty"); |
||||
|
||||
if (string.IsNullOrEmpty(typeName)) |
||||
throw new ArgumentException("typeName is null or empty"); |
||||
|
||||
foreach (var vc in WorkbenchSingleton.Workbench.ViewContentCollection.OfType<DecompiledViewContent>()) { |
||||
if (string.Equals(vc.AssemblyFile, assemblyFile, StringComparison.OrdinalIgnoreCase) && typeName == vc.FullTypeName) { |
||||
vc.WorkbenchWindow.SelectWindow(); |
||||
vc.JumpToEntity(entityTag); |
||||
return; |
||||
} |
||||
} |
||||
WorkbenchSingleton.Workbench.ShowView(new DecompiledViewContent(assemblyFile, typeName, entityTag)); |
||||
} |
||||
|
||||
public bool NavigateToMember(string assemblyFile, string typeName, string entityTag, int lineNumber) |
||||
{ |
||||
//close the window if exists - this is a workaround
|
||||
foreach (var vc in WorkbenchSingleton.Workbench.ViewContentCollection.OfType<DecompiledViewContent>()) { |
||||
if (string.Equals(vc.AssemblyFile, assemblyFile, StringComparison.OrdinalIgnoreCase) && typeName == vc.FullTypeName) { |
||||
vc.WorkbenchWindow.CloseWindow(true); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
var view = new DecompiledViewContent(assemblyFile, typeName, entityTag); |
||||
view.DecompilationFinished += delegate { view.JumpTo(lineNumber); }; |
||||
WorkbenchSingleton.Workbench.ShowView(view); |
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,272 @@
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.Windows; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Controls.Primitives; |
||||
using System.Windows.Input; |
||||
|
||||
using ICSharpCode.AvalonEdit; |
||||
using ICSharpCode.AvalonEdit.AddIn; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.AvalonEdit.Highlighting; |
||||
using ICSharpCode.AvalonEdit.Rendering; |
||||
using ICSharpCode.SharpDevelop; |
||||
using ICSharpCode.SharpDevelop.Bookmarks; |
||||
using ICSharpCode.SharpDevelop.Editor; |
||||
using ICSharpCode.SharpDevelop.Editor.AvalonEdit; |
||||
|
||||
namespace ICSharpCode.ILSpyAddIn.ViewContent |
||||
{ |
||||
class DecompiledTextEditorAdapter : AvalonEditTextEditorAdapter |
||||
{ |
||||
public DecompiledTextEditorAdapter(TextEditor textEditor) : base(textEditor) |
||||
{} |
||||
|
||||
public string DecompiledFullTypeName { get; set; } |
||||
|
||||
public override ICSharpCode.Core.FileName FileName { |
||||
get { return ICSharpCode.Core.FileName.Create(DecompiledFullTypeName); } |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Equivalent to AE.AddIn CodeEditor, but without editing capabilities.
|
||||
/// </summary>
|
||||
public class CodeView : Grid, IDisposable, ICodeEditor |
||||
{ |
||||
public event EventHandler DocumentChanged; |
||||
|
||||
readonly DecompiledTextEditorAdapter adapter; |
||||
readonly IconBarManager iconBarManager; |
||||
readonly IconBarMargin iconMargin; |
||||
readonly TextMarkerService textMarkerService; |
||||
|
||||
public CodeView(string decompiledFullTypeName) |
||||
{ |
||||
DecompiledFullTypeName = decompiledFullTypeName; |
||||
this.adapter = new DecompiledTextEditorAdapter(new SharpDevelopTextEditor { IsReadOnly = true }) { |
||||
DecompiledFullTypeName = decompiledFullTypeName |
||||
}; |
||||
this.Children.Add(adapter.TextEditor); |
||||
adapter.TextEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("C#"); |
||||
|
||||
// add margin
|
||||
this.iconMargin = new IconBarMargin(iconBarManager = new IconBarManager()); |
||||
this.adapter.TextEditor.TextArea.LeftMargins.Insert(0, iconMargin); |
||||
this.adapter.TextEditor.TextArea.TextView.VisualLinesChanged += delegate { iconMargin.InvalidateVisual(); }; |
||||
|
||||
// add marker service
|
||||
this.textMarkerService = new TextMarkerService(this); |
||||
this.adapter.TextEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService); |
||||
this.adapter.TextEditor.TextArea.TextView.LineTransformers.Add(textMarkerService); |
||||
this.adapter.TextEditor.TextArea.TextView.Services.AddService(typeof(ITextMarkerService), textMarkerService); |
||||
this.adapter.TextEditor.TextArea.TextView.Services.AddService(typeof(IBookmarkMargin), iconBarManager); |
||||
|
||||
// add events
|
||||
this.adapter.TextEditor.MouseHover += TextEditorMouseHover; |
||||
this.adapter.TextEditor.MouseHoverStopped += TextEditorMouseHoverStopped; |
||||
this.adapter.TextEditor.MouseLeave += TextEditorMouseLeave; |
||||
} |
||||
|
||||
#region Popup
|
||||
ToolTip toolTip; |
||||
Popup popup; |
||||
|
||||
void TextEditorMouseHover(object sender, MouseEventArgs e) |
||||
{ |
||||
ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(this.adapter); |
||||
var pos = adapter.TextEditor.GetPositionFromPoint(e.GetPosition(this)); |
||||
args.InDocument = pos.HasValue; |
||||
if (pos.HasValue) { |
||||
args.LogicalPosition = AvalonEditDocumentAdapter.ToLocation(pos.Value); |
||||
} |
||||
|
||||
if (!args.Handled) { |
||||
// if request wasn't handled by a marker, pass it to the ToolTipRequestService
|
||||
ToolTipRequestService.RequestToolTip(args); |
||||
} |
||||
|
||||
if (args.ContentToShow != null) { |
||||
var contentToShowITooltip = args.ContentToShow as ITooltip; |
||||
|
||||
if (contentToShowITooltip != null && contentToShowITooltip.ShowAsPopup) { |
||||
if (!(args.ContentToShow is UIElement)) { |
||||
throw new NotSupportedException("Content to show in Popup must be UIElement: " + args.ContentToShow); |
||||
} |
||||
if (popup == null) { |
||||
popup = CreatePopup(); |
||||
} |
||||
if (TryCloseExistingPopup(false)) { |
||||
// when popup content decides to close, close the popup
|
||||
contentToShowITooltip.Closed += (closedSender, closedArgs) => { popup.IsOpen = false; }; |
||||
popup.Child = (UIElement)args.ContentToShow; |
||||
//ICSharpCode.SharpDevelop.Debugging.DebuggerService.CurrentDebugger.IsProcessRunningChanged
|
||||
SetPopupPosition(popup, e); |
||||
popup.IsOpen = true; |
||||
} |
||||
e.Handled = true; |
||||
} else { |
||||
if (toolTip == null) { |
||||
toolTip = new ToolTip(); |
||||
toolTip.Closed += delegate { toolTip = null; }; |
||||
} |
||||
toolTip.PlacementTarget = this.adapter.TextEditor; // required for property inheritance
|
||||
|
||||
if(args.ContentToShow is string) { |
||||
toolTip.Content = new TextBlock |
||||
{ |
||||
Text = args.ContentToShow as string, |
||||
TextWrapping = TextWrapping.Wrap |
||||
}; |
||||
} |
||||
else |
||||
toolTip.Content = args.ContentToShow; |
||||
|
||||
toolTip.IsOpen = true; |
||||
e.Handled = true; |
||||
} |
||||
} else { |
||||
// close popup if mouse hovered over empty area
|
||||
if (popup != null) { |
||||
e.Handled = true; |
||||
} |
||||
TryCloseExistingPopup(false); |
||||
} |
||||
} |
||||
|
||||
bool TryCloseExistingPopup(bool mouseClick) |
||||
{ |
||||
bool canClose = true; |
||||
if (popup != null) { |
||||
var popupContentITooltip = popup.Child as ITooltip; |
||||
if (popupContentITooltip != null) { |
||||
canClose = popupContentITooltip.Close(mouseClick); |
||||
} |
||||
if (canClose) { |
||||
popup.IsOpen = false; |
||||
} |
||||
} |
||||
return canClose; |
||||
} |
||||
|
||||
void SetPopupPosition(Popup popup, MouseEventArgs mouseArgs) |
||||
{ |
||||
var popupPosition = GetPopupPosition(mouseArgs); |
||||
popup.HorizontalOffset = popupPosition.X; |
||||
popup.VerticalOffset = popupPosition.Y; |
||||
} |
||||
|
||||
/// <summary> Returns Popup position based on mouse position, in device independent units </summary>
|
||||
Point GetPopupPosition(MouseEventArgs mouseArgs) |
||||
{ |
||||
Point mousePos = mouseArgs.GetPosition(this); |
||||
Point positionInPixels; |
||||
// align Popup with line bottom
|
||||
TextViewPosition? logicalPos = adapter.TextEditor.GetPositionFromPoint(mousePos); |
||||
if (logicalPos.HasValue) { |
||||
var textView = adapter.TextEditor.TextArea.TextView; |
||||
positionInPixels = |
||||
textView.PointToScreen( |
||||
textView.GetVisualPosition(logicalPos.Value, VisualYPosition.LineBottom) - textView.ScrollOffset); |
||||
positionInPixels.X -= 4; |
||||
} else { |
||||
positionInPixels = PointToScreen(mousePos + new Vector(-4, 6)); |
||||
} |
||||
// use device independent units, because Popup Left/Top are in independent units
|
||||
return positionInPixels.TransformFromDevice(this); |
||||
} |
||||
|
||||
Popup CreatePopup() |
||||
{ |
||||
popup = new Popup(); |
||||
popup.Closed += (s, e) => popup = null; |
||||
popup.AllowsTransparency = true; |
||||
popup.PlacementTarget = this; // required for property inheritance
|
||||
popup.Placement = PlacementMode.Absolute; |
||||
popup.StaysOpen = true; |
||||
return popup; |
||||
} |
||||
|
||||
void TextEditorMouseHoverStopped(object sender, MouseEventArgs e) |
||||
{ |
||||
if (toolTip != null) { |
||||
toolTip.IsOpen = false; |
||||
e.Handled = true; |
||||
} |
||||
|
||||
TextEditorMouseLeave(sender, e); |
||||
} |
||||
|
||||
void TextEditorMouseLeave(object sender, MouseEventArgs e) |
||||
{ |
||||
if (popup != null && !popup.IsMouseOver) { |
||||
// do not close popup if mouse moved from editor to popup
|
||||
TryCloseExistingPopup(false); |
||||
} |
||||
} |
||||
|
||||
#endregion
|
||||
|
||||
public TextDocument Document { |
||||
get { return adapter.TextEditor.Document; } |
||||
set { |
||||
adapter.TextEditor.Document = value; |
||||
if (DocumentChanged != null) { |
||||
DocumentChanged(value, EventArgs.Empty); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public ITextEditor TextEditor { |
||||
get { |
||||
return adapter; |
||||
} |
||||
} |
||||
|
||||
public IconBarManager IconBarManager { |
||||
get { return iconBarManager; } |
||||
} |
||||
|
||||
public AvalonEditTextEditorAdapter Adapter { |
||||
get { |
||||
return adapter; |
||||
} |
||||
} |
||||
|
||||
public string DecompiledFullTypeName { |
||||
get; private set; |
||||
} |
||||
|
||||
public void Dispose() |
||||
{ |
||||
} |
||||
|
||||
public void UnfoldAndScroll(int lineNumber) |
||||
{ |
||||
if (lineNumber <= 0 || lineNumber > adapter.Document.TotalNumberOfLines) |
||||
return; |
||||
|
||||
//var line = adapter.TextEditor.Document.GetLineByNumber(lineNumber);
|
||||
|
||||
// unfold
|
||||
// var foldings = foldingManager.GetFoldingsContaining(line.Offset);
|
||||
// if (foldings != null) {
|
||||
// foreach (var folding in foldings) {
|
||||
// if (folding.IsFolded) {
|
||||
// folding.IsFolded = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// scroll to
|
||||
adapter.TextEditor.ScrollTo(lineNumber, 0); |
||||
} |
||||
|
||||
public void Redraw(ISegment segment, System.Windows.Threading.DispatcherPriority priority) |
||||
{ |
||||
this.adapter.TextEditor.TextArea.TextView.Redraw(segment, priority); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,291 @@
@@ -0,0 +1,291 @@
|
||||
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
||||
// This code is distributed under MIT X11 license (for details please see \doc\license.txt)
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
|
||||
using ICSharpCode.Core; |
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Ast; |
||||
using ICSharpCode.ILSpyAddIn.ViewContent; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.Utils; |
||||
using ICSharpCode.SharpDevelop; |
||||
using ICSharpCode.SharpDevelop.Bookmarks; |
||||
using ICSharpCode.SharpDevelop.Debugging; |
||||
using ICSharpCode.SharpDevelop.Gui; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.ILSpyAddIn |
||||
{ |
||||
/// <summary>
|
||||
/// Hosts a decompiled type.
|
||||
/// </summary>
|
||||
public class DecompiledViewContent : AbstractViewContentWithoutFile |
||||
{ |
||||
readonly string assemblyFile; |
||||
readonly string fullTypeName; |
||||
readonly string tempFileName; |
||||
|
||||
/// <summary>
|
||||
/// Entity to jump to once decompilation has finished.
|
||||
/// </summary>
|
||||
string jumpToEntityTagWhenDecompilationFinished; |
||||
|
||||
bool decompilationFinished; |
||||
|
||||
readonly CodeView codeView; |
||||
readonly CancellationTokenSource cancellation = new CancellationTokenSource(); |
||||
|
||||
#region Constructor
|
||||
public DecompiledViewContent(string assemblyFile, string fullTypeName, string entityTag) |
||||
{ |
||||
codeView = new CodeView(string.Format("{0},{1}", assemblyFile, fullTypeName)); |
||||
this.assemblyFile = assemblyFile; |
||||
this.fullTypeName = fullTypeName; |
||||
this.jumpToEntityTagWhenDecompilationFinished = entityTag; |
||||
|
||||
string shortTypeName = fullTypeName.Substring(fullTypeName.LastIndexOf('.') + 1); |
||||
this.TitleName = "[" + shortTypeName + "]"; |
||||
tempFileName = string.Format("decompiled/{0}.cs", fullTypeName); |
||||
this.InfoTip = tempFileName; |
||||
|
||||
Thread thread = new Thread(DecompilationThread); |
||||
thread.Name = "Decompiler (" + shortTypeName + ")"; |
||||
thread.Start(); |
||||
|
||||
BookmarkManager.Removed += BookmarkManager_Removed; |
||||
BookmarkManager.Added += BookmarkManager_Added; |
||||
} |
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
public string AssemblyFile { |
||||
get { return assemblyFile; } |
||||
} |
||||
|
||||
public string FullTypeName { |
||||
get { return fullTypeName; } |
||||
} |
||||
|
||||
public override object Control { |
||||
get { return codeView; } |
||||
} |
||||
|
||||
public override bool IsReadOnly { |
||||
get { return true; } |
||||
} |
||||
|
||||
public MemberReference MemberReference { |
||||
get; private set; |
||||
} |
||||
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
public override void Dispose() |
||||
{ |
||||
cancellation.Cancel(); |
||||
codeView.Dispose(); |
||||
BookmarkManager.Added -= BookmarkManager_Added; |
||||
BookmarkManager.Removed -= BookmarkManager_Removed; |
||||
base.Dispose(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region Load/Save
|
||||
public override void Load() |
||||
{ |
||||
// nothing to do...
|
||||
} |
||||
|
||||
public override void Save() |
||||
{ |
||||
if (!decompilationFinished) |
||||
return; |
||||
// TODO: show Save As dialog to allow the user to save the decompiled file
|
||||
} |
||||
#endregion
|
||||
|
||||
#region JumpToEntity
|
||||
public void JumpToEntity(string entityTag) |
||||
{ |
||||
if (!decompilationFinished) { |
||||
this.jumpToEntityTagWhenDecompilationFinished = entityTag; |
||||
return; |
||||
} |
||||
// TODO: implement this
|
||||
} |
||||
#endregion
|
||||
|
||||
#region Decompilation
|
||||
void DecompilationThread() |
||||
{ |
||||
try { |
||||
StringWriter writer = new StringWriter(); |
||||
RunDecompiler(assemblyFile, fullTypeName, new PlainTextOutput(writer), cancellation.Token); |
||||
if (!cancellation.IsCancellationRequested) { |
||||
WorkbenchSingleton.SafeThreadAsyncCall(OnDecompilationFinished, writer); |
||||
} |
||||
} catch (OperationCanceledException) { |
||||
// ignore cancellation
|
||||
} catch (Exception ex) { |
||||
if (cancellation.IsCancellationRequested) { |
||||
MessageService.ShowException(ex); |
||||
return; |
||||
} |
||||
AnalyticsMonitorService.TrackException(ex); |
||||
|
||||
StringWriter writer = new StringWriter(); |
||||
writer.WriteLine("Exception while decompiling " + fullTypeName); |
||||
writer.WriteLine(); |
||||
writer.WriteLine(ex.ToString()); |
||||
WorkbenchSingleton.SafeThreadAsyncCall(OnDecompilationFinished, writer); |
||||
} |
||||
} |
||||
|
||||
void RunDecompiler(string assemblyFile, string fullTypeName, ITextOutput textOutput, CancellationToken cancellationToken) |
||||
{ |
||||
ReaderParameters readerParameters = new ReaderParameters(); |
||||
// Use new assembly resolver instance so that the AssemblyDefinitions can be garbage-collected
|
||||
// once the code is decompiled.
|
||||
readerParameters.AssemblyResolver = new DefaultAssemblyResolver(); |
||||
|
||||
ModuleDefinition module = ModuleDefinition.ReadModule(assemblyFile, readerParameters); |
||||
TypeDefinition typeDefinition = module.GetType(fullTypeName); |
||||
if (typeDefinition == null) |
||||
throw new InvalidOperationException("Could not find type"); |
||||
DecompilerContext context = new DecompilerContext(module); |
||||
context.CancellationToken = cancellationToken; |
||||
AstBuilder astBuilder = new AstBuilder(context); |
||||
astBuilder.AddType(typeDefinition); |
||||
astBuilder.GenerateCode(textOutput); |
||||
|
||||
// save decompilation data
|
||||
var nodes = TreeTraversal |
||||
.PreOrder((AstNode)astBuilder.CompilationUnit, n => n.Children) |
||||
.Where(n => n is AttributedNode && n.Annotation<Tuple<int, int>>() != null); |
||||
MemberReference = typeDefinition; |
||||
int token = MemberReference.MetadataToken.ToInt32(); |
||||
if (!DebuggerService.ExternalDebugInformation.ContainsKey(token)) { |
||||
DebuggerService.ExternalDebugInformation.Add(token, new DecompileInformation { |
||||
CodeMappings = astBuilder.CodeMappings, |
||||
LocalVariables = astBuilder.LocalVariables, |
||||
DecompiledMemberReferences = astBuilder.DecompiledMemberReferences, |
||||
AstNodes = nodes |
||||
}); |
||||
} else { |
||||
DebuggerService.ExternalDebugInformation[token] = new DecompileInformation { |
||||
CodeMappings = astBuilder.CodeMappings, |
||||
LocalVariables = astBuilder.LocalVariables, |
||||
DecompiledMemberReferences = astBuilder.DecompiledMemberReferences, |
||||
AstNodes = nodes |
||||
}; |
||||
} |
||||
} |
||||
|
||||
void OnDecompilationFinished(StringWriter output) |
||||
{ |
||||
if (cancellation.IsCancellationRequested) |
||||
return; |
||||
codeView.Document.Text = output.ToString(); |
||||
codeView.Document.UndoStack.ClearAll(); |
||||
|
||||
this.decompilationFinished = true; |
||||
JumpToEntity(this.jumpToEntityTagWhenDecompilationFinished); |
||||
|
||||
// update UI
|
||||
UpdateIconMargin(output.ToString()); |
||||
UpdateDebuggingUI(); |
||||
|
||||
// fire event
|
||||
OnDecompilationFinished(EventArgs.Empty); |
||||
} |
||||
#endregion
|
||||
|
||||
#region Update UI
|
||||
void UpdateIconMargin(string text) |
||||
{ |
||||
codeView.IconBarManager.UpdateClassMemberBookmarks(ParserService.ParseFile(tempFileName, new StringTextBuffer(text))); |
||||
|
||||
// load bookmarks
|
||||
foreach (SDBookmark bookmark in BookmarkManager.GetBookmarks(codeView.Adapter.FileName)) { |
||||
bookmark.Document = codeView.Adapter.Document; |
||||
codeView.IconBarManager.Bookmarks.Add(bookmark); |
||||
} |
||||
} |
||||
|
||||
void UpdateDebuggingUI() |
||||
{ |
||||
if (!DebuggerService.IsDebuggerStarted) |
||||
return; |
||||
|
||||
if (DebuggerService.DebugStepInformation != null) { |
||||
// get debugging information
|
||||
DecompileInformation debugInformation = (DecompileInformation)DebuggerService.ExternalDebugInformation[MemberReference.MetadataToken.ToInt32()]; |
||||
int token = DebuggerService.DebugStepInformation.Item1; |
||||
int ilOffset = DebuggerService.DebugStepInformation.Item2; |
||||
int line; |
||||
MemberReference member; |
||||
if (debugInformation.CodeMappings == null || !debugInformation.CodeMappings.ContainsKey(token)) |
||||
return; |
||||
|
||||
debugInformation.CodeMappings[token].GetInstructionByTokenAndOffset(token, ilOffset, out member, out line); |
||||
|
||||
// HACK : if the codemappings are not built
|
||||
if (line == 0) { |
||||
DebuggerService.CurrentDebugger.StepOver(); |
||||
return; |
||||
} |
||||
// update bookmark & marker
|
||||
codeView.UnfoldAndScroll(line); |
||||
CurrentLineBookmark.SetPosition(this, line, 0, line, 0); |
||||
} |
||||
} |
||||
|
||||
public void JumpTo(int lineNumber) |
||||
{ |
||||
if (lineNumber <= 0) |
||||
return; |
||||
|
||||
if (codeView == null) |
||||
return; |
||||
|
||||
codeView.UnfoldAndScroll(lineNumber); |
||||
} |
||||
#endregion
|
||||
|
||||
#region Bookmarks
|
||||
void BookmarkManager_Removed(object sender, BookmarkEventArgs e) |
||||
{ |
||||
var mark = e.Bookmark; |
||||
if (mark != null && codeView.IconBarManager.Bookmarks.Contains(mark)) { |
||||
codeView.IconBarManager.Bookmarks.Remove(mark); |
||||
mark.Document = null; |
||||
} |
||||
} |
||||
|
||||
void BookmarkManager_Added(object sender, BookmarkEventArgs e) |
||||
{ |
||||
var mark = e.Bookmark; |
||||
if (mark != null && mark is BreakpointBookmark && mark.FileName == codeView.DecompiledFullTypeName) { |
||||
codeView.IconBarManager.Bookmarks.Add(mark); |
||||
mark.Document = codeView.Adapter.Document; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Events
|
||||
public event EventHandler DecompilationFinished; |
||||
|
||||
protected virtual void OnDecompilationFinished(EventArgs e) |
||||
{ |
||||
if (DecompilationFinished != null) { |
||||
DecompilationFinished(this, e); |
||||
} |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast |
||||
{ |
||||
/// <summary>
|
||||
/// ITypeResolveContext implementation that lazily loads types from Cecil.
|
||||
/// </summary>
|
||||
public class CecilTypeResolveContext : AbstractAnnotatable, ISynchronizedTypeResolveContext, IProjectContent |
||||
{ |
||||
readonly ModuleDefinition module; |
||||
readonly string[] namespaces; |
||||
readonly CecilLoader loader; |
||||
Dictionary<TypeDefinition, WeakReference> dict = new Dictionary<TypeDefinition, WeakReference>(); |
||||
int countUntilNextCleanup = 4; |
||||
|
||||
public CecilTypeResolveContext(ModuleDefinition module) |
||||
{ |
||||
this.loader = new CecilLoader(); |
||||
this.loader.IncludeInternalMembers = true; |
||||
this.module = module; |
||||
this.namespaces = module.Types.Select(t => t.Namespace).Distinct().ToArray(); |
||||
|
||||
List<IAttribute> assemblyAttributes = new List<IAttribute>(); |
||||
foreach (var attr in module.Assembly.CustomAttributes) { |
||||
assemblyAttributes.Add(loader.ReadAttribute(attr)); |
||||
} |
||||
this.AssemblyAttributes = assemblyAttributes.AsReadOnly(); |
||||
} |
||||
|
||||
ITypeDefinition GetClass(TypeDefinition cecilType) |
||||
{ |
||||
lock (dict) { |
||||
WeakReference wr; |
||||
ITypeDefinition type; |
||||
if (dict.TryGetValue(cecilType, out wr)) { |
||||
type = (ITypeDefinition)wr.Target; |
||||
} else { |
||||
wr = null; |
||||
type = null; |
||||
} |
||||
if (type == null) { |
||||
type = loader.LoadType(cecilType, this); |
||||
} |
||||
if (wr == null) { |
||||
if (--countUntilNextCleanup <= 0) |
||||
CleanupDict(); |
||||
wr = new WeakReference(type); |
||||
dict.Add(cecilType, wr); |
||||
} else { |
||||
wr.Target = type; |
||||
} |
||||
return type; |
||||
} |
||||
} |
||||
|
||||
void CleanupDict() |
||||
{ |
||||
List<TypeDefinition> deletedKeys = new List<TypeDefinition>(); |
||||
foreach (var pair in dict) { |
||||
if (!pair.Value.IsAlive) { |
||||
deletedKeys.Add(pair.Key); |
||||
} |
||||
} |
||||
foreach (var key in deletedKeys) { |
||||
dict.Remove(key); |
||||
} |
||||
countUntilNextCleanup = dict.Count + 4; |
||||
} |
||||
|
||||
public IList<IAttribute> AssemblyAttributes { get; private set; } |
||||
|
||||
public ITypeDefinition GetTypeDefinition(string nameSpace, string name, int typeParameterCount, StringComparer nameComparer) |
||||
{ |
||||
if (typeParameterCount > 0) |
||||
name = name + "`" + typeParameterCount.ToString(); |
||||
if (nameComparer == StringComparer.Ordinal) { |
||||
TypeDefinition cecilType = module.GetType(nameSpace, name); |
||||
if (cecilType != null) |
||||
return GetClass(cecilType); |
||||
else |
||||
return null; |
||||
} |
||||
foreach (TypeDefinition cecilType in module.Types) { |
||||
if (nameComparer.Equals(name, cecilType.Name) |
||||
&& nameComparer.Equals(nameSpace, cecilType.Namespace) |
||||
&& cecilType.GenericParameters.Count == typeParameterCount) |
||||
{ |
||||
return GetClass(cecilType); |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public IEnumerable<ITypeDefinition> GetTypes() |
||||
{ |
||||
foreach (TypeDefinition cecilType in module.Types) { |
||||
yield return GetClass(cecilType); |
||||
} |
||||
} |
||||
|
||||
public IEnumerable<ITypeDefinition> GetTypes(string nameSpace, StringComparer nameComparer) |
||||
{ |
||||
foreach (TypeDefinition cecilType in module.Types) { |
||||
if (nameComparer.Equals(nameSpace, cecilType.Namespace)) |
||||
yield return GetClass(cecilType); |
||||
} |
||||
} |
||||
|
||||
public IEnumerable<string> GetNamespaces() |
||||
{ |
||||
return namespaces; |
||||
} |
||||
|
||||
public string GetNamespace(string nameSpace, StringComparer nameComparer) |
||||
{ |
||||
foreach (string ns in namespaces) { |
||||
if (nameComparer.Equals(ns, nameSpace)) |
||||
return ns; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
ICSharpCode.NRefactory.Utils.CacheManager ITypeResolveContext.CacheManager { |
||||
get { |
||||
// We don't support caching
|
||||
return null; |
||||
} |
||||
} |
||||
|
||||
ISynchronizedTypeResolveContext ITypeResolveContext.Synchronize() |
||||
{ |
||||
// This class is logically immutable
|
||||
return this; |
||||
} |
||||
|
||||
void IDisposable.Dispose() |
||||
{ |
||||
// exit from Synchronize() block
|
||||
} |
||||
|
||||
IEnumerable<IParsedFile> IProjectContent.Files { |
||||
get { return new IParsedFile[0]; } |
||||
} |
||||
|
||||
IParsedFile IProjectContent.GetFile(string fileName) |
||||
{ |
||||
return null; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast |
||||
{ |
||||
/// <summary>
|
||||
/// Allows storing comments inside IEnumerable{Statement}. Used in the AstMethodBuilder.
|
||||
/// CommentStatement nodes are replaced with regular comments later on.
|
||||
/// </summary>
|
||||
class CommentStatement : Statement |
||||
{ |
||||
string comment; |
||||
|
||||
public CommentStatement(string comment) |
||||
{ |
||||
if (comment == null) |
||||
throw new ArgumentNullException("comment"); |
||||
this.comment = comment; |
||||
} |
||||
|
||||
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data) |
||||
{ |
||||
return default(S); |
||||
} |
||||
|
||||
public static void ReplaceAll(AstNode tree) |
||||
{ |
||||
foreach (var cs in tree.Descendants.OfType<CommentStatement>()) { |
||||
cs.Parent.InsertChildBefore(cs, new Comment(cs.comment), Roles.Comment); |
||||
cs.Remove(); |
||||
} |
||||
} |
||||
|
||||
protected override bool DoMatch(AstNode other, Match match) |
||||
{ |
||||
CommentStatement o = other as CommentStatement; |
||||
return o != null && MatchString(comment, o.comment); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Threading; |
||||
using ICSharpCode.Decompiler.Ast; |
||||
using ICSharpCode.NRefactory.TypeSystem; |
||||
using ICSharpCode.NRefactory.TypeSystem.Implementation; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
public class DecompilerContext |
||||
{ |
||||
public ModuleDefinition CurrentModule; |
||||
public CancellationToken CancellationToken; |
||||
public TypeDefinition CurrentType; |
||||
public MethodDefinition CurrentMethod; |
||||
public DecompilerSettings Settings = new DecompilerSettings(); |
||||
|
||||
// public ITypeResolveContext TypeResolveContext;
|
||||
// public IProjectContent ProjectContent;
|
||||
|
||||
public DecompilerContext(ModuleDefinition currentModule) |
||||
{ |
||||
if (currentModule == null) |
||||
throw new ArgumentNullException("currentModule"); |
||||
this.CurrentModule = currentModule; |
||||
|
||||
// this.ProjectContent = new CecilTypeResolveContext(currentModule);
|
||||
// List<ITypeResolveContext> resolveContexts = new List<ITypeResolveContext>();
|
||||
// resolveContexts.Add(this.ProjectContent);
|
||||
// foreach (AssemblyNameReference r in currentModule.AssemblyReferences) {
|
||||
// AssemblyDefinition d = currentModule.AssemblyResolver.Resolve(r);
|
||||
// if (d != null) {
|
||||
// resolveContexts.Add(new CecilTypeResolveContext(d.MainModule));
|
||||
// }
|
||||
// }
|
||||
// this.TypeResolveContext = new CompositeTypeResolveContext(resolveContexts);
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Used to pass variable names from a method to its anonymous methods.
|
||||
/// </summary>
|
||||
internal List<string> ReservedVariableNames = new List<string>(); |
||||
|
||||
public DecompilerContext Clone() |
||||
{ |
||||
DecompilerContext ctx = (DecompilerContext)MemberwiseClone(); |
||||
ctx.ReservedVariableNames = new List<string>(ctx.ReservedVariableNames); |
||||
return ctx; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast |
||||
{ |
||||
static class NRefactoryExtensions |
||||
{ |
||||
public static T WithAnnotation<T>(this T node, object annotation) where T : AstNode |
||||
{ |
||||
if (annotation != null) |
||||
node.AddAnnotation(annotation); |
||||
return node; |
||||
} |
||||
|
||||
public static T CopyAnnotationsFrom<T>(this T node, AstNode other) where T : AstNode |
||||
{ |
||||
foreach (object annotation in other.Annotations) { |
||||
node.AddAnnotation(annotation); |
||||
} |
||||
return node; |
||||
} |
||||
|
||||
public static T Detach<T>(this T node) where T : AstNode |
||||
{ |
||||
node.Remove(); |
||||
return node; |
||||
} |
||||
|
||||
public static Expression WithName(this Expression node, string patternGroupName) |
||||
{ |
||||
return new NamedNode(patternGroupName, node); |
||||
} |
||||
|
||||
public static Statement WithName(this Statement node, string patternGroupName) |
||||
{ |
||||
return new NamedNode(patternGroupName, node); |
||||
} |
||||
|
||||
public static void AddNamedArgument(this NRefactory.CSharp.Attribute attribute, string name, Expression argument) |
||||
{ |
||||
attribute.Arguments.Add(new AssignmentExpression(new IdentifierExpression(name), argument)); |
||||
} |
||||
|
||||
public static AstType ToType(this Pattern pattern) |
||||
{ |
||||
return pattern; |
||||
} |
||||
|
||||
public static Expression ToExpression(this Pattern pattern) |
||||
{ |
||||
return pattern; |
||||
} |
||||
|
||||
public static Statement ToStatement(this Pattern pattern) |
||||
{ |
||||
return pattern; |
||||
} |
||||
|
||||
public static Statement GetNextStatement(this Statement statement) |
||||
{ |
||||
AstNode next = statement.NextSibling; |
||||
while (next != null && !(next is Statement)) |
||||
next = next.NextSibling; |
||||
return (Statement)next; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,343 @@
@@ -0,0 +1,343 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.ILAst; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast |
||||
{ |
||||
public class NameVariables |
||||
{ |
||||
static readonly Dictionary<string, string> typeNameToVariableNameDict = new Dictionary<string, string> { |
||||
{ "System.Boolean", "flag" }, |
||||
{ "System.Byte", "b" }, |
||||
{ "System.SByte", "b" }, |
||||
{ "System.Int16", "num" }, |
||||
{ "System.Int32", "num" }, |
||||
{ "System.Int64", "num" }, |
||||
{ "System.UInt16", "num" }, |
||||
{ "System.UInt32", "num" }, |
||||
{ "System.UInt64", "num" }, |
||||
{ "System.Single", "num" }, |
||||
{ "System.Double", "num" }, |
||||
{ "System.Decimal", "num" }, |
||||
{ "System.String", "text" }, |
||||
{ "System.Object", "obj" }, |
||||
{ "System.Char", "c" } |
||||
}; |
||||
|
||||
|
||||
public static void AssignNamesToVariables(DecompilerContext context, IEnumerable<ILVariable> parameters, IEnumerable<ILVariable> variables, ILBlock methodBody) |
||||
{ |
||||
NameVariables nv = new NameVariables(); |
||||
nv.context = context; |
||||
nv.fieldNamesInCurrentType = context.CurrentType.Fields.Select(f => f.Name).ToList(); |
||||
// First mark existing variable names as reserved.
|
||||
foreach (string name in context.ReservedVariableNames) |
||||
nv.AddExistingName(name); |
||||
foreach (var p in parameters) |
||||
nv.AddExistingName(p.Name); |
||||
foreach (var v in variables) { |
||||
if (v.IsGenerated) { |
||||
// don't introduce names for variables generated by ILSpy - keep "expr"/"arg"
|
||||
nv.AddExistingName(v.Name); |
||||
} else if (v.OriginalVariable != null && context.Settings.UseDebugSymbols) { |
||||
string varName = v.OriginalVariable.Name; |
||||
if (string.IsNullOrEmpty(varName) || varName.StartsWith("V_", StringComparison.Ordinal) || !IsValidName(varName)) |
||||
{ |
||||
// don't use the name from the debug symbols if it looks like a generated name
|
||||
v.Name = null; |
||||
} else { |
||||
// use the name from the debug symbols
|
||||
// (but ensure we don't use the same name for two variables)
|
||||
v.Name = nv.GetAlternativeName(varName); |
||||
} |
||||
} else { |
||||
v.Name = null; |
||||
} |
||||
} |
||||
// Now generate names:
|
||||
foreach (ILVariable p in parameters) { |
||||
if (string.IsNullOrEmpty(p.Name)) |
||||
p.Name = nv.GenerateNameForVariable(p, methodBody); |
||||
} |
||||
foreach (ILVariable varDef in variables) { |
||||
if (string.IsNullOrEmpty(varDef.Name)) |
||||
varDef.Name = nv.GenerateNameForVariable(varDef, methodBody); |
||||
} |
||||
} |
||||
|
||||
static bool IsValidName(string varName) |
||||
{ |
||||
if (string.IsNullOrEmpty(varName)) |
||||
return false; |
||||
if (!(char.IsLetter(varName[0]) || varName[0] == '_')) |
||||
return false; |
||||
for (int i = 1; i < varName.Length; i++) { |
||||
if (!(char.IsLetterOrDigit(varName[i]) || varName[i] == '_')) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
DecompilerContext context; |
||||
List<string> fieldNamesInCurrentType; |
||||
Dictionary<string, int> typeNames = new Dictionary<string, int>(); |
||||
|
||||
public void AddExistingName(string name) |
||||
{ |
||||
if (string.IsNullOrEmpty(name)) |
||||
return; |
||||
int number; |
||||
string nameWithoutDigits = SplitName(name, out number); |
||||
int existingNumber; |
||||
if (typeNames.TryGetValue(nameWithoutDigits, out existingNumber)) { |
||||
typeNames[nameWithoutDigits] = Math.Max(number, existingNumber); |
||||
} else { |
||||
typeNames.Add(nameWithoutDigits, number); |
||||
} |
||||
} |
||||
|
||||
string SplitName(string name, out int number) |
||||
{ |
||||
// First, identify whether the name already ends with a number:
|
||||
int pos = name.Length; |
||||
while (pos > 0 && name[pos-1] >= '0' && name[pos-1] <= '9') |
||||
pos--; |
||||
if (pos < name.Length) { |
||||
if (int.TryParse(name.Substring(pos), out number)) { |
||||
return name.Substring(0, pos); |
||||
} |
||||
} |
||||
number = 1; |
||||
return name; |
||||
} |
||||
|
||||
const char maxLoopVariableName = 'n'; |
||||
|
||||
public string GetAlternativeName(string oldVariableName) |
||||
{ |
||||
if (oldVariableName.Length == 1 && oldVariableName[0] >= 'i' && oldVariableName[0] <= maxLoopVariableName) { |
||||
for (char c = 'i'; c <= maxLoopVariableName; c++) { |
||||
if (!typeNames.ContainsKey(c.ToString())) { |
||||
typeNames.Add(c.ToString(), 1); |
||||
return c.ToString(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
int number; |
||||
string nameWithoutDigits = SplitName(oldVariableName, out number); |
||||
|
||||
if (!typeNames.ContainsKey(nameWithoutDigits)) { |
||||
typeNames.Add(nameWithoutDigits, number - 1); |
||||
} |
||||
int count = ++typeNames[nameWithoutDigits]; |
||||
if (count != 1) { |
||||
return nameWithoutDigits + count.ToString(); |
||||
} else { |
||||
return nameWithoutDigits; |
||||
} |
||||
} |
||||
|
||||
string GenerateNameForVariable(ILVariable variable, ILBlock methodBody) |
||||
{ |
||||
string proposedName = null; |
||||
if (variable.Type == context.CurrentType.Module.TypeSystem.Int32) { |
||||
// test whether the variable might be a loop counter
|
||||
bool isLoopCounter = false; |
||||
foreach (ILWhileLoop loop in methodBody.GetSelfAndChildrenRecursive<ILWhileLoop>()) { |
||||
ILExpression expr = loop.Condition; |
||||
while (expr != null && expr.Code == ILCode.LogicNot) |
||||
expr = expr.Arguments[0]; |
||||
if (expr != null) { |
||||
switch (expr.Code) { |
||||
case ILCode.Clt: |
||||
case ILCode.Clt_Un: |
||||
case ILCode.Cgt: |
||||
case ILCode.Cgt_Un: |
||||
ILVariable loadVar; |
||||
if (expr.Arguments[0].Match(ILCode.Ldloc, out loadVar) && loadVar == variable) { |
||||
isLoopCounter = true; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if (isLoopCounter) { |
||||
// For loop variables, use i,j,k,l,m,n
|
||||
for (char c = 'i'; c <= maxLoopVariableName; c++) { |
||||
if (!typeNames.ContainsKey(c.ToString())) { |
||||
proposedName = c.ToString(); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
if (string.IsNullOrEmpty(proposedName)) { |
||||
var proposedNameForStores = |
||||
(from expr in methodBody.GetSelfAndChildrenRecursive<ILExpression>() |
||||
where expr.Code == ILCode.Stloc && expr.Operand == variable |
||||
select GetNameFromExpression(expr.Arguments.Single()) |
||||
).Except(fieldNamesInCurrentType).ToList(); |
||||
if (proposedNameForStores.Count == 1) { |
||||
proposedName = proposedNameForStores[0]; |
||||
} |
||||
} |
||||
if (string.IsNullOrEmpty(proposedName)) { |
||||
var proposedNameForLoads = |
||||
(from expr in methodBody.GetSelfAndChildrenRecursive<ILExpression>() |
||||
from i in Enumerable.Range(0, expr.Arguments.Count) |
||||
let arg = expr.Arguments[i] |
||||
where arg.Code == ILCode.Ldloc && arg.Operand == variable |
||||
select GetNameForArgument(expr, i) |
||||
).Except(fieldNamesInCurrentType).ToList(); |
||||
if (proposedNameForLoads.Count == 1) { |
||||
proposedName = proposedNameForLoads[0]; |
||||
} |
||||
} |
||||
if (string.IsNullOrEmpty(proposedName)) { |
||||
proposedName = GetNameByType(variable.Type); |
||||
} |
||||
|
||||
// remove any numbers from the proposed name
|
||||
int number; |
||||
proposedName = SplitName(proposedName, out number); |
||||
|
||||
if (!typeNames.ContainsKey(proposedName)) { |
||||
typeNames.Add(proposedName, 0); |
||||
} |
||||
int count = ++typeNames[proposedName]; |
||||
if (count > 1) { |
||||
return proposedName + count.ToString(); |
||||
} else { |
||||
return proposedName; |
||||
} |
||||
} |
||||
|
||||
static string GetNameFromExpression(ILExpression expr) |
||||
{ |
||||
switch (expr.Code) { |
||||
case ILCode.Ldfld: |
||||
case ILCode.Ldsfld: |
||||
return CleanUpVariableName(((FieldReference)expr.Operand).Name); |
||||
case ILCode.Call: |
||||
case ILCode.Callvirt: |
||||
case ILCode.CallGetter: |
||||
case ILCode.CallvirtGetter: |
||||
MethodReference mr = (MethodReference)expr.Operand; |
||||
if (mr.Name.StartsWith("get_", StringComparison.OrdinalIgnoreCase) && mr.Parameters.Count == 0) { |
||||
// use name from properties, but not from indexers
|
||||
return CleanUpVariableName(mr.Name.Substring(4)); |
||||
} else if (mr.Name.StartsWith("Get", StringComparison.OrdinalIgnoreCase) && mr.Name.Length >= 4 && char.IsUpper(mr.Name[3])) { |
||||
// use name from Get-methods
|
||||
return CleanUpVariableName(mr.Name.Substring(3)); |
||||
} |
||||
break; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
static string GetNameForArgument(ILExpression parent, int i) |
||||
{ |
||||
switch (parent.Code) { |
||||
case ILCode.Stfld: |
||||
case ILCode.Stsfld: |
||||
if (i == parent.Arguments.Count - 1) // last argument is stored value
|
||||
return CleanUpVariableName(((FieldReference)parent.Operand).Name); |
||||
else |
||||
break; |
||||
case ILCode.Call: |
||||
case ILCode.Callvirt: |
||||
case ILCode.Newobj: |
||||
case ILCode.CallGetter: |
||||
case ILCode.CallvirtGetter: |
||||
case ILCode.CallSetter: |
||||
case ILCode.CallvirtSetter: |
||||
MethodReference methodRef = (MethodReference)parent.Operand; |
||||
if (methodRef.Parameters.Count == 1 && i == parent.Arguments.Count - 1) { |
||||
// argument might be value of a setter
|
||||
if (methodRef.Name.StartsWith("set_", StringComparison.OrdinalIgnoreCase)) { |
||||
return CleanUpVariableName(methodRef.Name.Substring(4)); |
||||
} else if (methodRef.Name.StartsWith("Set", StringComparison.OrdinalIgnoreCase) && methodRef.Name.Length >= 4 && char.IsUpper(methodRef.Name[3])) { |
||||
return CleanUpVariableName(methodRef.Name.Substring(3)); |
||||
} |
||||
} |
||||
MethodDefinition methodDef = methodRef.Resolve(); |
||||
if (methodDef != null) { |
||||
var p = methodDef.Parameters.ElementAtOrDefault((parent.Code != ILCode.Newobj && methodDef.HasThis) ? i - 1 : i); |
||||
if (p != null && !string.IsNullOrEmpty(p.Name)) |
||||
return CleanUpVariableName(p.Name); |
||||
} |
||||
break; |
||||
case ILCode.Ret: |
||||
return "result"; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
string GetNameByType(TypeReference type) |
||||
{ |
||||
type = TypeAnalysis.UnpackModifiers(type); |
||||
|
||||
GenericInstanceType git = type as GenericInstanceType; |
||||
if (git != null && git.ElementType.FullName == "System.Nullable`1" && git.GenericArguments.Count == 1) { |
||||
type = ((GenericInstanceType)type).GenericArguments[0]; |
||||
} |
||||
|
||||
string name; |
||||
if (type.IsArray) { |
||||
name = "array"; |
||||
} else if (type.IsPointer || type.IsByReference) { |
||||
name = "ptr"; |
||||
} else if (type.Name.EndsWith("Exception", StringComparison.Ordinal)) { |
||||
name = "ex"; |
||||
} else if (!typeNameToVariableNameDict.TryGetValue(type.FullName, out name)) { |
||||
name = type.Name; |
||||
// remove the 'I' for interfaces
|
||||
if (name.Length >= 3 && name[0] == 'I' && char.IsUpper(name[1]) && char.IsLower(name[2])) |
||||
name = name.Substring(1); |
||||
name = CleanUpVariableName(name); |
||||
} |
||||
return name; |
||||
} |
||||
|
||||
static string CleanUpVariableName(string name) |
||||
{ |
||||
// remove the backtick (generics)
|
||||
int pos = name.IndexOf('`'); |
||||
if (pos >= 0) |
||||
name = name.Substring(0, pos); |
||||
|
||||
// remove field prefix:
|
||||
if (name.Length > 2 && name.StartsWith("m_", StringComparison.Ordinal)) |
||||
name = name.Substring(2); |
||||
else if (name.Length > 1 && name[0] == '_') |
||||
name = name.Substring(1); |
||||
|
||||
if (name.Length == 0) |
||||
return "obj"; |
||||
else |
||||
return char.ToLower(name[0]) + name.Substring(1); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,193 @@
@@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.ILAst; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast |
||||
{ |
||||
public class TextOutputFormatter : IOutputFormatter |
||||
{ |
||||
readonly ITextOutput output; |
||||
readonly Stack<AstNode> nodeStack = new Stack<AstNode>(); |
||||
int braceLevelWithinType = -1; |
||||
bool inDocumentationComment = false; |
||||
|
||||
public TextOutputFormatter(ITextOutput output) |
||||
{ |
||||
if (output == null) |
||||
throw new ArgumentNullException("output"); |
||||
this.output = output; |
||||
} |
||||
|
||||
public void WriteIdentifier(string identifier) |
||||
{ |
||||
MemberReference memberRef = GetCurrentMemberReference(); |
||||
|
||||
if (memberRef != null) |
||||
output.WriteReference(identifier, memberRef); |
||||
else |
||||
output.Write(identifier); |
||||
} |
||||
|
||||
MemberReference GetCurrentMemberReference() |
||||
{ |
||||
AstNode node = nodeStack.Peek(); |
||||
MemberReference memberRef = node.Annotation<MemberReference>(); |
||||
if (memberRef == null && node.Role == AstNode.Roles.TargetExpression && (node.Parent is InvocationExpression || node.Parent is ObjectCreateExpression)) { |
||||
memberRef = node.Parent.Annotation<MemberReference>(); |
||||
} |
||||
return memberRef; |
||||
} |
||||
|
||||
public void WriteKeyword(string keyword) |
||||
{ |
||||
output.Write(keyword); |
||||
} |
||||
|
||||
public void WriteToken(string token) |
||||
{ |
||||
// Attach member reference to token only if there's no identifier in the current node.
|
||||
MemberReference memberRef = GetCurrentMemberReference(); |
||||
if (memberRef != null && nodeStack.Peek().GetChildByRole(AstNode.Roles.Identifier).IsNull) |
||||
output.WriteReference(token, memberRef); |
||||
else |
||||
output.Write(token); |
||||
} |
||||
|
||||
public void Space() |
||||
{ |
||||
output.Write(' '); |
||||
} |
||||
|
||||
public void OpenBrace(BraceStyle style) |
||||
{ |
||||
if (braceLevelWithinType >= 0 || nodeStack.Peek() is TypeDeclaration) |
||||
braceLevelWithinType++; |
||||
if (nodeStack.OfType<BlockStatement>().Count() <= 1) { |
||||
output.MarkFoldStart(defaultCollapsed: braceLevelWithinType == 1); |
||||
} |
||||
output.WriteLine(); |
||||
output.WriteLine("{"); |
||||
output.Indent(); |
||||
} |
||||
|
||||
public void CloseBrace(BraceStyle style) |
||||
{ |
||||
output.Unindent(); |
||||
output.Write('}'); |
||||
if (nodeStack.OfType<BlockStatement>().Count() <= 1) |
||||
output.MarkFoldEnd(); |
||||
if (braceLevelWithinType >= 0) |
||||
braceLevelWithinType--; |
||||
} |
||||
|
||||
public void Indent() |
||||
{ |
||||
output.Indent(); |
||||
} |
||||
|
||||
public void Unindent() |
||||
{ |
||||
output.Unindent(); |
||||
} |
||||
|
||||
public void NewLine() |
||||
{ |
||||
output.WriteLine(); |
||||
} |
||||
|
||||
public void WriteComment(CommentType commentType, string content) |
||||
{ |
||||
switch (commentType) { |
||||
case CommentType.SingleLine: |
||||
output.Write("//"); |
||||
output.WriteLine(content); |
||||
break; |
||||
case CommentType.MultiLine: |
||||
output.Write("/*"); |
||||
output.Write(content); |
||||
output.Write("*/"); |
||||
break; |
||||
case CommentType.Documentation: |
||||
if (!inDocumentationComment) |
||||
output.MarkFoldStart("///" + content, true); |
||||
output.Write("///"); |
||||
output.Write(content); |
||||
inDocumentationComment = true; |
||||
bool isLastLine = !(nodeStack.Peek().NextSibling is Comment); |
||||
if (isLastLine) { |
||||
inDocumentationComment = false; |
||||
output.MarkFoldEnd(); |
||||
} |
||||
output.WriteLine(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public void StartNode(AstNode node) |
||||
{ |
||||
// code mappings
|
||||
var ranges = node.Annotation<List<ILRange>>(); |
||||
if (ranges != null && ranges.Count > 0) { |
||||
// find the ancestor that has method mapping as annotation
|
||||
if (node.Parent != null) |
||||
{ |
||||
var n = node.Ancestors.FirstOrDefault(a => a.Annotation<MemberMapping>() != null); |
||||
if (n != null) { |
||||
MemberMapping mapping = n.Annotation<MemberMapping>(); |
||||
|
||||
// add all ranges
|
||||
foreach (var range in ranges) { |
||||
mapping.MemberCodeMappings.Add(new SourceCodeMapping { |
||||
ILInstructionOffset = range, |
||||
SourceCodeLine = output.CurrentLine, |
||||
MemberMapping = mapping |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// definitions of types and their members
|
||||
Predicate<AstNode> predicate = n => n is AttributedNode; |
||||
|
||||
if (predicate(node)) { |
||||
var n = node as AttributedNode; |
||||
int c = 0; |
||||
if (n != null) |
||||
c = n.Attributes.Count; |
||||
node.AddAnnotation(Tuple.Create(output.CurrentLine + c, output.CurrentColumn)); |
||||
} |
||||
|
||||
nodeStack.Push(node); |
||||
} |
||||
|
||||
public void EndNode(AstNode node) |
||||
{ |
||||
if (nodeStack.Pop() != node) |
||||
throw new InvalidOperationException(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,356 @@
@@ -0,0 +1,356 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Linq; |
||||
using ICSharpCode.Decompiler.ILAst; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Add checked/unchecked blocks.
|
||||
/// </summary>
|
||||
public class AddCheckedBlocks : IAstTransform |
||||
{ |
||||
#region Annotation
|
||||
sealed class CheckedUncheckedAnnotation { |
||||
/// <summary>
|
||||
/// true=checked, false=unchecked
|
||||
/// </summary>
|
||||
public bool IsChecked; |
||||
} |
||||
|
||||
public static readonly object CheckedAnnotation = new CheckedUncheckedAnnotation { IsChecked = true }; |
||||
public static readonly object UncheckedAnnotation = new CheckedUncheckedAnnotation { IsChecked = false }; |
||||
#endregion
|
||||
|
||||
/* |
||||
We treat placing checked/unchecked blocks as an optimization problem, with the following goals: |
||||
1. Use minimum number of checked blocks+expressions |
||||
2. Prefer checked expressions over checked blocks |
||||
3. Make the scope of checked expressions as small as possible |
||||
4. Make the scope of checked blocks as large as possible |
||||
(where goal 1 has the highest priority) |
||||
*/ |
||||
|
||||
#region struct Cost
|
||||
struct Cost |
||||
{ |
||||
// highest possible cost so that the Blocks+Expressions addition doesn't overflow
|
||||
public static readonly Cost Infinite = new Cost(0x3fffffff, 0x3fffffff); |
||||
|
||||
public readonly int Blocks; |
||||
public readonly int Expressions; |
||||
|
||||
public Cost(int blocks, int expressions) |
||||
{ |
||||
this.Blocks = blocks; |
||||
this.Expressions = expressions; |
||||
} |
||||
|
||||
public static bool operator <(Cost a, Cost b) |
||||
{ |
||||
return a.Blocks + a.Expressions < b.Blocks + b.Expressions |
||||
|| a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks < b.Blocks; |
||||
} |
||||
|
||||
public static bool operator >(Cost a, Cost b) |
||||
{ |
||||
return a.Blocks + a.Expressions > b.Blocks + b.Expressions |
||||
|| a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks > b.Blocks; |
||||
} |
||||
|
||||
public static bool operator <=(Cost a, Cost b) |
||||
{ |
||||
return a.Blocks + a.Expressions < b.Blocks + b.Expressions |
||||
|| a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks <= b.Blocks; |
||||
} |
||||
|
||||
public static bool operator >=(Cost a, Cost b) |
||||
{ |
||||
return a.Blocks + a.Expressions > b.Blocks + b.Expressions |
||||
|| a.Blocks + a.Expressions == b.Blocks + b.Expressions && a.Blocks >= b.Blocks; |
||||
} |
||||
|
||||
public static Cost operator +(Cost a, Cost b) |
||||
{ |
||||
return new Cost(a.Blocks + b.Blocks, a.Expressions + b.Expressions); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Format("[{0} + {1}]", Blocks, Expressions); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region class InsertedNode
|
||||
/// <summary>
|
||||
/// Holds the blocks and expressions that should be inserted
|
||||
/// </summary>
|
||||
abstract class InsertedNode |
||||
{ |
||||
public static InsertedNode operator +(InsertedNode a, InsertedNode b) |
||||
{ |
||||
if (a == null) |
||||
return b; |
||||
if (b == null) |
||||
return a; |
||||
return new InsertedNodeList(a, b); |
||||
} |
||||
|
||||
public abstract void Insert(); |
||||
} |
||||
|
||||
class InsertedNodeList : InsertedNode |
||||
{ |
||||
readonly InsertedNode child1, child2; |
||||
|
||||
public InsertedNodeList(AddCheckedBlocks.InsertedNode child1, AddCheckedBlocks.InsertedNode child2) |
||||
{ |
||||
this.child1 = child1; |
||||
this.child2 = child2; |
||||
} |
||||
|
||||
public override void Insert() |
||||
{ |
||||
child1.Insert(); |
||||
child2.Insert(); |
||||
} |
||||
} |
||||
|
||||
class InsertedExpression : InsertedNode |
||||
{ |
||||
readonly Expression expression; |
||||
readonly bool isChecked; |
||||
|
||||
public InsertedExpression(Expression expression, bool isChecked) |
||||
{ |
||||
this.expression = expression; |
||||
this.isChecked = isChecked; |
||||
} |
||||
|
||||
public override void Insert() |
||||
{ |
||||
if (isChecked) |
||||
expression.ReplaceWith(e => new CheckedExpression { Expression = e }); |
||||
else |
||||
expression.ReplaceWith(e => new UncheckedExpression { Expression = e }); |
||||
} |
||||
} |
||||
|
||||
class ConvertCompoundAssignment : InsertedNode |
||||
{ |
||||
readonly Expression expression; |
||||
readonly bool isChecked; |
||||
|
||||
public ConvertCompoundAssignment(Expression expression, bool isChecked) |
||||
{ |
||||
this.expression = expression; |
||||
this.isChecked = isChecked; |
||||
} |
||||
|
||||
public override void Insert() |
||||
{ |
||||
AssignmentExpression assign = expression.Annotation<ReplaceMethodCallsWithOperators.RestoreOriginalAssignOperatorAnnotation>().Restore(expression); |
||||
expression.ReplaceWith(assign); |
||||
if (isChecked) |
||||
assign.Right = new CheckedExpression { Expression = assign.Right.Detach() }; |
||||
else |
||||
assign.Right = new UncheckedExpression { Expression = assign.Right.Detach() }; |
||||
} |
||||
} |
||||
|
||||
class InsertedBlock : InsertedNode |
||||
{ |
||||
readonly Statement firstStatement; // inclusive
|
||||
readonly Statement lastStatement; // exclusive
|
||||
readonly bool isChecked; |
||||
|
||||
public InsertedBlock(Statement firstStatement, Statement lastStatement, bool isChecked) |
||||
{ |
||||
this.firstStatement = firstStatement; |
||||
this.lastStatement = lastStatement; |
||||
this.isChecked = isChecked; |
||||
} |
||||
|
||||
public override void Insert() |
||||
{ |
||||
BlockStatement newBlock = new BlockStatement(); |
||||
// Move all statements except for the first
|
||||
Statement next; |
||||
for (Statement stmt = firstStatement.GetNextStatement(); stmt != lastStatement; stmt = next) { |
||||
next = stmt.GetNextStatement(); |
||||
newBlock.Add(stmt.Detach()); |
||||
} |
||||
// Replace the first statement with the new (un)checked block
|
||||
if (isChecked) |
||||
firstStatement.ReplaceWith(new CheckedStatement { Body = newBlock }); |
||||
else |
||||
firstStatement.ReplaceWith(new UncheckedStatement { Body = newBlock }); |
||||
// now also move the first node into the new block
|
||||
newBlock.Statements.InsertAfter(null, firstStatement); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region class Result
|
||||
/// <summary>
|
||||
/// Holds the result of an insertion operation.
|
||||
/// </summary>
|
||||
class Result |
||||
{ |
||||
public Cost CostInCheckedContext; |
||||
public InsertedNode NodesToInsertInCheckedContext; |
||||
public Cost CostInUncheckedContext; |
||||
public InsertedNode NodesToInsertInUncheckedContext; |
||||
} |
||||
#endregion
|
||||
|
||||
public void Run(AstNode node) |
||||
{ |
||||
BlockStatement block = node as BlockStatement; |
||||
if (block == null) { |
||||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
||||
Run(child); |
||||
} |
||||
} else { |
||||
Result r = GetResultFromBlock(block); |
||||
if (r.NodesToInsertInUncheckedContext != null) |
||||
r.NodesToInsertInUncheckedContext.Insert(); |
||||
} |
||||
} |
||||
|
||||
Result GetResultFromBlock(BlockStatement block) |
||||
{ |
||||
// For a block, we are tracking 4 possibilities:
|
||||
// a) context is checked, no unchecked block open
|
||||
Cost costCheckedContext = new Cost(0, 0); |
||||
InsertedNode nodesCheckedContext = null; |
||||
// b) context is checked, an unchecked block is open
|
||||
Cost costCheckedContextUncheckedBlockOpen = Cost.Infinite; |
||||
InsertedNode nodesCheckedContextUncheckedBlockOpen = null; |
||||
Statement uncheckedBlockStart = null; |
||||
// c) context is unchecked, no checked block open
|
||||
Cost costUncheckedContext = new Cost(0, 0); |
||||
InsertedNode nodesUncheckedContext = null; |
||||
// d) context is unchecked, a checked block is open
|
||||
Cost costUncheckedContextCheckedBlockOpen = Cost.Infinite; |
||||
InsertedNode nodesUncheckedContextCheckedBlockOpen = null; |
||||
Statement checkedBlockStart = null; |
||||
|
||||
Statement statement = block.Statements.FirstOrDefault(); |
||||
while (true) { |
||||
// Blocks can be closed 'for free'. We use '<=' so that blocks are closed as late as possible (goal 4)
|
||||
if (costCheckedContextUncheckedBlockOpen <= costCheckedContext) { |
||||
costCheckedContext = costCheckedContextUncheckedBlockOpen; |
||||
nodesCheckedContext = nodesCheckedContextUncheckedBlockOpen + new InsertedBlock(uncheckedBlockStart, statement, false); |
||||
} |
||||
if (costUncheckedContextCheckedBlockOpen <= costUncheckedContext) { |
||||
costUncheckedContext = costUncheckedContextCheckedBlockOpen; |
||||
nodesUncheckedContext = nodesUncheckedContextCheckedBlockOpen + new InsertedBlock(checkedBlockStart, statement, true); |
||||
} |
||||
if (statement == null) |
||||
break; |
||||
// Now try opening blocks. We use '<' so that blocks are opened as early as possible. (goal 4)
|
||||
if (costCheckedContext + new Cost(1, 0) < costCheckedContextUncheckedBlockOpen) { |
||||
costCheckedContextUncheckedBlockOpen = costCheckedContext + new Cost(1, 0); |
||||
nodesCheckedContextUncheckedBlockOpen = nodesCheckedContext; |
||||
uncheckedBlockStart = statement; |
||||
} |
||||
if (costUncheckedContext + new Cost(1, 0) < costUncheckedContextCheckedBlockOpen) { |
||||
costUncheckedContextCheckedBlockOpen = costUncheckedContext + new Cost(1, 0); |
||||
nodesUncheckedContextCheckedBlockOpen = nodesUncheckedContext; |
||||
checkedBlockStart = statement; |
||||
} |
||||
// Now handle the statement
|
||||
Result stmtResult = GetResult(statement); |
||||
|
||||
costCheckedContext += stmtResult.CostInCheckedContext; |
||||
nodesCheckedContext += stmtResult.NodesToInsertInCheckedContext; |
||||
costCheckedContextUncheckedBlockOpen += stmtResult.CostInUncheckedContext; |
||||
nodesCheckedContextUncheckedBlockOpen += stmtResult.NodesToInsertInUncheckedContext; |
||||
costUncheckedContext += stmtResult.CostInUncheckedContext; |
||||
nodesUncheckedContext += stmtResult.NodesToInsertInUncheckedContext; |
||||
costUncheckedContextCheckedBlockOpen += stmtResult.CostInCheckedContext; |
||||
nodesUncheckedContextCheckedBlockOpen += stmtResult.NodesToInsertInCheckedContext; |
||||
|
||||
statement = statement.GetNextStatement(); |
||||
} |
||||
|
||||
return new Result { |
||||
CostInCheckedContext = costCheckedContext, NodesToInsertInCheckedContext = nodesCheckedContext, |
||||
CostInUncheckedContext = costUncheckedContext, NodesToInsertInUncheckedContext = nodesUncheckedContext |
||||
}; |
||||
} |
||||
|
||||
Result GetResult(AstNode node) |
||||
{ |
||||
if (node is BlockStatement) |
||||
return GetResultFromBlock((BlockStatement)node); |
||||
Result result = new Result(); |
||||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
||||
Result childResult = GetResult(child); |
||||
result.CostInCheckedContext += childResult.CostInCheckedContext; |
||||
result.NodesToInsertInCheckedContext += childResult.NodesToInsertInCheckedContext; |
||||
result.CostInUncheckedContext += childResult.CostInUncheckedContext; |
||||
result.NodesToInsertInUncheckedContext += childResult.NodesToInsertInUncheckedContext; |
||||
} |
||||
Expression expr = node as Expression; |
||||
if (expr != null) { |
||||
CheckedUncheckedAnnotation annotation = expr.Annotation<CheckedUncheckedAnnotation>(); |
||||
if (annotation != null) { |
||||
// If the annotation requires this node to be in a specific context, add a huge cost to the other context
|
||||
// That huge cost gives us the option to ignore a required checked/unchecked expression when there wouldn't be any
|
||||
// solution otherwise. (e.g. "for (checked(M().x += 1); true; unchecked(M().x += 2)) {}")
|
||||
if (annotation.IsChecked) |
||||
result.CostInUncheckedContext += new Cost(10000, 0); |
||||
else |
||||
result.CostInCheckedContext += new Cost(10000, 0); |
||||
} |
||||
// Embed this node in an checked/unchecked expression:
|
||||
if (expr.Parent is ExpressionStatement) { |
||||
// We cannot use checked/unchecked for top-level-expressions.
|
||||
// However, we could try converting a compound assignment (checked(a+=b);) or unary operator (checked(a++);)
|
||||
// back to its old form.
|
||||
if (expr.Annotation<ReplaceMethodCallsWithOperators.RestoreOriginalAssignOperatorAnnotation>() != null) { |
||||
// We use '<' so that expressions are introduced on the deepest level possible (goal 3)
|
||||
if (result.CostInCheckedContext + new Cost(1, 1) < result.CostInUncheckedContext) { |
||||
result.CostInUncheckedContext = result.CostInCheckedContext + new Cost(1, 1); |
||||
result.NodesToInsertInUncheckedContext = result.NodesToInsertInCheckedContext + new ConvertCompoundAssignment(expr, true); |
||||
} else if (result.CostInUncheckedContext + new Cost(1, 1) < result.CostInCheckedContext) { |
||||
result.CostInCheckedContext = result.CostInUncheckedContext + new Cost(1, 1); |
||||
result.NodesToInsertInCheckedContext = result.NodesToInsertInUncheckedContext + new ConvertCompoundAssignment(expr, false); |
||||
} |
||||
} |
||||
} else { |
||||
// We use '<' so that expressions are introduced on the deepest level possible (goal 3)
|
||||
if (result.CostInCheckedContext + new Cost(0, 1) < result.CostInUncheckedContext) { |
||||
result.CostInUncheckedContext = result.CostInCheckedContext + new Cost(0, 1); |
||||
result.NodesToInsertInUncheckedContext = result.NodesToInsertInCheckedContext + new InsertedExpression(expr, true); |
||||
} else if (result.CostInUncheckedContext + new Cost(0, 1) < result.CostInCheckedContext) { |
||||
result.CostInCheckedContext = result.CostInUncheckedContext + new Cost(0, 1); |
||||
result.NodesToInsertInCheckedContext = result.NodesToInsertInUncheckedContext + new InsertedExpression(expr, false); |
||||
} |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Combines query expressions and removes transparent identifiers.
|
||||
/// </summary>
|
||||
public class CombineQueryExpressions : IAstTransform |
||||
{ |
||||
readonly DecompilerContext context; |
||||
|
||||
public CombineQueryExpressions(DecompilerContext context) |
||||
{ |
||||
this.context = context; |
||||
} |
||||
|
||||
public void Run(AstNode compilationUnit) |
||||
{ |
||||
if (!context.Settings.QueryExpressions) |
||||
return; |
||||
CombineQueries(compilationUnit); |
||||
} |
||||
|
||||
static readonly InvocationExpression castPattern = new InvocationExpression { |
||||
Target = new MemberReferenceExpression { |
||||
Target = new AnyNode("inExpr"), |
||||
MemberName = "Cast", |
||||
TypeArguments = { new AnyNode("targetType") } |
||||
}}; |
||||
|
||||
void CombineQueries(AstNode node) |
||||
{ |
||||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
||||
CombineQueries(child); |
||||
} |
||||
QueryExpression query = node as QueryExpression; |
||||
if (query != null) { |
||||
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); |
||||
QueryExpression innerQuery = fromClause.Expression as QueryExpression; |
||||
if (innerQuery != null) { |
||||
if (TryRemoveTransparentIdentifier(query, fromClause, innerQuery)) { |
||||
RemoveTransparentIdentifierReferences(query); |
||||
} else { |
||||
QueryContinuationClause continuation = new QueryContinuationClause(); |
||||
continuation.PrecedingQuery = innerQuery.Detach(); |
||||
continuation.Identifier = fromClause.Identifier; |
||||
fromClause.ReplaceWith(continuation); |
||||
} |
||||
} else { |
||||
Match m = castPattern.Match(fromClause.Expression); |
||||
if (m.Success) { |
||||
fromClause.Type = m.Get<AstType>("targetType").Single().Detach(); |
||||
fromClause.Expression = m.Get<Expression>("inExpr").Single().Detach(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
static readonly QuerySelectClause selectTransparentIdentifierPattern = new QuerySelectClause { |
||||
Expression = new ObjectCreateExpression { |
||||
Initializer = new ArrayInitializerExpression { |
||||
Elements = { |
||||
new NamedNode("nae1", new NamedArgumentExpression { Expression = new IdentifierExpression() }), |
||||
new NamedNode("nae2", new NamedArgumentExpression { Expression = new AnyNode() }) |
||||
} |
||||
} |
||||
}}; |
||||
|
||||
bool IsTransparentIdentifier(string identifier) |
||||
{ |
||||
return identifier.StartsWith("<>", StringComparison.Ordinal) && identifier.Contains("TransparentIdentifier"); |
||||
} |
||||
|
||||
bool TryRemoveTransparentIdentifier(QueryExpression query, QueryFromClause fromClause, QueryExpression innerQuery) |
||||
{ |
||||
if (!IsTransparentIdentifier(fromClause.Identifier)) |
||||
return false; |
||||
Match match = selectTransparentIdentifierPattern.Match(innerQuery.Clauses.Last()); |
||||
if (!match.Success) |
||||
return false; |
||||
QuerySelectClause selectClause = (QuerySelectClause)innerQuery.Clauses.Last(); |
||||
NamedArgumentExpression nae1 = match.Get<NamedArgumentExpression>("nae1").Single(); |
||||
NamedArgumentExpression nae2 = match.Get<NamedArgumentExpression>("nae2").Single(); |
||||
if (nae1.Identifier != ((IdentifierExpression)nae1.Expression).Identifier) |
||||
return false; |
||||
IdentifierExpression nae2IdentExpr = nae2.Expression as IdentifierExpression; |
||||
if (nae2IdentExpr != null && nae2.Identifier == nae2IdentExpr.Identifier) { |
||||
// from * in (from x in ... select new { x = x, y = y }) ...
|
||||
// =>
|
||||
// from x in ... ...
|
||||
fromClause.Remove(); |
||||
selectClause.Remove(); |
||||
// Move clauses from innerQuery to query
|
||||
QueryClause insertionPos = null; |
||||
foreach (var clause in innerQuery.Clauses) { |
||||
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); |
||||
} |
||||
} else { |
||||
// from * in (from x in ... select new { x = x, y = expr }) ...
|
||||
// =>
|
||||
// from x in ... let y = expr ...
|
||||
fromClause.Remove(); |
||||
selectClause.Remove(); |
||||
// Move clauses from innerQuery to query
|
||||
QueryClause insertionPos = null; |
||||
foreach (var clause in innerQuery.Clauses) { |
||||
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); |
||||
} |
||||
query.Clauses.InsertAfter(insertionPos, new QueryLetClause { Identifier = nae2.Identifier, Expression = nae2.Expression.Detach() }); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Removes all occurrences of transparent identifiers
|
||||
/// </summary>
|
||||
void RemoveTransparentIdentifierReferences(AstNode node) |
||||
{ |
||||
foreach (AstNode child in node.Children) { |
||||
RemoveTransparentIdentifierReferences(child); |
||||
} |
||||
MemberReferenceExpression mre = node as MemberReferenceExpression; |
||||
if (mre != null) { |
||||
IdentifierExpression ident = mre.Target as IdentifierExpression; |
||||
if (ident != null && IsTransparentIdentifier(ident.Identifier)) { |
||||
IdentifierExpression newIdent = new IdentifierExpression(mre.MemberName); |
||||
mre.TypeArguments.MoveTo(newIdent.TypeArguments); |
||||
newIdent.CopyAnnotationsFrom(mre); |
||||
newIdent.RemoveAnnotations<PropertyDeclaration>(); // remove the reference to the property of the anonymous type
|
||||
mre.ReplaceWith(newIdent); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Base class for AST visitors that need the current type/method context info.
|
||||
/// </summary>
|
||||
public abstract class ContextTrackingVisitor<TResult> : DepthFirstAstVisitor<object, TResult>, IAstTransform |
||||
{ |
||||
protected readonly DecompilerContext context; |
||||
|
||||
protected ContextTrackingVisitor(DecompilerContext context) |
||||
{ |
||||
if (context == null) |
||||
throw new ArgumentNullException("context"); |
||||
this.context = context; |
||||
} |
||||
|
||||
public override TResult VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) |
||||
{ |
||||
TypeDefinition oldType = context.CurrentType; |
||||
try { |
||||
context.CurrentType = typeDeclaration.Annotation<TypeDefinition>(); |
||||
return base.VisitTypeDeclaration(typeDeclaration, data); |
||||
} finally { |
||||
context.CurrentType = oldType; |
||||
} |
||||
} |
||||
|
||||
public override TResult VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) |
||||
{ |
||||
Debug.Assert(context.CurrentMethod == null); |
||||
try { |
||||
context.CurrentMethod = methodDeclaration.Annotation<MethodDefinition>(); |
||||
return base.VisitMethodDeclaration(methodDeclaration, data); |
||||
} finally { |
||||
context.CurrentMethod = null; |
||||
} |
||||
} |
||||
|
||||
public override TResult VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) |
||||
{ |
||||
Debug.Assert(context.CurrentMethod == null); |
||||
try { |
||||
context.CurrentMethod = constructorDeclaration.Annotation<MethodDefinition>(); |
||||
return base.VisitConstructorDeclaration(constructorDeclaration, data); |
||||
} finally { |
||||
context.CurrentMethod = null; |
||||
} |
||||
} |
||||
|
||||
public override TResult VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration, object data) |
||||
{ |
||||
Debug.Assert(context.CurrentMethod == null); |
||||
try { |
||||
context.CurrentMethod = destructorDeclaration.Annotation<MethodDefinition>(); |
||||
return base.VisitDestructorDeclaration(destructorDeclaration, data); |
||||
} finally { |
||||
context.CurrentMethod = null; |
||||
} |
||||
} |
||||
|
||||
public override TResult VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data) |
||||
{ |
||||
Debug.Assert(context.CurrentMethod == null); |
||||
try { |
||||
context.CurrentMethod = operatorDeclaration.Annotation<MethodDefinition>(); |
||||
return base.VisitOperatorDeclaration(operatorDeclaration, data); |
||||
} finally { |
||||
context.CurrentMethod = null; |
||||
} |
||||
} |
||||
|
||||
public override TResult VisitAccessor(Accessor accessor, object data) |
||||
{ |
||||
Debug.Assert(context.CurrentMethod == null); |
||||
try { |
||||
context.CurrentMethod = accessor.Annotation<MethodDefinition>(); |
||||
return base.VisitAccessor(accessor, data); |
||||
} finally { |
||||
context.CurrentMethod = null; |
||||
} |
||||
} |
||||
|
||||
void IAstTransform.Run(AstNode node) |
||||
{ |
||||
node.AcceptVisitor(this, null); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,176 @@
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// If the first element of a constructor is a chained constructor call, convert it into a constructor initializer.
|
||||
/// </summary>
|
||||
public class ConvertConstructorCallIntoInitializer : DepthFirstAstVisitor<object, object>, IAstTransform |
||||
{ |
||||
public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) |
||||
{ |
||||
ExpressionStatement stmt = constructorDeclaration.Body.Statements.FirstOrDefault() as ExpressionStatement; |
||||
if (stmt == null) |
||||
return null; |
||||
InvocationExpression invocation = stmt.Expression as InvocationExpression; |
||||
if (invocation == null) |
||||
return null; |
||||
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; |
||||
if (mre != null && mre.MemberName == ".ctor") { |
||||
ConstructorInitializer ci = new ConstructorInitializer(); |
||||
if (mre.Target is ThisReferenceExpression) |
||||
ci.ConstructorInitializerType = ConstructorInitializerType.This; |
||||
else if (mre.Target is BaseReferenceExpression) |
||||
ci.ConstructorInitializerType = ConstructorInitializerType.Base; |
||||
else |
||||
return null; |
||||
// Move arguments from invocation to initializer:
|
||||
invocation.Arguments.MoveTo(ci.Arguments); |
||||
// Add the initializer: (unless it is the default 'base()')
|
||||
if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0)) |
||||
constructorDeclaration.Initializer = ci.WithAnnotation(invocation.Annotation<MethodReference>()); |
||||
// Remove the statement:
|
||||
stmt.Remove(); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
static readonly ExpressionStatement fieldInitializerPattern = new ExpressionStatement { |
||||
Expression = new AssignmentExpression { |
||||
Left = new NamedNode("fieldAccess", new MemberReferenceExpression { Target = new ThisReferenceExpression() }), |
||||
Operator = AssignmentOperatorType.Assign, |
||||
Right = new AnyNode("initializer") |
||||
} |
||||
}; |
||||
|
||||
static readonly AstNode thisCallPattern = new ExpressionStatement(new ThisReferenceExpression().Invoke(".ctor", new Repeat(new AnyNode()))); |
||||
|
||||
public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) |
||||
{ |
||||
// Handle initializers on instance fields
|
||||
HandleInstanceFieldInitializers(typeDeclaration.Members); |
||||
|
||||
// Now convert base constructor calls to initializers:
|
||||
base.VisitTypeDeclaration(typeDeclaration, data); |
||||
|
||||
// Remove single empty constructor:
|
||||
RemoveSingleEmptyConstructor(typeDeclaration); |
||||
|
||||
// Handle initializers on static fields:
|
||||
HandleStaticFieldInitializers(typeDeclaration.Members); |
||||
return null; |
||||
} |
||||
|
||||
void HandleInstanceFieldInitializers(IEnumerable<AstNode> members) |
||||
{ |
||||
var instanceCtors = members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); |
||||
var instanceCtorsNotChainingWithThis = instanceCtors.Where(ctor => !thisCallPattern.IsMatch(ctor.Body.Statements.FirstOrDefault())).ToArray(); |
||||
if (instanceCtorsNotChainingWithThis.Length > 0) { |
||||
MethodDefinition ctorMethodDef = instanceCtorsNotChainingWithThis[0].Annotation<MethodDefinition>(); |
||||
if (ctorMethodDef != null && ctorMethodDef.DeclaringType.IsValueType) |
||||
return; |
||||
|
||||
// Recognize field initializers:
|
||||
// Convert first statement in all ctors (if all ctors have the same statement) into a field initializer.
|
||||
bool allSame; |
||||
do { |
||||
Match m = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[0].Body.FirstOrDefault()); |
||||
if (!m.Success) |
||||
break; |
||||
|
||||
FieldDefinition fieldDef = m.Get<AstNode>("fieldAccess").Single().Annotation<FieldReference>().ResolveWithinSameModule(); |
||||
if (fieldDef == null) |
||||
break; |
||||
AstNode fieldOrEventDecl = members.FirstOrDefault(f => f.Annotation<FieldDefinition>() == fieldDef); |
||||
if (fieldOrEventDecl == null) |
||||
break; |
||||
|
||||
allSame = true; |
||||
for (int i = 1; i < instanceCtorsNotChainingWithThis.Length; i++) { |
||||
if (!instanceCtors[0].Body.First().IsMatch(instanceCtorsNotChainingWithThis[i].Body.FirstOrDefault())) |
||||
allSame = false; |
||||
} |
||||
if (allSame) { |
||||
foreach (var ctor in instanceCtorsNotChainingWithThis) |
||||
ctor.Body.First().Remove(); |
||||
fieldOrEventDecl.GetChildrenByRole(AstNode.Roles.Variable).Single().Initializer = m.Get<Expression>("initializer").Single().Detach(); |
||||
} |
||||
} while (allSame); |
||||
} |
||||
} |
||||
|
||||
void RemoveSingleEmptyConstructor(TypeDeclaration typeDeclaration) |
||||
{ |
||||
var instanceCtors = typeDeclaration.Members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); |
||||
if (instanceCtors.Length == 1) { |
||||
ConstructorDeclaration emptyCtor = new ConstructorDeclaration(); |
||||
emptyCtor.Modifiers = ((typeDeclaration.Modifiers & Modifiers.Abstract) == Modifiers.Abstract ? Modifiers.Protected : Modifiers.Public); |
||||
emptyCtor.Body = new BlockStatement(); |
||||
if (emptyCtor.IsMatch(instanceCtors[0])) |
||||
instanceCtors[0].Remove(); |
||||
} |
||||
} |
||||
|
||||
void HandleStaticFieldInitializers(IEnumerable<AstNode> members) |
||||
{ |
||||
// Convert static constructor into field initializers if the class is BeforeFieldInit
|
||||
var staticCtor = members.OfType<ConstructorDeclaration>().FirstOrDefault(c => (c.Modifiers & Modifiers.Static) == Modifiers.Static); |
||||
if (staticCtor != null) { |
||||
MethodDefinition ctorMethodDef = staticCtor.Annotation<MethodDefinition>(); |
||||
if (ctorMethodDef != null && ctorMethodDef.DeclaringType.IsBeforeFieldInit) { |
||||
while (true) { |
||||
ExpressionStatement es = staticCtor.Body.Statements.FirstOrDefault() as ExpressionStatement; |
||||
if (es == null) |
||||
break; |
||||
AssignmentExpression assignment = es.Expression as AssignmentExpression; |
||||
if (assignment == null || assignment.Operator != AssignmentOperatorType.Assign) |
||||
break; |
||||
FieldDefinition fieldDef = assignment.Left.Annotation<FieldReference>().ResolveWithinSameModule(); |
||||
if (fieldDef == null || !fieldDef.IsStatic) |
||||
break; |
||||
FieldDeclaration fieldDecl = members.OfType<FieldDeclaration>().FirstOrDefault(f => f.Annotation<FieldDefinition>() == fieldDef); |
||||
if (fieldDecl == null) |
||||
break; |
||||
fieldDecl.Variables.Single().Initializer = assignment.Right.Detach(); |
||||
es.Remove(); |
||||
} |
||||
if (staticCtor.Body.Statements.Count == 0) |
||||
staticCtor.Remove(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void IAstTransform.Run(AstNode node) |
||||
{ |
||||
// If we're viewing some set of members (fields are direct children of CompilationUnit),
|
||||
// we also need to handle those:
|
||||
HandleInstanceFieldInitializers(node.Children); |
||||
HandleStaticFieldInitializers(node.Children); |
||||
|
||||
node.AcceptVisitor(this, null); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Transforms decimal constant fields.
|
||||
/// </summary>
|
||||
public class DecimalConstantTransform : DepthFirstAstVisitor<object, object>, IAstTransform |
||||
{ |
||||
static readonly PrimitiveType decimalType = new PrimitiveType("decimal"); |
||||
|
||||
public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data) |
||||
{ |
||||
const Modifiers staticReadOnly = Modifiers.Static | Modifiers.Readonly; |
||||
if ((fieldDeclaration.Modifiers & staticReadOnly) == staticReadOnly && decimalType.IsMatch(fieldDeclaration.ReturnType)) { |
||||
foreach (var attributeSection in fieldDeclaration.Attributes) { |
||||
foreach (var attribute in attributeSection.Attributes) { |
||||
TypeReference tr = attribute.Type.Annotation<TypeReference>(); |
||||
if (tr != null && tr.Name == "DecimalConstantAttribute" && tr.Namespace == "System.Runtime.CompilerServices") { |
||||
attribute.Remove(); |
||||
if (attributeSection.Attributes.Count == 0) |
||||
attributeSection.Remove(); |
||||
fieldDeclaration.Modifiers = (fieldDeclaration.Modifiers & ~staticReadOnly) | Modifiers.Const; |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public void Run(AstNode compilationUnit) |
||||
{ |
||||
compilationUnit.AcceptVisitor(this, null); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,329 @@
@@ -0,0 +1,329 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.CSharp.Analysis; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Moves variable declarations to improved positions.
|
||||
/// </summary>
|
||||
public class DeclareVariables : IAstTransform |
||||
{ |
||||
sealed class VariableToDeclare |
||||
{ |
||||
public AstType Type; |
||||
public string Name; |
||||
|
||||
public AssignmentExpression ReplacedAssignment; |
||||
public Statement InsertionPoint; |
||||
} |
||||
|
||||
readonly CancellationToken cancellationToken; |
||||
List<VariableToDeclare> variablesToDeclare = new List<VariableToDeclare>(); |
||||
|
||||
public DeclareVariables(DecompilerContext context) |
||||
{ |
||||
this.cancellationToken = context.CancellationToken; |
||||
} |
||||
|
||||
public void Run(AstNode node) |
||||
{ |
||||
Run(node, null); |
||||
// Declare all the variables at the end, after all the logic has run.
|
||||
// This is done so that definite assignment analysis can work on a single representation and doesn't have to be updated
|
||||
// when we change the AST.
|
||||
foreach (var v in variablesToDeclare) { |
||||
if (v.ReplacedAssignment == null) { |
||||
BlockStatement block = (BlockStatement)v.InsertionPoint.Parent; |
||||
block.Statements.InsertBefore( |
||||
v.InsertionPoint, |
||||
new VariableDeclarationStatement((AstType)v.Type.Clone(), v.Name)); |
||||
} |
||||
} |
||||
// First do all the insertions, then do all the replacements. This is necessary because a replacement might remove our reference point from the AST.
|
||||
foreach (var v in variablesToDeclare) { |
||||
if (v.ReplacedAssignment != null) { |
||||
// We clone the right expression so that it doesn't get removed from the old ExpressionStatement,
|
||||
// which might be still in use by the definite assignment graph.
|
||||
VariableDeclarationStatement varDecl = new VariableDeclarationStatement { |
||||
Type = (AstType)v.Type.Clone(), |
||||
Variables = { new VariableInitializer(v.Name, v.ReplacedAssignment.Right.Detach()).CopyAnnotationsFrom(v.ReplacedAssignment) } |
||||
}; |
||||
ExpressionStatement es = v.ReplacedAssignment.Parent as ExpressionStatement; |
||||
if (es != null) { |
||||
// Note: if this crashes with 'Cannot replace the root node', check whether two variables were assigned the same name
|
||||
es.ReplaceWith(varDecl.CopyAnnotationsFrom(es)); |
||||
} else { |
||||
v.ReplacedAssignment.ReplaceWith(varDecl); |
||||
} |
||||
} |
||||
} |
||||
variablesToDeclare = null; |
||||
} |
||||
|
||||
void Run(AstNode node, DefiniteAssignmentAnalysis daa) |
||||
{ |
||||
BlockStatement block = node as BlockStatement; |
||||
if (block != null) { |
||||
var variables = block.Statements.TakeWhile(stmt => stmt is VariableDeclarationStatement) |
||||
.Cast<VariableDeclarationStatement>().ToList(); |
||||
if (variables.Count > 0) { |
||||
// remove old variable declarations:
|
||||
foreach (VariableDeclarationStatement varDecl in variables) { |
||||
Debug.Assert(varDecl.Variables.Single().Initializer.IsNull); |
||||
varDecl.Remove(); |
||||
} |
||||
if (daa == null) { |
||||
// If possible, reuse the DefiniteAssignmentAnalysis that was created for the parent block
|
||||
daa = new DefiniteAssignmentAnalysis(block, cancellationToken); |
||||
} |
||||
foreach (VariableDeclarationStatement varDecl in variables) { |
||||
string variableName = varDecl.Variables.Single().Name; |
||||
bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null; |
||||
DeclareVariableInBlock(daa, block, varDecl.Type, variableName, allowPassIntoLoops); |
||||
} |
||||
} |
||||
} |
||||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
||||
Run(child, daa); |
||||
} |
||||
} |
||||
|
||||
void DeclareVariableInBlock(DefiniteAssignmentAnalysis daa, BlockStatement block, AstType type, string variableName, bool allowPassIntoLoops) |
||||
{ |
||||
// declarationPoint: The point where the variable would be declared, if we decide to declare it in this block
|
||||
Statement declarationPoint = null; |
||||
// Check whether we can move down the variable into the sub-blocks
|
||||
bool canMoveVariableIntoSubBlocks = FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); |
||||
if (declarationPoint == null) { |
||||
// The variable isn't used at all
|
||||
return; |
||||
} |
||||
if (canMoveVariableIntoSubBlocks) { |
||||
// Declare the variable within the sub-blocks
|
||||
foreach (Statement stmt in block.Statements) { |
||||
ForStatement forStmt = stmt as ForStatement; |
||||
if (forStmt != null && forStmt.Initializers.Count == 1) { |
||||
// handle the special case of moving a variable into the for initializer
|
||||
if (TryConvertAssignmentExpressionIntoVariableDeclaration(forStmt.Initializers.Single(), type, variableName)) |
||||
continue; |
||||
} |
||||
UsingStatement usingStmt = stmt as UsingStatement; |
||||
if (usingStmt != null && usingStmt.ResourceAcquisition is AssignmentExpression) { |
||||
// handle the special case of moving a variable into a using statement
|
||||
if (TryConvertAssignmentExpressionIntoVariableDeclaration((Expression)usingStmt.ResourceAcquisition, type, variableName)) |
||||
continue; |
||||
} |
||||
foreach (AstNode child in stmt.Children) { |
||||
BlockStatement subBlock = child as BlockStatement; |
||||
if (subBlock != null) { |
||||
DeclareVariableInBlock(daa, subBlock, type, variableName, allowPassIntoLoops); |
||||
} else if (HasNestedBlocks(child)) { |
||||
foreach (BlockStatement nestedSubBlock in child.Children.OfType<BlockStatement>()) { |
||||
DeclareVariableInBlock(daa, nestedSubBlock, type, variableName, allowPassIntoLoops); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
// Try converting an assignment expression into a VariableDeclarationStatement
|
||||
if (!TryConvertAssignmentExpressionIntoVariableDeclaration(declarationPoint, type, variableName)) { |
||||
// Declare the variable in front of declarationPoint
|
||||
variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, InsertionPoint = declarationPoint }); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool TryConvertAssignmentExpressionIntoVariableDeclaration(Statement declarationPoint, AstType type, string variableName) |
||||
{ |
||||
// convert the declarationPoint into a VariableDeclarationStatement
|
||||
ExpressionStatement es = declarationPoint as ExpressionStatement; |
||||
if (es != null) { |
||||
return TryConvertAssignmentExpressionIntoVariableDeclaration(es.Expression, type, variableName); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool TryConvertAssignmentExpressionIntoVariableDeclaration(Expression expression, AstType type, string variableName) |
||||
{ |
||||
AssignmentExpression ae = expression as AssignmentExpression; |
||||
if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { |
||||
IdentifierExpression ident = ae.Left as IdentifierExpression; |
||||
if (ident != null && ident.Identifier == variableName) { |
||||
variablesToDeclare.Add(new VariableToDeclare { Type = type, Name = variableName, ReplacedAssignment = ae }); |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Finds the declaration point for the variable within the specified block.
|
||||
/// </summary>
|
||||
/// <param name="daa">
|
||||
/// Definite assignment analysis, must be prepared for 'block' or one of its parents.
|
||||
/// </param>
|
||||
/// <param name="varDecl">The variable to declare</param>
|
||||
/// <param name="block">The block in which the variable should be declared</param>
|
||||
/// <param name="declarationPoint">
|
||||
/// Output parameter: the first statement within 'block' where the variable needs to be declared.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// Returns whether it is possible to move the variable declaration into sub-blocks.
|
||||
/// </returns>
|
||||
public static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, VariableDeclarationStatement varDecl, BlockStatement block, out Statement declarationPoint) |
||||
{ |
||||
string variableName = varDecl.Variables.Single().Name; |
||||
bool allowPassIntoLoops = varDecl.Variables.Single().Annotation<DelegateConstruction.CapturedVariableAnnotation>() == null; |
||||
return FindDeclarationPoint(daa, variableName, allowPassIntoLoops, block, out declarationPoint); |
||||
} |
||||
|
||||
static bool FindDeclarationPoint(DefiniteAssignmentAnalysis daa, string variableName, bool allowPassIntoLoops, BlockStatement block, out Statement declarationPoint) |
||||
{ |
||||
// declarationPoint: The point where the variable would be declared, if we decide to declare it in this block
|
||||
declarationPoint = null; |
||||
foreach (Statement stmt in block.Statements) { |
||||
if (UsesVariable(stmt, variableName)) { |
||||
if (declarationPoint == null) |
||||
declarationPoint = stmt; |
||||
if (!CanMoveVariableUseIntoSubBlock(stmt, variableName, allowPassIntoLoops)) { |
||||
// If it's not possible to move the variable use into a nested block,
|
||||
// we need to declare the variable in this block
|
||||
return false; |
||||
} |
||||
// If we can move the variable into the sub-block, we need to ensure that the remaining code
|
||||
// does not use the value that was assigned by the first sub-block
|
||||
Statement nextStatement = stmt.GetNextStatement(); |
||||
if (nextStatement != null) { |
||||
// Analyze the range from the next statement to the end of the block
|
||||
daa.SetAnalyzedRange(nextStatement, block); |
||||
daa.Analyze(variableName); |
||||
if (daa.UnassignedVariableUses.Count > 0) { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static bool CanMoveVariableUseIntoSubBlock(Statement stmt, string variableName, bool allowPassIntoLoops) |
||||
{ |
||||
if (!allowPassIntoLoops && (stmt is ForStatement || stmt is ForeachStatement || stmt is DoWhileStatement || stmt is WhileStatement)) |
||||
return false; |
||||
|
||||
ForStatement forStatement = stmt as ForStatement; |
||||
if (forStatement != null && forStatement.Initializers.Count == 1) { |
||||
// for-statement is special case: we can move variable declarations into the initializer
|
||||
ExpressionStatement es = forStatement.Initializers.Single() as ExpressionStatement; |
||||
if (es != null) { |
||||
AssignmentExpression ae = es.Expression as AssignmentExpression; |
||||
if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { |
||||
IdentifierExpression ident = ae.Left as IdentifierExpression; |
||||
if (ident != null && ident.Identifier == variableName) { |
||||
return !UsesVariable(ae.Right, variableName); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
UsingStatement usingStatement = stmt as UsingStatement; |
||||
if (usingStatement != null) { |
||||
// using-statement is special case: we can move variable declarations into the initializer
|
||||
AssignmentExpression ae = usingStatement.ResourceAcquisition as AssignmentExpression; |
||||
if (ae != null && ae.Operator == AssignmentOperatorType.Assign) { |
||||
IdentifierExpression ident = ae.Left as IdentifierExpression; |
||||
if (ident != null && ident.Identifier == variableName) { |
||||
return !UsesVariable(ae.Right, variableName); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// We can move the variable into a sub-block only if the variable is used in only that sub-block (and not in expressions such as the loop condition)
|
||||
for (AstNode child = stmt.FirstChild; child != null; child = child.NextSibling) { |
||||
if (!(child is BlockStatement) && UsesVariable(child, variableName)) { |
||||
if (HasNestedBlocks(child)) { |
||||
// catch clauses/switch sections can contain nested blocks
|
||||
for (AstNode grandchild = child.FirstChild; grandchild != null; grandchild = grandchild.NextSibling) { |
||||
if (!(grandchild is BlockStatement) && UsesVariable(grandchild, variableName)) |
||||
return false; |
||||
} |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static bool HasNestedBlocks(AstNode node) |
||||
{ |
||||
return node is CatchClause || node is SwitchSection; |
||||
} |
||||
|
||||
static bool UsesVariable(AstNode node, string variableName) |
||||
{ |
||||
IdentifierExpression ie = node as IdentifierExpression; |
||||
if (ie != null && ie.Identifier == variableName) |
||||
return true; |
||||
|
||||
FixedStatement fixedStatement = node as FixedStatement; |
||||
if (fixedStatement != null) { |
||||
foreach (VariableInitializer v in fixedStatement.Variables) { |
||||
if (v.Name == variableName) |
||||
return false; // no need to introduce the variable here
|
||||
} |
||||
} |
||||
|
||||
ForeachStatement foreachStatement = node as ForeachStatement; |
||||
if (foreachStatement != null) { |
||||
if (foreachStatement.VariableName == variableName) |
||||
return false; // no need to introduce the variable here
|
||||
} |
||||
|
||||
UsingStatement usingStatement = node as UsingStatement; |
||||
if (usingStatement != null) { |
||||
VariableDeclarationStatement varDecl = usingStatement.ResourceAcquisition as VariableDeclarationStatement; |
||||
if (varDecl != null) { |
||||
foreach (VariableInitializer v in varDecl.Variables) { |
||||
if (v.Name == variableName) |
||||
return false; // no need to introduce the variable here
|
||||
} |
||||
} |
||||
} |
||||
|
||||
CatchClause catchClause = node as CatchClause; |
||||
if (catchClause != null && catchClause.VariableName == variableName) { |
||||
return false; // no need to introduce the variable here
|
||||
} |
||||
|
||||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
||||
if (UsesVariable(child, variableName)) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,471 @@
@@ -0,0 +1,471 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.ILAst; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Converts "new Action(obj, ldftn(func))" into "new Action(obj.func)".
|
||||
/// For anonymous methods, creates an AnonymousMethodExpression.
|
||||
/// Also gets rid of any "Display Classes" left over after inlining an anonymous method.
|
||||
/// </summary>
|
||||
public class DelegateConstruction : ContextTrackingVisitor<object> |
||||
{ |
||||
internal sealed class Annotation |
||||
{ |
||||
/// <summary>
|
||||
/// ldftn or ldvirtftn?
|
||||
/// </summary>
|
||||
public readonly bool IsVirtual; |
||||
|
||||
public Annotation(bool isVirtual) |
||||
{ |
||||
this.IsVirtual = isVirtual; |
||||
} |
||||
} |
||||
|
||||
internal sealed class CapturedVariableAnnotation |
||||
{ |
||||
} |
||||
|
||||
List<string> currentlyUsedVariableNames = new List<string>(); |
||||
|
||||
public DelegateConstruction(DecompilerContext context) : base(context) |
||||
{ |
||||
} |
||||
|
||||
public override object VisitObjectCreateExpression(ObjectCreateExpression objectCreateExpression, object data) |
||||
{ |
||||
if (objectCreateExpression.Arguments.Count == 2) { |
||||
Expression obj = objectCreateExpression.Arguments.First(); |
||||
Expression func = objectCreateExpression.Arguments.Last(); |
||||
Annotation annotation = func.Annotation<Annotation>(); |
||||
if (annotation != null) { |
||||
IdentifierExpression methodIdent = (IdentifierExpression)((InvocationExpression)func).Arguments.Single(); |
||||
MethodReference method = methodIdent.Annotation<MethodReference>(); |
||||
if (method != null) { |
||||
if (HandleAnonymousMethod(objectCreateExpression, obj, method)) |
||||
return null; |
||||
// Perform the transformation to "new Action(obj.func)".
|
||||
obj.Remove(); |
||||
methodIdent.Remove(); |
||||
if (!annotation.IsVirtual && obj is ThisReferenceExpression) { |
||||
// maybe it's getting the pointer of a base method?
|
||||
if (method.DeclaringType.GetElementType() != context.CurrentType) { |
||||
obj = new BaseReferenceExpression(); |
||||
} |
||||
} |
||||
if (!annotation.IsVirtual && obj is NullReferenceExpression && !method.HasThis) { |
||||
// We're loading a static method.
|
||||
// However it is possible to load extension methods with an instance, so we compare the number of arguments:
|
||||
bool isExtensionMethod = false; |
||||
TypeReference delegateType = objectCreateExpression.Type.Annotation<TypeReference>(); |
||||
if (delegateType != null) { |
||||
TypeDefinition delegateTypeDef = delegateType.Resolve(); |
||||
if (delegateTypeDef != null) { |
||||
MethodDefinition invokeMethod = delegateTypeDef.Methods.FirstOrDefault(m => m.Name == "Invoke"); |
||||
if (invokeMethod != null) { |
||||
isExtensionMethod = (invokeMethod.Parameters.Count + 1 == method.Parameters.Count); |
||||
} |
||||
} |
||||
} |
||||
if (!isExtensionMethod) { |
||||
obj = new TypeReferenceExpression { Type = AstBuilder.ConvertType(method.DeclaringType) }; |
||||
} |
||||
} |
||||
// now transform the identifier into a member reference
|
||||
MemberReferenceExpression mre = new MemberReferenceExpression(); |
||||
mre.Target = obj; |
||||
mre.MemberName = methodIdent.Identifier; |
||||
methodIdent.TypeArguments.MoveTo(mre.TypeArguments); |
||||
mre.AddAnnotation(method); |
||||
objectCreateExpression.Arguments.Clear(); |
||||
objectCreateExpression.Arguments.Add(mre); |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
return base.VisitObjectCreateExpression(objectCreateExpression, data); |
||||
} |
||||
|
||||
internal static bool IsAnonymousMethod(DecompilerContext context, MethodDefinition method) |
||||
{ |
||||
if (method == null || !(method.Name.StartsWith("<", StringComparison.Ordinal) || method.Name.Contains("$"))) |
||||
return false; |
||||
if (!(method.IsCompilerGenerated() || IsPotentialClosure(context, method.DeclaringType))) |
||||
return false; |
||||
return true; |
||||
} |
||||
|
||||
bool HandleAnonymousMethod(ObjectCreateExpression objectCreateExpression, Expression target, MethodReference methodRef) |
||||
{ |
||||
if (!context.Settings.AnonymousMethods) |
||||
return false; // anonymous method decompilation is disabled
|
||||
if (target != null && !(target is IdentifierExpression || target is ThisReferenceExpression || target is NullReferenceExpression)) |
||||
return false; // don't copy arbitrary expressions, deal with identifiers only
|
||||
|
||||
// Anonymous methods are defined in the same assembly
|
||||
MethodDefinition method = methodRef.ResolveWithinSameModule(); |
||||
if (!IsAnonymousMethod(context, method)) |
||||
return false; |
||||
|
||||
// Create AnonymousMethodExpression and prepare parameters
|
||||
AnonymousMethodExpression ame = new AnonymousMethodExpression(); |
||||
ame.CopyAnnotationsFrom(objectCreateExpression); // copy ILRanges etc.
|
||||
ame.RemoveAnnotations<MethodReference>(); // remove reference to delegate ctor
|
||||
ame.AddAnnotation(method); // add reference to anonymous method
|
||||
ame.Parameters.AddRange(AstBuilder.MakeParameters(method, isLambda: true)); |
||||
ame.HasParameterList = true; |
||||
|
||||
// rename variables so that they don't conflict with the parameters:
|
||||
foreach (ParameterDeclaration pd in ame.Parameters) { |
||||
EnsureVariableNameIsAvailable(objectCreateExpression, pd.Name); |
||||
} |
||||
|
||||
// Decompile the anonymous method:
|
||||
|
||||
DecompilerContext subContext = context.Clone(); |
||||
subContext.CurrentMethod = method; |
||||
subContext.ReservedVariableNames.AddRange(currentlyUsedVariableNames); |
||||
BlockStatement body = AstMethodBodyBuilder.CreateMethodBody(method, subContext, ame.Parameters); |
||||
TransformationPipeline.RunTransformationsUntil(body, v => v is DelegateConstruction, subContext); |
||||
body.AcceptVisitor(this, null); |
||||
|
||||
|
||||
bool isLambda = false; |
||||
if (ame.Parameters.All(p => p.ParameterModifier == ParameterModifier.None)) { |
||||
isLambda = (body.Statements.Count == 1 && body.Statements.Single() is ReturnStatement); |
||||
} |
||||
// Remove the parameter list from an AnonymousMethodExpression if the original method had no names,
|
||||
// and the parameters are not used in the method body
|
||||
if (!isLambda && method.Parameters.All(p => string.IsNullOrEmpty(p.Name))) { |
||||
var parameterReferencingIdentifiers = |
||||
from ident in body.Descendants.OfType<IdentifierExpression>() |
||||
let v = ident.Annotation<ILVariable>() |
||||
where v != null && v.IsParameter && method.Parameters.Contains(v.OriginalParameter) |
||||
select ident; |
||||
if (!parameterReferencingIdentifiers.Any()) { |
||||
ame.Parameters.Clear(); |
||||
ame.HasParameterList = false; |
||||
} |
||||
} |
||||
|
||||
// Replace all occurrences of 'this' in the method body with the delegate's target:
|
||||
foreach (AstNode node in body.Descendants) { |
||||
if (node is ThisReferenceExpression) |
||||
node.ReplaceWith(target.Clone()); |
||||
|
||||
} |
||||
if (isLambda) { |
||||
LambdaExpression lambda = new LambdaExpression(); |
||||
lambda.CopyAnnotationsFrom(ame); |
||||
ame.Parameters.MoveTo(lambda.Parameters); |
||||
Expression returnExpr = ((ReturnStatement)body.Statements.Single()).Expression; |
||||
returnExpr.Remove(); |
||||
lambda.Body = returnExpr; |
||||
objectCreateExpression.ReplaceWith(lambda); |
||||
} else { |
||||
ame.Body = body; |
||||
objectCreateExpression.ReplaceWith(ame); |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
internal static bool IsPotentialClosure(DecompilerContext context, TypeDefinition potentialDisplayClass) |
||||
{ |
||||
if (potentialDisplayClass == null || !potentialDisplayClass.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) |
||||
return false; |
||||
// check that methodContainingType is within containingType
|
||||
while (potentialDisplayClass != context.CurrentType) { |
||||
potentialDisplayClass = potentialDisplayClass.DeclaringType; |
||||
if (potentialDisplayClass == null) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
#region Track current variables
|
||||
public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) |
||||
{ |
||||
Debug.Assert(currentlyUsedVariableNames.Count == 0); |
||||
try { |
||||
currentlyUsedVariableNames.AddRange(methodDeclaration.Parameters.Select(p => p.Name)); |
||||
return base.VisitMethodDeclaration(methodDeclaration, data); |
||||
} finally { |
||||
currentlyUsedVariableNames.Clear(); |
||||
} |
||||
} |
||||
|
||||
public override object VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data) |
||||
{ |
||||
Debug.Assert(currentlyUsedVariableNames.Count == 0); |
||||
try { |
||||
currentlyUsedVariableNames.AddRange(operatorDeclaration.Parameters.Select(p => p.Name)); |
||||
return base.VisitOperatorDeclaration(operatorDeclaration, data); |
||||
} finally { |
||||
currentlyUsedVariableNames.Clear(); |
||||
} |
||||
} |
||||
|
||||
public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) |
||||
{ |
||||
Debug.Assert(currentlyUsedVariableNames.Count == 0); |
||||
try { |
||||
currentlyUsedVariableNames.AddRange(constructorDeclaration.Parameters.Select(p => p.Name)); |
||||
return base.VisitConstructorDeclaration(constructorDeclaration, data); |
||||
} finally { |
||||
currentlyUsedVariableNames.Clear(); |
||||
} |
||||
} |
||||
|
||||
public override object VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration, object data) |
||||
{ |
||||
Debug.Assert(currentlyUsedVariableNames.Count == 0); |
||||
try { |
||||
currentlyUsedVariableNames.AddRange(indexerDeclaration.Parameters.Select(p => p.Name)); |
||||
return base.VisitIndexerDeclaration(indexerDeclaration, data); |
||||
} finally { |
||||
currentlyUsedVariableNames.Clear(); |
||||
} |
||||
} |
||||
|
||||
public override object VisitAccessor(Accessor accessor, object data) |
||||
{ |
||||
try { |
||||
currentlyUsedVariableNames.Add("value"); |
||||
return base.VisitAccessor(accessor, data); |
||||
} finally { |
||||
currentlyUsedVariableNames.RemoveAt(currentlyUsedVariableNames.Count - 1); |
||||
} |
||||
} |
||||
|
||||
public override object VisitVariableDeclarationStatement(VariableDeclarationStatement variableDeclarationStatement, object data) |
||||
{ |
||||
foreach (VariableInitializer v in variableDeclarationStatement.Variables) |
||||
currentlyUsedVariableNames.Add(v.Name); |
||||
return base.VisitVariableDeclarationStatement(variableDeclarationStatement, data); |
||||
} |
||||
|
||||
public override object VisitFixedStatement(FixedStatement fixedStatement, object data) |
||||
{ |
||||
foreach (VariableInitializer v in fixedStatement.Variables) |
||||
currentlyUsedVariableNames.Add(v.Name); |
||||
return base.VisitFixedStatement(fixedStatement, data); |
||||
} |
||||
#endregion
|
||||
|
||||
static readonly ExpressionStatement displayClassAssignmentPattern = |
||||
new ExpressionStatement(new AssignmentExpression( |
||||
new NamedNode("variable", new IdentifierExpression()), |
||||
new ObjectCreateExpression { Type = new AnyNode("type") } |
||||
)); |
||||
|
||||
public override object VisitBlockStatement(BlockStatement blockStatement, object data) |
||||
{ |
||||
int numberOfVariablesOutsideBlock = currentlyUsedVariableNames.Count; |
||||
base.VisitBlockStatement(blockStatement, data); |
||||
foreach (ExpressionStatement stmt in blockStatement.Statements.OfType<ExpressionStatement>().ToArray()) { |
||||
Match displayClassAssignmentMatch = displayClassAssignmentPattern.Match(stmt); |
||||
if (!displayClassAssignmentMatch.Success) |
||||
continue; |
||||
|
||||
ILVariable variable = displayClassAssignmentMatch.Get<AstNode>("variable").Single().Annotation<ILVariable>(); |
||||
if (variable == null) |
||||
continue; |
||||
TypeDefinition type = variable.Type.ResolveWithinSameModule(); |
||||
if (!IsPotentialClosure(context, type)) |
||||
continue; |
||||
if (displayClassAssignmentMatch.Get<AstType>("type").Single().Annotation<TypeReference>().ResolveWithinSameModule() != type) |
||||
continue; |
||||
|
||||
// Looks like we found a display class creation. Now let's verify that the variable is used only for field accesses:
|
||||
bool ok = true; |
||||
foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) { |
||||
if (identExpr.Identifier == variable.Name && identExpr != displayClassAssignmentMatch.Get("variable").Single()) { |
||||
if (!(identExpr.Parent is MemberReferenceExpression && identExpr.Parent.Annotation<FieldReference>() != null)) |
||||
ok = false; |
||||
} |
||||
} |
||||
if (!ok) |
||||
continue; |
||||
Dictionary<FieldReference, AstNode> dict = new Dictionary<FieldReference, AstNode>(); |
||||
|
||||
// Delete the variable declaration statement:
|
||||
VariableDeclarationStatement displayClassVarDecl = PatternStatementTransform.FindVariableDeclaration(stmt, variable.Name); |
||||
if (displayClassVarDecl != null) |
||||
displayClassVarDecl.Remove(); |
||||
|
||||
// Delete the assignment statement:
|
||||
AstNode cur = stmt.NextSibling; |
||||
stmt.Remove(); |
||||
|
||||
// Delete any following statements as long as they assign parameters to the display class
|
||||
BlockStatement rootBlock = blockStatement.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? blockStatement; |
||||
List<ILVariable> parameterOccurrances = rootBlock.Descendants.OfType<IdentifierExpression>() |
||||
.Select(n => n.Annotation<ILVariable>()).Where(p => p != null && p.IsParameter).ToList(); |
||||
AstNode next; |
||||
for (; cur != null; cur = next) { |
||||
next = cur.NextSibling; |
||||
|
||||
// Test for the pattern:
|
||||
// "variableName.MemberName = right;"
|
||||
ExpressionStatement closureFieldAssignmentPattern = new ExpressionStatement( |
||||
new AssignmentExpression( |
||||
new NamedNode("left", new MemberReferenceExpression { Target = new IdentifierExpression(variable.Name) }), |
||||
new AnyNode("right") |
||||
) |
||||
); |
||||
Match m = closureFieldAssignmentPattern.Match(cur); |
||||
if (m.Success) { |
||||
FieldDefinition fieldDef = m.Get<MemberReferenceExpression>("left").Single().Annotation<FieldReference>().ResolveWithinSameModule(); |
||||
AstNode right = m.Get<AstNode>("right").Single(); |
||||
bool isParameter = false; |
||||
bool isDisplayClassParentPointerAssignment = false; |
||||
if (right is ThisReferenceExpression) { |
||||
isParameter = true; |
||||
} else if (right is IdentifierExpression) { |
||||
// handle parameters only if the whole method contains no other occurrence except for 'right'
|
||||
ILVariable v = right.Annotation<ILVariable>(); |
||||
isParameter = v.IsParameter && parameterOccurrances.Count(c => c == v) == 1; |
||||
if (!isParameter && IsPotentialClosure(context, v.Type.ResolveWithinSameModule())) { |
||||
// parent display class within the same method
|
||||
// (closure2.localsX = closure1;)
|
||||
isDisplayClassParentPointerAssignment = true; |
||||
} |
||||
} else if (right is MemberReferenceExpression) { |
||||
// copy of parent display class reference from an outer lambda
|
||||
// closure2.localsX = this.localsY
|
||||
MemberReferenceExpression mre = m.Get<MemberReferenceExpression>("right").Single(); |
||||
do { |
||||
// descend into the targets of the mre as long as the field types are closures
|
||||
FieldDefinition fieldDef2 = mre.Annotation<FieldReference>().ResolveWithinSameModule(); |
||||
if (fieldDef2 == null || !IsPotentialClosure(context, fieldDef2.FieldType.ResolveWithinSameModule())) { |
||||
break; |
||||
} |
||||
// if we finally get to a this reference, it's copying a display class parent pointer
|
||||
if (mre.Target is ThisReferenceExpression) { |
||||
isDisplayClassParentPointerAssignment = true; |
||||
} |
||||
mre = mre.Target as MemberReferenceExpression; |
||||
} while (mre != null); |
||||
} |
||||
if (isParameter || isDisplayClassParentPointerAssignment) { |
||||
dict[fieldDef] = right; |
||||
cur.Remove(); |
||||
} else { |
||||
break; |
||||
} |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
// Now create variables for all fields of the display class (except for those that we already handled as parameters)
|
||||
List<Tuple<AstType, string>> variablesToDeclare = new List<Tuple<AstType, string>>(); |
||||
foreach (FieldDefinition field in type.Fields) { |
||||
if (field.IsStatic) |
||||
continue; // skip static fields
|
||||
if (dict.ContainsKey(field)) // skip field if it already was handled as parameter
|
||||
continue; |
||||
string capturedVariableName = field.Name; |
||||
if (capturedVariableName.StartsWith("$VB$Local_", StringComparison.Ordinal) && capturedVariableName.Length > 10) |
||||
capturedVariableName = capturedVariableName.Substring(10); |
||||
EnsureVariableNameIsAvailable(blockStatement, capturedVariableName); |
||||
currentlyUsedVariableNames.Add(capturedVariableName); |
||||
variablesToDeclare.Add(Tuple.Create(AstBuilder.ConvertType(field.FieldType, field), capturedVariableName)); |
||||
dict[field] = new IdentifierExpression(capturedVariableName); |
||||
} |
||||
|
||||
// Now figure out where the closure was accessed and use the simpler replacement expression there:
|
||||
foreach (var identExpr in blockStatement.Descendants.OfType<IdentifierExpression>()) { |
||||
if (identExpr.Identifier == variable.Name) { |
||||
MemberReferenceExpression mre = (MemberReferenceExpression)identExpr.Parent; |
||||
AstNode replacement; |
||||
if (dict.TryGetValue(mre.Annotation<FieldReference>().ResolveWithinSameModule(), out replacement)) { |
||||
mre.ReplaceWith(replacement.Clone()); |
||||
} |
||||
} |
||||
} |
||||
// Now insert the variable declarations (we can do this after the replacements only so that the scope detection works):
|
||||
Statement insertionPoint = blockStatement.Statements.FirstOrDefault(); |
||||
foreach (var tuple in variablesToDeclare) { |
||||
var newVarDecl = new VariableDeclarationStatement(tuple.Item1, tuple.Item2); |
||||
newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation()); |
||||
blockStatement.Statements.InsertBefore(insertionPoint, newVarDecl); |
||||
} |
||||
} |
||||
currentlyUsedVariableNames.RemoveRange(numberOfVariablesOutsideBlock, currentlyUsedVariableNames.Count - numberOfVariablesOutsideBlock); |
||||
return null; |
||||
} |
||||
|
||||
void EnsureVariableNameIsAvailable(AstNode currentNode, string name) |
||||
{ |
||||
int pos = currentlyUsedVariableNames.IndexOf(name); |
||||
if (pos < 0) { |
||||
// name is still available
|
||||
return; |
||||
} |
||||
// Naming conflict. Let's rename the existing variable so that the field keeps the name from metadata.
|
||||
NameVariables nv = new NameVariables(); |
||||
// Add currently used variable and parameter names
|
||||
foreach (string nameInUse in currentlyUsedVariableNames) |
||||
nv.AddExistingName(nameInUse); |
||||
// variables declared in child nodes of this block
|
||||
foreach (VariableInitializer vi in currentNode.Descendants.OfType<VariableInitializer>()) |
||||
nv.AddExistingName(vi.Name); |
||||
// parameters in child lambdas
|
||||
foreach (ParameterDeclaration pd in currentNode.Descendants.OfType<ParameterDeclaration>()) |
||||
nv.AddExistingName(pd.Name); |
||||
|
||||
string newName = nv.GetAlternativeName(name); |
||||
currentlyUsedVariableNames[pos] = newName; |
||||
|
||||
// find top-most block
|
||||
AstNode topMostBlock = currentNode.Ancestors.OfType<BlockStatement>().LastOrDefault() ?? currentNode; |
||||
|
||||
// rename identifiers
|
||||
foreach (IdentifierExpression ident in topMostBlock.Descendants.OfType<IdentifierExpression>()) { |
||||
if (ident.Identifier == name) { |
||||
ident.Identifier = newName; |
||||
ILVariable v = ident.Annotation<ILVariable>(); |
||||
if (v != null) |
||||
v.Name = newName; |
||||
} |
||||
} |
||||
// rename variable declarations
|
||||
foreach (VariableInitializer vi in topMostBlock.Descendants.OfType<VariableInitializer>()) { |
||||
if (vi.Name == name) { |
||||
vi.Name = newName; |
||||
ILVariable v = vi.Annotation<ILVariable>(); |
||||
if (v != null) |
||||
v.Name = newName; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Converts extension method calls into infix syntax.
|
||||
/// </summary>
|
||||
public class IntroduceExtensionMethods : IAstTransform |
||||
{ |
||||
readonly DecompilerContext context; |
||||
|
||||
public IntroduceExtensionMethods(DecompilerContext context) |
||||
{ |
||||
this.context = context; |
||||
} |
||||
|
||||
public void Run(AstNode compilationUnit) |
||||
{ |
||||
foreach (InvocationExpression invocation in compilationUnit.Descendants.OfType<InvocationExpression>()) { |
||||
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; |
||||
MethodReference methodReference = invocation.Annotation<MethodReference>(); |
||||
if (mre != null && mre.Target is TypeReferenceExpression && methodReference != null && invocation.Arguments.Any()) { |
||||
MethodDefinition d = methodReference.Resolve(); |
||||
if (d != null) { |
||||
foreach (var ca in d.CustomAttributes) { |
||||
if (ca.AttributeType.Name == "ExtensionAttribute" && ca.AttributeType.Namespace == "System.Runtime.CompilerServices") { |
||||
mre.Target = invocation.Arguments.First().Detach(); |
||||
if (invocation.Arguments.Any()) { |
||||
// HACK: removing type arguments should be done indepently from whether a method is an extension method,
|
||||
// just by testing whether the arguments can be inferred
|
||||
mre.TypeArguments.Clear(); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,295 @@
@@ -0,0 +1,295 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Decompiles query expressions.
|
||||
/// Based on C# 4.0 spec, §7.16.2 Query expression translation
|
||||
/// </summary>
|
||||
public class IntroduceQueryExpressions : IAstTransform |
||||
{ |
||||
readonly DecompilerContext context; |
||||
|
||||
public IntroduceQueryExpressions(DecompilerContext context) |
||||
{ |
||||
this.context = context; |
||||
} |
||||
|
||||
public void Run(AstNode compilationUnit) |
||||
{ |
||||
if (!context.Settings.QueryExpressions) |
||||
return; |
||||
DecompileQueries(compilationUnit); |
||||
// After all queries were decompiled, detect degenerate queries (queries not property terminated with 'select' or 'group')
|
||||
// and fix them, either by adding a degenerate select, or by combining them with another query.
|
||||
foreach (QueryExpression query in compilationUnit.Descendants.OfType<QueryExpression>()) { |
||||
QueryFromClause fromClause = (QueryFromClause)query.Clauses.First(); |
||||
if (IsDegenerateQuery(query)) { |
||||
// introduce select for degenerate query
|
||||
query.Clauses.Add(new QuerySelectClause { Expression = new IdentifierExpression(fromClause.Identifier) }); |
||||
} |
||||
// See if the data source of this query is a degenerate query,
|
||||
// and combine the queries if possible.
|
||||
QueryExpression innerQuery = fromClause.Expression as QueryExpression; |
||||
while (IsDegenerateQuery(innerQuery)) { |
||||
QueryFromClause innerFromClause = (QueryFromClause)innerQuery.Clauses.First(); |
||||
if (fromClause.Identifier != innerFromClause.Identifier) |
||||
break; |
||||
// Replace the fromClause with all clauses from the inner query
|
||||
fromClause.Remove(); |
||||
QueryClause insertionPos = null; |
||||
foreach (var clause in innerQuery.Clauses) { |
||||
query.Clauses.InsertAfter(insertionPos, insertionPos = clause.Detach()); |
||||
} |
||||
fromClause = innerFromClause; |
||||
innerQuery = fromClause.Expression as QueryExpression; |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool IsDegenerateQuery(QueryExpression query) |
||||
{ |
||||
if (query == null) |
||||
return false; |
||||
var lastClause = query.Clauses.LastOrDefault(); |
||||
return !(lastClause is QuerySelectClause || lastClause is QueryGroupClause); |
||||
} |
||||
|
||||
void DecompileQueries(AstNode node) |
||||
{ |
||||
QueryExpression query = DecompileQuery(node as InvocationExpression); |
||||
if (query != null) |
||||
node.ReplaceWith(query); |
||||
for (AstNode child = (query ?? node).FirstChild; child != null; child = child.NextSibling) { |
||||
DecompileQueries(child); |
||||
} |
||||
} |
||||
|
||||
QueryExpression DecompileQuery(InvocationExpression invocation) |
||||
{ |
||||
if (invocation == null) |
||||
return null; |
||||
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; |
||||
if (mre == null) |
||||
return null; |
||||
switch (mre.MemberName) { |
||||
case "Select": |
||||
{ |
||||
if (invocation.Arguments.Count != 1) |
||||
return null; |
||||
string parameterName; |
||||
Expression body; |
||||
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) { |
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); |
||||
query.Clauses.Add(new QuerySelectClause { Expression = body.Detach() }); |
||||
return query; |
||||
} |
||||
return null; |
||||
} |
||||
case "GroupBy": |
||||
{ |
||||
if (invocation.Arguments.Count == 2) { |
||||
string parameterName1, parameterName2; |
||||
Expression keySelector, elementSelector; |
||||
if (MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName1, out keySelector) |
||||
&& MatchSimpleLambda(invocation.Arguments.ElementAt(1), out parameterName2, out elementSelector) |
||||
&& parameterName1 == parameterName2) |
||||
{ |
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = parameterName1, Expression = mre.Target.Detach() }); |
||||
query.Clauses.Add(new QueryGroupClause { Projection = elementSelector.Detach(), Key = keySelector.Detach() }); |
||||
return query; |
||||
} |
||||
} else if (invocation.Arguments.Count == 1) { |
||||
string parameterName; |
||||
Expression keySelector; |
||||
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out keySelector)) { |
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); |
||||
query.Clauses.Add(new QueryGroupClause { Projection = new IdentifierExpression(parameterName), Key = keySelector.Detach() }); |
||||
return query; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
case "SelectMany": |
||||
{ |
||||
if (invocation.Arguments.Count != 2) |
||||
return null; |
||||
string parameterName; |
||||
Expression collectionSelector; |
||||
if (!MatchSimpleLambda(invocation.Arguments.ElementAt(0), out parameterName, out collectionSelector)) |
||||
return null; |
||||
LambdaExpression lambda = invocation.Arguments.ElementAt(1) as LambdaExpression; |
||||
if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) { |
||||
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); |
||||
ParameterDeclaration p2 = lambda.Parameters.ElementAt(1); |
||||
if (p1.Name == parameterName) { |
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = p1.Name, Expression = mre.Target.Detach() }); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = p2.Name, Expression = collectionSelector.Detach() }); |
||||
query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() }); |
||||
return query; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
case "Where": |
||||
{ |
||||
if (invocation.Arguments.Count != 1) |
||||
return null; |
||||
string parameterName; |
||||
Expression body; |
||||
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) { |
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); |
||||
query.Clauses.Add(new QueryWhereClause { Condition = body.Detach() }); |
||||
return query; |
||||
} |
||||
return null; |
||||
} |
||||
case "OrderBy": |
||||
case "OrderByDescending": |
||||
case "ThenBy": |
||||
case "ThenByDescending": |
||||
{ |
||||
if (invocation.Arguments.Count != 1) |
||||
return null; |
||||
string parameterName; |
||||
Expression orderExpression; |
||||
if (MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out orderExpression)) { |
||||
if (ValidateThenByChain(invocation, parameterName)) { |
||||
QueryOrderClause orderClause = new QueryOrderClause(); |
||||
InvocationExpression tmp = invocation; |
||||
while (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending") { |
||||
// insert new ordering at beginning
|
||||
orderClause.Orderings.InsertAfter( |
||||
null, new QueryOrdering { |
||||
Expression = orderExpression.Detach(), |
||||
Direction = (mre.MemberName == "ThenBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending) |
||||
}); |
||||
|
||||
tmp = (InvocationExpression)mre.Target; |
||||
mre = (MemberReferenceExpression)tmp.Target; |
||||
MatchSimpleLambda(tmp.Arguments.Single(), out parameterName, out orderExpression); |
||||
} |
||||
// insert new ordering at beginning
|
||||
orderClause.Orderings.InsertAfter( |
||||
null, new QueryOrdering { |
||||
Expression = orderExpression.Detach(), |
||||
Direction = (mre.MemberName == "OrderBy" ? QueryOrderingDirection.None : QueryOrderingDirection.Descending) |
||||
}); |
||||
|
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = parameterName, Expression = mre.Target.Detach() }); |
||||
query.Clauses.Add(orderClause); |
||||
return query; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
case "Join": |
||||
case "GroupJoin": |
||||
{ |
||||
if (invocation.Arguments.Count != 4) |
||||
return null; |
||||
Expression source1 = mre.Target; |
||||
Expression source2 = invocation.Arguments.ElementAt(0); |
||||
string elementName1, elementName2; |
||||
Expression key1, key2; |
||||
if (!MatchSimpleLambda(invocation.Arguments.ElementAt(1), out elementName1, out key1)) |
||||
return null; |
||||
if (!MatchSimpleLambda(invocation.Arguments.ElementAt(2), out elementName2, out key2)) |
||||
return null; |
||||
LambdaExpression lambda = invocation.Arguments.ElementAt(3) as LambdaExpression; |
||||
if (lambda != null && lambda.Parameters.Count == 2 && lambda.Body is Expression) { |
||||
ParameterDeclaration p1 = lambda.Parameters.ElementAt(0); |
||||
ParameterDeclaration p2 = lambda.Parameters.ElementAt(1); |
||||
if (p1.Name == elementName1 && (p2.Name == elementName2 || mre.MemberName == "GroupJoin")) { |
||||
QueryExpression query = new QueryExpression(); |
||||
query.Clauses.Add(new QueryFromClause { Identifier = elementName1, Expression = source1.Detach() }); |
||||
QueryJoinClause joinClause = new QueryJoinClause(); |
||||
joinClause.JoinIdentifier = elementName2; // join elementName2
|
||||
joinClause.InExpression = source2.Detach(); // in source2
|
||||
joinClause.OnExpression = key1.Detach(); // on key1
|
||||
joinClause.EqualsExpression = key2.Detach(); // equals key2
|
||||
if (mre.MemberName == "GroupJoin") { |
||||
joinClause.IntoIdentifier = p2.Name; // into p2.Name
|
||||
} |
||||
query.Clauses.Add(joinClause); |
||||
query.Clauses.Add(new QuerySelectClause { Expression = ((Expression)lambda.Body).Detach() }); |
||||
return query; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Ensure that all ThenBy's are correct, and that the list of ThenBy's is terminated by an 'OrderBy' invocation.
|
||||
/// </summary>
|
||||
bool ValidateThenByChain(InvocationExpression invocation, string expectedParameterName) |
||||
{ |
||||
if (invocation == null || invocation.Arguments.Count != 1) |
||||
return false; |
||||
MemberReferenceExpression mre = invocation.Target as MemberReferenceExpression; |
||||
if (mre == null) |
||||
return false; |
||||
string parameterName; |
||||
Expression body; |
||||
if (!MatchSimpleLambda(invocation.Arguments.Single(), out parameterName, out body)) |
||||
return false; |
||||
if (parameterName != expectedParameterName) |
||||
return false; |
||||
|
||||
if (mre.MemberName == "OrderBy" || mre.MemberName == "OrderByDescending") |
||||
return true; |
||||
else if (mre.MemberName == "ThenBy" || mre.MemberName == "ThenByDescending") |
||||
return ValidateThenByChain(mre.Target as InvocationExpression, expectedParameterName); |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>Matches simple lambdas of the form "a => b"</summary>
|
||||
bool MatchSimpleLambda(Expression expr, out string parameterName, out Expression body) |
||||
{ |
||||
LambdaExpression lambda = expr as LambdaExpression; |
||||
if (lambda != null && lambda.Parameters.Count == 1 && lambda.Body is Expression) { |
||||
ParameterDeclaration p = lambda.Parameters.Single(); |
||||
if (p.ParameterModifier == ParameterModifier.None) { |
||||
parameterName = p.Name; |
||||
body = (Expression)lambda.Body; |
||||
return true; |
||||
} |
||||
} |
||||
parameterName = null; |
||||
body = null; |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
public class IntroduceUnsafeModifier : DepthFirstAstVisitor<object, bool>, IAstTransform |
||||
{ |
||||
public static readonly object PointerArithmeticAnnotation = new PointerArithmetic(); |
||||
|
||||
sealed class PointerArithmetic {} |
||||
|
||||
public void Run(AstNode compilationUnit) |
||||
{ |
||||
compilationUnit.AcceptVisitor(this, null); |
||||
} |
||||
|
||||
protected override bool VisitChildren(AstNode node, object data) |
||||
{ |
||||
bool result = false; |
||||
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) { |
||||
result |= child.AcceptVisitor(this, data); |
||||
} |
||||
if (result && node is AttributedNode && !(node is Accessor)) { |
||||
((AttributedNode)node).Modifiers |= Modifiers.Unsafe; |
||||
return false; |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
public override bool VisitPointerReferenceExpression(PointerReferenceExpression pointerReferenceExpression, object data) |
||||
{ |
||||
base.VisitPointerReferenceExpression(pointerReferenceExpression, data); |
||||
return true; |
||||
} |
||||
|
||||
public override bool VisitComposedType(ComposedType composedType, object data) |
||||
{ |
||||
if (composedType.PointerRank > 0) |
||||
return true; |
||||
else |
||||
return base.VisitComposedType(composedType, data); |
||||
} |
||||
|
||||
public override bool VisitUnaryOperatorExpression(UnaryOperatorExpression unaryOperatorExpression, object data) |
||||
{ |
||||
bool result = base.VisitUnaryOperatorExpression(unaryOperatorExpression, data); |
||||
if (unaryOperatorExpression.Operator == UnaryOperatorType.Dereference) { |
||||
BinaryOperatorExpression bop = unaryOperatorExpression.Expression as BinaryOperatorExpression; |
||||
if (bop != null && bop.Operator == BinaryOperatorType.Add && bop.Annotation<PointerArithmetic>() != null) { |
||||
// transform "*(ptr + int)" to "ptr[int]"
|
||||
IndexerExpression indexer = new IndexerExpression(); |
||||
indexer.Target = bop.Left.Detach(); |
||||
indexer.Arguments.Add(bop.Right.Detach()); |
||||
indexer.CopyAnnotationsFrom(unaryOperatorExpression); |
||||
indexer.CopyAnnotationsFrom(bop); |
||||
unaryOperatorExpression.ReplaceWith(indexer); |
||||
} |
||||
return true; |
||||
} else if (unaryOperatorExpression.Operator == UnaryOperatorType.AddressOf) { |
||||
return true; |
||||
} else { |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
public override bool VisitMemberReferenceExpression(MemberReferenceExpression memberReferenceExpression, object data) |
||||
{ |
||||
bool result = base.VisitMemberReferenceExpression(memberReferenceExpression, data); |
||||
UnaryOperatorExpression uoe = memberReferenceExpression.Target as UnaryOperatorExpression; |
||||
if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference) { |
||||
PointerReferenceExpression pre = new PointerReferenceExpression(); |
||||
pre.Target = uoe.Expression.Detach(); |
||||
pre.MemberName = memberReferenceExpression.MemberName; |
||||
memberReferenceExpression.TypeArguments.MoveTo(pre.TypeArguments); |
||||
pre.CopyAnnotationsFrom(uoe); |
||||
pre.CopyAnnotationsFrom(memberReferenceExpression); |
||||
memberReferenceExpression.ReplaceWith(pre); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
public override bool VisitStackAllocExpression(StackAllocExpression stackAllocExpression, object data) |
||||
{ |
||||
base.VisitStackAllocExpression(stackAllocExpression, data); |
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,360 @@
@@ -0,0 +1,360 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Introduces using declarations.
|
||||
/// </summary>
|
||||
public class IntroduceUsingDeclarations : IAstTransform |
||||
{ |
||||
DecompilerContext context; |
||||
|
||||
public IntroduceUsingDeclarations(DecompilerContext context) |
||||
{ |
||||
this.context = context; |
||||
} |
||||
|
||||
public void Run(AstNode compilationUnit) |
||||
{ |
||||
if (!context.Settings.UsingDeclarations) |
||||
return; |
||||
|
||||
// First determine all the namespaces that need to be imported:
|
||||
compilationUnit.AcceptVisitor(new FindRequiredImports(this), null); |
||||
|
||||
importedNamespaces.Add("System"); // always import System, even when not necessary
|
||||
|
||||
// Now add using declarations for those namespaces:
|
||||
foreach (string ns in importedNamespaces.OrderByDescending(n => n)) { |
||||
// we go backwards (OrderByDescending) through the list of namespaces because we insert them backwards
|
||||
// (always inserting at the start of the list)
|
||||
string[] parts = ns.Split('.'); |
||||
AstType nsType = new SimpleType(parts[0]); |
||||
for (int i = 1; i < parts.Length; i++) { |
||||
nsType = new MemberType { Target = nsType, MemberName = parts[i] }; |
||||
} |
||||
compilationUnit.InsertChildAfter(null, new UsingDeclaration { Import = nsType }, CompilationUnit.MemberRole); |
||||
} |
||||
|
||||
if (!context.Settings.FullyQualifyAmbiguousTypeNames) |
||||
return; |
||||
|
||||
FindAmbiguousTypeNames(context.CurrentModule, internalsVisible: true); |
||||
foreach (AssemblyNameReference r in context.CurrentModule.AssemblyReferences) { |
||||
AssemblyDefinition d = context.CurrentModule.AssemblyResolver.Resolve(r); |
||||
if (d != null) |
||||
FindAmbiguousTypeNames(d.MainModule, internalsVisible: false); |
||||
} |
||||
|
||||
// verify that the SimpleTypes refer to the correct type (no ambiguities)
|
||||
compilationUnit.AcceptVisitor(new FullyQualifyAmbiguousTypeNamesVisitor(this), null); |
||||
} |
||||
|
||||
readonly HashSet<string> declaredNamespaces = new HashSet<string>() { string.Empty }; |
||||
readonly HashSet<string> importedNamespaces = new HashSet<string>(); |
||||
|
||||
// Note that we store type names with `n suffix, so we automatically disambiguate based on number of type parameters.
|
||||
readonly HashSet<string> availableTypeNames = new HashSet<string>(); |
||||
readonly HashSet<string> ambiguousTypeNames = new HashSet<string>(); |
||||
|
||||
sealed class FindRequiredImports : DepthFirstAstVisitor<object, object> |
||||
{ |
||||
readonly IntroduceUsingDeclarations transform; |
||||
string currentNamespace; |
||||
|
||||
public FindRequiredImports(IntroduceUsingDeclarations transform) |
||||
{ |
||||
this.transform = transform; |
||||
this.currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty; |
||||
} |
||||
|
||||
bool IsParentOfCurrentNamespace(string ns) |
||||
{ |
||||
if (ns.Length == 0) |
||||
return true; |
||||
if (currentNamespace.StartsWith(ns, StringComparison.Ordinal)) { |
||||
if (currentNamespace.Length == ns.Length) |
||||
return true; |
||||
if (currentNamespace[ns.Length] == '.') |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public override object VisitSimpleType(SimpleType simpleType, object data) |
||||
{ |
||||
TypeReference tr = simpleType.Annotation<TypeReference>(); |
||||
if (tr != null && !IsParentOfCurrentNamespace(tr.Namespace)) { |
||||
transform.importedNamespaces.Add(tr.Namespace); |
||||
} |
||||
return base.VisitSimpleType(simpleType, data); // also visit type arguments
|
||||
} |
||||
|
||||
public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) |
||||
{ |
||||
string oldNamespace = currentNamespace; |
||||
foreach (Identifier ident in namespaceDeclaration.Identifiers) { |
||||
currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident.Name); |
||||
transform.declaredNamespaces.Add(currentNamespace); |
||||
} |
||||
base.VisitNamespaceDeclaration(namespaceDeclaration, data); |
||||
currentNamespace = oldNamespace; |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
void FindAmbiguousTypeNames(ModuleDefinition module, bool internalsVisible) |
||||
{ |
||||
foreach (TypeDefinition type in module.Types) { |
||||
if (internalsVisible || type.IsPublic) { |
||||
if (importedNamespaces.Contains(type.Namespace) || declaredNamespaces.Contains(type.Namespace)) { |
||||
if (!availableTypeNames.Add(type.Name)) |
||||
ambiguousTypeNames.Add(type.Name); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
sealed class FullyQualifyAmbiguousTypeNamesVisitor : DepthFirstAstVisitor<object, object> |
||||
{ |
||||
readonly IntroduceUsingDeclarations transform; |
||||
string currentNamespace; |
||||
HashSet<string> currentMemberTypes; |
||||
Dictionary<string, MemberReference> currentMembers; |
||||
bool isWithinTypeReferenceExpression; |
||||
|
||||
public FullyQualifyAmbiguousTypeNamesVisitor(IntroduceUsingDeclarations transform) |
||||
{ |
||||
this.transform = transform; |
||||
this.currentNamespace = transform.context.CurrentType != null ? transform.context.CurrentType.Namespace : string.Empty; |
||||
} |
||||
|
||||
public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) |
||||
{ |
||||
string oldNamespace = currentNamespace; |
||||
foreach (Identifier ident in namespaceDeclaration.Identifiers) { |
||||
currentNamespace = NamespaceDeclaration.BuildQualifiedName(currentNamespace, ident.Name); |
||||
} |
||||
base.VisitNamespaceDeclaration(namespaceDeclaration, data); |
||||
currentNamespace = oldNamespace; |
||||
return null; |
||||
} |
||||
|
||||
public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) |
||||
{ |
||||
HashSet<string> oldMemberTypes = currentMemberTypes; |
||||
currentMemberTypes = currentMemberTypes != null ? new HashSet<string>(currentMemberTypes) : new HashSet<string>(); |
||||
|
||||
Dictionary<string, MemberReference> oldMembers = currentMembers; |
||||
currentMembers = new Dictionary<string, MemberReference>(); |
||||
|
||||
TypeDefinition typeDef = typeDeclaration.Annotation<TypeDefinition>(); |
||||
bool privateMembersVisible = true; |
||||
ModuleDefinition internalMembersVisibleInModule = typeDef.Module; |
||||
while (typeDef != null) { |
||||
foreach (GenericParameter gp in typeDef.GenericParameters) { |
||||
currentMemberTypes.Add(gp.Name); |
||||
} |
||||
foreach (TypeDefinition t in typeDef.NestedTypes) { |
||||
if (privateMembersVisible || IsVisible(t, internalMembersVisibleInModule)) |
||||
currentMemberTypes.Add(t.Name.Substring(t.Name.LastIndexOf('+') + 1)); |
||||
} |
||||
|
||||
foreach (MethodDefinition method in typeDef.Methods) { |
||||
if (privateMembersVisible || IsVisible(method, internalMembersVisibleInModule)) |
||||
AddCurrentMember(method); |
||||
} |
||||
foreach (PropertyDefinition property in typeDef.Properties) { |
||||
if (privateMembersVisible || IsVisible(property.GetMethod, internalMembersVisibleInModule) || IsVisible(property.SetMethod, internalMembersVisibleInModule)) |
||||
AddCurrentMember(property); |
||||
} |
||||
foreach (EventDefinition ev in typeDef.Events) { |
||||
if (privateMembersVisible || IsVisible(ev.AddMethod, internalMembersVisibleInModule) || IsVisible(ev.RemoveMethod, internalMembersVisibleInModule)) |
||||
AddCurrentMember(ev); |
||||
} |
||||
foreach (FieldDefinition f in typeDef.Fields) { |
||||
if (privateMembersVisible || IsVisible(f, internalMembersVisibleInModule)) |
||||
AddCurrentMember(f); |
||||
} |
||||
// repeat with base class:
|
||||
if (typeDef.BaseType != null) |
||||
typeDef = typeDef.BaseType.Resolve(); |
||||
else |
||||
typeDef = null; |
||||
privateMembersVisible = false; |
||||
} |
||||
|
||||
// Now add current members from outer classes:
|
||||
if (oldMembers != null) { |
||||
foreach (var pair in oldMembers) { |
||||
// add members from outer classes only if the inner class doesn't define the member
|
||||
if (!currentMembers.ContainsKey(pair.Key)) |
||||
currentMembers.Add(pair.Key, pair.Value); |
||||
} |
||||
} |
||||
|
||||
base.VisitTypeDeclaration(typeDeclaration, data); |
||||
currentMembers = oldMembers; |
||||
return null; |
||||
} |
||||
|
||||
void AddCurrentMember(MemberReference m) |
||||
{ |
||||
MemberReference existingMember; |
||||
if (currentMembers.TryGetValue(m.Name, out existingMember)) { |
||||
// We keep the existing member assignment if it was from another class (=from a derived class),
|
||||
// because members in derived classes have precedence over members in base classes.
|
||||
if (existingMember != null && existingMember.DeclaringType == m.DeclaringType) { |
||||
// Use null as value to signalize multiple members with the same name
|
||||
currentMembers[m.Name] = null; |
||||
} |
||||
} else { |
||||
currentMembers.Add(m.Name, m); |
||||
} |
||||
} |
||||
|
||||
bool IsVisible(MethodDefinition m, ModuleDefinition internalMembersVisibleInModule) |
||||
{ |
||||
if (m == null) |
||||
return false; |
||||
switch (m.Attributes & MethodAttributes.MemberAccessMask) { |
||||
case MethodAttributes.FamANDAssem: |
||||
case MethodAttributes.Assembly: |
||||
return m.Module == internalMembersVisibleInModule; |
||||
case MethodAttributes.Family: |
||||
case MethodAttributes.FamORAssem: |
||||
case MethodAttributes.Public: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
bool IsVisible(FieldDefinition f, ModuleDefinition internalMembersVisibleInModule) |
||||
{ |
||||
if (f == null) |
||||
return false; |
||||
switch (f.Attributes & FieldAttributes.FieldAccessMask) { |
||||
case FieldAttributes.FamANDAssem: |
||||
case FieldAttributes.Assembly: |
||||
return f.Module == internalMembersVisibleInModule; |
||||
case FieldAttributes.Family: |
||||
case FieldAttributes.FamORAssem: |
||||
case FieldAttributes.Public: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
bool IsVisible(TypeDefinition t, ModuleDefinition internalMembersVisibleInModule) |
||||
{ |
||||
if (t == null) |
||||
return false; |
||||
switch (t.Attributes & TypeAttributes.VisibilityMask) { |
||||
case TypeAttributes.NotPublic: |
||||
case TypeAttributes.NestedAssembly: |
||||
case TypeAttributes.NestedFamANDAssem: |
||||
return t.Module == internalMembersVisibleInModule; |
||||
case TypeAttributes.NestedFamily: |
||||
case TypeAttributes.NestedFamORAssem: |
||||
case TypeAttributes.NestedPublic: |
||||
case TypeAttributes.Public: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public override object VisitSimpleType(SimpleType simpleType, object data) |
||||
{ |
||||
// Handle type arguments first, so that the fixed-up type arguments get moved over to the MemberType,
|
||||
// if we're also creating one here.
|
||||
base.VisitSimpleType(simpleType, data); |
||||
TypeReference tr = simpleType.Annotation<TypeReference>(); |
||||
// Fully qualify any ambiguous type names.
|
||||
if (tr != null && IsAmbiguous(tr.Namespace, tr.Name)) { |
||||
AstType ns; |
||||
if (string.IsNullOrEmpty(tr.Namespace)) { |
||||
ns = new SimpleType("global"); |
||||
} else { |
||||
string[] parts = tr.Namespace.Split('.'); |
||||
if (IsAmbiguous(string.Empty, parts[0])) { |
||||
// conflict between namespace and type name/member name
|
||||
ns = new MemberType { Target = new SimpleType("global"), IsDoubleColon = true, MemberName = parts[0] }; |
||||
} else { |
||||
ns = new SimpleType(parts[0]); |
||||
} |
||||
for (int i = 1; i < parts.Length; i++) { |
||||
ns = new MemberType { Target = ns, MemberName = parts[i] }; |
||||
} |
||||
} |
||||
MemberType mt = new MemberType(); |
||||
mt.Target = ns; |
||||
mt.IsDoubleColon = string.IsNullOrEmpty(tr.Namespace); |
||||
mt.MemberName = simpleType.Identifier; |
||||
mt.CopyAnnotationsFrom(simpleType); |
||||
simpleType.TypeArguments.MoveTo(mt.TypeArguments); |
||||
simpleType.ReplaceWith(mt); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public override object VisitTypeReferenceExpression(TypeReferenceExpression typeReferenceExpression, object data) |
||||
{ |
||||
isWithinTypeReferenceExpression = true; |
||||
base.VisitTypeReferenceExpression(typeReferenceExpression, data); |
||||
isWithinTypeReferenceExpression = false; |
||||
return null; |
||||
} |
||||
|
||||
bool IsAmbiguous(string ns, string name) |
||||
{ |
||||
// If the type name conflicts with an inner class/type parameter, we need to fully-qualify it:
|
||||
if (currentMemberTypes != null && currentMemberTypes.Contains(name)) |
||||
return true; |
||||
// If the type name conflicts with a field/property etc. on the current class, we need to fully-qualify it,
|
||||
// if we're inside an expression.
|
||||
if (isWithinTypeReferenceExpression && currentMembers != null) { |
||||
MemberReference mr; |
||||
if (currentMembers.TryGetValue(name, out mr)) { |
||||
// However, in the special case where the member is a field or property with the same type
|
||||
// as is requested, then we can use the short name (if it's not otherwise ambiguous)
|
||||
PropertyDefinition prop = mr as PropertyDefinition; |
||||
FieldDefinition field = mr as FieldDefinition; |
||||
if (!(prop != null && prop.PropertyType.Namespace == ns && prop.PropertyType.Name == name) |
||||
&& !(field != null && field.FieldType.Namespace == ns && field.FieldType.Name == name)) |
||||
return true; |
||||
} |
||||
} |
||||
// If the type is defined in the current namespace,
|
||||
// then we can use the short name even if we imported type with same name from another namespace.
|
||||
if (ns == currentNamespace && !string.IsNullOrEmpty(ns)) |
||||
return false; |
||||
return transform.ambiguousTypeNames.Contains(name); |
||||
} |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,150 @@
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
public class PushNegation: DepthFirstAstVisitor<object, object>, IAstTransform |
||||
{ |
||||
public override object VisitUnaryOperatorExpression(UnaryOperatorExpression unary, object data) |
||||
{ |
||||
// Remove double negation
|
||||
// !!a
|
||||
if (unary.Operator == UnaryOperatorType.Not && |
||||
unary.Expression is UnaryOperatorExpression && |
||||
(unary.Expression as UnaryOperatorExpression).Operator == UnaryOperatorType.Not) |
||||
{ |
||||
AstNode newNode = (unary.Expression as UnaryOperatorExpression).Expression; |
||||
unary.ReplaceWith(newNode); |
||||
return newNode.AcceptVisitor(this, data); |
||||
} |
||||
|
||||
// Push through binary operation
|
||||
// !((a) op (b))
|
||||
BinaryOperatorExpression binaryOp = unary.Expression as BinaryOperatorExpression; |
||||
if (unary.Operator == UnaryOperatorType.Not && binaryOp != null) { |
||||
bool successful = true; |
||||
switch (binaryOp.Operator) { |
||||
case BinaryOperatorType.Equality: |
||||
binaryOp.Operator = BinaryOperatorType.InEquality; |
||||
break; |
||||
case BinaryOperatorType.InEquality: |
||||
binaryOp.Operator = BinaryOperatorType.Equality; |
||||
break; |
||||
case BinaryOperatorType.GreaterThan: // TODO: these are invalid for floats (stupid NaN)
|
||||
binaryOp.Operator = BinaryOperatorType.LessThanOrEqual; |
||||
break; |
||||
case BinaryOperatorType.GreaterThanOrEqual: |
||||
binaryOp.Operator = BinaryOperatorType.LessThan; |
||||
break; |
||||
case BinaryOperatorType.LessThanOrEqual: |
||||
binaryOp.Operator = BinaryOperatorType.GreaterThan; |
||||
break; |
||||
case BinaryOperatorType.LessThan: |
||||
binaryOp.Operator = BinaryOperatorType.GreaterThanOrEqual; |
||||
break; |
||||
default: |
||||
successful = false; |
||||
break; |
||||
} |
||||
if (successful) { |
||||
unary.ReplaceWith(binaryOp); |
||||
return binaryOp.AcceptVisitor(this, data); |
||||
} |
||||
|
||||
successful = true; |
||||
switch (binaryOp.Operator) { |
||||
case BinaryOperatorType.ConditionalAnd: |
||||
binaryOp.Operator = BinaryOperatorType.ConditionalOr; |
||||
break; |
||||
case BinaryOperatorType.ConditionalOr: |
||||
binaryOp.Operator = BinaryOperatorType.ConditionalAnd; |
||||
break; |
||||
default: |
||||
successful = false; |
||||
break; |
||||
} |
||||
if (successful) { |
||||
binaryOp.Left.ReplaceWith(e => new UnaryOperatorExpression(UnaryOperatorType.Not, e)); |
||||
binaryOp.Right.ReplaceWith(e => new UnaryOperatorExpression(UnaryOperatorType.Not, e)); |
||||
unary.ReplaceWith(binaryOp); |
||||
return binaryOp.AcceptVisitor(this, data); |
||||
} |
||||
} |
||||
return base.VisitUnaryOperatorExpression(unary, data); |
||||
} |
||||
|
||||
readonly static AstNode asCastIsNullPattern = new BinaryOperatorExpression( |
||||
new AnyNode("expr").ToExpression().CastAs(new AnyNode("type")), |
||||
BinaryOperatorType.Equality, |
||||
new NullReferenceExpression() |
||||
); |
||||
|
||||
readonly static AstNode asCastIsNotNullPattern = new BinaryOperatorExpression( |
||||
new AnyNode("expr").ToExpression().CastAs(new AnyNode("type")), |
||||
BinaryOperatorType.InEquality, |
||||
new NullReferenceExpression() |
||||
); |
||||
|
||||
public override object VisitBinaryOperatorExpression(BinaryOperatorExpression binaryOperatorExpression, object data) |
||||
{ |
||||
BinaryOperatorType op = binaryOperatorExpression.Operator; |
||||
bool? rightOperand = null; |
||||
if (binaryOperatorExpression.Right is PrimitiveExpression) |
||||
rightOperand = ((PrimitiveExpression)binaryOperatorExpression.Right).Value as bool?; |
||||
if (op == BinaryOperatorType.Equality && rightOperand == true || op == BinaryOperatorType.InEquality && rightOperand == false) { |
||||
// 'b == true' or 'b != false' is useless
|
||||
binaryOperatorExpression.Left.AcceptVisitor(this, data); |
||||
binaryOperatorExpression.ReplaceWith(binaryOperatorExpression.Left); |
||||
return null; |
||||
} else if (op == BinaryOperatorType.Equality && rightOperand == false || op == BinaryOperatorType.InEquality && rightOperand == true) { |
||||
// 'b == false' or 'b != true' is a negation:
|
||||
Expression left = binaryOperatorExpression.Left; |
||||
left.Remove(); |
||||
UnaryOperatorExpression uoe = new UnaryOperatorExpression(UnaryOperatorType.Not, left); |
||||
binaryOperatorExpression.ReplaceWith(uoe); |
||||
return uoe.AcceptVisitor(this, data); |
||||
} else { |
||||
bool negate = false; |
||||
Match m = asCastIsNotNullPattern.Match(binaryOperatorExpression); |
||||
if (!m.Success) { |
||||
m = asCastIsNullPattern.Match(binaryOperatorExpression); |
||||
negate = true; |
||||
} |
||||
if (m.Success) { |
||||
Expression expr = m.Get<Expression>("expr").Single().Detach().IsType(m.Get<AstType>("type").Single().Detach()); |
||||
if (negate) |
||||
expr = new UnaryOperatorExpression(UnaryOperatorType.Not, expr); |
||||
binaryOperatorExpression.ReplaceWith(expr); |
||||
return expr.AcceptVisitor(this, data); |
||||
} else { |
||||
return base.VisitBinaryOperatorExpression(binaryOperatorExpression, data); |
||||
} |
||||
} |
||||
} |
||||
void IAstTransform.Run(AstNode node) |
||||
{ |
||||
node.AcceptVisitor(this, null); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,288 @@
@@ -0,0 +1,288 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using ICSharpCode.NRefactory.PatternMatching; |
||||
using Mono.Cecil; |
||||
using Ast = ICSharpCode.NRefactory.CSharp; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
/// <summary>
|
||||
/// Replaces method calls with the appropriate operator expressions.
|
||||
/// Also simplifies "x = x op y" into "x op= y" where possible.
|
||||
/// </summary>
|
||||
public class ReplaceMethodCallsWithOperators : DepthFirstAstVisitor<object, object>, IAstTransform |
||||
{ |
||||
static readonly MemberReferenceExpression typeHandleOnTypeOfPattern = new MemberReferenceExpression { |
||||
Target = new Choice { |
||||
new TypeOfExpression(new AnyNode()), |
||||
new UndocumentedExpression { UndocumentedExpressionType = UndocumentedExpressionType.RefType, Arguments = { new AnyNode() } } |
||||
}, |
||||
MemberName = "TypeHandle" |
||||
}; |
||||
|
||||
public override object VisitInvocationExpression(InvocationExpression invocationExpression, object data) |
||||
{ |
||||
base.VisitInvocationExpression(invocationExpression, data); |
||||
|
||||
MethodReference methodRef = invocationExpression.Annotation<MethodReference>(); |
||||
if (methodRef == null) |
||||
return null; |
||||
var arguments = invocationExpression.Arguments.ToArray(); |
||||
|
||||
// Reduce "String.Concat(a, b)" to "a + b"
|
||||
if (methodRef.Name == "Concat" && methodRef.DeclaringType.FullName == "System.String" && arguments.Length >= 2) |
||||
{ |
||||
invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression
|
||||
Expression expr = arguments[0]; |
||||
for (int i = 1; i < arguments.Length; i++) { |
||||
expr = new BinaryOperatorExpression(expr, BinaryOperatorType.Add, arguments[i]); |
||||
} |
||||
invocationExpression.ReplaceWith(expr); |
||||
return null; |
||||
} |
||||
|
||||
switch (methodRef.FullName) { |
||||
case "System.Type System.Type::GetTypeFromHandle(System.RuntimeTypeHandle)": |
||||
if (arguments.Length == 1) { |
||||
if (typeHandleOnTypeOfPattern.IsMatch(arguments[0])) { |
||||
invocationExpression.ReplaceWith(((MemberReferenceExpression)arguments[0]).Target); |
||||
return null; |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
|
||||
BinaryOperatorType? bop = GetBinaryOperatorTypeFromMetadataName(methodRef.Name); |
||||
if (bop != null && arguments.Length == 2) { |
||||
invocationExpression.Arguments.Clear(); // detach arguments from invocationExpression
|
||||
invocationExpression.ReplaceWith( |
||||
new BinaryOperatorExpression(arguments[0], bop.Value, arguments[1]).WithAnnotation(methodRef) |
||||
); |
||||
return null; |
||||
} |
||||
UnaryOperatorType? uop = GetUnaryOperatorTypeFromMetadataName(methodRef.Name); |
||||
if (uop != null && arguments.Length == 1) { |
||||
arguments[0].Remove(); // detach argument
|
||||
invocationExpression.ReplaceWith( |
||||
new UnaryOperatorExpression(uop.Value, arguments[0]).WithAnnotation(methodRef) |
||||
); |
||||
return null; |
||||
} |
||||
if (methodRef.Name == "op_Explicit" && arguments.Length == 1) { |
||||
arguments[0].Remove(); // detach argument
|
||||
invocationExpression.ReplaceWith( |
||||
arguments[0].CastTo(AstBuilder.ConvertType(methodRef.ReturnType, methodRef.MethodReturnType)) |
||||
.WithAnnotation(methodRef) |
||||
); |
||||
return null; |
||||
} |
||||
if (methodRef.Name == "op_Implicit" && arguments.Length == 1) { |
||||
invocationExpression.ReplaceWith(arguments[0]); |
||||
return null; |
||||
} |
||||
if (methodRef.Name == "op_True" && arguments.Length == 1 && invocationExpression.Role == AstNode.Roles.Condition) { |
||||
invocationExpression.ReplaceWith(arguments[0]); |
||||
return null; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
BinaryOperatorType? GetBinaryOperatorTypeFromMetadataName(string name) |
||||
{ |
||||
switch (name) { |
||||
case "op_Addition": |
||||
return BinaryOperatorType.Add; |
||||
case "op_Subtraction": |
||||
return BinaryOperatorType.Subtract; |
||||
case "op_Multiply": |
||||
return BinaryOperatorType.Multiply; |
||||
case "op_Division": |
||||
return BinaryOperatorType.Divide; |
||||
case "op_Modulus": |
||||
return BinaryOperatorType.Modulus; |
||||
case "op_BitwiseAnd": |
||||
return BinaryOperatorType.BitwiseAnd; |
||||
case "op_BitwiseOr": |
||||
return BinaryOperatorType.BitwiseOr; |
||||
case "op_ExlusiveOr": |
||||
return BinaryOperatorType.ExclusiveOr; |
||||
case "op_LeftShift": |
||||
return BinaryOperatorType.ShiftLeft; |
||||
case "op_RightShift": |
||||
return BinaryOperatorType.ShiftRight; |
||||
case "op_Equality": |
||||
return BinaryOperatorType.Equality; |
||||
case "op_Inequality": |
||||
return BinaryOperatorType.InEquality; |
||||
case "op_LessThan": |
||||
return BinaryOperatorType.LessThan; |
||||
case "op_LessThanOrEqual": |
||||
return BinaryOperatorType.LessThanOrEqual; |
||||
case "op_GreaterThan": |
||||
return BinaryOperatorType.GreaterThan; |
||||
case "op_GreaterThanOrEqual": |
||||
return BinaryOperatorType.GreaterThanOrEqual; |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
UnaryOperatorType? GetUnaryOperatorTypeFromMetadataName(string name) |
||||
{ |
||||
switch (name) { |
||||
case "op_LogicalNot": |
||||
return UnaryOperatorType.Not; |
||||
case "op_OnesComplement": |
||||
return UnaryOperatorType.BitNot; |
||||
case "op_UnaryNegation": |
||||
return UnaryOperatorType.Minus; |
||||
case "op_UnaryPlus": |
||||
return UnaryOperatorType.Plus; |
||||
case "op_Increment": |
||||
return UnaryOperatorType.Increment; |
||||
case "op_Decrement": |
||||
return UnaryOperatorType.Decrement; |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// This annotation is used to convert a compound assignment "a += 2;" or increment operator "a++;"
|
||||
/// back to the original "a = a + 2;". This is sometimes necessary when the checked/unchecked semantics
|
||||
/// cannot be guaranteed otherwise (see CheckedUnchecked.ForWithCheckedInitializerAndUncheckedIterator test)
|
||||
/// </summary>
|
||||
public class RestoreOriginalAssignOperatorAnnotation |
||||
{ |
||||
readonly BinaryOperatorExpression binaryOperatorExpression; |
||||
|
||||
public RestoreOriginalAssignOperatorAnnotation(BinaryOperatorExpression binaryOperatorExpression) |
||||
{ |
||||
this.binaryOperatorExpression = binaryOperatorExpression; |
||||
} |
||||
|
||||
public AssignmentExpression Restore(Expression expression) |
||||
{ |
||||
expression.RemoveAnnotations<RestoreOriginalAssignOperatorAnnotation>(); |
||||
AssignmentExpression assign = expression as AssignmentExpression; |
||||
if (assign == null) { |
||||
UnaryOperatorExpression uoe = (UnaryOperatorExpression)expression; |
||||
assign = new AssignmentExpression(uoe.Expression.Detach(), new PrimitiveExpression(1)); |
||||
} else { |
||||
assign.Operator = AssignmentOperatorType.Assign; |
||||
} |
||||
binaryOperatorExpression.Right = assign.Right.Detach(); |
||||
assign.Right = binaryOperatorExpression; |
||||
return assign; |
||||
} |
||||
} |
||||
|
||||
public override object VisitAssignmentExpression(AssignmentExpression assignment, object data) |
||||
{ |
||||
base.VisitAssignmentExpression(assignment, data); |
||||
// Combine "x = x op y" into "x op= y"
|
||||
BinaryOperatorExpression binary = assignment.Right as BinaryOperatorExpression; |
||||
if (binary != null && assignment.Operator == AssignmentOperatorType.Assign) { |
||||
if (CanConvertToCompoundAssignment(assignment.Left) && assignment.Left.IsMatch(binary.Left)) { |
||||
assignment.Operator = GetAssignmentOperatorForBinaryOperator(binary.Operator); |
||||
if (assignment.Operator != AssignmentOperatorType.Assign) { |
||||
// If we found a shorter operator, get rid of the BinaryOperatorExpression:
|
||||
assignment.CopyAnnotationsFrom(binary); |
||||
assignment.Right = binary.Right; |
||||
assignment.AddAnnotation(new RestoreOriginalAssignOperatorAnnotation(binary)); |
||||
} |
||||
} |
||||
} |
||||
if (assignment.Operator == AssignmentOperatorType.Add || assignment.Operator == AssignmentOperatorType.Subtract) { |
||||
// detect increment/decrement
|
||||
if (assignment.Right.IsMatch(new PrimitiveExpression(1))) { |
||||
// only if it's not a custom operator
|
||||
if (assignment.Annotation<MethodReference>() == null) { |
||||
UnaryOperatorType type; |
||||
// When the parent is an expression statement, pre- or post-increment doesn't matter;
|
||||
// so we can pick post-increment which is more commonly used (for (int i = 0; i < x; i++))
|
||||
if (assignment.Parent is ExpressionStatement) |
||||
type = (assignment.Operator == AssignmentOperatorType.Add) ? UnaryOperatorType.PostIncrement : UnaryOperatorType.PostDecrement; |
||||
else |
||||
type = (assignment.Operator == AssignmentOperatorType.Add) ? UnaryOperatorType.Increment : UnaryOperatorType.Decrement; |
||||
assignment.ReplaceWith(new UnaryOperatorExpression(type, assignment.Left.Detach()).CopyAnnotationsFrom(assignment)); |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static AssignmentOperatorType GetAssignmentOperatorForBinaryOperator(BinaryOperatorType bop) |
||||
{ |
||||
switch (bop) { |
||||
case BinaryOperatorType.Add: |
||||
return AssignmentOperatorType.Add; |
||||
case BinaryOperatorType.Subtract: |
||||
return AssignmentOperatorType.Subtract; |
||||
case BinaryOperatorType.Multiply: |
||||
return AssignmentOperatorType.Multiply; |
||||
case BinaryOperatorType.Divide: |
||||
return AssignmentOperatorType.Divide; |
||||
case BinaryOperatorType.Modulus: |
||||
return AssignmentOperatorType.Modulus; |
||||
case BinaryOperatorType.ShiftLeft: |
||||
return AssignmentOperatorType.ShiftLeft; |
||||
case BinaryOperatorType.ShiftRight: |
||||
return AssignmentOperatorType.ShiftRight; |
||||
case BinaryOperatorType.BitwiseAnd: |
||||
return AssignmentOperatorType.BitwiseAnd; |
||||
case BinaryOperatorType.BitwiseOr: |
||||
return AssignmentOperatorType.BitwiseOr; |
||||
case BinaryOperatorType.ExclusiveOr: |
||||
return AssignmentOperatorType.ExclusiveOr; |
||||
default: |
||||
return AssignmentOperatorType.Assign; |
||||
} |
||||
} |
||||
|
||||
static bool CanConvertToCompoundAssignment(Expression left) |
||||
{ |
||||
MemberReferenceExpression mre = left as MemberReferenceExpression; |
||||
if (mre != null) |
||||
return IsWithoutSideEffects(mre.Target); |
||||
IndexerExpression ie = left as IndexerExpression; |
||||
if (ie != null) |
||||
return IsWithoutSideEffects(ie.Target) && ie.Arguments.All(IsWithoutSideEffects); |
||||
UnaryOperatorExpression uoe = left as UnaryOperatorExpression; |
||||
if (uoe != null && uoe.Operator == UnaryOperatorType.Dereference) |
||||
return IsWithoutSideEffects(uoe.Expression); |
||||
return IsWithoutSideEffects(left); |
||||
} |
||||
|
||||
static bool IsWithoutSideEffects(Expression left) |
||||
{ |
||||
return left is ThisReferenceExpression || left is IdentifierExpression || left is TypeReferenceExpression || left is BaseReferenceExpression; |
||||
} |
||||
|
||||
void IAstTransform.Run(AstNode node) |
||||
{ |
||||
node.AcceptVisitor(this, null); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Threading; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast.Transforms |
||||
{ |
||||
public interface IAstTransform |
||||
{ |
||||
void Run(AstNode compilationUnit); |
||||
} |
||||
|
||||
public static class TransformationPipeline |
||||
{ |
||||
public static IAstTransform[] CreatePipeline(DecompilerContext context) |
||||
{ |
||||
return new IAstTransform[] { |
||||
new PushNegation(), |
||||
new DelegateConstruction(context), |
||||
new PatternStatementTransform(context), |
||||
new ReplaceMethodCallsWithOperators(), |
||||
new IntroduceUnsafeModifier(), |
||||
new AddCheckedBlocks(), |
||||
new DeclareVariables(context), // should run after most transforms that modify statements
|
||||
new ConvertConstructorCallIntoInitializer(), // must run after DeclareVariables
|
||||
new DecimalConstantTransform(), |
||||
new IntroduceUsingDeclarations(context), |
||||
new IntroduceExtensionMethods(context), // must run after IntroduceUsingDeclarations
|
||||
new IntroduceQueryExpressions(context), // must run after IntroduceExtensionMethods
|
||||
new CombineQueryExpressions(context), |
||||
}; |
||||
} |
||||
|
||||
public static void RunTransformationsUntil(AstNode node, Predicate<IAstTransform> abortCondition, DecompilerContext context) |
||||
{ |
||||
if (node == null) |
||||
return; |
||||
|
||||
foreach (var transform in CreatePipeline(context)) { |
||||
context.CancellationToken.ThrowIfCancellationRequested(); |
||||
if (abortCondition != null && abortCondition(transform)) |
||||
return; |
||||
transform.Run(node); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,516 @@
@@ -0,0 +1,516 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Ast |
||||
{ |
||||
public static class TypesHierarchyHelpers |
||||
{ |
||||
public static bool IsBaseType(TypeDefinition baseType, TypeDefinition derivedType, bool resolveTypeArguments) |
||||
{ |
||||
if (resolveTypeArguments) |
||||
return BaseTypes(derivedType).Any(t => t.Item == baseType); |
||||
else { |
||||
var comparableBaseType = baseType.ResolveOrThrow(); |
||||
while (derivedType.BaseType != null) { |
||||
var resolvedBaseType = derivedType.BaseType.ResolveOrThrow(); |
||||
if (resolvedBaseType == null) |
||||
return false; |
||||
if (comparableBaseType == resolvedBaseType) |
||||
return true; |
||||
derivedType = resolvedBaseType; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Determines whether one method overrides or hides another method.
|
||||
/// </summary>
|
||||
/// <param name="parentMethod">The method declared in a base type.</param>
|
||||
/// <param name="childMethod">The method declared in a derived type.</param>
|
||||
/// <returns>true if <paramref name="childMethod"/> hides or overrides <paramref name="parentMethod"/>,
|
||||
/// otherwise false.</returns>
|
||||
public static bool IsBaseMethod(MethodDefinition parentMethod, MethodDefinition childMethod) |
||||
{ |
||||
if (parentMethod == null) |
||||
throw new ArgumentNullException("parentMethod"); |
||||
if (childMethod == null) |
||||
throw new ArgumentNullException("childMethod"); |
||||
|
||||
if (parentMethod.Name != childMethod.Name) |
||||
return false; |
||||
|
||||
if (parentMethod.HasParameters || childMethod.HasParameters) |
||||
if (!parentMethod.HasParameters || !childMethod.HasParameters || parentMethod.Parameters.Count != childMethod.Parameters.Count) |
||||
return false; |
||||
|
||||
return FindBaseMethods(childMethod).Any(m => m == parentMethod);// || (parentMethod.HasGenericParameters && m.);
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Determines whether a property overrides or hides another property.
|
||||
/// </summary>
|
||||
/// <param name="parentProperty">The property declared in a base type.</param>
|
||||
/// <param name="childProperty">The property declared in a derived type.</param>
|
||||
/// <returns>true if the <paramref name="childProperty"/> hides or overrides <paramref name="parentProperty"/>,
|
||||
/// otherwise false.</returns>
|
||||
public static bool IsBaseProperty(PropertyDefinition parentProperty, PropertyDefinition childProperty) |
||||
{ |
||||
if (parentProperty == null) |
||||
throw new ArgumentNullException("parentProperty"); |
||||
if (childProperty == null) |
||||
throw new ArgumentNullException("childProperty"); |
||||
|
||||
if (parentProperty.Name != childProperty.Name) |
||||
return false; |
||||
|
||||
if (parentProperty.HasParameters || childProperty.HasParameters) |
||||
if (!parentProperty.HasParameters || !childProperty.HasParameters || parentProperty.Parameters.Count != childProperty.Parameters.Count) |
||||
return false; |
||||
|
||||
return FindBaseProperties(childProperty).Any(m => m == parentProperty); |
||||
} |
||||
|
||||
public static bool IsBaseEvent(EventDefinition parentEvent, EventDefinition childEvent) |
||||
{ |
||||
if (parentEvent.Name != childEvent.Name) |
||||
return false; |
||||
|
||||
return FindBaseEvents(childEvent).Any(m => m == parentEvent); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Finds all methods from base types overridden or hidden by the specified method.
|
||||
/// </summary>
|
||||
/// <param name="method">The method which overrides or hides methods from base types.</param>
|
||||
/// <returns>Methods overriden or hidden by the specified method.</returns>
|
||||
public static IEnumerable<MethodDefinition> FindBaseMethods(MethodDefinition method) |
||||
{ |
||||
if (method == null) |
||||
throw new ArgumentNullException("method"); |
||||
|
||||
var typeContext = CreateGenericContext(method.DeclaringType); |
||||
var gMethod = typeContext.ApplyTo(method); |
||||
|
||||
foreach (var baseType in BaseTypes(method.DeclaringType)) |
||||
foreach (var baseMethod in baseType.Item.Methods) |
||||
if (MatchMethod(baseType.ApplyTo(baseMethod), gMethod) && IsVisibleFromDerived(baseMethod, method.DeclaringType)) { |
||||
yield return baseMethod; |
||||
if (baseMethod.IsNewSlot == baseMethod.IsVirtual) |
||||
yield break; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Finds all properties from base types overridden or hidden by the specified property.
|
||||
/// </summary>
|
||||
/// <param name="property">The property which overrides or hides properties from base types.</param>
|
||||
/// <returns>Properties overriden or hidden by the specified property.</returns>
|
||||
public static IEnumerable<PropertyDefinition> FindBaseProperties(PropertyDefinition property) |
||||
{ |
||||
if (property == null) |
||||
throw new ArgumentNullException("property"); |
||||
|
||||
if ((property.GetMethod ?? property.SetMethod).HasOverrides) |
||||
yield break; |
||||
|
||||
var typeContext = CreateGenericContext(property.DeclaringType); |
||||
var gProperty = typeContext.ApplyTo(property); |
||||
bool isIndexer = property.IsIndexer(); |
||||
|
||||
foreach (var baseType in BaseTypes(property.DeclaringType)) |
||||
foreach (var baseProperty in baseType.Item.Properties) |
||||
if (MatchProperty(baseType.ApplyTo(baseProperty), gProperty) |
||||
&& IsVisibleFromDerived(baseProperty, property.DeclaringType)) { |
||||
if (isIndexer != baseProperty.IsIndexer()) |
||||
continue; |
||||
yield return baseProperty; |
||||
var anyPropertyAccessor = baseProperty.GetMethod ?? baseProperty.SetMethod; |
||||
if (anyPropertyAccessor.IsNewSlot == anyPropertyAccessor.IsVirtual) |
||||
yield break; |
||||
} |
||||
} |
||||
|
||||
public static IEnumerable<EventDefinition> FindBaseEvents(EventDefinition eventDef) |
||||
{ |
||||
if (eventDef == null) |
||||
throw new ArgumentNullException("eventDef"); |
||||
|
||||
var typeContext = CreateGenericContext(eventDef.DeclaringType); |
||||
var gEvent = typeContext.ApplyTo(eventDef); |
||||
|
||||
foreach (var baseType in BaseTypes(eventDef.DeclaringType)) |
||||
foreach (var baseEvent in baseType.Item.Events) |
||||
if (MatchEvent(baseType.ApplyTo(baseEvent), gEvent) && IsVisibleFromDerived(baseEvent, eventDef.DeclaringType)) { |
||||
yield return baseEvent; |
||||
var anyEventAccessor = baseEvent.AddMethod ?? baseEvent.RemoveMethod; |
||||
if (anyEventAccessor.IsNewSlot == anyEventAccessor.IsVirtual) |
||||
yield break; |
||||
} |
||||
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Determinates whether member of the base type is visible from a derived type.
|
||||
/// </summary>
|
||||
/// <param name="baseMember">The member which visibility is checked.</param>
|
||||
/// <param name="derivedType">The derived type.</param>
|
||||
/// <returns>true if the member is visible from derived type, othewise false.</returns>
|
||||
public static bool IsVisibleFromDerived(IMemberDefinition baseMember, TypeDefinition derivedType) |
||||
{ |
||||
if (baseMember == null) |
||||
throw new ArgumentNullException("baseMember"); |
||||
if (derivedType == null) |
||||
throw new ArgumentNullException("derivedType"); |
||||
|
||||
var visibility = IsVisibleFromDerived(baseMember); |
||||
if (visibility.HasValue) |
||||
return visibility.Value; |
||||
|
||||
if (baseMember.DeclaringType.Module == derivedType.Module) |
||||
return true; |
||||
// TODO: Check also InternalsVisibleToAttribute.
|
||||
return false; |
||||
} |
||||
|
||||
private static bool? IsVisibleFromDerived(IMemberDefinition member) |
||||
{ |
||||
MethodAttributes attrs = GetAccessAttributes(member) & MethodAttributes.MemberAccessMask; |
||||
if (attrs == MethodAttributes.Private) |
||||
return false; |
||||
if (attrs == MethodAttributes.Assembly || attrs == MethodAttributes.FamANDAssem) |
||||
return null; |
||||
return true; |
||||
} |
||||
|
||||
private static MethodAttributes GetAccessAttributes(IMemberDefinition member) |
||||
{ |
||||
var fld = member as FieldDefinition; |
||||
if (fld != null) |
||||
return (MethodAttributes)fld.Attributes; |
||||
|
||||
var method = member as MethodDefinition; |
||||
if (method != null) |
||||
return method.Attributes; |
||||
|
||||
var prop = member as PropertyDefinition; |
||||
if (prop != null) { |
||||
return (prop.GetMethod ?? prop.SetMethod).Attributes; |
||||
} |
||||
|
||||
var evnt = member as EventDefinition; |
||||
if (evnt != null) { |
||||
return (evnt.AddMethod ?? evnt.RemoveMethod).Attributes; |
||||
} |
||||
|
||||
var nestedType = member as TypeDefinition; |
||||
if (nestedType != null) { |
||||
if (nestedType.IsNestedPrivate) |
||||
return MethodAttributes.Private; |
||||
if (nestedType.IsNestedAssembly || nestedType.IsNestedFamilyAndAssembly) |
||||
return MethodAttributes.Assembly; |
||||
return MethodAttributes.Public; |
||||
} |
||||
|
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
private static bool MatchMethod(GenericContext<MethodDefinition> candidate, GenericContext<MethodDefinition> method) |
||||
{ |
||||
var mCandidate = candidate.Item; |
||||
var mMethod = method.Item; |
||||
if (mCandidate.Name != mMethod.Name) |
||||
return false; |
||||
|
||||
if (mCandidate.HasOverrides) |
||||
return false; |
||||
|
||||
if (mCandidate.IsSpecialName != method.Item.IsSpecialName) |
||||
return false; |
||||
|
||||
if (mCandidate.HasGenericParameters || mMethod.HasGenericParameters) { |
||||
if (!mCandidate.HasGenericParameters || !mMethod.HasGenericParameters || mCandidate.GenericParameters.Count != mMethod.GenericParameters.Count) |
||||
return false; |
||||
} |
||||
|
||||
if (mCandidate.HasParameters || mMethod.HasParameters) { |
||||
if (!mCandidate.HasParameters || !mMethod.HasParameters || mCandidate.Parameters.Count != mMethod.Parameters.Count) |
||||
return false; |
||||
|
||||
for (int index = 0; index < mCandidate.Parameters.Count; index++) { |
||||
if (!MatchParameters(candidate.ApplyTo(mCandidate.Parameters[index]), method.ApplyTo(mMethod.Parameters[index]))) |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
public static bool MatchInterfaceMethod(MethodDefinition candidate, MethodDefinition method, TypeReference interfaceContextType) |
||||
{ |
||||
var candidateContext = CreateGenericContext(candidate.DeclaringType); |
||||
var gCandidate = candidateContext.ApplyTo(candidate); |
||||
|
||||
if (interfaceContextType is GenericInstanceType) { |
||||
var methodContext = new GenericContext<TypeDefinition>(interfaceContextType.Resolve(), ((GenericInstanceType)interfaceContextType).GenericArguments); |
||||
var gMethod = methodContext.ApplyTo(method); |
||||
return MatchMethod(gCandidate, gMethod); |
||||
} else { |
||||
var methodContext = CreateGenericContext(interfaceContextType.Resolve()); |
||||
var gMethod = candidateContext.ApplyTo(method); |
||||
return MatchMethod(gCandidate, gMethod); |
||||
} |
||||
} |
||||
|
||||
private static bool MatchProperty(GenericContext<PropertyDefinition> candidate, GenericContext<PropertyDefinition> property) |
||||
{ |
||||
var mCandidate = candidate.Item; |
||||
var mProperty = property.Item; |
||||
if (mCandidate.Name != mProperty.Name) |
||||
return false; |
||||
|
||||
if ((mCandidate.GetMethod ?? mCandidate.SetMethod).HasOverrides) |
||||
return false; |
||||
|
||||
if (mCandidate.HasParameters || mProperty.HasParameters) { |
||||
if (!mCandidate.HasParameters || !mProperty.HasParameters || mCandidate.Parameters.Count != mProperty.Parameters.Count) |
||||
return false; |
||||
|
||||
for (int index = 0; index < mCandidate.Parameters.Count; index++) { |
||||
if (!MatchParameters(candidate.ApplyTo(mCandidate.Parameters[index]), property.ApplyTo(mProperty.Parameters[index]))) |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private static bool MatchEvent(GenericContext<EventDefinition> candidate, GenericContext<EventDefinition> ev) |
||||
{ |
||||
var mCandidate = candidate.Item; |
||||
var mEvent = ev.Item; |
||||
if (mCandidate.Name != mEvent.Name) |
||||
return false; |
||||
|
||||
if ((mCandidate.AddMethod ?? mCandidate.RemoveMethod).HasOverrides) |
||||
return false; |
||||
|
||||
if (!IsSameType(candidate.ResolveWithContext(mCandidate.EventType), ev.ResolveWithContext(mEvent.EventType))) |
||||
return false; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
private static bool MatchParameters(GenericContext<ParameterDefinition> baseParameterType, GenericContext<ParameterDefinition> parameterType) |
||||
{ |
||||
if (baseParameterType.Item.IsIn != parameterType.Item.IsIn || |
||||
baseParameterType.Item.IsOut != parameterType.Item.IsOut) |
||||
return false; |
||||
var baseParam = baseParameterType.ResolveWithContext(baseParameterType.Item.ParameterType); |
||||
var param = parameterType.ResolveWithContext(parameterType.Item.ParameterType); |
||||
return IsSameType(baseParam, param); |
||||
} |
||||
|
||||
private static bool IsSameType(TypeReference tr1, TypeReference tr2) |
||||
{ |
||||
if (tr1 == tr2) |
||||
return true; |
||||
if (tr1 == null || tr2 == null) |
||||
return false; |
||||
|
||||
if (tr1.GetType() != tr2.GetType()) |
||||
return false; |
||||
|
||||
if (tr1.Name == tr2.Name && tr1.FullName == tr2.FullName) |
||||
return true; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private static IEnumerable<GenericContext<TypeDefinition>> BaseTypes(TypeDefinition type) |
||||
{ |
||||
return BaseTypes(CreateGenericContext(type)); |
||||
} |
||||
|
||||
private static IEnumerable<GenericContext<TypeDefinition>> BaseTypes(GenericContext<TypeDefinition> type) |
||||
{ |
||||
while (type.Item.BaseType != null) { |
||||
var baseType = type.Item.BaseType; |
||||
var genericBaseType = baseType as GenericInstanceType; |
||||
if (genericBaseType != null) { |
||||
type = new GenericContext<TypeDefinition>(genericBaseType.ResolveOrThrow(), |
||||
genericBaseType.GenericArguments.Select(t => type.ResolveWithContext(t))); |
||||
} else |
||||
type = new GenericContext<TypeDefinition>(baseType.ResolveOrThrow()); |
||||
yield return type; |
||||
} |
||||
} |
||||
|
||||
private static GenericContext<TypeDefinition> CreateGenericContext(TypeDefinition type) |
||||
{ |
||||
return type.HasGenericParameters |
||||
? new GenericContext<TypeDefinition>(type, type.GenericParameters) |
||||
: new GenericContext<TypeDefinition>(type); |
||||
} |
||||
|
||||
struct GenericContext<T> where T : class |
||||
{ |
||||
private static readonly ReadOnlyCollection<TypeReference> Empty = new ReadOnlyCollection<TypeReference>(new List<TypeReference>()); |
||||
private static readonly GenericParameter UnresolvedGenericTypeParameter = |
||||
new DummyGenericParameterProvider(false).DummyParameter; |
||||
private static readonly GenericParameter UnresolvedGenericMethodParameter = |
||||
new DummyGenericParameterProvider(true).DummyParameter; |
||||
|
||||
public readonly T Item; |
||||
public readonly ReadOnlyCollection<TypeReference> TypeArguments; |
||||
|
||||
public GenericContext(T item) |
||||
{ |
||||
if (item == null) |
||||
throw new ArgumentNullException("item"); |
||||
|
||||
Item = item; |
||||
TypeArguments = Empty; |
||||
} |
||||
|
||||
public GenericContext(T item, IEnumerable<TypeReference> typeArguments) |
||||
{ |
||||
if (item == null) |
||||
throw new ArgumentNullException("item"); |
||||
|
||||
Item = item; |
||||
var list = new List<TypeReference>(); |
||||
foreach (var arg in typeArguments) { |
||||
var resolved = arg != null ? arg.Resolve() : arg; |
||||
list.Add(resolved != null ? resolved : arg); |
||||
} |
||||
TypeArguments = new ReadOnlyCollection<TypeReference>(list); |
||||
} |
||||
|
||||
private GenericContext(T item, ReadOnlyCollection<TypeReference> typeArguments) |
||||
{ |
||||
Item = item; |
||||
TypeArguments = typeArguments; |
||||
} |
||||
|
||||
public TypeReference ResolveWithContext(TypeReference type) |
||||
{ |
||||
var genericParameter = type as GenericParameter; |
||||
if (genericParameter != null) |
||||
if (genericParameter.Owner.GenericParameterType == GenericParameterType.Type) |
||||
return this.TypeArguments[genericParameter.Position]; |
||||
else |
||||
return genericParameter.Owner.GenericParameterType == GenericParameterType.Type |
||||
? UnresolvedGenericTypeParameter : UnresolvedGenericMethodParameter; |
||||
var typeSpecification = type as TypeSpecification; |
||||
if (typeSpecification != null) { |
||||
var resolvedElementType = ResolveWithContext(typeSpecification.ElementType); |
||||
return ReplaceElementType(typeSpecification, resolvedElementType); |
||||
} |
||||
return type.ResolveOrThrow(); |
||||
} |
||||
|
||||
private TypeReference ReplaceElementType(TypeSpecification ts, TypeReference newElementType) |
||||
{ |
||||
var arrayType = ts as ArrayType; |
||||
if (arrayType != null) { |
||||
if (newElementType == arrayType.ElementType) |
||||
return arrayType; |
||||
var newArrayType = new ArrayType(newElementType, arrayType.Rank); |
||||
for (int dimension = 0; dimension < arrayType.Rank; dimension++) |
||||
newArrayType.Dimensions[dimension] = arrayType.Dimensions[dimension]; |
||||
return newArrayType; |
||||
} |
||||
var byReferenceType = ts as ByReferenceType; |
||||
if (byReferenceType != null) { |
||||
return new ByReferenceType(newElementType); |
||||
} |
||||
// TODO: should we throw an exception instead calling Resolve method?
|
||||
return ts.ResolveOrThrow(); |
||||
} |
||||
|
||||
public GenericContext<T2> ApplyTo<T2>(T2 item) where T2 : class |
||||
{ |
||||
return new GenericContext<T2>(item, this.TypeArguments); |
||||
} |
||||
|
||||
private class DummyGenericParameterProvider : IGenericParameterProvider |
||||
{ |
||||
readonly Mono.Cecil.GenericParameterType type; |
||||
readonly Mono.Collections.Generic.Collection<GenericParameter> parameters; |
||||
|
||||
public DummyGenericParameterProvider(bool methodTypeParameter) |
||||
{ |
||||
type = methodTypeParameter ? Mono.Cecil.GenericParameterType.Method : |
||||
Mono.Cecil.GenericParameterType.Type; |
||||
parameters = new Mono.Collections.Generic.Collection<GenericParameter>(1); |
||||
parameters.Add(new GenericParameter(this)); |
||||
} |
||||
|
||||
public GenericParameter DummyParameter |
||||
{ |
||||
get { return parameters[0]; } |
||||
} |
||||
|
||||
bool IGenericParameterProvider.HasGenericParameters |
||||
{ |
||||
get { throw new NotImplementedException(); } |
||||
} |
||||
|
||||
bool IGenericParameterProvider.IsDefinition |
||||
{ |
||||
get { throw new NotImplementedException(); } |
||||
} |
||||
|
||||
ModuleDefinition IGenericParameterProvider.Module |
||||
{ |
||||
get { throw new NotImplementedException(); } |
||||
} |
||||
|
||||
Mono.Collections.Generic.Collection<GenericParameter> IGenericParameterProvider.GenericParameters |
||||
{ |
||||
get { return parameters; } |
||||
} |
||||
|
||||
GenericParameterType IGenericParameterProvider.GenericParameterType |
||||
{ |
||||
get { return type; } |
||||
} |
||||
|
||||
MetadataToken IMetadataTokenProvider.MetadataToken |
||||
{ |
||||
get |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
set |
||||
{ |
||||
throw new NotImplementedException(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,301 @@
@@ -0,0 +1,301 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
/// <summary>
|
||||
/// Cecil helper methods.
|
||||
/// </summary>
|
||||
public static class CecilExtensions |
||||
{ |
||||
#region GetPushDelta / GetPopDelta
|
||||
public static int GetPushDelta(this Instruction instruction) |
||||
{ |
||||
OpCode code = instruction.OpCode; |
||||
switch (code.StackBehaviourPush) { |
||||
case StackBehaviour.Push0: |
||||
return 0; |
||||
|
||||
case StackBehaviour.Push1: |
||||
case StackBehaviour.Pushi: |
||||
case StackBehaviour.Pushi8: |
||||
case StackBehaviour.Pushr4: |
||||
case StackBehaviour.Pushr8: |
||||
case StackBehaviour.Pushref: |
||||
return 1; |
||||
|
||||
case StackBehaviour.Push1_push1: |
||||
return 2; |
||||
|
||||
case StackBehaviour.Varpush: |
||||
if (code.FlowControl != FlowControl.Call) |
||||
break; |
||||
|
||||
IMethodSignature method = (IMethodSignature) instruction.Operand; |
||||
return IsVoid (method.ReturnType) ? 0 : 1; |
||||
} |
||||
|
||||
throw new NotSupportedException (); |
||||
} |
||||
|
||||
public static int? GetPopDelta(this Instruction instruction) |
||||
{ |
||||
OpCode code = instruction.OpCode; |
||||
switch (code.StackBehaviourPop) { |
||||
case StackBehaviour.Pop0: |
||||
return 0; |
||||
case StackBehaviour.Popi: |
||||
case StackBehaviour.Popref: |
||||
case StackBehaviour.Pop1: |
||||
return 1; |
||||
|
||||
case StackBehaviour.Pop1_pop1: |
||||
case StackBehaviour.Popi_pop1: |
||||
case StackBehaviour.Popi_popi: |
||||
case StackBehaviour.Popi_popi8: |
||||
case StackBehaviour.Popi_popr4: |
||||
case StackBehaviour.Popi_popr8: |
||||
case StackBehaviour.Popref_pop1: |
||||
case StackBehaviour.Popref_popi: |
||||
return 2; |
||||
|
||||
case StackBehaviour.Popi_popi_popi: |
||||
case StackBehaviour.Popref_popi_popi: |
||||
case StackBehaviour.Popref_popi_popi8: |
||||
case StackBehaviour.Popref_popi_popr4: |
||||
case StackBehaviour.Popref_popi_popr8: |
||||
case StackBehaviour.Popref_popi_popref: |
||||
return 3; |
||||
|
||||
case StackBehaviour.PopAll: |
||||
return null; |
||||
|
||||
case StackBehaviour.Varpop: |
||||
if (code == OpCodes.Ret) |
||||
return null; |
||||
|
||||
if (code.FlowControl != FlowControl.Call) |
||||
break; |
||||
|
||||
IMethodSignature method = (IMethodSignature) instruction.Operand; |
||||
int count = method.HasParameters ? method.Parameters.Count : 0; |
||||
if (code == OpCodes.Calli || (method.HasThis && code != OpCodes.Newobj)) |
||||
++count; |
||||
|
||||
return count; |
||||
} |
||||
|
||||
throw new NotSupportedException (); |
||||
} |
||||
|
||||
public static bool IsVoid(this TypeReference type) |
||||
{ |
||||
while (type is OptionalModifierType || type is RequiredModifierType) |
||||
type = ((TypeSpecification)type).ElementType; |
||||
return type.MetadataType == MetadataType.Void; |
||||
} |
||||
|
||||
public static bool IsValueTypeOrVoid(this TypeReference type) |
||||
{ |
||||
while (type is OptionalModifierType || type is RequiredModifierType) |
||||
type = ((TypeSpecification)type).ElementType; |
||||
if (type is ArrayType) |
||||
return false; |
||||
return type.IsValueType || type.IsVoid(); |
||||
} |
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the (exclusive) end offset of this instruction.
|
||||
/// </summary>
|
||||
public static int GetEndOffset(this Instruction inst) |
||||
{ |
||||
return inst.Offset + inst.GetSize(); |
||||
} |
||||
|
||||
public static string OffsetToString(int offset) |
||||
{ |
||||
return string.Format("IL_{0:x4}", offset); |
||||
} |
||||
|
||||
public static HashSet<MethodDefinition> GetAccessorMethods(this TypeDefinition type) |
||||
{ |
||||
HashSet<MethodDefinition> accessorMethods = new HashSet<MethodDefinition>(); |
||||
foreach (var property in type.Properties) { |
||||
accessorMethods.Add(property.GetMethod); |
||||
accessorMethods.Add(property.SetMethod); |
||||
if (property.HasOtherMethods) { |
||||
foreach (var m in property.OtherMethods) |
||||
accessorMethods.Add(m); |
||||
} |
||||
} |
||||
foreach (EventDefinition ev in type.Events) { |
||||
accessorMethods.Add(ev.AddMethod); |
||||
accessorMethods.Add(ev.RemoveMethod); |
||||
accessorMethods.Add(ev.InvokeMethod); |
||||
if (ev.HasOtherMethods) { |
||||
foreach (var m in ev.OtherMethods) |
||||
accessorMethods.Add(m); |
||||
} |
||||
} |
||||
return accessorMethods; |
||||
} |
||||
|
||||
public static TypeDefinition ResolveWithinSameModule(this TypeReference type) |
||||
{ |
||||
if (type != null && type.GetElementType().Module == type.Module) |
||||
return type.Resolve(); |
||||
else |
||||
return null; |
||||
} |
||||
|
||||
public static FieldDefinition ResolveWithinSameModule(this FieldReference field) |
||||
{ |
||||
if (field != null && field.DeclaringType.GetElementType().Module == field.Module) |
||||
return field.Resolve(); |
||||
else |
||||
return null; |
||||
} |
||||
|
||||
public static MethodDefinition ResolveWithinSameModule(this MethodReference method) |
||||
{ |
||||
if (method != null && method.DeclaringType.GetElementType().Module == method.Module) |
||||
return method.Resolve(); |
||||
else |
||||
return null; |
||||
} |
||||
|
||||
public static TypeDefinition ResolveOrThrow(this TypeReference typeReference) |
||||
{ |
||||
var resolved = typeReference.Resolve(); |
||||
if (resolved == null) |
||||
throw new ReferenceResolvingException(); |
||||
return resolved; |
||||
} |
||||
|
||||
public static bool IsCompilerGenerated(this ICustomAttributeProvider provider) |
||||
{ |
||||
if (provider != null && provider.HasCustomAttributes) { |
||||
foreach (CustomAttribute a in provider.CustomAttributes) { |
||||
if (a.AttributeType.FullName == "System.Runtime.CompilerServices.CompilerGeneratedAttribute") |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public static bool IsCompilerGeneratedOrIsInCompilerGeneratedClass(this IMemberDefinition member) |
||||
{ |
||||
if (member == null) |
||||
return false; |
||||
if (member.IsCompilerGenerated()) |
||||
return true; |
||||
return IsCompilerGeneratedOrIsInCompilerGeneratedClass(member.DeclaringType); |
||||
} |
||||
|
||||
public static bool IsAnonymousType(this TypeReference type) |
||||
{ |
||||
if (type == null) |
||||
return false; |
||||
if (string.IsNullOrEmpty(type.Namespace) && type.Name.StartsWith("<>", StringComparison.Ordinal) && type.Name.Contains("AnonymousType")) { |
||||
TypeDefinition td = type.Resolve(); |
||||
return td != null && td.IsCompilerGenerated(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public static bool ContainsAnonymousType(this TypeReference type) |
||||
{ |
||||
GenericInstanceType git = type as GenericInstanceType; |
||||
if (git != null) { |
||||
if (IsAnonymousType(git)) |
||||
return true; |
||||
for (int i = 0; i < git.GenericArguments.Count; i++) { |
||||
if (git.GenericArguments[i].ContainsAnonymousType()) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
TypeSpecification typeSpec = type as TypeSpecification; |
||||
if (typeSpec != null) |
||||
return typeSpec.ElementType.ContainsAnonymousType(); |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
public static string GetDefaultMemberName(this TypeDefinition type) |
||||
{ |
||||
CustomAttribute attr; |
||||
return type.GetDefaultMemberName(out attr); |
||||
} |
||||
|
||||
public static string GetDefaultMemberName(this TypeDefinition type, out CustomAttribute defaultMemberAttribute) |
||||
{ |
||||
if (type.HasCustomAttributes) |
||||
foreach (CustomAttribute ca in type.CustomAttributes) |
||||
if (ca.Constructor.DeclaringType.Name == "DefaultMemberAttribute" && ca.Constructor.DeclaringType.Namespace == "System.Reflection" |
||||
&& ca.Constructor.FullName == @"System.Void System.Reflection.DefaultMemberAttribute::.ctor(System.String)") { |
||||
defaultMemberAttribute = ca; |
||||
return ca.ConstructorArguments[0].Value as string; |
||||
} |
||||
defaultMemberAttribute = null; |
||||
return null; |
||||
} |
||||
|
||||
public static bool IsIndexer(this PropertyDefinition property) |
||||
{ |
||||
CustomAttribute attr; |
||||
return property.IsIndexer(out attr); |
||||
} |
||||
|
||||
public static bool IsIndexer(this PropertyDefinition property, out CustomAttribute defaultMemberAttribute) |
||||
{ |
||||
defaultMemberAttribute = null; |
||||
if (property.HasParameters) { |
||||
var accessor = property.GetMethod ?? property.SetMethod; |
||||
PropertyDefinition basePropDef = property; |
||||
if (accessor.HasOverrides) { |
||||
// if the property is explicitly implementing an interface, look up the property in the interface:
|
||||
MethodDefinition baseAccessor = accessor.Overrides.First().Resolve(); |
||||
if (baseAccessor != null) { |
||||
foreach (PropertyDefinition baseProp in baseAccessor.DeclaringType.Properties) { |
||||
if (baseProp.GetMethod == baseAccessor || baseProp.SetMethod == baseAccessor) { |
||||
basePropDef = baseProp; |
||||
break; |
||||
} |
||||
} |
||||
} else |
||||
return false; |
||||
} |
||||
CustomAttribute attr; |
||||
var defaultMemberName = basePropDef.DeclaringType.GetDefaultMemberName(out attr); |
||||
if (defaultMemberName == basePropDef.Name) { |
||||
defaultMemberAttribute = attr; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,346 @@
@@ -0,0 +1,346 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.Ast; |
||||
using ICSharpCode.Decompiler.Disassembler; |
||||
using ICSharpCode.Decompiler.ILAst; |
||||
using ICSharpCode.NRefactory.CSharp; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
public enum DecompiledLanguages |
||||
{ |
||||
IL, |
||||
CSharp |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Base class for decompliler classes : AstBuilder & ReflectionDisassembler.
|
||||
/// </summary>
|
||||
public abstract class BaseCodeMappings |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the code mappings.
|
||||
/// <remarks>Key is the metadata token.</remarks>
|
||||
/// </summary>
|
||||
public Dictionary<int, List<MemberMapping>> CodeMappings { get; protected set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the MembeReference that is decompiled (a MethodDefinition, PropertyDefinition etc.)
|
||||
/// <remarks>Key is the metadata token.</remarks>
|
||||
/// </summary>
|
||||
public Dictionary<int, MemberReference> DecompiledMemberReferences { get; protected set; } |
||||
|
||||
/// <summary>
|
||||
/// Create data in the CodeMappings and DecompiledMemberReferences.
|
||||
/// </summary>
|
||||
/// <param name="token">Token of the current method.</param>
|
||||
/// <param name="member">Current member (MethodDefinition, PropertyDefinition, EventDefinition).</param>
|
||||
/// <remarks>The token is used in CodeMappings; member (and its token) is used in DecompiledMemberReferences.</remarks>
|
||||
protected virtual void CreateCodeMappings(int token, MemberReference member) |
||||
{ |
||||
this.CodeMappings.Add(token, new List<MemberMapping>()); |
||||
|
||||
int t = member.MetadataToken.ToInt32(); |
||||
if (!this.DecompiledMemberReferences.ContainsKey(t)) |
||||
this.DecompiledMemberReferences.Add(t, member); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Maps the source code to IL.
|
||||
/// </summary>
|
||||
public sealed class SourceCodeMapping |
||||
{ |
||||
/// <summary>
|
||||
/// Gets or sets the source code line number in the output.
|
||||
/// </summary>
|
||||
public int SourceCodeLine { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets or sets IL Range offset for the source code line. E.g.: 13-19 <-> 135.
|
||||
/// </summary>
|
||||
public ILRange ILInstructionOffset { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the member mapping this source code mapping belongs to.
|
||||
/// </summary>
|
||||
public MemberMapping MemberMapping { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Retrieves the array that contains the IL range and the missing gaps between ranges.
|
||||
/// </summary>
|
||||
/// <returns>The array representation of the step aranges.</returns>
|
||||
public int[] ToArray(bool isMatch) |
||||
{ |
||||
var currentList = new List<ILRange>(); |
||||
|
||||
// add list for the current source code line
|
||||
currentList.AddRange(ILRange.OrderAndJoint(MemberMapping.MemberCodeMappings |
||||
.FindAll(m => m.SourceCodeLine == this.SourceCodeLine) |
||||
.ConvertAll<ILRange>(m => m.ILInstructionOffset))); |
||||
|
||||
if (!isMatch) { |
||||
// add inverted
|
||||
currentList.AddRange(MemberMapping.InvertedList); |
||||
} else { |
||||
// if the current list contains the last mapping, add also the last gap
|
||||
var lastInverted = MemberMapping.InvertedList.LastOrDefault(); |
||||
if (lastInverted != null && lastInverted.From == currentList[currentList.Count - 1].To) |
||||
currentList.Add(lastInverted); |
||||
} |
||||
|
||||
// set the output
|
||||
var resultList = new List<int>(); |
||||
foreach (var element in ILRange.OrderAndJoint(currentList)) { |
||||
resultList.Add(element.From); |
||||
resultList.Add(element.To); |
||||
} |
||||
|
||||
return resultList.ToArray(); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Stores the method information and its source code mappings.
|
||||
/// </summary>
|
||||
public sealed class MemberMapping |
||||
{ |
||||
IEnumerable<ILRange> invertedList; |
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the mapping.
|
||||
/// </summary>
|
||||
public MemberReference MemberReference { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Metadata token of the member.
|
||||
/// </summary>
|
||||
public int MetadataToken { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the code size for the member mapping.
|
||||
/// </summary>
|
||||
public int CodeSize { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the source code mappings.
|
||||
/// </summary>
|
||||
public List<SourceCodeMapping> MemberCodeMappings { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the inverted IL Ranges.<br/>
|
||||
/// E.g.: for (0-9, 11-14, 14-18, 21-25) => (9-11,18-21).
|
||||
/// </summary>
|
||||
/// <returns>IL Range inverted list.</returns>
|
||||
public IEnumerable<ILRange> InvertedList |
||||
{ |
||||
get { |
||||
if (invertedList == null) { |
||||
var list = MemberCodeMappings.ConvertAll<ILRange>( |
||||
s => new ILRange { From = s.ILInstructionOffset.From, To = s.ILInstructionOffset.To }); |
||||
invertedList = ILRange.OrderAndJoint(ILRange.Invert(list, CodeSize)); |
||||
} |
||||
return invertedList; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Code mappings helper class.
|
||||
/// </summary>
|
||||
public static class CodeMappings |
||||
{ |
||||
/// <summary>
|
||||
/// Create code mapping for a method.
|
||||
/// </summary>
|
||||
/// <param name="method">Method to create the mapping for.</param>
|
||||
/// <param name="codeMappings">Source code mapping storage.</param>
|
||||
/// <param name="actualMemberReference">The actual member reference.</param>
|
||||
internal static MemberMapping CreateCodeMapping( |
||||
this MethodDefinition member, |
||||
List<MemberMapping> codeMappings, |
||||
MemberReference actualMemberReference = null) |
||||
{ |
||||
if (member == null || !member.HasBody) |
||||
return null; |
||||
|
||||
if (codeMappings == null) |
||||
return null; |
||||
|
||||
// create IL/CSharp code mappings - used in debugger
|
||||
MemberMapping currentMemberMapping = null; |
||||
|
||||
if (codeMappings.Find(map => map.MetadataToken == member.MetadataToken.ToInt32()) == null) { |
||||
currentMemberMapping = new MemberMapping() { |
||||
MetadataToken = member.MetadataToken.ToInt32(), |
||||
MemberCodeMappings = new List<SourceCodeMapping>(), |
||||
MemberReference = actualMemberReference ?? member, |
||||
CodeSize = member.Body.CodeSize |
||||
}; |
||||
|
||||
codeMappings.Add(currentMemberMapping); |
||||
} |
||||
|
||||
return currentMemberMapping; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets source code mapping and metadata token based on type name and line number.
|
||||
/// </summary>
|
||||
/// <param name="codeMappings">Code mappings storage.</param>
|
||||
/// <param name="typeName">Member reference name.</param>
|
||||
/// <param name="lineNumber">Line number.</param>
|
||||
/// <param name="metadataToken">Metadata token.</param>
|
||||
/// <returns></returns>
|
||||
public static SourceCodeMapping GetInstructionByLineNumber( |
||||
this List<MemberMapping> codeMappings, |
||||
int lineNumber, |
||||
out int metadataToken) |
||||
{ |
||||
if (codeMappings == null) |
||||
throw new ArgumentException("CodeMappings storage must be valid!"); |
||||
|
||||
foreach (var maping in codeMappings) { |
||||
var map = maping.MemberCodeMappings.Find(m => m.SourceCodeLine == lineNumber); |
||||
if (map != null) { |
||||
metadataToken = maping.MetadataToken; |
||||
return map; |
||||
} |
||||
} |
||||
|
||||
metadataToken = 0; |
||||
return null; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets a mapping given a type, a token and an IL offset.
|
||||
/// </summary>
|
||||
/// <param name="codeMappings">Code mappings storage.</param>
|
||||
/// <param name="token">Token.</param>
|
||||
/// <param name="ilOffset">IL offset.</param>
|
||||
/// <param name="isMatch">True, if perfect match.</param>
|
||||
/// <returns>A code mapping.</returns>
|
||||
public static SourceCodeMapping GetInstructionByTokenAndOffset( |
||||
this List<MemberMapping> codeMappings, |
||||
int token, |
||||
int ilOffset, |
||||
out bool isMatch) |
||||
{ |
||||
isMatch = false; |
||||
|
||||
if (codeMappings == null) |
||||
throw new ArgumentNullException("CodeMappings storage must be valid!"); |
||||
|
||||
var maping = codeMappings.Find(m => m.MetadataToken == token); |
||||
|
||||
if (maping == null) |
||||
return null; |
||||
|
||||
// try find an exact match
|
||||
var map = maping.MemberCodeMappings.Find(m => m.ILInstructionOffset.From <= ilOffset && ilOffset < m.ILInstructionOffset.To); |
||||
|
||||
if (map == null) { |
||||
// get the immediate next one
|
||||
map = maping.MemberCodeMappings.Find(m => m.ILInstructionOffset.From > ilOffset); |
||||
isMatch = false; |
||||
if (map == null) |
||||
map = maping.MemberCodeMappings.LastOrDefault(); // get the last
|
||||
|
||||
return map; |
||||
} |
||||
|
||||
isMatch = true; |
||||
return map; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the source code and type name from metadata token and offset.
|
||||
/// </summary>
|
||||
/// <param name="codeMappings">Code mappings storage.</param>
|
||||
/// <param name="token">Metadata token.</param>
|
||||
/// <param name="ilOffset">IL offset.</param>
|
||||
/// <param name="typeName">Type definition.</param>
|
||||
/// <param name="line">Line number.</param>
|
||||
/// <remarks>It is possible to exist to different types from different assemblies with the same metadata token.</remarks>
|
||||
public static bool GetInstructionByTokenAndOffset( |
||||
this List<MemberMapping> codeMappings, |
||||
int token, |
||||
int ilOffset, |
||||
out MemberReference member, |
||||
out int line) |
||||
{ |
||||
member = null; |
||||
line = 0; |
||||
|
||||
if (codeMappings == null) |
||||
throw new ArgumentException("CodeMappings storage must be valid!"); |
||||
|
||||
var mapping = codeMappings.Find(m => m.MetadataToken == token); |
||||
if (mapping == null) |
||||
return false; |
||||
|
||||
var codeMapping = mapping.MemberCodeMappings.Find( |
||||
cm => cm.ILInstructionOffset.From <= ilOffset && ilOffset <= cm.ILInstructionOffset.To - 1); |
||||
if (codeMapping == null) { |
||||
codeMapping = mapping.MemberCodeMappings.Find(cm => cm.ILInstructionOffset.From > ilOffset); |
||||
if (codeMapping == null) { |
||||
codeMapping = mapping.MemberCodeMappings.LastOrDefault(); |
||||
if (codeMapping == null) |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
member = mapping.MemberReference; |
||||
line = codeMapping.SourceCodeLine; |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Decompilation data. Can be used by other applications to store the decompilation data.
|
||||
/// </summary>
|
||||
public class DecompileInformation |
||||
{ |
||||
/// <summary>
|
||||
/// Gets ot sets the code mappings
|
||||
/// </summary>
|
||||
public Dictionary<int, List<MemberMapping>> CodeMappings { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the local variables.
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<int, IEnumerable<ILVariable>> LocalVariables { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the list of MembeReferences that are decompiled (TypeDefinitions, MethodDefinitions, etc)
|
||||
/// </summary>
|
||||
public Dictionary<int, MemberReference> DecompiledMemberReferences { get; set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets (or internal sets) the AST nodes.
|
||||
/// </summary>
|
||||
public IEnumerable<AstNode> AstNodes { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Runtime.Serialization; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
/// <summary>
|
||||
/// Desctiption of DecompilerException.
|
||||
/// </summary>
|
||||
public class DecompilerException : Exception, ISerializable |
||||
{ |
||||
public MethodDefinition DecompiledMethod { get; set; } |
||||
|
||||
public DecompilerException(MethodDefinition decompiledMethod, Exception innerException) |
||||
: base("Error decompiling " + decompiledMethod.FullName + Environment.NewLine, innerException) |
||||
{ |
||||
} |
||||
|
||||
// This constructor is needed for serialization.
|
||||
protected DecompilerException(SerializationInfo info, StreamingContext context) : base(info, context) |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,243 @@
@@ -0,0 +1,243 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.ComponentModel; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
/// <summary>
|
||||
/// Settings for the decompiler.
|
||||
/// </summary>
|
||||
public class DecompilerSettings : INotifyPropertyChanged |
||||
{ |
||||
bool anonymousMethods = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile anonymous methods/lambdas.
|
||||
/// </summary>
|
||||
public bool AnonymousMethods { |
||||
get { return anonymousMethods; } |
||||
set { |
||||
if (anonymousMethods != value) { |
||||
anonymousMethods = value; |
||||
OnPropertyChanged("AnonymousMethods"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool yieldReturn = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile enumerators.
|
||||
/// </summary>
|
||||
public bool YieldReturn { |
||||
get { return yieldReturn; } |
||||
set { |
||||
if (yieldReturn != value) { |
||||
yieldReturn = value; |
||||
OnPropertyChanged("YieldReturn"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool automaticProperties = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile automatic properties
|
||||
/// </summary>
|
||||
public bool AutomaticProperties { |
||||
get { return automaticProperties; } |
||||
set { |
||||
if (automaticProperties != value) { |
||||
automaticProperties = value; |
||||
OnPropertyChanged("AutomaticProperties"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool automaticEvents = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile automatic events
|
||||
/// </summary>
|
||||
public bool AutomaticEvents { |
||||
get { return automaticEvents; } |
||||
set { |
||||
if (automaticEvents != value) { |
||||
automaticEvents = value; |
||||
OnPropertyChanged("AutomaticEvents"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool usingStatement = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile using statements.
|
||||
/// </summary>
|
||||
public bool UsingStatement { |
||||
get { return usingStatement; } |
||||
set { |
||||
if (usingStatement != value) { |
||||
usingStatement = value; |
||||
OnPropertyChanged("UsingStatement"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool forEachStatement = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile foreach statements.
|
||||
/// </summary>
|
||||
public bool ForEachStatement { |
||||
get { return forEachStatement; } |
||||
set { |
||||
if (forEachStatement != value) { |
||||
forEachStatement = value; |
||||
OnPropertyChanged("ForEachStatement"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool lockStatement = true; |
||||
|
||||
/// <summary>
|
||||
/// Decompile lock statements.
|
||||
/// </summary>
|
||||
public bool LockStatement { |
||||
get { return lockStatement; } |
||||
set { |
||||
if (lockStatement != value) { |
||||
lockStatement = value; |
||||
OnPropertyChanged("LockStatement"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool switchStatementOnString = true; |
||||
|
||||
public bool SwitchStatementOnString { |
||||
get { return switchStatementOnString; } |
||||
set { |
||||
if (switchStatementOnString != value) { |
||||
switchStatementOnString = value; |
||||
OnPropertyChanged("SwitchStatementOnString"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool usingDeclarations = true; |
||||
|
||||
public bool UsingDeclarations { |
||||
get { return usingDeclarations; } |
||||
set { |
||||
if (usingDeclarations != value) { |
||||
usingDeclarations = value; |
||||
OnPropertyChanged("UsingDeclarations"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool queryExpressions = true; |
||||
|
||||
public bool QueryExpressions { |
||||
get { return queryExpressions; } |
||||
set { |
||||
if (queryExpressions != value) { |
||||
queryExpressions = value; |
||||
OnPropertyChanged("QueryExpressions"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool fullyQualifyAmbiguousTypeNames = true; |
||||
|
||||
public bool FullyQualifyAmbiguousTypeNames { |
||||
get { return fullyQualifyAmbiguousTypeNames; } |
||||
set { |
||||
if (fullyQualifyAmbiguousTypeNames != value) { |
||||
fullyQualifyAmbiguousTypeNames = value; |
||||
OnPropertyChanged("FullyQualifyAmbiguousTypeNames"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool useDebugSymbols = true; |
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets whether to use variable names from debug symbols, if available.
|
||||
/// </summary>
|
||||
public bool UseDebugSymbols { |
||||
get { return useDebugSymbols; } |
||||
set { |
||||
if (useDebugSymbols != value) { |
||||
useDebugSymbols = value; |
||||
OnPropertyChanged("UseDebugSymbols"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool objectCollectionInitializers = true; |
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets whether to use C# 3.0 object/collection initializers
|
||||
/// </summary>
|
||||
public bool ObjectOrCollectionInitializers { |
||||
get { return objectCollectionInitializers; } |
||||
set { |
||||
if (objectCollectionInitializers != value) { |
||||
objectCollectionInitializers = value; |
||||
OnPropertyChanged("ObjectCollectionInitializers"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool showXmlDocumentation = true; |
||||
|
||||
/// <summary>
|
||||
/// Gets/Sets whether to include XML documentation comments in the decompiled code
|
||||
/// </summary>
|
||||
public bool ShowXmlDocumentation { |
||||
get { return showXmlDocumentation; } |
||||
set { |
||||
if (showXmlDocumentation != value) { |
||||
showXmlDocumentation = value; |
||||
OnPropertyChanged("ShowXmlDocumentation"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged; |
||||
|
||||
protected virtual void OnPropertyChanged(string propertyName) |
||||
{ |
||||
if (PropertyChanged != null) { |
||||
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); |
||||
} |
||||
} |
||||
|
||||
public DecompilerSettings Clone() |
||||
{ |
||||
DecompilerSettings settings = (DecompilerSettings)MemberwiseClone(); |
||||
settings.PropertyChanged = null; |
||||
return settings; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,439 @@
@@ -0,0 +1,439 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Disassembler |
||||
{ |
||||
public enum ILNameSyntax |
||||
{ |
||||
/// <summary>
|
||||
/// class/valuetype + TypeName (built-in types use keyword syntax)
|
||||
/// </summary>
|
||||
Signature, |
||||
/// <summary>
|
||||
/// Like signature, but always refers to type parameters using their position
|
||||
/// </summary>
|
||||
SignatureNoNamedTypeParameters, |
||||
/// <summary>
|
||||
/// [assembly]Full.Type.Name (even for built-in types)
|
||||
/// </summary>
|
||||
TypeName, |
||||
/// <summary>
|
||||
/// Name (but built-in types use keyword syntax)
|
||||
/// </summary>
|
||||
ShortTypeName |
||||
} |
||||
|
||||
public static class DisassemblerHelpers |
||||
{ |
||||
public static void WriteOffsetReference(ITextOutput writer, Instruction instruction) |
||||
{ |
||||
writer.WriteReference(CecilExtensions.OffsetToString(instruction.Offset), instruction); |
||||
} |
||||
|
||||
public static void WriteTo(this ExceptionHandler exceptionHandler, ITextOutput writer) |
||||
{ |
||||
writer.Write("Try "); |
||||
WriteOffsetReference(writer, exceptionHandler.TryStart); |
||||
writer.Write('-'); |
||||
WriteOffsetReference(writer, exceptionHandler.TryEnd); |
||||
writer.Write(' '); |
||||
writer.Write(exceptionHandler.HandlerType.ToString()); |
||||
if (exceptionHandler.FilterStart != null) { |
||||
writer.Write(' '); |
||||
WriteOffsetReference(writer, exceptionHandler.FilterStart); |
||||
writer.Write(" handler "); |
||||
} |
||||
if (exceptionHandler.CatchType != null) { |
||||
writer.Write(' '); |
||||
exceptionHandler.CatchType.WriteTo(writer); |
||||
} |
||||
writer.Write(' '); |
||||
WriteOffsetReference(writer, exceptionHandler.HandlerStart); |
||||
writer.Write('-'); |
||||
WriteOffsetReference(writer, exceptionHandler.HandlerEnd); |
||||
} |
||||
|
||||
public static void WriteTo(this Instruction instruction, ITextOutput writer) |
||||
{ |
||||
writer.WriteDefinition(CecilExtensions.OffsetToString(instruction.Offset), instruction); |
||||
writer.Write(": "); |
||||
writer.WriteReference(instruction.OpCode.Name, instruction.OpCode); |
||||
if (instruction.Operand != null) { |
||||
writer.Write(' '); |
||||
if (instruction.OpCode == OpCodes.Ldtoken) { |
||||
if (instruction.Operand is MethodReference) |
||||
writer.Write("method "); |
||||
else if (instruction.Operand is FieldReference) |
||||
writer.Write("field "); |
||||
} |
||||
WriteOperand(writer, instruction.Operand); |
||||
} |
||||
} |
||||
|
||||
static void WriteLabelList(ITextOutput writer, Instruction[] instructions) |
||||
{ |
||||
writer.Write("("); |
||||
for(int i = 0; i < instructions.Length; i++) { |
||||
if(i != 0) writer.Write(", "); |
||||
WriteOffsetReference(writer, instructions[i]); |
||||
} |
||||
writer.Write(")"); |
||||
} |
||||
|
||||
static string ToInvariantCultureString(object value) |
||||
{ |
||||
IConvertible convertible = value as IConvertible; |
||||
return(null != convertible) |
||||
? convertible.ToString(System.Globalization.CultureInfo.InvariantCulture) |
||||
: value.ToString(); |
||||
} |
||||
|
||||
public static void WriteTo(this MethodReference method, ITextOutput writer) |
||||
{ |
||||
if (method.ExplicitThis) { |
||||
writer.Write("instance explicit "); |
||||
} |
||||
else if (method.HasThis) { |
||||
writer.Write("instance "); |
||||
} |
||||
method.ReturnType.WriteTo(writer, ILNameSyntax.SignatureNoNamedTypeParameters); |
||||
writer.Write(' '); |
||||
if (method.DeclaringType != null) { |
||||
method.DeclaringType.WriteTo(writer, ILNameSyntax.TypeName); |
||||
writer.Write("::"); |
||||
} |
||||
MethodDefinition md = method as MethodDefinition; |
||||
if (md != null && md.IsCompilerControlled) { |
||||
writer.WriteReference(Escape(method.Name + "$PST" + method.MetadataToken.ToInt32().ToString("X8")), method); |
||||
} else { |
||||
writer.WriteReference(Escape(method.Name), method); |
||||
} |
||||
GenericInstanceMethod gim = method as GenericInstanceMethod; |
||||
if (gim != null) { |
||||
writer.Write('<'); |
||||
for (int i = 0; i < gim.GenericArguments.Count; i++) { |
||||
if (i > 0) |
||||
writer.Write(", "); |
||||
gim.GenericArguments[i].WriteTo(writer); |
||||
} |
||||
writer.Write('>'); |
||||
} |
||||
writer.Write("("); |
||||
var parameters = method.Parameters; |
||||
for(int i = 0; i < parameters.Count; ++i) { |
||||
if (i > 0) writer.Write(", "); |
||||
parameters[i].ParameterType.WriteTo(writer, ILNameSyntax.SignatureNoNamedTypeParameters); |
||||
} |
||||
writer.Write(")"); |
||||
} |
||||
|
||||
static void WriteTo(this FieldReference field, ITextOutput writer) |
||||
{ |
||||
field.FieldType.WriteTo(writer, ILNameSyntax.SignatureNoNamedTypeParameters); |
||||
writer.Write(' '); |
||||
field.DeclaringType.WriteTo(writer, ILNameSyntax.TypeName); |
||||
writer.Write("::"); |
||||
writer.WriteReference(Escape(field.Name), field); |
||||
} |
||||
|
||||
static bool IsValidIdentifierCharacter(char c) |
||||
{ |
||||
return c == '_' || c == '$' || c == '@' || c == '?' || c == '`'; |
||||
} |
||||
|
||||
static bool IsValidIdentifier(string identifier) |
||||
{ |
||||
if (string.IsNullOrEmpty(identifier)) |
||||
return false; |
||||
if (!(char.IsLetter(identifier[0]) || IsValidIdentifierCharacter(identifier[0]))) { |
||||
// As a special case, .ctor and .cctor are valid despite starting with a dot
|
||||
return identifier == ".ctor" || identifier == ".cctor"; |
||||
} |
||||
for (int i = 1; i < identifier.Length; i++) { |
||||
if (!(char.IsLetterOrDigit(identifier[i]) || IsValidIdentifierCharacter(identifier[i]) || identifier[i] == '.')) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
static readonly HashSet<string> ilKeywords = BuildKeywordList( |
||||
"abstract", "algorithm", "alignment", "ansi", "any", "arglist", |
||||
"array", "as", "assembly", "assert", "at", "auto", "autochar", "beforefieldinit", |
||||
"blob", "blob_object", "bool", "brnull", "brnull.s", "brzero", "brzero.s", "bstr", |
||||
"bytearray", "byvalstr", "callmostderived", "carray", "catch", "cdecl", "cf", |
||||
"char", "cil", "class", "clsid", "const", "currency", "custom", "date", "decimal", |
||||
"default", "demand", "deny", "endmac", "enum", "error", "explicit", "extends", "extern", |
||||
"false", "famandassem", "family", "famorassem", "fastcall", "fault", "field", "filetime", |
||||
"filter", "final", "finally", "fixed", "float", "float32", "float64", "forwardref", |
||||
"fromunmanaged", "handler", "hidebysig", "hresult", "idispatch", "il", "illegal", |
||||
"implements", "implicitcom", "implicitres", "import", "in", "inheritcheck", "init", |
||||
"initonly", "instance", "int", "int16", "int32", "int64", "int8", "interface", "internalcall", |
||||
"iunknown", "lasterr", "lcid", "linkcheck", "literal", "localloc", "lpstr", "lpstruct", "lptstr", |
||||
"lpvoid", "lpwstr", "managed", "marshal", "method", "modopt", "modreq", "native", "nested", |
||||
"newslot", "noappdomain", "noinlining", "nomachine", "nomangle", "nometadata", "noncasdemand", |
||||
"noncasinheritance", "noncaslinkdemand", "noprocess", "not", "not_in_gc_heap", "notremotable", |
||||
"notserialized", "null", "nullref", "object", "objectref", "opt", "optil", "out", |
||||
"permitonly", "pinned", "pinvokeimpl", "prefix1", "prefix2", "prefix3", "prefix4", "prefix5", "prefix6", |
||||
"prefix7", "prefixref", "prejitdeny", "prejitgrant", "preservesig", "private", "privatescope", "protected", |
||||
"public", "record", "refany", "reqmin", "reqopt", "reqrefuse", "reqsecobj", "request", "retval", |
||||
"rtspecialname", "runtime", "safearray", "sealed", "sequential", "serializable", "special", "specialname", |
||||
"static", "stdcall", "storage", "stored_object", "stream", "streamed_object", "string", "struct", |
||||
"synchronized", "syschar", "sysstring", "tbstr", "thiscall", "tls", "to", "true", "typedref", |
||||
"unicode", "unmanaged", "unmanagedexp", "unsigned", "unused", "userdefined", "value", "valuetype", |
||||
"vararg", "variant", "vector", "virtual", "void", "wchar", "winapi", "with", "wrapper", |
||||
|
||||
// These are not listed as keywords in spec, but ILAsm treats them as such
|
||||
"property", "type", "flags", "callconv", "strict" |
||||
); |
||||
|
||||
static HashSet<string> BuildKeywordList(params string[] keywords) |
||||
{ |
||||
HashSet<string> s = new HashSet<string>(keywords); |
||||
foreach (var field in typeof(OpCodes).GetFields()) { |
||||
s.Add(((OpCode)field.GetValue(null)).Name); |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
public static string Escape(string identifier) |
||||
{ |
||||
if (IsValidIdentifier(identifier) && !ilKeywords.Contains(identifier)) { |
||||
return identifier; |
||||
} else { |
||||
// The ECMA specification says that ' inside SQString should be ecaped using an octal escape sequence,
|
||||
// but we follow Microsoft's ILDasm and use \'.
|
||||
return "'" + NRefactory.CSharp.OutputVisitor.ConvertString(identifier).Replace("'", "\\'") + "'"; |
||||
} |
||||
} |
||||
|
||||
public static void WriteTo(this TypeReference type, ITextOutput writer, ILNameSyntax syntax = ILNameSyntax.Signature) |
||||
{ |
||||
ILNameSyntax syntaxForElementTypes = syntax == ILNameSyntax.SignatureNoNamedTypeParameters ? syntax : ILNameSyntax.Signature; |
||||
if (type is PinnedType) { |
||||
((PinnedType)type).ElementType.WriteTo(writer, syntaxForElementTypes); |
||||
writer.Write(" pinned"); |
||||
} else if (type is ArrayType) { |
||||
ArrayType at = (ArrayType)type; |
||||
at.ElementType.WriteTo(writer, syntaxForElementTypes); |
||||
writer.Write('['); |
||||
writer.Write(string.Join(", ", at.Dimensions)); |
||||
writer.Write(']'); |
||||
} else if (type is GenericParameter) { |
||||
writer.Write('!'); |
||||
if (((GenericParameter)type).Owner.GenericParameterType == GenericParameterType.Method) |
||||
writer.Write('!'); |
||||
if (string.IsNullOrEmpty(type.Name) || type.Name[0] == '!' || syntax == ILNameSyntax.SignatureNoNamedTypeParameters) |
||||
writer.Write(((GenericParameter)type).Position.ToString()); |
||||
else |
||||
writer.Write(Escape(type.Name)); |
||||
} else if (type is ByReferenceType) { |
||||
((ByReferenceType)type).ElementType.WriteTo(writer, syntaxForElementTypes); |
||||
writer.Write('&'); |
||||
} else if (type is PointerType) { |
||||
((PointerType)type).ElementType.WriteTo(writer, syntaxForElementTypes); |
||||
writer.Write('*'); |
||||
} else if (type is GenericInstanceType) { |
||||
type.GetElementType().WriteTo(writer, syntaxForElementTypes); |
||||
writer.Write('<'); |
||||
var arguments = ((GenericInstanceType)type).GenericArguments; |
||||
for (int i = 0; i < arguments.Count; i++) { |
||||
if (i > 0) |
||||
writer.Write(", "); |
||||
arguments[i].WriteTo(writer, syntaxForElementTypes); |
||||
} |
||||
writer.Write('>'); |
||||
} else if (type is OptionalModifierType) { |
||||
((OptionalModifierType)type).ElementType.WriteTo(writer, syntax); |
||||
writer.Write(" modopt("); |
||||
((OptionalModifierType)type).ModifierType.WriteTo(writer, ILNameSyntax.TypeName); |
||||
writer.Write(") "); |
||||
} else if (type is RequiredModifierType) { |
||||
((RequiredModifierType)type).ElementType.WriteTo(writer, syntax); |
||||
writer.Write(" modreq("); |
||||
((RequiredModifierType)type).ModifierType.WriteTo(writer, ILNameSyntax.TypeName); |
||||
writer.Write(") "); |
||||
} else { |
||||
string name = PrimitiveTypeName(type.FullName); |
||||
if (syntax == ILNameSyntax.ShortTypeName) { |
||||
if (name != null) |
||||
writer.Write(name); |
||||
else |
||||
writer.WriteReference(Escape(type.Name), type); |
||||
} else if ((syntax == ILNameSyntax.Signature || syntax == ILNameSyntax.SignatureNoNamedTypeParameters) && name != null) { |
||||
writer.Write(name); |
||||
} else { |
||||
if (syntax == ILNameSyntax.Signature || syntax == ILNameSyntax.SignatureNoNamedTypeParameters) |
||||
writer.Write(type.IsValueType ? "valuetype " : "class "); |
||||
|
||||
if (type.DeclaringType != null) { |
||||
type.DeclaringType.WriteTo(writer, ILNameSyntax.TypeName); |
||||
writer.Write('/'); |
||||
writer.WriteReference(Escape(type.Name), type); |
||||
} else { |
||||
if (!type.IsDefinition && type.Scope != null && !(type is TypeSpecification)) |
||||
writer.Write("[{0}]", Escape(type.Scope.Name)); |
||||
writer.WriteReference(Escape(type.FullName), type); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static void WriteOperand(ITextOutput writer, object operand) |
||||
{ |
||||
if (operand == null) |
||||
throw new ArgumentNullException("operand"); |
||||
|
||||
Instruction targetInstruction = operand as Instruction; |
||||
if (targetInstruction != null) { |
||||
WriteOffsetReference(writer, targetInstruction); |
||||
return; |
||||
} |
||||
|
||||
Instruction[] targetInstructions = operand as Instruction[]; |
||||
if (targetInstructions != null) { |
||||
WriteLabelList(writer, targetInstructions); |
||||
return; |
||||
} |
||||
|
||||
VariableReference variableRef = operand as VariableReference; |
||||
if (variableRef != null) { |
||||
if (string.IsNullOrEmpty(variableRef.Name)) |
||||
writer.WriteReference(variableRef.Index.ToString(), variableRef); |
||||
else |
||||
writer.WriteReference(Escape(variableRef.Name), variableRef); |
||||
return; |
||||
} |
||||
|
||||
ParameterReference paramRef = operand as ParameterReference; |
||||
if (paramRef != null) { |
||||
if (string.IsNullOrEmpty(paramRef.Name)) |
||||
writer.WriteReference(paramRef.Index.ToString(), paramRef); |
||||
else |
||||
writer.WriteReference(Escape(paramRef.Name), paramRef); |
||||
return; |
||||
} |
||||
|
||||
MethodReference methodRef = operand as MethodReference; |
||||
if (methodRef != null) { |
||||
methodRef.WriteTo(writer); |
||||
return; |
||||
} |
||||
|
||||
TypeReference typeRef = operand as TypeReference; |
||||
if (typeRef != null) { |
||||
typeRef.WriteTo(writer, ILNameSyntax.TypeName); |
||||
return; |
||||
} |
||||
|
||||
FieldReference fieldRef = operand as FieldReference; |
||||
if (fieldRef != null) { |
||||
fieldRef.WriteTo(writer); |
||||
return; |
||||
} |
||||
|
||||
string s = operand as string; |
||||
if (s != null) { |
||||
writer.Write("\"" + NRefactory.CSharp.OutputVisitor.ConvertString(s) + "\""); |
||||
} else if (operand is char) { |
||||
writer.Write(((int)(char)operand).ToString()); |
||||
} else if (operand is float) { |
||||
float val = (float)operand; |
||||
if (val == 0) { |
||||
writer.Write("0.0"); |
||||
} else if (float.IsInfinity(val) || float.IsNaN(val)) { |
||||
byte[] data = BitConverter.GetBytes(val); |
||||
writer.Write('('); |
||||
for (int i = 0; i < data.Length; i++) { |
||||
if (i > 0) |
||||
writer.Write(' '); |
||||
writer.Write(data[i].ToString("X2")); |
||||
} |
||||
writer.Write(')'); |
||||
} else { |
||||
writer.Write(val.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); |
||||
} |
||||
} else if (operand is double) { |
||||
double val = (double)operand; |
||||
if (val == 0) { |
||||
writer.Write("0.0"); |
||||
} else if (double.IsInfinity(val) || double.IsNaN(val)) { |
||||
byte[] data = BitConverter.GetBytes(val); |
||||
writer.Write('('); |
||||
for (int i = 0; i < data.Length; i++) { |
||||
if (i > 0) |
||||
writer.Write(' '); |
||||
writer.Write(data[i].ToString("X2")); |
||||
} |
||||
writer.Write(')'); |
||||
} else { |
||||
writer.Write(val.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); |
||||
} |
||||
} else if (operand is bool) { |
||||
writer.Write((bool)operand ? "true" : "false"); |
||||
} else { |
||||
s = ToInvariantCultureString(operand); |
||||
writer.Write(s); |
||||
} |
||||
} |
||||
|
||||
public static string PrimitiveTypeName(string fullName) |
||||
{ |
||||
switch (fullName) { |
||||
case "System.SByte": |
||||
return "int8"; |
||||
case "System.Int16": |
||||
return "int16"; |
||||
case "System.Int32": |
||||
return "int32"; |
||||
case "System.Int64": |
||||
return "int64"; |
||||
case "System.Byte": |
||||
return "uint8"; |
||||
case "System.UInt16": |
||||
return "uint16"; |
||||
case "System.UInt32": |
||||
return "uint32"; |
||||
case "System.UInt64": |
||||
return "uint64"; |
||||
case "System.Single": |
||||
return "float32"; |
||||
case "System.Double": |
||||
return "float64"; |
||||
case "System.Void": |
||||
return "void"; |
||||
case "System.Boolean": |
||||
return "bool"; |
||||
case "System.String": |
||||
return "string"; |
||||
case "System.Char": |
||||
return "char"; |
||||
case "System.Object": |
||||
return "object"; |
||||
case "System.IntPtr": |
||||
return "native int"; |
||||
default: |
||||
return null; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,228 @@
@@ -0,0 +1,228 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using ICSharpCode.Decompiler.FlowAnalysis; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Disassembler |
||||
{ |
||||
/// <summary>
|
||||
/// Specifies the type of an IL structure.
|
||||
/// </summary>
|
||||
public enum ILStructureType |
||||
{ |
||||
/// <summary>
|
||||
/// The root block of the method
|
||||
/// </summary>
|
||||
Root, |
||||
/// <summary>
|
||||
/// A nested control structure representing a loop.
|
||||
/// </summary>
|
||||
Loop, |
||||
/// <summary>
|
||||
/// A nested control structure representing a try block.
|
||||
/// </summary>
|
||||
Try, |
||||
/// <summary>
|
||||
/// A nested control structure representing a catch, finally, or fault block.
|
||||
/// </summary>
|
||||
Handler, |
||||
/// <summary>
|
||||
/// A nested control structure representing an exception filter block.
|
||||
/// </summary>
|
||||
Filter |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// An IL structure.
|
||||
/// </summary>
|
||||
public class ILStructure |
||||
{ |
||||
public readonly ILStructureType Type; |
||||
|
||||
/// <summary>
|
||||
/// Start position of the structure.
|
||||
/// </summary>
|
||||
public readonly int StartOffset; |
||||
|
||||
/// <summary>
|
||||
/// End position of the structure. (exclusive)
|
||||
/// </summary>
|
||||
public readonly int EndOffset; |
||||
|
||||
/// <summary>
|
||||
/// The exception handler associated with the Try, Filter or Handler block.
|
||||
/// </summary>
|
||||
public readonly ExceptionHandler ExceptionHandler; |
||||
|
||||
/// <summary>
|
||||
/// The loop's entry point.
|
||||
/// </summary>
|
||||
public readonly Instruction LoopEntryPoint; |
||||
|
||||
/// <summary>
|
||||
/// The list of child structures.
|
||||
/// </summary>
|
||||
public readonly List<ILStructure> Children = new List<ILStructure>(); |
||||
|
||||
public ILStructure(MethodBody body) |
||||
: this(ILStructureType.Root, 0, body.CodeSize) |
||||
{ |
||||
// Build the tree of exception structures:
|
||||
for (int i = 0; i < body.ExceptionHandlers.Count; i++) { |
||||
ExceptionHandler eh = body.ExceptionHandlers[i]; |
||||
if (!body.ExceptionHandlers.Take(i).Any(oldEh => oldEh.TryStart == eh.TryStart && oldEh.TryEnd == eh.TryEnd)) |
||||
AddNestedStructure(new ILStructure(ILStructureType.Try, eh.TryStart.Offset, eh.TryEnd.Offset, eh)); |
||||
if (eh.HandlerType == ExceptionHandlerType.Filter) |
||||
AddNestedStructure(new ILStructure(ILStructureType.Filter, eh.FilterStart.Offset, eh.HandlerStart.Offset, eh)); |
||||
AddNestedStructure(new ILStructure(ILStructureType.Handler, eh.HandlerStart.Offset, eh.HandlerEnd == null ? body.CodeSize : eh.HandlerEnd.Offset, eh)); |
||||
} |
||||
// Very simple loop detection: look for backward branches
|
||||
List<KeyValuePair<Instruction, Instruction>> allBranches = FindAllBranches(body); |
||||
// We go through the branches in reverse so that we find the biggest possible loop boundary first (think loops with "continue;")
|
||||
for (int i = allBranches.Count - 1; i >= 0; i--) { |
||||
int loopEnd = allBranches[i].Key.GetEndOffset(); |
||||
int loopStart = allBranches[i].Value.Offset; |
||||
if (loopStart < loopEnd) { |
||||
// We found a backward branch. This is a potential loop.
|
||||
// Check that is has only one entry point:
|
||||
Instruction entryPoint = null; |
||||
|
||||
// entry point is first instruction in loop if prev inst isn't an unconditional branch
|
||||
Instruction prev = allBranches[i].Value.Previous; |
||||
if (prev != null && !OpCodeInfo.IsUnconditionalBranch(prev.OpCode)) |
||||
entryPoint = allBranches[i].Value; |
||||
|
||||
bool multipleEntryPoints = false; |
||||
foreach (var pair in allBranches) { |
||||
if (pair.Key.Offset < loopStart || pair.Key.Offset >= loopEnd) { |
||||
if (loopStart <= pair.Value.Offset && pair.Value.Offset < loopEnd) { |
||||
// jump from outside the loop into the loop
|
||||
if (entryPoint == null) |
||||
entryPoint = pair.Value; |
||||
else if (pair.Value != entryPoint) |
||||
multipleEntryPoints = true; |
||||
} |
||||
} |
||||
} |
||||
if (!multipleEntryPoints) { |
||||
AddNestedStructure(new ILStructure(ILStructureType.Loop, loopStart, loopEnd, entryPoint)); |
||||
} |
||||
} |
||||
} |
||||
SortChildren(); |
||||
} |
||||
|
||||
public ILStructure(ILStructureType type, int startOffset, int endOffset, ExceptionHandler handler = null) |
||||
{ |
||||
Debug.Assert(startOffset < endOffset); |
||||
this.Type = type; |
||||
this.StartOffset = startOffset; |
||||
this.EndOffset = endOffset; |
||||
this.ExceptionHandler = handler; |
||||
} |
||||
|
||||
public ILStructure(ILStructureType type, int startOffset, int endOffset, Instruction loopEntryPoint) |
||||
{ |
||||
Debug.Assert(startOffset < endOffset); |
||||
this.Type = type; |
||||
this.StartOffset = startOffset; |
||||
this.EndOffset = endOffset; |
||||
this.LoopEntryPoint = loopEntryPoint; |
||||
} |
||||
|
||||
bool AddNestedStructure(ILStructure newStructure) |
||||
{ |
||||
// special case: don't consider the loop-like structure of "continue;" statements to be nested loops
|
||||
if (this.Type == ILStructureType.Loop && newStructure.Type == ILStructureType.Loop && newStructure.StartOffset == this.StartOffset) |
||||
return false; |
||||
|
||||
// use <= for end-offset comparisons because both end and EndOffset are exclusive
|
||||
Debug.Assert(StartOffset <= newStructure.StartOffset && newStructure.EndOffset <= EndOffset); |
||||
foreach (ILStructure child in this.Children) { |
||||
if (child.StartOffset <= newStructure.StartOffset && newStructure.EndOffset <= child.EndOffset) { |
||||
return child.AddNestedStructure(newStructure); |
||||
} else if (!(child.EndOffset <= newStructure.StartOffset || newStructure.EndOffset <= child.StartOffset)) { |
||||
// child and newStructure overlap
|
||||
if (!(newStructure.StartOffset <= child.StartOffset && child.EndOffset <= newStructure.EndOffset)) { |
||||
// Invalid nesting, can't build a tree. -> Don't add the new structure.
|
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
// Move existing structures into the new structure:
|
||||
for (int i = 0; i < this.Children.Count; i++) { |
||||
ILStructure child = this.Children[i]; |
||||
if (newStructure.StartOffset <= child.StartOffset && child.EndOffset <= newStructure.EndOffset) { |
||||
this.Children.RemoveAt(i--); |
||||
newStructure.Children.Add(child); |
||||
} |
||||
} |
||||
// Add the structure here:
|
||||
this.Children.Add(newStructure); |
||||
return true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Finds all branches. Returns list of source offset->target offset mapping.
|
||||
/// Multiple entries for the same source offset are possible (switch statements).
|
||||
/// The result is sorted by source offset.
|
||||
/// </summary>
|
||||
List<KeyValuePair<Instruction, Instruction>> FindAllBranches(MethodBody body) |
||||
{ |
||||
var result = new List<KeyValuePair<Instruction, Instruction>>(); |
||||
foreach (Instruction inst in body.Instructions) { |
||||
switch (inst.OpCode.OperandType) { |
||||
case OperandType.InlineBrTarget: |
||||
case OperandType.ShortInlineBrTarget: |
||||
result.Add(new KeyValuePair<Instruction, Instruction>(inst, (Instruction)inst.Operand)); |
||||
break; |
||||
case OperandType.InlineSwitch: |
||||
foreach (Instruction target in (Instruction[])inst.Operand) |
||||
result.Add(new KeyValuePair<Instruction, Instruction>(inst, target)); |
||||
break; |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
void SortChildren() |
||||
{ |
||||
Children.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset)); |
||||
foreach (ILStructure child in Children) |
||||
child.SortChildren(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the innermost structure containing the specified offset.
|
||||
/// </summary>
|
||||
public ILStructure GetInnermost(int offset) |
||||
{ |
||||
Debug.Assert(StartOffset <= offset && offset < EndOffset); |
||||
foreach (ILStructure child in this.Children) { |
||||
if (child.StartOffset <= offset && offset < child.EndOffset) |
||||
return child.GetInnermost(offset); |
||||
} |
||||
return this; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,237 @@
@@ -0,0 +1,237 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
|
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.FlowAnalysis; |
||||
using ICSharpCode.Decompiler.ILAst; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.Disassembler |
||||
{ |
||||
/// <summary>
|
||||
/// Disassembles a method body.
|
||||
/// </summary>
|
||||
public sealed class MethodBodyDisassembler |
||||
{ |
||||
readonly ITextOutput output; |
||||
readonly bool detectControlStructure; |
||||
readonly CancellationToken cancellationToken; |
||||
|
||||
public MethodBodyDisassembler(ITextOutput output, bool detectControlStructure, CancellationToken cancellationToken) |
||||
{ |
||||
if (output == null) |
||||
throw new ArgumentNullException("output"); |
||||
this.output = output; |
||||
this.detectControlStructure = detectControlStructure; |
||||
this.cancellationToken = cancellationToken; |
||||
} |
||||
|
||||
public void Disassemble(MethodBody body, MemberMapping methodMapping) |
||||
{ |
||||
// start writing IL code
|
||||
MethodDefinition method = body.Method; |
||||
output.WriteLine("// Method begins at RVA 0x{0:x4}", method.RVA); |
||||
output.WriteLine("// Code size {0} (0x{0:x})", body.CodeSize); |
||||
output.WriteLine(".maxstack {0}", body.MaxStackSize); |
||||
if (method.DeclaringType.Module.Assembly.EntryPoint == method) |
||||
output.WriteLine (".entrypoint"); |
||||
|
||||
if (method.Body.HasVariables) { |
||||
output.Write(".locals "); |
||||
if (method.Body.InitLocals) |
||||
output.Write("init "); |
||||
output.WriteLine("("); |
||||
output.Indent(); |
||||
foreach (var v in method.Body.Variables) { |
||||
output.WriteDefinition("[" + v.Index + "] ", v); |
||||
v.VariableType.WriteTo(output); |
||||
if (!string.IsNullOrEmpty(v.Name)) { |
||||
output.Write(' '); |
||||
output.Write(DisassemblerHelpers.Escape(v.Name)); |
||||
} |
||||
if (v.Index + 1 < method.Body.Variables.Count) |
||||
output.Write(','); |
||||
output.WriteLine(); |
||||
} |
||||
output.Unindent(); |
||||
output.WriteLine(")"); |
||||
} |
||||
output.WriteLine(); |
||||
|
||||
if (detectControlStructure && body.Instructions.Count > 0) { |
||||
Instruction inst = body.Instructions[0]; |
||||
HashSet<int> branchTargets = GetBranchTargets(body.Instructions); |
||||
WriteStructureBody(new ILStructure(body), branchTargets, ref inst, methodMapping, method.Body.CodeSize); |
||||
} else { |
||||
foreach (var inst in method.Body.Instructions) { |
||||
inst.WriteTo(output); |
||||
|
||||
if (methodMapping != null) { |
||||
// add IL code mappings - used in debugger
|
||||
methodMapping.MemberCodeMappings.Add( |
||||
new SourceCodeMapping() { |
||||
SourceCodeLine = output.CurrentLine, |
||||
ILInstructionOffset = new ILRange { From = inst.Offset, To = inst.Next == null ? method.Body.CodeSize : inst.Next.Offset }, |
||||
MemberMapping = methodMapping |
||||
}); |
||||
} |
||||
|
||||
output.WriteLine(); |
||||
} |
||||
if (method.Body.HasExceptionHandlers) { |
||||
output.WriteLine(); |
||||
foreach (var eh in method.Body.ExceptionHandlers) { |
||||
eh.WriteTo(output); |
||||
output.WriteLine(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
HashSet<int> GetBranchTargets(IEnumerable<Instruction> instructions) |
||||
{ |
||||
HashSet<int> branchTargets = new HashSet<int>(); |
||||
foreach (var inst in instructions) { |
||||
Instruction target = inst.Operand as Instruction; |
||||
if (target != null) |
||||
branchTargets.Add(target.Offset); |
||||
Instruction[] targets = inst.Operand as Instruction[]; |
||||
if (targets != null) |
||||
foreach (Instruction t in targets) |
||||
branchTargets.Add(t.Offset); |
||||
} |
||||
return branchTargets; |
||||
} |
||||
|
||||
void WriteStructureHeader(ILStructure s) |
||||
{ |
||||
switch (s.Type) { |
||||
case ILStructureType.Loop: |
||||
output.Write("// loop start"); |
||||
if (s.LoopEntryPoint != null) { |
||||
output.Write(" (head: "); |
||||
DisassemblerHelpers.WriteOffsetReference(output, s.LoopEntryPoint); |
||||
output.Write(')'); |
||||
} |
||||
output.WriteLine(); |
||||
break; |
||||
case ILStructureType.Try: |
||||
output.WriteLine(".try"); |
||||
output.WriteLine("{"); |
||||
break; |
||||
case ILStructureType.Handler: |
||||
switch (s.ExceptionHandler.HandlerType) { |
||||
case Mono.Cecil.Cil.ExceptionHandlerType.Catch: |
||||
case Mono.Cecil.Cil.ExceptionHandlerType.Filter: |
||||
output.Write("catch"); |
||||
if (s.ExceptionHandler.CatchType != null) { |
||||
output.Write(' '); |
||||
s.ExceptionHandler.CatchType.WriteTo(output, ILNameSyntax.TypeName); |
||||
} |
||||
output.WriteLine(); |
||||
break; |
||||
case Mono.Cecil.Cil.ExceptionHandlerType.Finally: |
||||
output.WriteLine("finally"); |
||||
break; |
||||
case Mono.Cecil.Cil.ExceptionHandlerType.Fault: |
||||
output.WriteLine("fault"); |
||||
break; |
||||
default: |
||||
throw new NotSupportedException(); |
||||
} |
||||
output.WriteLine("{"); |
||||
break; |
||||
case ILStructureType.Filter: |
||||
output.WriteLine("filter"); |
||||
output.WriteLine("{"); |
||||
break; |
||||
default: |
||||
throw new NotSupportedException(); |
||||
} |
||||
output.Indent(); |
||||
} |
||||
|
||||
void WriteStructureBody(ILStructure s, HashSet<int> branchTargets, ref Instruction inst, MemberMapping currentMethodMapping, int codeSize) |
||||
{ |
||||
bool isFirstInstructionInStructure = true; |
||||
bool prevInstructionWasBranch = false; |
||||
int childIndex = 0; |
||||
while (inst != null && inst.Offset < s.EndOffset) { |
||||
int offset = inst.Offset; |
||||
if (childIndex < s.Children.Count && s.Children[childIndex].StartOffset <= offset && offset < s.Children[childIndex].EndOffset) { |
||||
ILStructure child = s.Children[childIndex++]; |
||||
WriteStructureHeader(child); |
||||
WriteStructureBody(child, branchTargets, ref inst, currentMethodMapping, codeSize); |
||||
WriteStructureFooter(child); |
||||
} else { |
||||
if (!isFirstInstructionInStructure && (prevInstructionWasBranch || branchTargets.Contains(offset))) { |
||||
output.WriteLine(); // put an empty line after branches, and in front of branch targets
|
||||
} |
||||
inst.WriteTo(output); |
||||
|
||||
// add IL code mappings - used in debugger
|
||||
if (currentMethodMapping != null) { |
||||
currentMethodMapping.MemberCodeMappings.Add( |
||||
new SourceCodeMapping() { |
||||
SourceCodeLine = output.CurrentLine, |
||||
ILInstructionOffset = new ILRange { From = inst.Offset, To = inst.Next == null ? codeSize : inst.Next.Offset }, |
||||
MemberMapping = currentMethodMapping |
||||
}); |
||||
} |
||||
|
||||
output.WriteLine(); |
||||
|
||||
prevInstructionWasBranch = inst.OpCode.FlowControl == FlowControl.Branch |
||||
|| inst.OpCode.FlowControl == FlowControl.Cond_Branch |
||||
|| inst.OpCode.FlowControl == FlowControl.Return |
||||
|| inst.OpCode.FlowControl == FlowControl.Throw; |
||||
|
||||
inst = inst.Next; |
||||
} |
||||
isFirstInstructionInStructure = false; |
||||
} |
||||
} |
||||
|
||||
void WriteStructureFooter(ILStructure s) |
||||
{ |
||||
output.Unindent(); |
||||
switch (s.Type) { |
||||
case ILStructureType.Loop: |
||||
output.WriteLine("// end loop"); |
||||
break; |
||||
case ILStructureType.Try: |
||||
output.WriteLine("} // end .try"); |
||||
break; |
||||
case ILStructureType.Handler: |
||||
output.WriteLine("} // end handler"); |
||||
break; |
||||
case ILStructureType.Filter: |
||||
output.WriteLine("} // end filter"); |
||||
break; |
||||
default: |
||||
throw new NotSupportedException(); |
||||
} |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,78 @@
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Describes the type of a control flow egde.
|
||||
/// </summary>
|
||||
public enum JumpType |
||||
{ |
||||
/// <summary>
|
||||
/// A regular control flow edge.
|
||||
/// </summary>
|
||||
Normal, |
||||
/// <summary>
|
||||
/// Jump to exception handler (an exception occurred)
|
||||
/// </summary>
|
||||
JumpToExceptionHandler, |
||||
/// <summary>
|
||||
/// Jump from try block to leave target:
|
||||
/// This is not a real jump, as the finally handler is executed first!
|
||||
/// </summary>
|
||||
LeaveTry, |
||||
/// <summary>
|
||||
/// Jump at endfinally (to any of the potential leave targets).
|
||||
/// For any leave-instruction, control flow enters the finally block - the edge to the leave target (LeaveTry) is not a real control flow edge.
|
||||
/// EndFinally edges are inserted at the end of the finally block, jumping to any of the targets of the leave instruction.
|
||||
/// This edge type is only used when copying of finally blocks is disabled (with copying, a normal deterministic edge is used at each copy of the endfinally node).
|
||||
/// </summary>
|
||||
EndFinally |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Represents an edge in the control flow graph, pointing from Source to Target.
|
||||
/// </summary>
|
||||
public sealed class ControlFlowEdge |
||||
{ |
||||
public readonly ControlFlowNode Source; |
||||
public readonly ControlFlowNode Target; |
||||
public readonly JumpType Type; |
||||
|
||||
public ControlFlowEdge(ControlFlowNode source, ControlFlowNode target, JumpType type) |
||||
{ |
||||
this.Source = source; |
||||
this.Target = target; |
||||
this.Type = type; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
switch (Type) { |
||||
case JumpType.Normal: |
||||
return "#" + Target.BlockIndex; |
||||
case JumpType.JumpToExceptionHandler: |
||||
return "e:#" + Target.BlockIndex; |
||||
default: |
||||
return Type.ToString() + ":#" + Target.BlockIndex; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,191 @@
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
|
||||
using ICSharpCode.NRefactory.Utils; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Contains the control flow graph.
|
||||
/// </summary>
|
||||
/// <remarks>Use ControlFlowGraph builder to create instances of the ControlFlowGraph.</remarks>
|
||||
public sealed class ControlFlowGraph |
||||
{ |
||||
readonly ReadOnlyCollection<ControlFlowNode> nodes; |
||||
|
||||
public ControlFlowNode EntryPoint { |
||||
get { return nodes[0]; } |
||||
} |
||||
|
||||
public ControlFlowNode RegularExit { |
||||
get { return nodes[1]; } |
||||
} |
||||
|
||||
public ControlFlowNode ExceptionalExit { |
||||
get { return nodes[2]; } |
||||
} |
||||
|
||||
public ReadOnlyCollection<ControlFlowNode> Nodes { |
||||
get { return nodes; } |
||||
} |
||||
|
||||
internal ControlFlowGraph(ControlFlowNode[] nodes) |
||||
{ |
||||
this.nodes = new ReadOnlyCollection<ControlFlowNode>(nodes); |
||||
Debug.Assert(EntryPoint.NodeType == ControlFlowNodeType.EntryPoint); |
||||
Debug.Assert(RegularExit.NodeType == ControlFlowNodeType.RegularExit); |
||||
Debug.Assert(ExceptionalExit.NodeType == ControlFlowNodeType.ExceptionalExit); |
||||
} |
||||
|
||||
public GraphVizGraph ExportGraph() |
||||
{ |
||||
GraphVizGraph graph = new GraphVizGraph(); |
||||
foreach (ControlFlowNode node in nodes) { |
||||
graph.AddNode(new GraphVizNode(node.BlockIndex) { label = node.ToString(), shape = "box" }); |
||||
} |
||||
foreach (ControlFlowNode node in nodes) { |
||||
foreach (ControlFlowEdge edge in node.Outgoing) { |
||||
GraphVizEdge e = new GraphVizEdge(edge.Source.BlockIndex, edge.Target.BlockIndex); |
||||
switch (edge.Type) { |
||||
case JumpType.Normal: |
||||
break; |
||||
case JumpType.LeaveTry: |
||||
case JumpType.EndFinally: |
||||
e.color = "red"; |
||||
break; |
||||
default: |
||||
e.color = "gray"; |
||||
//e.constraint = false;
|
||||
break; |
||||
} |
||||
graph.AddEdge(e); |
||||
} |
||||
if (node.ImmediateDominator != null) { |
||||
graph.AddEdge(new GraphVizEdge(node.ImmediateDominator.BlockIndex, node.BlockIndex) { color = "green", constraint = false }); |
||||
} |
||||
} |
||||
return graph; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Resets "Visited" to false for all nodes in this graph.
|
||||
/// </summary>
|
||||
public void ResetVisited() |
||||
{ |
||||
foreach (ControlFlowNode node in nodes) { |
||||
node.Visited = false; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Computes the dominator tree.
|
||||
/// </summary>
|
||||
public void ComputeDominance(CancellationToken cancellationToken = default(CancellationToken)) |
||||
{ |
||||
// A Simple, Fast Dominance Algorithm
|
||||
// Keith D. Cooper, Timothy J. Harvey and Ken Kennedy
|
||||
|
||||
EntryPoint.ImmediateDominator = EntryPoint; |
||||
bool changed = true; |
||||
while (changed) { |
||||
changed = false; |
||||
ResetVisited(); |
||||
|
||||
cancellationToken.ThrowIfCancellationRequested(); |
||||
|
||||
// for all nodes b except the entry point
|
||||
EntryPoint.TraversePreOrder( |
||||
b => b.Successors, |
||||
b => { |
||||
if (b != EntryPoint) { |
||||
ControlFlowNode newIdom = b.Predecessors.First(block => block.Visited && block != b); |
||||
// for all other predecessors p of b
|
||||
foreach (ControlFlowNode p in b.Predecessors) { |
||||
if (p != b && p.ImmediateDominator != null) { |
||||
newIdom = FindCommonDominator(p, newIdom); |
||||
} |
||||
} |
||||
if (b.ImmediateDominator != newIdom) { |
||||
b.ImmediateDominator = newIdom; |
||||
changed = true; |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
EntryPoint.ImmediateDominator = null; |
||||
foreach (ControlFlowNode node in nodes) { |
||||
if (node.ImmediateDominator != null) |
||||
node.ImmediateDominator.DominatorTreeChildren.Add(node); |
||||
} |
||||
} |
||||
|
||||
static ControlFlowNode FindCommonDominator(ControlFlowNode b1, ControlFlowNode b2) |
||||
{ |
||||
// Here we could use the postorder numbers to get rid of the hashset, see "A Simple, Fast Dominance Algorithm"
|
||||
HashSet<ControlFlowNode> path1 = new HashSet<ControlFlowNode>(); |
||||
while (b1 != null && path1.Add(b1)) |
||||
b1 = b1.ImmediateDominator; |
||||
while (b2 != null) { |
||||
if (path1.Contains(b2)) |
||||
return b2; |
||||
else |
||||
b2 = b2.ImmediateDominator; |
||||
} |
||||
throw new Exception("No common dominator found!"); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Computes dominance frontiers.
|
||||
/// This method requires that the dominator tree is already computed!
|
||||
/// </summary>
|
||||
public void ComputeDominanceFrontier() |
||||
{ |
||||
ResetVisited(); |
||||
|
||||
EntryPoint.TraversePostOrder( |
||||
b => b.DominatorTreeChildren, |
||||
n => { |
||||
//logger.WriteLine("Calculating dominance frontier for " + n.Name);
|
||||
n.DominanceFrontier = new HashSet<ControlFlowNode>(); |
||||
// DF_local computation
|
||||
foreach (ControlFlowNode succ in n.Successors) { |
||||
if (succ.ImmediateDominator != n) { |
||||
//logger.WriteLine(" local: " + succ.Name);
|
||||
n.DominanceFrontier.Add(succ); |
||||
} |
||||
} |
||||
// DF_up computation
|
||||
foreach (ControlFlowNode child in n.DominatorTreeChildren) { |
||||
foreach (ControlFlowNode p in child.DominanceFrontier) { |
||||
if (p.ImmediateDominator != n) { |
||||
//logger.WriteLine(" DF_up: " + p.Name + " (child=" + child.Name);
|
||||
n.DominanceFrontier.Add(p); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,439 @@
@@ -0,0 +1,439 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Constructs the Control Flow Graph from a Cecil method body.
|
||||
/// </summary>
|
||||
public sealed class ControlFlowGraphBuilder |
||||
{ |
||||
public static ControlFlowGraph Build(MethodBody methodBody) |
||||
{ |
||||
return new ControlFlowGraphBuilder(methodBody).Build(); |
||||
} |
||||
|
||||
// This option controls how finally blocks are handled:
|
||||
// false means that the endfinally instruction will jump to any of the leave targets (EndFinally edge type).
|
||||
// true means that a copy of the whole finally block is created for each leave target. In this case, each endfinally node will be connected with the leave
|
||||
// target using a normal edge.
|
||||
bool copyFinallyBlocks = false; |
||||
|
||||
MethodBody methodBody; |
||||
int[] offsets; // array index = instruction index; value = IL offset
|
||||
bool[] hasIncomingJumps; // array index = instruction index
|
||||
List<ControlFlowNode> nodes = new List<ControlFlowNode>(); |
||||
ControlFlowNode entryPoint; |
||||
ControlFlowNode regularExit; |
||||
ControlFlowNode exceptionalExit; |
||||
|
||||
private ControlFlowGraphBuilder(MethodBody methodBody) |
||||
{ |
||||
this.methodBody = methodBody; |
||||
offsets = methodBody.Instructions.Select(i => i.Offset).ToArray(); |
||||
hasIncomingJumps = new bool[methodBody.Instructions.Count]; |
||||
|
||||
entryPoint = new ControlFlowNode(0, 0, ControlFlowNodeType.EntryPoint); |
||||
nodes.Add(entryPoint); |
||||
regularExit = new ControlFlowNode(1, -1, ControlFlowNodeType.RegularExit); |
||||
nodes.Add(regularExit); |
||||
exceptionalExit = new ControlFlowNode(2, -1, ControlFlowNodeType.ExceptionalExit); |
||||
nodes.Add(exceptionalExit); |
||||
Debug.Assert(nodes.Count == 3); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Determines the index of the instruction (for use with the hasIncomingJumps array)
|
||||
/// </summary>
|
||||
int GetInstructionIndex(Instruction inst) |
||||
{ |
||||
int index = Array.BinarySearch(offsets, inst.Offset); |
||||
Debug.Assert(index >= 0); |
||||
return index; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Builds the ControlFlowGraph.
|
||||
/// </summary>
|
||||
public ControlFlowGraph Build() |
||||
{ |
||||
CalculateHasIncomingJumps(); |
||||
CreateNodes(); |
||||
CreateRegularControlFlow(); |
||||
CreateExceptionalControlFlow(); |
||||
if (copyFinallyBlocks) |
||||
CopyFinallyBlocksIntoLeaveEdges(); |
||||
else |
||||
TransformLeaveEdges(); |
||||
return new ControlFlowGraph(nodes.ToArray()); |
||||
} |
||||
|
||||
#region Step 1: calculate which instructions are the targets of jump instructions.
|
||||
void CalculateHasIncomingJumps() |
||||
{ |
||||
foreach (Instruction inst in methodBody.Instructions) { |
||||
if (inst.OpCode.OperandType == OperandType.InlineBrTarget || inst.OpCode.OperandType == OperandType.ShortInlineBrTarget) { |
||||
hasIncomingJumps[GetInstructionIndex((Instruction)inst.Operand)] = true; |
||||
} else if (inst.OpCode.OperandType == OperandType.InlineSwitch) { |
||||
foreach (Instruction i in (Instruction[])inst.Operand) |
||||
hasIncomingJumps[GetInstructionIndex(i)] = true; |
||||
} |
||||
} |
||||
foreach (ExceptionHandler eh in methodBody.ExceptionHandlers) { |
||||
if (eh.FilterStart != null) { |
||||
hasIncomingJumps[GetInstructionIndex(eh.FilterStart)] = true; |
||||
} |
||||
hasIncomingJumps[GetInstructionIndex(eh.HandlerStart)] = true; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Step 2: create nodes
|
||||
void CreateNodes() |
||||
{ |
||||
// Step 2a: find basic blocks and create nodes for them
|
||||
for (int i = 0; i < methodBody.Instructions.Count; i++) { |
||||
Instruction blockStart = methodBody.Instructions[i]; |
||||
ExceptionHandler blockStartEH = FindInnermostExceptionHandler(blockStart.Offset); |
||||
// try and see how big we can make that block:
|
||||
for (; i + 1 < methodBody.Instructions.Count; i++) { |
||||
Instruction inst = methodBody.Instructions[i]; |
||||
if (IsBranch(inst.OpCode) || CanThrowException(inst.OpCode)) |
||||
break; |
||||
if (hasIncomingJumps[i + 1]) |
||||
break; |
||||
if (inst.Next != null) { |
||||
// ensure that blocks never contain instructions from different try blocks
|
||||
ExceptionHandler instEH = FindInnermostExceptionHandler(inst.Next.Offset); |
||||
if (instEH != blockStartEH) |
||||
break; |
||||
} |
||||
} |
||||
|
||||
nodes.Add(new ControlFlowNode(nodes.Count, blockStart, methodBody.Instructions[i])); |
||||
} |
||||
// Step 2b: Create special nodes for the exception handling constructs
|
||||
foreach (ExceptionHandler handler in methodBody.ExceptionHandlers) { |
||||
if (handler.HandlerType == ExceptionHandlerType.Filter) |
||||
throw new NotSupportedException(); |
||||
ControlFlowNode endFinallyOrFaultNode = null; |
||||
if (handler.HandlerType == ExceptionHandlerType.Finally || handler.HandlerType == ExceptionHandlerType.Fault) { |
||||
endFinallyOrFaultNode = new ControlFlowNode(nodes.Count, handler.HandlerEnd.Offset, ControlFlowNodeType.EndFinallyOrFault); |
||||
nodes.Add(endFinallyOrFaultNode); |
||||
} |
||||
nodes.Add(new ControlFlowNode(nodes.Count, handler, endFinallyOrFaultNode)); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Step 3: create edges for the normal flow of control (assuming no exceptions thrown)
|
||||
void CreateRegularControlFlow() |
||||
{ |
||||
CreateEdge(entryPoint, methodBody.Instructions[0], JumpType.Normal); |
||||
foreach (ControlFlowNode node in nodes) { |
||||
if (node.End != null) { |
||||
// create normal edges from one instruction to the next
|
||||
if (!OpCodeInfo.IsUnconditionalBranch(node.End.OpCode)) |
||||
CreateEdge(node, node.End.Next, JumpType.Normal); |
||||
|
||||
// create edges for branch instructions
|
||||
if (node.End.OpCode.OperandType == OperandType.InlineBrTarget || node.End.OpCode.OperandType == OperandType.ShortInlineBrTarget) { |
||||
if (node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S) { |
||||
var handlerBlock = FindInnermostHandlerBlock(node.End.Offset); |
||||
if (handlerBlock.NodeType == ControlFlowNodeType.FinallyOrFaultHandler) |
||||
CreateEdge(node, (Instruction)node.End.Operand, JumpType.LeaveTry); |
||||
else |
||||
CreateEdge(node, (Instruction)node.End.Operand, JumpType.Normal); |
||||
} else { |
||||
CreateEdge(node, (Instruction)node.End.Operand, JumpType.Normal); |
||||
} |
||||
} else if (node.End.OpCode.OperandType == OperandType.InlineSwitch) { |
||||
foreach (Instruction i in (Instruction[])node.End.Operand) |
||||
CreateEdge(node, i, JumpType.Normal); |
||||
} |
||||
|
||||
// create edges for return instructions
|
||||
if (node.End.OpCode.FlowControl == FlowControl.Return) { |
||||
switch (node.End.OpCode.Code) { |
||||
case Code.Ret: |
||||
CreateEdge(node, regularExit, JumpType.Normal); |
||||
break; |
||||
case Code.Endfinally: |
||||
ControlFlowNode handlerBlock = FindInnermostHandlerBlock(node.End.Offset); |
||||
if (handlerBlock.EndFinallyOrFaultNode == null) |
||||
throw new InvalidProgramException("Found endfinally in block " + handlerBlock); |
||||
CreateEdge(node, handlerBlock.EndFinallyOrFaultNode, JumpType.Normal); |
||||
break; |
||||
default: |
||||
throw new NotSupportedException(node.End.OpCode.ToString()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Step 4: create edges for the exceptional control flow (from instructions that might throw, to the innermost containing exception handler)
|
||||
void CreateExceptionalControlFlow() |
||||
{ |
||||
foreach (ControlFlowNode node in nodes) { |
||||
if (node.End != null && CanThrowException(node.End.OpCode)) { |
||||
CreateEdge(node, FindInnermostExceptionHandlerNode(node.End.Offset), JumpType.JumpToExceptionHandler); |
||||
} |
||||
if (node.ExceptionHandler != null) { |
||||
if (node.EndFinallyOrFaultNode != null) { |
||||
// For Fault and Finally blocks, create edge from "EndFinally" to next exception handler.
|
||||
// This represents the exception bubbling up after finally block was executed.
|
||||
CreateEdge(node.EndFinallyOrFaultNode, FindParentExceptionHandlerNode(node), JumpType.JumpToExceptionHandler); |
||||
} else { |
||||
// For Catch blocks, create edge from "CatchHandler" block (at beginning) to next exception handler.
|
||||
// This represents the exception bubbling up because it did not match the type of the catch block.
|
||||
CreateEdge(node, FindParentExceptionHandlerNode(node), JumpType.JumpToExceptionHandler); |
||||
} |
||||
CreateEdge(node, node.ExceptionHandler.HandlerStart, JumpType.Normal); |
||||
} |
||||
} |
||||
} |
||||
|
||||
ExceptionHandler FindInnermostExceptionHandler(int instructionOffsetInTryBlock) |
||||
{ |
||||
foreach (ExceptionHandler h in methodBody.ExceptionHandlers) { |
||||
if (h.TryStart.Offset <= instructionOffsetInTryBlock && instructionOffsetInTryBlock < h.TryEnd.Offset) { |
||||
return h; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
ControlFlowNode FindInnermostExceptionHandlerNode(int instructionOffsetInTryBlock) |
||||
{ |
||||
ExceptionHandler h = FindInnermostExceptionHandler(instructionOffsetInTryBlock); |
||||
if (h != null) |
||||
return nodes.Single(n => n.ExceptionHandler == h && n.CopyFrom == null); |
||||
else |
||||
return exceptionalExit; |
||||
} |
||||
|
||||
ControlFlowNode FindInnermostHandlerBlock(int instructionOffset) |
||||
{ |
||||
foreach (ExceptionHandler h in methodBody.ExceptionHandlers) { |
||||
if (h.TryStart.Offset <= instructionOffset && instructionOffset < h.TryEnd.Offset |
||||
|| h.HandlerStart.Offset <= instructionOffset && instructionOffset < h.HandlerEnd.Offset) |
||||
{ |
||||
return nodes.Single(n => n.ExceptionHandler == h && n.CopyFrom == null); |
||||
} |
||||
} |
||||
return exceptionalExit; |
||||
} |
||||
|
||||
ControlFlowNode FindParentExceptionHandlerNode(ControlFlowNode exceptionHandler) |
||||
{ |
||||
Debug.Assert(exceptionHandler.NodeType == ControlFlowNodeType.CatchHandler |
||||
|| exceptionHandler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); |
||||
int offset = exceptionHandler.ExceptionHandler.TryStart.Offset; |
||||
for (int i = exceptionHandler.BlockIndex + 1; i < nodes.Count; i++) { |
||||
ExceptionHandler h = nodes[i].ExceptionHandler; |
||||
if (h != null && h.TryStart.Offset <= offset && offset < h.TryEnd.Offset) |
||||
return nodes[i]; |
||||
} |
||||
return exceptionalExit; |
||||
} |
||||
#endregion
|
||||
|
||||
#region Step 5a: replace LeaveTry edges with EndFinally edges
|
||||
// this is used only for copyFinallyBlocks==false; see Step 5b otherwise
|
||||
void TransformLeaveEdges() |
||||
{ |
||||
for (int i = nodes.Count - 1; i >= 0; i--) { |
||||
ControlFlowNode node = nodes[i]; |
||||
if (node.End != null && node.Outgoing.Count == 1 && node.Outgoing[0].Type == JumpType.LeaveTry) { |
||||
Debug.Assert(node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S); |
||||
|
||||
ControlFlowNode target = node.Outgoing[0].Target; |
||||
// remove the edge
|
||||
target.Incoming.Remove(node.Outgoing[0]); |
||||
node.Outgoing.Clear(); |
||||
|
||||
ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); |
||||
Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); |
||||
|
||||
CreateEdge(node, handler, JumpType.Normal); |
||||
CreateEdge(handler.EndFinallyOrFaultNode, target, JumpType.EndFinally); |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Step 5b: copy finally blocks into the LeaveTry edges
|
||||
void CopyFinallyBlocksIntoLeaveEdges() |
||||
{ |
||||
// We need to process try-finally blocks inside-out.
|
||||
// We'll do that by going through all instructions in reverse order
|
||||
for (int i = nodes.Count - 1; i >= 0; i--) { |
||||
ControlFlowNode node = nodes[i]; |
||||
if (node.End != null && node.Outgoing.Count == 1 && node.Outgoing[0].Type == JumpType.LeaveTry) { |
||||
Debug.Assert(node.End.OpCode == OpCodes.Leave || node.End.OpCode == OpCodes.Leave_S); |
||||
|
||||
ControlFlowNode target = node.Outgoing[0].Target; |
||||
// remove the edge
|
||||
target.Incoming.Remove(node.Outgoing[0]); |
||||
node.Outgoing.Clear(); |
||||
|
||||
ControlFlowNode handler = FindInnermostExceptionHandlerNode(node.End.Offset); |
||||
Debug.Assert(handler.NodeType == ControlFlowNodeType.FinallyOrFaultHandler); |
||||
|
||||
ControlFlowNode copy = CopyFinallySubGraph(handler, handler.EndFinallyOrFaultNode, target); |
||||
CreateEdge(node, copy, JumpType.Normal); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of all nodes pointing to 'end' and replaces those references with references to 'newEnd'.
|
||||
/// Nodes pointing to the copied node are copied recursively to update those references, too.
|
||||
/// This recursion stops at 'start'. The modified version of start is returned.
|
||||
/// </summary>
|
||||
ControlFlowNode CopyFinallySubGraph(ControlFlowNode start, ControlFlowNode end, ControlFlowNode newEnd) |
||||
{ |
||||
return new CopyFinallySubGraphLogic(this, start, end, newEnd).CopyFinallySubGraph(); |
||||
} |
||||
|
||||
class CopyFinallySubGraphLogic |
||||
{ |
||||
readonly ControlFlowGraphBuilder builder; |
||||
readonly Dictionary<ControlFlowNode, ControlFlowNode> oldToNew = new Dictionary<ControlFlowNode, ControlFlowNode>(); |
||||
readonly ControlFlowNode start; |
||||
readonly ControlFlowNode end; |
||||
readonly ControlFlowNode newEnd; |
||||
|
||||
public CopyFinallySubGraphLogic(ControlFlowGraphBuilder builder, ControlFlowNode start, ControlFlowNode end, ControlFlowNode newEnd) |
||||
{ |
||||
this.builder = builder; |
||||
this.start = start; |
||||
this.end = end; |
||||
this.newEnd = newEnd; |
||||
} |
||||
|
||||
internal ControlFlowNode CopyFinallySubGraph() |
||||
{ |
||||
foreach (ControlFlowNode n in end.Predecessors) { |
||||
CollectNodes(n); |
||||
} |
||||
foreach (var pair in oldToNew) |
||||
ReconstructEdges(pair.Key, pair.Value); |
||||
return GetNew(start); |
||||
} |
||||
|
||||
void CollectNodes(ControlFlowNode node) |
||||
{ |
||||
if (node == end || node == newEnd) |
||||
throw new InvalidOperationException("unexpected cycle involving finally construct"); |
||||
if (!oldToNew.ContainsKey(node)) { |
||||
int newBlockIndex = builder.nodes.Count; |
||||
ControlFlowNode copy; |
||||
switch (node.NodeType) { |
||||
case ControlFlowNodeType.Normal: |
||||
copy = new ControlFlowNode(newBlockIndex, node.Start, node.End); |
||||
break; |
||||
case ControlFlowNodeType.FinallyOrFaultHandler: |
||||
copy = new ControlFlowNode(newBlockIndex, node.ExceptionHandler, node.EndFinallyOrFaultNode); |
||||
break; |
||||
default: |
||||
// other nodes shouldn't occur when copying finally blocks
|
||||
throw new NotSupportedException(node.NodeType.ToString()); |
||||
} |
||||
copy.CopyFrom = node; |
||||
builder.nodes.Add(copy); |
||||
oldToNew.Add(node, copy); |
||||
|
||||
if (node != start) { |
||||
foreach (ControlFlowNode n in node.Predecessors) { |
||||
CollectNodes(n); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void ReconstructEdges(ControlFlowNode oldNode, ControlFlowNode newNode) |
||||
{ |
||||
foreach (ControlFlowEdge oldEdge in oldNode.Outgoing) { |
||||
builder.CreateEdge(newNode, GetNew(oldEdge.Target), oldEdge.Type); |
||||
} |
||||
} |
||||
|
||||
ControlFlowNode GetNew(ControlFlowNode oldNode) |
||||
{ |
||||
if (oldNode == end) |
||||
return newEnd; |
||||
ControlFlowNode newNode; |
||||
if (oldToNew.TryGetValue(oldNode, out newNode)) |
||||
return newNode; |
||||
return oldNode; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region CreateEdge methods
|
||||
void CreateEdge(ControlFlowNode fromNode, Instruction toInstruction, JumpType type) |
||||
{ |
||||
CreateEdge(fromNode, nodes.Single(n => n.Start == toInstruction), type); |
||||
} |
||||
|
||||
void CreateEdge(ControlFlowNode fromNode, ControlFlowNode toNode, JumpType type) |
||||
{ |
||||
ControlFlowEdge edge = new ControlFlowEdge(fromNode, toNode, type); |
||||
fromNode.Outgoing.Add(edge); |
||||
toNode.Incoming.Add(edge); |
||||
} |
||||
#endregion
|
||||
|
||||
#region OpCode info
|
||||
static bool CanThrowException(OpCode opcode) |
||||
{ |
||||
if (opcode.OpCodeType == OpCodeType.Prefix) |
||||
return false; |
||||
return OpCodeInfo.Get(opcode).CanThrow; |
||||
} |
||||
|
||||
static bool IsBranch(OpCode opcode) |
||||
{ |
||||
if (opcode.OpCodeType == OpCodeType.Prefix) |
||||
return false; |
||||
switch (opcode.FlowControl) { |
||||
case FlowControl.Cond_Branch: |
||||
case FlowControl.Branch: |
||||
case FlowControl.Throw: |
||||
case FlowControl.Return: |
||||
return true; |
||||
case FlowControl.Next: |
||||
case FlowControl.Call: |
||||
return false; |
||||
default: |
||||
throw new NotSupportedException(opcode.FlowControl.ToString()); |
||||
} |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,298 @@
@@ -0,0 +1,298 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.Disassembler; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Type of the control flow node
|
||||
/// </summary>
|
||||
public enum ControlFlowNodeType |
||||
{ |
||||
/// <summary>
|
||||
/// A normal node represents a basic block.
|
||||
/// </summary>
|
||||
Normal, |
||||
/// <summary>
|
||||
/// The entry point of the method.
|
||||
/// </summary>
|
||||
EntryPoint, |
||||
/// <summary>
|
||||
/// The exit point of the method (every ret instruction branches to this node)
|
||||
/// </summary>
|
||||
RegularExit, |
||||
/// <summary>
|
||||
/// This node represents leaving a method irregularly by throwing an exception.
|
||||
/// </summary>
|
||||
ExceptionalExit, |
||||
/// <summary>
|
||||
/// This node is used as a header for exception handler blocks.
|
||||
/// </summary>
|
||||
CatchHandler, |
||||
/// <summary>
|
||||
/// This node is used as a header for finally blocks and fault blocks.
|
||||
/// Every leave instruction in the try block leads to the handler of the containing finally block;
|
||||
/// and exceptional control flow also leads to this handler.
|
||||
/// </summary>
|
||||
FinallyOrFaultHandler, |
||||
/// <summary>
|
||||
/// This node is used as footer for finally blocks and fault blocks.
|
||||
/// Depending on the "copyFinallyBlocks" option used when creating the graph, it is connected with all leave targets using
|
||||
/// EndFinally edges (when not copying); or with a specific leave target using a normal edge (when copying).
|
||||
/// For fault blocks, an exception edge is used to represent the "re-throwing" of the exception.
|
||||
/// </summary>
|
||||
EndFinallyOrFault |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Represents a block in the control flow graph.
|
||||
/// </summary>
|
||||
public sealed class ControlFlowNode |
||||
{ |
||||
/// <summary>
|
||||
/// Index of this node in the ControlFlowGraph.Nodes collection.
|
||||
/// </summary>
|
||||
public readonly int BlockIndex; |
||||
|
||||
/// <summary>
|
||||
/// Gets the IL offset of this node.
|
||||
/// </summary>
|
||||
public readonly int Offset; |
||||
|
||||
/// <summary>
|
||||
/// Type of the node.
|
||||
/// </summary>
|
||||
public readonly ControlFlowNodeType NodeType; |
||||
|
||||
/// <summary>
|
||||
/// If this node is a FinallyOrFaultHandler node, this field points to the corresponding EndFinallyOrFault node.
|
||||
/// Otherwise, this field is null.
|
||||
/// </summary>
|
||||
public readonly ControlFlowNode EndFinallyOrFaultNode; |
||||
|
||||
/// <summary>
|
||||
/// Visited flag, used in various algorithms.
|
||||
/// Before using it in your algorithm, reset it to false by calling ControlFlowGraph.ResetVisited();
|
||||
/// </summary>
|
||||
public bool Visited; |
||||
|
||||
/// <summary>
|
||||
/// Gets whether this node is reachable. Requires that dominance is computed!
|
||||
/// </summary>
|
||||
public bool IsReachable { |
||||
get { return ImmediateDominator != null || NodeType == ControlFlowNodeType.EntryPoint; } |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Signalizes that this node is a copy of another node.
|
||||
/// </summary>
|
||||
public ControlFlowNode CopyFrom { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// Gets the immediate dominator (the parent in the dominator tree).
|
||||
/// Null if dominance has not been calculated; or if the node is unreachable.
|
||||
/// </summary>
|
||||
public ControlFlowNode ImmediateDominator { get; internal set; } |
||||
|
||||
/// <summary>
|
||||
/// List of children in the dominator tree.
|
||||
/// </summary>
|
||||
public readonly List<ControlFlowNode> DominatorTreeChildren = new List<ControlFlowNode>(); |
||||
|
||||
/// <summary>
|
||||
/// The dominance frontier of this node.
|
||||
/// This is the set of nodes for which this node dominates a predecessor, but which are not strictly dominated by this node.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// b.DominanceFrontier = { y in CFG; (exists p in predecessors(y): b dominates p) and not (b strictly dominates y)}
|
||||
/// </remarks>
|
||||
public HashSet<ControlFlowNode> DominanceFrontier; |
||||
|
||||
/// <summary>
|
||||
/// Start of code block represented by this node. Only set for nodetype == Normal.
|
||||
/// </summary>
|
||||
public readonly Instruction Start; |
||||
|
||||
/// <summary>
|
||||
/// End of the code block represented by this node. Only set for nodetype == Normal.
|
||||
/// The end is exclusive, the end instruction itself does not belong to this block.
|
||||
/// </summary>
|
||||
public readonly Instruction End; |
||||
|
||||
/// <summary>
|
||||
/// Gets the exception handler associated with this node.
|
||||
/// Only set for nodetype == CatchHandler or nodetype == FinallyOrFaultHandler.
|
||||
/// </summary>
|
||||
public readonly ExceptionHandler ExceptionHandler; |
||||
|
||||
/// <summary>
|
||||
/// List of incoming control flow edges.
|
||||
/// </summary>
|
||||
public readonly List<ControlFlowEdge> Incoming = new List<ControlFlowEdge>(); |
||||
|
||||
/// <summary>
|
||||
/// List of outgoing control flow edges.
|
||||
/// </summary>
|
||||
public readonly List<ControlFlowEdge> Outgoing = new List<ControlFlowEdge>(); |
||||
|
||||
/// <summary>
|
||||
/// Any user data
|
||||
/// </summary>
|
||||
public object UserData; |
||||
|
||||
internal ControlFlowNode(int blockIndex, int offset, ControlFlowNodeType nodeType) |
||||
{ |
||||
this.BlockIndex = blockIndex; |
||||
this.Offset = offset; |
||||
this.NodeType = nodeType; |
||||
} |
||||
|
||||
internal ControlFlowNode(int blockIndex, Instruction start, Instruction end) |
||||
{ |
||||
if (start == null) |
||||
throw new ArgumentNullException("start"); |
||||
if (end == null) |
||||
throw new ArgumentNullException("end"); |
||||
this.BlockIndex = blockIndex; |
||||
this.NodeType = ControlFlowNodeType.Normal; |
||||
this.Start = start; |
||||
this.End = end; |
||||
this.Offset = start.Offset; |
||||
} |
||||
|
||||
internal ControlFlowNode(int blockIndex, ExceptionHandler exceptionHandler, ControlFlowNode endFinallyOrFaultNode) |
||||
{ |
||||
this.BlockIndex = blockIndex; |
||||
this.NodeType = endFinallyOrFaultNode != null ? ControlFlowNodeType.FinallyOrFaultHandler : ControlFlowNodeType.CatchHandler; |
||||
this.ExceptionHandler = exceptionHandler; |
||||
this.EndFinallyOrFaultNode = endFinallyOrFaultNode; |
||||
Debug.Assert((exceptionHandler.HandlerType == ExceptionHandlerType.Finally || exceptionHandler.HandlerType == ExceptionHandlerType.Fault) == (endFinallyOrFaultNode != null)); |
||||
this.Offset = exceptionHandler.HandlerStart.Offset; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets all predecessors (=sources of incoming edges)
|
||||
/// </summary>
|
||||
public IEnumerable<ControlFlowNode> Predecessors { |
||||
get { |
||||
return Incoming.Select(e => e.Source); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets all successors (=targets of outgoing edges)
|
||||
/// </summary>
|
||||
public IEnumerable<ControlFlowNode> Successors { |
||||
get { |
||||
return Outgoing.Select(e => e.Target); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets all instructions in this node.
|
||||
/// Returns an empty list for special nodes that don't have any instructions.
|
||||
/// </summary>
|
||||
public IEnumerable<Instruction> Instructions { |
||||
get { |
||||
Instruction inst = Start; |
||||
if (inst != null) { |
||||
yield return inst; |
||||
while (inst != End) { |
||||
inst = inst.Next; |
||||
yield return inst; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void TraversePreOrder(Func<ControlFlowNode, IEnumerable<ControlFlowNode>> children, Action<ControlFlowNode> visitAction) |
||||
{ |
||||
if (Visited) |
||||
return; |
||||
Visited = true; |
||||
visitAction(this); |
||||
foreach (ControlFlowNode t in children(this)) |
||||
t.TraversePreOrder(children, visitAction); |
||||
} |
||||
|
||||
public void TraversePostOrder(Func<ControlFlowNode, IEnumerable<ControlFlowNode>> children, Action<ControlFlowNode> visitAction) |
||||
{ |
||||
if (Visited) |
||||
return; |
||||
Visited = true; |
||||
foreach (ControlFlowNode t in children(this)) |
||||
t.TraversePostOrder(children, visitAction); |
||||
visitAction(this); |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringWriter writer = new StringWriter(); |
||||
switch (NodeType) { |
||||
case ControlFlowNodeType.Normal: |
||||
int endOffset = End.GetEndOffset(); |
||||
writer.Write("Block #{0}: IL_{1:x4} to IL_{2:x4}", BlockIndex, Start.Offset, endOffset); |
||||
break; |
||||
case ControlFlowNodeType.CatchHandler: |
||||
case ControlFlowNodeType.FinallyOrFaultHandler: |
||||
writer.Write("Block #{0}: {1}: ", BlockIndex, NodeType); |
||||
Disassembler.DisassemblerHelpers.WriteTo(ExceptionHandler, new PlainTextOutput(writer)); |
||||
break; |
||||
default: |
||||
writer.Write("Block #{0}: {1}", BlockIndex, NodeType); |
||||
break; |
||||
} |
||||
// if (ImmediateDominator != null) {
|
||||
// writer.WriteLine();
|
||||
// writer.Write("ImmediateDominator: #{0}", ImmediateDominator.BlockIndex);
|
||||
// }
|
||||
if (DominanceFrontier != null && DominanceFrontier.Any()) { |
||||
writer.WriteLine(); |
||||
writer.Write("DominanceFrontier: " + string.Join(",", DominanceFrontier.OrderBy(d => d.BlockIndex).Select(d => d.BlockIndex.ToString()))); |
||||
} |
||||
foreach (Instruction inst in this.Instructions) { |
||||
writer.WriteLine(); |
||||
Disassembler.DisassemblerHelpers.WriteTo(inst, new PlainTextOutput(writer)); |
||||
} |
||||
return writer.ToString(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether <c>this</c> dominates <paramref name="node"/>.
|
||||
/// </summary>
|
||||
public bool Dominates(ControlFlowNode node) |
||||
{ |
||||
// TODO: this can be made O(1) by numbering the dominator tree
|
||||
ControlFlowNode tmp = node; |
||||
while (tmp != null) { |
||||
if (tmp == this) |
||||
return true; |
||||
tmp = tmp.ImmediateDominator; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,241 @@
@@ -0,0 +1,241 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using System.Threading; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Detects the structure of the control flow (exception blocks and loops).
|
||||
/// </summary>
|
||||
public class ControlStructureDetector |
||||
{ |
||||
public static ControlStructure DetectStructure(ControlFlowGraph g, IEnumerable<ExceptionHandler> exceptionHandlers, CancellationToken cancellationToken) |
||||
{ |
||||
ControlStructure root = new ControlStructure(new HashSet<ControlFlowNode>(g.Nodes), g.EntryPoint, ControlStructureType.Root); |
||||
// First build a structure tree out of the exception table
|
||||
DetectExceptionHandling(root, g, exceptionHandlers); |
||||
// Then run the loop detection.
|
||||
DetectLoops(g, root, cancellationToken); |
||||
return root; |
||||
} |
||||
|
||||
#region Exception Handling
|
||||
static void DetectExceptionHandling(ControlStructure current, ControlFlowGraph g, IEnumerable<ExceptionHandler> exceptionHandlers) |
||||
{ |
||||
// We rely on the fact that the exception handlers are sorted so that the innermost come first.
|
||||
// For each exception handler, we determine the nodes and substructures inside that handler, and move them into a new substructure.
|
||||
// This is always possible because exception handlers are guaranteed (by the CLR spec) to be properly nested and non-overlapping;
|
||||
// so they directly form the tree that we need.
|
||||
foreach (ExceptionHandler eh in exceptionHandlers) { |
||||
var tryNodes = FindNodes(current, eh.TryStart, eh.TryEnd); |
||||
current.Nodes.ExceptWith(tryNodes); |
||||
ControlStructure tryBlock = new ControlStructure( |
||||
tryNodes, |
||||
g.Nodes.Single(n => n.Start == eh.TryStart), |
||||
ControlStructureType.Try); |
||||
tryBlock.ExceptionHandler = eh; |
||||
MoveControlStructures(current, tryBlock, eh.TryStart, eh.TryEnd); |
||||
current.Children.Add(tryBlock); |
||||
|
||||
if (eh.FilterStart != null) { |
||||
throw new NotSupportedException(); |
||||
} |
||||
|
||||
var handlerNodes = FindNodes(current, eh.HandlerStart, eh.HandlerEnd); |
||||
var handlerNode = current.Nodes.Single(n => n.ExceptionHandler == eh); |
||||
handlerNodes.Add(handlerNode); |
||||
if (handlerNode.EndFinallyOrFaultNode != null) |
||||
handlerNodes.Add(handlerNode.EndFinallyOrFaultNode); |
||||
current.Nodes.ExceptWith(handlerNodes); |
||||
ControlStructure handlerBlock = new ControlStructure( |
||||
handlerNodes, handlerNode, ControlStructureType.Handler); |
||||
handlerBlock.ExceptionHandler = eh; |
||||
MoveControlStructures(current, handlerBlock, eh.HandlerStart, eh.HandlerEnd); |
||||
current.Children.Add(handlerBlock); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Removes all nodes from start to end (exclusive) from this ControlStructure and moves them to the target structure.
|
||||
/// </summary>
|
||||
static HashSet<ControlFlowNode> FindNodes(ControlStructure current, Instruction startInst, Instruction endInst) |
||||
{ |
||||
HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>(); |
||||
int start = startInst.Offset; |
||||
int end = endInst.Offset; |
||||
foreach (var node in current.Nodes.ToArray()) { |
||||
if (node.Start != null && start <= node.Start.Offset && node.Start.Offset < end) { |
||||
result.Add(node); |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
static void MoveControlStructures(ControlStructure current, ControlStructure target, Instruction startInst, Instruction endInst) |
||||
{ |
||||
for (int i = 0; i < current.Children.Count; i++) { |
||||
var child = current.Children[i]; |
||||
if (startInst.Offset <= child.EntryPoint.Offset && child.EntryPoint.Offset < endInst.Offset) { |
||||
current.Children.RemoveAt(i--); |
||||
target.Children.Add(child); |
||||
target.AllNodes.UnionWith(child.AllNodes); |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Loop Detection
|
||||
// Loop detection works like this:
|
||||
// We find a top-level loop by looking for its entry point, which is characterized by a node dominating its own predecessor.
|
||||
// Then we determine all other nodes that belong to such a loop (all nodes which lead to the entry point, and are dominated by it).
|
||||
// Finally, we check whether our result conforms with potential existing exception structures, and create the substructure for the loop if successful.
|
||||
|
||||
// This algorithm is applied recursively for any substructures (both detected loops and exception blocks)
|
||||
|
||||
// But maybe we should get rid of this complex stuff and instead treat every backward jump as a loop?
|
||||
// That should still work with the IL produced by compilers, and has the advantage that the detected loop bodies are consecutive IL regions.
|
||||
|
||||
static void DetectLoops(ControlFlowGraph g, ControlStructure current, CancellationToken cancellationToken) |
||||
{ |
||||
if (!current.EntryPoint.IsReachable) |
||||
return; |
||||
g.ResetVisited(); |
||||
cancellationToken.ThrowIfCancellationRequested(); |
||||
FindLoops(current, current.EntryPoint); |
||||
foreach (ControlStructure loop in current.Children) |
||||
DetectLoops(g, loop, cancellationToken); |
||||
} |
||||
|
||||
static void FindLoops(ControlStructure current, ControlFlowNode node) |
||||
{ |
||||
if (node.Visited) |
||||
return; |
||||
node.Visited = true; |
||||
if (current.Nodes.Contains(node) |
||||
&& node.DominanceFrontier.Contains(node) |
||||
&& !(node == current.EntryPoint && current.Type == ControlStructureType.Loop)) |
||||
{ |
||||
HashSet<ControlFlowNode> loopContents = new HashSet<ControlFlowNode>(); |
||||
FindLoopContents(current, loopContents, node, node); |
||||
List<ControlStructure> containedChildStructures = new List<ControlStructure>(); |
||||
bool invalidNesting = false; |
||||
foreach (ControlStructure childStructure in current.Children) { |
||||
if (childStructure.AllNodes.IsSubsetOf(loopContents)) { |
||||
containedChildStructures.Add(childStructure); |
||||
} else if (childStructure.AllNodes.Intersect(loopContents).Any()) { |
||||
invalidNesting = true; |
||||
} |
||||
} |
||||
if (!invalidNesting) { |
||||
current.Nodes.ExceptWith(loopContents); |
||||
ControlStructure ctl = new ControlStructure(loopContents, node, ControlStructureType.Loop); |
||||
foreach (ControlStructure childStructure in containedChildStructures) { |
||||
ctl.Children.Add(childStructure); |
||||
current.Children.Remove(childStructure); |
||||
ctl.Nodes.ExceptWith(childStructure.AllNodes); |
||||
} |
||||
current.Children.Add(ctl); |
||||
} |
||||
} |
||||
foreach (var edge in node.Outgoing) { |
||||
FindLoops(current, edge.Target); |
||||
} |
||||
} |
||||
|
||||
static void FindLoopContents(ControlStructure current, HashSet<ControlFlowNode> loopContents, ControlFlowNode loopHead, ControlFlowNode node) |
||||
{ |
||||
if (current.AllNodes.Contains(node) && loopHead.Dominates(node) && loopContents.Add(node)) { |
||||
foreach (var edge in node.Incoming) { |
||||
FindLoopContents(current, loopContents, loopHead, edge.Source); |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
} |
||||
|
||||
public enum ControlStructureType |
||||
{ |
||||
/// <summary>
|
||||
/// The root block of the method
|
||||
/// </summary>
|
||||
Root, |
||||
/// <summary>
|
||||
/// A nested control structure representing a loop.
|
||||
/// </summary>
|
||||
Loop, |
||||
/// <summary>
|
||||
/// A nested control structure representing a try block.
|
||||
/// </summary>
|
||||
Try, |
||||
/// <summary>
|
||||
/// A nested control structure representing a catch, finally, or fault block.
|
||||
/// </summary>
|
||||
Handler, |
||||
/// <summary>
|
||||
/// A nested control structure representing an exception filter block.
|
||||
/// </summary>
|
||||
Filter |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Represents the structure detected by the <see cref="ControlStructureDetector"/>.
|
||||
///
|
||||
/// This is a tree of ControlStructure nodes. Each node contains a set of CFG nodes, and every CFG node is contained in exactly one ControlStructure node.
|
||||
/// </summary>
|
||||
public class ControlStructure |
||||
{ |
||||
public readonly ControlStructureType Type; |
||||
public readonly List<ControlStructure> Children = new List<ControlStructure>(); |
||||
|
||||
/// <summary>
|
||||
/// The nodes in this control structure.
|
||||
/// </summary>
|
||||
public readonly HashSet<ControlFlowNode> Nodes; |
||||
|
||||
/// <summary>
|
||||
/// The nodes in this control structure and in all child control structures.
|
||||
/// </summary>
|
||||
public readonly HashSet<ControlFlowNode> AllNodes; |
||||
|
||||
/// <summary>
|
||||
/// The entry point of this control structure.
|
||||
/// </summary>
|
||||
public readonly ControlFlowNode EntryPoint; |
||||
|
||||
/// <summary>
|
||||
/// The exception handler associated with this Try,Handler or Finally structure.
|
||||
/// </summary>
|
||||
public ExceptionHandler ExceptionHandler; |
||||
|
||||
public ControlStructure(HashSet<ControlFlowNode> nodes, ControlFlowNode entryPoint, ControlStructureType type) |
||||
{ |
||||
if (nodes == null) |
||||
throw new ArgumentNullException("nodes"); |
||||
this.Nodes = nodes; |
||||
this.EntryPoint = entryPoint; |
||||
this.Type = type; |
||||
this.AllNodes = new HashSet<ControlFlowNode>(nodes); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,312 @@
@@ -0,0 +1,312 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Additional info about opcodes.
|
||||
/// </summary>
|
||||
sealed class OpCodeInfo |
||||
{ |
||||
public static bool IsUnconditionalBranch(OpCode opcode) |
||||
{ |
||||
if (opcode.OpCodeType == OpCodeType.Prefix) |
||||
return false; |
||||
switch (opcode.FlowControl) { |
||||
case FlowControl.Branch: |
||||
case FlowControl.Throw: |
||||
case FlowControl.Return: |
||||
return true; |
||||
case FlowControl.Next: |
||||
case FlowControl.Call: |
||||
case FlowControl.Cond_Branch: |
||||
return false; |
||||
default: |
||||
throw new NotSupportedException(opcode.FlowControl.ToString()); |
||||
} |
||||
} |
||||
|
||||
static readonly OpCodeInfo[] knownOpCodes = { |
||||
#region Base Instructions
|
||||
new OpCodeInfo(OpCodes.Add) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Add_Ovf) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Add_Ovf_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.And) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Arglist) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Beq) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Beq_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bge) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bge_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bge_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bge_Un_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bgt) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bgt_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bgt_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bgt_Un_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ble) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ble_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ble_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ble_Un_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Blt) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Blt_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Blt_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Blt_Un_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bne_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Bne_Un_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Br) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Br_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Break) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Brfalse) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Brfalse_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Brtrue) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Brtrue_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Call) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Calli) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ceq) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Cgt) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Cgt_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ckfinite) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Clt) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Clt_Un) { CanThrow = false }, |
||||
// conv.<to type>
|
||||
new OpCodeInfo(OpCodes.Conv_I1) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_I2) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_I4) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_I8) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_R4) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_R8) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_U1) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_U2) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_U4) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_U8) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_I) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_U) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Conv_R_Un) { CanThrow = false }, |
||||
// conv.ovf.<to type>
|
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I1) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I2) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I4) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I8) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U1) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U2) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U4) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U8) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I) { CanThrow = true}, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U) { CanThrow = true}, |
||||
// conv.ovf.<to type>.un
|
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I1_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I2_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I4_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I8_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U1_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U2_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U4_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U8_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_I_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Conv_Ovf_U_Un) { CanThrow = true }, |
||||
|
||||
//new OpCodeInfo(OpCodes.Cpblk) { CanThrow = true }, - no idea whether this might cause trouble for the type system, C# shouldn't use it so I'll disable it
|
||||
new OpCodeInfo(OpCodes.Div) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Div_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Dup) { CanThrow = true, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Endfilter) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Endfinally) { CanThrow = false }, |
||||
//new OpCodeInfo(OpCodes.Initblk) { CanThrow = true }, - no idea whether this might cause trouble for the type system, C# shouldn't use it so I'll disable it
|
||||
//new OpCodeInfo(OpCodes.Jmp) { CanThrow = true } - We don't support non-local control transfers.
|
||||
new OpCodeInfo(OpCodes.Ldarg) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldarg_0) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldarg_1) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldarg_2) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldarg_3) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldarg_S) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldarga) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldarga_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_M1) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_0) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_1) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_2) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_3) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_4) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_5) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_6) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_7) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_8) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I4_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_I8) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_R4) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldc_R8) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldftn) { CanThrow = false }, |
||||
// ldind.<type>
|
||||
new OpCodeInfo(OpCodes.Ldind_I1) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_I2) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_I4) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_I8) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_U1) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_U2) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_U4) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_R4) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_R8) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_I) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ldind_Ref) { CanThrow = true }, |
||||
// the ldloc exceptions described in the spec can only occur on methods without .localsinit - but csc always sets that flag
|
||||
new OpCodeInfo(OpCodes.Ldloc) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldloc_0) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldloc_1) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldloc_2) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldloc_3) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldloc_S) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Ldloca) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldloca_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldnull) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Leave) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Leave_S) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Localloc) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Mul) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Mul_Ovf) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Mul_Ovf_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Neg) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Nop) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Not) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Or) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Pop) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Rem) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Rem_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Ret) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Shl) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Shr) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Shr_Un) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Starg) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Starg_S) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Stind_I1) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_I2) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_I4) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_I8) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_R4) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_R8) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_I) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stind_Ref) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Stloc) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Stloc_0) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Stloc_1) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Stloc_2) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Stloc_3) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Stloc_S) { CanThrow = false, IsMoveInstruction = true }, |
||||
new OpCodeInfo(OpCodes.Sub) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Sub_Ovf) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Sub_Ovf_Un) { CanThrow = true }, |
||||
new OpCodeInfo(OpCodes.Switch) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Xor) { CanThrow = false }, |
||||
#endregion
|
||||
#region Object model instructions
|
||||
// CanThrow is true by default - most OO instructions can throw, so we don't specify CanThrow all of the time
|
||||
new OpCodeInfo(OpCodes.Box), |
||||
new OpCodeInfo(OpCodes.Callvirt), |
||||
new OpCodeInfo(OpCodes.Castclass), |
||||
new OpCodeInfo(OpCodes.Cpobj), |
||||
new OpCodeInfo(OpCodes.Initobj) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Isinst) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldelem_Any), |
||||
// ldelem.<type>
|
||||
new OpCodeInfo(OpCodes.Ldelem_I) , |
||||
new OpCodeInfo(OpCodes.Ldelem_I1), |
||||
new OpCodeInfo(OpCodes.Ldelem_I2), |
||||
new OpCodeInfo(OpCodes.Ldelem_I4), |
||||
new OpCodeInfo(OpCodes.Ldelem_I8), |
||||
new OpCodeInfo(OpCodes.Ldelem_R4), |
||||
new OpCodeInfo(OpCodes.Ldelem_R8), |
||||
new OpCodeInfo(OpCodes.Ldelem_Ref), |
||||
new OpCodeInfo(OpCodes.Ldelem_U1), |
||||
new OpCodeInfo(OpCodes.Ldelem_U2), |
||||
new OpCodeInfo(OpCodes.Ldelem_U4), |
||||
new OpCodeInfo(OpCodes.Ldelema) , |
||||
new OpCodeInfo(OpCodes.Ldfld) , |
||||
new OpCodeInfo(OpCodes.Ldflda), |
||||
new OpCodeInfo(OpCodes.Ldlen) , |
||||
new OpCodeInfo(OpCodes.Ldobj) , |
||||
new OpCodeInfo(OpCodes.Ldsfld), |
||||
new OpCodeInfo(OpCodes.Ldsflda), |
||||
new OpCodeInfo(OpCodes.Ldstr) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldtoken) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Ldvirtftn), |
||||
new OpCodeInfo(OpCodes.Mkrefany), |
||||
new OpCodeInfo(OpCodes.Newarr), |
||||
new OpCodeInfo(OpCodes.Newobj), |
||||
new OpCodeInfo(OpCodes.Refanytype) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Refanyval), |
||||
new OpCodeInfo(OpCodes.Rethrow), |
||||
new OpCodeInfo(OpCodes.Sizeof) { CanThrow = false }, |
||||
new OpCodeInfo(OpCodes.Stelem_Any), |
||||
new OpCodeInfo(OpCodes.Stelem_I1), |
||||
new OpCodeInfo(OpCodes.Stelem_I2), |
||||
new OpCodeInfo(OpCodes.Stelem_I4), |
||||
new OpCodeInfo(OpCodes.Stelem_I8), |
||||
new OpCodeInfo(OpCodes.Stelem_R4), |
||||
new OpCodeInfo(OpCodes.Stelem_R8), |
||||
new OpCodeInfo(OpCodes.Stelem_Ref), |
||||
new OpCodeInfo(OpCodes.Stfld), |
||||
new OpCodeInfo(OpCodes.Stobj), |
||||
new OpCodeInfo(OpCodes.Stsfld), |
||||
new OpCodeInfo(OpCodes.Throw), |
||||
new OpCodeInfo(OpCodes.Unbox), |
||||
new OpCodeInfo(OpCodes.Unbox_Any), |
||||
#endregion
|
||||
}; |
||||
static readonly Dictionary<Code, OpCodeInfo> knownOpCodeDict = knownOpCodes.ToDictionary(info => info.OpCode.Code); |
||||
|
||||
public static OpCodeInfo Get(OpCode opCode) |
||||
{ |
||||
return Get(opCode.Code); |
||||
} |
||||
|
||||
public static OpCodeInfo Get(Code code) |
||||
{ |
||||
OpCodeInfo info; |
||||
if (knownOpCodeDict.TryGetValue(code, out info)) |
||||
return info; |
||||
else |
||||
throw new NotSupportedException(code.ToString()); |
||||
} |
||||
|
||||
OpCode opcode; |
||||
|
||||
private OpCodeInfo(OpCode opcode) |
||||
{ |
||||
this.opcode = opcode; |
||||
this.CanThrow = true; |
||||
} |
||||
|
||||
public OpCode OpCode { get { return opcode; } } |
||||
|
||||
/// <summary>
|
||||
/// 'Move' kind of instructions have one input (may be stack or local variable) and copy that value to all outputs (again stack or local variable).
|
||||
/// </summary>
|
||||
public bool IsMoveInstruction { get; private set; } |
||||
|
||||
/// <summary>
|
||||
/// Specifies whether this opcode is capable of throwing exceptions.
|
||||
/// </summary>
|
||||
public bool CanThrow { get; private set; } |
||||
} |
||||
} |
@ -0,0 +1,174 @@
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
|
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// This is a transformation working on SSA form.
|
||||
/// It removes ldloca instructions and replaces them with SpecialOpCode.PrepareByOutCall or SpecialOpCode.PrepareByRefCall.
|
||||
/// This then allows the variable that had its address taken to also be transformed into SSA.
|
||||
/// </summary>
|
||||
sealed class SimplifyByRefCalls |
||||
{ |
||||
public static bool MakeByRefCallsSimple(SsaForm ssaForm) |
||||
{ |
||||
SimplifyByRefCalls instance = new SimplifyByRefCalls(ssaForm); |
||||
foreach (SsaBlock block in ssaForm.Blocks) { |
||||
for (int i = 0; i < block.Instructions.Count; i++) { |
||||
SsaInstruction inst = block.Instructions[i]; |
||||
if (inst.Instruction != null) { |
||||
switch (inst.Instruction.OpCode.Code) { |
||||
case Code.Call: |
||||
case Code.Callvirt: |
||||
instance.MakeByRefCallSimple(block, ref i, (IMethodSignature)inst.Instruction.Operand); |
||||
break; |
||||
case Code.Initobj: |
||||
instance.MakeInitObjCallSimple(block, ref i); |
||||
break; |
||||
case Code.Ldfld: |
||||
instance.MakeLoadFieldCallSimple(block, ref i); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
instance.RemoveRedundantInstructions(); |
||||
if (instance.couldSimplifySomething) |
||||
ssaForm.ComputeVariableUsage(); |
||||
return instance.couldSimplifySomething; |
||||
} |
||||
|
||||
readonly SsaForm ssaForm; |
||||
|
||||
bool couldSimplifySomething; |
||||
|
||||
// the list of ldloca instructions we will remove
|
||||
readonly List<SsaInstruction> redundantLoadAddressInstructions = new List<SsaInstruction>(); |
||||
|
||||
private SimplifyByRefCalls(SsaForm ssaForm) |
||||
{ |
||||
this.ssaForm = ssaForm; |
||||
} |
||||
|
||||
void MakeByRefCallSimple(SsaBlock block, ref int instructionIndexInBlock, IMethodSignature targetMethod) |
||||
{ |
||||
SsaInstruction inst = block.Instructions[instructionIndexInBlock]; |
||||
for (int i = 0; i < inst.Operands.Length; i++) { |
||||
SsaVariable operand = inst.Operands[i]; |
||||
if (operand.IsSingleAssignment && operand.Usage.Count == 1 && IsLoadAddress(operand.Definition)) { |
||||
// address is used for this method call only
|
||||
|
||||
Instruction loadAddressInstruction = operand.Definition.Instruction; |
||||
|
||||
// find target parameter type:
|
||||
bool isOut; |
||||
if (i == 0 && targetMethod.HasThis) { |
||||
isOut = false; |
||||
} else { |
||||
ParameterDefinition parameter = targetMethod.Parameters[i - (targetMethod.HasThis ? 1 : 0)]; |
||||
isOut = parameter.IsOut; |
||||
} |
||||
|
||||
SsaVariable addressTakenOf = GetVariableFromLoadAddressInstruction(loadAddressInstruction); |
||||
|
||||
// insert "Prepare" instruction on front
|
||||
SpecialOpCode loadOpCode = isOut ? SpecialOpCode.PrepareByOutCall : SpecialOpCode.PrepareByRefCall; |
||||
block.Instructions.Insert(instructionIndexInBlock++, new SsaInstruction( |
||||
block, null, operand, new SsaVariable[] { addressTakenOf }, specialOpCode: loadOpCode)); |
||||
|
||||
// insert "WriteAfterByRefOrOutCall" instruction after call
|
||||
block.Instructions.Insert(instructionIndexInBlock + 1, new SsaInstruction( |
||||
block, null, addressTakenOf, new SsaVariable[] { operand }, specialOpCode: SpecialOpCode.WriteAfterByRefOrOutCall)); |
||||
|
||||
couldSimplifySomething = true; |
||||
|
||||
// remove the loadAddressInstruction later
|
||||
// (later because it might be defined in the current block and we don't want instructionIndex to become invalid)
|
||||
redundantLoadAddressInstructions.Add(operand.Definition); |
||||
} |
||||
} |
||||
} |
||||
|
||||
SsaVariable GetVariableFromLoadAddressInstruction(Instruction loadAddressInstruction) |
||||
{ |
||||
if (loadAddressInstruction.OpCode == OpCodes.Ldloca) { |
||||
return ssaForm.GetOriginalVariable((VariableReference)loadAddressInstruction.Operand); |
||||
} else { |
||||
Debug.Assert(loadAddressInstruction.OpCode == OpCodes.Ldarga); |
||||
return ssaForm.GetOriginalVariable((ParameterReference)loadAddressInstruction.Operand); |
||||
} |
||||
} |
||||
|
||||
static bool IsLoadAddress(SsaInstruction inst) |
||||
{ |
||||
return inst.Instruction != null && (inst.Instruction.OpCode == OpCodes.Ldloca || inst.Instruction.OpCode == OpCodes.Ldarga); |
||||
} |
||||
|
||||
void MakeInitObjCallSimple(SsaBlock block, ref int instructionIndexInBlock) |
||||
{ |
||||
SsaInstruction inst = block.Instructions[instructionIndexInBlock]; |
||||
Debug.Assert(inst.Operands.Length == 1); |
||||
SsaVariable operand = inst.Operands[0]; |
||||
if (operand.IsSingleAssignment && operand.Usage.Count == 1 && IsLoadAddress(operand.Definition)) { |
||||
// replace instruction with special "InitObj" instruction
|
||||
block.Instructions[instructionIndexInBlock] = new SsaInstruction( |
||||
inst.ParentBlock, null, GetVariableFromLoadAddressInstruction(operand.Definition.Instruction), null, |
||||
specialOpCode: SpecialOpCode.InitObj, |
||||
typeOperand: (TypeReference)inst.Instruction.Operand); |
||||
|
||||
couldSimplifySomething = true; |
||||
|
||||
// remove the loadAddressInstruction later
|
||||
redundantLoadAddressInstructions.Add(operand.Definition); |
||||
} |
||||
} |
||||
|
||||
void MakeLoadFieldCallSimple(SsaBlock block, ref int instructionIndexInBlock) |
||||
{ |
||||
SsaInstruction inst = block.Instructions[instructionIndexInBlock]; |
||||
Debug.Assert(inst.Operands.Length == 1); |
||||
SsaVariable operand = inst.Operands[0]; |
||||
if (operand.IsSingleAssignment && operand.Usage.Count == 1 && IsLoadAddress(operand.Definition)) { |
||||
// insert special "PrepareForFieldAccess" instruction in front
|
||||
block.Instructions.Insert(instructionIndexInBlock++, new SsaInstruction( |
||||
inst.ParentBlock, null, operand, |
||||
new SsaVariable[] { GetVariableFromLoadAddressInstruction(operand.Definition.Instruction) }, |
||||
specialOpCode: SpecialOpCode.PrepareForFieldAccess)); |
||||
|
||||
couldSimplifySomething = true; |
||||
|
||||
// remove the loadAddressInstruction later
|
||||
redundantLoadAddressInstructions.Add(operand.Definition); |
||||
} |
||||
} |
||||
|
||||
void RemoveRedundantInstructions() |
||||
{ |
||||
foreach (SsaInstruction inst in redundantLoadAddressInstructions) { |
||||
inst.ParentBlock.Instructions.Remove(inst); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.IO; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// A block in a control flow graph; with instructions represented by "SsaInstructions" (instructions use variables, no evaluation stack).
|
||||
/// Usually these variables are in SSA form to make analysis easier.
|
||||
/// </summary>
|
||||
public sealed class SsaBlock |
||||
{ |
||||
public readonly List<SsaBlock> Successors = new List<SsaBlock>(); |
||||
public readonly List<SsaBlock> Predecessors = new List<SsaBlock>(); |
||||
public readonly ControlFlowNodeType NodeType; |
||||
public readonly List<SsaInstruction> Instructions = new List<SsaInstruction>(); |
||||
|
||||
/// <summary>
|
||||
/// The block index in the control flow graph.
|
||||
/// This correspons to the node index in ControlFlowGraph.Nodes, so it can be used to retrieve the original CFG node and look
|
||||
/// up additional information (e.g. dominance).
|
||||
/// </summary>
|
||||
public readonly int BlockIndex; |
||||
|
||||
internal SsaBlock(ControlFlowNode node) |
||||
{ |
||||
this.NodeType = node.NodeType; |
||||
this.BlockIndex = node.BlockIndex; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringWriter writer = new StringWriter(); |
||||
writer.Write("Block #{0} ({1})", BlockIndex, NodeType); |
||||
foreach (SsaInstruction inst in Instructions) { |
||||
writer.WriteLine(); |
||||
inst.WriteTo(writer); |
||||
} |
||||
return writer.ToString(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,162 @@
@@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2010 Daniel Grunwald
|
||||
//
|
||||
// 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.NRefactory.Utils; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Represents a graph of SsaBlocks.
|
||||
/// </summary>
|
||||
public sealed class SsaForm |
||||
{ |
||||
readonly SsaVariable[] parameters; |
||||
readonly SsaVariable[] locals; |
||||
public readonly ReadOnlyCollection<SsaVariable> OriginalVariables; |
||||
public readonly ReadOnlyCollection<SsaBlock> Blocks; |
||||
readonly bool methodHasThis; |
||||
|
||||
public SsaBlock EntryPoint { |
||||
get { return this.Blocks[0]; } |
||||
} |
||||
|
||||
public SsaBlock RegularExit { |
||||
get { return this.Blocks[1]; } |
||||
} |
||||
|
||||
public SsaBlock ExceptionalExit { |
||||
get { return this.Blocks[2]; } |
||||
} |
||||
|
||||
internal SsaForm(SsaBlock[] blocks, SsaVariable[] parameters, SsaVariable[] locals, SsaVariable[] stackLocations, bool methodHasThis) |
||||
{ |
||||
this.parameters = parameters; |
||||
this.locals = locals; |
||||
this.Blocks = new ReadOnlyCollection<SsaBlock>(blocks); |
||||
this.OriginalVariables = new ReadOnlyCollection<SsaVariable>(parameters.Concat(locals).Concat(stackLocations).ToList()); |
||||
this.methodHasThis = methodHasThis; |
||||
|
||||
Debug.Assert(EntryPoint.NodeType == ControlFlowNodeType.EntryPoint); |
||||
Debug.Assert(RegularExit.NodeType == ControlFlowNodeType.RegularExit); |
||||
Debug.Assert(ExceptionalExit.NodeType == ControlFlowNodeType.ExceptionalExit); |
||||
for (int i = 0; i < this.OriginalVariables.Count; i++) { |
||||
this.OriginalVariables[i].OriginalVariableIndex = i; |
||||
} |
||||
} |
||||
|
||||
public GraphVizGraph ExportBlockGraph(Func<SsaBlock, string> labelProvider = null) |
||||
{ |
||||
if (labelProvider == null) |
||||
labelProvider = b => b.ToString(); |
||||
GraphVizGraph graph = new GraphVizGraph(); |
||||
foreach (SsaBlock block in this.Blocks) { |
||||
graph.AddNode(new GraphVizNode(block.BlockIndex) { label = labelProvider(block), shape = "box" }); |
||||
} |
||||
foreach (SsaBlock block in this.Blocks) { |
||||
foreach (SsaBlock s in block.Successors) { |
||||
graph.AddEdge(new GraphVizEdge(block.BlockIndex, s.BlockIndex)); |
||||
} |
||||
} |
||||
return graph; |
||||
} |
||||
|
||||
public GraphVizGraph ExportVariableGraph(Func<SsaVariable, string> labelProvider = null) |
||||
{ |
||||
if (labelProvider == null) |
||||
labelProvider = v => v.ToString(); |
||||
GraphVizGraph graph = new GraphVizGraph(); |
||||
foreach (SsaVariable v in this.AllVariables) { |
||||
graph.AddNode(new GraphVizNode(v.Name) { label = labelProvider(v) }); |
||||
} |
||||
int instructionIndex = 0; |
||||
foreach (SsaBlock block in this.Blocks) { |
||||
foreach (SsaInstruction inst in block.Instructions) { |
||||
if (inst.Operands.Length == 0 && inst.Target == null) |
||||
continue; |
||||
string id = "instruction" + (++instructionIndex); |
||||
graph.AddNode(new GraphVizNode(id) { label = inst.ToString(), shape = "box" }); |
||||
foreach (SsaVariable op in inst.Operands) |
||||
graph.AddEdge(new GraphVizEdge(op.Name, id)); |
||||
if (inst.Target != null) |
||||
graph.AddEdge(new GraphVizEdge(id, inst.Target.Name)); |
||||
} |
||||
} |
||||
return graph; |
||||
} |
||||
|
||||
public SsaVariable GetOriginalVariable(ParameterReference parameter) |
||||
{ |
||||
if (methodHasThis) |
||||
return parameters[parameter.Index + 1]; |
||||
else |
||||
return parameters[parameter.Index]; |
||||
} |
||||
|
||||
public SsaVariable GetOriginalVariable(VariableReference variable) |
||||
{ |
||||
return locals[variable.Index]; |
||||
} |
||||
|
||||
#region ComputeVariableUsage
|
||||
public void ComputeVariableUsage() |
||||
{ |
||||
// clear data from previous runs
|
||||
foreach (SsaBlock block in this.Blocks) { |
||||
foreach (SsaInstruction inst in block.Instructions) { |
||||
foreach (SsaVariable v in inst.Operands) { |
||||
if (v.Usage != null) |
||||
v.Usage.Clear(); |
||||
} |
||||
if (inst.Target != null && inst.Target.Usage != null) |
||||
inst.Target.Usage.Clear(); |
||||
} |
||||
} |
||||
foreach (SsaBlock block in this.Blocks) { |
||||
foreach (SsaInstruction inst in block.Instructions) { |
||||
foreach (SsaVariable v in inst.Operands) { |
||||
if (v.Usage == null) |
||||
v.Usage = new List<SsaInstruction>(); |
||||
v.Usage.Add(inst); |
||||
} |
||||
if (inst.Target != null && inst.Target.Usage == null) |
||||
inst.Target.Usage = new List<SsaInstruction>(); |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
public IEnumerable<SsaVariable> AllVariables { |
||||
get { |
||||
return ( |
||||
from block in this.Blocks |
||||
from instruction in block.Instructions |
||||
where instruction.Target != null |
||||
select instruction.Target |
||||
).Distinct(); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,257 @@
@@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2010 Daniel Grunwald
|
||||
//
|
||||
// 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
|
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Constructs "SsaForm" graph for a CFG.
|
||||
/// This class transforms the method from stack-based IL to a register-based IL language.
|
||||
/// Then it calls into TransformToSsa to convert the resulting graph to static single assignment form.
|
||||
/// </summary>
|
||||
public sealed class SsaFormBuilder |
||||
{ |
||||
public static SsaForm Build(MethodDefinition method) |
||||
{ |
||||
if (method == null) |
||||
throw new ArgumentNullException("method"); |
||||
var cfg = ControlFlowGraphBuilder.Build(method.Body); |
||||
cfg.ComputeDominance(); |
||||
cfg.ComputeDominanceFrontier(); |
||||
var ssa = BuildRegisterIL(method, cfg); |
||||
TransformToSsa.Transform(cfg, ssa); |
||||
return ssa; |
||||
} |
||||
|
||||
public static SsaForm BuildRegisterIL(MethodDefinition method, ControlFlowGraph cfg) |
||||
{ |
||||
if (method == null) |
||||
throw new ArgumentNullException("method"); |
||||
if (cfg == null) |
||||
throw new ArgumentNullException("cfg"); |
||||
return new SsaFormBuilder(method, cfg).Build(); |
||||
} |
||||
|
||||
readonly MethodDefinition method; |
||||
readonly ControlFlowGraph cfg; |
||||
|
||||
readonly SsaBlock[] blocks; // array index = block index
|
||||
readonly int[] stackSizeAtBlockStart; // array index = block index
|
||||
|
||||
readonly SsaVariable[] parameters; // array index = parameter number
|
||||
readonly SsaVariable[] locals; // array index = local number
|
||||
readonly SsaVariable[] stackLocations; // array index = position on the IL evaluation stack
|
||||
SsaForm ssaForm; |
||||
|
||||
private SsaFormBuilder(MethodDefinition method, ControlFlowGraph cfg) |
||||
{ |
||||
this.method = method; |
||||
this.cfg = cfg; |
||||
|
||||
this.blocks = new SsaBlock[cfg.Nodes.Count]; |
||||
this.stackSizeAtBlockStart = new int[cfg.Nodes.Count]; |
||||
for (int i = 0; i < stackSizeAtBlockStart.Length; i++) { |
||||
stackSizeAtBlockStart[i] = -1; |
||||
} |
||||
stackSizeAtBlockStart[cfg.EntryPoint.BlockIndex] = 0; |
||||
|
||||
this.parameters = new SsaVariable[method.Parameters.Count + (method.HasThis ? 1 : 0)]; |
||||
if (method.HasThis) |
||||
parameters[0] = new SsaVariable(method.Body.ThisParameter); |
||||
for (int i = 0; i < method.Parameters.Count; i++) |
||||
parameters[i + (method.HasThis ? 1 : 0)] = new SsaVariable(method.Parameters[i]); |
||||
|
||||
this.locals = new SsaVariable[method.Body.Variables.Count]; |
||||
for (int i = 0; i < locals.Length; i++) |
||||
locals[i] = new SsaVariable(method.Body.Variables[i]); |
||||
|
||||
this.stackLocations = new SsaVariable[method.Body.MaxStackSize]; |
||||
for (int i = 0; i < stackLocations.Length; i++) { |
||||
stackLocations[i] = new SsaVariable(i); |
||||
} |
||||
} |
||||
|
||||
internal SsaForm Build() |
||||
{ |
||||
CreateGraphStructure(); |
||||
this.ssaForm = new SsaForm(blocks, parameters, locals, stackLocations, method.HasThis); |
||||
CreateInstructions(cfg.EntryPoint.BlockIndex); |
||||
CreateSpecialInstructions(); |
||||
return ssaForm; |
||||
} |
||||
|
||||
void CreateGraphStructure() |
||||
{ |
||||
for (int i = 0; i < blocks.Length; i++) { |
||||
blocks[i] = new SsaBlock(cfg.Nodes[i]); |
||||
} |
||||
for (int i = 0; i < blocks.Length; i++) { |
||||
foreach (ControlFlowNode node in cfg.Nodes[i].Successors) { |
||||
blocks[i].Successors.Add(blocks[node.BlockIndex]); |
||||
blocks[node.BlockIndex].Predecessors.Add(blocks[i]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void CreateInstructions(int blockIndex) |
||||
{ |
||||
ControlFlowNode cfgNode = cfg.Nodes[blockIndex]; |
||||
SsaBlock block = blocks[blockIndex]; |
||||
|
||||
int stackSize = stackSizeAtBlockStart[blockIndex]; |
||||
Debug.Assert(stackSize >= 0); |
||||
|
||||
List<Instruction> prefixes = new List<Instruction>(); |
||||
foreach (Instruction inst in cfgNode.Instructions) { |
||||
if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { |
||||
prefixes.Add(inst); |
||||
continue; |
||||
} |
||||
|
||||
int popCount = inst.GetPopDelta() ?? stackSize; |
||||
stackSize -= popCount; |
||||
if (stackSize < 0) |
||||
throw new InvalidProgramException("IL stack underflow"); |
||||
|
||||
int pushCount = inst.GetPushDelta(); |
||||
if (stackSize + pushCount > stackLocations.Length) |
||||
throw new InvalidProgramException("IL stack overflow"); |
||||
|
||||
SsaVariable target; |
||||
SsaVariable[] operands; |
||||
DetermineOperands(stackSize, inst, popCount, pushCount, out target, out operands); |
||||
|
||||
Instruction[] prefixArray = prefixes.Count > 0 ? prefixes.ToArray() : null; |
||||
prefixes.Clear(); |
||||
|
||||
// ignore NOP instructions
|
||||
if (!(inst.OpCode == OpCodes.Nop || inst.OpCode == OpCodes.Pop)) { |
||||
block.Instructions.Add(new SsaInstruction(block, inst, target, operands, prefixArray)); |
||||
} |
||||
stackSize += pushCount; |
||||
} |
||||
|
||||
foreach (ControlFlowEdge edge in cfgNode.Outgoing) { |
||||
int newStackSize; |
||||
switch (edge.Type) { |
||||
case JumpType.Normal: |
||||
newStackSize = stackSize; |
||||
break; |
||||
case JumpType.EndFinally: |
||||
if (stackSize != 0) |
||||
throw new NotSupportedException("stacksize must be 0 in endfinally edge"); |
||||
newStackSize = 0; |
||||
break; |
||||
case JumpType.JumpToExceptionHandler: |
||||
switch (edge.Target.NodeType) { |
||||
case ControlFlowNodeType.FinallyOrFaultHandler: |
||||
newStackSize = 0; |
||||
break; |
||||
case ControlFlowNodeType.ExceptionalExit: |
||||
case ControlFlowNodeType.CatchHandler: |
||||
newStackSize = 1; |
||||
break; |
||||
default: |
||||
throw new NotSupportedException("unsupported target node type: " + edge.Target.NodeType); |
||||
} |
||||
break; |
||||
default: |
||||
throw new NotSupportedException("unsupported jump type: " + edge.Type); |
||||
} |
||||
|
||||
int nextStackSize = stackSizeAtBlockStart[edge.Target.BlockIndex]; |
||||
if (nextStackSize == -1) { |
||||
stackSizeAtBlockStart[edge.Target.BlockIndex] = newStackSize; |
||||
CreateInstructions(edge.Target.BlockIndex); |
||||
} else if (nextStackSize != newStackSize) { |
||||
throw new InvalidProgramException("Stack size doesn't match"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void DetermineOperands(int stackSize, Instruction inst, int popCount, int pushCount, out SsaVariable target, out SsaVariable[] operands) |
||||
{ |
||||
switch (inst.OpCode.Code) { |
||||
case Code.Ldarg: |
||||
operands = new SsaVariable[] { ssaForm.GetOriginalVariable((ParameterReference)inst.Operand) }; |
||||
target = stackLocations[stackSize]; |
||||
break; |
||||
case Code.Starg: |
||||
operands = new SsaVariable[] { stackLocations[stackSize] }; |
||||
target = ssaForm.GetOriginalVariable((ParameterReference)inst.Operand); |
||||
break; |
||||
case Code.Ldloc: |
||||
operands = new SsaVariable[] { ssaForm.GetOriginalVariable((VariableReference)inst.Operand) }; |
||||
target = stackLocations[stackSize]; |
||||
break; |
||||
case Code.Stloc: |
||||
operands = new SsaVariable[] { stackLocations[stackSize] }; |
||||
target = ssaForm.GetOriginalVariable((VariableReference)inst.Operand); |
||||
break; |
||||
case Code.Dup: |
||||
operands = new SsaVariable[] { stackLocations[stackSize] }; |
||||
target = stackLocations[stackSize + 1]; |
||||
break; |
||||
default: |
||||
operands = new SsaVariable[popCount]; |
||||
for (int i = 0; i < popCount; i++) { |
||||
operands[i] = stackLocations[stackSize + i]; |
||||
} |
||||
|
||||
switch (pushCount) { |
||||
case 0: |
||||
target = null; |
||||
break; |
||||
case 1: |
||||
target = stackLocations[stackSize]; |
||||
break; |
||||
default: |
||||
throw new NotSupportedException("unsupported pushCount=" + pushCount); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void CreateSpecialInstructions() |
||||
{ |
||||
// Everything needs an initial write for the SSA transformation to work correctly.
|
||||
foreach (SsaVariable v in parameters) { |
||||
ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Parameter)); |
||||
} |
||||
foreach (SsaVariable v in locals) { |
||||
ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Uninitialized)); |
||||
} |
||||
foreach (SsaVariable v in stackLocations) { |
||||
ssaForm.EntryPoint.Instructions.Add(new SsaInstruction(ssaForm.EntryPoint, null, v, null, specialOpCode: SpecialOpCode.Uninitialized)); |
||||
} |
||||
foreach (SsaBlock b in blocks) { |
||||
if (b.NodeType == ControlFlowNodeType.CatchHandler) { |
||||
b.Instructions.Add(new SsaInstruction(b, null, stackLocations[0], null, |
||||
specialOpCode: SpecialOpCode.Exception, |
||||
typeOperand: cfg.Nodes[b.BlockIndex].ExceptionHandler.CatchType)); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,191 @@
@@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
|
||||
using ICSharpCode.Decompiler.Disassembler; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
public enum SpecialOpCode |
||||
{ |
||||
/// <summary>
|
||||
/// No special op code: SsaInstruction has a normal IL instruction
|
||||
/// </summary>
|
||||
None, |
||||
/// <summary>
|
||||
/// Φ function: chooses the appropriate variable based on which CFG edge was used to enter this block
|
||||
/// </summary>
|
||||
Phi, |
||||
/// <summary>
|
||||
/// Variable is read from before passing it by ref.
|
||||
/// This instruction constructs a managed reference to the variable.
|
||||
/// </summary>
|
||||
PrepareByRefCall, |
||||
/// <summary>
|
||||
/// This instruction constructs a managed reference to the variable.
|
||||
/// The variable is not really read from.
|
||||
/// </summary>
|
||||
PrepareByOutCall, |
||||
/// <summary>
|
||||
/// This instruction constructs a managed reference to the variable.
|
||||
/// The reference is used for a field access on a value type.
|
||||
/// </summary>
|
||||
PrepareForFieldAccess, |
||||
/// <summary>
|
||||
/// Variable is written to after passing it by ref or out.
|
||||
/// </summary>
|
||||
WriteAfterByRefOrOutCall, |
||||
/// <summary>
|
||||
/// Variable is not initialized.
|
||||
/// </summary>
|
||||
Uninitialized, |
||||
/// <summary>
|
||||
/// Value is passed in as parameter
|
||||
/// </summary>
|
||||
Parameter, |
||||
/// <summary>
|
||||
/// Value is a caught exception.
|
||||
/// TypeOperand is set to the exception type.
|
||||
/// </summary>
|
||||
Exception, |
||||
/// <summary>
|
||||
/// Initialize a value type. Unlike the real initobj instruction, this one does not take an address
|
||||
/// but assigns to the target variable.
|
||||
/// TypeOperand is set to the type being created.
|
||||
/// </summary>
|
||||
InitObj |
||||
} |
||||
|
||||
public sealed class SsaInstruction |
||||
{ |
||||
public readonly SsaBlock ParentBlock; |
||||
public readonly SpecialOpCode SpecialOpCode; |
||||
|
||||
/// <summary>
|
||||
/// The original IL instruction.
|
||||
/// May be null for "invented" instructions (SpecialOpCode != None).
|
||||
/// </summary>
|
||||
public readonly Instruction Instruction; |
||||
|
||||
/// <summary>
|
||||
/// Prefixes in front of the IL instruction.
|
||||
/// </summary>
|
||||
public readonly Instruction[] Prefixes; |
||||
|
||||
/// <summary>
|
||||
/// Gets the type operand. This is used only in combination with some special opcodes.
|
||||
/// </summary>
|
||||
public readonly TypeReference TypeOperand; |
||||
|
||||
public SsaVariable Target; |
||||
public SsaVariable[] Operands; |
||||
|
||||
static readonly SsaVariable[] emptyVariableArray = {}; |
||||
static readonly Instruction[] emptyInstructionArray = {}; |
||||
|
||||
public SsaInstruction(SsaBlock parentBlock, Instruction instruction, SsaVariable target, SsaVariable[] operands, |
||||
Instruction[] prefixes = null, SpecialOpCode specialOpCode = SpecialOpCode.None, |
||||
TypeReference typeOperand = null) |
||||
{ |
||||
this.ParentBlock = parentBlock; |
||||
this.Instruction = instruction; |
||||
this.Prefixes = prefixes ?? emptyInstructionArray; |
||||
this.Target = target; |
||||
this.Operands = operands ?? emptyVariableArray; |
||||
this.SpecialOpCode = specialOpCode; |
||||
this.TypeOperand = typeOperand; |
||||
Debug.Assert((typeOperand != null) == (specialOpCode == SpecialOpCode.Exception || specialOpCode == SpecialOpCode.InitObj)); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether this instruction is a simple assignment from one variable to another.
|
||||
/// </summary>
|
||||
public bool IsMoveInstruction { |
||||
get { |
||||
return Target != null && Operands.Length == 1 && Instruction != null && OpCodeInfo.Get(Instruction.OpCode).IsMoveInstruction; |
||||
} |
||||
} |
||||
|
||||
public void ReplaceVariableInOperands(SsaVariable oldVar, SsaVariable newVar) |
||||
{ |
||||
for (int i = 0; i < this.Operands.Length; i++) { |
||||
if (this.Operands[i] == oldVar) |
||||
this.Operands[i] = newVar; |
||||
} |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringWriter w = new StringWriter(); |
||||
WriteTo(w); |
||||
return w.ToString(); |
||||
} |
||||
|
||||
public void WriteTo(TextWriter writer) |
||||
{ |
||||
foreach (Instruction prefix in this.Prefixes) { |
||||
Disassembler.DisassemblerHelpers.WriteTo(prefix, new PlainTextOutput(writer)); |
||||
writer.WriteLine(); |
||||
} |
||||
if (Instruction != null && Instruction.Offset >= 0) { |
||||
writer.Write(CecilExtensions.OffsetToString(Instruction.Offset)); |
||||
writer.Write(": "); |
||||
} |
||||
if (Target != null) { |
||||
writer.Write(Target.ToString()); |
||||
writer.Write(" = "); |
||||
} |
||||
if (IsMoveInstruction) { |
||||
writer.Write(Operands[0].ToString()); |
||||
if (Instruction != null) { |
||||
writer.Write(" (" + Instruction.OpCode.Name + ")"); |
||||
} |
||||
} else { |
||||
if (Instruction == null) { |
||||
writer.Write(SpecialOpCode.ToString()); |
||||
} else { |
||||
writer.Write(Instruction.OpCode.Name); |
||||
if(null != Instruction.Operand) { |
||||
writer.Write(' '); |
||||
Disassembler.DisassemblerHelpers.WriteOperand(new PlainTextOutput(writer), Instruction.Operand); |
||||
writer.Write(' '); |
||||
} |
||||
} |
||||
if (TypeOperand != null) { |
||||
writer.Write(' '); |
||||
writer.Write(TypeOperand.ToString()); |
||||
writer.Write(' '); |
||||
} |
||||
if (Operands.Length > 0) { |
||||
writer.Write('('); |
||||
for (int i = 0; i < Operands.Length; i++) { |
||||
if (i > 0) |
||||
writer.Write(", "); |
||||
writer.Write(Operands[i].ToString()); |
||||
} |
||||
writer.Write(')'); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
|
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Contains some very simple optimizations that work on the SSA form.
|
||||
/// </summary>
|
||||
static class SsaOptimization |
||||
{ |
||||
public static void Optimize(SsaForm ssaForm) |
||||
{ |
||||
DirectlyStoreToVariables(ssaForm); |
||||
SimpleCopyPropagation(ssaForm); |
||||
RemoveDeadAssignments(ssaForm); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// When any instructions stores its result in a stack location that's used only once in a 'stloc' or 'starg' instruction,
|
||||
/// we optimize this to directly store in the target location.
|
||||
/// As optimization this is redundant (does the same as copy propagation), but it'll make us keep the variables named
|
||||
/// after locals instead of keeping the temps as using only the simple copy propagation would do.
|
||||
/// </summary>
|
||||
public static void DirectlyStoreToVariables(SsaForm ssaForm) |
||||
{ |
||||
foreach (SsaBlock block in ssaForm.Blocks) { |
||||
block.Instructions.RemoveAll( |
||||
inst => { |
||||
if (inst.Instruction != null && (inst.Instruction.OpCode == OpCodes.Stloc || inst.Instruction.OpCode == OpCodes.Starg)) { |
||||
SsaVariable target = inst.Target; |
||||
SsaVariable temp = inst.Operands[0]; |
||||
if (target.IsSingleAssignment && temp.IsSingleAssignment && temp.Usage.Count == 1 && temp.IsStackLocation) { |
||||
temp.Definition.Target = target; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
}); |
||||
} |
||||
ssaForm.ComputeVariableUsage(); // update usage after we modified stuff
|
||||
} |
||||
|
||||
public static void SimpleCopyPropagation(SsaForm ssaForm, bool onlyForStackLocations = true) |
||||
{ |
||||
foreach (SsaBlock block in ssaForm.Blocks) { |
||||
foreach (SsaInstruction inst in block.Instructions) { |
||||
if (inst.IsMoveInstruction && inst.Target.IsSingleAssignment && inst.Operands[0].IsSingleAssignment) { |
||||
if (inst.Target.IsStackLocation || !onlyForStackLocations) { |
||||
// replace all uses of 'target' with 'operands[0]'.
|
||||
foreach (SsaInstruction useInstruction in inst.Target.Usage) { |
||||
useInstruction.ReplaceVariableInOperands(inst.Target, inst.Operands[0]); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
ssaForm.ComputeVariableUsage(); // update usage after we modified stuff
|
||||
} |
||||
|
||||
public static void RemoveDeadAssignments(SsaForm ssaForm) |
||||
{ |
||||
HashSet<SsaVariable> liveVariables = new HashSet<SsaVariable>(); |
||||
// find variables that are used directly
|
||||
foreach (SsaBlock block in ssaForm.Blocks) { |
||||
foreach (SsaInstruction inst in block.Instructions) { |
||||
if (!CanRemoveAsDeadCode(inst)) { |
||||
if (inst.Target != null) |
||||
liveVariables.Add(inst.Target); |
||||
foreach (SsaVariable op in inst.Operands) { |
||||
liveVariables.Add(op); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Queue<SsaVariable> queue = new Queue<SsaVariable>(liveVariables); |
||||
// find variables that are used indirectly
|
||||
while (queue.Count > 0) { |
||||
SsaVariable v = queue.Dequeue(); |
||||
if (v.IsSingleAssignment) { |
||||
foreach (SsaVariable op in v.Definition.Operands) { |
||||
if (liveVariables.Add(op)) |
||||
queue.Enqueue(op); |
||||
} |
||||
} |
||||
} |
||||
// remove assignments to all unused variables
|
||||
foreach (SsaBlock block in ssaForm.Blocks) { |
||||
block.Instructions.RemoveAll( |
||||
inst => { |
||||
if (inst.Target != null && !liveVariables.Contains(inst.Target)) { |
||||
Debug.Assert(inst.Target.IsSingleAssignment); |
||||
return true; |
||||
} |
||||
return false; |
||||
}); |
||||
} |
||||
ssaForm.ComputeVariableUsage(); // update usage after we modified stuff
|
||||
} |
||||
|
||||
static bool CanRemoveAsDeadCode(SsaInstruction inst) |
||||
{ |
||||
if (inst.Target != null && !inst.Target.IsSingleAssignment) |
||||
return false; |
||||
switch (inst.SpecialOpCode) { |
||||
case SpecialOpCode.Phi: |
||||
case SpecialOpCode.Exception: |
||||
case SpecialOpCode.Parameter: |
||||
case SpecialOpCode.Uninitialized: |
||||
return true; |
||||
case SpecialOpCode.None: |
||||
return inst.IsMoveInstruction; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Represents a variable used with the SsaInstruction register-based instructions.
|
||||
/// Despite what the name suggests, the variable is not necessarily in single-assignment form - take a look at "bool IsSingleAssignment".
|
||||
/// </summary>
|
||||
public sealed class SsaVariable |
||||
{ |
||||
public int OriginalVariableIndex; |
||||
public readonly string Name; |
||||
public readonly bool IsStackLocation; |
||||
|
||||
public readonly ParameterDefinition Parameter; |
||||
public readonly VariableDefinition Variable; |
||||
|
||||
public SsaVariable(ParameterDefinition p) |
||||
{ |
||||
this.Name = string.IsNullOrEmpty(p.Name) ? "param" + p.Index : p.Name; |
||||
this.Parameter = p; |
||||
} |
||||
|
||||
public SsaVariable(VariableDefinition v) |
||||
{ |
||||
this.Name = string.IsNullOrEmpty(v.Name) ? "V_" + v.Index : v.Name; |
||||
this.Variable = v; |
||||
} |
||||
|
||||
public SsaVariable(int stackLocation) |
||||
{ |
||||
this.Name = "stack" + stackLocation; |
||||
this.IsStackLocation = true; |
||||
} |
||||
|
||||
public SsaVariable(SsaVariable original, string newName) |
||||
{ |
||||
this.Name = newName; |
||||
this.IsStackLocation = original.IsStackLocation; |
||||
this.OriginalVariableIndex = original.OriginalVariableIndex; |
||||
this.Parameter = original.Parameter; |
||||
this.Variable = original.Variable; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return Name; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether this variable has only a single assignment.
|
||||
/// This field is initialized in TransformToSsa step.
|
||||
/// </summary>
|
||||
/// <remarks>Not all variables can be transformed to single assignment form: variables that have their address taken
|
||||
/// cannot be represented in SSA (although SimplifyByRefCalls will get rid of the address-taking instruction in almost all cases)</remarks>
|
||||
public bool IsSingleAssignment; |
||||
|
||||
/// <summary>
|
||||
/// Gets the instruction defining the variable.
|
||||
/// This field is initialized in TransformToSsa step. It is only set for variables with a single assignment.
|
||||
/// </summary>
|
||||
public SsaInstruction Definition; |
||||
|
||||
/// <summary>
|
||||
/// Gets the places where a variable is used.
|
||||
/// If a single instruction reads a variable 2 times (e.g. adding to itself), then it must be included 2 times in this list!
|
||||
/// </summary>
|
||||
public List<SsaInstruction> Usage; |
||||
} |
||||
} |
@ -0,0 +1,254 @@
@@ -0,0 +1,254 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.FlowAnalysis |
||||
{ |
||||
/// <summary>
|
||||
/// Convers a method to static single assignment form.
|
||||
/// </summary>
|
||||
sealed class TransformToSsa |
||||
{ |
||||
public static void Transform(ControlFlowGraph cfg, SsaForm ssa, bool optimize = true) |
||||
{ |
||||
TransformToSsa transform = new TransformToSsa(cfg, ssa); |
||||
transform.ConvertVariablesToSsa(); |
||||
SsaOptimization.RemoveDeadAssignments(ssa); // required so that 'MakeByRefCallsSimple' can detect more cases
|
||||
if (SimplifyByRefCalls.MakeByRefCallsSimple(ssa)) { |
||||
transform.ConvertVariablesToSsa(); |
||||
} |
||||
if (optimize) |
||||
SsaOptimization.Optimize(ssa); |
||||
} |
||||
|
||||
readonly ControlFlowGraph cfg; |
||||
readonly SsaForm ssaForm; |
||||
readonly List<SsaInstruction>[] writeToOriginalVariables; // array index -> SsaVariable OriginalVariableIndex
|
||||
readonly bool[] addressTaken; // array index -> SsaVariable OriginalVariableIndex; value = whether ldloca instruction was used with variable
|
||||
|
||||
private TransformToSsa(ControlFlowGraph cfg, SsaForm ssaForm) |
||||
{ |
||||
this.cfg = cfg; |
||||
this.ssaForm = ssaForm; |
||||
this.writeToOriginalVariables = new List<SsaInstruction>[ssaForm.OriginalVariables.Count]; |
||||
this.addressTaken = new bool[ssaForm.OriginalVariables.Count]; |
||||
} |
||||
|
||||
#region CollectInformationAboutOriginalVariableUse
|
||||
void CollectInformationAboutOriginalVariableUse() |
||||
{ |
||||
Debug.Assert(addressTaken.Length == writeToOriginalVariables.Length); |
||||
for (int i = 0; i < writeToOriginalVariables.Length; i++) { |
||||
Debug.Assert(ssaForm.OriginalVariables[i].OriginalVariableIndex == i); |
||||
|
||||
addressTaken[i] = false; |
||||
// writeToOriginalVariables is only used when placing phi functions
|
||||
// we don't need to do that anymore for variables that are already in SSA form
|
||||
if (ssaForm.OriginalVariables[i].IsSingleAssignment) |
||||
writeToOriginalVariables[i] = null; |
||||
else |
||||
writeToOriginalVariables[i] = new List<SsaInstruction>(); |
||||
} |
||||
foreach (SsaBlock block in ssaForm.Blocks) { |
||||
foreach (SsaInstruction inst in block.Instructions) { |
||||
if (inst.Target != null ) { |
||||
var list = writeToOriginalVariables[inst.Target.OriginalVariableIndex]; |
||||
if (list != null) |
||||
list.Add(inst); |
||||
} |
||||
if (inst.Instruction != null) { |
||||
if (inst.Instruction.OpCode == OpCodes.Ldloca) { |
||||
addressTaken[ssaForm.GetOriginalVariable((VariableDefinition)inst.Instruction.Operand).OriginalVariableIndex] = true; |
||||
} else if (inst.Instruction.OpCode == OpCodes.Ldarga) { |
||||
addressTaken[ssaForm.GetOriginalVariable((ParameterDefinition)inst.Instruction.Operand).OriginalVariableIndex] = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region ConvertToSsa
|
||||
void ConvertVariablesToSsa() |
||||
{ |
||||
CollectInformationAboutOriginalVariableUse(); |
||||
bool[] processVariable = new bool[ssaForm.OriginalVariables.Count]; |
||||
foreach (SsaVariable variable in ssaForm.OriginalVariables) { |
||||
if (!variable.IsSingleAssignment && !addressTaken[variable.OriginalVariableIndex]) { |
||||
PlacePhiFunctions(variable); |
||||
processVariable[variable.OriginalVariableIndex] = true; |
||||
} |
||||
} |
||||
RenameVariables(processVariable); |
||||
foreach (SsaVariable variable in ssaForm.OriginalVariables) { |
||||
if (!addressTaken[variable.OriginalVariableIndex]) { |
||||
Debug.Assert(variable.IsSingleAssignment && variable.Definition != null); |
||||
} |
||||
} |
||||
ssaForm.ComputeVariableUsage(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region PlacePhiFunctions
|
||||
void PlacePhiFunctions(SsaVariable variable) |
||||
{ |
||||
cfg.ResetVisited(); |
||||
HashSet<SsaBlock> blocksWithPhi = new HashSet<SsaBlock>(); |
||||
Queue<ControlFlowNode> worklist = new Queue<ControlFlowNode>(); |
||||
foreach (SsaInstruction writeInstruction in writeToOriginalVariables[variable.OriginalVariableIndex]) { |
||||
ControlFlowNode cfgNode = cfg.Nodes[writeInstruction.ParentBlock.BlockIndex]; |
||||
if (!cfgNode.Visited) { |
||||
cfgNode.Visited = true; |
||||
worklist.Enqueue(cfgNode); |
||||
} |
||||
} |
||||
while (worklist.Count > 0) { |
||||
ControlFlowNode cfgNode = worklist.Dequeue(); |
||||
foreach (ControlFlowNode dfNode in cfgNode.DominanceFrontier) { |
||||
// we don't need phi functions in the exit node
|
||||
if (dfNode.NodeType == ControlFlowNodeType.RegularExit || dfNode.NodeType == ControlFlowNodeType.ExceptionalExit) |
||||
continue; |
||||
SsaBlock y = ssaForm.Blocks[dfNode.BlockIndex]; |
||||
if (blocksWithPhi.Add(y)) { |
||||
// add a phi instruction in y
|
||||
SsaVariable[] operands = Enumerable.Repeat(variable, dfNode.Incoming.Count).ToArray(); |
||||
y.Instructions.Insert(0, new SsaInstruction(y, null, variable, operands, specialOpCode: SpecialOpCode.Phi)); |
||||
if (!dfNode.Visited) { |
||||
dfNode.Visited = true; |
||||
worklist.Enqueue(dfNode); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region RenameVariable
|
||||
int tempVariableCounter = 1; |
||||
|
||||
void RenameVariables(bool[] processVariable) |
||||
{ |
||||
VariableRenamer r = new VariableRenamer(this, processVariable); |
||||
r.Visit(ssaForm.EntryPoint); |
||||
} |
||||
|
||||
sealed class VariableRenamer |
||||
{ |
||||
readonly TransformToSsa transform; |
||||
readonly ReadOnlyCollection<SsaVariable> inputVariables; |
||||
internal readonly Stack<SsaVariable>[] versionStacks; |
||||
int[] versionCounters; // specifies for each input variable the next version number
|
||||
|
||||
// processVariable = specifies for each input variable whether we should rename it
|
||||
public VariableRenamer(TransformToSsa transform, bool[] processVariable) |
||||
{ |
||||
this.transform = transform; |
||||
this.inputVariables = transform.ssaForm.OriginalVariables; |
||||
Debug.Assert(inputVariables.Count == processVariable.Length); |
||||
this.versionCounters = new int[inputVariables.Count]; |
||||
this.versionStacks = new Stack<SsaVariable>[inputVariables.Count]; |
||||
for (int i = 0; i < versionStacks.Length; i++) { |
||||
if (processVariable[i]) { |
||||
Debug.Assert(inputVariables[i].IsSingleAssignment == false); |
||||
// only create version stacks for the variables that we need to process and that weren't already processed earlier
|
||||
versionStacks[i] = new Stack<SsaVariable>(); |
||||
versionStacks[i].Push(inputVariables[i]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
SsaVariable MakeNewVersion(int variableIndex) |
||||
{ |
||||
int versionCounter = ++versionCounters[variableIndex]; |
||||
SsaVariable x = inputVariables[variableIndex]; |
||||
if (versionCounter == 1) { |
||||
return x; |
||||
} else { |
||||
if (x.IsStackLocation) { |
||||
return new SsaVariable(x, "temp" + (transform.tempVariableCounter++)); |
||||
} else { |
||||
return new SsaVariable(x, x.Name + "_" + versionCounter); |
||||
} |
||||
} |
||||
} |
||||
|
||||
internal void Visit(SsaBlock block) |
||||
{ |
||||
// duplicate top of all stacks
|
||||
foreach (var stack in versionStacks) { |
||||
if (stack != null) |
||||
stack.Push(stack.Peek()); |
||||
} |
||||
|
||||
foreach (SsaInstruction s in block.Instructions) { |
||||
// replace all uses of variables being processed with their current version.
|
||||
if (s.SpecialOpCode != SpecialOpCode.Phi) { |
||||
for (int i = 0; i < s.Operands.Length; i++) { |
||||
var stack = versionStacks[s.Operands[i].OriginalVariableIndex]; |
||||
if (stack != null) |
||||
s.Operands[i] = stack.Peek(); |
||||
} |
||||
} |
||||
// if we're writing to a variable we should process:
|
||||
if (s.Target != null) { |
||||
int targetIndex = s.Target.OriginalVariableIndex; |
||||
if (versionStacks[targetIndex] != null) { |
||||
s.Target = MakeNewVersion(targetIndex); |
||||
s.Target.IsSingleAssignment = true; |
||||
s.Target.Definition = s; |
||||
|
||||
// we already pushed our entry for this SsaBlock at the beginning (where we duplicated all stacks),
|
||||
// so now replace the top element
|
||||
versionStacks[targetIndex].Pop(); |
||||
versionStacks[targetIndex].Push(s.Target); |
||||
} |
||||
} |
||||
} |
||||
|
||||
foreach (SsaBlock succ in block.Successors) { |
||||
int j = succ.Predecessors.IndexOf(block); |
||||
Debug.Assert(j >= 0); |
||||
foreach (SsaInstruction f in succ.Instructions) { |
||||
if (f.SpecialOpCode == SpecialOpCode.Phi) { |
||||
var stack = versionStacks[f.Target.OriginalVariableIndex]; |
||||
if (stack != null) { |
||||
f.Operands[j] = stack.Peek(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
foreach (ControlFlowNode child in transform.cfg.Nodes[block.BlockIndex].DominatorTreeChildren) |
||||
Visit(transform.ssaForm.Blocks[child.BlockIndex]); |
||||
// restore stacks:
|
||||
foreach (var stack in versionStacks) { |
||||
if (stack != null) |
||||
stack.Pop(); |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build"> |
||||
<PropertyGroup> |
||||
<ProjectGuid>{984CC812-9470-4A13-AFF9-CC44068D666C}</ProjectGuid> |
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> |
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> |
||||
<OutputType>Library</OutputType> |
||||
<RootNamespace>ICSharpCode.Decompiler</RootNamespace> |
||||
<AssemblyName>ICSharpCode.Decompiler</AssemblyName> |
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion> |
||||
<TargetFrameworkProfile>Client</TargetFrameworkProfile> |
||||
<AppDesignerFolder>Properties</AppDesignerFolder> |
||||
<AllowUnsafeBlocks>False</AllowUnsafeBlocks> |
||||
<NoStdLib>False</NoStdLib> |
||||
<WarningLevel>4</WarningLevel> |
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition=" '$(Platform)' == 'AnyCPU' "> |
||||
<PlatformTarget>AnyCPU</PlatformTarget> |
||||
<RegisterForComInterop>False</RegisterForComInterop> |
||||
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies> |
||||
<BaseAddress>4194304</BaseAddress> |
||||
<FileAlignment>4096</FileAlignment> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> |
||||
<OutputPath>bin\Debug\</OutputPath> |
||||
<DebugSymbols>true</DebugSymbols> |
||||
<DebugType>Full</DebugType> |
||||
<Optimize>False</Optimize> |
||||
<CheckForOverflowUnderflow>True</CheckForOverflowUnderflow> |
||||
<DefineConstants>DEBUG;TRACE</DefineConstants> |
||||
</PropertyGroup> |
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' "> |
||||
<OutputPath>bin\Release\</OutputPath> |
||||
<DebugSymbols>False</DebugSymbols> |
||||
<DebugType>None</DebugType> |
||||
<Optimize>True</Optimize> |
||||
<CheckForOverflowUnderflow>False</CheckForOverflowUnderflow> |
||||
<DefineConstants>TRACE</DefineConstants> |
||||
</PropertyGroup> |
||||
<ItemGroup> |
||||
<Reference Include="System" /> |
||||
<Reference Include="System.Core"> |
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework> |
||||
</Reference> |
||||
<Reference Include="System.Xml" /> |
||||
<Reference Include="System.Xml.Linq"> |
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework> |
||||
</Reference> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<Compile Include="..\..\Main\GlobalAssemblyInfo.cs"> |
||||
<Link>Properties\GlobalAssemblyInfo.cs</Link> |
||||
</Compile> |
||||
<Compile Include="Ast\AstBuilder.cs" /> |
||||
<Compile Include="Ast\AstMethodBodyBuilder.cs" /> |
||||
<Compile Include="Ast\CecilTypeResolveContext.cs" /> |
||||
<Compile Include="Ast\CommentStatement.cs" /> |
||||
<Compile Include="Ast\DecompilerContext.cs" /> |
||||
<Compile Include="Ast\NameVariables.cs" /> |
||||
<Compile Include="Ast\NRefactoryExtensions.cs" /> |
||||
<Compile Include="Ast\TextOutputFormatter.cs" /> |
||||
<Compile Include="Ast\Transforms\AddCheckedBlocks.cs" /> |
||||
<Compile Include="Ast\Transforms\CombineQueryExpressions.cs" /> |
||||
<Compile Include="Ast\Transforms\ContextTrackingVisitor.cs" /> |
||||
<Compile Include="Ast\Transforms\ConvertConstructorCallIntoInitializer.cs" /> |
||||
<Compile Include="Ast\Transforms\DecimalConstantTransform.cs" /> |
||||
<Compile Include="Ast\Transforms\DeclareVariables.cs" /> |
||||
<Compile Include="Ast\Transforms\DelegateConstruction.cs" /> |
||||
<Compile Include="Ast\Transforms\IntroduceExtensionMethods.cs" /> |
||||
<Compile Include="Ast\Transforms\IntroduceQueryExpressions.cs" /> |
||||
<Compile Include="Ast\Transforms\IntroduceUnsafeModifier.cs" /> |
||||
<Compile Include="Ast\Transforms\IntroduceUsingDeclarations.cs" /> |
||||
<Compile Include="Ast\Transforms\ReplaceMethodCallsWithOperators.cs" /> |
||||
<Compile Include="Ast\Transforms\PushNegation.cs" /> |
||||
<Compile Include="Ast\Transforms\TransformationPipeline.cs" /> |
||||
<Compile Include="Ast\Transforms\PatternStatementTransform.cs" /> |
||||
<Compile Include="Ast\TypesHierarchyHelpers.cs" /> |
||||
<Compile Include="CecilExtensions.cs" /> |
||||
<Compile Include="CodeMappings.cs" /> |
||||
<Compile Include="DecompilerException.cs" /> |
||||
<Compile Include="DecompilerSettings.cs" /> |
||||
<Compile Include="Disassembler\DisassemblerHelpers.cs" /> |
||||
<Compile Include="Disassembler\ILStructure.cs" /> |
||||
<Compile Include="Disassembler\MethodBodyDisassembler.cs" /> |
||||
<Compile Include="Disassembler\ReflectionDisassembler.cs" /> |
||||
<Compile Include="FlowAnalysis\ControlFlowEdge.cs" /> |
||||
<Compile Include="FlowAnalysis\ControlFlowGraph.cs" /> |
||||
<Compile Include="FlowAnalysis\ControlFlowGraphBuilder.cs" /> |
||||
<Compile Include="FlowAnalysis\ControlFlowNode.cs" /> |
||||
<Compile Include="FlowAnalysis\ControlStructureDetector.cs" /> |
||||
<Compile Include="FlowAnalysis\OpCodeInfo.cs" /> |
||||
<Compile Include="FlowAnalysis\SimplifyByRefCalls.cs" /> |
||||
<Compile Include="FlowAnalysis\SsaBlock.cs" /> |
||||
<Compile Include="FlowAnalysis\SsaForm.cs" /> |
||||
<Compile Include="FlowAnalysis\SsaFormBuilder.cs" /> |
||||
<Compile Include="FlowAnalysis\SsaInstruction.cs" /> |
||||
<Compile Include="FlowAnalysis\SsaOptimization.cs" /> |
||||
<Compile Include="FlowAnalysis\SsaVariable.cs" /> |
||||
<Compile Include="FlowAnalysis\TransformToSsa.cs" /> |
||||
<Compile Include="ILAst\InitializerPeepholeTransforms.cs" /> |
||||
<Compile Include="ILAst\DefaultDictionary.cs" /> |
||||
<Compile Include="ILAst\GotoRemoval.cs" /> |
||||
<Compile Include="ILAst\ILAstBuilder.cs" /> |
||||
<Compile Include="ILAst\ILAstOptimizer.cs" /> |
||||
<Compile Include="ILAst\ILAstTypes.cs" /> |
||||
<Compile Include="ILAst\ILCodes.cs" /> |
||||
<Compile Include="ILAst\ILInlining.cs" /> |
||||
<Compile Include="ILAst\LoopsAndConditions.cs" /> |
||||
<Compile Include="ILAst\PatternMatching.cs" /> |
||||
<Compile Include="ILAst\PeepholeTransform.cs" /> |
||||
<Compile Include="ILAst\SimpleControlFlow.cs" /> |
||||
<Compile Include="ILAst\TypeAnalysis.cs" /> |
||||
<Compile Include="ILAst\YieldReturnDecompiler.cs" /> |
||||
<Compile Include="ITextOutput.cs" /> |
||||
<Compile Include="PlainTextOutput.cs" /> |
||||
<Compile Include="ReferenceResolvingException.cs" /> |
||||
<Compile Include="TextOutputWriter.cs" /> |
||||
<None Include="Properties\AssemblyInfo.cs" /> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<ProjectReference Include="..\Mono.Cecil\Mono.Cecil.csproj"> |
||||
<Project>{D68133BD-1E63-496E-9EDE-4FBDBF77B486}</Project> |
||||
<Name>Mono.Cecil</Name> |
||||
</ProjectReference> |
||||
<ProjectReference Include="..\NewNRefactory\ICSharpCode.NRefactory\ICSharpCode.NRefactory.csproj"> |
||||
<Project>{3B2A5653-EC97-4001-BB9B-D90F1AF2C371}</Project> |
||||
<Name>ICSharpCode.NRefactory</Name> |
||||
</ProjectReference> |
||||
</ItemGroup> |
||||
<ItemGroup> |
||||
<Folder Include="Ast" /> |
||||
<Folder Include="Ast\Transforms" /> |
||||
<Folder Include="Disassembler" /> |
||||
<Folder Include="ILAst" /> |
||||
</ItemGroup> |
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.Targets" /> |
||||
</Project> |
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections; |
||||
using System.Collections.Generic; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
/// <summary>
|
||||
/// Dictionary with default values.
|
||||
/// </summary>
|
||||
sealed class DefaultDictionary<TKey, TValue> : IDictionary<TKey, TValue> |
||||
{ |
||||
readonly IDictionary<TKey, TValue> dict; |
||||
readonly Func<TKey, TValue> defaultProvider; |
||||
|
||||
public DefaultDictionary(TValue defaultValue, IDictionary<TKey, TValue> dictionary = null) |
||||
: this(key => defaultValue, dictionary) |
||||
{ |
||||
} |
||||
|
||||
public DefaultDictionary(Func<TKey, TValue> defaultProvider = null, IDictionary<TKey, TValue> dictionary = null) |
||||
{ |
||||
this.dict = dictionary ?? new Dictionary<TKey, TValue>(); |
||||
this.defaultProvider = defaultProvider ?? (key => default(TValue)); |
||||
} |
||||
|
||||
public TValue this[TKey key] { |
||||
get { |
||||
TValue val; |
||||
if (dict.TryGetValue(key, out val)) |
||||
return val; |
||||
else |
||||
return dict[key] = defaultProvider(key); |
||||
} |
||||
set { |
||||
dict[key] = value; |
||||
} |
||||
} |
||||
|
||||
public ICollection<TKey> Keys { |
||||
get { return dict.Keys; } |
||||
} |
||||
|
||||
public ICollection<TValue> Values { |
||||
get { return dict.Values; } |
||||
} |
||||
|
||||
public int Count { |
||||
get { return dict.Count; } |
||||
} |
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly { |
||||
get { return false; } |
||||
} |
||||
|
||||
public bool ContainsKey(TKey key) |
||||
{ |
||||
return dict.ContainsKey(key); |
||||
} |
||||
|
||||
public void Add(TKey key, TValue value) |
||||
{ |
||||
dict.Add(key, value); |
||||
} |
||||
|
||||
public bool Remove(TKey key) |
||||
{ |
||||
return dict.Remove(key); |
||||
} |
||||
|
||||
public bool TryGetValue(TKey key, out TValue value) |
||||
{ |
||||
return dict.TryGetValue(key, out value); |
||||
} |
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item) |
||||
{ |
||||
dict.Add(item); |
||||
} |
||||
|
||||
public void Clear() |
||||
{ |
||||
dict.Clear(); |
||||
} |
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) |
||||
{ |
||||
return dict.Contains(item); |
||||
} |
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) |
||||
{ |
||||
dict.CopyTo(array, arrayIndex); |
||||
} |
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) |
||||
{ |
||||
return dict.Remove(item); |
||||
} |
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() |
||||
{ |
||||
return dict.GetEnumerator(); |
||||
} |
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
||||
{ |
||||
return dict.GetEnumerator(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,319 @@
@@ -0,0 +1,319 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public class GotoRemoval |
||||
{ |
||||
Dictionary<ILNode, ILNode> parent = new Dictionary<ILNode, ILNode>(); |
||||
Dictionary<ILNode, ILNode> nextSibling = new Dictionary<ILNode, ILNode>(); |
||||
|
||||
public void RemoveGotos(ILBlock method) |
||||
{ |
||||
// Build the navigation data
|
||||
parent[method] = null; |
||||
foreach (ILNode node in method.GetSelfAndChildrenRecursive<ILNode>()) { |
||||
ILNode previousChild = null; |
||||
foreach (ILNode child in node.GetChildren()) { |
||||
if (parent.ContainsKey(child)) |
||||
throw new Exception("The following expression is linked from several locations: " + child.ToString()); |
||||
parent[child] = node; |
||||
if (previousChild != null) |
||||
nextSibling[previousChild] = child; |
||||
previousChild = child; |
||||
} |
||||
if (previousChild != null) |
||||
nextSibling[previousChild] = null; |
||||
} |
||||
|
||||
// Simplify gotos
|
||||
bool modified; |
||||
do { |
||||
modified = false; |
||||
foreach (ILExpression gotoExpr in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.Code == ILCode.Br || e.Code == ILCode.Leave)) { |
||||
modified |= TrySimplifyGoto(gotoExpr); |
||||
} |
||||
} while(modified); |
||||
|
||||
RemoveRedundantCode(method); |
||||
} |
||||
|
||||
public static void RemoveRedundantCode(ILBlock method) |
||||
{ |
||||
// Remove dead lables and nops
|
||||
HashSet<ILLabel> liveLabels = new HashSet<ILLabel>(method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())); |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
block.Body = block.Body.Where(n => !n.Match(ILCode.Nop) && !(n is ILLabel && !liveLabels.Contains((ILLabel)n))).ToList(); |
||||
} |
||||
|
||||
// Remove redundant continue
|
||||
foreach(ILWhileLoop loop in method.GetSelfAndChildrenRecursive<ILWhileLoop>()) { |
||||
var body = loop.BodyBlock.Body; |
||||
if (body.Count > 0 && body.Last().Match(ILCode.LoopContinue)) { |
||||
body.RemoveAt(body.Count - 1); |
||||
} |
||||
} |
||||
|
||||
// Remove redundant break at the end of case
|
||||
// Remove redundant case blocks altogether
|
||||
foreach(ILSwitch ilSwitch in method.GetSelfAndChildrenRecursive<ILSwitch>()) { |
||||
foreach(ILBlock ilCase in ilSwitch.CaseBlocks) { |
||||
Debug.Assert(ilCase.EntryGoto == null); |
||||
|
||||
int count = ilCase.Body.Count; |
||||
if (count >= 2) { |
||||
if (ilCase.Body[count - 2].IsUnconditionalControlFlow() && |
||||
ilCase.Body[count - 1].Match(ILCode.LoopOrSwitchBreak)) |
||||
{ |
||||
ilCase.Body.RemoveAt(count - 1); |
||||
} |
||||
} |
||||
} |
||||
|
||||
var defaultCase = ilSwitch.CaseBlocks.SingleOrDefault(cb => cb.Values == null); |
||||
// If there is no default block, remove empty case blocks
|
||||
if (defaultCase == null || (defaultCase.Body.Count == 1 && defaultCase.Body.Single().Match(ILCode.LoopOrSwitchBreak))) { |
||||
ilSwitch.CaseBlocks.RemoveAll(b => b.Body.Count == 1 && b.Body.Single().Match(ILCode.LoopOrSwitchBreak)); |
||||
} |
||||
} |
||||
|
||||
// Remove redundant return at the end of method
|
||||
if (method.Body.Count > 0 && method.Body.Last().Match(ILCode.Ret) && ((ILExpression)method.Body.Last()).Arguments.Count == 0) { |
||||
method.Body.RemoveAt(method.Body.Count - 1); |
||||
} |
||||
|
||||
// Remove unreachable return statements
|
||||
bool modified = false; |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int i = 0; i < block.Body.Count - 1;) { |
||||
if (block.Body[i].IsUnconditionalControlFlow() && block.Body[i+1].Match(ILCode.Ret)) { |
||||
modified = true; |
||||
block.Body.RemoveAt(i+1); |
||||
} else { |
||||
i++; |
||||
} |
||||
} |
||||
} |
||||
if (modified) { |
||||
// More removals might be possible
|
||||
new GotoRemoval().RemoveGotos(method); |
||||
} |
||||
} |
||||
|
||||
IEnumerable<ILNode> GetParents(ILNode node) |
||||
{ |
||||
ILNode current = node; |
||||
while(true) { |
||||
current = parent[current]; |
||||
if (current == null) |
||||
yield break; |
||||
yield return current; |
||||
} |
||||
} |
||||
|
||||
bool TrySimplifyGoto(ILExpression gotoExpr) |
||||
{ |
||||
Debug.Assert(gotoExpr.Code == ILCode.Br || gotoExpr.Code == ILCode.Leave); |
||||
Debug.Assert(gotoExpr.Prefixes == null); |
||||
Debug.Assert(gotoExpr.Operand != null); |
||||
|
||||
ILNode target = Enter(gotoExpr, new HashSet<ILNode>()); |
||||
if (target == null) |
||||
return false; |
||||
|
||||
// The gotoExper is marked as visited because we do not want to
|
||||
// walk over node which we plan to modify
|
||||
|
||||
// The simulated path always has to start in the same try-block
|
||||
// in other for the same finally blocks to be executed.
|
||||
|
||||
if (target == Exit(gotoExpr, new HashSet<ILNode>() { gotoExpr })) { |
||||
gotoExpr.Code = ILCode.Nop; |
||||
gotoExpr.Operand = null; |
||||
if (target is ILExpression) |
||||
((ILExpression)target).ILRanges.AddRange(gotoExpr.ILRanges); |
||||
gotoExpr.ILRanges.Clear(); |
||||
return true; |
||||
} |
||||
|
||||
ILNode breakBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop || n is ILSwitch); |
||||
if (breakBlock != null && target == Exit(breakBlock, new HashSet<ILNode>() { gotoExpr })) { |
||||
gotoExpr.Code = ILCode.LoopOrSwitchBreak; |
||||
gotoExpr.Operand = null; |
||||
return true; |
||||
} |
||||
|
||||
ILNode continueBlock = GetParents(gotoExpr).FirstOrDefault(n => n is ILWhileLoop); |
||||
if (continueBlock != null && target == Enter(continueBlock, new HashSet<ILNode>() { gotoExpr })) { |
||||
gotoExpr.Code = ILCode.LoopContinue; |
||||
gotoExpr.Operand = null; |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Get the first expression to be excecuted if the instruction pointer is at the start of the given node.
|
||||
/// Try blocks may not be entered in any way. If possible, the try block is returned as the node to be executed.
|
||||
/// </summary>
|
||||
ILNode Enter(ILNode node, HashSet<ILNode> visitedNodes) |
||||
{ |
||||
if (node == null) |
||||
throw new ArgumentNullException(); |
||||
|
||||
if (!visitedNodes.Add(node)) |
||||
return null; // Infinite loop
|
||||
|
||||
ILLabel label = node as ILLabel; |
||||
if (label != null) { |
||||
return Exit(label, visitedNodes); |
||||
} |
||||
|
||||
ILExpression expr = node as ILExpression; |
||||
if (expr != null) { |
||||
if (expr.Code == ILCode.Br || expr.Code == ILCode.Leave) { |
||||
ILLabel target = (ILLabel)expr.Operand; |
||||
// Early exit - same try-block
|
||||
if (GetParents(expr).OfType<ILTryCatchBlock>().FirstOrDefault() == GetParents(target).OfType<ILTryCatchBlock>().FirstOrDefault()) |
||||
return Enter(target, visitedNodes); |
||||
// Make sure we are not entering any try-block
|
||||
var srcTryBlocks = GetParents(expr).OfType<ILTryCatchBlock>().Reverse().ToList(); |
||||
var dstTryBlocks = GetParents(target).OfType<ILTryCatchBlock>().Reverse().ToList(); |
||||
// Skip blocks that we are already in
|
||||
int i = 0; |
||||
while(i < srcTryBlocks.Count && i < dstTryBlocks.Count && srcTryBlocks[i] == dstTryBlocks[i]) i++; |
||||
if (i == dstTryBlocks.Count) { |
||||
return Enter(target, visitedNodes); |
||||
} else { |
||||
ILTryCatchBlock dstTryBlock = dstTryBlocks[i]; |
||||
// Check that the goto points to the start
|
||||
ILTryCatchBlock current = dstTryBlock; |
||||
while(current != null) { |
||||
foreach(ILNode n in current.TryBlock.Body) { |
||||
if (n is ILLabel) { |
||||
if (n == target) |
||||
return dstTryBlock; |
||||
} else if (!n.Match(ILCode.Nop)) { |
||||
current = n as ILTryCatchBlock; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
} else if (expr.Code == ILCode.Nop) { |
||||
return Exit(expr, visitedNodes); |
||||
} else if (expr.Code == ILCode.LoopOrSwitchBreak) { |
||||
ILNode breakBlock = GetParents(expr).First(n => n is ILWhileLoop || n is ILSwitch); |
||||
return Exit(breakBlock, new HashSet<ILNode>() { expr }); |
||||
} else if (expr.Code == ILCode.LoopContinue) { |
||||
ILNode continueBlock = GetParents(expr).First(n => n is ILWhileLoop); |
||||
return Enter(continueBlock, new HashSet<ILNode>() { expr }); |
||||
} else { |
||||
return expr; |
||||
} |
||||
} |
||||
|
||||
ILBlock block = node as ILBlock; |
||||
if (block != null) { |
||||
if (block.EntryGoto != null) { |
||||
return Enter(block.EntryGoto, visitedNodes); |
||||
} else if (block.Body.Count > 0) { |
||||
return Enter(block.Body[0], visitedNodes); |
||||
} else { |
||||
return Exit(block, visitedNodes); |
||||
} |
||||
} |
||||
|
||||
ILCondition cond = node as ILCondition; |
||||
if (cond != null) { |
||||
return cond.Condition; |
||||
} |
||||
|
||||
ILWhileLoop loop = node as ILWhileLoop; |
||||
if (loop != null) { |
||||
if (loop.Condition != null) { |
||||
return loop.Condition; |
||||
} else { |
||||
return Enter(loop.BodyBlock, visitedNodes); |
||||
} |
||||
} |
||||
|
||||
ILTryCatchBlock tryCatch = node as ILTryCatchBlock; |
||||
if (tryCatch != null) { |
||||
return tryCatch; |
||||
} |
||||
|
||||
ILSwitch ilSwitch = node as ILSwitch; |
||||
if (ilSwitch != null) { |
||||
return ilSwitch.Condition; |
||||
} |
||||
|
||||
throw new NotSupportedException(node.GetType().ToString()); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Get the first expression to be excecuted if the instruction pointer is at the end of the given node
|
||||
/// </summary>
|
||||
ILNode Exit(ILNode node, HashSet<ILNode> visitedNodes) |
||||
{ |
||||
if (node == null) |
||||
throw new ArgumentNullException(); |
||||
|
||||
ILNode nodeParent = parent[node]; |
||||
if (nodeParent == null) |
||||
return null; // Exited main body
|
||||
|
||||
if (nodeParent is ILBlock) { |
||||
ILNode nextNode = nextSibling[node]; |
||||
if (nextNode != null) { |
||||
return Enter(nextNode, visitedNodes); |
||||
} else { |
||||
return Exit(nodeParent, visitedNodes); |
||||
} |
||||
} |
||||
|
||||
if (nodeParent is ILCondition) { |
||||
return Exit(nodeParent, visitedNodes); |
||||
} |
||||
|
||||
if (nodeParent is ILTryCatchBlock) { |
||||
// Finally blocks are completely ignored.
|
||||
// We rely on the fact that try blocks can not be entered.
|
||||
return Exit(nodeParent, visitedNodes); |
||||
} |
||||
|
||||
if (nodeParent is ILSwitch) { |
||||
return null; // Implicit exit from switch is not allowed
|
||||
} |
||||
|
||||
if (nodeParent is ILWhileLoop) { |
||||
return Enter(nodeParent, visitedNodes); |
||||
} |
||||
|
||||
throw new NotSupportedException(nodeParent.GetType().ToString()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,823 @@
@@ -0,0 +1,823 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Concurrent; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Text; |
||||
|
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
using Cecil = Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public class ILAstBuilder |
||||
{ |
||||
static ByteCode[] EmptyByteCodeArray = new ByteCode[] {}; |
||||
|
||||
/// <summary> Immutable </summary>
|
||||
class StackSlot |
||||
{ |
||||
public readonly ByteCode[] PushedBy; // One of those
|
||||
public readonly ILVariable LoadFrom; // Where can we get the value from in AST
|
||||
|
||||
public StackSlot(ByteCode[] pushedBy, ILVariable loadFrom) |
||||
{ |
||||
this.PushedBy = pushedBy; |
||||
this.LoadFrom = loadFrom; |
||||
} |
||||
|
||||
public StackSlot(ByteCode pushedBy) |
||||
{ |
||||
this.PushedBy = new[] { pushedBy }; |
||||
this.LoadFrom = null; |
||||
} |
||||
|
||||
public static List<StackSlot> CloneStack(List<StackSlot> stack, int? popCount) |
||||
{ |
||||
if (popCount.HasValue) { |
||||
return stack.GetRange(0, stack.Count - popCount.Value); |
||||
} else { |
||||
return new List<StackSlot>(0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary> Immutable </summary>
|
||||
class VariableSlot |
||||
{ |
||||
public readonly ByteCode[] StoredBy; // One of those
|
||||
public readonly bool StoredByAll; // Overestimate which is useful for exceptional control flow.
|
||||
|
||||
public VariableSlot(ByteCode[] storedBy, bool storedByAll) |
||||
{ |
||||
this.StoredBy = storedBy; |
||||
this.StoredByAll = storedByAll; |
||||
} |
||||
|
||||
public VariableSlot(ByteCode storedBy) |
||||
{ |
||||
this.StoredBy = new[] { storedBy }; |
||||
this.StoredByAll = false; |
||||
} |
||||
|
||||
public static VariableSlot[] CloneVariableState(VariableSlot[] state) |
||||
{ |
||||
VariableSlot[] clone = new VariableSlot[state.Length]; |
||||
for (int i = 0; i < clone.Length; i++) { |
||||
clone[i] = state[i]; |
||||
} |
||||
return clone; |
||||
} |
||||
|
||||
public static VariableSlot[] MakeEmptyState(int varCount) |
||||
{ |
||||
VariableSlot[] emptyVariableState = new VariableSlot[varCount]; |
||||
for (int i = 0; i < emptyVariableState.Length; i++) { |
||||
emptyVariableState[i] = new VariableSlot(EmptyByteCodeArray, false); |
||||
} |
||||
return emptyVariableState; |
||||
} |
||||
|
||||
public static VariableSlot[] MakeFullState(int varCount) |
||||
{ |
||||
VariableSlot[] unknownVariableState = new VariableSlot[varCount]; |
||||
for (int i = 0; i < unknownVariableState.Length; i++) { |
||||
unknownVariableState[i] = new VariableSlot(EmptyByteCodeArray, true); |
||||
} |
||||
return unknownVariableState; |
||||
} |
||||
} |
||||
|
||||
class ByteCode |
||||
{ |
||||
public ILLabel Label; // Non-null only if needed
|
||||
public int Offset; |
||||
public int EndOffset; |
||||
public ILCode Code; |
||||
public object Operand; |
||||
public int? PopCount; // Null means pop all
|
||||
public int PushCount; |
||||
public string Name { get { return "IL_" + this.Offset.ToString("X2"); } } |
||||
public ByteCode Next; |
||||
public Instruction[] Prefixes; // Non-null only if needed
|
||||
public List<StackSlot> StackBefore; // Unique per bytecode; not shared
|
||||
public List<ILVariable> StoreTo; // Store result of instruction to those AST variables
|
||||
public VariableSlot[] VariablesBefore; // Unique per bytecode; not shared
|
||||
|
||||
public VariableDefinition OperandAsVariable { get { return (VariableDefinition)this.Operand; } } |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringBuilder sb = new StringBuilder(); |
||||
|
||||
// Label
|
||||
sb.Append(this.Name); |
||||
sb.Append(':'); |
||||
if (this.Label != null) |
||||
sb.Append('*'); |
||||
|
||||
// Name
|
||||
sb.Append(' '); |
||||
if (this.Prefixes != null) { |
||||
foreach (var prefix in this.Prefixes) { |
||||
sb.Append(prefix.OpCode.Name); |
||||
sb.Append(' '); |
||||
} |
||||
} |
||||
sb.Append(this.Code.GetName()); |
||||
|
||||
if (this.Operand != null) { |
||||
sb.Append(' '); |
||||
if (this.Operand is Instruction) { |
||||
sb.Append("IL_" + ((Instruction)this.Operand).Offset.ToString("X2")); |
||||
} else if (this.Operand is Instruction[]) { |
||||
foreach(Instruction inst in (Instruction[])this.Operand) { |
||||
sb.Append("IL_" + inst.Offset.ToString("X2")); |
||||
sb.Append(" "); |
||||
} |
||||
} else if (this.Operand is ILLabel) { |
||||
sb.Append(((ILLabel)this.Operand).Name); |
||||
} else if (this.Operand is ILLabel[]) { |
||||
foreach(ILLabel label in (ILLabel[])this.Operand) { |
||||
sb.Append(label.Name); |
||||
sb.Append(" "); |
||||
} |
||||
} else { |
||||
sb.Append(this.Operand.ToString()); |
||||
} |
||||
} |
||||
|
||||
if (this.StackBefore != null) { |
||||
sb.Append(" StackBefore={"); |
||||
bool first = true; |
||||
foreach (StackSlot slot in this.StackBefore) { |
||||
if (!first) sb.Append(","); |
||||
bool first2 = true; |
||||
foreach(ByteCode pushedBy in slot.PushedBy) { |
||||
if (!first2) sb.Append("|"); |
||||
sb.AppendFormat("IL_{0:X2}", pushedBy.Offset); |
||||
first2 = false; |
||||
} |
||||
first = false; |
||||
} |
||||
sb.Append("}"); |
||||
} |
||||
|
||||
if (this.StoreTo != null && this.StoreTo.Count > 0) { |
||||
sb.Append(" StoreTo={"); |
||||
bool first = true; |
||||
foreach (ILVariable stackVar in this.StoreTo) { |
||||
if (!first) sb.Append(","); |
||||
sb.Append(stackVar.Name); |
||||
first = false; |
||||
} |
||||
sb.Append("}"); |
||||
} |
||||
|
||||
if (this.VariablesBefore != null) { |
||||
sb.Append(" VarsBefore={"); |
||||
bool first = true; |
||||
foreach (VariableSlot varSlot in this.VariablesBefore) { |
||||
if (!first) sb.Append(","); |
||||
if (varSlot.StoredByAll) { |
||||
sb.Append("*"); |
||||
} else if (varSlot.StoredBy.Length == 0) { |
||||
sb.Append("_"); |
||||
} else { |
||||
bool first2 = true; |
||||
foreach (ByteCode storedBy in varSlot.StoredBy) { |
||||
if (!first2) sb.Append("|"); |
||||
sb.AppendFormat("IL_{0:X2}", storedBy.Offset); |
||||
first2 = false; |
||||
} |
||||
} |
||||
first = false; |
||||
} |
||||
sb.Append("}"); |
||||
} |
||||
|
||||
return sb.ToString(); |
||||
} |
||||
} |
||||
|
||||
MethodDefinition methodDef; |
||||
bool optimize; |
||||
|
||||
// Virtual instructions to load exception on stack
|
||||
Dictionary<ExceptionHandler, ByteCode> ldexceptions = new Dictionary<ExceptionHandler, ILAstBuilder.ByteCode>(); |
||||
|
||||
public List<ILNode> Build(MethodDefinition methodDef, bool optimize) |
||||
{ |
||||
this.methodDef = methodDef; |
||||
this.optimize = optimize; |
||||
|
||||
if (methodDef.Body.Instructions.Count == 0) return new List<ILNode>(); |
||||
|
||||
List<ByteCode> body = StackAnalysis(methodDef); |
||||
|
||||
List<ILNode> ast = ConvertToAst(body, new HashSet<ExceptionHandler>(methodDef.Body.ExceptionHandlers)); |
||||
|
||||
return ast; |
||||
} |
||||
|
||||
List<ByteCode> StackAnalysis(MethodDefinition methodDef) |
||||
{ |
||||
Dictionary<Instruction, ByteCode> instrToByteCode = new Dictionary<Instruction, ByteCode>(); |
||||
|
||||
// Create temporary structure for the stack analysis
|
||||
List<ByteCode> body = new List<ByteCode>(methodDef.Body.Instructions.Count); |
||||
List<Instruction> prefixes = null; |
||||
foreach(Instruction inst in methodDef.Body.Instructions) { |
||||
if (inst.OpCode.OpCodeType == OpCodeType.Prefix) { |
||||
if (prefixes == null) |
||||
prefixes = new List<Instruction>(1); |
||||
prefixes.Add(inst); |
||||
continue; |
||||
} |
||||
ILCode code = (ILCode)inst.OpCode.Code; |
||||
object operand = inst.Operand; |
||||
ILCodeUtil.ExpandMacro(ref code, ref operand, methodDef.Body); |
||||
ByteCode byteCode = new ByteCode() { |
||||
Offset = inst.Offset, |
||||
EndOffset = inst.Next != null ? inst.Next.Offset : methodDef.Body.CodeSize, |
||||
Code = code, |
||||
Operand = operand, |
||||
PopCount = inst.GetPopDelta(), |
||||
PushCount = inst.GetPushDelta() |
||||
}; |
||||
if (prefixes != null) { |
||||
instrToByteCode[prefixes[0]] = byteCode; |
||||
byteCode.Offset = prefixes[0].Offset; |
||||
byteCode.Prefixes = prefixes.ToArray(); |
||||
prefixes = null; |
||||
} else { |
||||
instrToByteCode[inst] = byteCode; |
||||
} |
||||
body.Add(byteCode); |
||||
} |
||||
for (int i = 0; i < body.Count - 1; i++) { |
||||
body[i].Next = body[i + 1]; |
||||
} |
||||
|
||||
Stack<ByteCode> agenda = new Stack<ByteCode>(); |
||||
|
||||
int varCount = methodDef.Body.Variables.Count; |
||||
|
||||
var exceptionHandlerStarts = new HashSet<ByteCode>(methodDef.Body.ExceptionHandlers.Select(eh => instrToByteCode[eh.HandlerStart])); |
||||
|
||||
// Add known states
|
||||
if(methodDef.Body.HasExceptionHandlers) { |
||||
foreach(ExceptionHandler ex in methodDef.Body.ExceptionHandlers) { |
||||
ByteCode handlerStart = instrToByteCode[ex.HandlerStart]; |
||||
handlerStart.StackBefore = new List<StackSlot>(); |
||||
handlerStart.VariablesBefore = VariableSlot.MakeFullState(varCount); |
||||
if (ex.HandlerType == ExceptionHandlerType.Catch || ex.HandlerType == ExceptionHandlerType.Filter) { |
||||
// Catch and Filter handlers start with the exeption on the stack
|
||||
ByteCode ldexception = new ByteCode() { |
||||
Code = ILCode.Ldexception, |
||||
Operand = ex.CatchType, |
||||
PopCount = 0, |
||||
PushCount = 1 |
||||
}; |
||||
ldexceptions[ex] = ldexception; |
||||
handlerStart.StackBefore.Add(new StackSlot(ldexception)); |
||||
} |
||||
agenda.Push(handlerStart); |
||||
|
||||
if (ex.HandlerType == ExceptionHandlerType.Filter) |
||||
{ |
||||
ByteCode filterStart = instrToByteCode[ex.FilterStart]; |
||||
filterStart.StackBefore = new List<StackSlot>(); |
||||
filterStart.VariablesBefore = VariableSlot.MakeFullState(varCount); |
||||
ByteCode ldexception = new ByteCode() { |
||||
Code = ILCode.Ldexception, |
||||
Operand = ex.CatchType, |
||||
PopCount = 0, |
||||
PushCount = 1 |
||||
}; |
||||
// TODO: ldexceptions[ex] = ldexception;
|
||||
filterStart.StackBefore.Add(new StackSlot(ldexception)); |
||||
agenda.Push(filterStart); |
||||
} |
||||
} |
||||
} |
||||
|
||||
body[0].StackBefore = new List<StackSlot>(); |
||||
body[0].VariablesBefore = VariableSlot.MakeEmptyState(varCount); |
||||
agenda.Push(body[0]); |
||||
|
||||
// Process agenda
|
||||
while(agenda.Count > 0) { |
||||
ByteCode byteCode = agenda.Pop(); |
||||
|
||||
// Calculate new stack
|
||||
List<StackSlot> newStack = StackSlot.CloneStack(byteCode.StackBefore, byteCode.PopCount); |
||||
for (int i = 0; i < byteCode.PushCount; i++) { |
||||
newStack.Add(new StackSlot(byteCode)); |
||||
} |
||||
|
||||
// Calculate new variable state
|
||||
VariableSlot[] newVariableState = VariableSlot.CloneVariableState(byteCode.VariablesBefore); |
||||
if (byteCode.Code == ILCode.Stloc) { |
||||
int varIndex = ((VariableReference)byteCode.Operand).Index; |
||||
newVariableState[varIndex] = new VariableSlot(byteCode); |
||||
} |
||||
|
||||
// After the leave, finally block might have touched the variables
|
||||
if (byteCode.Code == ILCode.Leave) { |
||||
newVariableState = VariableSlot.MakeFullState(varCount); |
||||
} |
||||
|
||||
// Find all successors
|
||||
List<ByteCode> branchTargets = new List<ByteCode>(); |
||||
if (!byteCode.Code.IsUnconditionalControlFlow()) { |
||||
if (exceptionHandlerStarts.Contains(byteCode.Next)) { |
||||
// Do not fall though down to exception handler
|
||||
// It is invalid IL as per ECMA-335 §12.4.2.8.1, but some obfuscators produce it
|
||||
} else { |
||||
branchTargets.Add(byteCode.Next); |
||||
} |
||||
} |
||||
if (byteCode.Operand is Instruction[]) { |
||||
foreach(Instruction inst in (Instruction[])byteCode.Operand) { |
||||
ByteCode target = instrToByteCode[inst]; |
||||
branchTargets.Add(target); |
||||
// The target of a branch must have label
|
||||
if (target.Label == null) { |
||||
target.Label = new ILLabel() { Name = target.Name }; |
||||
} |
||||
} |
||||
} else if (byteCode.Operand is Instruction) { |
||||
ByteCode target = instrToByteCode[(Instruction)byteCode.Operand]; |
||||
branchTargets.Add(target); |
||||
// The target of a branch must have label
|
||||
if (target.Label == null) { |
||||
target.Label = new ILLabel() { Name = target.Name }; |
||||
} |
||||
} |
||||
|
||||
// Apply the state to successors
|
||||
foreach (ByteCode branchTarget in branchTargets) { |
||||
if (branchTarget.StackBefore == null && branchTarget.VariablesBefore == null) { |
||||
if (branchTargets.Count == 1) { |
||||
branchTarget.StackBefore = newStack; |
||||
branchTarget.VariablesBefore = newVariableState; |
||||
} else { |
||||
// Do not share data for several bytecodes
|
||||
branchTarget.StackBefore = StackSlot.CloneStack(newStack, 0); |
||||
branchTarget.VariablesBefore = VariableSlot.CloneVariableState(newVariableState); |
||||
} |
||||
agenda.Push(branchTarget); |
||||
} else { |
||||
if (branchTarget.StackBefore.Count != newStack.Count) { |
||||
throw new Exception("Inconsistent stack size at " + byteCode.Name); |
||||
} |
||||
|
||||
// Be careful not to change our new data - it might be reused for several branch targets.
|
||||
// In general, be careful that two bytecodes never share data structures.
|
||||
|
||||
bool modified = false; |
||||
|
||||
// Merge stacks - modify the target
|
||||
for (int i = 0; i < newStack.Count; i++) { |
||||
ByteCode[] oldPushedBy = branchTarget.StackBefore[i].PushedBy; |
||||
ByteCode[] newPushedBy = oldPushedBy.Union(newStack[i].PushedBy); |
||||
if (newPushedBy.Length > oldPushedBy.Length) { |
||||
branchTarget.StackBefore[i] = new StackSlot(newPushedBy, null); |
||||
modified = true; |
||||
} |
||||
} |
||||
|
||||
// Merge variables - modify the target
|
||||
for (int i = 0; i < newVariableState.Length; i++) { |
||||
VariableSlot oldSlot = branchTarget.VariablesBefore[i]; |
||||
VariableSlot newSlot = newVariableState[i]; |
||||
// All can not be unioned further
|
||||
if (!oldSlot.StoredByAll) { |
||||
if (newSlot.StoredByAll) { |
||||
branchTarget.VariablesBefore[i] = newSlot; |
||||
modified = true; |
||||
} else { |
||||
ByteCode[] oldStoredBy = oldSlot.StoredBy; |
||||
ByteCode[] newStoredBy = oldStoredBy.Union(newSlot.StoredBy); |
||||
if (newStoredBy.Length > oldStoredBy.Length) { |
||||
branchTarget.VariablesBefore[i] = new VariableSlot(newStoredBy, false); |
||||
modified = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (modified) { |
||||
agenda.Push(branchTarget); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Occasionally the compilers or obfuscators generate unreachable code (which migt be intentonally invalid)
|
||||
// I belive it is safe to just remove it
|
||||
body.RemoveAll(b => b.StackBefore == null); |
||||
|
||||
// Genertate temporary variables to replace stack
|
||||
foreach(ByteCode byteCode in body) { |
||||
int argIdx = 0; |
||||
int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; |
||||
for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { |
||||
ILVariable tmpVar = new ILVariable() { Name = string.Format("arg_{0:X2}_{1}", byteCode.Offset, argIdx), IsGenerated = true }; |
||||
byteCode.StackBefore[i] = new StackSlot(byteCode.StackBefore[i].PushedBy, tmpVar); |
||||
foreach(ByteCode pushedBy in byteCode.StackBefore[i].PushedBy) { |
||||
if (pushedBy.StoreTo == null) { |
||||
pushedBy.StoreTo = new List<ILVariable>(1); |
||||
} |
||||
pushedBy.StoreTo.Add(tmpVar); |
||||
} |
||||
argIdx++; |
||||
} |
||||
} |
||||
|
||||
// Try to use single temporary variable insted of several if possilbe (especially useful for dup)
|
||||
// This has to be done after all temporary variables are assigned so we know about all loads
|
||||
foreach(ByteCode byteCode in body) { |
||||
if (byteCode.StoreTo != null && byteCode.StoreTo.Count > 1) { |
||||
var locVars = byteCode.StoreTo; |
||||
// For each of the variables, find the location where it is loaded - there should be preciesly one
|
||||
var loadedBy = locVars.Select(locVar => body.SelectMany(bc => bc.StackBefore).Single(s => s.LoadFrom == locVar)).ToList(); |
||||
// We now know that all the variables have a single load,
|
||||
// Let's make sure that they have also a single store - us
|
||||
if (loadedBy.All(slot => slot.PushedBy.Length == 1 && slot.PushedBy[0] == byteCode)) { |
||||
// Great - we can reduce everything into single variable
|
||||
ILVariable tmpVar = new ILVariable() { Name = string.Format("expr_{0:X2}", byteCode.Offset), IsGenerated = true }; |
||||
byteCode.StoreTo = new List<ILVariable>() { tmpVar }; |
||||
foreach(ByteCode bc in body) { |
||||
for (int i = 0; i < bc.StackBefore.Count; i++) { |
||||
// Is it one of the variable to be merged?
|
||||
if (locVars.Contains(bc.StackBefore[i].LoadFrom)) { |
||||
// Replace with the new temp variable
|
||||
bc.StackBefore[i] = new StackSlot(bc.StackBefore[i].PushedBy, tmpVar); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Split and convert the normal local variables
|
||||
ConvertLocalVariables(body); |
||||
|
||||
// Convert branch targets to labels
|
||||
foreach(ByteCode byteCode in body) { |
||||
if (byteCode.Operand is Instruction[]) { |
||||
List<ILLabel> newOperand = new List<ILLabel>(); |
||||
foreach(Instruction target in (Instruction[])byteCode.Operand) { |
||||
newOperand.Add(instrToByteCode[target].Label); |
||||
} |
||||
byteCode.Operand = newOperand.ToArray(); |
||||
} else if (byteCode.Operand is Instruction) { |
||||
byteCode.Operand = instrToByteCode[(Instruction)byteCode.Operand].Label; |
||||
} |
||||
} |
||||
|
||||
// Convert parameters to ILVariables
|
||||
ConvertParameters(body); |
||||
|
||||
return body; |
||||
} |
||||
|
||||
class VariableInfo |
||||
{ |
||||
public ILVariable Variable; |
||||
public List<ByteCode> Stores; |
||||
public List<ByteCode> Loads; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// If possible, separates local variables into several independent variables.
|
||||
/// It should undo any compilers merging.
|
||||
/// </summary>
|
||||
void ConvertLocalVariables(List<ByteCode> body) |
||||
{ |
||||
if (optimize) { |
||||
int varCount = methodDef.Body.Variables.Count; |
||||
|
||||
for(int variableIndex = 0; variableIndex < varCount; variableIndex++) { |
||||
// Find all stores and loads for this variable
|
||||
List<ByteCode> stores = body.Where(b => b.Code == ILCode.Stloc && b.Operand is VariableDefinition && b.OperandAsVariable.Index == variableIndex).ToList(); |
||||
List<ByteCode> loads = body.Where(b => (b.Code == ILCode.Ldloc || b.Code == ILCode.Ldloca) && b.Operand is VariableDefinition && b.OperandAsVariable.Index == variableIndex).ToList(); |
||||
TypeReference varType = methodDef.Body.Variables[variableIndex].VariableType; |
||||
|
||||
List<VariableInfo> newVars; |
||||
|
||||
bool isPinned = methodDef.Body.Variables[variableIndex].IsPinned; |
||||
// If the variable is pinned, use single variable.
|
||||
// If any of the loads is from "all", use single variable
|
||||
// If any of the loads is ldloca, fallback to single variable as well
|
||||
if (isPinned || loads.Any(b => b.VariablesBefore[variableIndex].StoredByAll || b.Code == ILCode.Ldloca)) { |
||||
newVars = new List<VariableInfo>(1) { new VariableInfo() { |
||||
Variable = new ILVariable() { |
||||
Name = "var_" + variableIndex, |
||||
Type = isPinned ? ((PinnedType)varType).ElementType : varType, |
||||
OriginalVariable = methodDef.Body.Variables[variableIndex] |
||||
}, |
||||
Stores = stores, |
||||
Loads = loads |
||||
}}; |
||||
} else { |
||||
// Create a new variable for each store
|
||||
newVars = stores.Select(st => new VariableInfo() { |
||||
Variable = new ILVariable() { |
||||
Name = "var_" + variableIndex + "_" + st.Offset.ToString("X2"), |
||||
Type = varType, |
||||
OriginalVariable = methodDef.Body.Variables[variableIndex] |
||||
}, |
||||
Stores = new List<ByteCode>() {st}, |
||||
Loads = new List<ByteCode>() |
||||
}).ToList(); |
||||
|
||||
// VB.NET uses the 'init' to allow use of uninitialized variables.
|
||||
// We do not really care about them too much - if the original variable
|
||||
// was uninitialized at that point it means that no store was called and
|
||||
// thus all our new variables must be uninitialized as well.
|
||||
// So it does not matter which one we load.
|
||||
|
||||
// TODO: We should add explicit initialization so that C# code compiles.
|
||||
// Remember to handle cases where one path inits the variable, but other does not.
|
||||
|
||||
// Add loads to the data structure; merge variables if necessary
|
||||
foreach(ByteCode load in loads) { |
||||
ByteCode[] storedBy = load.VariablesBefore[variableIndex].StoredBy; |
||||
if (storedBy.Length == 0) { |
||||
// Load which always loads the default ('uninitialized') value
|
||||
// Create a dummy variable just for this load
|
||||
newVars.Add(new VariableInfo() { |
||||
Variable = new ILVariable() { |
||||
Name = "var_" + variableIndex + "_" + load.Offset.ToString("X2") + "_default", |
||||
Type = varType, |
||||
OriginalVariable = methodDef.Body.Variables[variableIndex] |
||||
}, |
||||
Stores = new List<ByteCode>(), |
||||
Loads = new List<ByteCode>() { load } |
||||
}); |
||||
} else if (storedBy.Length == 1) { |
||||
VariableInfo newVar = newVars.Single(v => v.Stores.Contains(storedBy[0])); |
||||
newVar.Loads.Add(load); |
||||
} else { |
||||
List<VariableInfo> mergeVars = newVars.Where(v => v.Stores.Union(storedBy).Any()).ToList(); |
||||
VariableInfo mergedVar = new VariableInfo() { |
||||
Variable = mergeVars[0].Variable, |
||||
Stores = mergeVars.SelectMany(v => v.Stores).ToList(), |
||||
Loads = mergeVars.SelectMany(v => v.Loads).ToList() |
||||
}; |
||||
mergedVar.Loads.Add(load); |
||||
newVars = newVars.Except(mergeVars).ToList(); |
||||
newVars.Add(mergedVar); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Set bytecode operands
|
||||
foreach(VariableInfo newVar in newVars) { |
||||
foreach(ByteCode store in newVar.Stores) { |
||||
store.Operand = newVar.Variable; |
||||
} |
||||
foreach(ByteCode load in newVar.Loads) { |
||||
load.Operand = newVar.Variable; |
||||
} |
||||
} |
||||
} |
||||
} else { |
||||
var variables = methodDef.Body.Variables.Select(v => new ILVariable() { Name = string.IsNullOrEmpty(v.Name) ? "var_" + v.Index : v.Name, Type = v.VariableType, OriginalVariable = v }).ToList(); |
||||
foreach(ByteCode byteCode in body) { |
||||
if (byteCode.Code == ILCode.Ldloc || byteCode.Code == ILCode.Stloc || byteCode.Code == ILCode.Ldloca) { |
||||
int index = ((VariableDefinition)byteCode.Operand).Index; |
||||
byteCode.Operand = variables[index]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public List<ILVariable> Parameters = new List<ILVariable>(); |
||||
|
||||
void ConvertParameters(List<ByteCode> body) |
||||
{ |
||||
ILVariable thisParameter = null; |
||||
if (methodDef.HasThis) { |
||||
TypeReference type = methodDef.DeclaringType; |
||||
thisParameter = new ILVariable(); |
||||
thisParameter.Type = type.IsValueType ? new ByReferenceType(type) : type; |
||||
thisParameter.Name = "this"; |
||||
thisParameter.OriginalParameter = methodDef.Body.ThisParameter; |
||||
} |
||||
foreach (ParameterDefinition p in methodDef.Parameters) { |
||||
this.Parameters.Add(new ILVariable { Type = p.ParameterType, Name = p.Name, OriginalParameter = p }); |
||||
} |
||||
foreach (ByteCode byteCode in body) { |
||||
ParameterDefinition p; |
||||
switch (byteCode.Code) { |
||||
case ILCode.__Ldarg: |
||||
p = (ParameterDefinition)byteCode.Operand; |
||||
byteCode.Code = ILCode.Ldloc; |
||||
byteCode.Operand = p.Index < 0 ? thisParameter : this.Parameters[p.Index]; |
||||
break; |
||||
case ILCode.__Starg: |
||||
p = (ParameterDefinition)byteCode.Operand; |
||||
byteCode.Code = ILCode.Stloc; |
||||
byteCode.Operand = p.Index < 0 ? thisParameter : this.Parameters[p.Index]; |
||||
break; |
||||
case ILCode.__Ldarga: |
||||
p = (ParameterDefinition)byteCode.Operand; |
||||
byteCode.Code = ILCode.Ldloca; |
||||
byteCode.Operand = p.Index < 0 ? thisParameter : this.Parameters[p.Index]; |
||||
break; |
||||
} |
||||
} |
||||
if (thisParameter != null) |
||||
this.Parameters.Add(thisParameter); |
||||
} |
||||
|
||||
List<ILNode> ConvertToAst(List<ByteCode> body, HashSet<ExceptionHandler> ehs) |
||||
{ |
||||
List<ILNode> ast = new List<ILNode>(); |
||||
|
||||
while (ehs.Any()) { |
||||
ILTryCatchBlock tryCatchBlock = new ILTryCatchBlock(); |
||||
|
||||
// Find the first and widest scope
|
||||
int tryStart = ehs.Min(eh => eh.TryStart.Offset); |
||||
int tryEnd = ehs.Where(eh => eh.TryStart.Offset == tryStart).Max(eh => eh.TryEnd.Offset); |
||||
var handlers = ehs.Where(eh => eh.TryStart.Offset == tryStart && eh.TryEnd.Offset == tryEnd).OrderBy(eh => eh.TryStart.Offset).ToList(); |
||||
|
||||
// Remember that any part of the body migt have been removed due to unreachability
|
||||
|
||||
// Cut all instructions up to the try block
|
||||
{ |
||||
int tryStartIdx = 0; |
||||
while (tryStartIdx < body.Count && body[tryStartIdx].Offset < tryStart) tryStartIdx++; |
||||
ast.AddRange(ConvertToAst(body.CutRange(0, tryStartIdx))); |
||||
} |
||||
|
||||
// Cut the try block
|
||||
{ |
||||
HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(eh => (tryStart <= eh.TryStart.Offset && eh.TryEnd.Offset < tryEnd) || (tryStart < eh.TryStart.Offset && eh.TryEnd.Offset <= tryEnd))); |
||||
ehs.ExceptWith(nestedEHs); |
||||
int tryEndIdx = 0; |
||||
while (tryEndIdx < body.Count && body[tryEndIdx].Offset < tryEnd) tryEndIdx++; |
||||
tryCatchBlock.TryBlock = new ILBlock(ConvertToAst(body.CutRange(0, tryEndIdx), nestedEHs)); |
||||
} |
||||
|
||||
// Cut all handlers
|
||||
tryCatchBlock.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>(); |
||||
foreach(ExceptionHandler eh in handlers) { |
||||
int handlerEndOffset = eh.HandlerEnd == null ? methodDef.Body.CodeSize : eh.HandlerEnd.Offset; |
||||
int startIdx = 0; |
||||
while (startIdx < body.Count && body[startIdx].Offset < eh.HandlerStart.Offset) startIdx++; |
||||
int endIdx = 0; |
||||
while (endIdx < body.Count && body[endIdx].Offset < handlerEndOffset) endIdx++; |
||||
HashSet<ExceptionHandler> nestedEHs = new HashSet<ExceptionHandler>(ehs.Where(e => (eh.HandlerStart.Offset <= e.TryStart.Offset && e.TryEnd.Offset < handlerEndOffset) || (eh.HandlerStart.Offset < e.TryStart.Offset && e.TryEnd.Offset <= handlerEndOffset))); |
||||
ehs.ExceptWith(nestedEHs); |
||||
List<ILNode> handlerAst = ConvertToAst(body.CutRange(startIdx, endIdx - startIdx), nestedEHs); |
||||
if (eh.HandlerType == ExceptionHandlerType.Catch) { |
||||
ILTryCatchBlock.CatchBlock catchBlock = new ILTryCatchBlock.CatchBlock() { |
||||
ExceptionType = eh.CatchType, |
||||
Body = handlerAst |
||||
}; |
||||
// Handle the automatically pushed exception on the stack
|
||||
ByteCode ldexception = ldexceptions[eh]; |
||||
if (ldexception.StoreTo.Count == 0) { |
||||
throw new Exception("Exception should be consumed by something"); |
||||
} else if (ldexception.StoreTo.Count == 1) { |
||||
ILExpression first = catchBlock.Body[0] as ILExpression; |
||||
if (first != null && |
||||
first.Code == ILCode.Pop && |
||||
first.Arguments[0].Code == ILCode.Ldloc && |
||||
first.Arguments[0].Operand == ldexception.StoreTo[0]) |
||||
{ |
||||
// The exception is just poped - optimize it all away;
|
||||
catchBlock.ExceptionVariable = null; |
||||
catchBlock.Body.RemoveAt(0); |
||||
} else { |
||||
catchBlock.ExceptionVariable = ldexception.StoreTo[0]; |
||||
} |
||||
} else { |
||||
ILVariable exTemp = new ILVariable() { Name = "ex_" + eh.HandlerStart.Offset.ToString("X2"), IsGenerated = true }; |
||||
catchBlock.ExceptionVariable = exTemp; |
||||
foreach(ILVariable storeTo in ldexception.StoreTo) { |
||||
catchBlock.Body.Insert(0, new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, exTemp))); |
||||
} |
||||
} |
||||
tryCatchBlock.CatchBlocks.Add(catchBlock); |
||||
} else if (eh.HandlerType == ExceptionHandlerType.Finally) { |
||||
tryCatchBlock.FinallyBlock = new ILBlock(handlerAst); |
||||
} else if (eh.HandlerType == ExceptionHandlerType.Fault) { |
||||
tryCatchBlock.FaultBlock = new ILBlock(handlerAst); |
||||
} else { |
||||
// TODO: ExceptionHandlerType.Filter
|
||||
} |
||||
} |
||||
|
||||
ehs.ExceptWith(handlers); |
||||
|
||||
ast.Add(tryCatchBlock); |
||||
} |
||||
|
||||
// Add whatever is left
|
||||
ast.AddRange(ConvertToAst(body)); |
||||
|
||||
return ast; |
||||
} |
||||
|
||||
List<ILNode> ConvertToAst(List<ByteCode> body) |
||||
{ |
||||
List<ILNode> ast = new List<ILNode>(); |
||||
|
||||
// Convert stack-based IL code to ILAst tree
|
||||
foreach(ByteCode byteCode in body) { |
||||
ILRange ilRange = new ILRange() { From = byteCode.Offset, To = byteCode.EndOffset }; |
||||
|
||||
if (byteCode.StackBefore == null) { |
||||
// Unreachable code
|
||||
continue; |
||||
} |
||||
|
||||
ILExpression expr = new ILExpression(byteCode.Code, byteCode.Operand); |
||||
expr.ILRanges.Add(ilRange); |
||||
if (byteCode.Prefixes != null && byteCode.Prefixes.Length > 0) { |
||||
ILExpressionPrefix[] prefixes = new ILExpressionPrefix[byteCode.Prefixes.Length]; |
||||
for (int i = 0; i < prefixes.Length; i++) { |
||||
prefixes[i] = new ILExpressionPrefix((ILCode)byteCode.Prefixes[i].OpCode.Code, byteCode.Prefixes[i].Operand); |
||||
} |
||||
expr.Prefixes = prefixes; |
||||
} |
||||
|
||||
// Label for this instruction
|
||||
if (byteCode.Label != null) { |
||||
ast.Add(byteCode.Label); |
||||
} |
||||
|
||||
// Reference arguments using temporary variables
|
||||
int popCount = byteCode.PopCount ?? byteCode.StackBefore.Count; |
||||
for (int i = byteCode.StackBefore.Count - popCount; i < byteCode.StackBefore.Count; i++) { |
||||
StackSlot slot = byteCode.StackBefore[i]; |
||||
expr.Arguments.Add(new ILExpression(ILCode.Ldloc, slot.LoadFrom)); |
||||
} |
||||
|
||||
// Store the result to temporary variable(s) if needed
|
||||
if (byteCode.StoreTo == null || byteCode.StoreTo.Count == 0) { |
||||
ast.Add(expr); |
||||
} else if (byteCode.StoreTo.Count == 1) { |
||||
ast.Add(new ILExpression(ILCode.Stloc, byteCode.StoreTo[0], expr)); |
||||
} else { |
||||
ILVariable tmpVar = new ILVariable() { Name = "expr_" + byteCode.Offset.ToString("X2"), IsGenerated = true }; |
||||
ast.Add(new ILExpression(ILCode.Stloc, tmpVar, expr)); |
||||
foreach(ILVariable storeTo in byteCode.StoreTo.AsEnumerable().Reverse()) { |
||||
ast.Add(new ILExpression(ILCode.Stloc, storeTo, new ILExpression(ILCode.Ldloc, tmpVar))); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return ast; |
||||
} |
||||
} |
||||
|
||||
public static class ILAstBuilderExtensionMethods |
||||
{ |
||||
public static List<T> CutRange<T>(this List<T> list, int start, int count) |
||||
{ |
||||
List<T> ret = new List<T>(count); |
||||
for (int i = 0; i < count; i++) { |
||||
ret.Add(list[start + i]); |
||||
} |
||||
list.RemoveRange(start, count); |
||||
return ret; |
||||
} |
||||
|
||||
public static T[] Union<T>(this T[] a, T[] b) |
||||
{ |
||||
if (a.Length == 0) |
||||
return b; |
||||
if (b.Length == 0) |
||||
return a; |
||||
if (a.Length == 1 && b.Length == 1 && a[0].Equals(b[0])) |
||||
return a; |
||||
return Enumerable.Union(a, b).ToArray(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,830 @@
@@ -0,0 +1,830 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using ICSharpCode.Decompiler.FlowAnalysis; |
||||
using ICSharpCode.NRefactory.Utils; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
using Mono.CSharp; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public enum ILAstOptimizationStep |
||||
{ |
||||
RemoveRedundantCode, |
||||
ReduceBranchInstructionSet, |
||||
InlineVariables, |
||||
CopyPropagation, |
||||
YieldReturn, |
||||
PropertyAccessInstructions, |
||||
SplitToMovableBlocks, |
||||
TypeInference, |
||||
SimplifyShortCircuit, |
||||
SimplifyTernaryOperator, |
||||
SimplifyNullCoalescing, |
||||
JoinBasicBlocks, |
||||
SimplifyShiftOperators, |
||||
TransformDecimalCtorToConstant, |
||||
SimplifyLdObjAndStObj, |
||||
SimplifyCustomShortCircuit, |
||||
TransformArrayInitializers, |
||||
TransformMultidimensionalArrayInitializers, |
||||
TransformObjectInitializers, |
||||
MakeAssignmentExpression, |
||||
IntroducePostIncrement, |
||||
InlineVariables2, |
||||
FindLoops, |
||||
FindConditions, |
||||
FlattenNestedMovableBlocks, |
||||
RemoveEndFinally, |
||||
RemoveRedundantCode2, |
||||
GotoRemoval, |
||||
DuplicateReturns, |
||||
GotoRemoval2, |
||||
ReduceIfNesting, |
||||
InlineVariables3, |
||||
CachedDelegateInitialization, |
||||
IntroduceFixedStatements, |
||||
RecombineVariables, |
||||
TypeInference2, |
||||
RemoveRedundantCode3, |
||||
None |
||||
} |
||||
|
||||
public partial class ILAstOptimizer |
||||
{ |
||||
int nextLabelIndex = 0; |
||||
|
||||
DecompilerContext context; |
||||
TypeSystem typeSystem; |
||||
ILBlock method; |
||||
|
||||
public void Optimize(DecompilerContext context, ILBlock method, ILAstOptimizationStep abortBeforeStep = ILAstOptimizationStep.None) |
||||
{ |
||||
this.context = context; |
||||
this.typeSystem = context.CurrentMethod.Module.TypeSystem; |
||||
this.method = method; |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode) return; |
||||
RemoveRedundantCode(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.ReduceBranchInstructionSet) return; |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
ReduceBranchInstructionSet(block); |
||||
} |
||||
// ReduceBranchInstructionSet runs before inlining because the non-aggressive inlining heuristic
|
||||
// looks at which type of instruction consumes the inlined variable.
|
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.InlineVariables) return; |
||||
// Works better after simple goto removal because of the following debug pattern: stloc X; br Next; Next:; ldloc X
|
||||
ILInlining inlining1 = new ILInlining(method); |
||||
inlining1.InlineAllVariables(); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.CopyPropagation) return; |
||||
inlining1.CopyPropagation(); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.YieldReturn) return; |
||||
YieldReturnDecompiler.Run(context, method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.PropertyAccessInstructions) return; |
||||
IntroducePropertyAccessInstructions(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SplitToMovableBlocks) return; |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
SplitToBasicBlocks(block); |
||||
} |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.TypeInference) return; |
||||
// Types are needed for the ternary operator optimization
|
||||
TypeAnalysis.Run(context, method); |
||||
|
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
bool modified; |
||||
do { |
||||
modified = false; |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SimplifyShortCircuit) return; |
||||
modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyShortCircuit); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SimplifyTernaryOperator) return; |
||||
modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyTernaryOperator); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SimplifyNullCoalescing) return; |
||||
modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyNullCoalescing); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.JoinBasicBlocks) return; |
||||
modified |= block.RunOptimization(new SimpleControlFlow(context, method).JoinBasicBlocks); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SimplifyShiftOperators) return; |
||||
modified |= block.RunOptimization(SimplifyShiftOperators); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.TransformDecimalCtorToConstant) return; |
||||
modified |= block.RunOptimization(TransformDecimalCtorToConstant); |
||||
modified |= block.RunOptimization(SimplifyLdcI4ConvI8); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SimplifyLdObjAndStObj) return; |
||||
modified |= block.RunOptimization(SimplifyLdObjAndStObj); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.SimplifyCustomShortCircuit) return; |
||||
modified |= block.RunOptimization(new SimpleControlFlow(context, method).SimplifyCustomShortCircuit); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.TransformArrayInitializers) return; |
||||
modified |= block.RunOptimization(TransformArrayInitializers); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.TransformMultidimensionalArrayInitializers) return; |
||||
modified |= block.RunOptimization(TransformMultidimensionalArrayInitializers); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.TransformObjectInitializers) return; |
||||
modified |= block.RunOptimization(TransformObjectInitializers); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.MakeAssignmentExpression) return; |
||||
modified |= block.RunOptimization(MakeAssignmentExpression); |
||||
modified |= block.RunOptimization(MakeCompoundAssignments); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.IntroducePostIncrement) return; |
||||
modified |= block.RunOptimization(IntroducePostIncrement); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.InlineVariables2) return; |
||||
modified |= new ILInlining(method).InlineAllInBlock(block); |
||||
new ILInlining(method).CopyPropagation(); |
||||
|
||||
} while(modified); |
||||
} |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.FindLoops) return; |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
new LoopsAndConditions(context).FindLoops(block); |
||||
} |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.FindConditions) return; |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
new LoopsAndConditions(context).FindConditions(block); |
||||
} |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.FlattenNestedMovableBlocks) return; |
||||
FlattenBasicBlocks(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.RemoveEndFinally) return; |
||||
RemoveEndFinally(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode2) return; |
||||
RemoveRedundantCode(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval) return; |
||||
new GotoRemoval().RemoveGotos(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.DuplicateReturns) return; |
||||
DuplicateReturnStatements(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.GotoRemoval2) return; |
||||
new GotoRemoval().RemoveGotos(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.ReduceIfNesting) return; |
||||
ReduceIfNesting(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.InlineVariables3) return; |
||||
// The 2nd inlining pass is necessary because DuplicateReturns and the introduction of ternary operators
|
||||
// open up additional inlining possibilities.
|
||||
new ILInlining(method).InlineAllVariables(); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.CachedDelegateInitialization) return; |
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int i = 0; i < block.Body.Count; i++) { |
||||
// TODO: Move before loops
|
||||
CachedDelegateInitializationWithField(block, ref i); |
||||
CachedDelegateInitializationWithLocal(block, ref i); |
||||
} |
||||
} |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.IntroduceFixedStatements) return; |
||||
// we need post-order traversal, not pre-order, for "fixed" to work correctly
|
||||
foreach (ILBlock block in TreeTraversal.PostOrder<ILNode>(method, n => n.GetChildren()).OfType<ILBlock>()) { |
||||
for (int i = block.Body.Count - 1; i >= 0; i--) { |
||||
// TODO: Move before loops
|
||||
if (i < block.Body.Count) |
||||
IntroduceFixedStatements(block.Body, i); |
||||
} |
||||
} |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.RecombineVariables) return; |
||||
RecombineVariables(method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.TypeInference2) return; |
||||
TypeAnalysis.Reset(method); |
||||
TypeAnalysis.Run(context, method); |
||||
|
||||
if (abortBeforeStep == ILAstOptimizationStep.RemoveRedundantCode3) return; |
||||
GotoRemoval.RemoveRedundantCode(method); |
||||
|
||||
// ReportUnassignedILRanges(method);
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Removes redundatant Br, Nop, Dup, Pop
|
||||
/// </summary>
|
||||
/// <param name="method"></param>
|
||||
void RemoveRedundantCode(ILBlock method) |
||||
{ |
||||
Dictionary<ILLabel, int> labelRefCount = new Dictionary<ILLabel, int>(); |
||||
foreach (ILLabel target in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { |
||||
labelRefCount[target] = labelRefCount.GetOrDefault(target) + 1; |
||||
} |
||||
|
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
List<ILNode> body = block.Body; |
||||
List<ILNode> newBody = new List<ILNode>(body.Count); |
||||
for (int i = 0; i < body.Count; i++) { |
||||
ILLabel target; |
||||
ILExpression popExpr; |
||||
if (body[i].Match(ILCode.Br, out target) && i+1 < body.Count && body[i+1] == target) { |
||||
// Ignore the branch
|
||||
if (labelRefCount[target] == 1) |
||||
i++; // Ignore the label as well
|
||||
} else if (body[i].Match(ILCode.Nop)){ |
||||
// Ignore nop
|
||||
} else if (body[i].Match(ILCode.Pop, out popExpr)) { |
||||
ILVariable v; |
||||
if (!popExpr.Match(ILCode.Ldloc, out v)) |
||||
throw new Exception("Pop should have just ldloc at this stage"); |
||||
// Best effort to move the ILRange to previous statement
|
||||
ILVariable prevVar; |
||||
ILExpression prevExpr; |
||||
if (i - 1 >= 0 && body[i - 1].Match(ILCode.Stloc, out prevVar, out prevExpr) && prevVar == v) |
||||
prevExpr.ILRanges.AddRange(((ILExpression)body[i]).ILRanges); |
||||
// Ignore pop
|
||||
} else { |
||||
newBody.Add(body[i]); |
||||
} |
||||
} |
||||
block.Body = newBody; |
||||
} |
||||
|
||||
// 'dup' removal
|
||||
foreach (ILExpression expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { |
||||
for (int i = 0; i < expr.Arguments.Count; i++) { |
||||
ILExpression child; |
||||
if (expr.Arguments[i].Match(ILCode.Dup, out child)) { |
||||
child.ILRanges.AddRange(expr.Arguments[i].ILRanges); |
||||
expr.Arguments[i] = child; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Reduces the branch codes to just br and brtrue.
|
||||
/// Moves ILRanges to the branch argument
|
||||
/// </summary>
|
||||
void ReduceBranchInstructionSet(ILBlock block) |
||||
{ |
||||
for (int i = 0; i < block.Body.Count; i++) { |
||||
ILExpression expr = block.Body[i] as ILExpression; |
||||
if (expr != null && expr.Prefixes == null) { |
||||
switch(expr.Code) { |
||||
case ILCode.Switch: |
||||
case ILCode.Brtrue: |
||||
expr.Arguments.Single().ILRanges.AddRange(expr.ILRanges); |
||||
expr.ILRanges.Clear(); |
||||
continue; |
||||
case ILCode.__Brfalse: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, expr.Arguments.Single())); break; |
||||
case ILCode.__Beq: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Ceq, null, expr.Arguments)); break; |
||||
case ILCode.__Bne_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Ceq, null, expr.Arguments))); break; |
||||
case ILCode.__Bgt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt, null, expr.Arguments)); break; |
||||
case ILCode.__Bgt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments)); break; |
||||
case ILCode.__Ble: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt, null, expr.Arguments))); break; |
||||
case ILCode.__Ble_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Cgt_Un, null, expr.Arguments))); break; |
||||
case ILCode.__Blt: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt, null, expr.Arguments)); break; |
||||
case ILCode.__Blt_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.Clt_Un, null, expr.Arguments)); break; |
||||
case ILCode.__Bge: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt, null, expr.Arguments))); break; |
||||
case ILCode.__Bge_Un: block.Body[i] = new ILExpression(ILCode.Brtrue, expr.Operand, new ILExpression(ILCode.LogicNot, null, new ILExpression(ILCode.Clt_Un, null, expr.Arguments))); break; |
||||
default: |
||||
continue; |
||||
} |
||||
((ILExpression)block.Body[i]).Arguments.Single().ILRanges.AddRange(expr.ILRanges); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Converts call and callvirt instructions that read/write properties into CallGetter/CallSetter instructions.
|
||||
///
|
||||
/// CallGetter/CallSetter is used to allow the ILAst to represent "while ((SomeProperty = value) != null)".
|
||||
///
|
||||
/// Also simplifies 'newobj(SomeDelegate, target, ldvirtftn(F, target))' to 'newobj(SomeDelegate, target, ldvirtftn(F))'
|
||||
/// </summary>
|
||||
void IntroducePropertyAccessInstructions(ILNode node) |
||||
{ |
||||
ILExpression parentExpr = node as ILExpression; |
||||
if (parentExpr != null) { |
||||
for (int i = 0; i < parentExpr.Arguments.Count; i++) { |
||||
ILExpression expr = parentExpr.Arguments[i]; |
||||
IntroducePropertyAccessInstructions(expr); |
||||
IntroducePropertyAccessInstructions(expr, parentExpr, i); |
||||
} |
||||
} else { |
||||
foreach (ILNode child in node.GetChildren()) { |
||||
IntroducePropertyAccessInstructions(child); |
||||
ILExpression expr = child as ILExpression; |
||||
if (expr != null) { |
||||
IntroducePropertyAccessInstructions(expr, null, -1); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void IntroducePropertyAccessInstructions(ILExpression expr, ILExpression parentExpr, int posInParent) |
||||
{ |
||||
if (expr.Code == ILCode.Call || expr.Code == ILCode.Callvirt) { |
||||
MethodReference cecilMethod = (MethodReference)expr.Operand; |
||||
if (cecilMethod.DeclaringType is ArrayType) { |
||||
switch (cecilMethod.Name) { |
||||
case "Get": |
||||
expr.Code = ILCode.CallGetter; |
||||
break; |
||||
case "Set": |
||||
expr.Code = ILCode.CallSetter; |
||||
break; |
||||
case "Address": |
||||
ByReferenceType brt = cecilMethod.ReturnType as ByReferenceType; |
||||
if (brt != null) { |
||||
MethodReference getMethod = new MethodReference("Get", brt.ElementType, cecilMethod.DeclaringType); |
||||
foreach (var p in cecilMethod.Parameters) |
||||
getMethod.Parameters.Add(p); |
||||
getMethod.HasThis = cecilMethod.HasThis; |
||||
expr.Operand = getMethod; |
||||
} |
||||
expr.Code = ILCode.CallGetter; |
||||
if (parentExpr != null) { |
||||
parentExpr.Arguments[posInParent] = new ILExpression(ILCode.AddressOf, null, expr); |
||||
} |
||||
break; |
||||
} |
||||
} else { |
||||
MethodDefinition cecilMethodDef = cecilMethod.Resolve(); |
||||
if (cecilMethodDef != null) { |
||||
if (cecilMethodDef.IsGetter) |
||||
expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallGetter : ILCode.CallvirtGetter; |
||||
else if (cecilMethodDef.IsSetter) |
||||
expr.Code = (expr.Code == ILCode.Call) ? ILCode.CallSetter : ILCode.CallvirtSetter; |
||||
} |
||||
} |
||||
} else if (expr.Code == ILCode.Newobj && expr.Arguments.Count == 2) { |
||||
// Might be 'newobj(SomeDelegate, target, ldvirtftn(F, target))'.
|
||||
ILVariable target; |
||||
if (expr.Arguments[0].Match(ILCode.Ldloc, out target) |
||||
&& expr.Arguments[1].Code == ILCode.Ldvirtftn |
||||
&& expr.Arguments[1].Arguments.Count == 1 |
||||
&& expr.Arguments[1].Arguments[0].MatchLdloc(target)) |
||||
{ |
||||
// Remove the 'target' argument from the ldvirtftn instruction.
|
||||
// It's not needed in the translation to C#, and needs to be eliminated so that the target expression
|
||||
// can be inlined.
|
||||
expr.Arguments[1].Arguments.Clear(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Group input into a set of blocks that can be later arbitraliby schufled.
|
||||
/// The method adds necessary branches to make control flow between blocks
|
||||
/// explicit and thus order independent.
|
||||
/// </summary>
|
||||
void SplitToBasicBlocks(ILBlock block) |
||||
{ |
||||
List<ILNode> basicBlocks = new List<ILNode>(); |
||||
|
||||
ILLabel entryLabel = block.Body.FirstOrDefault() as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++) }; |
||||
ILBasicBlock basicBlock = new ILBasicBlock(); |
||||
basicBlocks.Add(basicBlock); |
||||
basicBlock.Body.Add(entryLabel); |
||||
block.EntryGoto = new ILExpression(ILCode.Br, entryLabel); |
||||
|
||||
if (block.Body.Count > 0) { |
||||
if (block.Body[0] != entryLabel) |
||||
basicBlock.Body.Add(block.Body[0]); |
||||
|
||||
for (int i = 1; i < block.Body.Count; i++) { |
||||
ILNode lastNode = block.Body[i - 1]; |
||||
ILNode currNode = block.Body[i]; |
||||
|
||||
// Start a new basic block if necessary
|
||||
if (currNode is ILLabel || |
||||
currNode is ILTryCatchBlock || // Counts as label
|
||||
lastNode.IsConditionalControlFlow() || |
||||
lastNode.IsUnconditionalControlFlow()) |
||||
{ |
||||
// Try to reuse the label
|
||||
ILLabel label = currNode as ILLabel ?? new ILLabel() { Name = "Block_" + (nextLabelIndex++).ToString() }; |
||||
|
||||
// Terminate the last block
|
||||
if (!lastNode.IsUnconditionalControlFlow()) { |
||||
// Explicit branch from one block to other
|
||||
basicBlock.Body.Add(new ILExpression(ILCode.Br, label)); |
||||
} |
||||
|
||||
// Start the new block
|
||||
basicBlock = new ILBasicBlock(); |
||||
basicBlocks.Add(basicBlock); |
||||
basicBlock.Body.Add(label); |
||||
|
||||
// Add the node to the basic block
|
||||
if (currNode != label) |
||||
basicBlock.Body.Add(currNode); |
||||
} else { |
||||
basicBlock.Body.Add(currNode); |
||||
} |
||||
} |
||||
} |
||||
|
||||
block.Body = basicBlocks; |
||||
return; |
||||
} |
||||
|
||||
void DuplicateReturnStatements(ILBlock method) |
||||
{ |
||||
Dictionary<ILLabel, ILNode> nextSibling = new Dictionary<ILLabel, ILNode>(); |
||||
|
||||
// Build navigation data
|
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int i = 0; i < block.Body.Count - 1; i++) { |
||||
ILLabel curr = block.Body[i] as ILLabel; |
||||
if (curr != null) { |
||||
nextSibling[curr] = block.Body[i + 1]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Duplicate returns
|
||||
foreach(ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int i = 0; i < block.Body.Count; i++) { |
||||
ILLabel targetLabel; |
||||
if (block.Body[i].Match(ILCode.Br, out targetLabel) || block.Body[i].Match(ILCode.Leave, out targetLabel)) { |
||||
// Skip extra labels
|
||||
while(nextSibling.ContainsKey(targetLabel) && nextSibling[targetLabel] is ILLabel) { |
||||
targetLabel = (ILLabel)nextSibling[targetLabel]; |
||||
} |
||||
|
||||
// Inline return statement
|
||||
ILNode target; |
||||
List<ILExpression> retArgs; |
||||
if (nextSibling.TryGetValue(targetLabel, out target)) { |
||||
if (target.Match(ILCode.Ret, out retArgs)) { |
||||
ILVariable locVar; |
||||
object constValue; |
||||
if (retArgs.Count == 0) { |
||||
block.Body[i] = new ILExpression(ILCode.Ret, null); |
||||
} else if (retArgs.Single().Match(ILCode.Ldloc, out locVar)) { |
||||
block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldloc, locVar)); |
||||
} else if (retArgs.Single().Match(ILCode.Ldc_I4, out constValue)) { |
||||
block.Body[i] = new ILExpression(ILCode.Ret, null, new ILExpression(ILCode.Ldc_I4, constValue)); |
||||
} |
||||
} |
||||
} else { |
||||
if (method.Body.Count > 0 && method.Body.Last() == targetLabel) { |
||||
// It exits the main method - so it is same as return;
|
||||
block.Body[i] = new ILExpression(ILCode.Ret, null); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Flattens all nested basic blocks, except the the top level 'node' argument
|
||||
/// </summary>
|
||||
void FlattenBasicBlocks(ILNode node) |
||||
{ |
||||
ILBlock block = node as ILBlock; |
||||
if (block != null) { |
||||
List<ILNode> flatBody = new List<ILNode>(); |
||||
foreach (ILNode child in block.GetChildren()) { |
||||
FlattenBasicBlocks(child); |
||||
ILBasicBlock childAsBB = child as ILBasicBlock; |
||||
if (childAsBB != null) { |
||||
if (!(childAsBB.Body.FirstOrDefault() is ILLabel)) |
||||
throw new Exception("Basic block has to start with a label. \n" + childAsBB.ToString()); |
||||
if (childAsBB.Body.LastOrDefault() is ILExpression && !childAsBB.Body.LastOrDefault().IsUnconditionalControlFlow()) |
||||
throw new Exception("Basci block has to end with unconditional control flow. \n" + childAsBB.ToString()); |
||||
flatBody.AddRange(childAsBB.GetChildren()); |
||||
} else { |
||||
flatBody.Add(child); |
||||
} |
||||
} |
||||
block.EntryGoto = null; |
||||
block.Body = flatBody; |
||||
} else if (node is ILExpression) { |
||||
// Optimization - no need to check expressions
|
||||
} else if (node != null) { |
||||
// Recursively find all ILBlocks
|
||||
foreach(ILNode child in node.GetChildren()) { |
||||
FlattenBasicBlocks(child); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Replace endfinally with jump to the end of the finally block
|
||||
/// </summary>
|
||||
void RemoveEndFinally(ILBlock method) |
||||
{ |
||||
// Go thought the list in reverse so that we do the nested blocks first
|
||||
foreach(var tryCatch in method.GetSelfAndChildrenRecursive<ILTryCatchBlock>(tc => tc.FinallyBlock != null).Reverse()) { |
||||
ILLabel label = new ILLabel() { Name = "EndFinally_" + nextLabelIndex++ }; |
||||
tryCatch.FinallyBlock.Body.Add(label); |
||||
foreach(var block in tryCatch.FinallyBlock.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int i = 0; i < block.Body.Count; i++) { |
||||
if (block.Body[i].Match(ILCode.Endfinally)) { |
||||
block.Body[i] = new ILExpression(ILCode.Br, label).WithILRanges(((ILExpression)block.Body[i]).ILRanges); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Reduce the nesting of conditions.
|
||||
/// It should be done on flat data that already had most gotos removed
|
||||
/// </summary>
|
||||
void ReduceIfNesting(ILNode node) |
||||
{ |
||||
ILBlock block = node as ILBlock; |
||||
if (block != null) { |
||||
for (int i = 0; i < block.Body.Count; i++) { |
||||
ILCondition cond = block.Body[i] as ILCondition; |
||||
if (cond != null) { |
||||
bool trueExits = cond.TrueBlock.Body.LastOrDefault().IsUnconditionalControlFlow(); |
||||
bool falseExits = cond.FalseBlock.Body.LastOrDefault().IsUnconditionalControlFlow(); |
||||
|
||||
if (trueExits) { |
||||
// Move the false block after the condition
|
||||
block.Body.InsertRange(i + 1, cond.FalseBlock.GetChildren()); |
||||
cond.FalseBlock = new ILBlock(); |
||||
} else if (falseExits) { |
||||
// Move the true block after the condition
|
||||
block.Body.InsertRange(i + 1, cond.TrueBlock.GetChildren()); |
||||
cond.TrueBlock = new ILBlock(); |
||||
} |
||||
|
||||
// Eliminate empty true block
|
||||
if (!cond.TrueBlock.GetChildren().Any() && cond.FalseBlock.GetChildren().Any()) { |
||||
// Swap bodies
|
||||
ILBlock tmp = cond.TrueBlock; |
||||
cond.TrueBlock = cond.FalseBlock; |
||||
cond.FalseBlock = tmp; |
||||
cond.Condition = new ILExpression(ILCode.LogicNot, null, cond.Condition); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// We are changing the number of blocks so we use plain old recursion to get all blocks
|
||||
foreach(ILNode child in node.GetChildren()) { |
||||
if (child != null && !(child is ILExpression)) |
||||
ReduceIfNesting(child); |
||||
} |
||||
} |
||||
|
||||
void RecombineVariables(ILBlock method) |
||||
{ |
||||
// Recombine variables that were split when the ILAst was created
|
||||
// This ensures that a single IL variable is a single C# variable (gets assigned only one name)
|
||||
// The DeclareVariables transformation might then split up the C# variable again if it is used indendently in two separate scopes.
|
||||
Dictionary<VariableDefinition, ILVariable> dict = new Dictionary<VariableDefinition, ILVariable>(); |
||||
ReplaceVariables( |
||||
method, |
||||
delegate(ILVariable v) { |
||||
if (v.OriginalVariable == null) |
||||
return v; |
||||
ILVariable combinedVariable; |
||||
if (!dict.TryGetValue(v.OriginalVariable, out combinedVariable)) { |
||||
dict.Add(v.OriginalVariable, v); |
||||
combinedVariable = v; |
||||
} |
||||
return combinedVariable; |
||||
}); |
||||
} |
||||
|
||||
public static void ReplaceVariables(ILNode node, Func<ILVariable, ILVariable> variableMapping) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
if (expr != null) { |
||||
ILVariable v = expr.Operand as ILVariable; |
||||
if (v != null) |
||||
expr.Operand = variableMapping(v); |
||||
foreach (ILExpression child in expr.Arguments) |
||||
ReplaceVariables(child, variableMapping); |
||||
} else { |
||||
var catchBlock = node as ILTryCatchBlock.CatchBlock; |
||||
if (catchBlock != null && catchBlock.ExceptionVariable != null) { |
||||
catchBlock.ExceptionVariable = variableMapping(catchBlock.ExceptionVariable); |
||||
} |
||||
|
||||
foreach (ILNode child in node.GetChildren()) |
||||
ReplaceVariables(child, variableMapping); |
||||
} |
||||
} |
||||
|
||||
void ReportUnassignedILRanges(ILBlock method) |
||||
{ |
||||
var unassigned = ILRange.Invert(method.GetSelfAndChildrenRecursive<ILExpression>().SelectMany(e => e.ILRanges), context.CurrentMethod.Body.CodeSize).ToList(); |
||||
if (unassigned.Count > 0) |
||||
Debug.WriteLine(string.Format("Unassigned ILRanges for {0}.{1}: {2}", this.context.CurrentMethod.DeclaringType.Name, this.context.CurrentMethod.Name, string.Join(", ", unassigned.Select(r => r.ToString())))); |
||||
} |
||||
} |
||||
|
||||
public static class ILAstOptimizerExtensionMethods |
||||
{ |
||||
/// <summary>
|
||||
/// Perform one pass of a given optimization on this block.
|
||||
/// This block must consist of only basicblocks.
|
||||
/// </summary>
|
||||
public static bool RunOptimization(this ILBlock block, Func<List<ILNode>, ILBasicBlock, int, bool> optimization) |
||||
{ |
||||
bool modified = false; |
||||
List<ILNode> body = block.Body; |
||||
for (int i = body.Count - 1; i >= 0; i--) { |
||||
if (i < body.Count && optimization(body, (ILBasicBlock)body[i], i)) { |
||||
modified = true; |
||||
} |
||||
} |
||||
return modified; |
||||
} |
||||
|
||||
public static bool RunOptimization(this ILBlock block, Func<List<ILNode>, ILExpression, int, bool> optimization) |
||||
{ |
||||
bool modified = false; |
||||
foreach (ILBasicBlock bb in block.Body) { |
||||
for (int i = bb.Body.Count - 1; i >= 0; i--) { |
||||
ILExpression expr = bb.Body.ElementAtOrDefault(i) as ILExpression; |
||||
if (expr != null && optimization(bb.Body, expr, i)) { |
||||
modified = true; |
||||
} |
||||
} |
||||
} |
||||
return modified; |
||||
} |
||||
|
||||
public static bool IsConditionalControlFlow(this ILNode node) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
return expr != null && expr.Code.IsConditionalControlFlow(); |
||||
} |
||||
|
||||
public static bool IsUnconditionalControlFlow(this ILNode node) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
return expr != null && expr.Code.IsUnconditionalControlFlow(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// The expression has no effect on the program and can be removed
|
||||
/// if its return value is not needed.
|
||||
/// </summary>
|
||||
public static bool HasNoSideEffects(this ILExpression expr) |
||||
{ |
||||
// Remember that if expression can throw an exception, it is a side effect
|
||||
|
||||
switch(expr.Code) { |
||||
case ILCode.Ldloc: |
||||
case ILCode.Ldloca: |
||||
case ILCode.Ldstr: |
||||
case ILCode.Ldnull: |
||||
case ILCode.Ldc_I4: |
||||
case ILCode.Ldc_I8: |
||||
case ILCode.Ldc_R4: |
||||
case ILCode.Ldc_R8: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static bool IsStoreToArray(this ILCode code) |
||||
{ |
||||
switch (code) { |
||||
case ILCode.Stelem_Any: |
||||
case ILCode.Stelem_I: |
||||
case ILCode.Stelem_I1: |
||||
case ILCode.Stelem_I2: |
||||
case ILCode.Stelem_I4: |
||||
case ILCode.Stelem_I8: |
||||
case ILCode.Stelem_R4: |
||||
case ILCode.Stelem_R8: |
||||
case ILCode.Stelem_Ref: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static bool IsLoadFromArray(this ILCode code) |
||||
{ |
||||
switch (code) { |
||||
case ILCode.Ldelem_Any: |
||||
case ILCode.Ldelem_I: |
||||
case ILCode.Ldelem_I1: |
||||
case ILCode.Ldelem_I2: |
||||
case ILCode.Ldelem_I4: |
||||
case ILCode.Ldelem_I8: |
||||
case ILCode.Ldelem_U1: |
||||
case ILCode.Ldelem_U2: |
||||
case ILCode.Ldelem_U4: |
||||
case ILCode.Ldelem_R4: |
||||
case ILCode.Ldelem_R8: |
||||
case ILCode.Ldelem_Ref: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Can the expression be used as a statement in C#?
|
||||
/// </summary>
|
||||
public static bool CanBeExpressionStatement(this ILExpression expr) |
||||
{ |
||||
switch(expr.Code) { |
||||
case ILCode.Call: |
||||
case ILCode.Callvirt: |
||||
// property getters can't be expression statements, but all other method calls can be
|
||||
MethodReference mr = (MethodReference)expr.Operand; |
||||
return !mr.Name.StartsWith("get_", StringComparison.Ordinal); |
||||
case ILCode.CallSetter: |
||||
case ILCode.CallvirtSetter: |
||||
case ILCode.Newobj: |
||||
case ILCode.Newarr: |
||||
case ILCode.Stloc: |
||||
case ILCode.Stobj: |
||||
case ILCode.Stsfld: |
||||
case ILCode.Stfld: |
||||
case ILCode.Stind_Ref: |
||||
case ILCode.Stelem_Any: |
||||
case ILCode.Stelem_I: |
||||
case ILCode.Stelem_I1: |
||||
case ILCode.Stelem_I2: |
||||
case ILCode.Stelem_I4: |
||||
case ILCode.Stelem_I8: |
||||
case ILCode.Stelem_R4: |
||||
case ILCode.Stelem_R8: |
||||
case ILCode.Stelem_Ref: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static ILExpression WithILRanges(this ILExpression expr, IEnumerable<ILRange> ilranges) |
||||
{ |
||||
expr.ILRanges.AddRange(ilranges); |
||||
return expr; |
||||
} |
||||
|
||||
public static void RemoveTail(this List<ILNode> body, params ILCode[] codes) |
||||
{ |
||||
for (int i = 0; i < codes.Length; i++) { |
||||
if (((ILExpression)body[body.Count - codes.Length + i]).Code != codes[i]) |
||||
throw new Exception("Tailing code does not match expected."); |
||||
} |
||||
body.RemoveRange(body.Count - codes.Length, codes.Length); |
||||
} |
||||
|
||||
public static V GetOrDefault<K,V>(this Dictionary<K, V> dict, K key) |
||||
{ |
||||
V ret; |
||||
dict.TryGetValue(key, out ret); |
||||
return ret; |
||||
} |
||||
|
||||
public static void RemoveOrThrow<T>(this ICollection<T> collection, T item) |
||||
{ |
||||
if (!collection.Remove(item)) |
||||
throw new Exception("The item was not found in the collection"); |
||||
} |
||||
|
||||
public static void RemoveOrThrow<K,V>(this Dictionary<K,V> collection, K key) |
||||
{ |
||||
if (!collection.Remove(key)) |
||||
throw new Exception("The key was not found in the dictionary"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,593 @@
@@ -0,0 +1,593 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.IO; |
||||
using System.Linq; |
||||
using System.Text; |
||||
using ICSharpCode.Decompiler; |
||||
using ICSharpCode.Decompiler.Disassembler; |
||||
using ICSharpCode.NRefactory.Utils; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
using Mono.CSharp; |
||||
using Cecil = Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public abstract class ILNode |
||||
{ |
||||
public IEnumerable<T> GetSelfAndChildrenRecursive<T>(Func<T, bool> predicate = null) where T: ILNode |
||||
{ |
||||
List<T> result = new List<T>(16); |
||||
AccumulateSelfAndChildrenRecursive(result, predicate); |
||||
return result; |
||||
} |
||||
|
||||
void AccumulateSelfAndChildrenRecursive<T>(List<T> list, Func<T, bool> predicate) where T:ILNode |
||||
{ |
||||
// Note: RemoveEndFinally depends on self coming before children
|
||||
T thisAsT = this as T; |
||||
if (thisAsT != null && (predicate == null || predicate(thisAsT))) |
||||
list.Add(thisAsT); |
||||
foreach (ILNode node in this.GetChildren()) { |
||||
if (node != null) |
||||
node.AccumulateSelfAndChildrenRecursive(list, predicate); |
||||
} |
||||
} |
||||
|
||||
public virtual IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
yield break; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
StringWriter w = new StringWriter(); |
||||
WriteTo(new PlainTextOutput(w)); |
||||
return w.ToString().Replace("\r\n", "; "); |
||||
} |
||||
|
||||
public abstract void WriteTo(ITextOutput output); |
||||
} |
||||
|
||||
public class ILBlock: ILNode |
||||
{ |
||||
public ILExpression EntryGoto; |
||||
|
||||
public List<ILNode> Body; |
||||
|
||||
public ILBlock(params ILNode[] body) |
||||
{ |
||||
this.Body = new List<ILNode>(body); |
||||
} |
||||
|
||||
public ILBlock(List<ILNode> body) |
||||
{ |
||||
this.Body = body; |
||||
} |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
if (this.EntryGoto != null) |
||||
yield return this.EntryGoto; |
||||
foreach(ILNode child in this.Body) { |
||||
yield return child; |
||||
} |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
foreach(ILNode child in this.GetChildren()) { |
||||
child.WriteTo(output); |
||||
output.WriteLine(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public class ILBasicBlock: ILNode |
||||
{ |
||||
/// <remarks> Body has to start with a label and end with unconditional control flow </remarks>
|
||||
public List<ILNode> Body = new List<ILNode>(); |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
return this.Body; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
foreach(ILNode child in this.GetChildren()) { |
||||
child.WriteTo(output); |
||||
output.WriteLine(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public class ILLabel: ILNode |
||||
{ |
||||
public string Name; |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.WriteDefinition(Name + ":", this); |
||||
} |
||||
} |
||||
|
||||
public class ILTryCatchBlock: ILNode |
||||
{ |
||||
public class CatchBlock: ILBlock |
||||
{ |
||||
public TypeReference ExceptionType; |
||||
public ILVariable ExceptionVariable; |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.Write("catch "); |
||||
output.WriteReference(ExceptionType.FullName, ExceptionType); |
||||
if (ExceptionVariable != null) { |
||||
output.Write(' '); |
||||
output.Write(ExceptionVariable.Name); |
||||
} |
||||
output.WriteLine(" {"); |
||||
output.Indent(); |
||||
base.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
} |
||||
|
||||
public ILBlock TryBlock; |
||||
public List<CatchBlock> CatchBlocks; |
||||
public ILBlock FinallyBlock; |
||||
public ILBlock FaultBlock; |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
if (this.TryBlock != null) |
||||
yield return this.TryBlock; |
||||
foreach (var catchBlock in this.CatchBlocks) { |
||||
yield return catchBlock; |
||||
} |
||||
if (this.FaultBlock != null) |
||||
yield return this.FaultBlock; |
||||
if (this.FinallyBlock != null) |
||||
yield return this.FinallyBlock; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.WriteLine(".try {"); |
||||
output.Indent(); |
||||
TryBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
foreach (CatchBlock block in CatchBlocks) { |
||||
block.WriteTo(output); |
||||
} |
||||
if (FaultBlock != null) { |
||||
output.WriteLine("fault {"); |
||||
output.Indent(); |
||||
FaultBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
if (FinallyBlock != null) { |
||||
output.WriteLine("finally {"); |
||||
output.Indent(); |
||||
FinallyBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public class ILVariable |
||||
{ |
||||
public string Name; |
||||
public bool IsGenerated; |
||||
public TypeReference Type; |
||||
public VariableDefinition OriginalVariable; |
||||
public ParameterDefinition OriginalParameter; |
||||
|
||||
public bool IsPinned { |
||||
get { return OriginalVariable != null && OriginalVariable.IsPinned; } |
||||
} |
||||
|
||||
public bool IsParameter { |
||||
get { return OriginalParameter != null; } |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return Name; |
||||
} |
||||
} |
||||
|
||||
public class ILRange |
||||
{ |
||||
public int From; |
||||
public int To; // Exlusive
|
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Format("{0}-{1}", From.ToString("X"), To.ToString("X")); |
||||
} |
||||
|
||||
public static List<ILRange> OrderAndJoint(IEnumerable<ILRange> input) |
||||
{ |
||||
if (input == null) |
||||
throw new ArgumentNullException("Input is null!"); |
||||
|
||||
List<ILRange> ranges = input.Where(r => r != null).OrderBy(r => r.From).ToList(); |
||||
for (int i = 0; i < ranges.Count - 1;) { |
||||
ILRange curr = ranges[i]; |
||||
ILRange next = ranges[i + 1]; |
||||
// Merge consequtive ranges if they intersect
|
||||
if (curr.From <= next.From && next.From <= curr.To) { |
||||
curr.To = Math.Max(curr.To, next.To); |
||||
ranges.RemoveAt(i + 1); |
||||
} else { |
||||
i++; |
||||
} |
||||
} |
||||
return ranges; |
||||
} |
||||
|
||||
public static IEnumerable<ILRange> Invert(IEnumerable<ILRange> input, int codeSize) |
||||
{ |
||||
if (input == null) |
||||
throw new ArgumentNullException("Input is null!"); |
||||
|
||||
if (codeSize <= 0) |
||||
throw new ArgumentException("Code size must be grater than 0"); |
||||
|
||||
var ordered = OrderAndJoint(input); |
||||
if (ordered.Count == 0) { |
||||
yield return new ILRange() { From = 0, To = codeSize }; |
||||
} else { |
||||
// Gap before the first element
|
||||
if (ordered.First().From != 0) |
||||
yield return new ILRange() { From = 0, To = ordered.First().From }; |
||||
|
||||
// Gaps between elements
|
||||
for (int i = 0; i < ordered.Count - 1; i++) |
||||
yield return new ILRange() { From = ordered[i].To, To = ordered[i + 1].From }; |
||||
|
||||
// Gap after the last element
|
||||
Debug.Assert(ordered.Last().To <= codeSize); |
||||
if (ordered.Last().To != codeSize) |
||||
yield return new ILRange() { From = ordered.Last().To, To = codeSize }; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public class ILExpressionPrefix |
||||
{ |
||||
public readonly ILCode Code; |
||||
public readonly object Operand; |
||||
|
||||
public ILExpressionPrefix(ILCode code, object operand = null) |
||||
{ |
||||
this.Code = code; |
||||
this.Operand = operand; |
||||
} |
||||
} |
||||
|
||||
public class ILExpression : ILNode |
||||
{ |
||||
public ILCode Code { get; set; } |
||||
public object Operand { get; set; } |
||||
public List<ILExpression> Arguments { get; set; } |
||||
public ILExpressionPrefix[] Prefixes { get; set; } |
||||
// Mapping to the original instructions (useful for debugging)
|
||||
public List<ILRange> ILRanges { get; set; } |
||||
|
||||
public TypeReference ExpectedType { get; set; } |
||||
public TypeReference InferredType { get; set; } |
||||
|
||||
public static readonly object AnyOperand = new object(); |
||||
|
||||
public ILExpression(ILCode code, object operand, List<ILExpression> args) |
||||
{ |
||||
if (operand is ILExpression) |
||||
throw new ArgumentException("operand"); |
||||
|
||||
this.Code = code; |
||||
this.Operand = operand; |
||||
this.Arguments = new List<ILExpression>(args); |
||||
this.ILRanges = new List<ILRange>(1); |
||||
} |
||||
|
||||
public ILExpression(ILCode code, object operand, params ILExpression[] args) |
||||
{ |
||||
if (operand is ILExpression) |
||||
throw new ArgumentException("operand"); |
||||
|
||||
this.Code = code; |
||||
this.Operand = operand; |
||||
this.Arguments = new List<ILExpression>(args); |
||||
this.ILRanges = new List<ILRange>(1); |
||||
} |
||||
|
||||
public void AddPrefix(ILExpressionPrefix prefix) |
||||
{ |
||||
ILExpressionPrefix[] arr = this.Prefixes; |
||||
if (arr == null) |
||||
arr = new ILExpressionPrefix[1]; |
||||
else |
||||
Array.Resize(ref arr, arr.Length + 1); |
||||
arr[arr.Length - 1] = prefix; |
||||
this.Prefixes = arr; |
||||
} |
||||
|
||||
public ILExpressionPrefix GetPrefix(ILCode code) |
||||
{ |
||||
var prefixes = this.Prefixes; |
||||
if (prefixes != null) { |
||||
foreach (ILExpressionPrefix p in prefixes) { |
||||
if (p.Code == code) |
||||
return p; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
return Arguments; |
||||
} |
||||
|
||||
public bool IsBranch() |
||||
{ |
||||
return this.Operand is ILLabel || this.Operand is ILLabel[]; |
||||
} |
||||
|
||||
public IEnumerable<ILLabel> GetBranchTargets() |
||||
{ |
||||
if (this.Operand is ILLabel) { |
||||
return new ILLabel[] { (ILLabel)this.Operand }; |
||||
} else if (this.Operand is ILLabel[]) { |
||||
return (ILLabel[])this.Operand; |
||||
} else { |
||||
return new ILLabel[] { }; |
||||
} |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
if (Operand is ILVariable && ((ILVariable)Operand).IsGenerated) { |
||||
if (Code == ILCode.Stloc && this.InferredType == null) { |
||||
output.Write(((ILVariable)Operand).Name); |
||||
output.Write(" = "); |
||||
Arguments.First().WriteTo(output); |
||||
return; |
||||
} else if (Code == ILCode.Ldloc) { |
||||
output.Write(((ILVariable)Operand).Name); |
||||
if (this.InferredType != null) { |
||||
output.Write(':'); |
||||
this.InferredType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
if (this.ExpectedType != null && this.ExpectedType.FullName != this.InferredType.FullName) { |
||||
output.Write("[exp:"); |
||||
this.ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
output.Write(']'); |
||||
} |
||||
} |
||||
return; |
||||
} |
||||
} |
||||
|
||||
if (this.Prefixes != null) { |
||||
foreach (var prefix in this.Prefixes) { |
||||
output.Write(prefix.Code.GetName()); |
||||
output.Write(". "); |
||||
} |
||||
} |
||||
|
||||
output.Write(Code.GetName()); |
||||
if (this.InferredType != null) { |
||||
output.Write(':'); |
||||
this.InferredType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
if (this.ExpectedType != null && this.ExpectedType.FullName != this.InferredType.FullName) { |
||||
output.Write("[exp:"); |
||||
this.ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
output.Write(']'); |
||||
} |
||||
} else if (this.ExpectedType != null) { |
||||
output.Write("[exp:"); |
||||
this.ExpectedType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
output.Write(']'); |
||||
} |
||||
output.Write('('); |
||||
bool first = true; |
||||
if (Operand != null) { |
||||
if (Operand is ILLabel) { |
||||
output.WriteReference(((ILLabel)Operand).Name, Operand); |
||||
} else if (Operand is ILLabel[]) { |
||||
ILLabel[] labels = (ILLabel[])Operand; |
||||
for (int i = 0; i < labels.Length; i++) { |
||||
if (i > 0) |
||||
output.Write(", "); |
||||
output.WriteReference(labels[i].Name, labels[i]); |
||||
} |
||||
} else if (Operand is MethodReference) { |
||||
MethodReference method = (MethodReference)Operand; |
||||
if (method.DeclaringType != null) { |
||||
method.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
output.Write("::"); |
||||
} |
||||
output.WriteReference(method.Name, method); |
||||
} else if (Operand is FieldReference) { |
||||
FieldReference field = (FieldReference)Operand; |
||||
field.DeclaringType.WriteTo(output, ILNameSyntax.ShortTypeName); |
||||
output.Write("::"); |
||||
output.WriteReference(field.Name, field); |
||||
} else { |
||||
DisassemblerHelpers.WriteOperand(output, Operand); |
||||
} |
||||
first = false; |
||||
} |
||||
foreach (ILExpression arg in this.Arguments) { |
||||
if (!first) output.Write(", "); |
||||
arg.WriteTo(output); |
||||
first = false; |
||||
} |
||||
output.Write(')'); |
||||
} |
||||
} |
||||
|
||||
public class ILWhileLoop : ILNode |
||||
{ |
||||
public ILExpression Condition; |
||||
public ILBlock BodyBlock; |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
if (this.Condition != null) |
||||
yield return this.Condition; |
||||
if (this.BodyBlock != null) |
||||
yield return this.BodyBlock; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.WriteLine(""); |
||||
output.Write("loop ("); |
||||
if (this.Condition != null) |
||||
this.Condition.WriteTo(output); |
||||
output.WriteLine(") {"); |
||||
output.Indent(); |
||||
this.BodyBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
} |
||||
|
||||
public class ILCondition : ILNode |
||||
{ |
||||
public ILExpression Condition; |
||||
public ILBlock TrueBlock; // Branch was taken
|
||||
public ILBlock FalseBlock; // Fall-though
|
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
if (this.Condition != null) |
||||
yield return this.Condition; |
||||
if (this.TrueBlock != null) |
||||
yield return this.TrueBlock; |
||||
if (this.FalseBlock != null) |
||||
yield return this.FalseBlock; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.Write("if ("); |
||||
Condition.WriteTo(output); |
||||
output.WriteLine(") {"); |
||||
output.Indent(); |
||||
TrueBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.Write("}"); |
||||
if (FalseBlock != null) { |
||||
output.WriteLine(" else {"); |
||||
output.Indent(); |
||||
FalseBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
public class ILSwitch: ILNode |
||||
{ |
||||
public class CaseBlock: ILBlock |
||||
{ |
||||
public List<int> Values; // null for the default case
|
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
if (this.Values != null) { |
||||
foreach (int i in this.Values) { |
||||
output.WriteLine("case {0}:", i); |
||||
} |
||||
} else { |
||||
output.WriteLine("default:"); |
||||
} |
||||
output.Indent(); |
||||
base.WriteTo(output); |
||||
output.Unindent(); |
||||
} |
||||
} |
||||
|
||||
public ILExpression Condition; |
||||
public List<CaseBlock> CaseBlocks = new List<CaseBlock>(); |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
if (this.Condition != null) |
||||
yield return this.Condition; |
||||
foreach (ILBlock caseBlock in this.CaseBlocks) { |
||||
yield return caseBlock; |
||||
} |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.Write("switch ("); |
||||
Condition.WriteTo(output); |
||||
output.WriteLine(") {"); |
||||
output.Indent(); |
||||
foreach (CaseBlock caseBlock in this.CaseBlocks) { |
||||
caseBlock.WriteTo(output); |
||||
} |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
} |
||||
|
||||
public class ILFixedStatement : ILNode |
||||
{ |
||||
public List<ILExpression> Initializers = new List<ILExpression>(); |
||||
public ILBlock BodyBlock; |
||||
|
||||
public override IEnumerable<ILNode> GetChildren() |
||||
{ |
||||
foreach (ILExpression initializer in this.Initializers) |
||||
yield return initializer; |
||||
if (this.BodyBlock != null) |
||||
yield return this.BodyBlock; |
||||
} |
||||
|
||||
public override void WriteTo(ITextOutput output) |
||||
{ |
||||
output.Write("fixed ("); |
||||
for (int i = 0; i < this.Initializers.Count; i++) { |
||||
if (i > 0) |
||||
output.Write(", "); |
||||
this.Initializers[i].WriteTo(output); |
||||
} |
||||
output.WriteLine(") {"); |
||||
output.Indent(); |
||||
this.BodyBlock.WriteTo(output); |
||||
output.Unindent(); |
||||
output.WriteLine("}"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,459 @@
@@ -0,0 +1,459 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using Mono.Cecil; |
||||
using Mono.Cecil.Cil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public enum ILCode |
||||
{ |
||||
// For convenience, the start is exactly identical to Mono.Cecil.Cil.Code
|
||||
// Instructions that should not be used are prepended by __
|
||||
Nop, |
||||
Break, |
||||
__Ldarg_0, |
||||
__Ldarg_1, |
||||
__Ldarg_2, |
||||
__Ldarg_3, |
||||
__Ldloc_0, |
||||
__Ldloc_1, |
||||
__Ldloc_2, |
||||
__Ldloc_3, |
||||
__Stloc_0, |
||||
__Stloc_1, |
||||
__Stloc_2, |
||||
__Stloc_3, |
||||
__Ldarg_S, |
||||
__Ldarga_S, |
||||
__Starg_S, |
||||
__Ldloc_S, |
||||
__Ldloca_S, |
||||
__Stloc_S, |
||||
Ldnull, |
||||
__Ldc_I4_M1, |
||||
__Ldc_I4_0, |
||||
__Ldc_I4_1, |
||||
__Ldc_I4_2, |
||||
__Ldc_I4_3, |
||||
__Ldc_I4_4, |
||||
__Ldc_I4_5, |
||||
__Ldc_I4_6, |
||||
__Ldc_I4_7, |
||||
__Ldc_I4_8, |
||||
__Ldc_I4_S, |
||||
Ldc_I4, |
||||
Ldc_I8, |
||||
Ldc_R4, |
||||
Ldc_R8, |
||||
Dup, |
||||
Pop, |
||||
Jmp, |
||||
Call, |
||||
Calli, |
||||
Ret, |
||||
__Br_S, |
||||
__Brfalse_S, |
||||
__Brtrue_S, |
||||
__Beq_S, |
||||
__Bge_S, |
||||
__Bgt_S, |
||||
__Ble_S, |
||||
__Blt_S, |
||||
__Bne_Un_S, |
||||
__Bge_Un_S, |
||||
__Bgt_Un_S, |
||||
__Ble_Un_S, |
||||
__Blt_Un_S, |
||||
Br, |
||||
__Brfalse, |
||||
Brtrue, |
||||
__Beq, |
||||
__Bge, |
||||
__Bgt, |
||||
__Ble, |
||||
__Blt, |
||||
__Bne_Un, |
||||
__Bge_Un, |
||||
__Bgt_Un, |
||||
__Ble_Un, |
||||
__Blt_Un, |
||||
Switch, |
||||
__Ldind_I1, |
||||
__Ldind_U1, |
||||
__Ldind_I2, |
||||
__Ldind_U2, |
||||
__Ldind_I4, |
||||
__Ldind_U4, |
||||
__Ldind_I8, |
||||
__Ldind_I, |
||||
__Ldind_R4, |
||||
__Ldind_R8, |
||||
Ldind_Ref, |
||||
Stind_Ref, |
||||
__Stind_I1, |
||||
__Stind_I2, |
||||
__Stind_I4, |
||||
__Stind_I8, |
||||
__Stind_R4, |
||||
__Stind_R8, |
||||
Add, |
||||
Sub, |
||||
Mul, |
||||
Div, |
||||
Div_Un, |
||||
Rem, |
||||
Rem_Un, |
||||
And, |
||||
Or, |
||||
Xor, |
||||
Shl, |
||||
Shr, |
||||
Shr_Un, |
||||
Neg, |
||||
Not, |
||||
Conv_I1, |
||||
Conv_I2, |
||||
Conv_I4, |
||||
Conv_I8, |
||||
Conv_R4, |
||||
Conv_R8, |
||||
Conv_U4, |
||||
Conv_U8, |
||||
Callvirt, |
||||
Cpobj, |
||||
Ldobj, |
||||
Ldstr, |
||||
Newobj, |
||||
Castclass, |
||||
Isinst, |
||||
Conv_R_Un, |
||||
Unbox, |
||||
Throw, |
||||
Ldfld, |
||||
Ldflda, |
||||
Stfld, |
||||
Ldsfld, |
||||
Ldsflda, |
||||
Stsfld, |
||||
Stobj, |
||||
Conv_Ovf_I1_Un, |
||||
Conv_Ovf_I2_Un, |
||||
Conv_Ovf_I4_Un, |
||||
Conv_Ovf_I8_Un, |
||||
Conv_Ovf_U1_Un, |
||||
Conv_Ovf_U2_Un, |
||||
Conv_Ovf_U4_Un, |
||||
Conv_Ovf_U8_Un, |
||||
Conv_Ovf_I_Un, |
||||
Conv_Ovf_U_Un, |
||||
Box, |
||||
Newarr, |
||||
Ldlen, |
||||
Ldelema, |
||||
Ldelem_I1, |
||||
Ldelem_U1, |
||||
Ldelem_I2, |
||||
Ldelem_U2, |
||||
Ldelem_I4, |
||||
Ldelem_U4, |
||||
Ldelem_I8, |
||||
Ldelem_I, |
||||
Ldelem_R4, |
||||
Ldelem_R8, |
||||
Ldelem_Ref, |
||||
Stelem_I, |
||||
Stelem_I1, |
||||
Stelem_I2, |
||||
Stelem_I4, |
||||
Stelem_I8, |
||||
Stelem_R4, |
||||
Stelem_R8, |
||||
Stelem_Ref, |
||||
Ldelem_Any, |
||||
Stelem_Any, |
||||
Unbox_Any, |
||||
Conv_Ovf_I1, |
||||
Conv_Ovf_U1, |
||||
Conv_Ovf_I2, |
||||
Conv_Ovf_U2, |
||||
Conv_Ovf_I4, |
||||
Conv_Ovf_U4, |
||||
Conv_Ovf_I8, |
||||
Conv_Ovf_U8, |
||||
Refanyval, |
||||
Ckfinite, |
||||
Mkrefany, |
||||
Ldtoken, |
||||
Conv_U2, |
||||
Conv_U1, |
||||
Conv_I, |
||||
Conv_Ovf_I, |
||||
Conv_Ovf_U, |
||||
Add_Ovf, |
||||
Add_Ovf_Un, |
||||
Mul_Ovf, |
||||
Mul_Ovf_Un, |
||||
Sub_Ovf, |
||||
Sub_Ovf_Un, |
||||
Endfinally, |
||||
Leave, |
||||
__Leave_S, |
||||
__Stind_I, |
||||
Conv_U, |
||||
Arglist, |
||||
Ceq, |
||||
Cgt, |
||||
Cgt_Un, |
||||
Clt, |
||||
Clt_Un, |
||||
Ldftn, |
||||
Ldvirtftn, |
||||
__Ldarg, |
||||
__Ldarga, |
||||
__Starg, |
||||
Ldloc, |
||||
Ldloca, |
||||
Stloc, |
||||
Localloc, |
||||
Endfilter, |
||||
Unaligned, |
||||
Volatile, |
||||
Tail, |
||||
Initobj, |
||||
Constrained, |
||||
Cpblk, |
||||
Initblk, |
||||
No, |
||||
Rethrow, |
||||
Sizeof, |
||||
Refanytype, |
||||
Readonly, |
||||
|
||||
// Virtual codes - defined for convenience
|
||||
Ldexception, // Operand holds the CatchType for catch handler, null for filter
|
||||
LogicNot, |
||||
LogicAnd, |
||||
LogicOr, |
||||
NullCoalescing, |
||||
InitArray, // Array Initializer
|
||||
|
||||
// new Class { Prop = 1, Collection = { { 2, 3 }, {4, 5} }}
|
||||
// is represented as:
|
||||
// InitObject(newobj Class,
|
||||
// CallSetter(Prop, InitializedObject, 1),
|
||||
// InitCollection(CallGetter(Collection, InitializedObject))),
|
||||
// Call(Add, InitializedObject, 2, 3),
|
||||
// Call(Add, InitializedObject, 4, 5)))
|
||||
InitObject, // Object initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements
|
||||
InitCollection, // Collection initializer: first arg is newobj/defaultvalue, remaining args are the initializing statements
|
||||
InitializedObject, // Refers the the object being initialized (refers to first arg in parent InitObject or InitCollection instruction)
|
||||
|
||||
TernaryOp, // ?:
|
||||
LoopOrSwitchBreak, |
||||
LoopContinue, |
||||
Ldc_Decimal, |
||||
YieldBreak, |
||||
YieldReturn, |
||||
/// <summary>
|
||||
/// Represents the 'default(T)' instruction.
|
||||
/// </summary>
|
||||
/// <remarks>Introduced by SimplifyLdObjAndStObj step</remarks>
|
||||
DefaultValue, |
||||
/// <summary>
|
||||
/// ILExpression with a single child: binary operator.
|
||||
/// This expression means that the binary operator will also assign the new value to its left-hand side.
|
||||
/// 'CompoundAssignment' must not be used for local variables, as inlining (and other) optimizations don't know that it modifies the variable.
|
||||
/// </summary>
|
||||
/// <remarks>Introduced by MakeCompoundAssignments step</remarks>
|
||||
CompoundAssignment, |
||||
/// <summary>
|
||||
/// Represents the post-increment operator.
|
||||
/// The first argument is the address of the variable to increment (ldloca instruction).
|
||||
/// The second arugment is the amount the variable is incremented by (ldc.i4 instruction)
|
||||
/// </summary>
|
||||
/// <remarks>Introduced by IntroducePostIncrement step</remarks>
|
||||
PostIncrement, |
||||
PostIncrement_Ovf, // checked variant of PostIncrement
|
||||
PostIncrement_Ovf_Un, // checked variant of PostIncrement, for unsigned integers
|
||||
/// <summary>Calls the getter of a static property (or indexer), or of an instance property on 'base'</summary>
|
||||
CallGetter, |
||||
/// <summary>Calls the getter of an instance property (or indexer)</summary>
|
||||
CallvirtGetter, |
||||
/// <summary>Calls the setter of a static property (or indexer), or of an instance property on 'base'</summary>
|
||||
/// <remarks>This allows us to represent "while ((SomeProperty = val) != null) {}"</remarks>
|
||||
CallSetter, |
||||
/// <summary>Calls the setter of a instance property (or indexer)</summary>
|
||||
CallvirtSetter, |
||||
/// <summary>Simulates getting the address of the argument instruction.</summary>
|
||||
/// <remarks>
|
||||
/// Used for postincrement for properties, and to represent the Address() method on multi-dimensional arrays.
|
||||
/// Also used when inlining a method call on a value type: "stloc(v, ...); call(M, ldloca(v));" becomes "call(M, AddressOf(...))"
|
||||
/// </remarks>
|
||||
AddressOf |
||||
} |
||||
|
||||
public static class ILCodeUtil |
||||
{ |
||||
public static string GetName(this ILCode code) |
||||
{ |
||||
return code.ToString().ToLowerInvariant().TrimStart('_').Replace('_','.'); |
||||
} |
||||
|
||||
public static bool IsConditionalControlFlow(this ILCode code) |
||||
{ |
||||
switch(code) { |
||||
case ILCode.__Brfalse_S: |
||||
case ILCode.__Brtrue_S: |
||||
case ILCode.__Beq_S: |
||||
case ILCode.__Bge_S: |
||||
case ILCode.__Bgt_S: |
||||
case ILCode.__Ble_S: |
||||
case ILCode.__Blt_S: |
||||
case ILCode.__Bne_Un_S: |
||||
case ILCode.__Bge_Un_S: |
||||
case ILCode.__Bgt_Un_S: |
||||
case ILCode.__Ble_Un_S: |
||||
case ILCode.__Blt_Un_S: |
||||
case ILCode.__Brfalse: |
||||
case ILCode.Brtrue: |
||||
case ILCode.__Beq: |
||||
case ILCode.__Bge: |
||||
case ILCode.__Bgt: |
||||
case ILCode.__Ble: |
||||
case ILCode.__Blt: |
||||
case ILCode.__Bne_Un: |
||||
case ILCode.__Bge_Un: |
||||
case ILCode.__Bgt_Un: |
||||
case ILCode.__Ble_Un: |
||||
case ILCode.__Blt_Un: |
||||
case ILCode.Switch: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static bool IsUnconditionalControlFlow(this ILCode code) |
||||
{ |
||||
switch(code) { |
||||
case ILCode.Br: |
||||
case ILCode.__Br_S: |
||||
case ILCode.Leave: |
||||
case ILCode.__Leave_S: |
||||
case ILCode.Ret: |
||||
case ILCode.Endfilter: |
||||
case ILCode.Endfinally: |
||||
case ILCode.Throw: |
||||
case ILCode.Rethrow: |
||||
case ILCode.LoopContinue: |
||||
case ILCode.LoopOrSwitchBreak: |
||||
case ILCode.YieldBreak: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static void ExpandMacro(ref ILCode code, ref object operand, MethodBody methodBody) |
||||
{ |
||||
switch (code) { |
||||
case ILCode.__Ldarg_0: code = ILCode.__Ldarg; operand = methodBody.GetParameter(0); break; |
||||
case ILCode.__Ldarg_1: code = ILCode.__Ldarg; operand = methodBody.GetParameter(1); break; |
||||
case ILCode.__Ldarg_2: code = ILCode.__Ldarg; operand = methodBody.GetParameter(2); break; |
||||
case ILCode.__Ldarg_3: code = ILCode.__Ldarg; operand = methodBody.GetParameter(3); break; |
||||
case ILCode.__Ldloc_0: code = ILCode.Ldloc; operand = methodBody.Variables[0]; break; |
||||
case ILCode.__Ldloc_1: code = ILCode.Ldloc; operand = methodBody.Variables[1]; break; |
||||
case ILCode.__Ldloc_2: code = ILCode.Ldloc; operand = methodBody.Variables[2]; break; |
||||
case ILCode.__Ldloc_3: code = ILCode.Ldloc; operand = methodBody.Variables[3]; break; |
||||
case ILCode.__Stloc_0: code = ILCode.Stloc; operand = methodBody.Variables[0]; break; |
||||
case ILCode.__Stloc_1: code = ILCode.Stloc; operand = methodBody.Variables[1]; break; |
||||
case ILCode.__Stloc_2: code = ILCode.Stloc; operand = methodBody.Variables[2]; break; |
||||
case ILCode.__Stloc_3: code = ILCode.Stloc; operand = methodBody.Variables[3]; break; |
||||
case ILCode.__Ldarg_S: code = ILCode.__Ldarg; break; |
||||
case ILCode.__Ldarga_S: code = ILCode.__Ldarga; break; |
||||
case ILCode.__Starg_S: code = ILCode.__Starg; break; |
||||
case ILCode.__Ldloc_S: code = ILCode.Ldloc; break; |
||||
case ILCode.__Ldloca_S: code = ILCode.Ldloca; break; |
||||
case ILCode.__Stloc_S: code = ILCode.Stloc; break; |
||||
case ILCode.__Ldc_I4_M1: code = ILCode.Ldc_I4; operand = -1; break; |
||||
case ILCode.__Ldc_I4_0: code = ILCode.Ldc_I4; operand = 0; break; |
||||
case ILCode.__Ldc_I4_1: code = ILCode.Ldc_I4; operand = 1; break; |
||||
case ILCode.__Ldc_I4_2: code = ILCode.Ldc_I4; operand = 2; break; |
||||
case ILCode.__Ldc_I4_3: code = ILCode.Ldc_I4; operand = 3; break; |
||||
case ILCode.__Ldc_I4_4: code = ILCode.Ldc_I4; operand = 4; break; |
||||
case ILCode.__Ldc_I4_5: code = ILCode.Ldc_I4; operand = 5; break; |
||||
case ILCode.__Ldc_I4_6: code = ILCode.Ldc_I4; operand = 6; break; |
||||
case ILCode.__Ldc_I4_7: code = ILCode.Ldc_I4; operand = 7; break; |
||||
case ILCode.__Ldc_I4_8: code = ILCode.Ldc_I4; operand = 8; break; |
||||
case ILCode.__Ldc_I4_S: code = ILCode.Ldc_I4; operand = (int) (sbyte) operand; break; |
||||
case ILCode.__Br_S: code = ILCode.Br; break; |
||||
case ILCode.__Brfalse_S: code = ILCode.__Brfalse; break; |
||||
case ILCode.__Brtrue_S: code = ILCode.Brtrue; break; |
||||
case ILCode.__Beq_S: code = ILCode.__Beq; break; |
||||
case ILCode.__Bge_S: code = ILCode.__Bge; break; |
||||
case ILCode.__Bgt_S: code = ILCode.__Bgt; break; |
||||
case ILCode.__Ble_S: code = ILCode.__Ble; break; |
||||
case ILCode.__Blt_S: code = ILCode.__Blt; break; |
||||
case ILCode.__Bne_Un_S: code = ILCode.__Bne_Un; break; |
||||
case ILCode.__Bge_Un_S: code = ILCode.__Bge_Un; break; |
||||
case ILCode.__Bgt_Un_S: code = ILCode.__Bgt_Un; break; |
||||
case ILCode.__Ble_Un_S: code = ILCode.__Ble_Un; break; |
||||
case ILCode.__Blt_Un_S: code = ILCode.__Blt_Un; break; |
||||
case ILCode.__Leave_S: code = ILCode.Leave; break; |
||||
case ILCode.__Ldind_I: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.IntPtr; break; |
||||
case ILCode.__Ldind_I1: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.SByte; break; |
||||
case ILCode.__Ldind_I2: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int16; break; |
||||
case ILCode.__Ldind_I4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int32; break; |
||||
case ILCode.__Ldind_I8: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Int64; break; |
||||
case ILCode.__Ldind_U1: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Byte; break; |
||||
case ILCode.__Ldind_U2: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.UInt16; break; |
||||
case ILCode.__Ldind_U4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.UInt32; break; |
||||
case ILCode.__Ldind_R4: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Single; break; |
||||
case ILCode.__Ldind_R8: code = ILCode.Ldobj; operand = methodBody.Method.Module.TypeSystem.Double; break; |
||||
case ILCode.__Stind_I: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.IntPtr; break; |
||||
case ILCode.__Stind_I1: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Byte; break; |
||||
case ILCode.__Stind_I2: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int16; break; |
||||
case ILCode.__Stind_I4: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int32; break; |
||||
case ILCode.__Stind_I8: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Int64; break; |
||||
case ILCode.__Stind_R4: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Single; break; |
||||
case ILCode.__Stind_R8: code = ILCode.Stobj; operand = methodBody.Method.Module.TypeSystem.Double; break; |
||||
} |
||||
} |
||||
|
||||
public static ParameterDefinition GetParameter (this MethodBody self, int index) |
||||
{ |
||||
var method = self.Method; |
||||
|
||||
if (method.HasThis) { |
||||
if (index == 0) |
||||
return self.ThisParameter; |
||||
|
||||
index--; |
||||
} |
||||
|
||||
var parameters = method.Parameters; |
||||
|
||||
if (index < 0 || index >= parameters.Count) |
||||
return null; |
||||
|
||||
return parameters [index]; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,515 @@
@@ -0,0 +1,515 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
/// <summary>
|
||||
/// Performs inlining transformations.
|
||||
/// </summary>
|
||||
public class ILInlining |
||||
{ |
||||
readonly ILBlock method; |
||||
internal Dictionary<ILVariable, int> numStloc = new Dictionary<ILVariable, int>(); |
||||
internal Dictionary<ILVariable, int> numLdloc = new Dictionary<ILVariable, int>(); |
||||
internal Dictionary<ILVariable, int> numLdloca = new Dictionary<ILVariable, int>(); |
||||
|
||||
public ILInlining(ILBlock method) |
||||
{ |
||||
this.method = method; |
||||
AnalyzeMethod(); |
||||
} |
||||
|
||||
void AnalyzeMethod() |
||||
{ |
||||
numStloc.Clear(); |
||||
numLdloc.Clear(); |
||||
numLdloca.Clear(); |
||||
|
||||
// Analyse the whole method
|
||||
AnalyzeNode(method); |
||||
} |
||||
|
||||
void AnalyzeNode(ILNode node) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
if (expr != null) { |
||||
ILVariable locVar = expr.Operand as ILVariable; |
||||
if (locVar != null) { |
||||
if (expr.Code == ILCode.Stloc) { |
||||
numStloc[locVar] = numStloc.GetOrDefault(locVar) + 1; |
||||
} else if (expr.Code == ILCode.Ldloc) { |
||||
numLdloc[locVar] = numLdloc.GetOrDefault(locVar) + 1; |
||||
} else if (expr.Code == ILCode.Ldloca) { |
||||
numLdloca[locVar] = numLdloca.GetOrDefault(locVar) + 1; |
||||
} else { |
||||
throw new NotSupportedException(expr.Code.ToString()); |
||||
} |
||||
} |
||||
foreach (ILExpression child in expr.Arguments) |
||||
AnalyzeNode(child); |
||||
} else { |
||||
var catchBlock = node as ILTryCatchBlock.CatchBlock; |
||||
if (catchBlock != null && catchBlock.ExceptionVariable != null) { |
||||
numStloc[catchBlock.ExceptionVariable] = numStloc.GetOrDefault(catchBlock.ExceptionVariable) + 1; |
||||
} |
||||
|
||||
foreach (ILNode child in node.GetChildren()) |
||||
AnalyzeNode(child); |
||||
} |
||||
} |
||||
|
||||
public bool InlineAllVariables() |
||||
{ |
||||
bool modified = false; |
||||
ILInlining i = new ILInlining(method); |
||||
foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) |
||||
modified |= i.InlineAllInBlock(block); |
||||
return modified; |
||||
} |
||||
|
||||
public bool InlineAllInBlock(ILBlock block) |
||||
{ |
||||
bool modified = false; |
||||
List<ILNode> body = block.Body; |
||||
if (block is ILTryCatchBlock.CatchBlock && body.Count > 1) { |
||||
ILVariable v = ((ILTryCatchBlock.CatchBlock)block).ExceptionVariable; |
||||
if (v != null && v.IsGenerated) { |
||||
if (numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1 && numLdloc.GetOrDefault(v) == 1) { |
||||
ILVariable v2; |
||||
ILExpression ldException; |
||||
if (body[0].Match(ILCode.Stloc, out v2, out ldException) && ldException.MatchLdloc(v)) { |
||||
body.RemoveAt(0); |
||||
((ILTryCatchBlock.CatchBlock)block).ExceptionVariable = v2; |
||||
modified = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
for(int i = 0; i < body.Count - 1;) { |
||||
ILVariable locVar; |
||||
ILExpression expr; |
||||
if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(block.Body, i, aggressive: false)) { |
||||
modified = true; |
||||
i = Math.Max(0, i - 1); // Go back one step
|
||||
} else { |
||||
i++; |
||||
} |
||||
} |
||||
foreach(ILBasicBlock bb in body.OfType<ILBasicBlock>()) { |
||||
modified |= InlineAllInBasicBlock(bb); |
||||
} |
||||
return modified; |
||||
} |
||||
|
||||
public bool InlineAllInBasicBlock(ILBasicBlock bb) |
||||
{ |
||||
bool modified = false; |
||||
List<ILNode> body = bb.Body; |
||||
for(int i = 0; i < body.Count;) { |
||||
ILVariable locVar; |
||||
ILExpression expr; |
||||
if (body[i].Match(ILCode.Stloc, out locVar, out expr) && InlineOneIfPossible(bb.Body, i, aggressive: false)) { |
||||
modified = true; |
||||
i = Math.Max(0, i - 1); // Go back one step
|
||||
} else { |
||||
i++; |
||||
} |
||||
} |
||||
return modified; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Inlines instructions before pos into block.Body[pos].
|
||||
/// </summary>
|
||||
/// <returns>The number of instructions that were inlined.</returns>
|
||||
public int InlineInto(List<ILNode> body, int pos, bool aggressive) |
||||
{ |
||||
if (pos >= body.Count) |
||||
return 0; |
||||
int count = 0; |
||||
while (--pos >= 0) { |
||||
ILExpression expr = body[pos] as ILExpression; |
||||
if (expr == null || expr.Code != ILCode.Stloc) |
||||
break; |
||||
if (InlineOneIfPossible(body, pos, aggressive)) |
||||
count++; |
||||
else |
||||
break; |
||||
} |
||||
return count; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Aggressively inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
|
||||
/// If inlining was possible; we will continue to inline (non-aggressively) into the the combined instruction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// After the operation, pos will point to the new combined instruction.
|
||||
/// </remarks>
|
||||
public bool InlineIfPossible(List<ILNode> body, ref int pos) |
||||
{ |
||||
if (InlineOneIfPossible(body, pos, true)) { |
||||
pos -= InlineInto(body, pos, false); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Inlines the stloc instruction at block.Body[pos] into the next instruction, if possible.
|
||||
/// </summary>
|
||||
public bool InlineOneIfPossible(List<ILNode> body, int pos, bool aggressive) |
||||
{ |
||||
ILVariable v; |
||||
ILExpression inlinedExpression; |
||||
if (body[pos].Match(ILCode.Stloc, out v, out inlinedExpression) && !v.IsPinned) { |
||||
if (InlineIfPossible(v, inlinedExpression, body.ElementAtOrDefault(pos+1), aggressive)) { |
||||
// Assign the ranges of the stloc instruction:
|
||||
inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges); |
||||
// Remove the stloc instruction:
|
||||
body.RemoveAt(pos); |
||||
return true; |
||||
} else if (numLdloc.GetOrDefault(v) == 0 && numLdloca.GetOrDefault(v) == 0) { |
||||
// The variable is never loaded
|
||||
if (inlinedExpression.HasNoSideEffects()) { |
||||
// Remove completely
|
||||
body.RemoveAt(pos); |
||||
return true; |
||||
} else if (inlinedExpression.CanBeExpressionStatement() && v.IsGenerated) { |
||||
// Assign the ranges of the stloc instruction:
|
||||
inlinedExpression.ILRanges.AddRange(((ILExpression)body[pos]).ILRanges); |
||||
// Remove the stloc, but keep the inner expression
|
||||
body[pos] = inlinedExpression; |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Inlines 'expr' into 'next', if possible.
|
||||
/// </summary>
|
||||
bool InlineIfPossible(ILVariable v, ILExpression inlinedExpression, ILNode next, bool aggressive) |
||||
{ |
||||
// ensure the variable is accessed only a single time
|
||||
if (numStloc.GetOrDefault(v) != 1) |
||||
return false; |
||||
int ldloc = numLdloc.GetOrDefault(v); |
||||
if (ldloc > 1 || ldloc + numLdloca.GetOrDefault(v) != 1) |
||||
return false; |
||||
|
||||
if (next is ILCondition) |
||||
next = ((ILCondition)next).Condition; |
||||
else if (next is ILWhileLoop) |
||||
next = ((ILWhileLoop)next).Condition; |
||||
|
||||
ILExpression parent; |
||||
int pos; |
||||
if (FindLoadInNext(next as ILExpression, v, inlinedExpression, out parent, out pos) == true) { |
||||
if (ldloc == 0) { |
||||
if (!IsGeneratedValueTypeTemporary((ILExpression)next, parent, pos, v, inlinedExpression)) |
||||
return false; |
||||
} else { |
||||
if (!aggressive && !v.IsGenerated && !NonAggressiveInlineInto((ILExpression)next, parent, inlinedExpression)) |
||||
return false; |
||||
} |
||||
|
||||
// Assign the ranges of the ldloc instruction:
|
||||
inlinedExpression.ILRanges.AddRange(parent.Arguments[pos].ILRanges); |
||||
|
||||
if (ldloc == 0) { |
||||
// it was an ldloca instruction, so we need to use the pseudo-opcode 'addressof' so that the types
|
||||
// comes out correctly
|
||||
parent.Arguments[pos] = new ILExpression(ILCode.AddressOf, null, inlinedExpression); |
||||
} else { |
||||
parent.Arguments[pos] = inlinedExpression; |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Is this a temporary variable generated by the C# compiler for instance method calls on value type values
|
||||
/// </summary>
|
||||
/// <param name="next">The next top-level expression</param>
|
||||
/// <param name="parent">The direct parent of the load within 'next'</param>
|
||||
/// <param name="pos">Index of the load within 'parent'</param>
|
||||
/// <param name="v">The variable being inlined.</param>
|
||||
/// <param name="inlinedExpression">The expression being inlined</param>
|
||||
bool IsGeneratedValueTypeTemporary(ILExpression next, ILExpression parent, int pos, ILVariable v, ILExpression inlinedExpression) |
||||
{ |
||||
if (pos == 0 && v.Type != null && v.Type.IsValueType) { |
||||
// Inlining a value type variable is allowed only if the resulting code will maintain the semantics
|
||||
// that the method is operating on a copy.
|
||||
// Thus, we have to disallow inlining of other locals, fields, array elements, dereferenced pointers
|
||||
switch (inlinedExpression.Code) { |
||||
case ILCode.Ldloc: |
||||
case ILCode.Stloc: |
||||
case ILCode.CompoundAssignment: |
||||
case ILCode.Ldelem_Any: |
||||
case ILCode.Ldelem_I: |
||||
case ILCode.Ldelem_I1: |
||||
case ILCode.Ldelem_I2: |
||||
case ILCode.Ldelem_I4: |
||||
case ILCode.Ldelem_I8: |
||||
case ILCode.Ldelem_R4: |
||||
case ILCode.Ldelem_R8: |
||||
case ILCode.Ldelem_Ref: |
||||
case ILCode.Ldelem_U1: |
||||
case ILCode.Ldelem_U2: |
||||
case ILCode.Ldelem_U4: |
||||
case ILCode.Ldobj: |
||||
case ILCode.Ldind_Ref: |
||||
return false; |
||||
case ILCode.Ldfld: |
||||
case ILCode.Stfld: |
||||
case ILCode.Ldsfld: |
||||
case ILCode.Stsfld: |
||||
// allow inlining field access only if it's a readonly field
|
||||
FieldDefinition f = ((FieldReference)inlinedExpression.Operand).Resolve(); |
||||
if (!(f != null && f.IsInitOnly)) |
||||
return false; |
||||
break; |
||||
case ILCode.Call: |
||||
case ILCode.CallGetter: |
||||
// inlining runs both before and after IntroducePropertyAccessInstructions,
|
||||
// so we have to handle both 'call' and 'callgetter'
|
||||
MethodReference mr = (MethodReference)inlinedExpression.Operand; |
||||
// ensure that it's not an multi-dimensional array getter
|
||||
if (mr.DeclaringType is ArrayType) |
||||
return false; |
||||
goto case ILCode.Callvirt; |
||||
case ILCode.Callvirt: |
||||
case ILCode.CallvirtGetter: |
||||
// don't inline foreach loop variables:
|
||||
mr = (MethodReference)inlinedExpression.Operand; |
||||
if (mr.Name == "get_Current" && mr.HasThis) |
||||
return false; |
||||
break; |
||||
case ILCode.Castclass: |
||||
case ILCode.Unbox_Any: |
||||
// These are valid, but might occur as part of a foreach loop variable.
|
||||
ILExpression arg = inlinedExpression.Arguments[0]; |
||||
if (arg.Code == ILCode.CallGetter || arg.Code == ILCode.CallvirtGetter || arg.Code == ILCode.Call || arg.Code == ILCode.Callvirt) { |
||||
mr = (MethodReference)arg.Operand; |
||||
if (mr.Name == "get_Current" && mr.HasThis) |
||||
return false; // looks like a foreach loop variable, so don't inline it
|
||||
} |
||||
break; |
||||
} |
||||
|
||||
// inline the compiler-generated variable that are used when accessing a member on a value type:
|
||||
switch (parent.Code) { |
||||
case ILCode.Call: |
||||
case ILCode.CallGetter: |
||||
case ILCode.CallSetter: |
||||
case ILCode.Callvirt: |
||||
case ILCode.CallvirtGetter: |
||||
case ILCode.CallvirtSetter: |
||||
MethodReference mr = (MethodReference)parent.Operand; |
||||
return mr.HasThis; |
||||
case ILCode.Stfld: |
||||
case ILCode.Ldfld: |
||||
case ILCode.Ldflda: |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Determines whether a variable should be inlined in non-aggressive mode, even though it is not a generated variable.
|
||||
/// </summary>
|
||||
/// <param name="next">The next top-level expression</param>
|
||||
/// <param name="parent">The direct parent of the load within 'next'</param>
|
||||
/// <param name="inlinedExpression">The expression being inlined</param>
|
||||
bool NonAggressiveInlineInto(ILExpression next, ILExpression parent, ILExpression inlinedExpression) |
||||
{ |
||||
if (inlinedExpression.Code == ILCode.DefaultValue) |
||||
return true; |
||||
|
||||
switch (next.Code) { |
||||
case ILCode.Ret: |
||||
case ILCode.Brtrue: |
||||
return parent == next; |
||||
case ILCode.Switch: |
||||
return parent == next || (parent.Code == ILCode.Sub && parent == next.Arguments[0]); |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether 'expressionBeingMoved' can be inlined into 'expr'.
|
||||
/// </summary>
|
||||
public bool CanInlineInto(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved) |
||||
{ |
||||
ILExpression parent; |
||||
int pos; |
||||
return FindLoadInNext(expr, v, expressionBeingMoved, out parent, out pos) == true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Finds the position to inline to.
|
||||
/// </summary>
|
||||
/// <returns>true = found; false = cannot continue search; null = not found</returns>
|
||||
bool? FindLoadInNext(ILExpression expr, ILVariable v, ILExpression expressionBeingMoved, out ILExpression parent, out int pos) |
||||
{ |
||||
parent = null; |
||||
pos = 0; |
||||
if (expr == null) |
||||
return false; |
||||
for (int i = 0; i < expr.Arguments.Count; i++) { |
||||
// Stop when seeing an opcode that does not guarantee that its operands will be evaluated.
|
||||
// Inlining in that case might result in the inlined expresion not being evaluted.
|
||||
if (i == 1 && (expr.Code == ILCode.LogicAnd || expr.Code == ILCode.LogicOr || expr.Code == ILCode.TernaryOp || expr.Code == ILCode.NullCoalescing)) |
||||
return false; |
||||
|
||||
ILExpression arg = expr.Arguments[i]; |
||||
|
||||
if ((arg.Code == ILCode.Ldloc || arg.Code == ILCode.Ldloca) && arg.Operand == v) { |
||||
parent = expr; |
||||
pos = i; |
||||
return true; |
||||
} |
||||
bool? r = FindLoadInNext(arg, v, expressionBeingMoved, out parent, out pos); |
||||
if (r != null) |
||||
return r; |
||||
} |
||||
if (IsSafeForInlineOver(expr, expressionBeingMoved)) |
||||
return null; // continue searching
|
||||
else |
||||
return false; // abort, inlining not possible
|
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Determines whether it is save to move 'expressionBeingMoved' past 'expr'
|
||||
/// </summary>
|
||||
bool IsSafeForInlineOver(ILExpression expr, ILExpression expressionBeingMoved) |
||||
{ |
||||
switch (expr.Code) { |
||||
case ILCode.Ldloc: |
||||
ILVariable loadedVar = (ILVariable)expr.Operand; |
||||
if (numLdloca.GetOrDefault(loadedVar) != 0) { |
||||
// abort, inlining is not possible
|
||||
return false; |
||||
} |
||||
foreach (ILExpression potentialStore in expressionBeingMoved.GetSelfAndChildrenRecursive<ILExpression>()) { |
||||
if (potentialStore.Code == ILCode.Stloc && potentialStore.Operand == loadedVar) |
||||
return false; |
||||
} |
||||
// the expression is loading a non-forbidden variable
|
||||
return true; |
||||
case ILCode.Ldloca: |
||||
case ILCode.Ldflda: |
||||
case ILCode.Ldsflda: |
||||
case ILCode.Ldelema: |
||||
// address-loading instructions are safe if their arguments are safe
|
||||
foreach (ILExpression arg in expr.Arguments) { |
||||
if (!IsSafeForInlineOver(arg, expressionBeingMoved)) |
||||
return false; |
||||
} |
||||
return true; |
||||
default: |
||||
// abort, inlining is not possible
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Runs a very simple form of copy propagation.
|
||||
/// Copy propagation is used in two cases:
|
||||
/// 1) assignments from arguments to local variables
|
||||
/// If the target variable is assigned to only once (so always is that argument) and the argument is never changed (no ldarga/starg),
|
||||
/// then we can replace the variable with the argument.
|
||||
/// 2) assignments of address-loading instructions to local variables
|
||||
/// </summary>
|
||||
public void CopyPropagation() |
||||
{ |
||||
foreach (ILBlock block in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int i = 0; i < block.Body.Count; i++) { |
||||
ILVariable v; |
||||
ILExpression copiedExpr; |
||||
if (block.Body[i].Match(ILCode.Stloc, out v, out copiedExpr) |
||||
&& !v.IsParameter && numStloc.GetOrDefault(v) == 1 && numLdloca.GetOrDefault(v) == 0 |
||||
&& CanPerformCopyPropagation(copiedExpr, v)) |
||||
{ |
||||
// un-inline the arguments of the ldArg instruction
|
||||
ILVariable[] uninlinedArgs = new ILVariable[copiedExpr.Arguments.Count]; |
||||
for (int j = 0; j < uninlinedArgs.Length; j++) { |
||||
uninlinedArgs[j] = new ILVariable { IsGenerated = true, Name = v.Name + "_cp_" + j }; |
||||
block.Body.Insert(i++, new ILExpression(ILCode.Stloc, uninlinedArgs[j], copiedExpr.Arguments[j])); |
||||
} |
||||
|
||||
// perform copy propagation:
|
||||
foreach (var expr in method.GetSelfAndChildrenRecursive<ILExpression>()) { |
||||
if (expr.Code == ILCode.Ldloc && expr.Operand == v) { |
||||
expr.Code = copiedExpr.Code; |
||||
expr.Operand = copiedExpr.Operand; |
||||
for (int j = 0; j < uninlinedArgs.Length; j++) { |
||||
expr.Arguments.Add(new ILExpression(ILCode.Ldloc, uninlinedArgs[j])); |
||||
} |
||||
} |
||||
} |
||||
|
||||
block.Body.RemoveAt(i); |
||||
if (uninlinedArgs.Length > 0) { |
||||
// if we un-inlined stuff; we need to update the usage counters
|
||||
AnalyzeMethod(); |
||||
} |
||||
InlineInto(block.Body, i, aggressive: false); // maybe inlining gets possible after the removal of block.Body[i]
|
||||
i -= uninlinedArgs.Length + 1; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
bool CanPerformCopyPropagation(ILExpression expr, ILVariable copyVariable) |
||||
{ |
||||
switch (expr.Code) { |
||||
case ILCode.Ldloca: |
||||
case ILCode.Ldelema: |
||||
case ILCode.Ldflda: |
||||
case ILCode.Ldsflda: |
||||
// All address-loading instructions always return the same value for a given operand/argument combination,
|
||||
// so they can be safely copied.
|
||||
return true; |
||||
case ILCode.Ldloc: |
||||
ILVariable v = (ILVariable)expr.Operand; |
||||
if (v.IsParameter) { |
||||
// Parameters can be copied only if they aren't assigned to (directly or indirectly via ldarga)
|
||||
return numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 0; |
||||
} else { |
||||
// Variables are be copied only if both they and the target copy variable are generated,
|
||||
// and if the variable has only a single assignment
|
||||
return v.IsGenerated && copyVariable.IsGenerated && numLdloca.GetOrDefault(v) == 0 && numStloc.GetOrDefault(v) == 1; |
||||
} |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,523 @@
@@ -0,0 +1,523 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
/// <summary>
|
||||
/// IL AST transformation that introduces array, object and collection initializers.
|
||||
/// </summary>
|
||||
partial class ILAstOptimizer |
||||
{ |
||||
#region Array Initializers
|
||||
bool TransformArrayInitializers(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
ILVariable v, v3; |
||||
ILExpression newarrExpr; |
||||
TypeReference elementType; |
||||
ILExpression lengthExpr; |
||||
int arrayLength; |
||||
if (expr.Match(ILCode.Stloc, out v, out newarrExpr) && |
||||
newarrExpr.Match(ILCode.Newarr, out elementType, out lengthExpr) && |
||||
lengthExpr.Match(ILCode.Ldc_I4, out arrayLength) && |
||||
arrayLength > 0) { |
||||
ILExpression[] newArr; |
||||
int initArrayPos; |
||||
if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, elementType, arrayLength, out newArr, out initArrayPos)) { |
||||
var arrayType = new ArrayType(elementType, 1); |
||||
arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength); |
||||
body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr)); |
||||
body.RemoveAt(initArrayPos); |
||||
} |
||||
// Put in a limit so that we don't consume too much memory if the code allocates a huge array
|
||||
// and populates it extremely sparsly. However, 255 "null" elements in a row actually occur in the Mono C# compiler!
|
||||
const int maxConsecutiveDefaultValueExpressions = 300; |
||||
List<ILExpression> operands = new List<ILExpression>(); |
||||
int numberOfInstructionsToRemove = 0; |
||||
for (int j = pos + 1; j < body.Count; j++) { |
||||
ILExpression nextExpr = body[j] as ILExpression; |
||||
int arrayPos; |
||||
if (nextExpr != null && |
||||
nextExpr.Code.IsStoreToArray() && |
||||
nextExpr.Arguments[0].Match(ILCode.Ldloc, out v3) && |
||||
v == v3 && |
||||
nextExpr.Arguments[1].Match(ILCode.Ldc_I4, out arrayPos) && |
||||
arrayPos >= operands.Count && |
||||
arrayPos <= operands.Count + maxConsecutiveDefaultValueExpressions) { |
||||
while (operands.Count < arrayPos) |
||||
operands.Add(new ILExpression(ILCode.DefaultValue, elementType)); |
||||
operands.Add(nextExpr.Arguments[2]); |
||||
numberOfInstructionsToRemove++; |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
if (operands.Count == arrayLength) { |
||||
var arrayType = new ArrayType(elementType, 1); |
||||
arrayType.Dimensions[0] = new ArrayDimension(0, arrayLength); |
||||
expr.Arguments[0] = new ILExpression(ILCode.InitArray, arrayType, operands); |
||||
body.RemoveRange(pos + 1, numberOfInstructionsToRemove); |
||||
|
||||
new ILInlining(method).InlineIfPossible(body, ref pos); |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool TransformMultidimensionalArrayInitializers(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
ILVariable v; |
||||
ILExpression newarrExpr; |
||||
MethodReference ctor; |
||||
List<ILExpression> ctorArgs; |
||||
ArrayType arrayType; |
||||
if (expr.Match(ILCode.Stloc, out v, out newarrExpr) && |
||||
newarrExpr.Match(ILCode.Newobj, out ctor, out ctorArgs) && |
||||
(arrayType = (ctor.DeclaringType as ArrayType)) != null && |
||||
arrayType.Rank == ctorArgs.Count) { |
||||
// Clone the type, so we can muck about with the Dimensions
|
||||
arrayType = new ArrayType(arrayType.ElementType, arrayType.Rank); |
||||
var arrayLengths = new int[arrayType.Rank]; |
||||
for (int i = 0; i < arrayType.Rank; i++) { |
||||
if (!ctorArgs[i].Match(ILCode.Ldc_I4, out arrayLengths[i])) return false; |
||||
if (arrayLengths[i] <= 0) return false; |
||||
arrayType.Dimensions[i] = new ArrayDimension(0, arrayLengths[i]); |
||||
} |
||||
|
||||
var totalElements = arrayLengths.Aggregate(1, (t, l) => t * l); |
||||
ILExpression[] newArr; |
||||
int initArrayPos; |
||||
if (ForwardScanInitializeArrayRuntimeHelper(body, pos + 1, v, arrayType, totalElements, out newArr, out initArrayPos)) { |
||||
var mdArr = Array.CreateInstance(typeof(ILExpression), arrayLengths); |
||||
body[pos] = new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.InitArray, arrayType, newArr)); |
||||
body.RemoveAt(initArrayPos); |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool ForwardScanInitializeArrayRuntimeHelper(List<ILNode> body, int pos, ILVariable array, TypeReference arrayType, int arrayLength, out ILExpression[] values, out int foundPos) |
||||
{ |
||||
ILVariable v2; |
||||
MethodReference methodRef; |
||||
ILExpression methodArg1; |
||||
ILExpression methodArg2; |
||||
FieldReference fieldRef; |
||||
if (body.ElementAtOrDefault(pos).Match(ILCode.Call, out methodRef, out methodArg1, out methodArg2) && |
||||
methodRef.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers" && |
||||
methodRef.Name == "InitializeArray" && |
||||
methodArg1.Match(ILCode.Ldloc, out v2) && |
||||
array == v2 && |
||||
methodArg2.Match(ILCode.Ldtoken, out fieldRef)) |
||||
{ |
||||
FieldDefinition fieldDef = fieldRef.ResolveWithinSameModule(); |
||||
if (fieldDef != null && fieldDef.InitialValue != null) { |
||||
ILExpression[] newArr = new ILExpression[arrayLength]; |
||||
if (DecodeArrayInitializer(TypeAnalysis.GetTypeCode(arrayType.GetElementType()), |
||||
fieldDef.InitialValue, newArr)) |
||||
{ |
||||
values = newArr; |
||||
foundPos = pos; |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
values = null; |
||||
foundPos = -1; |
||||
return false; |
||||
} |
||||
|
||||
static bool DecodeArrayInitializer(TypeCode elementType, byte[] initialValue, ILExpression[] output) |
||||
{ |
||||
switch (elementType) { |
||||
case TypeCode.Boolean: |
||||
case TypeCode.Byte: |
||||
if (initialValue.Length == output.Length) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_I4, (int)initialValue[j]); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.SByte: |
||||
if (initialValue.Length == output.Length) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_I4, (int)unchecked((sbyte)initialValue[j])); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.Int16: |
||||
if (initialValue.Length == output.Length * 2) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_I4, (int)BitConverter.ToInt16(initialValue, j * 2)); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.Char: |
||||
case TypeCode.UInt16: |
||||
if (initialValue.Length == output.Length * 2) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_I4, (int)BitConverter.ToUInt16(initialValue, j * 2)); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.Int32: |
||||
case TypeCode.UInt32: |
||||
if (initialValue.Length == output.Length * 4) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_I4, BitConverter.ToInt32(initialValue, j * 4)); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.Int64: |
||||
case TypeCode.UInt64: |
||||
if (initialValue.Length == output.Length * 8) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_I8, BitConverter.ToInt64(initialValue, j * 8)); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.Single: |
||||
if (initialValue.Length == output.Length * 4) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_R4, BitConverter.ToSingle(initialValue, j * 4)); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
case TypeCode.Double: |
||||
if (initialValue.Length == output.Length * 8) { |
||||
for (int j = 0; j < output.Length; j++) { |
||||
output[j] = new ILExpression(ILCode.Ldc_R8, BitConverter.ToDouble(initialValue, j * 8)); |
||||
} |
||||
return true; |
||||
} |
||||
return false; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Handles both object and collection initializers.
|
||||
/// </summary>
|
||||
bool TransformObjectInitializers(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
if (!context.Settings.ObjectOrCollectionInitializers) |
||||
return false; |
||||
|
||||
Debug.Assert(body[pos] == expr); // should be called for top-level expressions only
|
||||
ILVariable v; |
||||
ILExpression newObjExpr; |
||||
TypeReference newObjType; |
||||
bool isValueType; |
||||
MethodReference ctor; |
||||
List<ILExpression> ctorArgs; |
||||
if (expr.Match(ILCode.Stloc, out v, out newObjExpr)) { |
||||
if (newObjExpr.Match(ILCode.Newobj, out ctor, out ctorArgs)) { |
||||
// v = newObj(ctor, ctorArgs)
|
||||
newObjType = ctor.DeclaringType; |
||||
isValueType = false; |
||||
} else if (newObjExpr.Match(ILCode.DefaultValue, out newObjType)) { |
||||
// v = defaultvalue(type)
|
||||
isValueType = true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} else if (expr.Match(ILCode.Call, out ctor, out ctorArgs)) { |
||||
// call(SomeStruct::.ctor, ldloca(v), remainingArgs)
|
||||
if (ctorArgs.Count > 0 && ctorArgs[0].Match(ILCode.Ldloca, out v)) { |
||||
isValueType = true; |
||||
newObjType = ctor.DeclaringType; |
||||
ctorArgs = new List<ILExpression>(ctorArgs); |
||||
ctorArgs.RemoveAt(0); |
||||
newObjExpr = new ILExpression(ILCode.Newobj, ctor, ctorArgs); |
||||
} else { |
||||
return false; |
||||
} |
||||
} else { |
||||
return false; |
||||
} |
||||
if (newObjType.IsValueType != isValueType) |
||||
return false; |
||||
|
||||
int originalPos = pos; |
||||
|
||||
// don't use object initializer syntax for closures
|
||||
if (Ast.Transforms.DelegateConstruction.IsPotentialClosure(context, newObjType.ResolveWithinSameModule())) |
||||
return false; |
||||
|
||||
ILExpression initializer = ParseObjectInitializer(body, ref pos, v, newObjExpr, IsCollectionType(newObjType), isValueType); |
||||
|
||||
if (initializer.Arguments.Count == 1) // only newobj argument, no initializer elements
|
||||
return false; |
||||
int totalElementCount = pos - originalPos - 1; // totalElementCount: includes elements from nested collections
|
||||
Debug.Assert(totalElementCount >= initializer.Arguments.Count - 1); |
||||
|
||||
// Verify that we can inline 'v' into the next instruction:
|
||||
|
||||
if (pos >= body.Count) |
||||
return false; // reached end of block, but there should be another instruction which consumes the initialized object
|
||||
|
||||
ILInlining inlining = new ILInlining(method); |
||||
if (isValueType) { |
||||
// one ldloc for the use of the initialized object
|
||||
if (inlining.numLdloc.GetOrDefault(v) != 1) |
||||
return false; |
||||
// one ldloca for each initializer argument, and also for the ctor call (if it exists)
|
||||
if (inlining.numLdloca.GetOrDefault(v) != totalElementCount + (expr.Code == ILCode.Call ? 1 : 0)) |
||||
return false; |
||||
// one stloc for the initial store (if no ctor call was used)
|
||||
if (inlining.numStloc.GetOrDefault(v) != (expr.Code == ILCode.Call ? 0 : 1)) |
||||
return false; |
||||
} else { |
||||
// one ldloc for each initializer argument, and another ldloc for the use of the initialized object
|
||||
if (inlining.numLdloc.GetOrDefault(v) != totalElementCount + 1) |
||||
return false; |
||||
if (!(inlining.numStloc.GetOrDefault(v) == 1 && inlining.numLdloca.GetOrDefault(v) == 0)) |
||||
return false; |
||||
} |
||||
ILExpression nextExpr = body[pos] as ILExpression; |
||||
if (!inlining.CanInlineInto(nextExpr, v, initializer)) |
||||
return false; |
||||
|
||||
if (expr.Code == ILCode.Stloc) { |
||||
expr.Arguments[0] = initializer; |
||||
} else { |
||||
Debug.Assert(expr.Code == ILCode.Call); |
||||
expr.Code = ILCode.Stloc; |
||||
expr.Operand = v; |
||||
expr.Arguments.Clear(); |
||||
expr.Arguments.Add(initializer); |
||||
} |
||||
// remove all the instructions that were pulled into the initializer
|
||||
body.RemoveRange(originalPos + 1, pos - originalPos - 1); |
||||
|
||||
// now that we know that it's an object initializer, change all the first arguments to 'InitializedObject'
|
||||
ChangeFirstArgumentToInitializedObject(initializer); |
||||
|
||||
inlining = new ILInlining(method); |
||||
inlining.InlineIfPossible(body, ref originalPos); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether the type supports collection initializers.
|
||||
/// </summary>
|
||||
static bool IsCollectionType(TypeReference tr) |
||||
{ |
||||
if (tr == null) |
||||
return false; |
||||
TypeDefinition td = tr.Resolve(); |
||||
while (td != null) { |
||||
if (td.Interfaces.Any(intf => intf.Name == "IEnumerable" && intf.Namespace == "System.Collections")) |
||||
return true; |
||||
td = td.BaseType != null ? td.BaseType.Resolve() : null; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether 'expr' represents a setter in an object initializer.
|
||||
/// ('CallvirtSetter(Property, v, value)')
|
||||
/// </summary>
|
||||
static bool IsSetterInObjectInitializer(ILExpression expr) |
||||
{ |
||||
if (expr == null) |
||||
return false; |
||||
if (expr.Code == ILCode.CallvirtSetter || expr.Code == ILCode.CallSetter || expr.Code == ILCode.Stfld) { |
||||
return expr.Arguments.Count == 2; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets whether 'expr' represents the invocation of an 'Add' method in a collection initializer.
|
||||
/// </summary>
|
||||
static bool IsAddMethodCall(ILExpression expr) |
||||
{ |
||||
MethodReference addMethod; |
||||
List<ILExpression> args; |
||||
if (expr.Match(ILCode.Callvirt, out addMethod, out args)) { |
||||
if (addMethod.Name == "Add" && addMethod.HasThis) { |
||||
return args.Count >= 2; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Parses an object initializer.
|
||||
/// </summary>
|
||||
/// <param name="body">ILAst block</param>
|
||||
/// <param name="pos">
|
||||
/// Input: position of the instruction assigning to 'v'.
|
||||
/// Output: first position after the object initializer
|
||||
/// </param>
|
||||
/// <param name="v">The variable that holds the object being initialized</param>
|
||||
/// <param name="newObjExpr">The newobj instruction</param>
|
||||
/// <returns>InitObject instruction</returns>
|
||||
ILExpression ParseObjectInitializer(List<ILNode> body, ref int pos, ILVariable v, ILExpression newObjExpr, bool isCollection, bool isValueType) |
||||
{ |
||||
// Take care not to modify any existing ILExpressions in here.
|
||||
// We just construct new ones around the old ones, any modifications must wait until the whole
|
||||
// object/collection initializer was analyzed.
|
||||
ILExpression objectInitializer = new ILExpression(isCollection ? ILCode.InitCollection : ILCode.InitObject, null, newObjExpr); |
||||
List<ILExpression> initializerStack = new List<ILExpression>(); |
||||
initializerStack.Add(objectInitializer); |
||||
while (++pos < body.Count) { |
||||
ILExpression nextExpr = body[pos] as ILExpression; |
||||
if (IsSetterInObjectInitializer(nextExpr)) { |
||||
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, false, isValueType)) { |
||||
CleanupInitializerStackAfterFailedAdjustment(initializerStack); |
||||
break; |
||||
} |
||||
initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); |
||||
} else if (IsAddMethodCall(nextExpr)) { |
||||
if (!AdjustInitializerStack(initializerStack, nextExpr.Arguments[0], v, true, isValueType)) { |
||||
CleanupInitializerStackAfterFailedAdjustment(initializerStack); |
||||
break; |
||||
} |
||||
initializerStack[initializerStack.Count - 1].Arguments.Add(nextExpr); |
||||
} else { |
||||
// can't match any more initializers: end of object initializer
|
||||
break; |
||||
} |
||||
} |
||||
return objectInitializer; |
||||
} |
||||
|
||||
static bool AdjustInitializerStack(List<ILExpression> initializerStack, ILExpression argument, ILVariable v, bool isCollection, bool isValueType) |
||||
{ |
||||
// Argument is of the form 'getter(getter(...(v)))'
|
||||
// Unpack it into a list of getters:
|
||||
List<ILExpression> getters = new List<ILExpression>(); |
||||
while (argument.Code == ILCode.CallvirtGetter || argument.Code == ILCode.CallGetter || argument.Code == ILCode.Ldfld) { |
||||
getters.Add(argument); |
||||
if (argument.Arguments.Count != 1) |
||||
return false; |
||||
argument = argument.Arguments[0]; |
||||
} |
||||
// Ensure that the final argument is 'v'
|
||||
if (isValueType) { |
||||
ILVariable loadedVar; |
||||
if (!(argument.Match(ILCode.Ldloca, out loadedVar) && loadedVar == v)) |
||||
return false; |
||||
} else { |
||||
if (!argument.MatchLdloc(v)) |
||||
return false; |
||||
} |
||||
// Now compare the getters with those that are currently active on the initializer stack:
|
||||
int i; |
||||
for (i = 1; i <= Math.Min(getters.Count, initializerStack.Count - 1); i++) { |
||||
ILExpression g1 = initializerStack[i].Arguments[0]; // getter stored in initializer
|
||||
ILExpression g2 = getters[getters.Count - i]; // matching getter from argument
|
||||
if (g1.Operand != g2.Operand) { |
||||
// operands differ, so we abort the comparison
|
||||
break; |
||||
} |
||||
} |
||||
// Remove all initializers from the stack that were not matched with one from the argument:
|
||||
initializerStack.RemoveRange(i, initializerStack.Count - i); |
||||
// Now create new initializers for the remaining arguments:
|
||||
for (; i <= getters.Count; i++) { |
||||
ILExpression g = getters[getters.Count - i]; |
||||
MemberReference mr = (MemberReference)g.Operand; |
||||
TypeReference returnType; |
||||
if (mr is FieldReference) |
||||
returnType = TypeAnalysis.GetFieldType((FieldReference)mr); |
||||
else |
||||
returnType = TypeAnalysis.SubstituteTypeArgs(((MethodReference)mr).ReturnType, mr); |
||||
|
||||
ILExpression nestedInitializer = new ILExpression( |
||||
IsCollectionType(returnType) ? ILCode.InitCollection : ILCode.InitObject, |
||||
null, g); |
||||
// add new initializer to its parent:
|
||||
ILExpression parentInitializer = initializerStack[initializerStack.Count - 1]; |
||||
if (parentInitializer.Code == ILCode.InitCollection) { |
||||
// can't add children to collection initializer
|
||||
if (parentInitializer.Arguments.Count == 1) { |
||||
// convert empty collection initializer to object initializer
|
||||
parentInitializer.Code = ILCode.InitObject; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
parentInitializer.Arguments.Add(nestedInitializer); |
||||
initializerStack.Add(nestedInitializer); |
||||
} |
||||
ILExpression lastInitializer = initializerStack[initializerStack.Count - 1]; |
||||
if (isCollection) { |
||||
return lastInitializer.Code == ILCode.InitCollection; |
||||
} else { |
||||
if (lastInitializer.Code == ILCode.InitCollection) { |
||||
if (lastInitializer.Arguments.Count == 1) { |
||||
// convert empty collection initializer to object initializer
|
||||
lastInitializer.Code = ILCode.InitObject; |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} else { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
static void CleanupInitializerStackAfterFailedAdjustment(List<ILExpression> initializerStack) |
||||
{ |
||||
// There might be empty nested initializers left over; so we'll remove those:
|
||||
while (initializerStack.Count > 1 && initializerStack[initializerStack.Count - 1].Arguments.Count == 1) { |
||||
ILExpression parent = initializerStack[initializerStack.Count - 2]; |
||||
Debug.Assert(parent.Arguments.Last() == initializerStack[initializerStack.Count - 1]); |
||||
parent.Arguments.RemoveAt(parent.Arguments.Count - 1); |
||||
initializerStack.RemoveAt(initializerStack.Count - 1); |
||||
} |
||||
} |
||||
|
||||
static void ChangeFirstArgumentToInitializedObject(ILExpression initializer) |
||||
{ |
||||
// Go through all elements in the initializer (so skip the newobj-instr. at the start)
|
||||
for (int i = 1; i < initializer.Arguments.Count; i++) { |
||||
ILExpression element = initializer.Arguments[i]; |
||||
if (element.Code == ILCode.InitCollection || element.Code == ILCode.InitObject) { |
||||
// nested collection/object initializer
|
||||
ILExpression getCollection = element.Arguments[0]; |
||||
getCollection.Arguments[0] = new ILExpression(ILCode.InitializedObject, null); |
||||
ChangeFirstArgumentToInitializedObject(element); // handle the collection elements
|
||||
} else { |
||||
element.Arguments[0] = new ILExpression(ILCode.InitializedObject, null); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,449 @@
@@ -0,0 +1,449 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.Decompiler.FlowAnalysis; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
/// <summary>
|
||||
/// Description of LoopsAndConditions.
|
||||
/// </summary>
|
||||
public class LoopsAndConditions |
||||
{ |
||||
Dictionary<ILLabel, ControlFlowNode> labelToCfNode = new Dictionary<ILLabel, ControlFlowNode>(); |
||||
|
||||
readonly DecompilerContext context; |
||||
|
||||
uint nextLabelIndex = 0; |
||||
|
||||
public LoopsAndConditions(DecompilerContext context) |
||||
{ |
||||
this.context = context; |
||||
} |
||||
|
||||
public void FindLoops(ILBlock block) |
||||
{ |
||||
if (block.Body.Count > 0) { |
||||
ControlFlowGraph graph; |
||||
graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); |
||||
graph.ComputeDominance(context.CancellationToken); |
||||
graph.ComputeDominanceFrontier(); |
||||
block.Body = FindLoops(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint, false); |
||||
} |
||||
} |
||||
|
||||
public void FindConditions(ILBlock block) |
||||
{ |
||||
if (block.Body.Count > 0) { |
||||
ControlFlowGraph graph; |
||||
graph = BuildGraph(block.Body, (ILLabel)block.EntryGoto.Operand); |
||||
graph.ComputeDominance(context.CancellationToken); |
||||
graph.ComputeDominanceFrontier(); |
||||
block.Body = FindConditions(new HashSet<ControlFlowNode>(graph.Nodes.Skip(3)), graph.EntryPoint); |
||||
} |
||||
} |
||||
|
||||
ControlFlowGraph BuildGraph(List<ILNode> nodes, ILLabel entryLabel) |
||||
{ |
||||
int index = 0; |
||||
List<ControlFlowNode> cfNodes = new List<ControlFlowNode>(); |
||||
ControlFlowNode entryPoint = new ControlFlowNode(index++, 0, ControlFlowNodeType.EntryPoint); |
||||
cfNodes.Add(entryPoint); |
||||
ControlFlowNode regularExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.RegularExit); |
||||
cfNodes.Add(regularExit); |
||||
ControlFlowNode exceptionalExit = new ControlFlowNode(index++, -1, ControlFlowNodeType.ExceptionalExit); |
||||
cfNodes.Add(exceptionalExit); |
||||
|
||||
// Create graph nodes
|
||||
labelToCfNode = new Dictionary<ILLabel, ControlFlowNode>(); |
||||
Dictionary<ILNode, ControlFlowNode> astNodeToCfNode = new Dictionary<ILNode, ControlFlowNode>(); |
||||
foreach(ILBasicBlock node in nodes) { |
||||
ControlFlowNode cfNode = new ControlFlowNode(index++, -1, ControlFlowNodeType.Normal); |
||||
cfNodes.Add(cfNode); |
||||
astNodeToCfNode[node] = cfNode; |
||||
cfNode.UserData = node; |
||||
|
||||
// Find all contained labels
|
||||
foreach(ILLabel label in node.GetSelfAndChildrenRecursive<ILLabel>()) { |
||||
labelToCfNode[label] = cfNode; |
||||
} |
||||
} |
||||
|
||||
// Entry endge
|
||||
ControlFlowNode entryNode = labelToCfNode[entryLabel]; |
||||
ControlFlowEdge entryEdge = new ControlFlowEdge(entryPoint, entryNode, JumpType.Normal); |
||||
entryPoint.Outgoing.Add(entryEdge); |
||||
entryNode.Incoming.Add(entryEdge); |
||||
|
||||
// Create edges
|
||||
foreach(ILBasicBlock node in nodes) { |
||||
ControlFlowNode source = astNodeToCfNode[node]; |
||||
|
||||
// Find all branches
|
||||
foreach(ILLabel target in node.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { |
||||
ControlFlowNode destination; |
||||
// Labels which are out of out scope will not be int the collection
|
||||
// Insert self edge only if we are sure we are a loop
|
||||
if (labelToCfNode.TryGetValue(target, out destination) && (destination != source || target == node.Body.FirstOrDefault())) { |
||||
ControlFlowEdge edge = new ControlFlowEdge(source, destination, JumpType.Normal); |
||||
source.Outgoing.Add(edge); |
||||
destination.Incoming.Add(edge); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return new ControlFlowGraph(cfNodes.ToArray()); |
||||
} |
||||
|
||||
List<ILNode> FindLoops(HashSet<ControlFlowNode> scope, ControlFlowNode entryPoint, bool excludeEntryPoint) |
||||
{ |
||||
List<ILNode> result = new List<ILNode>(); |
||||
|
||||
// Do not modify entry data
|
||||
scope = new HashSet<ControlFlowNode>(scope); |
||||
|
||||
Queue<ControlFlowNode> agenda = new Queue<ControlFlowNode>(); |
||||
agenda.Enqueue(entryPoint); |
||||
while(agenda.Count > 0) { |
||||
ControlFlowNode node = agenda.Dequeue(); |
||||
|
||||
// If the node is a loop header
|
||||
if (scope.Contains(node) |
||||
&& node.DominanceFrontier.Contains(node) |
||||
&& (node != entryPoint || !excludeEntryPoint)) |
||||
{ |
||||
HashSet<ControlFlowNode> loopContents = FindLoopContent(scope, node); |
||||
|
||||
// If the first expression is a loop condition
|
||||
ILBasicBlock basicBlock = (ILBasicBlock)node.UserData; |
||||
ILExpression condExpr; |
||||
ILLabel trueLabel; |
||||
ILLabel falseLabel; |
||||
// It has to be just brtrue - any preceding code would introduce goto
|
||||
if(basicBlock.MatchSingleAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) |
||||
{ |
||||
ControlFlowNode trueTarget; |
||||
labelToCfNode.TryGetValue(trueLabel, out trueTarget); |
||||
ControlFlowNode falseTarget; |
||||
labelToCfNode.TryGetValue(falseLabel, out falseTarget); |
||||
|
||||
// If one point inside the loop and the other outside
|
||||
if ((!loopContents.Contains(trueTarget) && loopContents.Contains(falseTarget)) || |
||||
(loopContents.Contains(trueTarget) && !loopContents.Contains(falseTarget)) ) |
||||
{ |
||||
loopContents.RemoveOrThrow(node); |
||||
scope.RemoveOrThrow(node); |
||||
|
||||
// If false means enter the loop
|
||||
if (loopContents.Contains(falseTarget) || falseTarget == node) |
||||
{ |
||||
// Negate the condition
|
||||
condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); |
||||
ILLabel tmp = trueLabel; |
||||
trueLabel = falseLabel; |
||||
falseLabel = tmp; |
||||
} |
||||
|
||||
ControlFlowNode postLoopTarget; |
||||
labelToCfNode.TryGetValue(falseLabel, out postLoopTarget); |
||||
if (postLoopTarget != null) { |
||||
// Pull more nodes into the loop
|
||||
HashSet<ControlFlowNode> postLoopContents = FindDominatedNodes(scope, postLoopTarget); |
||||
var pullIn = scope.Except(postLoopContents).Where(n => node.Dominates(n)); |
||||
loopContents.UnionWith(pullIn); |
||||
} |
||||
|
||||
// Use loop to implement the brtrue
|
||||
basicBlock.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); |
||||
basicBlock.Body.Add(new ILWhileLoop() { |
||||
Condition = condExpr, |
||||
BodyBlock = new ILBlock() { |
||||
EntryGoto = new ILExpression(ILCode.Br, trueLabel), |
||||
Body = FindLoops(loopContents, node, false) |
||||
} |
||||
}); |
||||
basicBlock.Body.Add(new ILExpression(ILCode.Br, falseLabel)); |
||||
result.Add(basicBlock); |
||||
|
||||
scope.ExceptWith(loopContents); |
||||
} |
||||
} |
||||
|
||||
// Fallback method: while(true)
|
||||
if (scope.Contains(node)) { |
||||
result.Add(new ILBasicBlock() { |
||||
Body = new List<ILNode>() { |
||||
new ILLabel() { Name = "Loop_" + (nextLabelIndex++) }, |
||||
new ILWhileLoop() { |
||||
BodyBlock = new ILBlock() { |
||||
EntryGoto = new ILExpression(ILCode.Br, (ILLabel)basicBlock.Body.First()), |
||||
Body = FindLoops(loopContents, node, true) |
||||
} |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
scope.ExceptWith(loopContents); |
||||
} |
||||
} |
||||
|
||||
// Using the dominator tree should ensure we find the the widest loop first
|
||||
foreach(var child in node.DominatorTreeChildren) { |
||||
agenda.Enqueue(child); |
||||
} |
||||
} |
||||
|
||||
// Add whatever is left
|
||||
foreach(var node in scope) { |
||||
result.Add((ILNode)node.UserData); |
||||
} |
||||
scope.Clear(); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
List<ILNode> FindConditions(HashSet<ControlFlowNode> scope, ControlFlowNode entryNode) |
||||
{ |
||||
List<ILNode> result = new List<ILNode>(); |
||||
|
||||
// Do not modify entry data
|
||||
scope = new HashSet<ControlFlowNode>(scope); |
||||
|
||||
HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(); |
||||
agenda.Add(entryNode); |
||||
while(agenda.Any()) { |
||||
ControlFlowNode node = agenda.First(); |
||||
// Attempt for a good order
|
||||
while(agenda.Contains(node.ImmediateDominator)) { |
||||
node = node.ImmediateDominator; |
||||
} |
||||
agenda.Remove(node); |
||||
|
||||
// Find a block that represents a simple condition
|
||||
if (scope.Contains(node)) { |
||||
|
||||
ILBasicBlock block = (ILBasicBlock)node.UserData; |
||||
|
||||
{ |
||||
// Switch
|
||||
ILLabel[] caseLabels; |
||||
ILExpression switchArg; |
||||
ILLabel fallLabel; |
||||
if (block.MatchLastAndBr(ILCode.Switch, out caseLabels, out switchArg, out fallLabel)) { |
||||
|
||||
// Replace the switch code with ILSwitch
|
||||
ILSwitch ilSwitch = new ILSwitch() { Condition = switchArg }; |
||||
block.Body.RemoveTail(ILCode.Switch, ILCode.Br); |
||||
block.Body.Add(ilSwitch); |
||||
block.Body.Add(new ILExpression(ILCode.Br, fallLabel)); |
||||
result.Add(block); |
||||
|
||||
// Remove the item so that it is not picked up as content
|
||||
scope.RemoveOrThrow(node); |
||||
|
||||
// Find the switch offset
|
||||
int addValue = 0; |
||||
List<ILExpression> subArgs; |
||||
if (ilSwitch.Condition.Match(ILCode.Sub, out subArgs) && subArgs[1].Match(ILCode.Ldc_I4, out addValue)) { |
||||
ilSwitch.Condition = subArgs[0]; |
||||
} |
||||
|
||||
// Pull in code of cases
|
||||
ControlFlowNode fallTarget = null; |
||||
labelToCfNode.TryGetValue(fallLabel, out fallTarget); |
||||
|
||||
HashSet<ControlFlowNode> frontiers = new HashSet<ControlFlowNode>(); |
||||
if (fallTarget != null) |
||||
frontiers.UnionWith(fallTarget.DominanceFrontier.Except(new [] { fallTarget })); |
||||
|
||||
foreach(ILLabel condLabel in caseLabels) { |
||||
ControlFlowNode condTarget = null; |
||||
labelToCfNode.TryGetValue(condLabel, out condTarget); |
||||
if (condTarget != null) |
||||
frontiers.UnionWith(condTarget.DominanceFrontier.Except(new [] { condTarget })); |
||||
} |
||||
|
||||
for (int i = 0; i < caseLabels.Length; i++) { |
||||
ILLabel condLabel = caseLabels[i]; |
||||
|
||||
// Find or create new case block
|
||||
ILSwitch.CaseBlock caseBlock = ilSwitch.CaseBlocks.FirstOrDefault(b => b.EntryGoto.Operand == condLabel); |
||||
if (caseBlock == null) { |
||||
caseBlock = new ILSwitch.CaseBlock() { |
||||
Values = new List<int>(), |
||||
EntryGoto = new ILExpression(ILCode.Br, condLabel) |
||||
}; |
||||
ilSwitch.CaseBlocks.Add(caseBlock); |
||||
|
||||
ControlFlowNode condTarget = null; |
||||
labelToCfNode.TryGetValue(condLabel, out condTarget); |
||||
if (condTarget != null && !frontiers.Contains(condTarget)) { |
||||
HashSet<ControlFlowNode> content = FindDominatedNodes(scope, condTarget); |
||||
scope.ExceptWith(content); |
||||
caseBlock.Body.AddRange(FindConditions(content, condTarget)); |
||||
// Add explicit break which should not be used by default, but the goto removal might decide to use it
|
||||
caseBlock.Body.Add(new ILBasicBlock() { |
||||
Body = { |
||||
new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, |
||||
new ILExpression(ILCode.LoopOrSwitchBreak, null) |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
caseBlock.Values.Add(i + addValue); |
||||
} |
||||
|
||||
// Heuristis to determine if we want to use fallthough as default case
|
||||
if (fallTarget != null && !frontiers.Contains(fallTarget)) { |
||||
HashSet<ControlFlowNode> content = FindDominatedNodes(scope, fallTarget); |
||||
if (content.Any()) { |
||||
var caseBlock = new ILSwitch.CaseBlock() { EntryGoto = new ILExpression(ILCode.Br, fallLabel) }; |
||||
ilSwitch.CaseBlocks.Add(caseBlock); |
||||
block.Body.RemoveTail(ILCode.Br); |
||||
|
||||
scope.ExceptWith(content); |
||||
caseBlock.Body.AddRange(FindConditions(content, fallTarget)); |
||||
// Add explicit break which should not be used by default, but the goto removal might decide to use it
|
||||
caseBlock.Body.Add(new ILBasicBlock() { |
||||
Body = { |
||||
new ILLabel() { Name = "SwitchBreak_" + (nextLabelIndex++) }, |
||||
new ILExpression(ILCode.LoopOrSwitchBreak, null) |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Two-way branch
|
||||
ILExpression condExpr; |
||||
ILLabel trueLabel; |
||||
ILLabel falseLabel; |
||||
if(block.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { |
||||
|
||||
// Swap bodies since that seems to be the usual C# order
|
||||
ILLabel temp = trueLabel; |
||||
trueLabel = falseLabel; |
||||
falseLabel = temp; |
||||
condExpr = new ILExpression(ILCode.LogicNot, null, condExpr); |
||||
|
||||
// Convert the brtrue to ILCondition
|
||||
ILCondition ilCond = new ILCondition() { |
||||
Condition = condExpr, |
||||
TrueBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, trueLabel) }, |
||||
FalseBlock = new ILBlock() { EntryGoto = new ILExpression(ILCode.Br, falseLabel) } |
||||
}; |
||||
block.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); |
||||
block.Body.Add(ilCond); |
||||
result.Add(block); |
||||
|
||||
// Remove the item immediately so that it is not picked up as content
|
||||
scope.RemoveOrThrow(node); |
||||
|
||||
ControlFlowNode trueTarget = null; |
||||
labelToCfNode.TryGetValue(trueLabel, out trueTarget); |
||||
ControlFlowNode falseTarget = null; |
||||
labelToCfNode.TryGetValue(falseLabel, out falseTarget); |
||||
|
||||
// Pull in the conditional code
|
||||
HashSet<ControlFlowNode> frontiers = new HashSet<ControlFlowNode>(); |
||||
if (trueTarget != null) |
||||
frontiers.UnionWith(trueTarget.DominanceFrontier.Except(new [] { trueTarget })); |
||||
if (falseTarget != null) |
||||
frontiers.UnionWith(falseTarget.DominanceFrontier.Except(new [] { falseTarget })); |
||||
|
||||
if (trueTarget != null && !frontiers.Contains(trueTarget)) { |
||||
HashSet<ControlFlowNode> content = FindDominatedNodes(scope, trueTarget); |
||||
scope.ExceptWith(content); |
||||
ilCond.TrueBlock.Body.AddRange(FindConditions(content, trueTarget)); |
||||
} |
||||
if (falseTarget != null && !frontiers.Contains(falseTarget)) { |
||||
HashSet<ControlFlowNode> content = FindDominatedNodes(scope, falseTarget); |
||||
scope.ExceptWith(content); |
||||
ilCond.FalseBlock.Body.AddRange(FindConditions(content, falseTarget)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Add the node now so that we have good ordering
|
||||
if (scope.Contains(node)) { |
||||
result.Add((ILNode)node.UserData); |
||||
scope.Remove(node); |
||||
} |
||||
} |
||||
|
||||
// Using the dominator tree should ensure we find the the widest loop first
|
||||
foreach(var child in node.DominatorTreeChildren) { |
||||
agenda.Add(child); |
||||
} |
||||
} |
||||
|
||||
// Add whatever is left
|
||||
foreach(var node in scope) { |
||||
result.Add((ILNode)node.UserData); |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static HashSet<ControlFlowNode> FindDominatedNodes(HashSet<ControlFlowNode> scope, ControlFlowNode head) |
||||
{ |
||||
HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(); |
||||
HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>(); |
||||
agenda.Add(head); |
||||
|
||||
while(agenda.Count > 0) { |
||||
ControlFlowNode addNode = agenda.First(); |
||||
agenda.Remove(addNode); |
||||
|
||||
if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { |
||||
foreach (var successor in addNode.Successors) { |
||||
agenda.Add(successor); |
||||
} |
||||
} |
||||
} |
||||
|
||||
return result; |
||||
} |
||||
|
||||
static HashSet<ControlFlowNode> FindLoopContent(HashSet<ControlFlowNode> scope, ControlFlowNode head) |
||||
{ |
||||
var viaBackEdges = head.Predecessors.Where(p => head.Dominates(p)); |
||||
HashSet<ControlFlowNode> agenda = new HashSet<ControlFlowNode>(viaBackEdges); |
||||
HashSet<ControlFlowNode> result = new HashSet<ControlFlowNode>(); |
||||
|
||||
while(agenda.Count > 0) { |
||||
ControlFlowNode addNode = agenda.First(); |
||||
agenda.Remove(addNode); |
||||
|
||||
if (scope.Contains(addNode) && head.Dominates(addNode) && result.Add(addNode)) { |
||||
foreach (var predecessor in addNode.Predecessors) { |
||||
agenda.Add(predecessor); |
||||
} |
||||
} |
||||
} |
||||
if (scope.Contains(head)) |
||||
result.Add(head); |
||||
|
||||
return result; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,159 @@
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public static class PatternMatching |
||||
{ |
||||
public static bool Match(this ILNode node, ILCode code) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
return expr != null && expr.Prefixes == null && expr.Code == code; |
||||
} |
||||
|
||||
public static bool Match<T>(this ILNode node, ILCode code, out T operand) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
if (expr != null && expr.Prefixes == null && expr.Code == code && expr.Arguments.Count == 0) { |
||||
operand = (T)expr.Operand; |
||||
return true; |
||||
} |
||||
operand = default(T); |
||||
return false; |
||||
} |
||||
|
||||
public static bool Match(this ILNode node, ILCode code, out List<ILExpression> args) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
if (expr != null && expr.Prefixes == null && expr.Code == code) { |
||||
Debug.Assert(expr.Operand == null); |
||||
args = expr.Arguments; |
||||
return true; |
||||
} |
||||
args = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool Match(this ILNode node, ILCode code, out ILExpression arg) |
||||
{ |
||||
List<ILExpression> args; |
||||
if (node.Match(code, out args) && args.Count == 1) { |
||||
arg = args[0]; |
||||
return true; |
||||
} |
||||
arg = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool Match<T>(this ILNode node, ILCode code, out T operand, out List<ILExpression> args) |
||||
{ |
||||
ILExpression expr = node as ILExpression; |
||||
if (expr != null && expr.Prefixes == null && expr.Code == code) { |
||||
operand = (T)expr.Operand; |
||||
args = expr.Arguments; |
||||
return true; |
||||
} |
||||
operand = default(T); |
||||
args = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg) |
||||
{ |
||||
List<ILExpression> args; |
||||
if (node.Match(code, out operand, out args) && args.Count == 1) { |
||||
arg = args[0]; |
||||
return true; |
||||
} |
||||
arg = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool Match<T>(this ILNode node, ILCode code, out T operand, out ILExpression arg1, out ILExpression arg2) |
||||
{ |
||||
List<ILExpression> args; |
||||
if (node.Match(code, out operand, out args) && args.Count == 2) { |
||||
arg1 = args[0]; |
||||
arg2 = args[1]; |
||||
return true; |
||||
} |
||||
arg1 = null; |
||||
arg2 = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool MatchSingle<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg) |
||||
{ |
||||
if (bb.Body.Count == 2 && |
||||
bb.Body[0] is ILLabel && |
||||
bb.Body[1].Match(code, out operand, out arg)) |
||||
{ |
||||
return true; |
||||
} |
||||
operand = default(T); |
||||
arg = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool MatchSingleAndBr<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel brLabel) |
||||
{ |
||||
if (bb.Body.Count == 3 && |
||||
bb.Body[0] is ILLabel && |
||||
bb.Body[1].Match(code, out operand, out arg) && |
||||
bb.Body[2].Match(ILCode.Br, out brLabel)) |
||||
{ |
||||
return true; |
||||
} |
||||
operand = default(T); |
||||
arg = null; |
||||
brLabel = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool MatchLastAndBr<T>(this ILBasicBlock bb, ILCode code, out T operand, out ILExpression arg, out ILLabel brLabel) |
||||
{ |
||||
if (bb.Body.ElementAtOrDefault(bb.Body.Count - 2).Match(code, out operand, out arg) && |
||||
bb.Body.LastOrDefault().Match(ILCode.Br, out brLabel)) |
||||
{ |
||||
return true; |
||||
} |
||||
operand = default(T); |
||||
arg = null; |
||||
brLabel = null; |
||||
return false; |
||||
} |
||||
|
||||
public static bool MatchThis(this ILNode node) |
||||
{ |
||||
ILVariable v; |
||||
return node.Match(ILCode.Ldloc, out v) && v.IsParameter && v.OriginalParameter.Index == -1; |
||||
} |
||||
|
||||
public static bool MatchLdloc(this ILNode node, ILVariable expectedVar) |
||||
{ |
||||
ILVariable v; |
||||
return node.Match(ILCode.Ldloc, out v) && v == expectedVar; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,907 @@
@@ -0,0 +1,907 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
using ICSharpCode.NRefactory.Utils; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public partial class ILAstOptimizer |
||||
{ |
||||
#region TransformDecimalCtorToConstant
|
||||
static bool TransformDecimalCtorToConstant(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
MethodReference r; |
||||
List<ILExpression> args; |
||||
if (expr.Match(ILCode.Newobj, out r, out args) && |
||||
r.DeclaringType.Namespace == "System" && |
||||
r.DeclaringType.Name == "Decimal") |
||||
{ |
||||
if (args.Count == 1) { |
||||
int val; |
||||
if (args[0].Match(ILCode.Ldc_I4, out val)) { |
||||
expr.Code = ILCode.Ldc_Decimal; |
||||
expr.Operand = new decimal(val); |
||||
expr.InferredType = r.DeclaringType; |
||||
expr.Arguments.Clear(); |
||||
return true; |
||||
} |
||||
} else if (args.Count == 5) { |
||||
int lo, mid, hi, isNegative, scale; |
||||
if (expr.Arguments[0].Match(ILCode.Ldc_I4, out lo) && |
||||
expr.Arguments[1].Match(ILCode.Ldc_I4, out mid) && |
||||
expr.Arguments[2].Match(ILCode.Ldc_I4, out hi) && |
||||
expr.Arguments[3].Match(ILCode.Ldc_I4, out isNegative) && |
||||
expr.Arguments[4].Match(ILCode.Ldc_I4, out scale)) |
||||
{ |
||||
expr.Code = ILCode.Ldc_Decimal; |
||||
expr.Operand = new decimal(lo, mid, hi, isNegative != 0, (byte)scale); |
||||
expr.InferredType = r.DeclaringType; |
||||
expr.Arguments.Clear(); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
bool modified = false; |
||||
foreach(ILExpression arg in expr.Arguments) { |
||||
modified |= TransformDecimalCtorToConstant(null, arg, -1); |
||||
} |
||||
return modified; |
||||
} |
||||
#endregion
|
||||
|
||||
#region SimplifyLdObjAndStObj
|
||||
static bool SimplifyLdObjAndStObj(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
bool modified = false; |
||||
expr = SimplifyLdObjAndStObj(expr, ref modified); |
||||
if (modified && body != null) |
||||
body[pos] = expr; |
||||
for (int i = 0; i < expr.Arguments.Count; i++) { |
||||
expr.Arguments[i] = SimplifyLdObjAndStObj(expr.Arguments[i], ref modified); |
||||
modified |= SimplifyLdObjAndStObj(null, expr.Arguments[i], -1); |
||||
} |
||||
return modified; |
||||
} |
||||
|
||||
static ILExpression SimplifyLdObjAndStObj(ILExpression expr, ref bool modified) |
||||
{ |
||||
if (expr.Code == ILCode.Initobj) { |
||||
expr.Code = ILCode.Stobj; |
||||
expr.Arguments.Add(new ILExpression(ILCode.DefaultValue, expr.Operand)); |
||||
modified = true; |
||||
} else if (expr.Code == ILCode.Cpobj) { |
||||
expr.Code = ILCode.Stobj; |
||||
expr.Arguments[1] = new ILExpression(ILCode.Ldobj, expr.Operand, expr.Arguments[1]); |
||||
modified = true; |
||||
} |
||||
ILExpression arg, arg2; |
||||
TypeReference type; |
||||
ILCode? newCode = null; |
||||
if (expr.Match(ILCode.Stobj, out type, out arg, out arg2)) { |
||||
switch (arg.Code) { |
||||
case ILCode.Ldelema: newCode = ILCode.Stelem_Any; break; |
||||
case ILCode.Ldloca: newCode = ILCode.Stloc; break; |
||||
case ILCode.Ldflda: newCode = ILCode.Stfld; break; |
||||
case ILCode.Ldsflda: newCode = ILCode.Stsfld; break; |
||||
} |
||||
} else if (expr.Match(ILCode.Ldobj, out type, out arg)) { |
||||
switch (arg.Code) { |
||||
case ILCode.Ldelema: newCode = ILCode.Ldelem_Any; break; |
||||
case ILCode.Ldloca: newCode = ILCode.Ldloc; break; |
||||
case ILCode.Ldflda: newCode = ILCode.Ldfld; break; |
||||
case ILCode.Ldsflda: newCode = ILCode.Ldsfld; break; |
||||
} |
||||
} |
||||
if (newCode != null) { |
||||
arg.Code = newCode.Value; |
||||
if (expr.Code == ILCode.Stobj) { |
||||
arg.InferredType = expr.InferredType; |
||||
arg.ExpectedType = expr.ExpectedType; |
||||
arg.Arguments.Add(arg2); |
||||
} |
||||
arg.ILRanges.AddRange(expr.ILRanges); |
||||
modified = true; |
||||
return arg; |
||||
} else { |
||||
return expr; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region SimplifyLdcI4ConvI8
|
||||
static bool SimplifyLdcI4ConvI8(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
ILExpression ldc; |
||||
int val; |
||||
if (expr.Match(ILCode.Conv_I8, out ldc) && ldc.Match(ILCode.Ldc_I4, out val)) { |
||||
expr.Code = ILCode.Ldc_I8; |
||||
expr.Operand = (long)val; |
||||
expr.Arguments.Clear(); |
||||
return true; |
||||
} |
||||
bool modified = false; |
||||
foreach(ILExpression arg in expr.Arguments) { |
||||
modified |= SimplifyLdcI4ConvI8(null, arg, -1); |
||||
} |
||||
return modified; |
||||
} |
||||
#endregion
|
||||
|
||||
#region CachedDelegateInitialization
|
||||
void CachedDelegateInitializationWithField(ILBlock block, ref int i) |
||||
{ |
||||
// if (logicnot(ldsfld(field))) {
|
||||
// stsfld(field, newobj(Action::.ctor, ldnull(), ldftn(method)))
|
||||
// } else {
|
||||
// }
|
||||
// ...(..., ldsfld(field), ...)
|
||||
|
||||
ILCondition c = block.Body[i] as ILCondition; |
||||
if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null) |
||||
return; |
||||
if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0)) |
||||
return; |
||||
if (!c.Condition.Match(ILCode.LogicNot)) |
||||
return; |
||||
ILExpression condition = c.Condition.Arguments.Single() as ILExpression; |
||||
if (condition == null || condition.Code != ILCode.Ldsfld) |
||||
return; |
||||
FieldDefinition field = ((FieldReference)condition.Operand).ResolveWithinSameModule(); // field is defined in current assembly
|
||||
if (field == null || !field.IsCompilerGeneratedOrIsInCompilerGeneratedClass()) |
||||
return; |
||||
ILExpression stsfld = c.TrueBlock.Body[0] as ILExpression; |
||||
if (!(stsfld != null && stsfld.Code == ILCode.Stsfld && ((FieldReference)stsfld.Operand).ResolveWithinSameModule() == field)) |
||||
return; |
||||
ILExpression newObj = stsfld.Arguments[0]; |
||||
if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) |
||||
return; |
||||
if (newObj.Arguments[0].Code != ILCode.Ldnull) |
||||
return; |
||||
if (newObj.Arguments[1].Code != ILCode.Ldftn) |
||||
return; |
||||
MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly
|
||||
if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) |
||||
return; |
||||
|
||||
ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); |
||||
if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count( |
||||
e => e.Code == ILCode.Ldsfld && ((FieldReference)e.Operand).ResolveWithinSameModule() == field) == 1) |
||||
{ |
||||
foreach (ILExpression parent in followingNode.GetSelfAndChildrenRecursive<ILExpression>()) { |
||||
for (int j = 0; j < parent.Arguments.Count; j++) { |
||||
if (parent.Arguments[j].Code == ILCode.Ldsfld && ((FieldReference)parent.Arguments[j].Operand).ResolveWithinSameModule() == field) { |
||||
parent.Arguments[j] = newObj; |
||||
block.Body.RemoveAt(i); |
||||
i -= new ILInlining(method).InlineInto(block.Body, i, aggressive: false); |
||||
return; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
void CachedDelegateInitializationWithLocal(ILBlock block, ref int i) |
||||
{ |
||||
// if (logicnot(ldloc(v))) {
|
||||
// stloc(v, newobj(Action::.ctor, ldloc(displayClass), ldftn(method)))
|
||||
// } else {
|
||||
// }
|
||||
// ...(..., ldloc(v), ...)
|
||||
|
||||
ILCondition c = block.Body[i] as ILCondition; |
||||
if (c == null || c.Condition == null && c.TrueBlock == null || c.FalseBlock == null) |
||||
return; |
||||
if (!(c.TrueBlock.Body.Count == 1 && c.FalseBlock.Body.Count == 0)) |
||||
return; |
||||
if (!c.Condition.Match(ILCode.LogicNot)) |
||||
return; |
||||
ILExpression condition = c.Condition.Arguments.Single() as ILExpression; |
||||
if (condition == null || condition.Code != ILCode.Ldloc) |
||||
return; |
||||
ILVariable v = (ILVariable)condition.Operand; |
||||
ILExpression stloc = c.TrueBlock.Body[0] as ILExpression; |
||||
if (!(stloc != null && stloc.Code == ILCode.Stloc && (ILVariable)stloc.Operand == v)) |
||||
return; |
||||
ILExpression newObj = stloc.Arguments[0]; |
||||
if (!(newObj.Code == ILCode.Newobj && newObj.Arguments.Count == 2)) |
||||
return; |
||||
if (newObj.Arguments[0].Code != ILCode.Ldloc) |
||||
return; |
||||
if (newObj.Arguments[1].Code != ILCode.Ldftn) |
||||
return; |
||||
MethodDefinition anonymousMethod = ((MethodReference)newObj.Arguments[1].Operand).ResolveWithinSameModule(); // method is defined in current assembly
|
||||
if (!Ast.Transforms.DelegateConstruction.IsAnonymousMethod(context, anonymousMethod)) |
||||
return; |
||||
|
||||
ILNode followingNode = block.Body.ElementAtOrDefault(i + 1); |
||||
if (followingNode != null && followingNode.GetSelfAndChildrenRecursive<ILExpression>().Count( |
||||
e => e.Code == ILCode.Ldloc && (ILVariable)e.Operand == v) == 1) |
||||
{ |
||||
ILInlining inlining = new ILInlining(method); |
||||
if (!(inlining.numLdloc.GetOrDefault(v) == 2 && inlining.numStloc.GetOrDefault(v) == 2 && inlining.numLdloca.GetOrDefault(v) == 0)) |
||||
return; |
||||
|
||||
// Find the store instruction that initializes the local to null:
|
||||
foreach (ILBlock storeBlock in method.GetSelfAndChildrenRecursive<ILBlock>()) { |
||||
for (int j = 0; j < storeBlock.Body.Count; j++) { |
||||
ILVariable storedVar; |
||||
ILExpression storedExpr; |
||||
if (storeBlock.Body[j].Match(ILCode.Stloc, out storedVar, out storedExpr) && storedVar == v && storedExpr.Match(ILCode.Ldnull)) { |
||||
// Remove the instruction
|
||||
storeBlock.Body.RemoveAt(j); |
||||
if (storeBlock == block && j < i) |
||||
i--; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
block.Body[i] = stloc; // remove the 'if (v==null)'
|
||||
inlining = new ILInlining(method); |
||||
inlining.InlineIfPossible(block.Body, ref i); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region MakeAssignmentExpression
|
||||
bool MakeAssignmentExpression(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
// exprVar = ...
|
||||
// stloc(v, exprVar)
|
||||
// ->
|
||||
// exprVar = stloc(v, ...))
|
||||
ILVariable exprVar; |
||||
ILExpression initializer; |
||||
if (!(expr.Match(ILCode.Stloc, out exprVar, out initializer) && exprVar.IsGenerated)) |
||||
return false; |
||||
ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; |
||||
ILVariable v; |
||||
ILExpression stLocArg; |
||||
if (nextExpr.Match(ILCode.Stloc, out v, out stLocArg) && stLocArg.MatchLdloc(exprVar)) { |
||||
ILExpression store2 = body.ElementAtOrDefault(pos + 2) as ILExpression; |
||||
if (StoreCanBeConvertedToAssignment(store2, exprVar)) { |
||||
// expr_44 = ...
|
||||
// stloc(v1, expr_44)
|
||||
// anystore(v2, expr_44)
|
||||
// ->
|
||||
// stloc(v1, anystore(v2, ...))
|
||||
ILInlining inlining = new ILInlining(method); |
||||
if (inlining.numLdloc.GetOrDefault(exprVar) == 2 && inlining.numStloc.GetOrDefault(exprVar) == 1) { |
||||
body.RemoveAt(pos + 2); // remove store2
|
||||
body.RemoveAt(pos); // remove expr = ...
|
||||
nextExpr.Arguments[0] = store2; |
||||
store2.Arguments[store2.Arguments.Count - 1] = initializer; |
||||
|
||||
inlining.InlineIfPossible(body, ref pos); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
body.RemoveAt(pos + 1); // remove stloc
|
||||
nextExpr.Arguments[0] = initializer; |
||||
((ILExpression)body[pos]).Arguments[0] = nextExpr; |
||||
return true; |
||||
} else if ((nextExpr.Code == ILCode.Stsfld || nextExpr.Code == ILCode.CallSetter || nextExpr.Code == ILCode.CallvirtSetter) && nextExpr.Arguments.Count == 1) { |
||||
// exprVar = ...
|
||||
// stsfld(fld, exprVar)
|
||||
// ->
|
||||
// exprVar = stsfld(fld, ...))
|
||||
if (nextExpr.Arguments[0].MatchLdloc(exprVar)) { |
||||
body.RemoveAt(pos + 1); // remove stsfld
|
||||
nextExpr.Arguments[0] = initializer; |
||||
((ILExpression)body[pos]).Arguments[0] = nextExpr; |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool StoreCanBeConvertedToAssignment(ILExpression store, ILVariable exprVar) |
||||
{ |
||||
if (store == null) |
||||
return false; |
||||
switch (store.Code) { |
||||
case ILCode.Stloc: |
||||
case ILCode.Stfld: |
||||
case ILCode.Stsfld: |
||||
case ILCode.Stobj: |
||||
case ILCode.CallSetter: |
||||
case ILCode.CallvirtSetter: |
||||
break; |
||||
default: |
||||
if (!store.Code.IsStoreToArray()) |
||||
return false; |
||||
break; |
||||
} |
||||
return store.Arguments.Last().Code == ILCode.Ldloc && store.Arguments.Last().Operand == exprVar; |
||||
} |
||||
#endregion
|
||||
|
||||
#region MakeCompoundAssignments
|
||||
bool MakeCompoundAssignments(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
bool modified = false; |
||||
modified |= MakeCompoundAssignment(expr); |
||||
// Static fields and local variables are not handled here - those are expressions without side effects
|
||||
// and get handled by ReplaceMethodCallsWithOperators
|
||||
// (which does a reversible transform to the short operator form, as the introduction of checked/unchecked might have to revert to the long form).
|
||||
foreach (ILExpression arg in expr.Arguments) { |
||||
modified |= MakeCompoundAssignments(null, arg, -1); |
||||
} |
||||
if (modified && body != null) |
||||
new ILInlining(method).InlineInto(body, pos, aggressive: false); |
||||
return modified; |
||||
} |
||||
|
||||
bool MakeCompoundAssignment(ILExpression expr) |
||||
{ |
||||
// stelem.any(T, ldloc(array), ldloc(pos), <OP>(ldelem.any(T, ldloc(array), ldloc(pos)), <RIGHT>))
|
||||
// or
|
||||
// stobj(T, ldloc(ptr), <OP>(ldobj(T, ldloc(ptr)), <RIGHT>))
|
||||
ILCode expectedLdelemCode; |
||||
switch (expr.Code) { |
||||
case ILCode.Stelem_Any: |
||||
expectedLdelemCode = ILCode.Ldelem_Any; |
||||
break; |
||||
case ILCode.Stfld: |
||||
expectedLdelemCode = ILCode.Ldfld; |
||||
break; |
||||
case ILCode.Stobj: |
||||
expectedLdelemCode = ILCode.Ldobj; |
||||
break; |
||||
case ILCode.CallSetter: |
||||
expectedLdelemCode = ILCode.CallGetter; |
||||
break; |
||||
case ILCode.CallvirtSetter: |
||||
expectedLdelemCode = ILCode.CallvirtGetter; |
||||
break; |
||||
default: |
||||
return false; |
||||
} |
||||
|
||||
// all arguments except the last (so either array+pos, or ptr):
|
||||
bool hasGeneratedVar = false; |
||||
for (int i = 0; i < expr.Arguments.Count - 1; i++) { |
||||
ILVariable inputVar; |
||||
if (!expr.Arguments[i].Match(ILCode.Ldloc, out inputVar)) |
||||
return false; |
||||
hasGeneratedVar |= inputVar.IsGenerated; |
||||
} |
||||
// At least one of the variables must be generated; otherwise we just keep the expanded form.
|
||||
// We do this because we want compound assignments to be represented in ILAst only when strictly necessary;
|
||||
// other compound assignments will be introduced by ReplaceMethodCallsWithOperator
|
||||
// (which uses a reversible transformation, see ReplaceMethodCallsWithOperator.RestoreOriginalAssignOperatorAnnotation)
|
||||
if (!hasGeneratedVar) |
||||
return false; |
||||
|
||||
ILExpression op = expr.Arguments.Last(); |
||||
if (!CanBeRepresentedAsCompoundAssignment(op.Code)) |
||||
return false; |
||||
ILExpression ldelem = op.Arguments[0]; |
||||
if (ldelem.Code != expectedLdelemCode) |
||||
return false; |
||||
Debug.Assert(ldelem.Arguments.Count == expr.Arguments.Count - 1); |
||||
for (int i = 0; i < ldelem.Arguments.Count; i++) { |
||||
if (!ldelem.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand)) |
||||
return false; |
||||
} |
||||
expr.Code = ILCode.CompoundAssignment; |
||||
expr.Operand = null; |
||||
expr.Arguments.RemoveRange(0, ldelem.Arguments.Count); |
||||
// result is "CompoundAssignment(<OP>(ldelem.any(...), <RIGHT>))"
|
||||
return true; |
||||
} |
||||
|
||||
static bool CanBeRepresentedAsCompoundAssignment(ILCode code) |
||||
{ |
||||
switch (code) { |
||||
case ILCode.Add: |
||||
case ILCode.Add_Ovf: |
||||
case ILCode.Add_Ovf_Un: |
||||
case ILCode.Sub: |
||||
case ILCode.Sub_Ovf: |
||||
case ILCode.Sub_Ovf_Un: |
||||
case ILCode.Mul: |
||||
case ILCode.Mul_Ovf: |
||||
case ILCode.Mul_Ovf_Un: |
||||
case ILCode.Div: |
||||
case ILCode.Div_Un: |
||||
case ILCode.Rem: |
||||
case ILCode.Rem_Un: |
||||
case ILCode.And: |
||||
case ILCode.Or: |
||||
case ILCode.Xor: |
||||
case ILCode.Shl: |
||||
case ILCode.Shr: |
||||
case ILCode.Shr_Un: |
||||
return true; |
||||
default: |
||||
return false; |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region IntroducePostIncrement
|
||||
|
||||
bool IntroducePostIncrement(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
bool modified = IntroducePostIncrementForVariables(body, expr, pos); |
||||
Debug.Assert(body[pos] == expr); // IntroducePostIncrementForVariables shouldn't change the expression reference
|
||||
ILExpression newExpr = IntroducePostIncrementForInstanceFields(expr); |
||||
if (newExpr != null) { |
||||
modified = true; |
||||
body[pos] = newExpr; |
||||
new ILInlining(method).InlineIfPossible(body, ref pos); |
||||
} |
||||
return modified; |
||||
} |
||||
|
||||
bool IntroducePostIncrementForVariables(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
// Works for variables and static fields/properties
|
||||
|
||||
// expr = ldloc(i)
|
||||
// stloc(i, add(expr, ldc.i4(1)))
|
||||
// ->
|
||||
// expr = postincrement(1, ldloca(i))
|
||||
ILVariable exprVar; |
||||
ILExpression exprInit; |
||||
if (!(expr.Match(ILCode.Stloc, out exprVar, out exprInit) && exprVar.IsGenerated)) |
||||
return false; |
||||
|
||||
//The next expression
|
||||
ILExpression nextExpr = body.ElementAtOrDefault(pos + 1) as ILExpression; |
||||
if (nextExpr == null) |
||||
return false; |
||||
|
||||
ILCode loadInstruction = exprInit.Code; |
||||
ILCode storeInstruction = nextExpr.Code; |
||||
bool recombineVariable = false; |
||||
|
||||
// We only recognise local variables, static fields, and static getters with no arguments
|
||||
switch (loadInstruction) { |
||||
case ILCode.Ldloc: |
||||
//Must be a matching store type
|
||||
if (storeInstruction != ILCode.Stloc) |
||||
return false; |
||||
ILVariable loadVar = (ILVariable)exprInit.Operand; |
||||
ILVariable storeVar = (ILVariable)nextExpr.Operand; |
||||
if (loadVar != storeVar) { |
||||
if (loadVar.OriginalVariable != null && loadVar.OriginalVariable == storeVar.OriginalVariable) |
||||
recombineVariable = true; |
||||
else |
||||
return false; |
||||
} |
||||
break; |
||||
case ILCode.Ldsfld: |
||||
if (storeInstruction != ILCode.Stsfld) |
||||
return false; |
||||
if (exprInit.Operand != nextExpr.Operand) |
||||
return false; |
||||
break; |
||||
case ILCode.CallGetter: |
||||
// non-static getters would have the 'this' argument
|
||||
if (exprInit.Arguments.Count != 0) |
||||
return false; |
||||
if (storeInstruction != ILCode.CallSetter) |
||||
return false; |
||||
if (!IsGetterSetterPair(exprInit.Operand, nextExpr.Operand)) |
||||
return false; |
||||
break; |
||||
default: |
||||
return false; |
||||
} |
||||
|
||||
ILExpression addExpr = nextExpr.Arguments[0]; |
||||
|
||||
int incrementAmount; |
||||
ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount); |
||||
if (!(incrementAmount != 0 && addExpr.Arguments[0].MatchLdloc(exprVar))) |
||||
return false; |
||||
|
||||
if (recombineVariable) { |
||||
// Split local variable, unsplit these two instances
|
||||
// replace nextExpr.Operand with exprInit.Operand
|
||||
ReplaceVariables(method, oldVar => oldVar == nextExpr.Operand ? (ILVariable)exprInit.Operand : oldVar); |
||||
} |
||||
|
||||
switch (loadInstruction) { |
||||
case ILCode.Ldloc: |
||||
exprInit.Code = ILCode.Ldloca; |
||||
break; |
||||
case ILCode.Ldsfld: |
||||
exprInit.Code = ILCode.Ldsflda; |
||||
break; |
||||
case ILCode.CallGetter: |
||||
exprInit = new ILExpression(ILCode.AddressOf, null, exprInit); |
||||
break; |
||||
} |
||||
expr.Arguments[0] = new ILExpression(incrementCode, incrementAmount, exprInit); |
||||
body.RemoveAt(pos + 1); // TODO ILRanges
|
||||
return true; |
||||
} |
||||
|
||||
static bool IsGetterSetterPair(object getterOperand, object setterOperand) |
||||
{ |
||||
MethodReference getter = getterOperand as MethodReference; |
||||
MethodReference setter = setterOperand as MethodReference; |
||||
if (getter == null || setter == null) |
||||
return false; |
||||
if (!TypeAnalysis.IsSameType(getter.DeclaringType, setter.DeclaringType)) |
||||
return false; |
||||
MethodDefinition getterDef = getter.Resolve(); |
||||
MethodDefinition setterDef = setter.Resolve(); |
||||
if (getterDef == null || setterDef == null) |
||||
return false; |
||||
foreach (PropertyDefinition prop in getterDef.DeclaringType.Properties) { |
||||
if (prop.GetMethod == getterDef) |
||||
return prop.SetMethod == setterDef; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
ILExpression IntroducePostIncrementForInstanceFields(ILExpression expr) |
||||
{ |
||||
// stfld(field, ldloc(instance), add(stloc(helperVar, ldfld(field, ldloc(instance))), ldc.i4(1)))
|
||||
// -> stloc(helperVar, postincrement(1, ldflda(field, ldloc(instance))))
|
||||
|
||||
// Also works for array elements and pointers:
|
||||
|
||||
// stelem.any(T, ldloc(instance), ldloc(pos), add(stloc(helperVar, ldelem.any(T, ldloc(instance), ldloc(pos))), ldc.i4(1)))
|
||||
// -> stloc(helperVar, postincrement(1, ldelema(ldloc(instance), ldloc(pos))))
|
||||
|
||||
// stobj(T, ldloc(ptr), add(stloc(helperVar, ldobj(T, ldloc(ptr)), ldc.i4(1))))
|
||||
// -> stloc(helperVar, postIncrement(1, ldloc(ptr)))
|
||||
|
||||
// callsetter(set_P, ldloc(instance), add(stloc(helperVar, callgetter(get_P, ldloc(instance))), ldc.i4(1)))
|
||||
// -> stloc(helperVar, postIncrement(1, propertyaddress. callgetter(get_P, ldloc(instance))))
|
||||
|
||||
if (!(expr.Code == ILCode.Stfld || expr.Code.IsStoreToArray() || expr.Code == ILCode.Stobj || expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter)) |
||||
return null; |
||||
|
||||
// Test that all arguments except the last are ldloc (1 arg for fields and pointers, 2 args for arrays)
|
||||
for (int i = 0; i < expr.Arguments.Count - 1; i++) { |
||||
if (expr.Arguments[i].Code != ILCode.Ldloc) |
||||
return null; |
||||
} |
||||
|
||||
ILExpression addExpr = expr.Arguments[expr.Arguments.Count - 1]; |
||||
int incrementAmount; |
||||
ILCode incrementCode = GetIncrementCode(addExpr, out incrementAmount); |
||||
ILVariable helperVar; |
||||
ILExpression initialValue; |
||||
if (!(incrementAmount != 0 && addExpr.Arguments[0].Match(ILCode.Stloc, out helperVar, out initialValue))) |
||||
return null; |
||||
|
||||
if (expr.Code == ILCode.Stfld) { |
||||
if (initialValue.Code != ILCode.Ldfld) |
||||
return null; |
||||
// There might be two different FieldReference instances, so we compare the field's signatures:
|
||||
FieldReference getField = (FieldReference)initialValue.Operand; |
||||
FieldReference setField = (FieldReference)expr.Operand; |
||||
if (!(TypeAnalysis.IsSameType(getField.DeclaringType, setField.DeclaringType) |
||||
&& getField.Name == setField.Name && TypeAnalysis.IsSameType(getField.FieldType, setField.FieldType))) |
||||
{ |
||||
return null; |
||||
} |
||||
} else if (expr.Code == ILCode.Stobj) { |
||||
if (!(initialValue.Code == ILCode.Ldobj && initialValue.Operand == expr.Operand)) |
||||
return null; |
||||
} else if (expr.Code == ILCode.CallSetter) { |
||||
if (!(initialValue.Code == ILCode.CallGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand))) |
||||
return null; |
||||
} else if (expr.Code == ILCode.CallvirtSetter) { |
||||
if (!(initialValue.Code == ILCode.CallvirtGetter && IsGetterSetterPair(initialValue.Operand, expr.Operand))) |
||||
return null; |
||||
} else { |
||||
if (!initialValue.Code.IsLoadFromArray()) |
||||
return null; |
||||
} |
||||
Debug.Assert(expr.Arguments.Count - 1 == initialValue.Arguments.Count); |
||||
for (int i = 0; i < initialValue.Arguments.Count; i++) { |
||||
if (!initialValue.Arguments[i].MatchLdloc((ILVariable)expr.Arguments[i].Operand)) |
||||
return null; |
||||
} |
||||
|
||||
ILExpression stloc = addExpr.Arguments[0]; |
||||
if (expr.Code == ILCode.Stobj) { |
||||
stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue.Arguments[0]); |
||||
} else if (expr.Code == ILCode.CallSetter || expr.Code == ILCode.CallvirtSetter) { |
||||
initialValue = new ILExpression(ILCode.AddressOf, null, initialValue); |
||||
stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue); |
||||
} else { |
||||
stloc.Arguments[0] = new ILExpression(ILCode.PostIncrement, incrementAmount, initialValue); |
||||
initialValue.Code = (expr.Code == ILCode.Stfld ? ILCode.Ldflda : ILCode.Ldelema); |
||||
} |
||||
// TODO: ILRanges?
|
||||
|
||||
return stloc; |
||||
} |
||||
|
||||
ILCode GetIncrementCode(ILExpression addExpr, out int incrementAmount) |
||||
{ |
||||
ILCode incrementCode; |
||||
bool decrement = false; |
||||
switch (addExpr.Code) { |
||||
case ILCode.Add: |
||||
incrementCode = ILCode.PostIncrement; |
||||
break; |
||||
case ILCode.Add_Ovf: |
||||
incrementCode = ILCode.PostIncrement_Ovf; |
||||
break; |
||||
case ILCode.Add_Ovf_Un: |
||||
incrementCode = ILCode.PostIncrement_Ovf_Un; |
||||
break; |
||||
case ILCode.Sub: |
||||
incrementCode = ILCode.PostIncrement; |
||||
decrement = true; |
||||
break; |
||||
case ILCode.Sub_Ovf: |
||||
incrementCode = ILCode.PostIncrement_Ovf; |
||||
decrement = true; |
||||
break; |
||||
case ILCode.Sub_Ovf_Un: |
||||
incrementCode = ILCode.PostIncrement_Ovf_Un; |
||||
decrement = true; |
||||
break; |
||||
default: |
||||
incrementAmount = 0; |
||||
return ILCode.Nop; |
||||
} |
||||
if (addExpr.Arguments[1].Match(ILCode.Ldc_I4, out incrementAmount)) { |
||||
if (incrementAmount == -1 || incrementAmount == 1) { // TODO pointer increment?
|
||||
if (decrement) |
||||
incrementAmount = -incrementAmount; |
||||
return incrementCode; |
||||
} |
||||
} |
||||
incrementAmount = 0; |
||||
return ILCode.Nop; |
||||
} |
||||
#endregion
|
||||
|
||||
#region IntroduceFixedStatements
|
||||
bool IntroduceFixedStatements(List<ILNode> body, int i) |
||||
{ |
||||
ILExpression initValue; |
||||
ILVariable pinnedVar; |
||||
int initEndPos; |
||||
if (!MatchFixedInitializer(body, i, out pinnedVar, out initValue, out initEndPos)) |
||||
return false; |
||||
|
||||
ILFixedStatement fixedStmt = body.ElementAtOrDefault(initEndPos) as ILFixedStatement; |
||||
if (fixedStmt != null) { |
||||
ILExpression expr = fixedStmt.BodyBlock.Body.LastOrDefault() as ILExpression; |
||||
if (expr != null && expr.Code == ILCode.Stloc && expr.Operand == pinnedVar && IsNullOrZero(expr.Arguments[0])) { |
||||
// we found a second initializer for the existing fixed statement
|
||||
fixedStmt.Initializers.Insert(0, initValue); |
||||
body.RemoveRange(i, initEndPos - i); |
||||
fixedStmt.BodyBlock.Body.RemoveAt(fixedStmt.BodyBlock.Body.Count - 1); |
||||
if (pinnedVar.Type.IsByReference) |
||||
pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
// find where pinnedVar is reset to 0:
|
||||
int j; |
||||
for (j = initEndPos; j < body.Count; j++) { |
||||
ILVariable v2; |
||||
ILExpression storedVal; |
||||
// stloc(pinned_Var, conv.u(ldc.i4(0)))
|
||||
if (body[j].Match(ILCode.Stloc, out v2, out storedVal) && v2 == pinnedVar) { |
||||
if (IsNullOrZero(storedVal)) { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
// Create fixed statement from i to j
|
||||
fixedStmt = new ILFixedStatement(); |
||||
fixedStmt.Initializers.Add(initValue); |
||||
fixedStmt.BodyBlock = new ILBlock(body.GetRange(initEndPos, j - initEndPos)); // from initEndPos to j-1 (inclusive)
|
||||
body.RemoveRange(i + 1, Math.Min(j, body.Count - 1) - i); // from i+1 to j (inclusive)
|
||||
body[i] = fixedStmt; |
||||
if (pinnedVar.Type.IsByReference) |
||||
pinnedVar.Type = new PointerType(((ByReferenceType)pinnedVar.Type).ElementType); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool IsNullOrZero(ILExpression expr) |
||||
{ |
||||
if (expr.Code == ILCode.Conv_U || expr.Code == ILCode.Conv_I) |
||||
expr = expr.Arguments[0]; |
||||
return (expr.Code == ILCode.Ldc_I4 && (int)expr.Operand == 0) || expr.Code == ILCode.Ldnull; |
||||
} |
||||
|
||||
bool MatchFixedInitializer(List<ILNode> body, int i, out ILVariable pinnedVar, out ILExpression initValue, out int nextPos) |
||||
{ |
||||
if (body[i].Match(ILCode.Stloc, out pinnedVar, out initValue) && pinnedVar.IsPinned && !IsNullOrZero(initValue)) { |
||||
initValue = (ILExpression)body[i]; |
||||
nextPos = i + 1; |
||||
HandleStringFixing(pinnedVar, body, ref nextPos, ref initValue); |
||||
return true; |
||||
} |
||||
ILCondition ifStmt = body[i] as ILCondition; |
||||
ILExpression arrayLoadingExpr; |
||||
if (ifStmt != null && MatchFixedArrayInitializerCondition(ifStmt.Condition, out arrayLoadingExpr)) { |
||||
ILVariable arrayVariable = (ILVariable)arrayLoadingExpr.Operand; |
||||
ILExpression trueValue; |
||||
if (ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 |
||||
&& ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out pinnedVar, out trueValue) |
||||
&& pinnedVar.IsPinned && IsNullOrZero(trueValue)) |
||||
{ |
||||
if (ifStmt.FalseBlock != null && ifStmt.FalseBlock.Body.Count == 1 && ifStmt.FalseBlock.Body[0] is ILFixedStatement) { |
||||
ILFixedStatement fixedStmt = (ILFixedStatement)ifStmt.FalseBlock.Body[0]; |
||||
ILVariable stlocVar; |
||||
ILExpression falseValue; |
||||
if (fixedStmt.Initializers.Count == 1 && fixedStmt.BodyBlock.Body.Count == 0 |
||||
&& fixedStmt.Initializers[0].Match(ILCode.Stloc, out stlocVar, out falseValue) && stlocVar == pinnedVar) |
||||
{ |
||||
ILVariable loadedVariable; |
||||
if (falseValue.Code == ILCode.Ldelema |
||||
&& falseValue.Arguments[0].Match(ILCode.Ldloc, out loadedVariable) && loadedVariable == arrayVariable |
||||
&& IsNullOrZero(falseValue.Arguments[1])) |
||||
{ |
||||
// OK, we detected the pattern for fixing an array.
|
||||
// Now check whether the loading expression was a store ot a temp. var
|
||||
// that can be eliminated.
|
||||
if (arrayLoadingExpr.Code == ILCode.Stloc) { |
||||
ILInlining inlining = new ILInlining(method); |
||||
if (inlining.numLdloc.GetOrDefault(arrayVariable) == 2 && |
||||
inlining.numStloc.GetOrDefault(arrayVariable) == 1 && inlining.numLdloca.GetOrDefault(arrayVariable) == 0) |
||||
{ |
||||
arrayLoadingExpr = arrayLoadingExpr.Arguments[0]; |
||||
} |
||||
} |
||||
initValue = new ILExpression(ILCode.Stloc, pinnedVar, arrayLoadingExpr); |
||||
nextPos = i + 1; |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
initValue = null; |
||||
nextPos = -1; |
||||
return false; |
||||
} |
||||
|
||||
bool MatchFixedArrayInitializerCondition(ILExpression condition, out ILExpression initValue) |
||||
{ |
||||
ILExpression logicAnd; |
||||
ILVariable arrayVar; |
||||
if (condition.Match(ILCode.LogicNot, out logicAnd) && logicAnd.Code == ILCode.LogicAnd) { |
||||
initValue = UnpackDoubleNegation(logicAnd.Arguments[0]); |
||||
ILExpression arrayVarInitializer; |
||||
if (initValue.Match(ILCode.Ldloc, out arrayVar) |
||||
|| initValue.Match(ILCode.Stloc, out arrayVar, out arrayVarInitializer)) |
||||
{ |
||||
ILExpression arrayLength = logicAnd.Arguments[1]; |
||||
if (arrayLength.Code == ILCode.Conv_I4) |
||||
arrayLength = arrayLength.Arguments[0]; |
||||
return arrayLength.Code == ILCode.Ldlen && arrayLength.Arguments[0].MatchLdloc(arrayVar); |
||||
} |
||||
} |
||||
initValue = null; |
||||
return false; |
||||
} |
||||
|
||||
ILExpression UnpackDoubleNegation(ILExpression expr) |
||||
{ |
||||
ILExpression negated; |
||||
if (expr.Match(ILCode.LogicNot, out negated) && negated.Match(ILCode.LogicNot, out negated)) |
||||
return negated; |
||||
else |
||||
return expr; |
||||
} |
||||
|
||||
bool HandleStringFixing(ILVariable pinnedVar, List<ILNode> body, ref int pos, ref ILExpression fixedStmtInitializer) |
||||
{ |
||||
// fixed (stloc(pinnedVar, ldloc(text))) {
|
||||
// var1 = var2 = conv.i(ldloc(pinnedVar))
|
||||
// if (logicnot(logicnot(var1))) {
|
||||
// var2 = add(var1, call(RuntimeHelpers::get_OffsetToStringData))
|
||||
// }
|
||||
// stloc(ptrVar, var2)
|
||||
// ...
|
||||
|
||||
if (pos >= body.Count) |
||||
return false; |
||||
|
||||
ILVariable var1, var2; |
||||
ILExpression varAssignment, ptrInitialization; |
||||
if (!(body[pos].Match(ILCode.Stloc, out var1, out varAssignment) && varAssignment.Match(ILCode.Stloc, out var2, out ptrInitialization))) |
||||
return false; |
||||
if (!(var1.IsGenerated && var2.IsGenerated)) |
||||
return false; |
||||
if (ptrInitialization.Code == ILCode.Conv_I || ptrInitialization.Code == ILCode.Conv_U) |
||||
ptrInitialization = ptrInitialization.Arguments[0]; |
||||
if (!ptrInitialization.MatchLdloc(pinnedVar)) |
||||
return false; |
||||
|
||||
ILCondition ifStmt = body[pos + 1] as ILCondition; |
||||
if (!(ifStmt != null && ifStmt.TrueBlock != null && ifStmt.TrueBlock.Body.Count == 1 && (ifStmt.FalseBlock == null || ifStmt.FalseBlock.Body.Count == 0))) |
||||
return false; |
||||
if (!UnpackDoubleNegation(ifStmt.Condition).MatchLdloc(var1)) |
||||
return false; |
||||
ILVariable assignedVar; |
||||
ILExpression assignedExpr; |
||||
if (!(ifStmt.TrueBlock.Body[0].Match(ILCode.Stloc, out assignedVar, out assignedExpr) && assignedVar == var2 && assignedExpr.Code == ILCode.Add)) |
||||
return false; |
||||
MethodReference calledMethod; |
||||
if (!(assignedExpr.Arguments[0].MatchLdloc(var1))) |
||||
return false; |
||||
if (!(assignedExpr.Arguments[1].Match(ILCode.Call, out calledMethod) || assignedExpr.Arguments[1].Match(ILCode.CallGetter, out calledMethod))) |
||||
return false; |
||||
if (!(calledMethod.Name == "get_OffsetToStringData" && calledMethod.DeclaringType.FullName == "System.Runtime.CompilerServices.RuntimeHelpers")) |
||||
return false; |
||||
|
||||
ILVariable pointerVar; |
||||
if (body[pos + 2].Match(ILCode.Stloc, out pointerVar, out assignedExpr) && assignedExpr.MatchLdloc(var2)) { |
||||
pos += 3; |
||||
fixedStmtInitializer.Operand = pointerVar; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
#endregion
|
||||
|
||||
#region SimplifyShiftOperators
|
||||
static bool SimplifyShiftOperators(List<ILNode> body, ILExpression expr, int pos) |
||||
{ |
||||
// C# compiles "a << b" to "a << (b & 31)", so we will remove the "& 31" if possible.
|
||||
bool modified = false; |
||||
SimplifyShiftOperators(expr, ref modified); |
||||
return modified; |
||||
} |
||||
|
||||
static void SimplifyShiftOperators(ILExpression expr, ref bool modified) |
||||
{ |
||||
for (int i = 0; i < expr.Arguments.Count; i++) |
||||
SimplifyShiftOperators(expr.Arguments[i], ref modified); |
||||
if (expr.Code != ILCode.Shl && expr.Code != ILCode.Shr && expr.Code != ILCode.Shr_Un) |
||||
return; |
||||
var a = expr.Arguments[1]; |
||||
if (a.Code != ILCode.And || a.Arguments[1].Code != ILCode.Ldc_I4 || expr.InferredType == null) |
||||
return; |
||||
int mask; |
||||
switch (expr.InferredType.MetadataType) { |
||||
case MetadataType.Int32: |
||||
case MetadataType.UInt32: mask = 31; break; |
||||
case MetadataType.Int64: |
||||
case MetadataType.UInt64: mask = 63; break; |
||||
default: return; |
||||
} |
||||
if ((int)a.Arguments[1].Operand != mask) return; |
||||
var res = a.Arguments[0]; |
||||
res.ILRanges.AddRange(a.ILRanges); |
||||
res.ILRanges.AddRange(a.Arguments[1].ILRanges); |
||||
expr.Arguments[1] = res; |
||||
modified = true; |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,376 @@
@@ -0,0 +1,376 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
|
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public class SimpleControlFlow |
||||
{ |
||||
Dictionary<ILLabel, int> labelGlobalRefCount = new Dictionary<ILLabel, int>(); |
||||
Dictionary<ILLabel, ILBasicBlock> labelToBasicBlock = new Dictionary<ILLabel, ILBasicBlock>(); |
||||
|
||||
DecompilerContext context; |
||||
TypeSystem typeSystem; |
||||
|
||||
public SimpleControlFlow(DecompilerContext context, ILBlock method) |
||||
{ |
||||
this.context = context; |
||||
this.typeSystem = context.CurrentMethod.Module.TypeSystem; |
||||
|
||||
foreach(ILLabel target in method.GetSelfAndChildrenRecursive<ILExpression>(e => e.IsBranch()).SelectMany(e => e.GetBranchTargets())) { |
||||
labelGlobalRefCount[target] = labelGlobalRefCount.GetOrDefault(target) + 1; |
||||
} |
||||
foreach(ILBasicBlock bb in method.GetSelfAndChildrenRecursive<ILBasicBlock>()) { |
||||
foreach(ILLabel label in bb.GetChildren().OfType<ILLabel>()) { |
||||
labelToBasicBlock[label] = bb; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public bool SimplifyTernaryOperator(List<ILNode> body, ILBasicBlock head, int pos) |
||||
{ |
||||
Debug.Assert(body.Contains(head)); |
||||
|
||||
ILExpression condExpr; |
||||
ILLabel trueLabel; |
||||
ILLabel falseLabel; |
||||
ILVariable trueLocVar = null; |
||||
ILExpression trueExpr; |
||||
ILLabel trueFall; |
||||
ILVariable falseLocVar = null; |
||||
ILExpression falseExpr; |
||||
ILLabel falseFall; |
||||
object unused; |
||||
|
||||
if (head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel) && |
||||
labelGlobalRefCount[trueLabel] == 1 && |
||||
labelGlobalRefCount[falseLabel] == 1 && |
||||
((labelToBasicBlock[trueLabel].MatchSingleAndBr(ILCode.Stloc, out trueLocVar, out trueExpr, out trueFall) && |
||||
labelToBasicBlock[falseLabel].MatchSingleAndBr(ILCode.Stloc, out falseLocVar, out falseExpr, out falseFall) && |
||||
trueLocVar == falseLocVar && trueFall == falseFall) || |
||||
(labelToBasicBlock[trueLabel].MatchSingle(ILCode.Ret, out unused, out trueExpr) && |
||||
labelToBasicBlock[falseLabel].MatchSingle(ILCode.Ret, out unused, out falseExpr))) && |
||||
body.Contains(labelToBasicBlock[trueLabel]) && |
||||
body.Contains(labelToBasicBlock[falseLabel]) |
||||
) |
||||
{ |
||||
bool isStloc = trueLocVar != null; |
||||
ILCode opCode = isStloc ? ILCode.Stloc : ILCode.Ret; |
||||
TypeReference retType = isStloc ? trueLocVar.Type : this.context.CurrentMethod.ReturnType; |
||||
bool retTypeIsBoolean = TypeAnalysis.IsBoolean(retType); |
||||
int leftBoolVal; |
||||
int rightBoolVal; |
||||
ILExpression newExpr; |
||||
// a ? true:false is equivalent to a
|
||||
// a ? false:true is equivalent to !a
|
||||
// a ? true : b is equivalent to a || b
|
||||
// a ? b : true is equivalent to !a || b
|
||||
// a ? b : false is equivalent to a && b
|
||||
// a ? false : b is equivalent to !a && b
|
||||
if (retTypeIsBoolean && |
||||
trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal) && |
||||
falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal) && |
||||
((leftBoolVal != 0 && rightBoolVal == 0) || (leftBoolVal == 0 && rightBoolVal != 0)) |
||||
) |
||||
{ |
||||
// It can be expressed as trivilal expression
|
||||
if (leftBoolVal != 0) { |
||||
newExpr = condExpr; |
||||
} else { |
||||
newExpr = new ILExpression(ILCode.LogicNot, null, condExpr); |
||||
} |
||||
} else if (retTypeIsBoolean && trueExpr.Match(ILCode.Ldc_I4, out leftBoolVal)) { |
||||
// It can be expressed as logical expression
|
||||
if (leftBoolVal != 0) { |
||||
newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, condExpr, falseExpr); |
||||
} else { |
||||
newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, new ILExpression(ILCode.LogicNot, null, condExpr), falseExpr); |
||||
} |
||||
} else if (retTypeIsBoolean && falseExpr.Match(ILCode.Ldc_I4, out rightBoolVal)) { |
||||
// It can be expressed as logical expression
|
||||
if (rightBoolVal != 0) { |
||||
newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, new ILExpression(ILCode.LogicNot, null, condExpr), trueExpr); |
||||
} else { |
||||
newExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, condExpr, trueExpr); |
||||
} |
||||
} else { |
||||
// Ternary operator tends to create long complicated return statements
|
||||
if (opCode == ILCode.Ret) |
||||
return false; |
||||
|
||||
// Only simplify generated variables
|
||||
if (opCode == ILCode.Stloc && !trueLocVar.IsGenerated) |
||||
return false; |
||||
|
||||
// Create ternary expression
|
||||
newExpr = new ILExpression(ILCode.TernaryOp, null, condExpr, trueExpr, falseExpr); |
||||
} |
||||
|
||||
head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); |
||||
head.Body.Add(new ILExpression(opCode, trueLocVar, newExpr)); |
||||
if (isStloc) |
||||
head.Body.Add(new ILExpression(ILCode.Br, trueFall)); |
||||
|
||||
// Remove the old basic blocks
|
||||
body.RemoveOrThrow(labelToBasicBlock[trueLabel]); |
||||
body.RemoveOrThrow(labelToBasicBlock[falseLabel]); |
||||
|
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public bool SimplifyNullCoalescing(List<ILNode> body, ILBasicBlock head, int pos) |
||||
{ |
||||
// ...
|
||||
// v = ldloc(leftVar)
|
||||
// brtrue(endBBLabel, ldloc(leftVar))
|
||||
// br(rightBBLabel)
|
||||
//
|
||||
// rightBBLabel:
|
||||
// v = rightExpr
|
||||
// br(endBBLabel)
|
||||
// ...
|
||||
// =>
|
||||
// ...
|
||||
// v = NullCoalescing(ldloc(leftVar), rightExpr)
|
||||
// br(endBBLabel)
|
||||
|
||||
ILVariable v, v2; |
||||
ILExpression leftExpr, leftExpr2; |
||||
ILVariable leftVar; |
||||
ILLabel endBBLabel, endBBLabel2; |
||||
ILLabel rightBBLabel; |
||||
ILBasicBlock rightBB; |
||||
ILExpression rightExpr; |
||||
if (head.Body.Count >= 3 && |
||||
head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out v, out leftExpr) && |
||||
leftExpr.Match(ILCode.Ldloc, out leftVar) && |
||||
head.MatchLastAndBr(ILCode.Brtrue, out endBBLabel, out leftExpr2, out rightBBLabel) && |
||||
leftExpr2.MatchLdloc(leftVar) && |
||||
labelToBasicBlock.TryGetValue(rightBBLabel, out rightBB) && |
||||
rightBB.MatchSingleAndBr(ILCode.Stloc, out v2, out rightExpr, out endBBLabel2) && |
||||
v == v2 && |
||||
endBBLabel == endBBLabel2 && |
||||
labelGlobalRefCount.GetOrDefault(rightBBLabel) == 1 && |
||||
body.Contains(rightBB) |
||||
) |
||||
{ |
||||
head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br); |
||||
head.Body.Add(new ILExpression(ILCode.Stloc, v, new ILExpression(ILCode.NullCoalescing, null, leftExpr, rightExpr))); |
||||
head.Body.Add(new ILExpression(ILCode.Br, endBBLabel)); |
||||
|
||||
body.RemoveOrThrow(labelToBasicBlock[rightBBLabel]); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public bool SimplifyShortCircuit(List<ILNode> body, ILBasicBlock head, int pos) |
||||
{ |
||||
Debug.Assert(body.Contains(head)); |
||||
|
||||
ILExpression condExpr; |
||||
ILLabel trueLabel; |
||||
ILLabel falseLabel; |
||||
if(head.MatchLastAndBr(ILCode.Brtrue, out trueLabel, out condExpr, out falseLabel)) { |
||||
for (int pass = 0; pass < 2; pass++) { |
||||
|
||||
// On the second pass, swap labels and negate expression of the first branch
|
||||
// It is slightly ugly, but much better then copy-pasting this whole block
|
||||
ILLabel nextLabel = (pass == 0) ? trueLabel : falseLabel; |
||||
ILLabel otherLablel = (pass == 0) ? falseLabel : trueLabel; |
||||
bool negate = (pass == 1); |
||||
|
||||
ILBasicBlock nextBasicBlock = labelToBasicBlock[nextLabel]; |
||||
ILExpression nextCondExpr; |
||||
ILLabel nextTrueLablel; |
||||
ILLabel nextFalseLabel; |
||||
if (body.Contains(nextBasicBlock) && |
||||
nextBasicBlock != head && |
||||
labelGlobalRefCount[(ILLabel)nextBasicBlock.Body.First()] == 1 && |
||||
nextBasicBlock.MatchSingleAndBr(ILCode.Brtrue, out nextTrueLablel, out nextCondExpr, out nextFalseLabel) && |
||||
(otherLablel == nextFalseLabel || otherLablel == nextTrueLablel)) |
||||
{ |
||||
// Create short cicuit branch
|
||||
ILExpression logicExpr; |
||||
if (otherLablel == nextFalseLabel) { |
||||
logicExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicAnd, negate ? new ILExpression(ILCode.LogicNot, null, condExpr) : condExpr, nextCondExpr); |
||||
} else { |
||||
logicExpr = MakeLeftAssociativeShortCircuit(ILCode.LogicOr, negate ? condExpr : new ILExpression(ILCode.LogicNot, null, condExpr), nextCondExpr); |
||||
} |
||||
head.Body.RemoveTail(ILCode.Brtrue, ILCode.Br); |
||||
head.Body.Add(new ILExpression(ILCode.Brtrue, nextTrueLablel, logicExpr)); |
||||
head.Body.Add(new ILExpression(ILCode.Br, nextFalseLabel)); |
||||
|
||||
// Remove the inlined branch from scope
|
||||
body.RemoveOrThrow(nextBasicBlock); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public bool SimplifyCustomShortCircuit(List<ILNode> body, ILBasicBlock head, int pos) |
||||
{ |
||||
Debug.Assert(body.Contains(head)); |
||||
|
||||
// --- looking for the following pattern ---
|
||||
// stloc(targetVar, leftVar)
|
||||
// brtrue(exitLabel, call(op_False, leftVar)
|
||||
// br(followingBlock)
|
||||
//
|
||||
// FollowingBlock:
|
||||
// stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression))
|
||||
// br(exitLabel)
|
||||
// ---
|
||||
|
||||
if (head.Body.Count < 3) |
||||
return false; |
||||
|
||||
// looking for:
|
||||
// stloc(targetVar, leftVar)
|
||||
ILVariable targetVar; |
||||
ILExpression targetVarInitExpr; |
||||
if (!head.Body[head.Body.Count - 3].Match(ILCode.Stloc, out targetVar, out targetVarInitExpr)) |
||||
return false; |
||||
|
||||
ILVariable leftVar; |
||||
if (!targetVarInitExpr.Match(ILCode.Ldloc, out leftVar)) |
||||
return false; |
||||
|
||||
// looking for:
|
||||
// brtrue(exitLabel, call(op_False, leftVar)
|
||||
// br(followingBlock)
|
||||
ILExpression callExpr; |
||||
ILLabel exitLabel; |
||||
ILLabel followingBlock; |
||||
if(!head.MatchLastAndBr(ILCode.Brtrue, out exitLabel, out callExpr, out followingBlock)) |
||||
return false; |
||||
|
||||
if (labelGlobalRefCount[followingBlock] > 1) |
||||
return false; |
||||
|
||||
MethodReference opFalse; |
||||
ILExpression opFalseArg; |
||||
if (!callExpr.Match(ILCode.Call, out opFalse, out opFalseArg)) |
||||
return false; |
||||
|
||||
// ignore operators other than op_False and op_True
|
||||
if (opFalse.Name != "op_False" && opFalse.Name != "op_True") |
||||
return false; |
||||
|
||||
if (!opFalseArg.MatchLdloc(leftVar)) |
||||
return false; |
||||
|
||||
ILBasicBlock followingBasicBlock = labelToBasicBlock[followingBlock]; |
||||
|
||||
// FollowingBlock:
|
||||
// stloc(targetVar, call(op_BitwiseAnd, leftVar, rightExpression))
|
||||
// br(exitLabel)
|
||||
ILVariable _targetVar; |
||||
ILExpression opBitwiseCallExpr; |
||||
ILLabel _exitLabel; |
||||
if (!followingBasicBlock.MatchSingleAndBr(ILCode.Stloc, out _targetVar, out opBitwiseCallExpr, out _exitLabel)) |
||||
return false; |
||||
|
||||
if (_targetVar != targetVar || exitLabel != _exitLabel) |
||||
return false; |
||||
|
||||
MethodReference opBitwise; |
||||
ILExpression leftVarExpression; |
||||
ILExpression rightExpression; |
||||
if (!opBitwiseCallExpr.Match(ILCode.Call, out opBitwise, out leftVarExpression, out rightExpression)) |
||||
return false; |
||||
|
||||
if (!leftVarExpression.MatchLdloc(leftVar)) |
||||
return false; |
||||
|
||||
// ignore operators other than op_BitwiseAnd and op_BitwiseOr
|
||||
if (opBitwise.Name != "op_BitwiseAnd" && opBitwise.Name != "op_BitwiseOr") |
||||
return false; |
||||
|
||||
// insert:
|
||||
// stloc(targetVar, LogicAnd(C::op_BitwiseAnd, leftVar, rightExpression)
|
||||
// br(exitLabel)
|
||||
ILCode op = opBitwise.Name == "op_BitwiseAnd" ? ILCode.LogicAnd : ILCode.LogicOr; |
||||
|
||||
if (op == ILCode.LogicAnd && opFalse.Name != "op_False") |
||||
return false; |
||||
|
||||
if (op == ILCode.LogicOr && opFalse.Name != "op_True") |
||||
return false; |
||||
|
||||
ILExpression shortCircuitExpr = MakeLeftAssociativeShortCircuit(op, opFalseArg, rightExpression); |
||||
shortCircuitExpr.Operand = opBitwise; |
||||
|
||||
head.Body.RemoveTail(ILCode.Stloc, ILCode.Brtrue, ILCode.Br); |
||||
head.Body.Add(new ILExpression(ILCode.Stloc, targetVar, shortCircuitExpr)); |
||||
head.Body.Add(new ILExpression(ILCode.Br, exitLabel)); |
||||
body.Remove(followingBasicBlock); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
ILExpression MakeLeftAssociativeShortCircuit(ILCode code, ILExpression left, ILExpression right) |
||||
{ |
||||
// Assuming that the inputs are already left associative
|
||||
if (right.Match(code)) { |
||||
// Find the leftmost logical expression
|
||||
ILExpression current = right; |
||||
while(current.Arguments[0].Match(code)) |
||||
current = current.Arguments[0]; |
||||
current.Arguments[0] = new ILExpression(code, null, left, current.Arguments[0]); |
||||
return right; |
||||
} else { |
||||
return new ILExpression(code, null, left, right); |
||||
} |
||||
} |
||||
|
||||
public bool JoinBasicBlocks(List<ILNode> body, ILBasicBlock head, int pos) |
||||
{ |
||||
ILLabel nextLabel; |
||||
ILBasicBlock nextBB; |
||||
if (!head.Body.ElementAtOrDefault(head.Body.Count - 2).IsConditionalControlFlow() && |
||||
head.Body.Last().Match(ILCode.Br, out nextLabel) && |
||||
labelGlobalRefCount[nextLabel] == 1 && |
||||
labelToBasicBlock.TryGetValue(nextLabel, out nextBB) && |
||||
body.Contains(nextBB) && |
||||
nextBB.Body.First() == nextLabel && |
||||
!nextBB.Body.OfType<ILTryCatchBlock>().Any() |
||||
) |
||||
{ |
||||
head.Body.RemoveTail(ILCode.Br); |
||||
nextBB.Body.RemoveAt(0); // Remove label
|
||||
head.Body.AddRange(nextBB.Body); |
||||
|
||||
body.RemoveOrThrow(nextBB); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,956 @@
@@ -0,0 +1,956 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Linq; |
||||
using Mono.Cecil; |
||||
|
||||
namespace ICSharpCode.Decompiler.ILAst |
||||
{ |
||||
public class YieldReturnDecompiler |
||||
{ |
||||
// For a description on the code generated by the C# compiler for yield return:
|
||||
// http://csharpindepth.com/Articles/Chapter6/IteratorBlockImplementation.aspx
|
||||
|
||||
// The idea here is:
|
||||
// - Figure out whether the current method is instanciating an enumerator
|
||||
// - Figure out which of the fields is the state field
|
||||
// - Construct an exception table based on states. This allows us to determine, for each state, what the parent try block is.
|
||||
|
||||
/// <summary>
|
||||
/// This exception is thrown when we find something else than we expect from the C# compiler.
|
||||
/// This aborts the analysis and makes the whole transform fail.
|
||||
/// </summary>
|
||||
class YieldAnalysisFailedException : Exception {} |
||||
|
||||
DecompilerContext context; |
||||
TypeDefinition enumeratorType; |
||||
MethodDefinition enumeratorCtor; |
||||
MethodDefinition disposeMethod; |
||||
FieldDefinition stateField; |
||||
FieldDefinition currentField; |
||||
Dictionary<FieldDefinition, ILVariable> fieldToParameterMap = new Dictionary<FieldDefinition, ILVariable>(); |
||||
List<ILNode> newBody; |
||||
|
||||
#region Run() method
|
||||
public static void Run(DecompilerContext context, ILBlock method) |
||||
{ |
||||
if (!context.Settings.YieldReturn) |
||||
return; // abort if enumerator decompilation is disabled
|
||||
var yrd = new YieldReturnDecompiler(); |
||||
yrd.context = context; |
||||
if (!yrd.MatchEnumeratorCreationPattern(method)) |
||||
return; |
||||
yrd.enumeratorType = yrd.enumeratorCtor.DeclaringType; |
||||
#if DEBUG
|
||||
if (Debugger.IsAttached) { |
||||
yrd.Run(); |
||||
} else { |
||||
#endif
|
||||
try { |
||||
yrd.Run(); |
||||
} catch (YieldAnalysisFailedException) { |
||||
return; |
||||
} |
||||
#if DEBUG
|
||||
} |
||||
#endif
|
||||
method.Body.Clear(); |
||||
method.EntryGoto = null; |
||||
method.Body.AddRange(yrd.newBody); |
||||
|
||||
// Repeat the inlining/copy propagation optimization because the conversion of field access
|
||||
// to local variables can open up additional inlining possibilities.
|
||||
ILInlining inlining = new ILInlining(method); |
||||
inlining.InlineAllVariables(); |
||||
inlining.CopyPropagation(); |
||||
} |
||||
|
||||
void Run() |
||||
{ |
||||
AnalyzeCtor(); |
||||
AnalyzeCurrentProperty(); |
||||
ResolveIEnumerableIEnumeratorFieldMapping(); |
||||
ConstructExceptionTable(); |
||||
AnalyzeMoveNext(); |
||||
TranslateFieldsToLocalAccess(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region Match the enumerator creation pattern
|
||||
bool MatchEnumeratorCreationPattern(ILBlock method) |
||||
{ |
||||
if (method.Body.Count == 0) |
||||
return false; |
||||
ILExpression newObj; |
||||
if (method.Body.Count == 1) { |
||||
// ret(newobj(...))
|
||||
if (method.Body[0].Match(ILCode.Ret, out newObj)) |
||||
return MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor); |
||||
else |
||||
return false; |
||||
} |
||||
// stloc(var_1, newobj(..)
|
||||
ILVariable var1; |
||||
if (!method.Body[0].Match(ILCode.Stloc, out var1, out newObj)) |
||||
return false; |
||||
if (!MatchEnumeratorCreationNewObj(newObj, out enumeratorCtor)) |
||||
return false; |
||||
|
||||
int i; |
||||
for (i = 1; i < method.Body.Count; i++) { |
||||
// stfld(..., ldloc(var_1), ldloc(parameter))
|
||||
FieldReference storedField; |
||||
ILExpression ldloc, loadParameter; |
||||
if (!method.Body[i].Match(ILCode.Stfld, out storedField, out ldloc, out loadParameter)) |
||||
break; |
||||
ILVariable loadedVar, loadedArg; |
||||
if (!ldloc.Match(ILCode.Ldloc, out loadedVar) || !loadParameter.Match(ILCode.Ldloc, out loadedArg)) |
||||
return false; |
||||
storedField = GetFieldDefinition(storedField); |
||||
if (loadedVar != var1 || storedField == null || !loadedArg.IsParameter) |
||||
return false; |
||||
fieldToParameterMap[(FieldDefinition)storedField] = loadedArg; |
||||
} |
||||
ILVariable var2; |
||||
ILExpression ldlocForStloc2; |
||||
if (i < method.Body.Count && method.Body[i].Match(ILCode.Stloc, out var2, out ldlocForStloc2)) { |
||||
// stloc(var_2, ldloc(var_1))
|
||||
if (ldlocForStloc2.Code != ILCode.Ldloc || ldlocForStloc2.Operand != var1) |
||||
return false; |
||||
i++; |
||||
} else { |
||||
// the compiler might skip the above instruction in release builds; in that case, it directly returns stloc.Operand
|
||||
var2 = var1; |
||||
} |
||||
ILExpression retArg; |
||||
if (i < method.Body.Count && method.Body[i].Match(ILCode.Ret, out retArg)) { |
||||
// ret(ldloc(var_2))
|
||||
if (retArg.Code == ILCode.Ldloc && retArg.Operand == var2) { |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
static FieldDefinition GetFieldDefinition(FieldReference field) |
||||
{ |
||||
return CecilExtensions.ResolveWithinSameModule(field); |
||||
} |
||||
|
||||
static MethodDefinition GetMethodDefinition(MethodReference method) |
||||
{ |
||||
return CecilExtensions.ResolveWithinSameModule(method); |
||||
} |
||||
|
||||
bool MatchEnumeratorCreationNewObj(ILExpression expr, out MethodDefinition ctor) |
||||
{ |
||||
// newobj(CurrentType/...::.ctor, ldc.i4(-2))
|
||||
ctor = null; |
||||
if (expr.Code != ILCode.Newobj || expr.Arguments.Count != 1) |
||||
return false; |
||||
if (expr.Arguments[0].Code != ILCode.Ldc_I4) |
||||
return false; |
||||
int initialState = (int)expr.Arguments[0].Operand; |
||||
if (!(initialState == -2 || initialState == 0)) |
||||
return false; |
||||
ctor = GetMethodDefinition(expr.Operand as MethodReference); |
||||
if (ctor == null || ctor.DeclaringType.DeclaringType != context.CurrentType) |
||||
return false; |
||||
return IsCompilerGeneratorEnumerator(ctor.DeclaringType); |
||||
} |
||||
|
||||
public static bool IsCompilerGeneratorEnumerator(TypeDefinition type) |
||||
{ |
||||
if (!(type.DeclaringType != null && type.IsCompilerGenerated())) |
||||
return false; |
||||
foreach (TypeReference i in type.Interfaces) { |
||||
if (i.Namespace == "System.Collections" && i.Name == "IEnumerator") |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
#endregion
|
||||
|
||||
#region Figure out what the 'state' field is (analysis of .ctor())
|
||||
/// <summary>
|
||||
/// Looks at the enumerator's ctor and figures out which of the fields holds the state.
|
||||
/// </summary>
|
||||
void AnalyzeCtor() |
||||
{ |
||||
ILBlock method = CreateILAst(enumeratorCtor); |
||||
|
||||
foreach (ILNode node in method.Body) { |
||||
FieldReference field; |
||||
ILExpression instExpr; |
||||
ILExpression stExpr; |
||||
ILVariable arg; |
||||
if (node.Match(ILCode.Stfld, out field, out instExpr, out stExpr) && |
||||
instExpr.MatchThis() && |
||||
stExpr.Match(ILCode.Ldloc, out arg) && |
||||
arg.IsParameter && arg.OriginalParameter.Index == 0) |
||||
{ |
||||
stateField = GetFieldDefinition(field); |
||||
} |
||||
} |
||||
if (stateField == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Creates ILAst for the specified method, optimized up to before the 'YieldReturn' step.
|
||||
/// </summary>
|
||||
ILBlock CreateILAst(MethodDefinition method) |
||||
{ |
||||
if (method == null || !method.HasBody) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
ILBlock ilMethod = new ILBlock(); |
||||
ILAstBuilder astBuilder = new ILAstBuilder(); |
||||
ilMethod.Body = astBuilder.Build(method, true); |
||||
ILAstOptimizer optimizer = new ILAstOptimizer(); |
||||
optimizer.Optimize(context, ilMethod, ILAstOptimizationStep.YieldReturn); |
||||
return ilMethod; |
||||
} |
||||
#endregion
|
||||
|
||||
#region Figure out what the 'current' field is (analysis of get_Current())
|
||||
/// <summary>
|
||||
/// Looks at the enumerator's get_Current method and figures out which of the fields holds the current value.
|
||||
/// </summary>
|
||||
void AnalyzeCurrentProperty() |
||||
{ |
||||
MethodDefinition getCurrentMethod = enumeratorType.Methods.FirstOrDefault( |
||||
m => m.Name.StartsWith("System.Collections.Generic.IEnumerator", StringComparison.Ordinal) |
||||
&& m.Name.EndsWith(".get_Current", StringComparison.Ordinal)); |
||||
ILBlock method = CreateILAst(getCurrentMethod); |
||||
if (method.Body.Count == 1) { |
||||
// release builds directly return the current field
|
||||
ILExpression retExpr; |
||||
FieldReference field; |
||||
ILExpression ldFromObj; |
||||
if (method.Body[0].Match(ILCode.Ret, out retExpr) && |
||||
retExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && |
||||
ldFromObj.MatchThis()) |
||||
{ |
||||
currentField = GetFieldDefinition(field); |
||||
} |
||||
} else if (method.Body.Count == 2) { |
||||
ILVariable v, v2; |
||||
ILExpression stExpr; |
||||
FieldReference field; |
||||
ILExpression ldFromObj; |
||||
ILExpression retExpr; |
||||
if (method.Body[0].Match(ILCode.Stloc, out v, out stExpr) && |
||||
stExpr.Match(ILCode.Ldfld, out field, out ldFromObj) && |
||||
ldFromObj.MatchThis() && |
||||
method.Body[1].Match(ILCode.Ret, out retExpr) && |
||||
retExpr.Match(ILCode.Ldloc, out v2) && |
||||
v == v2) |
||||
{ |
||||
currentField = GetFieldDefinition(field); |
||||
} |
||||
} |
||||
if (currentField == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
#endregion
|
||||
|
||||
#region Figure out the mapping of IEnumerable fields to IEnumerator fields (analysis of GetEnumerator())
|
||||
void ResolveIEnumerableIEnumeratorFieldMapping() |
||||
{ |
||||
MethodDefinition getEnumeratorMethod = enumeratorType.Methods.FirstOrDefault( |
||||
m => m.Name.StartsWith("System.Collections.Generic.IEnumerable", StringComparison.Ordinal) |
||||
&& m.Name.EndsWith(".GetEnumerator", StringComparison.Ordinal)); |
||||
if (getEnumeratorMethod == null) |
||||
return; // no mappings (maybe it's just an IEnumerator implementation?)
|
||||
|
||||
ILBlock method = CreateILAst(getEnumeratorMethod); |
||||
foreach (ILNode node in method.Body) { |
||||
FieldReference stField; |
||||
ILExpression stToObj; |
||||
ILExpression stExpr; |
||||
FieldReference ldField; |
||||
ILExpression ldFromObj; |
||||
if (node.Match(ILCode.Stfld, out stField, out stToObj, out stExpr) && |
||||
stExpr.Match(ILCode.Ldfld, out ldField, out ldFromObj) && |
||||
ldFromObj.MatchThis()) |
||||
{ |
||||
FieldDefinition storedField = GetFieldDefinition(stField); |
||||
FieldDefinition loadedField = GetFieldDefinition(ldField); |
||||
if (storedField != null && loadedField != null) { |
||||
ILVariable mappedParameter; |
||||
if (fieldToParameterMap.TryGetValue(loadedField, out mappedParameter)) |
||||
fieldToParameterMap[storedField] = mappedParameter; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Construction of the exception table (analysis of Dispose())
|
||||
// We construct the exception table by analyzing the enumerator's Dispose() method.
|
||||
|
||||
// Assumption: there are no loops/backward jumps
|
||||
// We 'run' the code, with "state" being a symbolic variable
|
||||
// so it can form expressions like "state + x" (when there's a sub instruction)
|
||||
// For each instruction, we maintain a list of value ranges for state for which the instruction is reachable.
|
||||
// This is (int.MinValue, int.MaxValue) for the first instruction.
|
||||
// These ranges are propagated depending on the conditional jumps performed by the code.
|
||||
|
||||
Dictionary<MethodDefinition, Interval> finallyMethodToStateInterval; |
||||
|
||||
void ConstructExceptionTable() |
||||
{ |
||||
disposeMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "System.IDisposable.Dispose"); |
||||
ILBlock ilMethod = CreateILAst(disposeMethod); |
||||
|
||||
finallyMethodToStateInterval = new Dictionary<MethodDefinition, Interval>(); |
||||
|
||||
InitStateRanges(ilMethod.Body[0]); |
||||
AssignStateRanges(ilMethod.Body, ilMethod.Body.Count, forDispose: true); |
||||
|
||||
// Now look at the finally blocks:
|
||||
foreach (var tryFinally in ilMethod.GetSelfAndChildrenRecursive<ILTryCatchBlock>()) { |
||||
Interval interval = ranges[tryFinally.TryBlock.Body[0]].ToEnclosingInterval(); |
||||
var finallyBody = tryFinally.FinallyBlock.Body; |
||||
if (finallyBody.Count != 2) |
||||
throw new YieldAnalysisFailedException(); |
||||
ILExpression call = finallyBody[0] as ILExpression; |
||||
if (call == null || call.Code != ILCode.Call || call.Arguments.Count != 1) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (!call.Arguments[0].MatchThis()) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (!finallyBody[1].Match(ILCode.Endfinally)) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
MethodDefinition mdef = GetMethodDefinition(call.Operand as MethodReference); |
||||
if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) |
||||
throw new YieldAnalysisFailedException(); |
||||
finallyMethodToStateInterval.Add(mdef, interval); |
||||
} |
||||
ranges = null; |
||||
} |
||||
#endregion
|
||||
|
||||
#region Assign StateRanges / Symbolic Execution (used for analysis of Dispose() and MoveNext())
|
||||
#region struct Interval / class StateRange
|
||||
struct Interval |
||||
{ |
||||
public readonly int Start, End; |
||||
|
||||
public Interval(int start, int end) |
||||
{ |
||||
Debug.Assert(start <= end || (start == 0 && end == -1)); |
||||
this.Start = start; |
||||
this.End = end; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Format("({0} to {1})", Start, End); |
||||
} |
||||
} |
||||
|
||||
class StateRange |
||||
{ |
||||
readonly List<Interval> data = new List<Interval>(); |
||||
|
||||
public StateRange() |
||||
{ |
||||
} |
||||
|
||||
public StateRange(int start, int end) |
||||
{ |
||||
this.data.Add(new Interval(start, end)); |
||||
} |
||||
|
||||
public bool Contains(int val) |
||||
{ |
||||
foreach (Interval v in data) { |
||||
if (v.Start <= val && val <= v.End) |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
public void UnionWith(StateRange other) |
||||
{ |
||||
data.AddRange(other.data); |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Unions this state range with (other intersect (minVal to maxVal))
|
||||
/// </summary>
|
||||
public void UnionWith(StateRange other, int minVal, int maxVal) |
||||
{ |
||||
foreach (Interval v in other.data) { |
||||
int start = Math.Max(v.Start, minVal); |
||||
int end = Math.Min(v.End, maxVal); |
||||
if (start <= end) |
||||
data.Add(new Interval(start, end)); |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Merges overlapping interval ranges.
|
||||
/// </summary>
|
||||
public void Simplify() |
||||
{ |
||||
if (data.Count < 2) |
||||
return; |
||||
data.Sort((a, b) => a.Start.CompareTo(b.Start)); |
||||
Interval prev = data[0]; |
||||
int prevIndex = 0; |
||||
for (int i = 1; i < data.Count; i++) { |
||||
Interval next = data[i]; |
||||
Debug.Assert(prev.Start <= next.Start); |
||||
if (next.Start <= prev.End + 1) { // intervals overlapping or touching
|
||||
prev = new Interval(prev.Start, Math.Max(prev.End, next.End)); |
||||
data[prevIndex] = prev; |
||||
data[i] = new Interval(0, -1); // mark as deleted
|
||||
} else { |
||||
prev = next; |
||||
prevIndex = i; |
||||
} |
||||
} |
||||
data.RemoveAll(i => i.Start > i.End); // remove all entries that were marked as deleted
|
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Join(",", data); |
||||
} |
||||
|
||||
public Interval ToEnclosingInterval() |
||||
{ |
||||
if (data.Count == 0) |
||||
throw new YieldAnalysisFailedException(); |
||||
return new Interval(data[0].Start, data[data.Count - 1].End); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
DefaultDictionary<ILNode, StateRange> ranges; |
||||
ILVariable rangeAnalysisStateVariable; |
||||
|
||||
/// <summary>
|
||||
/// Initializes the state range logic:
|
||||
/// Clears 'ranges' and sets 'ranges[entryPoint]' to the full range (int.MinValue to int.MaxValue)
|
||||
/// </summary>
|
||||
void InitStateRanges(ILNode entryPoint) |
||||
{ |
||||
ranges = new DefaultDictionary<ILNode, StateRange>(n => new StateRange()); |
||||
ranges[entryPoint] = new StateRange(int.MinValue, int.MaxValue); |
||||
rangeAnalysisStateVariable = null; |
||||
} |
||||
|
||||
int AssignStateRanges(List<ILNode> body, int bodyLength, bool forDispose) |
||||
{ |
||||
if (bodyLength == 0) |
||||
return 0; |
||||
for (int i = 0; i < bodyLength; i++) { |
||||
StateRange nodeRange = ranges[body[i]]; |
||||
nodeRange.Simplify(); |
||||
|
||||
ILLabel label = body[i] as ILLabel; |
||||
if (label != null) { |
||||
ranges[body[i + 1]].UnionWith(nodeRange); |
||||
continue; |
||||
} |
||||
|
||||
ILTryCatchBlock tryFinally = body[i] as ILTryCatchBlock; |
||||
if (tryFinally != null) { |
||||
if (!forDispose || tryFinally.CatchBlocks.Count != 0 || tryFinally.FaultBlock != null || tryFinally.FinallyBlock == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
ranges[tryFinally.TryBlock].UnionWith(nodeRange); |
||||
if (tryFinally.TryBlock.Body.Count != 0) { |
||||
ranges[tryFinally.TryBlock.Body[0]].UnionWith(nodeRange); |
||||
AssignStateRanges(tryFinally.TryBlock.Body, tryFinally.TryBlock.Body.Count, forDispose); |
||||
} |
||||
continue; |
||||
} |
||||
|
||||
ILExpression expr = body[i] as ILExpression; |
||||
if (expr == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
switch (expr.Code) { |
||||
case ILCode.Switch: |
||||
{ |
||||
SymbolicValue val = Eval(expr.Arguments[0]); |
||||
if (val.Type != SymbolicValueType.State) |
||||
throw new YieldAnalysisFailedException(); |
||||
ILLabel[] targetLabels = (ILLabel[])expr.Operand; |
||||
for (int j = 0; j < targetLabels.Length; j++) { |
||||
int state = j - val.Constant; |
||||
ranges[targetLabels[j]].UnionWith(nodeRange, state, state); |
||||
} |
||||
StateRange nextRange = ranges[body[i + 1]]; |
||||
nextRange.UnionWith(nodeRange, int.MinValue, -1 - val.Constant); |
||||
nextRange.UnionWith(nodeRange, targetLabels.Length - val.Constant, int.MaxValue); |
||||
break; |
||||
} |
||||
case ILCode.Br: |
||||
case ILCode.Leave: |
||||
ranges[(ILLabel)expr.Operand].UnionWith(nodeRange); |
||||
break; |
||||
case ILCode.Brtrue: |
||||
{ |
||||
SymbolicValue val = Eval(expr.Arguments[0]); |
||||
if (val.Type == SymbolicValueType.StateEquals) { |
||||
ranges[(ILLabel)expr.Operand].UnionWith(nodeRange, val.Constant, val.Constant); |
||||
StateRange nextRange = ranges[body[i + 1]]; |
||||
nextRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); |
||||
nextRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); |
||||
} else if (val.Type == SymbolicValueType.StateInEquals) { |
||||
ranges[body[i + 1]].UnionWith(nodeRange, val.Constant, val.Constant); |
||||
StateRange targetRange = ranges[(ILLabel)expr.Operand]; |
||||
targetRange.UnionWith(nodeRange, int.MinValue, val.Constant - 1); |
||||
targetRange.UnionWith(nodeRange, val.Constant + 1, int.MaxValue); |
||||
} else { |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
break; |
||||
} |
||||
case ILCode.Nop: |
||||
ranges[body[i + 1]].UnionWith(nodeRange); |
||||
break; |
||||
case ILCode.Ret: |
||||
break; |
||||
case ILCode.Stloc: |
||||
{ |
||||
SymbolicValue val = Eval(expr.Arguments[0]); |
||||
if (val.Type == SymbolicValueType.State && val.Constant == 0 && rangeAnalysisStateVariable == null) |
||||
rangeAnalysisStateVariable = (ILVariable)expr.Operand; |
||||
else |
||||
throw new YieldAnalysisFailedException(); |
||||
goto case ILCode.Nop; |
||||
} |
||||
case ILCode.Call: |
||||
// in some cases (e.g. foreach over array) the C# compiler produces a finally method outside of try-finally blocks
|
||||
if (forDispose) { |
||||
MethodDefinition mdef = GetMethodDefinition(expr.Operand as MethodReference); |
||||
if (mdef == null || finallyMethodToStateInterval.ContainsKey(mdef)) |
||||
throw new YieldAnalysisFailedException(); |
||||
finallyMethodToStateInterval.Add(mdef, nodeRange.ToEnclosingInterval()); |
||||
} else { |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
break; |
||||
default: |
||||
if (forDispose) |
||||
throw new YieldAnalysisFailedException(); |
||||
else |
||||
return i; |
||||
} |
||||
} |
||||
return bodyLength; |
||||
} |
||||
|
||||
enum SymbolicValueType |
||||
{ |
||||
/// <summary>
|
||||
/// int: Constant (result of ldc.i4)
|
||||
/// </summary>
|
||||
IntegerConstant, |
||||
/// <summary>
|
||||
/// int: State + Constant
|
||||
/// </summary>
|
||||
State, |
||||
/// <summary>
|
||||
/// This pointer (result of ldarg.0)
|
||||
/// </summary>
|
||||
This, |
||||
/// <summary>
|
||||
/// bool: State == Constant
|
||||
/// </summary>
|
||||
StateEquals, |
||||
/// <summary>
|
||||
/// bool: State != Constant
|
||||
/// </summary>
|
||||
StateInEquals |
||||
} |
||||
|
||||
struct SymbolicValue |
||||
{ |
||||
public readonly int Constant; |
||||
public readonly SymbolicValueType Type; |
||||
|
||||
public SymbolicValue(SymbolicValueType type, int constant = 0) |
||||
{ |
||||
this.Type = type; |
||||
this.Constant = constant; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return string.Format("[SymbolicValue {0}: {1}]", this.Type, this.Constant); |
||||
} |
||||
} |
||||
|
||||
SymbolicValue Eval(ILExpression expr) |
||||
{ |
||||
SymbolicValue left, right; |
||||
switch (expr.Code) { |
||||
case ILCode.Sub: |
||||
left = Eval(expr.Arguments[0]); |
||||
right = Eval(expr.Arguments[1]); |
||||
if (left.Type != SymbolicValueType.State && left.Type != SymbolicValueType.IntegerConstant) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (right.Type != SymbolicValueType.IntegerConstant) |
||||
throw new YieldAnalysisFailedException(); |
||||
return new SymbolicValue(left.Type, unchecked ( left.Constant - right.Constant )); |
||||
case ILCode.Ldfld: |
||||
if (Eval(expr.Arguments[0]).Type != SymbolicValueType.This) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (GetFieldDefinition(expr.Operand as FieldReference) != stateField) |
||||
throw new YieldAnalysisFailedException(); |
||||
return new SymbolicValue(SymbolicValueType.State); |
||||
case ILCode.Ldloc: |
||||
ILVariable loadedVariable = (ILVariable)expr.Operand; |
||||
if (loadedVariable == rangeAnalysisStateVariable) |
||||
return new SymbolicValue(SymbolicValueType.State); |
||||
else if (loadedVariable.IsParameter && loadedVariable.OriginalParameter.Index < 0) |
||||
return new SymbolicValue(SymbolicValueType.This); |
||||
else |
||||
throw new YieldAnalysisFailedException(); |
||||
case ILCode.Ldc_I4: |
||||
return new SymbolicValue(SymbolicValueType.IntegerConstant, (int)expr.Operand); |
||||
case ILCode.Ceq: |
||||
left = Eval(expr.Arguments[0]); |
||||
right = Eval(expr.Arguments[1]); |
||||
if (left.Type != SymbolicValueType.State || right.Type != SymbolicValueType.IntegerConstant) |
||||
throw new YieldAnalysisFailedException(); |
||||
// bool: (state + left.Constant == right.Constant)
|
||||
// bool: (state == right.Constant - left.Constant)
|
||||
return new SymbolicValue(SymbolicValueType.StateEquals, unchecked ( right.Constant - left.Constant )); |
||||
case ILCode.LogicNot: |
||||
SymbolicValue val = Eval(expr.Arguments[0]); |
||||
if (val.Type == SymbolicValueType.StateEquals) |
||||
return new SymbolicValue(SymbolicValueType.StateInEquals, val.Constant); |
||||
else if (val.Type == SymbolicValueType.StateInEquals) |
||||
return new SymbolicValue(SymbolicValueType.StateEquals, val.Constant); |
||||
else |
||||
throw new YieldAnalysisFailedException(); |
||||
default: |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
} |
||||
#endregion
|
||||
|
||||
#region Analysis of MoveNext()
|
||||
ILVariable returnVariable; |
||||
ILLabel returnLabel; |
||||
ILLabel returnFalseLabel; |
||||
|
||||
void AnalyzeMoveNext() |
||||
{ |
||||
MethodDefinition moveNextMethod = enumeratorType.Methods.FirstOrDefault(m => m.Name == "MoveNext"); |
||||
ILBlock ilMethod = CreateILAst(moveNextMethod); |
||||
|
||||
if (ilMethod.Body.Count == 0) |
||||
throw new YieldAnalysisFailedException(); |
||||
ILExpression lastReturnArg; |
||||
if (!ilMethod.Body.Last().Match(ILCode.Ret, out lastReturnArg)) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
// There are two possibilities:
|
||||
if (lastReturnArg.Code == ILCode.Ldloc) { |
||||
// a) the compiler uses a variable for returns (in debug builds, or when there are try-finally blocks)
|
||||
returnVariable = (ILVariable)lastReturnArg.Operand; |
||||
returnLabel = ilMethod.Body.ElementAtOrDefault(ilMethod.Body.Count - 2) as ILLabel; |
||||
if (returnLabel == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
} else { |
||||
// b) the compiler directly returns constants
|
||||
returnVariable = null; |
||||
returnLabel = null; |
||||
// In this case, the last return must return false.
|
||||
if (lastReturnArg.Code != ILCode.Ldc_I4 || (int)lastReturnArg.Operand != 0) |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
|
||||
ILTryCatchBlock tryFaultBlock = ilMethod.Body[0] as ILTryCatchBlock; |
||||
List<ILNode> body; |
||||
int bodyLength; |
||||
if (tryFaultBlock != null) { |
||||
// there are try-finally blocks
|
||||
if (returnVariable == null) // in this case, we must use a return variable
|
||||
throw new YieldAnalysisFailedException(); |
||||
// must be a try-fault block:
|
||||
if (tryFaultBlock.CatchBlocks.Count != 0 || tryFaultBlock.FinallyBlock != null || tryFaultBlock.FaultBlock == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
ILBlock faultBlock = tryFaultBlock.FaultBlock; |
||||
// Ensure the fault block contains the call to Dispose().
|
||||
if (faultBlock.Body.Count != 2) |
||||
throw new YieldAnalysisFailedException(); |
||||
MethodReference disposeMethodRef; |
||||
ILExpression disposeArg; |
||||
if (!faultBlock.Body[0].Match(ILCode.Call, out disposeMethodRef, out disposeArg)) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (GetMethodDefinition(disposeMethodRef) != disposeMethod || !disposeArg.MatchThis()) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (!faultBlock.Body[1].Match(ILCode.Endfinally)) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
body = tryFaultBlock.TryBlock.Body; |
||||
bodyLength = body.Count; |
||||
} else { |
||||
// no try-finally blocks
|
||||
body = ilMethod.Body; |
||||
if (returnVariable == null) |
||||
bodyLength = body.Count - 1; // all except for the return statement
|
||||
else |
||||
bodyLength = body.Count - 2; // all except for the return label and statement
|
||||
} |
||||
|
||||
// Now verify that the last instruction in the body is 'ret(false)'
|
||||
if (returnVariable != null) { |
||||
// If we don't have a return variable, we already verified that above.
|
||||
// If we do have one, check for 'stloc(returnVariable, ldc.i4(0))'
|
||||
|
||||
// Maybe might be a jump to the return label after the stloc:
|
||||
ILExpression leave = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; |
||||
if (leave != null && (leave.Code == ILCode.Br || leave.Code == ILCode.Leave) && leave.Operand == returnLabel) |
||||
bodyLength--; |
||||
ILExpression store0 = body.ElementAtOrDefault(bodyLength - 1) as ILExpression; |
||||
if (store0 == null || store0.Code != ILCode.Stloc || store0.Operand != returnVariable) |
||||
throw new YieldAnalysisFailedException(); |
||||
if (store0.Arguments[0].Code != ILCode.Ldc_I4 || (int)store0.Arguments[0].Operand != 0) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
bodyLength--; // don't conside the stloc instruction to be part of the body
|
||||
} |
||||
// verify that the last element in the body is a label pointing to the 'ret(false)'
|
||||
returnFalseLabel = body.ElementAtOrDefault(bodyLength - 1) as ILLabel; |
||||
if (returnFalseLabel == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
InitStateRanges(body[0]); |
||||
int pos = AssignStateRanges(body, bodyLength, forDispose: false); |
||||
if (pos > 0 && body[pos - 1] is ILLabel) { |
||||
pos--; |
||||
} else { |
||||
// ensure that the first element at body[pos] is a label:
|
||||
ILLabel newLabel = new ILLabel(); |
||||
newLabel.Name = "YieldReturnEntryPoint"; |
||||
ranges[newLabel] = ranges[body[pos]]; // give the label the range of the instruction at body[pos]
|
||||
|
||||
body.Insert(pos, newLabel); |
||||
bodyLength++; |
||||
} |
||||
|
||||
List<KeyValuePair<ILLabel, StateRange>> labels = new List<KeyValuePair<ILLabel, StateRange>>(); |
||||
for (int i = pos; i < bodyLength; i++) { |
||||
ILLabel label = body[i] as ILLabel; |
||||
if (label != null) { |
||||
labels.Add(new KeyValuePair<ILLabel, StateRange>(label, ranges[label])); |
||||
} |
||||
} |
||||
|
||||
ConvertBody(body, pos, bodyLength, labels); |
||||
} |
||||
#endregion
|
||||
|
||||
#region ConvertBody
|
||||
struct SetState |
||||
{ |
||||
public readonly int NewBodyPos; |
||||
public readonly int NewState; |
||||
|
||||
public SetState(int newBodyPos, int newState) |
||||
{ |
||||
this.NewBodyPos = newBodyPos; |
||||
this.NewState = newState; |
||||
} |
||||
} |
||||
|
||||
void ConvertBody(List<ILNode> body, int startPos, int bodyLength, List<KeyValuePair<ILLabel, StateRange>> labels) |
||||
{ |
||||
newBody = new List<ILNode>(); |
||||
newBody.Add(MakeGoTo(labels, 0)); |
||||
List<SetState> stateChanges = new List<SetState>(); |
||||
int currentState = -1; |
||||
// Copy all instructions from the old body to newBody.
|
||||
for (int pos = startPos; pos < bodyLength; pos++) { |
||||
ILExpression expr = body[pos] as ILExpression; |
||||
if (expr != null && expr.Code == ILCode.Stfld && expr.Arguments[0].MatchThis()) { |
||||
// Handle stores to 'state' or 'current'
|
||||
if (GetFieldDefinition(expr.Operand as FieldReference) == stateField) { |
||||
if (expr.Arguments[1].Code != ILCode.Ldc_I4) |
||||
throw new YieldAnalysisFailedException(); |
||||
currentState = (int)expr.Arguments[1].Operand; |
||||
stateChanges.Add(new SetState(newBody.Count, currentState)); |
||||
} else if (GetFieldDefinition(expr.Operand as FieldReference) == currentField) { |
||||
newBody.Add(new ILExpression(ILCode.YieldReturn, null, expr.Arguments[1])); |
||||
} else { |
||||
newBody.Add(body[pos]); |
||||
} |
||||
} else if (returnVariable != null && expr != null && expr.Code == ILCode.Stloc && expr.Operand == returnVariable) { |
||||
// handle store+branch to the returnVariable
|
||||
ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; |
||||
if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnLabel || expr.Arguments[0].Code != ILCode.Ldc_I4) |
||||
throw new YieldAnalysisFailedException(); |
||||
int val = (int)expr.Arguments[0].Operand; |
||||
if (val == 0) { |
||||
newBody.Add(MakeGoTo(returnFalseLabel)); |
||||
} else if (val == 1) { |
||||
newBody.Add(MakeGoTo(labels, currentState)); |
||||
} else { |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
} else if (expr != null && expr.Code == ILCode.Ret) { |
||||
if (expr.Arguments.Count != 1 || expr.Arguments[0].Code != ILCode.Ldc_I4) |
||||
throw new YieldAnalysisFailedException(); |
||||
// handle direct return (e.g. in release builds)
|
||||
int val = (int)expr.Arguments[0].Operand; |
||||
if (val == 0) { |
||||
newBody.Add(MakeGoTo(returnFalseLabel)); |
||||
} else if (val == 1) { |
||||
newBody.Add(MakeGoTo(labels, currentState)); |
||||
} else { |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
} else if (expr != null && expr.Code == ILCode.Call && expr.Arguments.Count == 1 && expr.Arguments[0].MatchThis()) { |
||||
MethodDefinition method = GetMethodDefinition(expr.Operand as MethodReference); |
||||
if (method == null) |
||||
throw new YieldAnalysisFailedException(); |
||||
Interval interval; |
||||
if (method == disposeMethod) { |
||||
// Explicit call to dispose is used for "yield break;" within the method.
|
||||
ILExpression br = body.ElementAtOrDefault(++pos) as ILExpression; |
||||
if (br == null || !(br.Code == ILCode.Br || br.Code == ILCode.Leave) || br.Operand != returnFalseLabel) |
||||
throw new YieldAnalysisFailedException(); |
||||
newBody.Add(MakeGoTo(returnFalseLabel)); |
||||
} else if (finallyMethodToStateInterval.TryGetValue(method, out interval)) { |
||||
// Call to Finally-method
|
||||
int index = stateChanges.FindIndex(ss => ss.NewState >= interval.Start && ss.NewState <= interval.End); |
||||
if (index < 0) |
||||
throw new YieldAnalysisFailedException(); |
||||
|
||||
ILLabel label = new ILLabel(); |
||||
label.Name = "JumpOutOfTryFinally" + interval.Start + "_" + interval.End; |
||||
newBody.Add(new ILExpression(ILCode.Leave, label)); |
||||
|
||||
SetState stateChange = stateChanges[index]; |
||||
// Move all instructions from stateChange.Pos to newBody.Count into a try-block
|
||||
stateChanges.RemoveRange(index, stateChanges.Count - index); // remove all state changes up to the one we found
|
||||
ILTryCatchBlock tryFinally = new ILTryCatchBlock(); |
||||
tryFinally.TryBlock = new ILBlock(newBody.GetRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos)); |
||||
newBody.RemoveRange(stateChange.NewBodyPos, newBody.Count - stateChange.NewBodyPos); // remove all nodes that we just moved into the try block
|
||||
tryFinally.CatchBlocks = new List<ILTryCatchBlock.CatchBlock>(); |
||||
tryFinally.FinallyBlock = ConvertFinallyBlock(method); |
||||
newBody.Add(tryFinally); |
||||
newBody.Add(label); |
||||
} |
||||
} else { |
||||
newBody.Add(body[pos]); |
||||
} |
||||
} |
||||
newBody.Add(new ILExpression(ILCode.YieldBreak, null)); |
||||
} |
||||
|
||||
ILExpression MakeGoTo(ILLabel targetLabel) |
||||
{ |
||||
if (targetLabel == returnFalseLabel) |
||||
return new ILExpression(ILCode.YieldBreak, null); |
||||
else |
||||
return new ILExpression(ILCode.Br, targetLabel); |
||||
} |
||||
|
||||
ILExpression MakeGoTo(List<KeyValuePair<ILLabel, StateRange>> labels, int state) |
||||
{ |
||||
foreach (var pair in labels) { |
||||
if (pair.Value.Contains(state)) |
||||
return MakeGoTo(pair.Key); |
||||
} |
||||
throw new YieldAnalysisFailedException(); |
||||
} |
||||
|
||||
ILBlock ConvertFinallyBlock(MethodDefinition finallyMethod) |
||||
{ |
||||
ILBlock block = CreateILAst(finallyMethod); |
||||
// Get rid of assignment to state
|
||||
FieldReference stfld; |
||||
List<ILExpression> args; |
||||
if (block.Body.Count > 0 && block.Body[0].Match(ILCode.Stfld, out stfld, out args)) { |
||||
if (GetFieldDefinition(stfld) == stateField && args[0].MatchThis()) |
||||
block.Body.RemoveAt(0); |
||||
} |
||||
// Convert ret to endfinally
|
||||
foreach (ILExpression expr in block.GetSelfAndChildrenRecursive<ILExpression>()) { |
||||
if (expr.Code == ILCode.Ret) |
||||
expr.Code = ILCode.Endfinally; |
||||
} |
||||
return block; |
||||
} |
||||
#endregion
|
||||
|
||||
#region TranslateFieldsToLocalAccess
|
||||
void TranslateFieldsToLocalAccess() |
||||
{ |
||||
var fieldToLocalMap = new DefaultDictionary<FieldDefinition, ILVariable>(f => new ILVariable { Name = f.Name, Type = f.FieldType }); |
||||
foreach (ILNode node in newBody) { |
||||
foreach (ILExpression expr in node.GetSelfAndChildrenRecursive<ILExpression>()) { |
||||
FieldDefinition field = GetFieldDefinition(expr.Operand as FieldReference); |
||||
if (field != null) { |
||||
switch (expr.Code) { |
||||
case ILCode.Ldfld: |
||||
if (expr.Arguments[0].MatchThis()) { |
||||
expr.Code = ILCode.Ldloc; |
||||
if (fieldToParameterMap.ContainsKey(field)) { |
||||
expr.Operand = fieldToParameterMap[field]; |
||||
} else { |
||||
expr.Operand = fieldToLocalMap[field]; |
||||
} |
||||
expr.Arguments.Clear(); |
||||
} |
||||
break; |
||||
case ILCode.Stfld: |
||||
if (expr.Arguments[0].MatchThis()) { |
||||
expr.Code = ILCode.Stloc; |
||||
if (fieldToParameterMap.ContainsKey(field)) { |
||||
expr.Operand = fieldToParameterMap[field]; |
||||
} else { |
||||
expr.Operand = fieldToLocalMap[field]; |
||||
} |
||||
expr.Arguments.RemoveAt(0); |
||||
} |
||||
break; |
||||
case ILCode.Ldflda: |
||||
if (expr.Arguments[0].MatchThis()) { |
||||
expr.Code = ILCode.Ldloca; |
||||
if (fieldToParameterMap.ContainsKey(field)) { |
||||
expr.Operand = fieldToParameterMap[field]; |
||||
} else { |
||||
expr.Operand = fieldToLocalMap[field]; |
||||
} |
||||
expr.Arguments.Clear(); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
#endregion
|
||||
} |
||||
} |
@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
public interface ITextOutput |
||||
{ |
||||
int CurrentLine { get; } |
||||
int CurrentColumn { get; } |
||||
|
||||
void Indent(); |
||||
void Unindent(); |
||||
void Write(char ch); |
||||
void Write(string text); |
||||
void WriteLine(); |
||||
void WriteDefinition(string text, object definition); |
||||
void WriteReference(string text, object reference); |
||||
|
||||
void MarkFoldStart(string collapsedText = "...", bool defaultCollapsed = false); |
||||
void MarkFoldEnd(); |
||||
} |
||||
|
||||
public static class TextOutputExtensions |
||||
{ |
||||
public static void Write(this ITextOutput output, string format, params object[] args) |
||||
{ |
||||
output.Write(string.Format(format, args)); |
||||
} |
||||
|
||||
public static void WriteLine(this ITextOutput output, string text) |
||||
{ |
||||
output.Write(text); |
||||
output.WriteLine(); |
||||
} |
||||
|
||||
public static void WriteLine(this ITextOutput output, string format, params object[] args) |
||||
{ |
||||
output.WriteLine(string.Format(format, args)); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
using System; |
||||
using System.IO; |
||||
|
||||
namespace ICSharpCode.Decompiler |
||||
{ |
||||
public sealed class PlainTextOutput : ITextOutput |
||||
{ |
||||
const int TAB_SIZE = 4; |
||||
|
||||
readonly TextWriter writer; |
||||
int indent; |
||||
bool needsIndent; |
||||
int lineNumber = 1; |
||||
int columnNumber = 1; |
||||
|
||||
public PlainTextOutput(TextWriter writer) |
||||
{ |
||||
if (writer == null) |
||||
throw new ArgumentNullException("writer"); |
||||
this.writer = writer; |
||||
} |
||||
|
||||
public PlainTextOutput() |
||||
{ |
||||
this.writer = new StringWriter(); |
||||
} |
||||
|
||||
public int CurrentLine { |
||||
get { return lineNumber; } |
||||
} |
||||
|
||||
public int CurrentColumn { |
||||
get { return columnNumber; } |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return writer.ToString(); |
||||
} |
||||
|
||||
public void Indent() |
||||
{ |
||||
indent++; |
||||
} |
||||
|
||||
public void Unindent() |
||||
{ |
||||
indent--; |
||||
} |
||||
|
||||
void WriteIndent() |
||||
{ |
||||
if (needsIndent) { |
||||
needsIndent = false; |
||||
for (int i = 0; i < indent; i++) { |
||||
writer.Write('\t'); |
||||
columnNumber += TAB_SIZE - 1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public void Write(char ch) |
||||
{ |
||||
WriteIndent(); |
||||
writer.Write(ch); |
||||
columnNumber++; |
||||
} |
||||
|
||||
public void Write(string text) |
||||
{ |
||||
WriteIndent(); |
||||
writer.Write(text); |
||||
columnNumber += text.Length; |
||||
} |
||||
|
||||
public void WriteLine() |
||||
{ |
||||
lineNumber++; |
||||
writer.WriteLine(); |
||||
needsIndent = true; |
||||
columnNumber = TAB_SIZE * indent; |
||||
} |
||||
|
||||
public void WriteDefinition(string text, object definition) |
||||
{ |
||||
Write(text); |
||||
} |
||||
|
||||
public void WriteReference(string text, object reference) |
||||
{ |
||||
Write(text); |
||||
} |
||||
|
||||
void ITextOutput.MarkFoldStart(string collapsedText, bool defaultCollapsed) |
||||
{ |
||||
} |
||||
|
||||
void ITextOutput.MarkFoldEnd() |
||||
{ |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
#region Using directives
|
||||
|
||||
using System; |
||||
using System.Resources; |
||||
using System.Reflection; |
||||
using System.Runtime.InteropServices; |
||||
|
||||
#endregion
|
||||
|
||||
[assembly: AssemblyTitle("ICSharpCode.Decompiler")] |
||||
[assembly: AssemblyDescription("IL decompiler engine")] |
||||
[assembly: AssemblyCompany("ic#code")] |
||||
[assembly: AssemblyProduct("ILSpy")] |
||||
[assembly: AssemblyCopyright("Copyright 2011 AlphaSierraPapa for the SharpDevelop Team")] |
||||
[assembly: AssemblyTrademark("")] |
||||
[assembly: AssemblyCulture("")] |
||||
|
||||
// This sets the default COM visibility of types in the assembly to invisible.
|
||||
// If you need to expose a type to COM, use [ComVisible(true)] on that type.
|
||||
[assembly: ComVisible(false)] |
||||
|
||||
[assembly: AssemblyVersion("2.0.0.1221")] |
||||
[assembly: AssemblyInformationalVersion("2.0.0.1221-26633dc2")] |
||||
[assembly: NeutralResourcesLanguage("en-US")] |
||||
|
||||
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2243:AttributeStringLiteralsShouldParseCorrectly", |
||||
Justification = "AssemblyInformationalVersion does not need to be a parsable version")] |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue