From 8a68a94d355302d4db0f144a02791aa80b6ab24e Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Sat, 7 Oct 2017 00:13:14 +0200 Subject: [PATCH] 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' --- .../CSharp/StatementBuilder.cs | 26 +++-- .../FlowAnalysis/DataFlowVisitor.cs | 16 ++- ICSharpCode.Decompiler/IL/BlockBuilder.cs | 11 +- .../IL/ControlFlow/ConditionDetection.cs | 39 +------ .../IL/ControlFlow/StateRangeAnalysis.cs | 4 - .../IL/ControlFlow/SwitchAnalysis.cs | 23 ++-- .../IL/ControlFlow/SwitchDetection.cs | 37 +++--- ICSharpCode.Decompiler/IL/ILReader.cs | 5 +- ICSharpCode.Decompiler/IL/Instructions.cs | 62 +++++++++- ICSharpCode.Decompiler/IL/Instructions.tt | 9 +- .../IL/Instructions/Branch.cs | 15 +-- .../IL/Instructions/SwitchInstruction.cs | 109 +++++++++++++----- .../IL/Transforms/SwitchOnStringTransform.cs | 1 - 13 files changed, 223 insertions(+), 134 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs index d5923cb3e..3ddb00fe9 100644 --- a/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/StatementBuilder.cs @@ -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; } diff --git a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs index 2ac18d313..0632edc79 100644 --- a/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs +++ b/ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs @@ -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); diff --git a/ICSharpCode.Decompiler/IL/BlockBuilder.cs b/ICSharpCode.Decompiler/IL/BlockBuilder.cs index 2ba957cb4..46e51c794 100644 --- a/ICSharpCode.Decompiler/IL/BlockBuilder.cs +++ b/ICSharpCode.Decompiler/IL/BlockBuilder.cs @@ -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; } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs index 48ecdf218..21da91022 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs @@ -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 && 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 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)); } diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs index fa4fa097c..9ef815526 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs @@ -140,7 +140,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow SymbolicValue val = evalContext.Eval(switchInst.Value); if (val.Type != SymbolicValueType.State) goto default; - List allSectionLabels = new List(); List exitIntervals = new List(); foreach (var section in switchInst.Sections) { // switch (state + Constant) @@ -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: diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs index c280faa02..962c76f3f 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs @@ -94,25 +94,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow /// If false, analyze the whole block. 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 // 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 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)) { diff --git a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs index 897d41e7e..5c31baa9a 100644 --- a/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs +++ b/ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs @@ -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 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 // 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(); // 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); diff --git a/ICSharpCode.Decompiler/IL/ILReader.cs b/ICSharpCode.Decompiler/IL/ILReader.cs index b401f5c5d..9ed9cb9ad 100644 --- a/ICSharpCode.Decompiler/IL/ILReader.cs +++ b/ICSharpCode.Decompiler/IL/ILReader.cs @@ -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; } diff --git a/ICSharpCode.Decompiler/IL/Instructions.cs b/ICSharpCode.Decompiler/IL/Instructions.cs index c6f361279..ccb7bdedf 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.cs +++ b/ICSharpCode.Decompiler/IL/Instructions.cs @@ -63,6 +63,8 @@ namespace ICSharpCode.Decompiler.IL SwitchInstruction, /// Switch section within a switch statement SwitchSection, + /// Unconditional branch to switch section. goto case target; + GotoCase, /// Try-catch statement. TryCatch, /// Catch handler within a try-catch statement. @@ -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 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 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 +{ + /// Unconditional branch to switch section. goto case target; + 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(ILVisitor visitor) + { + return visitor.VisitGotoCase(this); + } + public override T AcceptVisitor(ILVisitor 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 { 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 { 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 { 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 "if.notnull", "switch", "switch.section", + "goto.case", "try.catch", "try.catch.handler", "try.finally", diff --git a/ICSharpCode.Decompiler/IL/Instructions.tt b/ICSharpCode.Decompiler/IL/Instructions.tt index f25e3fa0d..c75172ddf 100644 --- a/ICSharpCode.Decompiler/IL/Instructions.tt +++ b/ICSharpCode.Decompiler/IL/Instructions.tt @@ -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. goto target;", - 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 @@ }), 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. goto case target;", + 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)"), diff --git a/ICSharpCode.Decompiler/IL/Instructions/Branch.cs b/ICSharpCode.Decompiler/IL/Instructions/Branch.cs index 8cfab388b..7085ab6a1 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Branch.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Branch.cs @@ -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; } } diff --git a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs index 8e22ffe20..5de07f592 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs @@ -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 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); - + + /// + /// If the switch instruction is lifted, the value instruction produces a value of type Nullable{T} for some + /// integral type T. The section with SwitchSection.HasNullLabel is called if the value is null. + /// + public bool IsLifted; + public SwitchInstruction(ILInstruction value) : base(OpCode.SwitchInstruction) { this.Value = value; - this.DefaultBody = new Nop(); - this.Sections = new InstructionCollection(this, 2); + this.Sections = new InstructionCollection(this, 1); } ILInstruction value; @@ -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 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 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 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 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 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 this.Labels = LongSet.Empty; } + /// + /// If true, serves as 'case null' in a lifted switch. + /// + public bool HasNullLabel { get; set; } + + /// + /// The set of labels that cause execution to jump to this switch section. + /// public LongSet Labels { get; set; } protected override InstructionFlags ComputeFlags() @@ -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); } } + + /// + /// Unconditional branch to switch section. goto case target;/goto default; + /// + /// + /// Like normal branches, can trigger finally blocks. + /// + 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().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); + } + } + } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index dc35b6158..0b7d27359 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -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;