diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index bead8f466..748cd1666 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -56,6 +56,7 @@ namespace ICSharpCode.Decompiler.CSharp new ControlFlowSimplification(), new ILInlining(), new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms + new SwitchDetection(), new LoopDetection(), new IntroduceExitPoints(), new ConditionDetection(), diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index 39944524e..91e02b694 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -76,9 +76,9 @@ namespace ICSharpCode.Decompiler.CSharp value = i != 0; } else if (type.Kind == TypeKind.Enum) { var enumType = type.GetDefinition().EnumUnderlyingType; - value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, true); + value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, false); } else { - value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, true); + value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, false); } return new CaseLabel(exprBuilder.ConvertConstantValue(new ConstantResolveResult(type, value))); } @@ -92,7 +92,7 @@ namespace ICSharpCode.Decompiler.CSharp var stmt = new SwitchStatement() { Expression = value }; foreach (var section in inst.Sections) { var astSection = new ICSharpCode.NRefactory.CSharp.SwitchSection(); - astSection.CaseLabels.AddRange(section.Labels.Range().Select(i => CreateTypedCaseLabel(i, value.Type))); + astSection.CaseLabels.AddRange(section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type))); astSection.Statements.Add(Convert(section.Body)); stmt.SwitchSections.Add(astSection); } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs index ad4b3f4eb..739378a73 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs @@ -159,7 +159,7 @@ namespace ICSharpCode.Decompiler.FlowAnalysis /// the first place. /// /// - /// The simple state "bool isReachable", would implement ReplaceWith as: + /// The simple state "bool isReachable", would implement ReplaceWithBottom as: /// this.isReachable = false; /// void ReplaceWithBottom(); diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b5d858c26..2ae10ad42 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -96,6 +96,8 @@ + + True True @@ -172,6 +174,7 @@ + TextTemplatingFileGenerator @@ -207,7 +210,6 @@ - diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs new file mode 100644 index 000000000..c2ccb9bbd --- /dev/null +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ICSharpCode.Decompiler.IL.ControlFlow +{ + /// + /// C# switch statements are not necessarily compiled into IL switch instructions. + /// For example, when the label values are not contiguous, the C# compiler + /// will generate if statements similar to a binary search. + /// + /// This class analyses such sequences of if statements to reconstruct the original switch. + /// + /// + /// This analysis expects to be run on basic blocks (not extended basic blocks). + /// + class SwitchAnalysis + { + /// + /// The variable that is used to represent the switch expression. + /// null while analyzing the first block. + /// + ILVariable switchVar; + + /// + /// The variable to be used as the argument of the switch instruction. + /// + public ILVariable SwitchVariable + { + get { return switchVar; } + } + + /// + /// Gets the sections that were detected by the previoous AnalyzeBlock() call. + /// + public readonly List> Sections = new List>(); + + readonly Dictionary targetBlockToSectionIndex = new Dictionary(); + + /// + /// Blocks that can be deleted if the tail of the initial block is replaced with a switch instruction. + /// + public readonly List InnerBlocks = new List(); + + Block rootBlock; + + /// + /// Analyze the last two statements in the block and see if they can be turned into a + /// switch instruction. + /// + /// true if the block could be analyzed successfully; false otherwise + public bool AnalyzeBlock(Block block) + { + switchVar = null; + rootBlock = block; + targetBlockToSectionIndex.Clear(); + Sections.Clear(); + InnerBlocks.Clear(); + return AnalyzeBlock(block, LongSet.Universe, tailOnly: true); + } + + /// + /// Analyzes the tail end (last two instructions) of a block. + /// + /// + /// Sets switchVar and defaultInstruction if they are null, + /// and adds found sections to sectionLabels and sectionInstructions. + /// + /// If the function returns false, sectionLabels and sectionInstructions are unmodified. + /// + /// The block to analyze. + /// The possible values of the "interesting" variable + /// when control flow reaches this block. + /// If true, analyze only the tail (last two instructions). + /// If false, analyze the whole block. + bool AnalyzeBlock(Block block, LongSet inputValues, bool tailOnly = false) + { + if (tailOnly) { + Debug.Assert(block == rootBlock); + if (block.Instructions.Count < 2) + return false; + } else { + Debug.Assert(switchVar != null); // switchVar should always be determined by the top-level call + if (block.IncomingEdgeCount != 1 || block == rootBlock) + return false; // for now, let's only consider if-structures that form a tree + if (block.Instructions.Count != 2) + return false; + if (block.Parent != rootBlock.Parent) + return false; // all blocks should belong to the same container + } + var inst = block.Instructions[block.Instructions.Count - 2]; + ILInstruction condition, trueInst; + LongSet trueValues; + if (inst.MatchIfInstruction(out condition, out trueInst) + && AnalyzeCondition(condition, out trueValues) + ) { + trueValues = trueValues.IntersectWith(inputValues); + Block trueBlock; + if (trueInst.MatchBranch(out trueBlock) && AnalyzeBlock(trueBlock, trueValues)) { + // OK, true block was further analyzed. + InnerBlocks.Add(trueBlock); + } else { + // Create switch section for trueInst. + AddSection(trueValues, trueInst); + } + } else if (inst.OpCode == OpCode.SwitchInstruction) { + if (AnalyzeSwitch((SwitchInstruction)inst, inputValues, out trueValues)) { + // OK + } else { // switch analysis failed (e.g. switchVar mismatch) + return false; + } + } else { // unknown inst + return false; + } + + var remainingValues = inputValues.ExceptWith(trueValues); + ILInstruction falseInst = block.Instructions.Last(); + Block falseBlock; + if (falseInst.MatchBranch(out falseBlock) && AnalyzeBlock(falseBlock, remainingValues)) { + // OK, false block was further analyzed. + InnerBlocks.Add(falseBlock); + } else { + // Create switch section for falseInst. + AddSection(remainingValues, falseInst); + } + return true; + } + + private bool AnalyzeSwitch(SwitchInstruction inst, LongSet inputValues, out LongSet anyMatchValues) + { + anyMatchValues = LongSet.Empty; + long offset; + if (MatchSwitchVar(inst.Value)) { + offset = 0; + } else if (inst.Value.OpCode == OpCode.BinaryNumericInstruction) { + var bop = (BinaryNumericInstruction)inst.Value; + if (bop.CheckForOverflow) + return false; + long val; + if (MatchSwitchVar(bop.Left) && MatchLdcI(bop.Right, out val)) { + switch (bop.Operator) { + case BinaryNumericOperator.Add: + offset = -val; + break; + case BinaryNumericOperator.Sub: + offset = val; + break; + default: // unknown bop.Operator + return false; + } + } else { // unknown bop.Left + return false; + } + } else { // unknown inst.Value + return false; + } + foreach (var section in inst.Sections) { + var matchValues = section.Labels.AddOffset(offset).IntersectWith(inputValues); + AddSection(matchValues, section.Body); + anyMatchValues = anyMatchValues.UnionWith(matchValues); + } + return true; + } + + /// + /// Adds a new section to the Sections list. + /// + /// If the instruction is a branch instruction, unify the new section with an existing section + /// that also branches to the same target. + /// + void AddSection(LongSet values, ILInstruction inst) + { + Block targetBlock; + if (inst.MatchBranch(out targetBlock)) { + int index; + if (targetBlockToSectionIndex.TryGetValue(targetBlock, out index)) { + Sections[index] = new KeyValuePair( + Sections[index].Key.UnionWith(values), + inst + ); + } else { + targetBlockToSectionIndex.Add(targetBlock, Sections.Count); + Sections.Add(new KeyValuePair(values, inst)); + } + } else { + Sections.Add(new KeyValuePair(values, inst)); + } + } + + bool MatchSwitchVar(ILInstruction inst) + { + if (switchVar != null) + return inst.MatchLdLoc(switchVar); + else + return inst.MatchLdLoc(out switchVar); + } + + bool MatchLdcI(ILInstruction inst, out long val) + { + if (inst.MatchLdcI8(out val)) + return true; + int intVal; + if (inst.MatchLdcI4(out intVal)) { + val = intVal; + return true; + } + return false; + } + + /// + /// Analyzes the boolean condition, returning the set of values of the interesting + /// variable for which the condition evaluates to true. + /// + private bool AnalyzeCondition(ILInstruction condition, out LongSet trueValues) + { + ILInstruction arg; + Comp comp = condition as Comp; + long val; + if (comp != null && MatchSwitchVar(comp.Left) && MatchLdcI(comp.Right, out val)) { + // if (comp(V OP val)) + switch (comp.Kind) { + case ComparisonKind.Equality: + trueValues = new LongSet(val); + return true; + case ComparisonKind.Inequality: + trueValues = new LongSet(val).Invert(); + return true; + case ComparisonKind.LessThan: + trueValues = MakeGreaterThanOrEqualSet(val, comp.Sign).Invert(); + return true; + case ComparisonKind.LessThanOrEqual: + trueValues = MakeLessThanOrEqualSet(val, comp.Sign); + return true; + case ComparisonKind.GreaterThan: + trueValues = MakeLessThanOrEqualSet(val, comp.Sign).Invert(); + return true; + case ComparisonKind.GreaterThanOrEqual: + trueValues = MakeGreaterThanOrEqualSet(val, comp.Sign); + return true; + default: + trueValues = LongSet.Empty; + return false; + } + } else if (MatchSwitchVar(condition)) { + // if (ldloc V) --> branch for all values except 0 + trueValues = new LongSet(0).Invert(); + return true; + } else if (condition.MatchLogicNot(out arg)) { + // if (logic.not(X)) --> branch for all values where if (X) does not branch + LongSet falseValues; + bool res = AnalyzeCondition(arg, out falseValues); + trueValues = falseValues.Invert(); + return res; + } else { + trueValues = LongSet.Empty; + return false; + } + } + + private LongSet MakeGreaterThanOrEqualSet(long val, Sign sign) + { + if (sign == Sign.Signed) { + return new LongSet(LongInterval.Inclusive(val, long.MaxValue)); + } else { + Debug.Assert(sign == Sign.Unsigned); + if (val >= 0) { + // The range val to ulong.MaxValue expressed with signed longs + // is not a single contiguous range, but two ranges: + return new LongSet(LongInterval.Inclusive(val, long.MaxValue)) + .UnionWith(new LongSet(new LongInterval(long.MinValue, 0))); + } else { + return new LongSet(new LongInterval(val, 0)); + } + } + } + + private LongSet MakeLessThanOrEqualSet(long val, Sign sign) + { + if (sign == Sign.Signed) { + return new LongSet(LongInterval.Inclusive(long.MinValue, val)); + } else { + Debug.Assert(sign == Sign.Unsigned); + if (val >= 0) { + return new LongSet(LongInterval.Inclusive(0, val)); + } else { + // The range 0 to (ulong)val expressed with signed longs + // is not a single contiguous range, but two ranges: + return new LongSet(LongInterval.Inclusive(0, long.MaxValue)) + .UnionWith(new LongSet(LongInterval.Inclusive(long.MinValue, val))); + } + } + } + } +} diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs new file mode 100644 index 000000000..74d86aaf4 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs @@ -0,0 +1,87 @@ +// 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 ICSharpCode.Decompiler.IL.Transforms; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +namespace ICSharpCode.Decompiler.IL.ControlFlow +{ + /// + /// C# switch statements are not necessarily compiled into + /// IL switch instructions (e.g. when the integer values are non-contiguous). + /// + /// Detect sequences of conditional branches that all test a single integer value, + /// and simplify them into a ILAst switch instruction (which like C# does not require contiguous values). + /// + class SwitchDetection : IILTransform + { + SwitchAnalysis analysis = new SwitchAnalysis(); + + public void Run(ILFunction function, ILTransformContext context) + { + foreach (var container in function.Descendants.OfType()) { + bool needBlockCleanup = false; + foreach (var block in container.Blocks) { + if (analysis.AnalyzeBlock(block) && UseCSharpSwitch(analysis)) { + var hugeSection = analysis.Sections.Single(s => s.Key.Count() > 50); + + var sw = new SwitchInstruction(new LdLoc(analysis.SwitchVariable)); + foreach (var section in analysis.Sections) { + if (!section.Key.SetEquals(hugeSection.Key)) { + sw.Sections.Add(new SwitchSection + { + Labels = section.Key, + Body = section.Value + }); + } + } + block.Instructions[block.Instructions.Count - 2] = sw; + block.Instructions[block.Instructions.Count - 1] = hugeSection.Value; + // mark all inner blocks that were converted to the switch statement for deletion + foreach (var innerBlock in analysis.InnerBlocks) { + Debug.Assert(innerBlock.Parent == container); + Debug.Assert(innerBlock != container.EntryPoint); + innerBlock.Instructions.Clear(); + } + needBlockCleanup = true; + } + } + if (needBlockCleanup) { + Debug.Assert(container.Blocks.All(b => b.Instructions.Count != 0 || b.IncomingEdgeCount == 0)); + container.Blocks.RemoveAll(b => b.Instructions.Count == 0); + } + } + } + + const ulong MaxValuesPerSection = 50; + + /// + /// Tests whether we should prefer a switch statement over an if statement. + /// + static bool UseCSharpSwitch(SwitchAnalysis analysis) + { + return analysis.InnerBlocks.Any() + && analysis.Sections.Count(s => s.Key.Count() > MaxValuesPerSection) == 1; + } + } +} diff --git a/ICSharpCode.Decompiler/IL/Instructions/Branch.cs b/ICSharpCode.Decompiler/IL/Instructions/Branch.cs index 042b32ba5..ccafa3909 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Branch.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Branch.cs @@ -107,6 +107,7 @@ namespace ICSharpCode.Decompiler.IL if (phase > ILPhase.InILReader) { Debug.Assert(targetBlock.Parent is BlockContainer); Debug.Assert(this.IsDescendantOf(targetBlock.Parent)); + Debug.Assert(targetBlock.Parent.Children[targetBlock.ChildIndex] == targetBlock); } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs index c6c8941f5..92509b571 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs @@ -24,7 +24,11 @@ using System.Linq; namespace ICSharpCode.Decompiler.IL { /// - /// Description of SwitchInstruction. + /// Generalization of IL switch-case: like a VB switch over integers, this instruction + /// supports integer value ranges as labels. + /// + /// The section labels are using 'long' as integer type. + /// If the Value instruction produces StackType.I4 or I, the value is implicitly sign-extended to I8. /// partial class SwitchInstruction { @@ -52,6 +56,7 @@ namespace ICSharpCode.Decompiler.IL protected override InstructionFlags ComputeFlags() { var sectionFlags = InstructionFlags.ControlFlow; + // note: the initial sectionFlags also represent the implicit empty 'default' case foreach (var section in Sections) { sectionFlags = SemanticHelper.CombineBranches(sectionFlags, section.Flags); } @@ -120,9 +125,9 @@ namespace ICSharpCode.Decompiler.IL internal override void CheckInvariant(ILPhase phase) { base.CheckInvariant(phase); - LongSet sets = new LongSet(ImmutableArray.Empty); + LongSet sets = LongSet.Empty; foreach (var section in Sections) { - Debug.Assert(!section.Labels.Intersects(sets)); + Debug.Assert(!section.Labels.Overlaps(sets)); sets = sets.UnionWith(section.Labels); } } @@ -133,7 +138,7 @@ namespace ICSharpCode.Decompiler.IL public SwitchSection() : base(OpCode.SwitchSection) { - + this.Labels = LongSet.Empty; } public LongSet Labels { get; set; } diff --git a/ICSharpCode.Decompiler/Tests/Util/LongSetTests.cs b/ICSharpCode.Decompiler/Tests/Util/LongSetTests.cs index a81ac57e5..417e5b888 100644 --- a/ICSharpCode.Decompiler/Tests/Util/LongSetTests.cs +++ b/ICSharpCode.Decompiler/Tests/Util/LongSetTests.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Immutable; +using System.Linq; using NUnit.Framework; namespace ICSharpCode.Decompiler.Tests.Util @@ -28,12 +29,95 @@ namespace ICSharpCode.Decompiler.Tests.Util [Test] public void UpperBound() { - var longSet = new LongSet(new [] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray()); + var longSet = new LongSet(new[] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray()); Assert.AreEqual(0, longSet.upper_bound(0)); for (int i = 1; i <= 5; i++) Assert.AreEqual(1, longSet.upper_bound(i)); for (int i = 6; i <= 10; i++) Assert.AreEqual(2, longSet.upper_bound(i)); } + + [Test] + public void UniverseContainsAll() + { + Assert.IsTrue(LongSet.Universe.Contains(long.MinValue)); + Assert.IsTrue(LongSet.Universe.Contains(1)); + Assert.IsTrue(LongSet.Universe.Contains(long.MaxValue)); + Assert.IsFalse(LongSet.Universe.IsEmpty); + } + + [Test] + public void IntersectUniverse() + { + Assert.AreEqual(LongSet.Universe, LongSet.Universe.IntersectWith(LongSet.Universe)); + Assert.AreEqual(LongSet.Empty, LongSet.Universe.IntersectWith(LongSet.Empty)); + Assert.AreEqual(new LongSet(long.MaxValue), LongSet.Universe.IntersectWith(new LongSet(long.MaxValue))); + var longSet = new LongSet(new[] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray()); + Assert.AreEqual(longSet, longSet.IntersectWith(LongSet.Universe)); + } + + [Test] + public void UnionUniverse() + { + Assert.AreEqual(LongSet.Universe, LongSet.Universe.UnionWith(LongSet.Universe)); + Assert.AreEqual(LongSet.Universe, LongSet.Universe.UnionWith(LongSet.Empty)); + Assert.AreEqual(LongSet.Universe, LongSet.Universe.UnionWith(new LongSet(long.MaxValue))); + var longSet = new LongSet(new[] { new LongInterval(1, 5), new LongInterval(6, 7) }.ToImmutableArray()); + Assert.AreEqual(LongSet.Universe, longSet.UnionWith(LongSet.Universe)); + } + + [Test] + public void ExceptWithUniverse() + { + Assert.AreEqual(LongSet.Universe, LongSet.Universe.ExceptWith(LongSet.Empty)); + Assert.AreEqual(LongSet.Empty, LongSet.Universe.ExceptWith(LongSet.Universe)); + Assert.AreEqual(LongSet.Empty, LongSet.Empty.ExceptWith(LongSet.Universe)); + Assert.AreEqual(LongSet.Empty, LongSet.Empty.ExceptWith(LongSet.Empty)); + } + + [Test] + public void UnionWith() + { + Assert.AreEqual(new LongSet(new LongInterval(0, 2)), + new LongSet(0).UnionWith(new LongSet(1))); + + Assert.AreEqual(LongSet.Universe, new LongSet(0).Invert().UnionWith(new LongSet(0))); + } + + [Test] + public void AddTo() + { + Assert.AreEqual(new LongSet(1), new LongSet(0).AddOffset(1)); + Assert.AreEqual(new LongSet(long.MinValue), new LongSet(long.MaxValue).AddOffset(1)); + + TestAddTo(new LongSet(new LongInterval(-10, 10)), 5); + TestAddTo(new LongSet(new LongInterval(-10, 10)), long.MaxValue); + Assert.AreEqual(new LongSet(10).Invert(), new LongSet(0).Invert().AddOffset(10)); + Assert.AreEqual(new LongSet(20).Invert(), new LongSet(30).Invert().AddOffset(-10)); + } + + void TestAddTo(LongSet input, long constant) + { + Assert.AreEqual( + input.Values.Select(e => unchecked(e + constant)).OrderBy(e => e).ToList(), + input.AddOffset(constant).Values.ToList()); + } + + [Test] + public void Values() + { + Assert.IsFalse(LongSet.Empty.Values.Any()); + Assert.IsTrue(LongSet.Universe.Values.Any()); + Assert.AreEqual(new[] { 1, 2, 3 }, new LongSet(LongInterval.Inclusive(1, 3)).Values.ToArray()); + } + + [Test] + public void ValueCount() + { + Assert.AreEqual(0, LongSet.Empty.Count()); + Assert.AreEqual(ulong.MaxValue, new LongSet(3).Invert().Count()); + Assert.AreEqual(ulong.MaxValue, LongSet.Universe.Count()); + Assert.AreEqual(long.MaxValue + 2ul, new LongSet(LongInterval.Inclusive(-1, long.MaxValue)).Count()); + } } } diff --git a/ICSharpCode.Decompiler/Util/BitSet.cs b/ICSharpCode.Decompiler/Util/BitSet.cs index 0717c4315..737f65c30 100644 --- a/ICSharpCode.Decompiler/Util/BitSet.cs +++ b/ICSharpCode.Decompiler/Util/BitSet.cs @@ -17,9 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Text; @@ -124,7 +121,7 @@ namespace ICSharpCode.Decompiler public bool IsProperSupersetOf(BitSet other) { - return IsSubsetOf(other) && !SetEquals(other); + return IsSupersetOf(other) && !SetEquals(other); } /// diff --git a/ICSharpCode.Decompiler/Util/Interval.cs b/ICSharpCode.Decompiler/Util/Interval.cs index 0d65f0bab..6504045b3 100644 --- a/ICSharpCode.Decompiler/Util/Interval.cs +++ b/ICSharpCode.Decompiler/Util/Interval.cs @@ -1,11 +1,23 @@ -using System; -using System.Collections; +// 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.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ICSharpCode.Decompiler { @@ -133,7 +145,7 @@ namespace ICSharpCode.Decompiler /// /// Start <= unchecked(End - 1): normal interval /// Start == End: empty interval - /// Special case: Start == End == int.MinValue: interval containing all integers, not an empty interval! + /// Special case: Start == End == long.MinValue: interval containing all integers, not an empty interval! /// public struct LongInterval : IEquatable { @@ -151,14 +163,19 @@ namespace ICSharpCode.Decompiler /// If possible, prefer using InclusiveEnd for comparisons, as that does not have an overflow problem. /// public readonly long End; - + /// /// Creates a new interval. /// /// Start position (inclusive) /// End position (exclusive). - /// Note that it is possible to create an interval that includes int.MaxValue - /// by using end==int.MaxValue+1==int.MinValue. + /// Note that it is possible to create an interval that includes long.MaxValue + /// by using end==long.MaxValue+1==long.MinValue. + /// + /// This method can be used to create an empty interval by specifying start==end, + /// however this is error-prone due to the special case of + /// start==end==long.MinValue being interpreted as the full interval [long.MinValue,long.MaxValue]. + /// public LongInterval(long start, long end) { if (!(start <= unchecked(end - 1) || start == end)) @@ -166,6 +183,20 @@ namespace ICSharpCode.Decompiler this.Start = start; this.End = end; } + + /// + /// Creates a new interval from start to end. + /// Unlike the constructor where the end position is exclusive, + /// this method interprets the end position as inclusive. + /// + /// This method cannot be used to construct an empty interval. + /// + public static LongInterval Inclusive(long start, long inclusiveEnd) + { + if (!(start <= inclusiveEnd)) + throw new ArgumentException(); + return new LongInterval(start, unchecked(inclusiveEnd + 1)); + } /// /// Gets the inclusive end of the interval. (End - 1) @@ -190,7 +221,7 @@ namespace ICSharpCode.Decompiler public bool Contains(long val) { - // Use 'val <= InclusiveEnd' instead of 'val < End' to allow intervals to include int.MaxValue. + // Use 'val <= InclusiveEnd' instead of 'val < End' to allow intervals to include long.MaxValue. return Start <= val && val <= InclusiveEnd; } @@ -261,137 +292,4 @@ namespace ICSharpCode.Decompiler } #endregion } - - /// - /// An immutable set of longs, that is implemented as a list of intervals. - /// - public struct LongSet - { - public readonly ImmutableArray Intervals; - - public LongSet(ImmutableArray intervals) - { - this.Intervals = intervals; - } - - public LongSet(long value) - : this(ImmutableArray.Create(new LongInterval(value, unchecked(value + 1)))) - { - } - - public bool IsEmpty - { - get { return Intervals.IsDefaultOrEmpty; } - } - - IEnumerable DoIntersectWith(LongSet other) - { - var enumA = this.Intervals.GetEnumerator(); - var enumB = other.Intervals.GetEnumerator(); - bool moreA = enumA.MoveNext(); - bool moreB = enumB.MoveNext(); - while (moreA && moreB) { - LongInterval a = enumA.Current; - LongInterval b = enumB.Current; - LongInterval intersection = a.Intersect(b); - if (!intersection.IsEmpty) { - yield return intersection; - } - if (a.InclusiveEnd < b.InclusiveEnd) { - moreA = enumA.MoveNext(); - } else { - moreB = enumB.MoveNext(); - } - } - } - - public bool Intersects(LongSet other) - { - return DoIntersectWith(other).Any(); - } - - public LongSet IntersectWith(LongSet other) - { - return new LongSet(DoIntersectWith(other).ToImmutableArray()); - } - - IEnumerable DoUnionWith(LongSet other) - { - long start = long.MinValue; - long end = long.MinValue; - bool empty = true; - foreach (var element in this.Intervals.Merge(other.Intervals, (a, b) => a.Start.CompareTo(b.Start))) { - Debug.Assert(start <= element.Start); - - if (!empty && element.Start < end - 1) { - // element overlaps or touches [start, end), so combine the intervals: - if (element.End == long.MinValue) { - // special case: element goes all the way up to long.MaxValue inclusive - yield return new LongInterval(start, element.End); - break; - } else { - end = Math.Max(end, element.End); - } - } else { - // flush existing interval: - if (!empty) { - yield return new LongInterval(start, end); - } else { - empty = false; - } - start = element.Start; - end = element.End; - } - if (end == long.MinValue) { - // special case: element goes all the way up to long.MaxValue inclusive - // all further intervals in the input must be contained in [start, end), - // so ignore them (and avoid trouble due to the overflow in `end`). - break; - } - } - if (!empty) { - yield return new LongInterval(start, end); - } - } - - public LongSet UnionWith(LongSet other) - { - return new LongSet(DoUnionWith(other).ToImmutableArray()); - } - - public bool Contains(long val) - { - int index = upper_bound(val); - return index > 0 && Intervals[index - 1].Contains(val); - } - - internal int upper_bound(long val) - { - int min = 0, max = Intervals.Length - 1; - while (max >= min) { - int m = min + (max - min) / 2; - LongInterval i = Intervals[m]; - if (val < i.Start) { - max = m - 1; - continue; - } - if (val > i.End) { - min = m + 1; - continue; - } - return m + 1; - } - return min; - } - - public IEnumerable Range() - { - return Intervals.SelectMany(i => i.Range()); - } - - public override string ToString() - { - return string.Join(",", Intervals); - } - } } diff --git a/ICSharpCode.Decompiler/Util/LongSet.cs b/ICSharpCode.Decompiler/Util/LongSet.cs new file mode 100644 index 000000000..94ede546c --- /dev/null +++ b/ICSharpCode.Decompiler/Util/LongSet.cs @@ -0,0 +1,301 @@ +// 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.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; + +namespace ICSharpCode.Decompiler +{ + /// + /// An immutable set of longs, that is implemented as a list of intervals. + /// + public struct LongSet : IEquatable + { + public readonly ImmutableArray Intervals; + + public LongSet(ImmutableArray intervals) + { + this.Intervals = intervals; +#if DEBUG + long minValue = long.MinValue; + for (int i = 0; i < intervals.Length; i++) { + Debug.Assert(!intervals[i].IsEmpty); + Debug.Assert(minValue <= intervals[i].Start); + if (intervals[i].InclusiveEnd == long.MaxValue - 1 || intervals[i].InclusiveEnd == long.MaxValue) { + // An inclusive end of long.MaxValue-1 or long.MaxValue means (after the gap of 1 element), + // there isn't any room for more non-empty intervals. + Debug.Assert(i == intervals.Length - 1); + } else { + minValue = checked(intervals[i].End + 1); // enforce least 1 gap between intervals + } + } +#endif + } + + /// + /// Create a new LongSet that contains a single value. + /// + public LongSet(long value) + : this(ImmutableArray.Create(LongInterval.Inclusive(value, value))) + { + } + + /// + /// Create a new LongSet that contains the values from the interval. + /// + public LongSet(LongInterval interval) + : this(interval.IsEmpty ? Empty.Intervals : ImmutableArray.Create(interval)) + { + } + + /// + /// The empty LongSet. + /// + public static readonly LongSet Empty = new LongSet(ImmutableArray.Create()); + + /// + /// The LongSet that contains all possible long values. + /// + public static readonly LongSet Universe = new LongSet(LongInterval.Inclusive(long.MinValue, long.MaxValue)); + + public bool IsEmpty + { + get { return Intervals.IsEmpty; } + } + + /// + /// Gets the number of values in this LongSet. + /// Note: for LongSet.Universe, the number of values does not fit into ulong. + /// Instead, this property returns the off-by-one value ulong.MaxValue to avoid overflow. + /// + public ulong Count() + { + unchecked { + ulong count = 0; + foreach (var interval in Intervals) { + count += (ulong)(interval.End - interval.Start); + } + if (count == 0 && !Intervals.IsEmpty) + return ulong.MaxValue; + else + return count; + } + } + + + IEnumerable DoIntersectWith(LongSet other) + { + var enumA = this.Intervals.GetEnumerator(); + var enumB = other.Intervals.GetEnumerator(); + bool moreA = enumA.MoveNext(); + bool moreB = enumB.MoveNext(); + while (moreA && moreB) { + LongInterval a = enumA.Current; + LongInterval b = enumB.Current; + LongInterval intersection = a.Intersect(b); + if (!intersection.IsEmpty) { + yield return intersection; + } + if (a.InclusiveEnd < b.InclusiveEnd) { + moreA = enumA.MoveNext(); + } else { + moreB = enumB.MoveNext(); + } + } + } + + public bool Overlaps(LongSet other) + { + return DoIntersectWith(other).Any(); + } + + public LongSet IntersectWith(LongSet other) + { + return new LongSet(DoIntersectWith(other).ToImmutableArray()); + } + + /// + /// Given an enumerable of non-empty intervals sorted by the starting position, + /// merges overlapping or touching intervals to create a valid interval array for LongSet. + /// + static IEnumerable MergeOverlapping(IEnumerable input) + { + long start = long.MinValue; + long end = long.MinValue; + bool empty = true; + foreach (var element in input) { + Debug.Assert(start <= element.Start); + Debug.Assert(!element.IsEmpty); + + if (!empty && element.Start <= end) { + // element overlaps or touches [start, end), so combine the intervals: + if (element.End == long.MinValue) { + // special case: element goes all the way up to long.MaxValue inclusive + end = long.MinValue; + } else { + end = Math.Max(end, element.End); + } + } else { + // flush existing interval: + if (!empty) { + yield return new LongInterval(start, end); + } else { + empty = false; + } + start = element.Start; + end = element.End; + } + if (end == long.MinValue) { + // special case: element goes all the way up to long.MaxValue inclusive + // all further intervals in the input must be contained in [start, end), + // so ignore them (and avoid trouble due to the overflow in `end`). + break; + } + } + if (!empty) { + yield return new LongInterval(start, end); + } + } + + public LongSet UnionWith(LongSet other) + { + var mergedIntervals = this.Intervals.Merge(other.Intervals, (a, b) => a.Start.CompareTo(b.Start)); + return new LongSet(MergeOverlapping(mergedIntervals).ToImmutableArray()); + } + + /// + /// Creates a new LongSet where val is added to each element of this LongSet. + /// + public LongSet AddOffset(long val) + { + var newIntervals = new List(Intervals.Length + 1); + foreach (var element in Intervals) { + long newStart = unchecked(element.Start + val); + long newInclusiveEnd = unchecked(element.InclusiveEnd + val); + if (newStart <= newInclusiveEnd) { + newIntervals.Add(LongInterval.Inclusive(newStart, newInclusiveEnd)); + } else { + // interval got split by integer overflow + newIntervals.Add(LongInterval.Inclusive(newStart, long.MaxValue)); + newIntervals.Add(LongInterval.Inclusive(long.MinValue, newInclusiveEnd)); + } + } + newIntervals.Sort((a, b) => a.Start.CompareTo(b.Start)); + return new LongSet(MergeOverlapping(newIntervals).ToImmutableArray()); + } + + /// + /// Creates a new set that contains all values that are in this, but not in other. + /// + public LongSet ExceptWith(LongSet other) + { + return IntersectWith(other.Invert()); + } + + /// + /// Creates a new LongSet that contains all elements not contained in this LongSet. + /// + public LongSet Invert() + { + // The loop below assumes a non-empty LongSet, so handle the empty case specially. + if (IsEmpty) { + return Universe; + } + List newIntervals = new List(Intervals.Length + 1); + long prevEnd = long.MinValue; // previous exclusive end + foreach (var interval in Intervals) { + if (interval.Start > prevEnd) { + newIntervals.Add(new LongInterval(prevEnd, interval.Start)); + } + prevEnd = interval.End; + } + // create a final interval up to long.MaxValue inclusive + if (prevEnd != long.MinValue) { + newIntervals.Add(new LongInterval(prevEnd, long.MinValue)); + } + return new LongSet(newIntervals.ToImmutableArray()); + } + + public bool Contains(long val) + { + int index = upper_bound(val); + return index > 0 && Intervals[index - 1].Contains(val); + } + + internal int upper_bound(long val) + { + int min = 0, max = Intervals.Length - 1; + while (max >= min) { + int m = min + (max - min) / 2; + LongInterval i = Intervals[m]; + if (val < i.Start) { + max = m - 1; + continue; + } + if (val > i.End) { + min = m + 1; + continue; + } + return m + 1; + } + return min; + } + + public IEnumerable Values + { + get { return Intervals.SelectMany(i => i.Range()); } + } + + public override string ToString() + { + return string.Join(",", Intervals); + } + + #region Equals and GetHashCode implementation + public override bool Equals(object obj) + { + return obj is LongSet && SetEquals((LongSet)obj); + } + + public override int GetHashCode() + { + throw new NotImplementedException(); + } + + [Obsolete("Explicitly call SetEquals() instead.")] + public bool Equals(LongSet other) + { + return SetEquals(other); + } + + public bool SetEquals(LongSet other) + { + if (Intervals.Length != other.Intervals.Length) + return false; + for (int i = 0; i < Intervals.Length; i++) { + if (Intervals[i] != other.Intervals[i]) + return false; + } + return true; + } + #endregion + } +} diff --git a/ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs b/ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs index f11980b7b..18d0ed115 100644 --- a/ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs +++ b/ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs @@ -67,7 +67,7 @@ namespace ILSpy.BamlDecompiler if (ilSwitch != null) { foreach (var section in ilSwitch.Sections) { var events = FindEvents(section.Body); - foreach (long id in section.Labels.Range()) + foreach (long id in section.Labels.Values) result.Add(id, events); } } else {