// Copyright (c) 2014 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 ICSharpCode.Decompiler.IL.Patterns; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL { internal enum ILPhase { /// /// Reading the individual instructions. /// * Variables don't have scopes yet as the ILFunction is not created yet. /// * Branches point to IL offsets, not blocks. /// InILReader, /// /// The usual invariants are established. /// Normal, /// /// Special phase within the async-await decompiler, where a few selected invariants /// are temporarily suspended. (see Leave.CheckInvariant) /// InAsyncAwait } /// /// Represents a decoded IL instruction /// public abstract partial class ILInstruction { public readonly OpCode OpCode; protected ILInstruction(OpCode opCode) { this.OpCode = opCode; } protected void ValidateChild(ILInstruction inst) { if (inst == null) throw new ArgumentNullException(nameof(inst)); Debug.Assert(!this.IsDescendantOf(inst), "ILAst must form a tree"); // If a call to ReplaceWith() triggers the "ILAst must form a tree" assertion, // make sure to read the remarks on the ReplaceWith() method. } [Conditional("DEBUG")] internal virtual void CheckInvariant(ILPhase phase) { foreach (var child in Children) { Debug.Assert(child.Parent == this); Debug.Assert(this.GetChild(child.ChildIndex) == child); // if child flags are invalid, parent flags must be too // exception: nested ILFunctions (lambdas) Debug.Assert(this is ILFunction || child.flags != invalidFlags || this.flags == invalidFlags); Debug.Assert(child.IsConnected == this.IsConnected); child.CheckInvariant(phase); } Debug.Assert((this.DirectFlags & ~this.Flags) == 0, "All DirectFlags must also appear in this.Flags"); } /// /// Gets whether this node is a descendant of . /// Also returns true if this==. /// /// /// This method uses the Parent property, so it may produce surprising results /// when called on orphaned nodes or with a possibleAncestor that contains stale positions /// (see remarks on Parent property). /// public bool IsDescendantOf(ILInstruction possibleAncestor) { for (ILInstruction ancestor = this; ancestor != null; ancestor = ancestor.Parent) { if (ancestor == possibleAncestor) return true; } return false; } /// /// Gets the stack type of the value produced by this instruction. /// public abstract StackType ResultType { get; } /* Not sure if it's a good idea to offer this on all instructions -- * e.g. ldloc for a local of type `int?` would return StackType.O (because it's not a lifted operation), * even though the underlying type is int = StackType.I4. /// /// Gets the underlying result type of the value produced by this instruction. /// /// If this is a lifted operation, the ResultType will be `StackType.O` (because Nullable{T} is a struct), /// and UnderlyingResultType will be result type of the corresponding non-lifted operation. /// /// If this is not a lifted operation, the underlying result type is equal to the result type. /// public virtual StackType UnderlyingResultType { get => ResultType; } */ internal static StackType CommonResultType(StackType a, StackType b) { if (a == StackType.I || b == StackType.I) return StackType.I; Debug.Assert(a == b); return a; } /// /// Gets whether this node (or any subnode) was modified since the last ResetDirty() call. /// /// /// IsDirty is used by the LoopingTransform, and must not be used by individual transforms within the loop. /// public bool IsDirty { get; private set; } protected void MakeDirty() { for (ILInstruction inst = this; inst != null && !inst.IsDirty; inst = inst.parent) inst.IsDirty = true; } /// /// Marks this node (and all subnodes) as IsDirty=false. /// /// /// IsDirty is used by the LoopingTransform, and must not be used by individual transforms within the loop. /// public void ResetDirty() { foreach (ILInstruction inst in Descendants) inst.IsDirty = false; } const InstructionFlags invalidFlags = (InstructionFlags)(-1); InstructionFlags flags = invalidFlags; /// /// Gets the flags describing the behavior of this instruction. /// This property computes the flags on-demand and caches them /// until some change to the ILAst invalidates the cache. /// /// /// Flag cache invalidation makes use of the Parent property, /// so it is possible for this property to return a stale value /// if the instruction contains "stale positions" (see remarks on Parent property). /// public InstructionFlags Flags { get { if (flags == invalidFlags) { flags = ComputeFlags(); } return flags; } } /// /// Returns whether the instruction (or one of its child instructions) has at least one of the specified flags. /// public bool HasFlag(InstructionFlags flags) { return (this.Flags & flags) != 0; } /// /// Returns whether the instruction (without considering child instructions) has at least one of the specified flags. /// public bool HasDirectFlag(InstructionFlags flags) { return (this.DirectFlags & flags) != 0; } protected void InvalidateFlags() { for (ILInstruction inst = this; inst != null && inst.flags != invalidFlags; inst = inst.parent) inst.flags = invalidFlags; } protected abstract InstructionFlags ComputeFlags(); /// /// Gets the flags for this instruction only, without considering the child instructions. /// public abstract InstructionFlags DirectFlags { get; } /// /// Gets the ILRange for this instruction alone, ignoring the operands. /// public Interval ILRange; public void AddILRange(Interval ilRange) { // TODO: try to combine the two ranges this.ILRange = ilRange; } /// /// Writes the ILAst to the text output. /// public abstract void WriteTo(ITextOutput output, ILAstWritingOptions options); public override string ToString() { var output = new PlainTextOutput(); WriteTo(output, new ILAstWritingOptions()); if (!ILRange.IsEmpty) { output.Write(" at IL_" + ILRange.Start.ToString("x4")); } return output.ToString(); } /// /// Calls the Visit*-method on the visitor corresponding to the concrete type of this instruction. /// public abstract void AcceptVisitor(ILVisitor visitor); /// /// Calls the Visit*-method on the visitor corresponding to the concrete type of this instruction. /// public abstract T AcceptVisitor(ILVisitor visitor); /// /// Calls the Visit*-method on the visitor corresponding to the concrete type of this instruction. /// public abstract T AcceptVisitor(ILVisitor visitor, C context); /// /// Gets the child nodes of this instruction. /// /// /// The ChildrenCollection does not actually store the list of children, /// it merely allows accessing the children stored in the various slots. /// public ChildrenCollection Children { get { return new ChildrenCollection(this); } } protected abstract int GetChildCount(); protected abstract ILInstruction GetChild(int index); protected abstract void SetChild(int index, ILInstruction value); protected abstract SlotInfo GetChildSlot(int index); #region ChildrenCollection + ChildrenEnumerator public struct ChildrenCollection : IReadOnlyList { readonly ILInstruction inst; internal ChildrenCollection(ILInstruction inst) { Debug.Assert(inst != null); this.inst = inst; } public int Count { get { return inst.GetChildCount(); } } public ILInstruction this[int index] { get { return inst.GetChild(index); } } public ChildrenEnumerator GetEnumerator() { return new ChildrenEnumerator(inst); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } #if DEBUG int activeEnumerators; [Conditional("DEBUG")] internal void StartEnumerator() { activeEnumerators++; } [Conditional("DEBUG")] internal void StopEnumerator() { Debug.Assert(activeEnumerators > 0); activeEnumerators--; } #endif [Conditional("DEBUG")] internal void AssertNoEnumerators() { #if DEBUG Debug.Assert(activeEnumerators == 0); #endif } /// /// Enumerator over the children of an ILInstruction. /// Warning: even though this is a struct, it is invalid to copy: /// the number of constructor calls must match the number of dispose calls. /// public struct ChildrenEnumerator : IEnumerator { ILInstruction inst; readonly int end; int pos; internal ChildrenEnumerator(ILInstruction inst) { Debug.Assert(inst != null); this.inst = inst; this.pos = -1; this.end = inst.GetChildCount(); #if DEBUG inst.StartEnumerator(); #endif } public ILInstruction Current { get { return inst.GetChild(pos); } } public bool MoveNext() { return ++pos < end; } public void Dispose() { #if DEBUG if (inst != null) { inst.StopEnumerator(); inst = null; } #endif } object System.Collections.IEnumerator.Current { get { return this.Current; } } void System.Collections.IEnumerator.Reset() { pos = -1; } } #endregion /// /// Replaces this ILInstruction with the given replacement instruction. /// /// /// It is temporarily possible for a node to be used in multiple places in the ILAst, /// this method only replaces this node at its primary position (see remarks on ). /// /// This means you cannot use ReplaceWith() to wrap an instruction in another node. /// For example, node.ReplaceWith(new BitNot(node)) will first call the BitNot constructor, /// which sets node.Parent to the BitNot instance. /// The ReplaceWith() call then attempts to set BitNot.Argument to the BitNot instance, /// which creates a cyclic ILAst. Meanwhile, node's original parent remains unmodified. /// /// The solution in this case is to avoid using ReplaceWith. /// If the parent node is unknown, the following trick can be used: /// /// node.Parent.Children[node.ChildIndex] = new BitNot(node); /// /// Unlike the ReplaceWith() call, this will evaluate node.Parent and node.ChildIndex /// before the BitNot constructor is called, thus modifying the expected position in the ILAst. /// public void ReplaceWith(ILInstruction replacement) { Debug.Assert(parent.GetChild(ChildIndex) == this); if (replacement == this) return; parent.SetChild(ChildIndex, replacement); } /// /// Returns all descendants of the ILInstruction in post-order. /// (including the ILInstruction itself) /// /// /// Within a loop 'foreach (var node in inst.Descendants)', it is illegal to /// add or remove from the child collections of node's ancestors, as those are /// currently being enumerated. /// Note that it is valid to modify node's children as those were already previously visited. /// As a special case, it is also allowed to replace node itself with another node. /// public IEnumerable Descendants { get { // Copy of TreeTraversal.PostOrder() specialized for ChildrenEnumerator // We could potentially eliminate the stack by using Parent/ChildIndex, // but that makes it difficult to reason about the behavior in the cases // where Parent/ChildIndex is not accurate (stale positions), especially // if the ILAst is modified during enumeration. Stack stack = new Stack(); ChildrenEnumerator enumerator = new ChildrenEnumerator(this); try { while (true) { while (enumerator.MoveNext()) { var element = enumerator.Current; stack.Push(enumerator); enumerator = new ChildrenEnumerator(element); } enumerator.Dispose(); if (stack.Count > 0) { enumerator = stack.Pop(); yield return enumerator.Current; } else { break; } } } finally { enumerator.Dispose(); while (stack.Count > 0) { stack.Pop().Dispose(); } } yield return this; } } /// /// Gets the ancestors of this node (including the node itself as first element). /// public IEnumerable Ancestors { get { for (ILInstruction node = this; node != null; node = node.Parent) { yield return node; } } } /// /// Number of parents that refer to this instruction and are connected to the root. /// Usually is 0 for unconnected nodes and 1 for connected nodes, but may temporarily increase to 2 /// when the ILAst is re-arranged (e.g. within SetChildInstruction), /// or possibly even more (re-arrangement with stale positions). /// byte refCount; internal void AddRef() { if (refCount++ == 0) { Connected(); } } internal void ReleaseRef() { Debug.Assert(refCount > 0); if (--refCount == 0) { Disconnected(); } } /// /// Gets whether this ILInstruction is connected to the root node of the ILAst. /// /// /// This property returns true if the ILInstruction is reachable from the root node /// of the ILAst; it does not make use of the Parent field so the considerations /// about orphaned nodes and stale positions don't apply. /// protected internal bool IsConnected { get { return refCount > 0; } } /// /// Called after the ILInstruction was connected to the root node of the ILAst. /// protected virtual void Connected() { foreach (var child in Children) child.AddRef(); } /// /// Called after the ILInstruction was disconnected from the root node of the ILAst. /// protected virtual void Disconnected() { foreach (var child in Children) child.ReleaseRef(); } ILInstruction parent; /// /// Gets the parent of this ILInstruction. /// /// /// It is temporarily possible for a node to be used in multiple places in the ILAst /// (making the ILAst a DAG instead of a tree). /// The Parent and ChildIndex properties are written whenever /// a node is stored in a slot. /// The node's occurrence in that slot is termed the "primary position" of the node, /// and all other (older) uses of the nodes are termed "stale positions". /// /// A consistent ILAst must not contain any stale positions. /// Debug builds of ILSpy check the ILAst for consistency after every IL transform. /// /// If a slot containing a node is overwritten with another node, the Parent /// and ChildIndex of the old node are not modified. /// This allows overwriting stale positions to restore consistency of the ILAst. /// /// If a "primary position" is overwritten, the Parent of the old node also remains unmodified. /// This makes the old node an "orphaned node". /// Orphaned nodes may later be added back to the ILAst (or can just be garbage-collected). /// /// Note that is it is possible (though unusual) for a stale position to reference an orphaned node. /// public ILInstruction Parent { get { return parent; } } /// /// Gets the index of this node in the Parent.Children collection. /// /// /// It is temporarily possible for a node to be used in multiple places in the ILAst, /// this property returns the index of the primary position of this node (see remarks on ). /// public int ChildIndex { get; internal set; } = -1; /// /// Gets information about the slot in which this instruction is stored. /// (i.e., the relation of this instruction to its parent instruction) /// /// /// It is temporarily possible for a node to be used in multiple places in the ILAst, /// this property returns the slot of the primary position of this node (see remarks on ). /// /// Precondition: this node must not be orphaned. /// public SlotInfo SlotInfo { get { Debug.Assert(parent.GetChild(this.ChildIndex) == this); return parent.GetChildSlot(this.ChildIndex); } } /// /// Replaces a child of this ILInstruction. /// /// Reference to the field holding the child /// New child /// Index of the field in the Children collection protected internal void SetChildInstruction(ref ILInstruction childPointer, ILInstruction newValue, int index) { ILInstruction oldValue = childPointer; Debug.Assert(oldValue == GetChild(index)); if (oldValue == newValue) return; childPointer = newValue; if (newValue != null) { newValue.parent = this; newValue.ChildIndex = index; } InvalidateFlags(); MakeDirty(); if (refCount > 0) { // The new value may be a subtree of the old value. // We first call AddRef(), then ReleaseRef() to prevent the subtree // that stays connected from receiving a Disconnected() notification followed by a Connected() notification. if (newValue != null) newValue.AddRef(); if (oldValue != null) oldValue.ReleaseRef(); } } /// /// Called when a new child is added to a InstructionCollection. /// protected internal void InstructionCollectionAdded(ILInstruction newChild) { Debug.Assert(GetChild(newChild.ChildIndex) == newChild); Debug.Assert(!this.IsDescendantOf(newChild), "ILAst must form a tree"); // If a call to ReplaceWith() triggers the "ILAst must form a tree" assertion, // make sure to read the remarks on the ReplaceWith() method. newChild.parent = this; if (refCount > 0) newChild.AddRef(); } /// /// Called when a child is removed from a InstructionCollection. /// protected internal void InstructionCollectionRemoved(ILInstruction oldChild) { if (refCount > 0) oldChild.ReleaseRef(); } /// /// Called when a series of add/remove operations on the InstructionCollection is complete. /// protected internal virtual void InstructionCollectionUpdateComplete() { InvalidateFlags(); MakeDirty(); } /// /// Creates a deep clone of the ILInstruction. /// /// /// It is valid to clone nodes with stale positions (see remarks on Parent); /// the result of such a clone will not contain any stale positions (nodes at /// multiple positions will be cloned once per position). /// public abstract ILInstruction Clone(); /// /// Creates a shallow clone of the ILInstruction. /// /// /// Like MemberwiseClone(), except that the new instruction starts as disconnected. /// protected ILInstruction ShallowClone() { ILInstruction inst = (ILInstruction)MemberwiseClone(); // reset refCount and parent so that the cloned instruction starts as disconnected inst.refCount = 0; inst.parent = null; inst.flags = invalidFlags; #if DEBUG inst.activeEnumerators = 0; #endif return inst; } /// /// Attempts to match the specified node against the pattern. /// /// this: The syntactic pattern. /// The syntax node to test against the pattern. /// /// Returns a match object describing the result of the matching operation. /// Check the property to see whether the match was successful. /// For successful matches, the match object allows retrieving the nodes that were matched with the captured groups. /// public Match Match(ILInstruction node) { Match match = new Match(); match.Success = PerformMatch(node, ref match); return match; } /// /// Attempts matching this instruction against the other instruction. /// /// The instruction to compare with. /// The match object, used to store global state during the match (such as the results of capture groups). /// Returns whether the (partial) match was successful. /// If the method returns true, it adds the capture groups (if any) to the match. /// If the method returns false, the match object may remain in a partially-updated state and /// needs to be restored before it can be reused. protected internal abstract bool PerformMatch(ILInstruction other, ref Match match); /// /// Attempts matching this instruction against a list of other instructions (or a part of said list). /// /// Stores state about the current list match. /// The match object, used to store global state during the match (such as the results of capture groups). /// Returns whether the (partial) match was successful. /// If the method returns true, it updates listMatch.SyntaxIndex to point to the next node that was not part of the match, /// and adds the capture groups (if any) to the match. /// If the method returns false, the listMatch and match objects remain in a partially-updated state and need to be restored /// before they can be reused. protected internal virtual bool PerformMatch(ref ListMatch listMatch, ref Match match) { // Base implementation expects the node to match a single element. // Any patterns matching 0 or more than 1 element must override this method. if (listMatch.SyntaxIndex < listMatch.SyntaxList.Count) { if (PerformMatch(listMatch.SyntaxList[listMatch.SyntaxIndex], ref match)) { listMatch.SyntaxIndex++; return true; } } return false; } } public interface IInstructionWithTypeOperand { IType Type { get; } } public interface IInstructionWithFieldOperand { IField Field { get; } } public interface IInstructionWithMethodOperand { IMethod Method { get; } } public interface ILiftableInstruction { /// /// Gets whether the instruction was lifted; that is, whether is accepts /// potentially nullable arguments. /// bool IsLifted { get; } /// /// If the instruction is lifted and returns a nullable result, /// gets the underlying result type. /// /// Note that not all lifted instructions return a nullable result: /// C# comparisons always return a bool! /// StackType UnderlyingResultType { get; } } public class ILAstWritingOptions { /// /// Sugar for logic.not/and/or. /// public bool UseLogicOperationSugar { get; set; } /// /// Sugar for ldfld/stfld. /// public bool UseFieldSugar { get; set; } } }