Browse Source

Simplify use of SwitchInstruction in ILAst

* the default case is now handled as a normal case
* when dealing with basic blocks, SwitchInstruction will be the last instruction in the block
* introduced ILAst instruction for 'goto case'
pull/887/head
Daniel Grunwald 8 years ago
parent
commit
8a68a94d35
  1. 26
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  2. 16
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  3. 11
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  4. 39
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  5. 4
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
  6. 23
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs
  7. 37
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
  8. 5
      ICSharpCode.Decompiler/IL/ILReader.cs
  9. 62
      ICSharpCode.Decompiler/IL/Instructions.cs
  10. 9
      ICSharpCode.Decompiler/IL/Instructions.tt
  11. 15
      ICSharpCode.Decompiler/IL/Instructions/Branch.cs
  12. 109
      ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs
  13. 1
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

26
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -112,21 +112,29 @@ namespace ICSharpCode.Decompiler.CSharp @@ -112,21 +112,29 @@ namespace ICSharpCode.Decompiler.CSharp
value = exprBuilder.Translate(inst.Value);
}
var stmt = new SwitchStatement() { Expression = value };
// Pick the section with the most labels as default section.
IL.SwitchSection defaultSection = inst.Sections.First();
foreach (var section in inst.Sections) {
if (section.Labels.Count() > defaultSection.Labels.Count()) {
defaultSection = section;
}
}
var stmt = new SwitchStatement() { Expression = value };
foreach (var section in inst.Sections) {
var astSection = new Syntax.SwitchSection();
astSection.CaseLabels.AddRange(section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type, strToInt?.Map)));
if (section == defaultSection) {
astSection.CaseLabels.Add(new CaseLabel());
} else {
if (section.HasNullLabel) {
astSection.CaseLabels.Add(new CaseLabel(new NullReferenceExpression()));
}
astSection.CaseLabels.AddRange(section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type, strToInt?.Map)));
}
ConvertSwitchSectionBody(astSection, section.Body);
stmt.SwitchSections.Add(astSection);
}
if (inst.DefaultBody.OpCode != OpCode.Nop) {
var astSection = new Syntax.SwitchSection();
astSection.CaseLabels.Add(new CaseLabel());
ConvertSwitchSectionBody(astSection, inst.DefaultBody);
stmt.SwitchSections.Add(astSection);
}
breakTarget = oldBreakTarget;
return stmt;
}

16
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -614,17 +614,27 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -614,17 +614,27 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
DebugStartPoint(inst);
inst.Value.AcceptVisitor(this);
State beforeSections = state.Clone();
inst.DefaultBody.AcceptVisitor(this);
inst.Sections[0].AcceptVisitor(this);
State afterSections = state.Clone();
foreach (var section in inst.Sections) {
for (int i = 1; i < inst.Sections.Count; ++i) {
state.ReplaceWith(beforeSections);
section.AcceptVisitor(this);
inst.Sections[i].AcceptVisitor(this);
afterSections.JoinWith(state);
}
state = afterSections;
DebugEndPoint(inst);
}
protected internal override void VisitGotoCase(GotoCase inst)
{
// Handling goto case here is tricky:
// We'll need a fixed-point iteration for SwitchInstruction similar to BlockContainer,
// and we'll need to handle GotoCase like Branch, including stuff like ProcessBranchesLeavingTryFinally().
// Hopefully we won't need a data-flow analysis in the decompiler pipeline after 'goto case' instructions
// are already introduced.
throw new NotImplementedException();
}
protected internal override void VisitYieldReturn(YieldReturn inst)
{
DebugStartPoint(inst);

11
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -163,8 +163,15 @@ namespace ICSharpCode.Decompiler.IL @@ -163,8 +163,15 @@ namespace ICSharpCode.Decompiler.IL
if (currentBlock == null)
return;
currentBlock.ILRange = new Interval(currentBlock.ILRange.Start, currentILOffset);
if (fallthrough)
currentBlock.Instructions.Add(new Branch(currentILOffset));
if (fallthrough) {
if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) {
// Instead of putting the default branch after the switch instruction
switchInst.Sections.Last().Body = new Branch(currentILOffset);
Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable));
} else {
currentBlock.Instructions.Add(new Branch(currentILOffset));
}
}
currentBlock = null;
}

39
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -63,18 +63,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -63,18 +63,17 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Last instruction is one with unreachable endpoint
// (guaranteed by combination of BlockContainer and Block invariants)
Debug.Assert(block.Instructions.Last().HasFlag(InstructionFlags.EndPointUnreachable));
ILInstruction exitInst = block.Instructions.Last();
if (exitInst is SwitchInstruction switchInst) {
HandleSwitchInstruction(cfgNode, block, switchInst);
}
// Previous-to-last instruction might have conditional control flow,
// usually an IfInstruction with a branch:
IfInstruction ifInst = block.Instructions.SecondToLastOrDefault() as IfInstruction;
if (ifInst != null && ifInst.FalseInst.OpCode == OpCode.Nop) {
HandleIfInstruction(cfgNode, block, ifInst, ref exitInst);
} else {
SwitchInstruction switchInst = block.Instructions.SecondToLastOrDefault() as SwitchInstruction;
if (switchInst != null) {
HandleSwitchInstruction(cfgNode, block, switchInst, ref exitInst);
}
}
if (IsUsableBranchToChild(cfgNode, exitInst)) {
// "...; goto usableblock;"
@ -289,9 +288,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -289,9 +288,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& cfgNode.Dominates(context.ControlFlowGraph.GetNode(targetBlock));
}
private void HandleSwitchInstruction(ControlFlowNode cfgNode, Block block, SwitchInstruction sw, ref ILInstruction exitInst)
private void HandleSwitchInstruction(ControlFlowNode cfgNode, Block block, SwitchInstruction sw)
{
Debug.Assert(sw.DefaultBody is Nop);
// First, move blocks into the switch section
foreach (var section in sw.Sections) {
if (IsUsableBranchToChild(cfgNode, section.Body)) {
@ -301,32 +299,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -301,32 +299,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
section.Body = targetBlock;
}
}
// Move the code following the switch into the default section
if (IsUsableBranchToChild(cfgNode, exitInst)) {
// switch(...){} goto targetBlock;
// ---> switch(..) { default: { targetBlock } }
var targetBlock = ((Branch)exitInst).TargetBlock;
targetBlock.Remove();
sw.DefaultBody = targetBlock;
if (IsBranchOrLeave(targetBlock.Instructions.Last())) {
exitInst = block.Instructions[block.Instructions.Count - 1] = targetBlock.Instructions.Last();
targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1);
} else {
exitInst = null;
block.Instructions.RemoveAt(block.Instructions.Count - 1);
}
}
// Remove compatible exitInsts from switch sections:
foreach (var section in sw.Sections) {
Block sectionBlock = section.Body as Block;
if (sectionBlock != null && exitInst == null && IsBranchOrLeave(sectionBlock.Instructions.Last())) {
exitInst = sectionBlock.Instructions.Last();
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
block.Instructions.Add(exitInst);
} else if (sectionBlock != null && DetectExitPoints.CompatibleExitInstruction(exitInst, sectionBlock.Instructions.Last())) {
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
}
}
// TODO: find a common exit point among the cases, and if possible inline that into this block.
sw.Sections.ReplaceList(sw.Sections.OrderBy(s => s.Body.ILRange.Start));
}

4
ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

@ -140,7 +140,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -140,7 +140,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
SymbolicValue val = evalContext.Eval(switchInst.Value);
if (val.Type != SymbolicValueType.State)
goto default;
List<LongInterval> allSectionLabels = new List<LongInterval>();
List<LongInterval> exitIntervals = new List<LongInterval>();
foreach (var section in switchInst.Sections) {
// switch (state + Constant)
@ -148,12 +147,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -148,12 +147,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// iff (state + Constant == value)
// iff (state == value - Constant)
var effectiveLabels = section.Labels.AddOffset(unchecked(-val.Constant));
allSectionLabels.AddRange(effectiveLabels.Intervals);
var result = AssignStateRanges(section.Body, stateRange.IntersectWith(effectiveLabels));
exitIntervals.AddRange(result.Intervals);
}
var defaultSectionLabels = stateRange.ExceptWith(new LongSet(allSectionLabels));
exitIntervals.AddRange(AssignStateRanges(switchInst.DefaultBody, defaultSectionLabels).Intervals);
// exitIntervals = union of exits of all sections
return new LongSet(exitIntervals);
case IfInstruction ifInst:

23
ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs

@ -94,25 +94,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -94,25 +94,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// If false, analyze the whole block.</param>
bool AnalyzeBlock(Block block, LongSet inputValues, bool tailOnly = false)
{
if (block.Instructions.Count == 0) {
// might happen if the block was already marked for deletion in SwitchDetection
return 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)
if (block.Instructions.Count >= 2
&& block.Instructions[block.Instructions.Count - 2].MatchIfInstruction(out var condition, out var trueInst)
&& AnalyzeCondition(condition, out trueValues)
) {
if (!(tailOnly || block.Instructions.Count == 2))
return false;
trueValues = trueValues.IntersectWith(inputValues);
Block trueBlock;
if (trueInst.MatchBranch(out trueBlock) && AnalyzeBlock(trueBlock, trueValues)) {
@ -122,8 +123,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -122,8 +123,10 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Create switch section for trueInst.
AddSection(trueValues, trueInst);
}
} else if (inst.OpCode == OpCode.SwitchInstruction) {
if (AnalyzeSwitch((SwitchInstruction)inst, inputValues, out trueValues)) {
} else if (block.Instructions.Last() is SwitchInstruction switchInst) {
if (!(tailOnly || block.Instructions.Count == 1))
return false;
if (AnalyzeSwitch(switchInst, inputValues, out trueValues)) {
ContainsILSwitch = true; // OK
} else { // switch analysis failed (e.g. switchVar mismatch)
return false;
@ -147,7 +150,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -147,7 +150,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
private bool AnalyzeSwitch(SwitchInstruction inst, LongSet inputValues, out LongSet anyMatchValues)
{
Debug.Assert(inst.DefaultBody is Nop);
Debug.Assert(!inst.IsLifted);
anyMatchValues = LongSet.Empty;
long offset;
if (MatchSwitchVar(inst.Value)) {

37
ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs

@ -60,16 +60,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -60,16 +60,19 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
var sw = new SwitchInstruction(new LdLoc(analysis.SwitchVariable));
foreach (var section in analysis.Sections) {
if (!section.Key.SetEquals(defaultSection.Key)) {
sw.Sections.Add(new SwitchSection
{
Labels = section.Key,
Body = section.Value
});
}
sw.Sections.Add(new SwitchSection {
Labels = section.Key,
Body = section.Value
});
}
if (block.Instructions.Last() is Branch) {
Debug.Assert(block.Instructions.SecondToLastOrDefault() is IfInstruction);
block.Instructions.RemoveAt(block.Instructions.Count - 1);
} else {
Debug.Assert(block.Instructions.Last() is SwitchInstruction);
}
block.Instructions[block.Instructions.Count - 2] = sw;
block.Instructions[block.Instructions.Count - 1] = defaultSection.Value;
block.Instructions[block.Instructions.Count - 1] = sw;
// mark all inner blocks that were converted to the switch statement for deletion
foreach (var innerBlock in analysis.InnerBlocks) {
Debug.Assert(innerBlock.Parent == block.Parent);
@ -87,8 +90,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -87,8 +90,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
internal static void SimplifySwitchInstruction(Block block)
{
// due to our of of basic blocks at this point,
// switch instructions can only appear as second-to-last insturction
var sw = block.Instructions.SecondToLastOrDefault() as SwitchInstruction;
// switch instructions can only appear as last insturction
var sw = block.Instructions.LastOrDefault() as SwitchInstruction;
if (sw == null)
return;
@ -96,19 +99,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -96,19 +99,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Any switch instructions will only have branch instructions in the sections.
// Combine sections with identical branch target:
block.Instructions.Last().MatchBranch(out Block defaultTarget);
var dict = new Dictionary<Block, SwitchSection>(); // branch target -> switch section
sw.Sections.RemoveAll(
section => {
Block target;
if (section.Body.MatchBranch(out target)) {
SwitchSection primarySection;
if (target == defaultTarget) {
// This section is just an alternative for 'default'.
Debug.Assert(sw.DefaultBody is Nop);
return true; // remove this section
} else if (dict.TryGetValue(target, out primarySection)) {
if (section.Body.MatchBranch(out Block target)) {
if (dict.TryGetValue(target, out SwitchSection primarySection)) {
primarySection.Labels = primarySection.Labels.UnionWith(section.Labels);
primarySection.HasNullLabel |= section.HasNullLabel;
return true; // remove this section
} else {
dict.Add(target, section);

5
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1232,7 +1232,10 @@ namespace ICSharpCode.Decompiler.IL @@ -1232,7 +1232,10 @@ namespace ICSharpCode.Decompiler.IL
section.Body = new Branch(target);
instr.Sections.Add(section);
}
var defaultSection = new SwitchSection();
defaultSection.Labels = new LongSet(new LongInterval(0, labels.Length)).Invert();
defaultSection.Body = new Nop();
instr.Sections.Add(defaultSection);
return instr;
}

62
ICSharpCode.Decompiler/IL/Instructions.cs

@ -63,6 +63,8 @@ namespace ICSharpCode.Decompiler.IL @@ -63,6 +63,8 @@ namespace ICSharpCode.Decompiler.IL
SwitchInstruction,
/// <summary>Switch section within a switch statement</summary>
SwitchSection,
/// <summary>Unconditional branch to switch section. <c>goto case target;</c></summary>
GotoCase,
/// <summary>Try-catch statement.</summary>
TryCatch,
/// <summary>Catch handler within a try-catch statement.</summary>
@ -1014,6 +1016,15 @@ namespace ICSharpCode.Decompiler.IL @@ -1014,6 +1016,15 @@ namespace ICSharpCode.Decompiler.IL
public sealed partial class Branch : SimpleInstruction
{
public override StackType ResultType { get { return StackType.Void; } }
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.EndPointUnreachable | InstructionFlags.MayBranch;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.EndPointUnreachable | InstructionFlags.MayBranch;
}
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitBranch(this);
@ -1319,7 +1330,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1319,7 +1330,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as SwitchInstruction;
return o != null && Value.PerformMatch(o.Value, ref match) && DefaultBody.PerformMatch(o.DefaultBody, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match);
return o != null && IsLifted == o.IsLifted && Value.PerformMatch(o.Value, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match);
}
}
}
@ -1391,7 +1402,41 @@ namespace ICSharpCode.Decompiler.IL @@ -1391,7 +1402,41 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as SwitchSection;
return o != null && this.body.PerformMatch(o.body, ref match) && this.Labels.Intervals.SequenceEqual(o.Labels.Intervals);
return o != null && this.body.PerformMatch(o.body, ref match) && this.Labels.SetEquals(o.Labels) && this.HasNullLabel == o.HasNullLabel;
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Unconditional branch to switch section. <c>goto case target;</c></summary>
public sealed partial class GotoCase : SimpleInstruction
{
public override StackType ResultType { get { return StackType.Void; } }
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.EndPointUnreachable | InstructionFlags.MayBranch;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.EndPointUnreachable | InstructionFlags.MayBranch;
}
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitGotoCase(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitGotoCase(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitGotoCase(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as GotoCase;
return o != null && this.TargetSection == o.TargetSection;
}
}
}
@ -4528,6 +4573,10 @@ namespace ICSharpCode.Decompiler.IL @@ -4528,6 +4573,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitGotoCase(GotoCase inst)
{
Default(inst);
}
protected internal virtual void VisitTryCatch(TryCatch inst)
{
Default(inst);
@ -4818,6 +4867,10 @@ namespace ICSharpCode.Decompiler.IL @@ -4818,6 +4867,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitGotoCase(GotoCase inst)
{
return Default(inst);
}
protected internal virtual T VisitTryCatch(TryCatch inst)
{
return Default(inst);
@ -5108,6 +5161,10 @@ namespace ICSharpCode.Decompiler.IL @@ -5108,6 +5161,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
protected internal virtual T VisitGotoCase(GotoCase inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitTryCatch(TryCatch inst, C context)
{
return Default(inst, context);
@ -5342,6 +5399,7 @@ namespace ICSharpCode.Decompiler.IL @@ -5342,6 +5399,7 @@ namespace ICSharpCode.Decompiler.IL
"if.notnull",
"switch",
"switch.section",
"goto.case",
"try.catch",
"try.catch.handler",
"try.finally",

9
ICSharpCode.Decompiler/IL/Instructions.tt

@ -73,7 +73,7 @@ @@ -73,7 +73,7 @@
new OpCode("bit.not", "Bitwise NOT", Unary, CustomConstructor, MatchCondition("IsLifted == o.IsLifted && UnderlyingResultType == o.UnderlyingResultType")),
new OpCode("arglist", "Retrieves the RuntimeArgumentHandle.", NoArguments, ResultType("O")),
new OpCode("br", "Unconditional branch. <c>goto target;</c>",
CustomClassName("Branch"), NoArguments, CustomConstructor, UnconditionalBranch, MayBranch, CustomComputeFlags,
CustomClassName("Branch"), NoArguments, CustomConstructor, UnconditionalBranch, MayBranch,
MatchCondition("this.TargetBlock == o.TargetBlock")),
new OpCode("leave", "Unconditional branch to end of block container. Return is represented using IsLeavingFunction and an (optional) return value. The block container evaluates to the value produced by the argument of the leave instruction.",
CustomConstructor, CustomArguments("value"), UnconditionalBranch, MayBranch, CustomWriteTo, CustomComputeFlags,
@ -93,11 +93,14 @@ @@ -93,11 +93,14 @@
}), CustomConstructor, CustomComputeFlags, CustomWriteTo),
new OpCode("switch", "Switch statement",
CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"),
MatchCondition("Value.PerformMatch(o.Value, ref match) && DefaultBody.PerformMatch(o.DefaultBody, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")),
MatchCondition("IsLifted == o.IsLifted && Value.PerformMatch(o.Value, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")),
new OpCode("switch.section", "Switch section within a switch statement",
CustomClassName("SwitchSection"), CustomChildren(new [] { new ChildInfo("body") }),
CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"),
MatchCondition("this.Labels.Intervals.SequenceEqual(o.Labels.Intervals)")),
MatchCondition("this.Labels.SetEquals(o.Labels) && this.HasNullLabel == o.HasNullLabel")),
new OpCode("goto.case", "Unconditional branch to switch section. <c>goto case target;</c>",
CustomClassName("GotoCase"), NoArguments, CustomConstructor, UnconditionalBranch, MayBranch,
MatchCondition("this.TargetSection == o.TargetSection")),
new OpCode("try.catch", "Try-catch statement.",
BaseClass("TryInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo,
MatchCondition("TryBlock.PerformMatch(o.TryBlock, ref match)"),

15
ICSharpCode.Decompiler/IL/Instructions/Branch.cs

@ -39,23 +39,10 @@ namespace ICSharpCode.Decompiler.IL @@ -39,23 +39,10 @@ namespace ICSharpCode.Decompiler.IL
public Branch(Block targetBlock) : base(OpCode.Branch)
{
if (targetBlock == null)
throw new ArgumentNullException(nameof(targetBlock));
this.targetBlock = targetBlock;
this.targetBlock = targetBlock ?? throw new ArgumentNullException(nameof(targetBlock));
this.targetILOffset = targetBlock.ILRange.Start;
}
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
}
public int TargetILOffset {
get { return targetBlock != null ? targetBlock.ILRange.Start : targetILOffset; }
}

109
ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.Util;
@ -32,15 +33,19 @@ namespace ICSharpCode.Decompiler.IL @@ -32,15 +33,19 @@ namespace ICSharpCode.Decompiler.IL
partial class SwitchInstruction
{
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
public static readonly SlotInfo DefaultBodySlot = new SlotInfo("DefaultBody");
public static readonly SlotInfo SectionSlot = new SlotInfo("Section", isCollection: true);
/// <summary>
/// If the switch instruction is lifted, the value instruction produces a value of type <c>Nullable{T}</c> for some
/// integral type T. The section with <c>SwitchSection.HasNullLabel</c> is called if the value is null.
/// </summary>
public bool IsLifted;
public SwitchInstruction(ILInstruction value)
: base(OpCode.SwitchInstruction)
{
this.Value = value;
this.DefaultBody = new Nop();
this.Sections = new InstructionCollection<SwitchSection>(this, 2);
this.Sections = new InstructionCollection<SwitchSection>(this, 1);
}
ILInstruction value;
@ -52,21 +57,11 @@ namespace ICSharpCode.Decompiler.IL @@ -52,21 +57,11 @@ namespace ICSharpCode.Decompiler.IL
}
}
ILInstruction defaultBody;
public ILInstruction DefaultBody {
get { return this.defaultBody; }
set {
ValidateChild(value);
SetChildInstruction(ref this.defaultBody, value, 1);
}
}
public readonly InstructionCollection<SwitchSection> Sections;
protected override InstructionFlags ComputeFlags()
{
var sectionFlags = defaultBody.Flags;
var sectionFlags = InstructionFlags.EndPointUnreachable; // neutral element for CombineBranches()
foreach (var section in Sections) {
sectionFlags = SemanticHelper.CombineBranches(sectionFlags, section.Flags);
}
@ -81,15 +76,15 @@ namespace ICSharpCode.Decompiler.IL @@ -81,15 +76,15 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("switch (");
output.Write("switch");
if (IsLifted)
output.Write(".lifted");
output.Write(" (");
value.WriteTo(output, options);
output.Write(") ");
output.MarkFoldStart("{...}");
output.WriteLine("{");
output.Indent();
output.Write("default: ");
defaultBody.WriteTo(output, options);
output.WriteLine();
foreach (var section in this.Sections) {
section.WriteTo(output, options);
output.WriteLine();
@ -101,34 +96,28 @@ namespace ICSharpCode.Decompiler.IL @@ -101,34 +96,28 @@ namespace ICSharpCode.Decompiler.IL
protected override int GetChildCount()
{
return 2 + Sections.Count;
return 1 + Sections.Count;
}
protected override ILInstruction GetChild(int index)
{
if (index == 0)
return value;
else if (index == 1)
return defaultBody;
return Sections[index - 2];
return Sections[index - 1];
}
protected override void SetChild(int index, ILInstruction value)
{
if (index == 0)
Value = value;
else if (index == 1)
DefaultBody = value;
else
Sections[index - 2] = (SwitchSection)value;
Sections[index - 1] = (SwitchSection)value;
}
protected override SlotInfo GetChildSlot(int index)
{
if (index == 0)
return ValueSlot;
else if (index == 1)
return DefaultBodySlot;
return SectionSlot;
}
@ -137,7 +126,6 @@ namespace ICSharpCode.Decompiler.IL @@ -137,7 +126,6 @@ namespace ICSharpCode.Decompiler.IL
var clone = new SwitchInstruction(value.Clone());
clone.ILRange = this.ILRange;
clone.Value = value.Clone();
this.DefaultBody = defaultBody.Clone();
clone.Sections.AddRange(this.Sections.Select(h => (SwitchSection)h.Clone()));
return clone;
}
@ -145,12 +133,19 @@ namespace ICSharpCode.Decompiler.IL @@ -145,12 +133,19 @@ namespace ICSharpCode.Decompiler.IL
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
bool expectNullSection = this.IsLifted;
LongSet sets = LongSet.Empty;
foreach (var section in Sections) {
Debug.Assert(!section.Labels.IsEmpty);
if (section.HasNullLabel) {
Debug.Assert(expectNullSection, "Duplicate 'case null' or 'case null' in non-lifted switch.");
expectNullSection = false;
}
Debug.Assert(!section.Labels.IsEmpty || section.HasNullLabel);
Debug.Assert(!section.Labels.Overlaps(sets));
sets = sets.UnionWith(section.Labels);
}
Debug.Assert(sets.SetEquals(LongSet.Universe), "switch does not handle all possible cases");
Debug.Assert(!expectNullSection, "Lifted switch is missing 'case null'");
}
}
@ -162,6 +157,14 @@ namespace ICSharpCode.Decompiler.IL @@ -162,6 +157,14 @@ namespace ICSharpCode.Decompiler.IL
this.Labels = LongSet.Empty;
}
/// <summary>
/// If true, serves as 'case null' in a lifted switch.
/// </summary>
public bool HasNullLabel { get; set; }
/// <summary>
/// The set of labels that cause execution to jump to this switch section.
/// </summary>
public LongSet Labels { get; set; }
protected override InstructionFlags ComputeFlags()
@ -177,11 +180,53 @@ namespace ICSharpCode.Decompiler.IL @@ -177,11 +180,53 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("case ");
output.Write(Labels.ToString());
output.WriteDefinition("case", this, isLocal: true);
output.Write(' ');
if (HasNullLabel) {
output.Write("null");
if (!Labels.IsEmpty) {
output.Write(", ");
output.Write(Labels.ToString());
}
} else {
output.Write(Labels.ToString());
}
output.Write(": ");
body.WriteTo(output, options);
}
}
/// <summary>
/// Unconditional branch to switch section. <c>goto case target;</c>/<c>goto default;</c>
/// </summary>
/// <remarks>
/// Like normal branches, can trigger finally blocks.
/// </remarks>
partial class GotoCase // : IBranchOrLeaveInstruction
{
public SwitchSection TargetSection { get; }
public GotoCase(SwitchSection target) : base(OpCode.GotoCase)
{
this.TargetSection = target ?? throw new ArgumentNullException(nameof(target));
}
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
var parentSwitch = this.Ancestors.OfType<SwitchInstruction>().First();
Debug.Assert(parentSwitch == TargetSection.Parent);
}
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("goto.case ");
if (TargetSection.HasNullLabel) {
output.WriteReference("null", TargetSection, isLocal: true);
} else {
output.WriteReference(TargetSection.Labels.Values.First().ToString(), TargetSection, isLocal: true);
}
}
}
}

1
ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

@ -283,7 +283,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -283,7 +283,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
var stringToInt = new StringToInt(new LdLoc(switchValueVar), stringValues.ToArray());
inst = new SwitchInstruction(stringToInt);
inst.DefaultBody = switchInst.DefaultBody;
inst.Sections.AddRange(sections);
blockAfterSwitch = defaultBlock;
return true;

Loading…
Cancel
Save