Browse Source

[switch] Add basic SwitchOnStringTransform for Roslyn switch on strings.

pull/887/head
Siegfried Pammer 8 years ago
parent
commit
ddd5f43b41
  1. 89
      ICSharpCode.Decompiler.Tests/Switch.cs
  2. 38
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs
  3. 1
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  4. 17
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  5. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  6. 114
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

89
ICSharpCode.Decompiler.Tests/Switch.cs

@ -1,89 +0,0 @@ @@ -1,89 +0,0 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// 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;
public static class Switch
{
public static string ShortSwitchOverString(string text)
{
switch (text) {
case "First case":
return "Text";
default:
return "Default";
}
}
public static string SwitchOverString1(string text)
{
switch (text)
{
case "First case":
return "Text1";
case "Second case":
case "2nd case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
case null:
return null;
default:
return "Default";
}
}
public static string SwitchOverString2()
{
switch (Environment.UserName)
{
case "First case":
return "Text1";
case "Second case":
return "Text2";
case "Third case":
return "Text3";
case "Fourth case":
return "Text4";
case "Fifth case":
return "Text5";
case "Sixth case":
return "Text6";
default:
return "Default";
}
}
public static string SwitchOverBool(bool b)
{
switch (b) {
case true:
return bool.TrueString;
case false:
return bool.FalseString;
default:
return null;
}
}
}

38
ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs

@ -30,6 +30,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -30,6 +30,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
Console.WriteLine(SwitchOverString2());
Console.WriteLine(SwitchOverBool(true));
Console.WriteLine(SwitchOverBool(false));
SwitchInLoop(0);
SwitchWithGoto(1);
SwitchWithGoto(2);
SwitchWithGoto(3);
SwitchWithGoto(4);
}
static void TestCase<T>(Func<T, string> target, params T[] args)
@ -105,6 +110,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -105,6 +110,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
return "Text5";
case "Sixth case":
return "Text6";
case "Seventh case":
return "Text7";
case "Eighth case":
return "Text8";
case "Ninth case":
return "Text9";
case "Tenth case":
return "Text10";
case "Eleventh case":
return "Text11";
default:
return "Default";
}
@ -141,10 +156,31 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -141,10 +156,31 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
default:
Console.WriteLine("default");
Console.WriteLine("more code");
throw new ArgumentException();
return;
}
i++;
}
}
public static void SwitchWithGoto(int i)
{
switch (i) {
case 1:
Console.WriteLine("one");
goto default;
case 2:
Console.WriteLine("two");
goto case 3;
case 3:
Console.WriteLine("three");
break;
case 4:
Console.WriteLine("four");
return;
default:
Console.WriteLine("default");
break;
}
}
}
}

1
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -85,6 +85,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -85,6 +85,7 @@ namespace ICSharpCode.Decompiler.CSharp
new RemoveDeadVariableInit(),
new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements
new SwitchDetection(),
new SwitchOnStringTransform(),
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
// Even though it's a post-order block-transform as most other transforms,

17
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -83,11 +83,13 @@ namespace ICSharpCode.Decompiler.CSharp @@ -83,11 +83,13 @@ namespace ICSharpCode.Decompiler.CSharp
return new IfElseStatement(condition, trueStatement, falseStatement);
}
CaseLabel CreateTypedCaseLabel(long i, IType type)
CaseLabel CreateTypedCaseLabel(long i, IType type, string[] map = null)
{
object value;
if (type.IsKnownType(KnownTypeCode.Boolean)) {
value = i != 0;
} else if (type.IsKnownType(KnownTypeCode.String) && map != null) {
value = map[i];
} else if (type.Kind == TypeKind.Enum) {
var enumType = type.GetDefinition().EnumUnderlyingType;
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, false);
@ -101,12 +103,19 @@ namespace ICSharpCode.Decompiler.CSharp @@ -101,12 +103,19 @@ namespace ICSharpCode.Decompiler.CSharp
{
var oldBreakTarget = breakTarget;
breakTarget = null; // 'break' within a switch would only leave the switch
var value = exprBuilder.Translate(inst.Value);
TranslatedExpression value;
var strToInt = inst.Value as StringToInt;
if (strToInt != null) {
value = exprBuilder.Translate(strToInt.Argument);
} else {
value = exprBuilder.Translate(inst.Value);
}
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)));
astSection.CaseLabels.AddRange(section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type, strToInt?.Map)));
ConvertSwitchSectionBody(astSection, section.Body);
stmt.SwitchSections.Add(astSection);
}

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -273,6 +273,7 @@ @@ -273,6 +273,7 @@
<Compile Include="DotNetCore\UnresolvedAssemblyNameReference.cs" />
<Compile Include="IL\Instructions\StringToInt.cs" />
<Compile Include="IL\Instructions\UsingInstruction.cs" />
<Compile Include="IL\Transforms\SwitchOnStringTransform.cs" />
<Compile Include="IL\Transforms\UsingTransform.cs" />
<Compile Include="IL\ControlFlow\AsyncAwaitDecompiler.cs" />
<Compile Include="IL\ControlFlow\ControlFlowGraph.cs" />

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

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
// Copyright (c) 2017 Siegfried Pammer
//
// 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.Linq;
using System.Text;
namespace ICSharpCode.Decompiler.IL.Transforms
{
class SwitchOnStringTransform : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
foreach (var block in function.Descendants.OfType<Block>()) {
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (!MatchRoslynSwitchOnString(block.Instructions, i, out var newSwitch))
continue;
block.Instructions[i].ReplaceWith(newSwitch);
block.Instructions.RemoveAt(i - 1);
i--;
// This happens in some cases:
// Use correct index after transformation.
if (i >= block.Instructions.Count)
i = block.Instructions.Count;
}
if (block.Parent is BlockContainer container)
changedContainers.Add(container);
}
foreach (var container in changedContainers)
container.SortBlocks(deleteUnreachableBlocks: true);
}
bool MatchRoslynSwitchOnString(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction inst)
{
inst = null;
if (i < 1) return false;
if (!(instructions[i] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var targetVar) &&
MatchComputeStringHashCall(instructions[i - 1], targetVar, out var switchValue)))
return false;
var stringValues = new List<(int, string, Block)>();
int index = 0;
foreach (var section in switchInst.Sections) {
if (!section.Body.MatchBranch(out Block target))
return false;
if (target.IncomingEdgeCount != 1 || target.Instructions.Count == 0)
return false;
if (!target.Instructions[0].MatchIfInstruction(out var condition, out var bodyBranch))
return false;
if (!MatchStringEqualityComparison(condition, switchValue.Variable, out string stringValue))
return false;
if (!bodyBranch.MatchBranch(out Block body))
return false;
stringValues.Add((index++, stringValue, body));
}
var value = new StringToInt(switchValue.Clone(), stringValues.Select(item => item.Item2).ToArray());
inst = new SwitchInstruction(value);
inst.Sections.AddRange(stringValues.Select(section => new SwitchSection { Labels = new Util.LongSet(section.Item1), Body = new Branch(section.Item3) }));
return true;
}
bool MatchComputeStringHashCall(ILInstruction inst, ILVariable targetVar, out LdLoc switchValue)
{
switchValue = null;
if (!inst.MatchStLoc(targetVar, out var value))
return false;
if (!(value is Call c && c.Arguments.Count == 1 && c.Method.Name == "ComputeStringHash" && c.Method.IsCompilerGeneratedOrIsInCompilerGeneratedClass()))
return false;
if (!(c.Arguments[0] is LdLoc))
return false;
switchValue = (LdLoc)c.Arguments[0];
return true;
}
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue)
{
stringValue = null;
ILInstruction left, right;
if (condition is Call c && c.Method.IsOperator && c.Method.Name == "op_Equality" && c.Arguments.Count == 2) {
left = c.Arguments[0];
right = c.Arguments[1];
if (!right.MatchLdStr(out stringValue))
return false;
} else if (condition.MatchCompEquals(out left, out right) && right.MatchLdNull()) {
} else return false;
return left.MatchLdLoc(variable);
}
}
}
Loading…
Cancel
Save