Browse Source

Add support for mcs 2.6.4 switch-on-string

pull/1087/head
Siegfried Pammer 7 years ago
parent
commit
7544eac5b4
  1. 3
      ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs
  2. 4
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs
  3. 11
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  4. 26
      ICSharpCode.Decompiler/IL/Instructions/StringToInt.cs
  5. 25
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

3
ICSharpCode.Decompiler.Tests/CorrectnessTestRunner.cs

@ -115,9 +115,6 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public void Switch([ValueSource("defaultOptions")] CompilerOptions options) public void Switch([ValueSource("defaultOptions")] CompilerOptions options)
{ {
if (options.HasFlag(CompilerOptions.UseMcs)) {
Assert.Ignore("Decompiler bug with mono!");
}
RunCS(options: options); RunCS(options: options);
} }

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

@ -25,7 +25,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public static void Main() public static void Main()
{ {
TestCase(SparseIntegerSwitch, -100, 1, 2, 3, 4); TestCase(SparseIntegerSwitch, -100, 1, 2, 3, 4);
#if !MCS
TestCase(ShortSwitchOverString, "First case", "Else"); TestCase(ShortSwitchOverString, "First case", "Else");
#endif
TestCase(ShortSwitchOverString2, "First case", "Second case", "Third case", "Else"); TestCase(ShortSwitchOverString2, "First case", "Second case", "Third case", "Else");
TestCase(ShortSwitchOverStringNoExplicitDefault, "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"); TestCase(SwitchOverString1, "First case", "Second case", "2nd case", "Third case", "Fourth case", "Fifth case", "Sixth case", null, "default", "else");
@ -65,6 +67,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
} }
} }
#if !MCS
public static string ShortSwitchOverString(string text) public static string ShortSwitchOverString(string text)
{ {
Console.WriteLine("ShortSwitchOverString: " + text); Console.WriteLine("ShortSwitchOverString: " + text);
@ -75,6 +78,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
return "Default"; return "Default";
} }
} }
#endif
public static string ShortSwitchOverString2(string text) public static string ShortSwitchOverString2(string text)
{ {

11
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -83,7 +83,7 @@ namespace ICSharpCode.Decompiler.CSharp
return new IfElseStatement(condition, trueStatement, falseStatement); return new IfElseStatement(condition, trueStatement, falseStatement);
} }
ConstantResolveResult CreateTypedCaseLabel(long i, IType type, string[] map = null) IEnumerable<ConstantResolveResult> CreateTypedCaseLabel(long i, IType type, List<(string Key, int Value)> map = null)
{ {
object value; object value;
// unpack nullable type, if necessary: // unpack nullable type, if necessary:
@ -92,14 +92,17 @@ namespace ICSharpCode.Decompiler.CSharp
if (type.IsKnownType(KnownTypeCode.Boolean)) { if (type.IsKnownType(KnownTypeCode.Boolean)) {
value = i != 0; value = i != 0;
} else if (type.IsKnownType(KnownTypeCode.String) && map != null) { } else if (type.IsKnownType(KnownTypeCode.String) && map != null) {
value = map[i]; var keys = map.Where(entry => entry.Value == i).Select(entry => entry.Key);
foreach (var key in keys)
yield return new ConstantResolveResult(type, key);
yield break;
} else if (type.Kind == TypeKind.Enum) { } else if (type.Kind == TypeKind.Enum) {
var enumType = type.GetDefinition().EnumUnderlyingType; var enumType = type.GetDefinition().EnumUnderlyingType;
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, false); value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(enumType), i, false);
} else { } else {
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, false); value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, false);
} }
return new ConstantResolveResult(type, value); yield return new ConstantResolveResult(type, value);
} }
protected internal override Statement VisitSwitchInstruction(SwitchInstruction inst) protected internal override Statement VisitSwitchInstruction(SwitchInstruction inst)
@ -143,7 +146,7 @@ namespace ICSharpCode.Decompiler.CSharp
astSection.CaseLabels.Add(new CaseLabel()); astSection.CaseLabels.Add(new CaseLabel());
firstValueResolveResult = null; firstValueResolveResult = null;
} else { } else {
var values = section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type, strToInt?.Map)).ToArray(); var values = section.Labels.Values.SelectMany(i => CreateTypedCaseLabel(i, value.Type, strToInt?.Map)).ToArray();
if (section.HasNullLabel) { if (section.HasNullLabel) {
astSection.CaseLabels.Add(new CaseLabel(new NullReferenceExpression())); astSection.CaseLabels.Add(new CaseLabel(new NullReferenceExpression()));
firstValueResolveResult = new ConstantResolveResult(SpecialType.NullType, null); firstValueResolveResult = new ConstantResolveResult(SpecialType.NullType, null);

26
ICSharpCode.Decompiler/IL/Instructions/StringToInt.cs

@ -17,28 +17,46 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.Collections.Generic;
namespace ICSharpCode.Decompiler.IL namespace ICSharpCode.Decompiler.IL
{ {
partial class StringToInt partial class StringToInt
{ {
public string[] Map { get; } public List<(string Key, int Value)> Map { get; }
public StringToInt(ILInstruction argument, string[] map) public StringToInt(ILInstruction argument, List<(string Key, int Value)> map)
: base(OpCode.StringToInt) : base(OpCode.StringToInt)
{ {
this.Argument = argument; this.Argument = argument;
this.Map = map; this.Map = map;
} }
public StringToInt(ILInstruction argument, string[] map)
: this(argument, ArrayToDictionary(map))
{
}
static List<(string Key, int Value)> ArrayToDictionary(string[] map)
{
var dict = new List<(string Key, int Value)>();
for (int i = 0; i < map.Length; i++) {
dict.Add((map[i], i));
}
return dict;
}
public override void WriteTo(ITextOutput output, ILAstWritingOptions options) public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{ {
ILRange.WriteTo(output, options); ILRange.WriteTo(output, options);
output.Write("string.to.int ("); output.Write("string.to.int (");
Argument.WriteTo(output, options); Argument.WriteTo(output, options);
output.Write(", { "); output.Write(", { ");
for (int i = 0; i < Map.Length; i++) { int i = 0;
foreach (var entry in Map) {
if (i > 0) output.Write(", "); if (i > 0) output.Write(", ");
output.Write($"[{i}] = \"{Map[i]}\""); output.Write($"[\"{entry.Key}\"] = {entry.Value}");
i++;
} }
output.Write(" })"); output.Write(" })");
} }

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

@ -245,7 +245,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (!defaultBlockJump.MatchBranch(out var defaultBlock)) if (!defaultBlockJump.MatchBranch(out var defaultBlock))
return false; return false;
if (!(condition.MatchLogicNot(out var arg) && arg is Call c && c.Method.Name == "TryGetValue" && if (!(condition.MatchLogicNot(out var arg) && arg is CallInstruction c && c.Method.Name == "TryGetValue" &&
MatchDictionaryFieldLoad(c.Arguments[0], IsStringToIntDictionary, out var dictField2, out _) && dictField2.Equals(dictField))) MatchDictionaryFieldLoad(c.Arguments[0], IsStringToIntDictionary, out var dictField2, out _) && dictField2.Equals(dictField)))
return false; return false;
if (!c.Arguments[1].MatchLdLoc(switchValueVar) || !c.Arguments[2].MatchLdLoca(out var switchIndexVar)) if (!c.Arguments[1].MatchLdLoc(switchValueVar) || !c.Arguments[2].MatchLdLoca(out var switchIndexVar))
@ -274,7 +274,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchValue = new LdLoc(switchValueVar); switchValue = new LdLoc(switchValueVar);
keepAssignmentBefore = true; keepAssignmentBefore = true;
} }
var stringToInt = new StringToInt(switchValue, stringValues.ToArray()); var stringToInt = new StringToInt(switchValue, stringValues);
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
instructions[i + 1].ReplaceWith(inst); instructions[i + 1].ReplaceWith(inst);
@ -290,7 +290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
private bool AddNullSection(List<SwitchSection> sections, List<string> stringValues, Block nullValueCaseBlock) bool AddNullSection(List<SwitchSection> sections, List<(string, int)> stringValues, Block nullValueCaseBlock)
{ {
var label = new LongSet(sections.Count); var label = new LongSet(sections.Count);
var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray(); var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray();
@ -301,7 +301,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; // cannot remove only label return false; // cannot remove only label
possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label); possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label);
} }
stringValues.Add(null); stringValues.Add((null, (int)label.Values.First()));
sections.Add(new SwitchSection() { Labels = label, Body = new Branch(nullValueCaseBlock) }); sections.Add(new SwitchSection() { Labels = label, Body = new Branch(nullValueCaseBlock) });
return true; return true;
} }
@ -322,7 +322,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary> /// <summary>
/// Matches and extracts values from Add-call sequences. /// Matches and extracts values from Add-call sequences.
/// </summary> /// </summary>
bool ExtractStringValuesFromInitBlock(Block block, out List<string> values, Block targetBlock, IType dictionaryType, IField dictionaryField) bool ExtractStringValuesFromInitBlock(Block block, out List<(string, int)> values, Block targetBlock, IType dictionaryType, IField dictionaryField)
{ {
values = null; values = null;
// stloc dictVar(newobj Dictionary..ctor(ldc.i4 valuesLength)) // stloc dictVar(newobj Dictionary..ctor(ldc.i4 valuesLength))
@ -342,10 +342,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!newObj.Arguments[0].MatchLdcI4(out valuesLength)) if (!newObj.Arguments[0].MatchLdcI4(out valuesLength))
return false; return false;
} }
values = new List<string>(valuesLength); values = new List<(string, int)>(valuesLength);
int i = 0; int i = 0;
while (MatchAddCall(dictionaryType, block.Instructions[i + 1], dictVar, i, out var value)) { while (MatchAddCall(dictionaryType, block.Instructions[i + 1], dictVar, out var index, out var value)) {
values.Add(value); values.Add((value, index));
i++; i++;
} }
// final store to compiler-generated variable: // final store to compiler-generated variable:
@ -362,16 +362,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// -or- /// -or-
/// call Add(ldloc dictVar, ldstr value, box System.Int32(ldc.i4 index)) /// call Add(ldloc dictVar, ldstr value, box System.Int32(ldc.i4 index))
/// </summary> /// </summary>
bool MatchAddCall(IType dictionaryType, ILInstruction inst, ILVariable dictVar, int index, out string value) bool MatchAddCall(IType dictionaryType, ILInstruction inst, ILVariable dictVar, out int index, out string value)
{ {
value = null; value = null;
if (!(inst is Call c && c.Method.Name == "Add" && c.Arguments.Count == 3)) index = -1;
if (!(inst is CallInstruction c && c.Method.Name == "Add" && c.Arguments.Count == 3))
return false; return false;
if (!(c.Arguments[0].MatchLdLoc(dictVar) && c.Arguments[1].MatchLdStr(out value))) if (!(c.Arguments[0].MatchLdLoc(dictVar) && c.Arguments[1].MatchLdStr(out value)))
return false; return false;
if (!(c.Method.DeclaringType.Equals(dictionaryType) && !c.Method.IsStatic)) if (!(c.Method.DeclaringType.Equals(dictionaryType) && !c.Method.IsStatic))
return false; return false;
return (c.Arguments[2].MatchLdcI4(index) || (c.Arguments[2].MatchBox(out var arg, out _) && arg.MatchLdcI4(index))); return (c.Arguments[2].MatchLdcI4(out index) || (c.Arguments[2].MatchBox(out var arg, out _) && arg.MatchLdcI4(out index)));
} }
bool IsStringToIntDictionary(IType dictionaryType) bool IsStringToIntDictionary(IType dictionaryType)
@ -474,7 +475,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
} }
} }
var stringToInt = new StringToInt(switchValue, stringValues.ToArray()); var stringToInt = new StringToInt(switchValue, stringValues);
var inst = new SwitchInstruction(stringToInt); var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections); inst.Sections.AddRange(sections);
instructions[i + 1].ReplaceWith(inst); instructions[i + 1].ReplaceWith(inst);

Loading…
Cancel
Save