diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs index 574b6d094..590bd2006 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs @@ -63,6 +63,11 @@ namespace ICSharpCode.Decompiler.IL /// 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) { @@ -89,6 +94,16 @@ namespace ICSharpCode.Decompiler.IL 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 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) { @@ -150,6 +165,10 @@ namespace ICSharpCode.Decompiler.IL /// /// 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); @@ -278,6 +297,10 @@ namespace ICSharpCode.Decompiler.IL /// /// 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); @@ -300,6 +323,10 @@ namespace ICSharpCode.Decompiler.IL 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 { @@ -330,7 +357,8 @@ namespace ICSharpCode.Decompiler.IL /// /// 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). + /// when the ILAst is re-arranged (e.g. within SetChildInstruction), + /// or possibly even more (re-arrangement with stale positions). /// byte refCount; @@ -352,6 +380,11 @@ namespace ICSharpCode.Decompiler.IL /// /// 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 make use of the Parent field so the considerations + /// about orphaned nodes and stale positions don't apply. + /// protected bool IsConnected { get { return refCount > 0; } } @@ -379,21 +412,51 @@ namespace ICSharpCode.Decompiler.IL /// /// 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. + /// Gets the index of this node in the Parent.Children collection. /// - public int ChildIndex { get; internal set; } + /// + /// 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 ). + /// public SlotInfo SlotInfo { get { + Debug.Assert(parent.GetChild(this.ChildIndex) == this); return parent.GetChildSlot(this.ChildIndex); } } @@ -415,6 +478,7 @@ namespace ICSharpCode.Decompiler.IL newValue.parent = this; newValue.ChildIndex = index; } + InvalidateFlags(); if (refCount > 0) { // The new value may be a subtree of the old value. // We first call AddRef(), then ReleaseRef() to prevent the subtree @@ -424,7 +488,6 @@ namespace ICSharpCode.Decompiler.IL if (oldValue != null) oldValue.ReleaseRef(); } - InvalidateFlags(); } /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs b/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs index 4fd6f7b16..019bacc47 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/InstructionCollection.cs @@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.IL /// public int IndexOf(T item) { - // If this collection is the item's primary location, we can use ChildIndex: + // If this collection is the item's primary position, we can use ChildIndex: int index = item.ChildIndex - firstChildIndex; if (index >= 0 && index <= list.Count && list[index] == item) return index; diff --git a/ICSharpCode.Decompiler/IL/SlotInfo.cs b/ICSharpCode.Decompiler/IL/SlotInfo.cs index db7b8cd3c..954f43460 100644 --- a/ICSharpCode.Decompiler/IL/SlotInfo.cs +++ b/ICSharpCode.Decompiler/IL/SlotInfo.cs @@ -1,11 +1,21 @@ -/* - * Created by SharpDevelop. - * User: Daniel - * Date: 2015-05-31 - * Time: 14:26 - * - * To change this template use Tools | Options | Coding | Edit Standard Headers. - */ +// 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; namespace ICSharpCode.Decompiler.IL diff --git a/doc/ILAst.txt b/doc/ILAst.txt index 506814ae4..6578cd12b 100644 --- a/doc/ILAst.txt +++ b/doc/ILAst.txt @@ -1,7 +1,7 @@ The first step ICSharpCode.Decompiler performs to decompile a method is to translate the IL code into the 'ILAst'. -An ILAst node (ILExpression in the code) usually has other nodes as arguments, +An ILAst node (ILInstruction in the code) usually has other nodes as arguments, and performs a computation with the result of those arguments. The evaluation of a node results in either: