diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs index cdecec92f..f28e691cf 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs @@ -26,6 +26,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness { TestCase(SparseIntegerSwitch, -100, 1, 2, 3, 4); TestCase(ShortSwitchOverString, "First case", "Else"); + TestCase(ShortSwitchOverString2, "First case", "Second case", "Third case", "Else"); + TestCase(ShortSwitchOverStringNoExplicitDefault, "First case", "Second case", "Third case", "Else"); TestCase(SwitchOverString1, "First case", "Second case", "2nd case", "Third case", "Fourth case", "Fifth case", "Sixth case", null, "default", "else"); Console.WriteLine(SwitchOverString2()); Console.WriteLine(SwitchOverBool(true)); @@ -74,6 +76,35 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness } } + public static string ShortSwitchOverString2(string text) + { + Console.WriteLine("ShortSwitchOverString2: " + text); + switch (text) { + case "First case": + return "Text1"; + case "Second case": + return "Text2"; + case "Third case": + return "Text3"; + default: + return "Default"; + } + } + + public static string ShortSwitchOverStringNoExplicitDefault(string text) + { + Console.WriteLine("ShortSwitchOverStringNoExplicitDefault: " + text); + switch (text) { + case "First case": + return "Text1"; + case "Second case": + return "Text2"; + case "Third case": + return "Text3"; + } + return "Default"; + } + public static string SwitchOverString1(string text) { Console.WriteLine("SwitchOverString1: " + text); diff --git a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs index ee6f2b6a7..b533e8d3b 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.IL.Transforms { @@ -34,7 +35,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms for (int i = block.Instructions.Count - 1; i >= 0; i--) { SwitchInstruction newSwitch; Block blockAfterSwitch = null; - if (!MatchLegacySwitchOnString(block.Instructions, i, out newSwitch, out blockAfterSwitch) && + if (!MatchCascadingIfStatements(block.Instructions, i, out newSwitch, out blockAfterSwitch) && + !MatchLegacySwitchOnString(block.Instructions, i, out newSwitch, out blockAfterSwitch) && !MatchRoslynSwitchOnString(block.Instructions, i, out newSwitch)) continue; @@ -43,9 +45,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms } block.Instructions[i].ReplaceWith(newSwitch); - block.Instructions.RemoveAt(i - 1); - - i--; + if (newSwitch.Value.MatchLdLoc(out var switchVar) && !block.Instructions[i - 1].MatchLdLoc(switchVar)) { + block.Instructions.RemoveAt(i - 1); + i--; + } // This happens in some cases: // Use correct index after transformation. @@ -61,6 +64,79 @@ namespace ICSharpCode.Decompiler.IL.Transforms container.SortBlocks(deleteUnreachableBlocks: true); } + bool MatchCascadingIfStatements(InstructionCollection instructions, int i, out SwitchInstruction inst, out Block blockAfterSwitch) + { + inst = null; + blockAfterSwitch = null; + if (i < 1) return false; + // match first block: checking switch-value for null or first value (Roslyn) + if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump) && + instructions[i - 1].MatchStLoc(out var switchValueVar, out var switchValue) && switchValueVar.Type.IsKnownType(KnownTypeCode.String))) + return false; + if (!firstBlockJump.MatchBranch(out var firstBlock)) + return false; + bool isLegacy; + Block defaultBlock; + List<(string, Block)> values = new List<(string, Block)>(); + if (condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull() && left.MatchLdLoc(switchValueVar)) { + isLegacy = true; + defaultBlock = firstBlock; + } else if (MatchStringEqualityComparison(condition, switchValueVar, out string value)) { + isLegacy = false; + defaultBlock = null; + values.Add((value, firstBlock)); + } else return false; + if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump)) + return false; + Block currentCaseBlock = nextCaseJump.TargetBlock; + Block nextCaseBlock; + while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out Block block)) != null) { + values.Add((value, block)); + currentCaseBlock = nextCaseBlock; + } + if (!ExtractLastJumpFromBlock(currentCaseBlock, out var exitBlock)) + return false; + if (values.Count == 0) + return false; + if (!values.All(b => ExtractLastJumpFromBlock(b.Item2, out var nextExit) && nextExit == exitBlock)) + return false; + if (currentCaseBlock.IncomingEdgeCount == (isLegacy ? 2 : 1)) { + var sections = new List(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = new Branch(b.Item2) })); + var stringToInt = new StringToInt(new LdLoc(switchValueVar), values.SelectArray(item => item.Item1)); + inst = new SwitchInstruction(stringToInt); + inst.Sections.AddRange(sections); + blockAfterSwitch = currentCaseBlock; + return true; + } + return false; + } + + bool ExtractLastJumpFromBlock(Block block, out Block exitBlock) + { + exitBlock = null; + var lastInst = block.Instructions.LastOrDefault(); + if (lastInst == null || !lastInst.MatchBranch(out exitBlock)) + return false; + return true; + } + + Block MatchCaseBlock(Block currentBlock, ILVariable switchVariable, out string value, out Block caseBlock) + { + value = null; + caseBlock = null; + if (currentBlock.IncomingEdgeCount != 1 || currentBlock.Instructions.Count != 2) + return null; + if (!currentBlock.Instructions[0].MatchIfInstruction(out var condition, out var caseBlockBranch)) + return null; + if (!caseBlockBranch.MatchBranch(out caseBlock)) + return null; + if (!MatchStringEqualityComparison(condition, switchVariable, out value)) + return null; + if (!currentBlock.Instructions[1].MatchBranch(out var nextBlock)) + return null; + return nextBlock; + } + bool MatchLegacySwitchOnString(InstructionCollection instructions, int i, out SwitchInstruction inst, out Block blockAfterSwitch) { inst = null;