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 {