diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 5ab7f756f..328c100b4 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -50,6 +50,7 @@ namespace ICSharpCode.Decompiler.CSharp new ILInlining(), // temporary pass, just to make the ILAst easier to read while debugging loop detection new LoopDetection(), new IntroduceExitPoints(), + new SplitVariables(), new ConditionDetection(), new ILInlining(), new CopyPropagation(), diff --git a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs index 47df34d49..b39ecc3a3 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/ReachingDefinitions.cs @@ -17,6 +17,12 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.IL; namespace ICSharpCode.Decompiler.FlowAnalysis { @@ -24,8 +30,523 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// Implements the "reaching definitions" analysis. /// /// https://en.wikipedia.org/wiki/Reaching_definition + /// + /// By "definitions", we mean stores to local variables. /// + /// + /// Possible "definitions" that store to a variable are: + /// * StLoc + /// * TryCatchHandler (for the exception variable) + /// * ReachingDefinitions.UninitializedVariable for uninitialized variables. + /// Note that we do not keep track of LdLoca/references/pointers. + /// The analysis will likely be wrong/incomplete for variables with AddressCount != 0. + /// public class ReachingDefinitions { + #region Documentation + member fields + /// + /// A special Nop instruction that gets used as a fake store if the variable + /// is possibly uninitialized. + /// + public static readonly ILInstruction UninitializedVariable = new Nop(); + + // The reaching definition analysis tracks a 'state'. + // This 'state' represents the reaching definitions at a given source code position. + // There are many states (one per source code position, i.e. ILInstruction), + // but we don't store all of them. + // We only keep track of: + // a) the current state in the RDVisitor + // This state corresponds to the instruction currently being visited, + // and gets mutated as we traverse the ILAst. + // b) the input state for each control flow node + // This also gets mutated as the analysis learns about new control flow edges. + // + // A state can either be reachable, or unreachable: + // 1) unreachable + // Note that during the analysis, "unreachable" just means we have not yet found a path + // from the entry point to the node. States transition from unreachable to reachable as + // the analysis processes more control flow paths. + // 2) reachable + // In this case, the state contains, for each variable, the set of stores that might have + // written to the variable before the control flow reached the state's source code position. + // This set does not include stores that were definitely overwritten by other stores to the + // same variable. + // During the analysis, the set of stores gets extended as the analysis processes more code paths. + // For loops, we need to analyze the loop body before we can know the state for the loop backedge, + // but we need to know the input state for the loop body (to which the backedge state contributes) + // before we can analyze the loop body. + // Solution: we repeat the analysis of the loop body multiple times, until the state no longer changes. + // Because all state changes are irreversible (we only add to the set of stores, we never remove from it), + // this algorithm will eventually terminate. + // To make it terminate reasonably quickly, we need to process the control flow nodes in the correct order: + // reverse post-order. This class assumes the blocks in each block container are already sorted appropriately. + // The caller can use BlockContainer.SortBlocks() for this. + + // The state as described above could be represented as a `Dictionary>`. + // To consume less memory, we instead assign an integer index to all stores in the analyzed function ("store index"), + // and store the state as a `BitSet` instead. + // Each bit in the set corresponds to one store instruction, and is `true` iff the store is a reaching definition + // for the variable it is storing to. + // The `allStores` array has the same length as the bit sets and holds the corresponding `ILInstruction` objects (store instructions). + // All stores for a single variable occupy a contiguous segment of the `allStores` array (and thus also of the `state`). + + /// + /// To distinguish unreachable from reachable states, we use the first bit in the bitset to store the 'reachable bit'. + /// If this bit is set, the state is reachable, and the remaining bits + /// + const int ReachableBit = 0; + + /// + /// Because bit number 0 is the ReachableBit, we start counting store indices at 1. + /// + const int FirstStoreIndex = 1; + + /// + /// The function being analyzed. + /// + readonly ILVariableScope scope; + + /// + /// All stores for all variables in the scope. + /// + /// state[storeIndex] is true iff allStores[storeIndex] is a reaching definition. + /// Invariant: state.Length == allStores.Length. + /// + readonly ILInstruction[] allStores; + + /// + /// Maps instructions appearing in allStores to their index. + /// + /// Invariant: allStores[storeIndexMap[inst]] == inst + /// + /// Does not contain UninitializedVariable (as that special instruction has multiple store indices, one per variable) + /// + readonly Dictionary storeIndexMap = new Dictionary(); + + /// + /// For all variables v: allStores[firstStoreIndexForVariable[v.IndexInScope]] is the UninitializedVariable entry for v. + /// The next few stores (up to firstStoreIndexForVariable[v.IndexInScope + 1], exclusive) are the full list of stores for v. + /// + /// + /// Invariant: firstStoreIndexForVariable[scope.Variables.Count] == allStores.Length + /// + readonly int[] firstStoreIndexForVariable; + + /// + /// activeVariable[v.IndexInScope] is true iff RD analysis is enabled for the variable. + /// + readonly BitSet activeVariables; + + /// + /// Holds the state for incoming branches. + /// + /// + /// Only used for blocks in block containers; not for inline blocks. + /// + readonly Dictionary stateOnBranch = new Dictionary(); + + /// + /// Holds the state at the block container end-point. (=state for incoming 'leave' instructions) + /// + readonly Dictionary stateOnLeave = new Dictionary(); + #endregion + + #region Constructor + /// + /// Run reaching definitions analysis for the specified variable scope. + /// + public ReachingDefinitions(ILVariableScope scope, Predicate pred) + : this(scope, GetActiveVariableBitSet(scope, pred)) + { + } + + static BitSet GetActiveVariableBitSet(ILVariableScope scope, Predicate pred) + { + if (scope == null) + throw new ArgumentNullException("scope"); + BitSet activeVariables = new BitSet(scope.Variables.Count); + for (int vi = 0; vi < scope.Variables.Count; vi++) { + activeVariables[vi] = pred(scope.Variables[vi]); + } + return activeVariables; + } + + /// + /// Run reaching definitions analysis for the specified variable scope. + /// + public ReachingDefinitions(ILVariableScope scope, BitSet activeVariables) + { + if (scope == null) + throw new ArgumentNullException("scope"); + if (activeVariables == null) + throw new ArgumentNullException("activeVariables"); + this.scope = scope; + this.activeVariables = activeVariables; + + // Fill `allStores` and `storeIndexMap` and `firstStoreIndexForVariable`. + var storesByVar = FindAllStoresByVariable(scope, activeVariables); + allStores = new ILInstruction[FirstStoreIndex + storesByVar.Sum(l => l != null ? l.Count : 0)]; + firstStoreIndexForVariable = new int[scope.Variables.Count + 1]; + int si = FirstStoreIndex; + for (int vi = 0; vi < storesByVar.Length; vi++) { + firstStoreIndexForVariable[vi] = si; + var stores = storesByVar[vi]; + if (stores != null) { + int expectedStoreCount = scope.Variables[vi].StoreCount; + if (scope.Variables[vi].Kind != VariableKind.Parameter && scope.Variables[vi].Kind != VariableKind.This) { + // Extra store for UninitializedVariable + expectedStoreCount += 1; + // Note that for VariableKind.Parameter/This, this extra store + // is already accounted for in ILVariable.StoreCount. + } + Debug.Assert(stores.Count == expectedStoreCount); + stores.CopyTo(allStores, si); + // Add all stores except for UninitializedVariable to storeIndexMap. + for (int i = 1; i < stores.Count; i++) { + storeIndexMap.Add(stores[i], si + i); + } + si += stores.Count; + } + } + firstStoreIndexForVariable[scope.Variables.Count] = si; + Debug.Assert(si == allStores.Length); + + this.workList = CreateWorkLists(scope); + InitStateDictionaries(); + RDVisitor visitor = new RDVisitor(this); + scope.Children.Single().AcceptVisitor(visitor); + } + + /// + /// Fill allStores and storeIndexMap. + /// + static List[] FindAllStoresByVariable(ILVariableScope scope, BitSet activeVariables) + { + // For each variable, find the list of ILInstructions storing to that variable + List[] storesByVar = new List[scope.Variables.Count]; + for (int vi = 0; vi < storesByVar.Length; vi++) { + if (activeVariables[vi]) + storesByVar[vi] = new List { UninitializedVariable }; + } + foreach (var inst in scope.Descendants) { + ILVariable v; + if (inst.MatchStLoc(out v) || inst.MatchTryCatchHandler(out v)) { + if (v.Scope == scope && activeVariables[v.IndexInScope]) { + storesByVar[v.IndexInScope].Add(inst); + } + } + } + return storesByVar; + } + #endregion + + #region State Management + /// + /// Create the initial state (reachable + all variables uninitialized). + /// + BitSet CreateInitialState() + { + BitSet initialState = new BitSet(allStores.Length); + initialState.Set(ReachableBit); + for (int vi = 0; vi < scope.Variables.Count; vi++) { + if (activeVariables[vi]) { + Debug.Assert(allStores[firstStoreIndexForVariable[vi]] == UninitializedVariable); + initialState.Set(firstStoreIndexForVariable[vi]); + } + } + return initialState; + } + + BitSet CreateUnreachableState() + { + return new BitSet(allStores.Length); + } + + void InitStateDictionaries() + { + foreach (var container in scope.Descendants.OfType()) { + foreach (var block in container.Blocks) { + stateOnBranch.Add(block, CreateUnreachableState()); + } + stateOnLeave.Add(container, CreateUnreachableState()); + } + } + + /// + /// Merge incomingState into state. + /// + /// + /// Returns true if state was modified. + /// + static bool MergeState(BitSet state, BitSet incomingState) + { + if (!incomingState[ReachableBit]) { + // MergeState() is a no-op if the incoming state is unreachable + return false; + } + if (state[ReachableBit]) { + // both reachable: state |= incomingState; + if (state.IsSupersetOf(incomingState)) { + // UnionWith() wouldn't actually change the state, + // so we have to return false + return false; + } + state.UnionWith(incomingState); + } else { + state.ReplaceWith(incomingState); + } + return true; + } + #endregion + + #region Worklist + /// + /// For each block container, stores the set of blocks (via Block.ChildIndex) that had their incoming state + /// changed and were not processed yet. + /// + readonly Dictionary> workList; + + static Dictionary> CreateWorkLists(ILVariableScope scope) + { + var worklists = new Dictionary>(); + foreach (var container in scope.Descendants.OfType()) { + worklists.Add(container, new SortedSet()); + } + return worklists; + } + + /// + /// The work list keeps track of which blocks had their incoming state updated, + /// but did not run yet. + /// + void AddToWorkList(Block block) + { + BlockContainer container = (BlockContainer)block.Parent; + workList[container].Add(block.ChildIndex); + } + #endregion + + /// + /// Visitor that traverses the ILInstruction tree. + /// + class RDVisitor : ILVisitor + { + readonly ReachingDefinitions rd; + + internal RDVisitor(ReachingDefinitions rd) + { + this.rd = rd; + this.state = rd.CreateInitialState(); + this.stateOnException = rd.CreateUnreachableState(); + } + + /// + /// Combines state of all possible exceptional control flow paths in the current try block. + /// + BitSet stateOnException; + + /// + /// Current state. + /// Gets mutated as the visitor traverses the ILAst. + /// + BitSet state; + + protected override void Default(ILInstruction inst) + { + // This method assumes normal control flow and no branches. + if ((inst.DirectFlags & (InstructionFlags.ControlFlow | InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable)) != 0) { + throw new NotImplementedException("RDVisitor is missing implementation for " + inst.GetType().Name); + } + + // Since this instruction has normal control flow, we can evaluate our children left-to-right. + foreach (var child in inst.Children) { + child.AcceptVisitor(this); + Debug.Assert(!(state[ReachableBit] && child.HasFlag(InstructionFlags.EndPointUnreachable))); + } + + // If this instruction can throw an exception, handle the exceptional control flow edge. + if ((inst.DirectFlags & InstructionFlags.MayThrow) != 0) { + MayThrow(); + } + } + + /// + /// Handle control flow when `throwInst` throws an exception. + /// + void MayThrow() + { + MergeState(stateOnException, state); + } + + void MarkUnreachable() + { + state.Clear(ReachableBit); + } + + protected internal override void VisitStLoc(StLoc inst) + { + Default(inst); + ILVariable v = inst.Variable; + if (v.Scope == rd.scope && rd.activeVariables[v.IndexInScope]) { + // Clear the set of stores for this variable: + state.Clear(rd.firstStoreIndexForVariable[v.IndexInScope], + rd.firstStoreIndexForVariable[v.IndexInScope + 1]); + // And replace it with this store: + state.Set(rd.storeIndexMap[inst]); + } + } + + protected internal override void VisitBlockContainer(BlockContainer container) + { + SortedSet worklist = rd.workList[container]; + if (MergeState(rd.stateOnBranch[container.EntryPoint], state)) { + worklist.Add(0); // add container entry point to work list + } + // Because we use a SortedSet for the work list, + // we always process the blocks in the same order as they are in the container + // (usually reverse post-order). + while (worklist.Count > 0) { + int blockIndex = worklist.Min; + worklist.Remove(blockIndex); + Block block = container.Blocks[blockIndex]; + state.ReplaceWith(rd.stateOnBranch[block]); + block.AcceptVisitor(this); + } + state.ReplaceWith(rd.stateOnLeave[container]); + } + + protected internal override void VisitBranch(Branch inst) + { + if (MergeState(rd.stateOnBranch[inst.TargetBlock], state)) { + rd.AddToWorkList(inst.TargetBlock); + } + MarkUnreachable(); + } + + protected internal override void VisitLeave(Leave inst) + { + Debug.Assert(inst.IsDescendantOf(inst.TargetContainer)); + MergeState(rd.stateOnLeave[inst.TargetContainer], state); + // Note: We don't have to put the block container onto the work queue, + // because it's an ancestor of the Leave instruction, and hence + // we are currently somewhere within the VisitBlockContainer() call. + MarkUnreachable(); + } + + protected internal override void VisitReturn(Return inst) + { + if (inst.ReturnValue != null) + inst.ReturnValue.AcceptVisitor(this); + MarkUnreachable(); + } + + protected internal override void VisitThrow(Throw inst) + { + inst.Argument.AcceptVisitor(this); + MayThrow(); + MarkUnreachable(); + } + + protected internal override void VisitRethrow(Rethrow inst) + { + MayThrow(); + MarkUnreachable(); + } + + /// + /// Visits the TryBlock. + /// + /// Returns a new BitSet representing the state of exceptional control flow transfer + /// out of the try block. + /// + BitSet HandleTryBlock(TryInstruction inst) + { + BitSet oldStateOnException = stateOnException; + BitSet newStateOnException = rd.CreateUnreachableState(); + + stateOnException = newStateOnException; + inst.TryBlock.AcceptVisitor(this); + stateOnException = oldStateOnException; + + return newStateOnException; + } + + protected internal override void VisitTryCatch(TryCatch inst) + { + BitSet caughtState = HandleTryBlock(inst); + BitSet endpointState = state.Clone(); + // The exception might get propagated if no handler matches the type: + MergeState(stateOnException, caughtState); + foreach (var handler in inst.Handlers) { + state.ReplaceWith(caughtState); + handler.Filter.AcceptVisitor(this); + // if the filter return false, any mutations done by the filter + // will be visible by the remaining handlers + // (but it's also possible that the filter didn't get executed at all + // because the exception type doesn't match) + MergeState(caughtState, state); + + handler.Body.AcceptVisitor(this); + MergeState(endpointState, state); + } + state = endpointState; + } + + protected internal override void VisitTryFinally(TryFinally inst) + { + // I don't think there's a good way to track dataflow across finally blocks + // without duplicating the whole finally block. + // We'll just approximate 'try { .. } finally { .. }' as 'try { .. } catch {} .. if (?) rethrow; }' + BitSet caughtState = HandleTryBlock(inst); + MergeState(state, caughtState); + inst.FinallyBlock.AcceptVisitor(this); + MayThrow(); + // Our approximation allows the impossible code path where the try block wasn't fully executed + // and the finally block did not rethrow the exception. + // This can cause us to not be marked unreachable in cases where the simple InstructionFlags + // know the path is unreachable -- so use the flag to fix the reachable bit. + if (inst.HasFlag(InstructionFlags.EndPointUnreachable)) { + MarkUnreachable(); + } + } + + protected internal override void VisitTryFault(TryFault inst) + { + // try-fault executes fault block if an exception occurs in try, + // and always rethrows the exception at the end. + BitSet caughtState = HandleTryBlock(inst); + BitSet noException = state; + state = caughtState; + inst.FaultBlock.AcceptVisitor(this); + MayThrow(); // rethrow the exception after the fault block + + // try-fault exits normally only if no exception occurred + state = noException; + } + + protected internal override void VisitIfInstruction(IfInstruction inst) + { + inst.Condition.AcceptVisitor(this); + BitSet branchState = state.Clone(); + inst.TrueInst.AcceptVisitor(this); + BitSet afterTrueState = state; + state = branchState; + inst.FalseInst.AcceptVisitor(this); + MergeState(state, afterTrueState); + } + + protected internal override void VisitSwitchInstruction(SwitchInstruction inst) + { + inst.Value.AcceptVisitor(this); + BitSet beforeSections = state.Clone(); + BitSet afterSections = rd.CreateUnreachableState(); + foreach (var section in inst.Sections) { + state.ReplaceWith(beforeSections); + section.AcceptVisitor(this); + MergeState(afterSections, state); + } + state = afterSections; + } + } } } diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index f8d1b69f2..053ab934e 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -123,6 +123,7 @@ + @@ -155,6 +156,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/InstructionFlags.cs b/ICSharpCode.Decompiler/IL/InstructionFlags.cs index 2f2a1abc5..b269dfad2 100644 --- a/ICSharpCode.Decompiler/IL/InstructionFlags.cs +++ b/ICSharpCode.Decompiler/IL/InstructionFlags.cs @@ -66,5 +66,16 @@ namespace ICSharpCode.Decompiler.IL /// (unless the instruction represents an infinite loop). /// EndPointUnreachable = 0x400, + /// + /// The instruction contains some kind of internal control flow. + /// + /// + /// If this flag is not set, the all descendants of the instruction are fully evaluated (modulo MayThrow/MayBranch) + /// in left-to-right pre-order. + /// + /// Note that branch instructions don't have this flag set, because their control flow is not internal + /// (and they don't have any unusual argument evaluation rules). + /// + ControlFlow = 0x800, } } diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index 9b64b1bc6..f8eb1f4c5 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -219,6 +219,15 @@ namespace ICSharpCode.Decompiler.IL var clone = (SimpleInstruction)ShallowClone(); return clone; } + protected override InstructionFlags ComputeFlags() + { + return InstructionFlags.None; + } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } + } } /// Instruction with a single argument @@ -277,7 +286,12 @@ namespace ICSharpCode.Decompiler.IL } protected override InstructionFlags ComputeFlags() { - return argument.Flags; + return argument.Flags | InstructionFlags.None; + } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } } public override void WriteTo(ITextOutput output) { @@ -362,7 +376,12 @@ namespace ICSharpCode.Decompiler.IL } protected override InstructionFlags ComputeFlags() { - return left.Flags | right.Flags; + return left.Flags | right.Flags | InstructionFlags.None; + } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } } public override void WriteTo(ITextOutput output) { @@ -422,6 +441,11 @@ namespace ICSharpCode.Decompiler.IL { return Arguments.Aggregate(InstructionFlags.None, (f, arg) => f | arg.Flags) | InstructionFlags.MayThrow | InstructionFlags.SideEffect; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow | InstructionFlags.SideEffect; + } + } } /// No operation. Takes 0 arguments and returns void. @@ -609,6 +633,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitDiv(this); @@ -630,6 +659,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitRem(this); @@ -1062,6 +1096,11 @@ namespace ICSharpCode.Decompiler.IL { return InstructionFlags.SideEffect; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitDebugBreak(this); @@ -1202,6 +1241,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitCkfinite(this); @@ -1346,6 +1390,11 @@ namespace ICSharpCode.Decompiler.IL { return value.Flags; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -1424,6 +1473,11 @@ namespace ICSharpCode.Decompiler.IL { return value.Flags; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -1625,6 +1679,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -1709,6 +1768,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitLocAlloc(this); @@ -1834,6 +1898,11 @@ namespace ICSharpCode.Decompiler.IL { return target.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { if (IsVolatile) @@ -1920,6 +1989,11 @@ namespace ICSharpCode.Decompiler.IL { return target.Flags | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2024,6 +2098,11 @@ namespace ICSharpCode.Decompiler.IL { return target.Flags | value.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { if (IsVolatile) @@ -2068,6 +2147,11 @@ namespace ICSharpCode.Decompiler.IL { return InstructionFlags.SideEffect; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect; + } + } public override void WriteTo(ITextOutput output) { if (IsVolatile) @@ -2182,6 +2266,11 @@ namespace ICSharpCode.Decompiler.IL { return value.Flags | InstructionFlags.SideEffect; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect; + } + } public override void WriteTo(ITextOutput output) { if (IsVolatile) @@ -2220,6 +2309,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2336,6 +2430,11 @@ namespace ICSharpCode.Decompiler.IL { return target.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { if (IsVolatile) @@ -2444,6 +2543,11 @@ namespace ICSharpCode.Decompiler.IL { return target.Flags | value.Flags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.SideEffect | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { if (IsVolatile) @@ -2484,6 +2588,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.SideEffect | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2518,6 +2627,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2552,6 +2666,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.SideEffect | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.SideEffect | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2640,6 +2759,11 @@ namespace ICSharpCode.Decompiler.IL { return Indices.Aggregate(InstructionFlags.None, (f, arg) => f | arg.Flags) | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2701,6 +2825,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitThrow(this); @@ -2722,6 +2851,11 @@ namespace ICSharpCode.Decompiler.IL { return InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow | InstructionFlags.EndPointUnreachable; + } + } public override void AcceptVisitor(ILVisitor visitor) { visitor.VisitRethrow(this); @@ -2818,6 +2952,11 @@ namespace ICSharpCode.Decompiler.IL { return array.Flags | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); @@ -2907,6 +3046,11 @@ namespace ICSharpCode.Decompiler.IL { return array.Flags | Indices.Aggregate(InstructionFlags.None, (f, arg) => f | arg.Flags) | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { if (IsReadOnly) @@ -2994,6 +3138,11 @@ namespace ICSharpCode.Decompiler.IL { return base.ComputeFlags() | InstructionFlags.MayThrow; } + public override InstructionFlags DirectFlags { + get { + return base.DirectFlags | InstructionFlags.MayThrow; + } + } public override void WriteTo(ITextOutput output) { output.Write(OpCode); diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index 55c53a356..5310d7579 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -25,11 +25,11 @@ <# OpCode[] baseClasses = { new OpCode("SimpleInstruction", "Instruction without any arguments", - AbstractBaseClass, CustomArguments(), CustomWriteTo), + AbstractBaseClass, CustomArguments(), CustomWriteTo, HasFlag("InstructionFlags.None")), new OpCode("UnaryInstruction", "Instruction with a single argument", - AbstractBaseClass, CustomArguments("argument")), + AbstractBaseClass, CustomArguments("argument"), HasFlag("InstructionFlags.None")), new OpCode("BinaryInstruction", "Instruction with two arguments: Left and Right", - AbstractBaseClass, CustomArguments("left", "right")), + AbstractBaseClass, CustomArguments("left", "right"), HasFlag("InstructionFlags.None")), new OpCode("CallInstruction", "Instruction with a list of arguments.", AbstractBaseClass, CustomChildren(new []{ new ArgumentInfo("arguments") { IsCollection = true }}), CustomWriteTo, MayThrow, SideEffect), @@ -239,6 +239,13 @@ namespace ICSharpCode.Decompiler.IL return <#=string.Join(" | ", opCode.Flags)#>; } <# } #> +<# if (opCode.GenerateComputeFlags && opCode.Flags.Any(f => f != "base.ComputeFlags()")) { #> + public override InstructionFlags DirectFlags { + get { + return <#=opCode.DirectFlags.Count > 0 ? string.Join(" | ", opCode.DirectFlags) : "InstructionFlags.None"#>; + } + } +<# } #> <# if (opCode.GenerateWriteTo) { #> public override void WriteTo(ITextOutput output) {<#=Body(opCode.WriteToBody)#>} @@ -415,8 +422,10 @@ namespace ICSharpCode.Decompiler.IL public List Members = new List(); public List Flags = new List(); + public List DirectFlags = new List(); public bool GenerateComputeFlags = true; + public bool GenerateAcceptVisitor = true; public bool GenerateWriteTo = false; @@ -479,6 +488,7 @@ namespace ICSharpCode.Decompiler.IL { return opCode => { opCode.Flags.Add(name); + opCode.DirectFlags.Add(name); }; } @@ -510,13 +520,19 @@ namespace ICSharpCode.Decompiler.IL static Action MayBranch = HasFlag("InstructionFlags.MayBranch"); // UnconditionalBranch trait: the instruction does not produce a result normally; it always branches or throws an exception. Implies VoidResult. + // UnconditionalBranch should be paired with either MayBranch or MayThrow (or both). static Action UnconditionalBranch = VoidResult + HasFlag("InstructionFlags.EndPointUnreachable"); + // ControlFlow trait: the instruction involves some form of internal control flow + // Instructions without this trait must evaluate all arguments left-to-right before having any effect of their own. + static Action ControlFlow = HasFlag("InstructionFlags.ControlFlow"); + static Action BaseClass(string name) { return opCode => { opCode.BaseClass = name; opCode.Flags.Add("base.ComputeFlags()"); + opCode.DirectFlags.Add("base.DirectFlags"); }; } @@ -527,8 +543,7 @@ namespace ICSharpCode.Decompiler.IL // Unary trait: the instruction has a single argument static Action Unary = opCode => { - opCode.BaseClass = "UnaryInstruction"; - opCode.Flags.Add("base.ComputeFlags()"); + BaseClass("UnaryInstruction")(opCode); opCode.ConstructorParameters.Add("ILInstruction argument"); opCode.MatchParameters.Add(new MatchParamInfo { TypeName = "ILInstruction", Name = "argument", FieldName = "Argument" }); opCode.BaseConstructorArguments.Add("argument"); @@ -539,8 +554,7 @@ namespace ICSharpCode.Decompiler.IL // Binary trait: the instruction has two arguments named 'Left' and 'Right' static Action Binary = opCode => { - opCode.BaseClass = "BinaryInstruction"; - opCode.Flags.Add("base.ComputeFlags()"); + BaseClass("BinaryInstruction")(opCode); opCode.ConstructorParameters.Add("ILInstruction left"); opCode.ConstructorParameters.Add("ILInstruction right"); opCode.BaseConstructorArguments.Add("left"); diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index fe9e15c4c..aea7482fa 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -172,5 +172,11 @@ namespace ICSharpCode.Decompiler.IL flags |= FinalInstruction.Flags; return flags; } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } + } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs index 0ba0d45e5..feb5852be 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs @@ -149,7 +149,7 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { - InstructionFlags flags = InstructionFlags.None; + InstructionFlags flags = InstructionFlags.ControlFlow; foreach (var block in Blocks) { flags |= block.Flags; } @@ -160,5 +160,48 @@ namespace ICSharpCode.Decompiler.IL flags &= ~InstructionFlags.EndPointUnreachable; return flags; } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } + } + + /// + /// Sort the blocks in reverse post-order over the control flow graph between the blocks. + /// + public void SortBlocks(bool deleteUnreachableBlocks = false) + { + // Visit blocks in post-order + BitSet visited = new BitSet(Blocks.Count); + List postOrder = new List(); + + Action visit = null; + visit = delegate(Block block) { + Debug.Assert(block.Parent == this); + if (!visited[block.ChildIndex]) { + visited[block.ChildIndex] = true; + + foreach (var branch in block.Descendants.OfType()) { + if (branch.TargetBlock.Parent == this) { + visit(branch.TargetBlock); + } + } + + postOrder.Add(block); + } + }; + visit(EntryPoint); + + postOrder.Reverse(); + if (!deleteUnreachableBlocks) { + for (int i = 0; i < Blocks.Count; i++) { + if (!visited[i]) + postOrder.Add(Blocks[i]); + } + } + Debug.Assert(postOrder[0] == Blocks[0]); + Blocks.ReplaceList(postOrder); + } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs index f76333732..7a1784c90 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILFunction.cs @@ -59,7 +59,13 @@ namespace ICSharpCode.Decompiler.IL { // Creating a lambda may throw OutOfMemoryException // We intentionally don't propagate any flags from the lambda body! - return InstructionFlags.MayThrow; + return InstructionFlags.MayThrow | InstructionFlags.ControlFlow; + } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayThrow | InstructionFlags.ControlFlow; + } } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs index d3a7b991d..191feaffe 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/ILInstruction.cs @@ -71,6 +71,7 @@ namespace ICSharpCode.Decompiler.IL Debug.Assert(child.IsConnected == this.IsConnected); child.CheckInvariant(phase); } + Debug.Assert((this.DirectFlags & ~this.Flags) == 0, "All DirectFlags must also appear in this.Flags"); } /// @@ -114,7 +115,7 @@ namespace ICSharpCode.Decompiler.IL /// until some change to the ILAst invalidates the cache. /// /// - /// Flag cache invalidation makes of the Parent property, + /// 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). /// @@ -143,6 +144,11 @@ namespace ICSharpCode.Decompiler.IL 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. /// diff --git a/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs index ccc723c51..2b9d636cd 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/IfInstruction.cs @@ -52,9 +52,15 @@ namespace ICSharpCode.Decompiler.IL } } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } + } + protected override InstructionFlags ComputeFlags() { - return condition.Flags | CombineFlags(trueInst.Flags, falseInst.Flags); + return InstructionFlags.ControlFlow | condition.Flags | CombineFlags(trueInst.Flags, falseInst.Flags); } internal static InstructionFlags CombineFlags(InstructionFlags trueFlags, InstructionFlags falseFlags) diff --git a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs index c8f8477da..b1d7f647e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs @@ -100,5 +100,16 @@ namespace ICSharpCode.Decompiler.IL var inst = this as Leave; return inst != null && inst.TargetContainer == targetContainer; } + + public bool MatchTryCatchHandler(out ILVariable variable) + { + var inst = this as TryCatchHandler; + if (inst != null) { + variable = inst.Variable; + return true; + } + variable = null; + return false; + } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/Return.cs b/ICSharpCode.Decompiler/IL/Instructions/Return.cs index d683dcfb9..053293692 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Return.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Return.cs @@ -71,6 +71,12 @@ namespace ICSharpCode.Decompiler.IL return flags; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable; + } + } + public override void WriteTo(ITextOutput output) { output.Write(OpCode); diff --git a/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs index f8a2e323d..528bbc6a8 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/SimpleInstruction.cs @@ -33,11 +33,7 @@ namespace ICSharpCode.Decompiler.IL public override void WriteTo(ITextOutput output) { output.Write(OpCode); - } - - protected override InstructionFlags ComputeFlags() - { - return InstructionFlags.None; + // the non-custom WriteTo would add useless parentheses } } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs index f5052eb6f..d3d604961 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs @@ -49,13 +49,19 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { - var sectionFlags = InstructionFlags.None; + var sectionFlags = InstructionFlags.ControlFlow; foreach (var section in Sections) { sectionFlags = IfInstruction.CombineFlags(sectionFlags, section.Flags); } return value.Flags | sectionFlags; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } + } + public override void WriteTo(ITextOutput output) { output.Write("switch ("); @@ -122,6 +128,12 @@ namespace ICSharpCode.Decompiler.IL return body.Flags; } + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.None; + } + } + public override void WriteTo(ITextOutput output) { output.Write("case "); diff --git a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs index 1e8b0be84..1b53e108e 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/TryInstruction.cs @@ -85,7 +85,13 @@ namespace ICSharpCode.Decompiler.IL var flags = TryBlock.Flags; foreach (var handler in Handlers) flags = IfInstruction.CombineFlags(flags, handler.Flags); - return flags; + return flags | InstructionFlags.ControlFlow; + } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } } protected override int GetChildCount() @@ -144,7 +150,14 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { - return filter.Flags | body.Flags; + return filter.Flags | body.Flags | InstructionFlags.ControlFlow; + } + + public override InstructionFlags DirectFlags { + get { + // the body is not evaluated if the filter returns 0 + return InstructionFlags.ControlFlow; + } } public override void WriteTo(ITextOutput output) @@ -217,7 +230,13 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { // if the endpoint of either the try or the finally is unreachable, the endpoint of the try-finally will be unreachable - return TryBlock.Flags | finallyBlock.Flags; + return TryBlock.Flags | finallyBlock.Flags | InstructionFlags.ControlFlow; + } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } } protected override int GetChildCount() @@ -304,7 +323,13 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { // The endpoint of the try-fault is unreachable iff the try endpoint is unreachable - return TryBlock.Flags | (faultBlock.Flags & ~InstructionFlags.EndPointUnreachable); + return TryBlock.Flags | (faultBlock.Flags & ~InstructionFlags.EndPointUnreachable) | InstructionFlags.ControlFlow; + } + + public override InstructionFlags DirectFlags { + get { + return InstructionFlags.ControlFlow; + } } protected override int GetChildCount() diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs index 53170c5d1..f4692d8fb 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs @@ -35,6 +35,7 @@ namespace ICSharpCode.Decompiler.IL foreach (var block in function.Descendants.OfType()) { InlineAllInBlock(block); } + function.Variables.RemoveDead(); } public bool InlineAllInBlock(Block block) diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs new file mode 100644 index 000000000..c7c787e2f --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2016 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.Linq; +using ICSharpCode.Decompiler.FlowAnalysis; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + /// + /// Split variables where possible. + /// + public class SplitVariables : IILTransform + { + public void Run(ILFunction function, ILTransformContext context) + { + foreach (var container in function.Descendants.OfType()) { + container.SortBlocks(); + } + var rd = new ReachingDefinitions(function, v => v.Kind != VariableKind.StackSlot); + } + } +} diff --git a/ICSharpCode.Decompiler/Util/BitSet.cs b/ICSharpCode.Decompiler/Util/BitSet.cs new file mode 100644 index 000000000..196f38c14 --- /dev/null +++ b/ICSharpCode.Decompiler/Util/BitSet.cs @@ -0,0 +1,127 @@ +// Copyright (c) 2016 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; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Text; + +namespace ICSharpCode.Decompiler +{ + /// + /// Improved version of BitArray + /// + public class BitSet + { + readonly BitArray bits; + + /// + /// Creates a new bitset, where initially all bits are zero. + /// + public BitSet(int capacity) + { + this.bits = new BitArray(capacity); + } + + private BitSet(BitArray bits) + { + this.bits = bits; + } + + public BitSet Clone() + { + return new BitSet((BitArray)bits.Clone()); + } + + public bool this[int index] { + get { + return bits[index]; + } + set { + bits[index] = value; + } + } + + /// + /// Gets whether this set is a subset of other, or equal. + /// + public bool IsSubsetOf(BitSet other) + { + for (int i = 0; i < bits.Length; i++) { + if (bits[i] && !other[i]) + return false; + } + return true; + } + + public bool IsSupersetOf(BitSet other) + { + return other.IsSubsetOf(this); + } + + public void UnionWith(BitSet other) + { + bits.Or(other.bits); + } + + public void Clear(int index) + { + bits[index] = false; + } + + public void Clear(int startIndex, int endIndex) + { + for (int i = startIndex; i < endIndex; i++) { + bits[i] = false; + } + } + + public void Set(int index) + { + bits[index] = true; + } + + public void ReplaceWith(BitSet incoming) + { + Debug.Assert(bits.Length == incoming.bits.Length); + for (int i = 0; i < bits.Length; i++) { + bits[i] = incoming.bits[i]; + } + } + + public override string ToString() + { + StringBuilder b = new StringBuilder(); + b.Append('{'); + for (int i = 0; i < bits.Length; i++) { + if (bits[i]) { + if (b.Length > 1) + b.Append(", "); + if (b.Length > 500) { + b.Append("..."); + break; + } + b.Append(i); + } + } + b.Append('}'); + return b.ToString(); + } + } +}