.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

420 lines
13 KiB

// Copyright (c) 2010-2013 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.
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// Non-generic base of <see cref="AstNodeCollection{T}"/>, letting the <see cref="AstNode"/> base
/// manipulate a collection slot (remove an element by reference) without knowing its element type.
/// </summary>
public abstract class AstNodeCollection
{
internal abstract void AddNode(AstNode node);
internal abstract void InsertNodeBefore(AstNode? existing, AstNode node);
internal abstract void InsertNodeAfter(AstNode? existing, AstNode node);
internal abstract bool RemoveNode(AstNode node);
// The pattern matcher consumes a collection as an IReadOnlyList<INode>. The view is a separate
// (cached) object rather than the collection itself, because having AstNodeCollection<T>
// implement IEnumerable<INode> as well as IEnumerable<T> would make every LINQ call on a typed
// collection ambiguous.
private protected abstract int NodeCount { get; }
private protected abstract INode NodeAt(int index);
IReadOnlyList<INode>? nodeListView;
internal IReadOnlyList<INode> AsNodeList() => nodeListView ??= new NodeListView(this);
sealed class NodeListView : IReadOnlyList<INode>
{
readonly AstNodeCollection collection;
public NodeListView(AstNodeCollection collection) => this.collection = collection;
public INode this[int index] => collection.NodeAt(index);
public int Count => collection.NodeCount;
public IEnumerator<INode> GetEnumerator()
{
int count = collection.NodeCount;
for (int i = 0; i < count; i++)
yield return collection.NodeAt(i);
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
/// <summary>
/// Represents the children of an <see cref="AstNode"/> that occupy one collection slot.
/// The elements are stored in a list owned by the collection; the parent's flattened child-index
/// space contains them as a contiguous run, renumbered lazily by the parent after a mutation.
/// </summary>
public class AstNodeCollection<T> : AstNodeCollection, ICollection<T>, IReadOnlyList<T>
where T : AstNode
{
readonly AstNode parent;
readonly CSharpSlotInfo kind;
readonly List<T> list = new List<T>();
public AstNodeCollection(AstNode parent, CSharpSlotInfo kind)
{
this.parent = parent ?? throw new ArgumentNullException(nameof(parent));
this.kind = kind;
}
public int Count {
get { return list.Count; }
}
private protected override int NodeCount => list.Count;
private protected override INode NodeAt(int index) => list[index];
public T this[int index] {
get { return list[index]; }
set {
T old = list[index];
if (old == value)
return;
ValidateNewChild(value);
old.ClearParentAndIndex();
list[index] = value;
value.SetParent(parent);
parent.InvalidateChildIndices();
}
}
void ValidateNewChild(T child)
{
if (child == null)
throw new ArgumentNullException(nameof(child));
if (child == parent)
throw new ArgumentException("Cannot add a node to itself as a child.", nameof(child));
if (child.Parent != null)
throw new ArgumentException("Node is already used in another tree.", nameof(child));
}
public void Add(T element)
{
if (element == null)
return;
ValidateNewChild(element);
list.Add(element);
element.SetParent(parent);
parent.InvalidateChildIndices();
}
public void AddRange(IEnumerable<T> nodes)
{
// Evaluate 'nodes' first, since it might change when we add the new children
// Example: collection.AddRange(collection);
if (nodes != null)
{
foreach (T node in nodes is ICollection<T> ? nodes : new List<T>(nodes))
Add(node);
}
}
public void AddRange(T[] nodes)
{
// Fast overload for arrays - we don't need to create a copy
if (nodes != null)
{
foreach (T node in nodes)
Add(node);
}
}
public void ReplaceWith(IEnumerable<T> nodes)
{
// Evaluate 'nodes' first, since it might change when we call Clear()
// Example: collection.ReplaceWith(collection);
List<T>? newNodes = nodes != null ? new List<T>(nodes) : null;
Clear();
if (newNodes != null)
{
foreach (T node in newNodes)
Add(node);
}
}
public void MoveTo(ICollection<T> targetCollection)
{
if (targetCollection == null)
throw new ArgumentNullException(nameof(targetCollection));
foreach (T node in list.ToArray())
{
node.Remove();
targetCollection.Add(node);
}
}
public bool Contains(T element)
{
return element != null && element.Parent == parent && list.Contains(element);
}
public int IndexOf(T element)
{
if (element == null || element.Parent != parent)
return -1;
return list.IndexOf(element);
}
public bool Remove(T element)
{
int index = IndexOf(element);
if (index < 0)
return false;
list.RemoveAt(index);
element.ClearParentAndIndex();
parent.InvalidateChildIndices();
return true;
}
internal override void AddNode(AstNode node)
{
Add((T)node);
}
internal override void InsertNodeBefore(AstNode? existing, AstNode node)
{
// 'existing' may belong to a different slot (e.g. the node after the last collection
// element); in that case it is not found here and the new node is appended.
if (existing is T e && IndexOf(e) >= 0)
InsertBefore(e, (T)node);
else
Add((T)node);
}
internal override void InsertNodeAfter(AstNode? existing, AstNode node)
{
if (existing is T e && IndexOf(e) >= 0)
InsertAfter(e, (T)node);
else
Insert(0, (T)node);
}
internal override bool RemoveNode(AstNode node)
{
return node is T typed && Remove(typed);
}
public void CopyTo(T[] array, int arrayIndex)
{
list.CopyTo(array, arrayIndex);
}
public void Clear()
{
foreach (T item in list)
item.ClearParentAndIndex();
list.Clear();
parent.InvalidateChildIndices();
}
public IEnumerable<T> Detach()
{
T[] items = list.ToArray();
Clear();
return items;
}
/// <summary>
/// Returns the first element for which the predicate returns true,
/// or null if no such object is found.
/// </summary>
public T FirstOrNull(Func<T, bool>? predicate = null)
{
foreach (T item in list)
if (predicate == null || predicate(item))
return item;
return null!;
}
/// <summary>
/// Returns the last element for which the predicate returns true,
/// or null if no such object is found.
/// </summary>
public T LastOrNull(Func<T, bool>? predicate = null)
{
T result = null!;
foreach (T item in list)
if (predicate == null || predicate(item))
result = item;
return result;
}
bool ICollection<T>.IsReadOnly {
get { return false; }
}
// Returned by value, so a foreach over a concrete collection allocates nothing; only enumeration
// through the IEnumerable<T> interface (e.g. LINQ) boxes it.
public Enumerator GetEnumerator() => new Enumerator(list);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(list);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(list);
/// <summary>
/// Enumerates the collection while tolerating removal or replacement of the current element during
/// the loop body -- the behavior transforms that mutate a collection mid-foreach rely on. Before
/// yielding an element it captures the following one, then resumes on that captured successor
/// located by identity, so the walk follows node identity hand-over-hand like the old linked-list
/// enumerator: removing or replacing the current element advances to the captured successor (a
/// replacement is not re-visited), and inserting or removing other elements only shifts the
/// backing-list indices, which the identity resume absorbs. The one mutation it cannot follow is
/// removing the captured successor itself during the body -- that element is gone, so enumeration
/// stops there (the linked-list enumerator lost track of it too).
/// </summary>
public struct Enumerator : IEnumerator<T>
{
readonly List<T> list;
int pos; // index at which 'current' was found when it was yielded
T? current; // the element handed out by the last MoveNext
T? next; // the element that followed 'current' at the moment it was yielded
bool started;
internal Enumerator(List<T> list)
{
this.list = list;
this.pos = 0;
this.current = null;
this.next = null;
this.started = false;
}
public readonly T Current => current!;
readonly object? IEnumerator.Current => current;
public bool MoveNext()
{
if (!started)
{
started = true;
if (list.Count == 0)
return false;
pos = 0;
}
else
{
if (next == null)
{
current = null;
return false;
}
// Resume on the successor captured before the body ran, located by identity. The two
// fast paths cover the common shapes -- the successor still sits at pos+1, or 'current'
// was removed so the successor dropped into pos -- and any other mutation falls back to
// an O(n) identity search. Each fast path is guarded by ReferenceEquals(next), so it can
// only fire when the successor genuinely sits there; landing the cursor anywhere else
// would be the only way to skip or re-yield an element.
int resume;
if (pos + 1 < list.Count && ReferenceEquals(list[pos + 1], next))
resume = pos + 1;
else if (pos < list.Count && ReferenceEquals(list[pos], next))
resume = pos;
else
resume = list.IndexOf(next);
// Runtime guard against the cursor mislocating: the chosen index must equal the
// authoritative identity position (also -1 == -1 when the successor was removed). It
// holds for every mutation the body can make today, and the decompiler test suite runs
// it after every transform, so a future change that lets the cursor skip or re-yield an
// element fails loudly here instead of silently corrupting the output.
Debug.Assert(resume == list.IndexOf(next),
"AstNodeCollection enumerator lost track of its position during a mid-enumeration mutation.");
if (resume < 0)
{
current = null;
next = null;
return false;
}
pos = resume;
}
current = list[pos];
next = pos + 1 < list.Count ? list[pos + 1] : null;
return true;
}
public void Reset()
{
pos = 0;
current = null;
next = null;
started = false;
}
public readonly void Dispose() { }
}
#region Equals and GetHashCode implementation
public override int GetHashCode()
{
return parent.GetHashCode() ^ kind.GetHashCode();
}
public override bool Equals(object? obj)
{
return obj is AstNodeCollection<T> other && this.parent == other.parent && this.kind == other.kind;
}
#endregion
internal bool DoMatch(AstNodeCollection<T> other, Match match)
{
// Both collections are already the per-slot child lists, so the matcher walks them by index.
return Pattern.DoMatchCollection(AsNodeList(), other.AsNodeList(), match);
}
public void InsertAfter(T? existingItem, T newItem)
{
// A null existingItem yields IndexOf == -1, so the new item is inserted at the front.
Insert(IndexOf(existingItem!) + 1, newItem);
}
public void InsertBefore(T? existingItem, T newItem)
{
// A null existingItem yields IndexOf == -1, so the new item is appended.
int index = IndexOf(existingItem!);
Insert(index < 0 ? list.Count : index, newItem);
}
void Insert(int index, T newItem)
{
if (newItem == null)
return;
ValidateNewChild(newItem);
list.Insert(index, newItem);
newItem.SetParent(parent);
parent.InvalidateChildIndices();
}
/// <summary>
/// Applies the <paramref name="visitor"/> to all nodes in this collection.
/// </summary>
public void AcceptVisitor(IAstVisitor visitor)
{
foreach (T item in this)
item.AcceptVisitor(visitor);
}
}
}