Browse Source

Fix switch-on-string transform for optimized Roslyn.

pull/3362/head
Siegfried Pammer 6 months ago
parent
commit
e1e2f739f6
  1. 100
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs
  2. 41
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

100
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs

@ -82,6 +82,46 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
public class ImplicitConversionConflictWithLong
{
private readonly int s;
public ImplicitConversionConflictWithLong(int s)
{
this.s = s;
}
public static implicit operator int(ImplicitConversionConflictWithLong v)
{
return v.s;
}
public static implicit operator long(ImplicitConversionConflictWithLong v)
{
return v.s;
}
}
public class ImplicitConversionConflictWithString
{
private readonly int s;
public ImplicitConversionConflictWithString(int s)
{
this.s = s;
}
public static implicit operator int(ImplicitConversionConflictWithString v)
{
return v.s;
}
public static implicit operator string(ImplicitConversionConflictWithString v)
{
return string.Empty;
}
}
public class ExplicitInt public class ExplicitInt
{ {
private readonly int s; private readonly int s;
@ -396,6 +436,62 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
public static void SwitchOverImplicitIntConflictLong(ImplicitConversionConflictWithLong i)
{
switch ((int)i)
{
case 0:
Console.WriteLine("zero");
break;
case 5:
Console.WriteLine("five");
break;
case 10:
Console.WriteLine("ten");
break;
case 15:
Console.WriteLine("fifteen");
break;
case 20:
Console.WriteLine("twenty");
break;
case 25:
Console.WriteLine("twenty-five");
break;
case 30:
Console.WriteLine("thirty");
break;
}
}
public static void SwitchOverImplicitIntConflictString(ImplicitConversionConflictWithString i)
{
switch ((string)i)
{
case "0":
Console.WriteLine("zero");
break;
case "5":
Console.WriteLine("five");
break;
case "10":
Console.WriteLine("ten");
break;
case "15":
Console.WriteLine("fifteen");
break;
case "20":
Console.WriteLine("twenty");
break;
case "25":
Console.WriteLine("twenty-five");
break;
case "30":
Console.WriteLine("thirty");
break;
}
}
// SwitchDetection.UseCSharpSwitch requires more complex heuristic to identify this when compiled with Roslyn // SwitchDetection.UseCSharpSwitch requires more complex heuristic to identify this when compiled with Roslyn
public static void CompactSwitchOverInt(int i) public static void CompactSwitchOverInt(int i)
{ {
@ -507,9 +603,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public static string SwitchOverImplicitString(ImplicitString s) public static string SwitchOverImplicitString(ImplicitString s)
{ {
// we emit an explicit cast, because the rules used by the C# compiler are counter-intuitive: switch (s)
// The C# compiler does *not* take the type of the switch labels into account at all.
switch ((string)s)
{ {
case "First case": case "First case":
return "Text1"; return "Text1";

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

@ -1070,10 +1070,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
stringValues.Add((null, nullValueCaseBlock)); stringValues.Add((null, nullValueCaseBlock));
} }
// In newer Roslyn versions (>=3.7) the null check appears in the default case, not prior to the switch. // In newer Roslyn versions (>=3.7) the null check appears in the default case, not prior to the switch.
if (!stringValues.Any(pair => pair.Value == null) && IsNullCheckInDefaultBlock(ref exitOrDefaultBlock, switchValueLoad.Variable, out nullValueCaseBlock)) ILInstruction exitOrDefault = exitOrDefaultBlock;
if (!stringValues.Any(pair => pair.Value == null) && IsNullCheckInDefaultBlock(ref exitOrDefault, switchValueLoad.Variable, out nullValueCaseBlock))
{ {
stringValues.Add((null, nullValueCaseBlock)); stringValues.Add((null, nullValueCaseBlock));
} }
exitOrDefaultBlock = (Block)exitOrDefault;
context.Step(nameof(MatchRoslynSwitchOnString), switchValueLoad); context.Step(nameof(MatchRoslynSwitchOnString), switchValueLoad);
if (exitOrDefaultBlock != null) if (exitOrDefaultBlock != null)
@ -1176,7 +1178,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (!instructions[i + 1].MatchBranch(out var nextBlock)) if (!instructions[i + 1].MatchBranch(out var nextBlock))
return false; return false;
if (!exitBlockJump.MatchBranch(out nullCase)) if (!exitBlockJump.MatchBranch(out nullCase) && !exitBlockJump.MatchLeave(out _))
return false; return false;
// if (comp(ldloc switchValueVar == ldnull)) br ... // if (comp(ldloc switchValueVar == ldnull)) br ...
// br switchOnLengthBlock // br switchOnLengthBlock
@ -1202,7 +1204,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchValueVar = null; // will be extracted in MatchSwitchOnLengthBlock switchValueVar = null; // will be extracted in MatchSwitchOnLengthBlock
switchOnLengthBlockStartOffset = i; switchOnLengthBlockStartOffset = i;
} }
Block defaultCase = null; ILInstruction defaultCase = null;
if (!MatchSwitchOnLengthBlock(ref switchValueVar, switchOnLengthBlock, switchOnLengthBlockStartOffset, out var blocksByLength)) if (!MatchSwitchOnLengthBlock(ref switchValueVar, switchOnLengthBlock, switchOnLengthBlockStartOffset, out var blocksByLength))
return false; return false;
List<(string, ILInstruction)> stringValues = new(); List<(string, ILInstruction)> stringValues = new();
@ -1216,7 +1218,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
else else
{ {
int length = (int)b.Length.Intervals[0].Start; int length = (int)b.Length.Intervals[0].Start;
if (MatchSwitchOnCharBlock(b.TargetBlock, length, switchValueVar, out var mapping)) switch (b.TargetBlock)
{
case Leave leave:
break;
case Block targetBlock:
if (MatchSwitchOnCharBlock(targetBlock, length, switchValueVar, out var mapping))
{ {
foreach (var item in mapping) foreach (var item in mapping)
{ {
@ -1230,7 +1237,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
} }
else if (MatchRoslynCaseBlockHead(b.TargetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _)) else if (MatchRoslynCaseBlockHead(targetBlock, switchValueVar, out var bodyOrLeave, out var exit, out string stringValue, out _))
{ {
if (exit != defaultCase) if (exit != defaultCase)
return false; return false;
@ -1251,6 +1258,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
return false; return false;
} }
break;
default:
return false;
}
} }
} }
@ -1278,7 +1290,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values, switchValueVar.Type)); var newSwitch = new SwitchInstruction(new StringToInt(new LdLoc(switchValueVar), values, switchValueVar.Type));
newSwitch.Sections.AddRange(sections); newSwitch.Sections.AddRange(sections);
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = new Branch(defaultCase) }); newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultCase is Block b2 ? new Branch(b2) : defaultCase });
newSwitch.AddILRange(instructions[i]); newSwitch.AddILRange(instructions[i]);
if (nullCase != null) if (nullCase != null)
{ {
@ -1399,7 +1411,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return results?.Count > 0; return results?.Count > 0;
} }
bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengthBlock, int startOffset, out List<(LongSet Length, Block TargetBlock)> blocks) bool MatchSwitchOnLengthBlock(ref ILVariable switchValueVar, Block switchOnLengthBlock, int startOffset, out List<(LongSet Length, ILInstruction TargetBlock)> blocks)
{ {
blocks = null; blocks = null;
SwitchInstruction @switch; SwitchInstruction @switch;
@ -1482,17 +1494,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
if (section.HasNullLabel) if (section.HasNullLabel)
return false; return false;
if (!section.Body.MatchBranch(out var target)) if (!section.Body.MatchBranch(out var target) && !section.Body.MatchLeave(out _))
return false; return false;
ILInstruction targetInst = target ?? section.Body;
if (section.Labels.Count() != 1) if (section.Labels.Count() != 1)
{ {
defaultCase ??= target; defaultCase ??= targetInst;
if (defaultCase != target) if (defaultCase != targetInst)
return false; return false;
} }
else else
{ {
blocks.Add((section.Labels, target)); blocks.Add((section.Labels, targetInst));
} }
} }
return true; return true;
@ -1506,10 +1519,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// br newDefaultBlock /// br newDefaultBlock
/// } /// }
/// </summary> /// </summary>
private bool IsNullCheckInDefaultBlock(ref Block exitOrDefaultBlock, ILVariable switchVar, out Block nullValueCaseBlock) private bool IsNullCheckInDefaultBlock(ref ILInstruction exitOrDefault, ILVariable switchVar, out Block nullValueCaseBlock)
{ {
nullValueCaseBlock = null; nullValueCaseBlock = null;
if (exitOrDefaultBlock == null) if (exitOrDefault is not Block exitOrDefaultBlock)
return false; return false;
if (!exitOrDefaultBlock.Instructions[0].MatchIfInstruction(out var condition, out var thenBranch)) if (!exitOrDefaultBlock.Instructions[0].MatchIfInstruction(out var condition, out var thenBranch))
return false; return false;
@ -1523,7 +1536,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (elseBlock.Parent != exitOrDefaultBlock.Parent) if (elseBlock.Parent != exitOrDefaultBlock.Parent)
return false; return false;
exitOrDefaultBlock = elseBlock; exitOrDefault = elseBlock;
return true; return true;
} }

Loading…
Cancel
Save