#nullable enable // 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.TypeSystem; namespace ICSharpCode.Decompiler.IL { public enum VariableKind { /// /// A local variable. /// Local, /// /// A pinned local variable (not associated with a pinned region) /// PinnedLocal, /// /// A pinned local variable (associated with a pinned region) /// PinnedRegionLocal, /// /// A local variable used as using-resource variable. /// UsingLocal, /// /// A local variable used as foreach variable. /// ForeachLocal, /// /// A local variable used inside an array, collection or /// object initializer block to denote the object being initialized. /// InitializerTarget, /// /// A parameter. /// Parameter, /// /// Variable created for exception handler. /// ExceptionStackSlot, /// /// Local variable used in a catch block. /// ExceptionLocal, /// /// Variable created from stack slot. /// StackSlot, /// /// Variable in BlockKind.CallWithNamedArgs /// NamedArgument, /// /// Local variable that holds the display class used for lambdas within this function. /// DisplayClassLocal, /// /// Local variable declared within a pattern match. /// PatternLocal, /// /// Temporary variable declared in a deconstruction init section. /// DeconstructionInitTemporary, } static class VariableKindExtensions { public static bool IsThis(this ILVariable v) { return v.Kind == VariableKind.Parameter && v.Index < 0; } public static bool IsLocal(this VariableKind kind) { switch (kind) { case VariableKind.Local: case VariableKind.ExceptionLocal: case VariableKind.ForeachLocal: case VariableKind.UsingLocal: case VariableKind.PatternLocal: case VariableKind.PinnedLocal: case VariableKind.PinnedRegionLocal: case VariableKind.DisplayClassLocal: return true; default: return false; } } } [DebuggerDisplay("{Name} : {Type}")] public class ILVariable { VariableKind kind; public VariableKind Kind { get { return kind; } internal set { if (kind == VariableKind.Parameter) throw new InvalidOperationException("Kind=Parameter cannot be changed!"); if (Index != null && value.IsLocal() && !kind.IsLocal()) { // For variables, Index has different meaning than for stack slots, // so we need to reset it to null. // StackSlot -> ForeachLocal can happen sometimes (e.g. PST.TransformForeachOnArray) Index = null; } kind = value; } } public readonly StackType StackType; IType type; public IType Type { get { return type; } internal set { if (value.GetStackType() != StackType) throw new ArgumentException($"Expected stack-type: {StackType} may not be changed. Found: {value.GetStackType()}"); type = value; } } /// /// This variable is either a C# 7 'in' parameter or must be declared as 'ref readonly'. /// public bool IsRefReadOnly { get; internal set; } /// /// The index of the local variable or parameter (depending on Kind) /// /// For VariableKinds with "Local" in the name: /// * if non-null, the Index refers to the LocalVariableSignature. /// * index may be null for variables that used to be fields (captured by lambda/async) /// For Parameters, the Index refers to the method's list of parameters. /// The special "this" parameter has index -1. /// For ExceptionStackSlot, the index is the IL offset of the exception handler. /// For other kinds, the index has no meaning, and is usually null. /// public int? Index { get; private set; } [Conditional("DEBUG")] internal void CheckInvariant() { switch (kind) { case VariableKind.Local: case VariableKind.ForeachLocal: case VariableKind.PatternLocal: case VariableKind.PinnedLocal: case VariableKind.PinnedRegionLocal: case VariableKind.UsingLocal: case VariableKind.ExceptionLocal: case VariableKind.DisplayClassLocal: // in range of LocalVariableSignature Debug.Assert(Index == null || Index >= 0); break; case VariableKind.Parameter: // -1 for the "this" parameter Debug.Assert(Index >= -1); Debug.Assert(Function == null || Index < Function.Parameters.Count); break; case VariableKind.ExceptionStackSlot: Debug.Assert(Index >= 0); break; } } public string? Name { get; set; } public bool HasGeneratedName { get; set; } /// /// Gets the function in which this variable is declared. /// /// /// This property is set automatically when the variable is added to the ILFunction.Variables collection. /// public ILFunction? Function { get; internal set; } /// /// Gets the block container in which this variable is captured. /// For captured variables declared inside the loop, the capture scope is the BlockContainer of the loop. /// For captured variables declared outside of the loop, the capture scope is the BlockContainer of the parent function. /// /// /// This property returns null for variables that are not captured. /// public BlockContainer? CaptureScope { get; internal set; } /// /// Gets the index of this variable within the Function.Variables collection. /// /// /// This property is set automatically when the variable is added to the VariableScope.Variables collection. /// It may change if an item with a lower index is removed from the collection. /// public int IndexInFunction { get; internal set; } /// /// Number of ldloc instructions referencing this variable. /// /// /// This variable is automatically updated when adding/removing ldloc instructions from the ILAst. /// public int LoadCount => LoadInstructions.Count; readonly List loadInstructions = new List(); /// /// List of ldloc instructions referencing this variable. /// /// /// This list is automatically updated when adding/removing ldloc instructions from the ILAst. /// public IReadOnlyList LoadInstructions => loadInstructions; /// /// Number of store instructions referencing this variable, /// plus 1 if HasInitialValue. /// /// Stores are: /// /// stloc /// TryCatchHandler (assigning the exception variable) /// PinnedRegion (assigning the pointer variable) /// initial values () /// /// /// /// This variable is automatically updated when adding/removing stores instructions from the ILAst. /// public int StoreCount => (usesInitialValue ? 1 : 0) + StoreInstructions.Count; readonly List storeInstructions = new List(); /// /// List of store instructions referencing this variable. /// /// Stores are: /// /// stloc /// TryCatchHandler (assigning the exception variable) /// PinnedRegion (assigning the pointer variable) /// initial values () -- however, there is no instruction for /// the initial value, so it is not contained in the store list. /// /// /// /// This list is automatically updated when adding/removing stores instructions from the ILAst. /// public IReadOnlyList StoreInstructions => storeInstructions; /// /// Number of ldloca instructions referencing this variable. /// /// /// This variable is automatically updated when adding/removing ldloca instructions from the ILAst. /// public int AddressCount => AddressInstructions.Count; readonly List addressInstructions = new List(); /// /// List of ldloca instructions referencing this variable. /// /// /// This list is automatically updated when adding/removing ldloca instructions from the ILAst. /// public IReadOnlyList AddressInstructions => addressInstructions; internal void AddLoadInstruction(LdLoc inst) => inst.IndexInLoadInstructionList = AddInstruction(loadInstructions, inst); internal void AddStoreInstruction(IStoreInstruction inst) => inst.IndexInStoreInstructionList = AddInstruction(storeInstructions, inst); internal void AddAddressInstruction(LdLoca inst) => inst.IndexInAddressInstructionList = AddInstruction(addressInstructions, inst); internal void RemoveLoadInstruction(LdLoc inst) => RemoveInstruction(loadInstructions, inst.IndexInLoadInstructionList, inst); internal void RemoveStoreInstruction(IStoreInstruction inst) => RemoveInstruction(storeInstructions, inst.IndexInStoreInstructionList, inst); internal void RemoveAddressInstruction(LdLoca inst) => RemoveInstruction(addressInstructions, inst.IndexInAddressInstructionList, inst); int AddInstruction(List list, T inst) where T : class, IInstructionWithVariableOperand { list.Add(inst); return list.Count - 1; } void RemoveInstruction(List list, int index, T? inst) where T : class, IInstructionWithVariableOperand { Debug.Assert(list[index] == inst); int indexToMove = list.Count - 1; list[index] = list[indexToMove]; list[index].IndexInVariableInstructionMapping = index; list.RemoveAt(indexToMove); } bool initialValueIsInitialized; /// /// Gets/Sets whether the variable's initial value is initialized. /// This is always true for parameters (incl. this). /// /// Normal variables have an initial value if the function uses ".locals init". /// public bool InitialValueIsInitialized { get { return initialValueIsInitialized; } set { if (Kind == VariableKind.Parameter && !value) throw new InvalidOperationException("Cannot remove InitialValueIsInitialized from parameters"); initialValueIsInitialized = value; } } bool usesInitialValue; /// /// Gets/Sets whether the initial value of the variable is used. /// This is always true for parameters (incl. this). /// /// Normal variables use the initial value, if no explicit initialization is done. /// /// /// The following table shows the relationship between /// and . /// /// /// /// /// Meaning /// /// /// /// /// This variable's initial value is zero-initialized (.locals init) and the initial value is used. /// From C#'s point of view a the value default(T) is assigned at the site of declaration. /// /// /// /// /// This variable's initial value is zero-initialized (.locals init) and the initial value is not used. /// From C#'s point of view no implicit initialization occurs, because the code assigns a value /// explicitly, before the variable is first read. /// /// /// /// /// This variable's initial value is uninitialized (.locals without init) and the /// initial value is used. /// From C#'s point of view a call to System.Runtime.CompilerServices.Unsafe.SkipInit(out T) /// is generated after the declaration. /// /// /// /// /// This variable's initial value is uninitialized (.locals without init) and the /// initial value is not used. /// From C#'s point of view no implicit initialization occurs, because the code assigns a value /// explicitly, before the variable is first read. /// /// /// public bool UsesInitialValue { get { return usesInitialValue; } set { if (Kind == VariableKind.Parameter && !value) throw new InvalidOperationException("Cannot remove UsesInitialValue from parameters"); usesInitialValue = value; } } [Obsolete("Use 'UsesInitialValue' instead.")] public bool HasInitialValue { get => UsesInitialValue; set => UsesInitialValue = value; } /// /// Gets whether the variable is in SSA form: /// There is exactly 1 store, and every load sees the value from that store. /// /// /// Note: the single store is not necessary a store instruction, it might also /// be the use of the implicit initial value. /// For example: for parameters, IsSingleDefinition will only return true if /// the parameter is never assigned to within the function. /// public bool IsSingleDefinition { get { return StoreCount == 1 && AddressCount == 0; } } /// /// Gets whether the variable is dead - unused. /// public bool IsDead { get { return StoreInstructions.Count == 0 && LoadCount == 0 && AddressCount == 0; } } /// /// The field which was converted to a local variable. /// Set when the variable is from a 'yield return' or 'async' state machine. /// public IField? StateMachineField; /// /// If enabled, remove dead stores to this variable as if the "Remove dead code" option is enabled. /// internal bool RemoveIfRedundant; private bool hasNullCheck; /// /// Gets/sets whether a parameter has an auto-generated null check, i.e., the !! modifier. /// Returns false for all variables except parameters. /// public bool HasNullCheck { get => hasNullCheck; set { if (Kind != VariableKind.Parameter && value) throw new InvalidOperationException("Cannot set HasNullCheck on local variables!"); hasNullCheck = value; } } public ILVariable(VariableKind kind, IType type, int? index = null) { if (type == null) throw new ArgumentNullException(nameof(type)); this.Kind = kind; this.type = type; this.StackType = type.GetStackType(); this.Index = index; if (kind == VariableKind.Parameter) { this.InitialValueIsInitialized = true; this.UsesInitialValue = true; } CheckInvariant(); } public ILVariable(VariableKind kind, IType type, StackType stackType, int? index = null) { if (type == null) throw new ArgumentNullException(nameof(type)); this.Kind = kind; this.type = type; this.StackType = stackType; this.Index = index; if (kind == VariableKind.Parameter) { this.InitialValueIsInitialized = true; this.UsesInitialValue = true; } CheckInvariant(); } public override string? ToString() { return Name; } internal void WriteDefinitionTo(ITextOutput output) { if (IsRefReadOnly) { output.Write("readonly "); } switch (Kind) { case VariableKind.Local: output.Write("local "); break; case VariableKind.PinnedLocal: output.Write("pinned local "); break; case VariableKind.PinnedRegionLocal: output.Write("PinnedRegion local "); break; case VariableKind.Parameter: output.Write("param "); break; case VariableKind.ExceptionLocal: output.Write("exception local "); break; case VariableKind.ExceptionStackSlot: output.Write("exception stack "); break; case VariableKind.StackSlot: output.Write("stack "); break; case VariableKind.InitializerTarget: output.Write("initializer "); break; case VariableKind.ForeachLocal: output.Write("foreach "); break; case VariableKind.UsingLocal: output.Write("using "); break; case VariableKind.NamedArgument: output.Write("named_arg "); break; case VariableKind.DisplayClassLocal: output.Write("display_class local "); break; case VariableKind.PatternLocal: output.Write("pattern local "); break; case VariableKind.DeconstructionInitTemporary: output.Write("deconstruction init temporary "); break; default: throw new ArgumentOutOfRangeException(); } output.WriteLocalReference(this.Name, this, isDefinition: true); output.Write(" : "); Type.WriteTo(output); output.Write('('); if (Kind == VariableKind.Parameter || Kind == VariableKind.Local || Kind == VariableKind.PinnedLocal || Kind == VariableKind.PinnedRegionLocal) { output.Write("Index={0}, ", Index); } output.Write("LoadCount={0}, AddressCount={1}, StoreCount={2})", LoadCount, AddressCount, StoreCount); if (Kind != VariableKind.Parameter) { if (initialValueIsInitialized) { output.Write(" init"); } else { output.Write(" uninit"); } if (usesInitialValue) { output.Write(" used"); } else { output.Write(" unused"); } } if (CaptureScope != null) { output.Write(" captured in "); output.WriteLocalReference(CaptureScope.EntryPoint?.Label, CaptureScope); } if (StateMachineField != null) { output.Write(" from state-machine"); } } internal void WriteTo(ITextOutput output) { output.WriteLocalReference(this.Name, this); } /// /// Gets whether this variable occurs within the specified instruction. /// internal bool IsUsedWithin(ILInstruction inst) { if (inst is IInstructionWithVariableOperand iwvo && iwvo.Variable == this) { return true; } foreach (var child in inst.Children) { if (IsUsedWithin(child)) return true; } return false; } } public interface IInstructionWithVariableOperand { ILVariable Variable { get; set; } int IndexInVariableInstructionMapping { get; set; } } public interface IStoreInstruction : IInstructionWithVariableOperand { int IndexInStoreInstructionList { get; set; } } interface ILoadInstruction : IInstructionWithVariableOperand { int IndexInLoadInstructionList { get; set; } } interface IAddressInstruction : IInstructionWithVariableOperand { int IndexInAddressInstructionList { get; set; } } public class ILVariableEqualityComparer : IEqualityComparer { public static readonly ILVariableEqualityComparer Instance = new ILVariableEqualityComparer(); public bool Equals(ILVariable? x, ILVariable? y) { if (x == y) return true; if (x == null || y == null) return false; if (x.Kind == VariableKind.StackSlot || y.Kind == VariableKind.StackSlot) return false; if (x.Kind == VariableKind.PatternLocal || y.Kind == VariableKind.PatternLocal) return false; if (!(x.Function == y.Function && x.Kind == y.Kind)) return false; if (x.Index != null) return x.Index == y.Index; else if (x.StateMachineField != null) return x.StateMachineField.Equals(y.StateMachineField); else return false; } public int GetHashCode(ILVariable obj) { if (obj.Kind == VariableKind.StackSlot) return obj.GetHashCode(); return (obj.Function, obj.Kind, obj.Index).GetHashCode(); } } }