// 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,
}
///
/// 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");
}
[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; }
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);
public override string ToString()
{
var output = new PlainTextOutput();
WriteTo(output);
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 ).
///
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");
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; }
}
}