Browse Source

Fix #1809: Support VB Select on string.

pull/1843/head
Daniel Grunwald 6 years ago
parent
commit
832c18f0be
  1. 23
      ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs
  2. 1
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  3. 3
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  4. 37
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.cs
  5. 24
      ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.vb
  6. 6
      ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs
  7. 133
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs

23
ICSharpCode.Decompiler.Tests/Helpers/RemoveCompilerAttribute.cs

@ -13,14 +13,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -13,14 +13,12 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
var section = (AttributeSection)attribute.Parent;
SimpleType type = attribute.Type as SimpleType;
if (section.AttributeTarget == "assembly" &&
(type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable" || type.Identifier == "TargetFramework"))
{
(type.Identifier == "CompilationRelaxations" || type.Identifier == "RuntimeCompatibility" || type.Identifier == "SecurityPermission" || type.Identifier == "PermissionSet" || type.Identifier == "AssemblyVersion" || type.Identifier == "Debuggable" || type.Identifier == "TargetFramework")) {
attribute.Remove();
if (section.Attributes.Count == 0)
section.Remove();
}
if (section.AttributeTarget == "module" && type.Identifier == "UnverifiableCode")
{
if (section.AttributeTarget == "module" && type.Identifier == "UnverifiableCode") {
attribute.Remove();
if (section.Attributes.Count == 0)
section.Remove();
@ -60,4 +58,21 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -60,4 +58,21 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
rootNode.AcceptVisitor(this);
}
}
public class RemoveNamespaceMy : DepthFirstAstVisitor, IAstTransform
{
public override void VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration)
{
if (namespaceDeclaration.Name == "My") {
namespaceDeclaration.Remove();
} else {
base.VisitNamespaceDeclaration(namespaceDeclaration);
}
}
public void Run(AstNode rootNode, TransformContext context)
{
rootNode.AcceptVisitor(this);
}
}
}

1
ICSharpCode.Decompiler.Tests/Helpers/Tester.cs

@ -482,6 +482,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers @@ -482,6 +482,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
CSharpDecompiler decompiler = new CSharpDecompiler(typeSystem, settings);
decompiler.AstTransforms.Insert(0, new RemoveEmbeddedAttributes());
decompiler.AstTransforms.Insert(0, new RemoveCompilerAttribute());
decompiler.AstTransforms.Insert(0, new RemoveNamespaceMy());
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
var syntaxTree = decompiler.DecompileWholeModuleAsSingleFile(sortTypes: true);

3
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -81,6 +81,7 @@ @@ -81,6 +81,7 @@
<ItemGroup>
<Compile Include="DisassemblerPrettyTestRunner.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\VBPretty\Select.cs" />
<None Include="TestCases\ILPretty\WeirdEnums.cs" />
<Compile Include="TestCases\ILPretty\ConstantBlobs.cs" />
<None Include="TestCases\Pretty\AsyncStreams.cs" />
@ -91,6 +92,7 @@ @@ -91,6 +92,7 @@
<Compile Include="TestCases\Ugly\NoForEachStatement.cs" />
<None Include="TestCases\Ugly\NoExtensionMethods.Expected.cs" />
<Compile Include="TestCases\Ugly\NoExtensionMethods.cs" />
<None Include="TestCases\VBPretty\Select.vb" />
<None Include="TestCases\VBPretty\VBCompoundAssign.cs" />
<Compile Include="TestCases\Pretty\ThrowExpressions.cs" />
<None Include="TestCases\ILPretty\Issue1145.cs" />
@ -261,6 +263,7 @@ @@ -261,6 +263,7 @@
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.VisualBasic" />
</ItemGroup>
<ItemGroup>

37
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.cs

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
using Microsoft.VisualBasic.CompilerServices;
using System;
[StandardModule]
internal sealed class Program
{
public static void SelectOnString()
{
switch (Environment.CommandLine) {
case "123":
Console.WriteLine("a");
break;
case "444":
Console.WriteLine("b");
break;
case "222":
Console.WriteLine("c");
break;
case "11":
Console.WriteLine("d");
break;
case "dd":
Console.WriteLine("e");
break;
case "sss":
Console.WriteLine("f");
break;
case "aa":
Console.WriteLine("g");
break;
case null:
case "":
Console.WriteLine("empty");
break;
}
}
}

24
ICSharpCode.Decompiler.Tests/TestCases/VBPretty/Select.vb

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
Imports System
Module Program
Sub SelectOnString()
Select Case Environment.CommandLine
Case "123"
Console.WriteLine("a")
Case "444"
Console.WriteLine("b")
Case "222"
Console.WriteLine("c")
Case "11"
Console.WriteLine("d")
Case "dd"
Console.WriteLine("e")
Case "sss"
Console.WriteLine("f")
Case "aa"
Console.WriteLine("g")
Case ""
Console.WriteLine("empty")
End Select
End Sub
End Module

6
ICSharpCode.Decompiler.Tests/VBPrettyTestRunner.cs

@ -72,6 +72,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -72,6 +72,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(options: options | CompilerOptions.Library);
}
[Test]
public void Select([ValueSource(nameof(defaultOptions))] CompilerOptions options)
{
Run(options: options | CompilerOptions.Library);
}
void Run([CallerMemberName] string testName = null, CompilerOptions options = CompilerOptions.UseDebug, DecompilerSettings settings = null)
{
var vbFile = Path.Combine(TestCasePath, testName + ".vb");

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

@ -160,6 +160,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -160,6 +160,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// if (comp(ldloc switchValueVar == ldnull)) br defaultBlock
if (!instructions[i].MatchIfInstruction(out var condition, out var firstBlockOrDefaultJump))
return false;
var nextCaseJump = instructions[i + 1];
while (condition.MatchLogicNot(out var arg)) {
condition = arg;
ExtensionMethods.Swap(ref firstBlockOrDefaultJump, ref nextCaseJump);
}
// match call to operator ==(string, string)
if (!MatchStringEqualityComparison(condition, out var switchValueVar, out string firstBlockValue, out bool isVBCompareString))
return false;
if (isVBCompareString) {
ExtensionMethods.Swap(ref firstBlockOrDefaultJump, ref nextCaseJump);
}
if (firstBlockOrDefaultJump.MatchBranch(out var firstBlock)) {
// success
@ -172,11 +183,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -172,11 +183,12 @@ namespace ICSharpCode.Decompiler.IL.Transforms
List<(string, ILInstruction)> values = new List<(string, ILInstruction)>();
ILInstruction switchValue = null;
// match call to operator ==(string, string)
if (!MatchStringEqualityComparison(condition, out var switchValueVar, out string firstBlockValue))
return false;
if (isVBCompareString && string.IsNullOrEmpty(firstBlockValue)) {
values.Add((null, firstBlock ?? firstBlockOrDefaultJump));
values.Add((string.Empty, firstBlock ?? firstBlockOrDefaultJump));
} else {
values.Add((firstBlockValue, firstBlock ?? firstBlockOrDefaultJump));
}
bool extraLoad = false;
bool keepAssignmentBefore = false;
@ -215,13 +227,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -215,13 +227,17 @@ namespace ICSharpCode.Decompiler.IL.Transforms
switchValue = new LdLoc(switchValueVar);
}
// if instruction must be followed by a branch to the next case
if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump))
if (!nextCaseJump.MatchBranch(out Block currentCaseBlock))
return false;
// extract all cases and add them to the values list.
Block currentCaseBlock = nextCaseJump.TargetBlock;
ILInstruction nextCaseBlock;
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out Block block)) != null) {
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out bool emptyStringEqualsNull, out Block block)) != null) {
if (emptyStringEqualsNull && string.IsNullOrEmpty(value)) {
values.Add((null, block));
values.Add((string.Empty, block));
} else {
values.Add((value, block));
}
currentCaseBlock = nextCaseBlock as Block;
if (currentCaseBlock == null)
break;
@ -367,38 +383,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -367,38 +383,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// The <paramref name="switchVariable"/> is updated if the value gets copied to a different variable.
/// See comments below for more info.
/// </summary>
ILInstruction MatchCaseBlock(Block currentBlock, ILVariable switchVariable, out string value, out Block caseBlock)
ILInstruction MatchCaseBlock(Block currentBlock, ILVariable switchVariable, out string value, out bool emptyStringEqualsNull, out Block caseBlock)
{
value = null;
caseBlock = null;
emptyStringEqualsNull = false;
if (currentBlock.IncomingEdgeCount != 1 || currentBlock.Instructions.Count != 2)
return null;
if (!currentBlock.Instructions[0].MatchIfInstruction(out var condition, out var caseBlockBranch))
if (!currentBlock.MatchIfAtEndOfBlock(out var condition, out var caseBlockBranch, out var nextBlockBranch))
return null;
if (!caseBlockBranch.MatchBranch(out caseBlock))
if (!MatchStringEqualityComparison(condition, switchVariable, out value, out bool isVBCompareString)) {
return null;
Block nextBlock;
BlockContainer blockContainer = null;
if (condition.MatchLogicNot(out var inner)) {
condition = inner;
nextBlock = caseBlock;
if (!currentBlock.Instructions[1].MatchBranch(out caseBlock))
}
if (isVBCompareString) {
ExtensionMethods.Swap(ref caseBlockBranch, ref nextBlockBranch);
emptyStringEqualsNull = true;
}
if (!caseBlockBranch.MatchBranch(out caseBlock))
return null;
} else {
if (currentBlock.Instructions[1].MatchBranch(out nextBlock)) {
if (nextBlockBranch.MatchBranch(out Block nextBlock)) {
// success
} else if (currentBlock.Instructions[1].MatchLeave(out blockContainer)) {
return nextBlock;
} else if (nextBlockBranch.MatchLeave(out BlockContainer blockContainer)) {
// success
return blockContainer;
} else {
return null;
}
}
if (!MatchStringEqualityComparison(condition, switchVariable, out value)) {
return null;
}
return nextBlock ?? (ILInstruction)blockContainer;
}
/// <summary>
/// Matches the C# 2.0 switch-on-string pattern, which uses Dictionary&lt;string, int&gt;.
@ -836,8 +849,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -836,8 +849,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
MatchComputeStringHashCall(switchBlockInstructions[switchBlockInstructionsOffset], switchValueVar, out LdLoc switchValueLoad)))
return false;
var stringValues = new List<(int Index, string Value, ILInstruction TargetBlockOrLeave)>();
int index = 0;
var stringValues = new List<(string Value, ILInstruction TargetBlockOrLeave)>();
SwitchSection defaultSection = switchInst.Sections.MaxBy(s => s.Labels.Count());
Block exitOrDefaultBlock = null;
foreach (var section in switchInst.Sections) {
@ -846,20 +858,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -846,20 +858,27 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!section.Body.MatchBranch(out Block target))
return false;
string stringValue;
bool emptyStringEqualsNull;
if (MatchRoslynEmptyStringCaseBlockHead(target, switchValueLoad.Variable, out ILInstruction targetOrLeave, out Block currentExitBlock)) {
stringValue = "";
} else if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out targetOrLeave, out currentExitBlock, out stringValue)) {
emptyStringEqualsNull = false;
} else if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out targetOrLeave, out currentExitBlock, out stringValue, out emptyStringEqualsNull)) {
return false;
}
if (exitOrDefaultBlock != null && exitOrDefaultBlock != currentExitBlock)
return false;
exitOrDefaultBlock = currentExitBlock;
stringValues.Add((index++, stringValue, targetOrLeave));
if (emptyStringEqualsNull && string.IsNullOrEmpty(stringValue)) {
stringValues.Add((null, targetOrLeave));
stringValues.Add((string.Empty, targetOrLeave));
} else {
stringValues.Add((stringValue, targetOrLeave));
}
}
if (nullValueCaseBlock != null && exitOrDefaultBlock != nullValueCaseBlock) {
stringValues.Add((index++, null, nullValueCaseBlock));
stringValues.Add((null, nullValueCaseBlock));
}
ILInstruction switchValueInst = switchValueLoad;
@ -914,13 +933,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -914,13 +933,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
SwitchInstruction ReplaceWithSwitchInstruction(int offset)
{
var defaultLabel = new LongSet(new LongInterval(0, index)).Invert();
var defaultLabel = new LongSet(new LongInterval(0, stringValues.Count)).Invert();
var values = new string[stringValues.Count];
var sections = new SwitchSection[stringValues.Count];
foreach (var (idx, (label, value, bodyInstruction)) in stringValues.WithIndex()) {
foreach (var (idx, (value, bodyInstruction)) in stringValues.WithIndex()) {
values[idx] = value;
var body = bodyInstruction is Block b ? new Branch(b) : bodyInstruction;
sections[idx] = new SwitchSection { Labels = new LongSet(label), Body = body };
sections[idx] = new SwitchSection { Labels = new LongSet(idx), Body = body };
}
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, values));
newSwitch.Sections.AddRange(sections);
@ -935,26 +954,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -935,26 +954,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// if (call op_Equality(ldloc switchValueVar, stringValue)) br body
/// br exit
/// </summary>
bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out ILInstruction bodyOrLeave, out Block defaultOrExitBlock, out string stringValue)
bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out ILInstruction bodyOrLeave, out Block defaultOrExitBlock, out string stringValue, out bool emptyStringEqualsNull)
{
bodyOrLeave = null;
defaultOrExitBlock = null;
stringValue = null;
emptyStringEqualsNull = false;
if (target.Instructions.Count != 2)
return false;
if (!target.Instructions[0].MatchIfInstruction(out var condition, out var bodyBranch))
return false;
ILInstruction exitBranch;
ILInstruction exitBranch = target.Instructions[1];
// Handle negated conditions first:
if (condition.MatchLogicNot(out var expr)) {
exitBranch = bodyBranch;
bodyBranch = target.Instructions[1];
while (condition.MatchLogicNot(out var expr)) {
ExtensionMethods.Swap(ref exitBranch, ref bodyBranch);
condition = expr;
} else {
exitBranch = target.Instructions[1];
}
if (!MatchStringEqualityComparison(condition, switchValueVar, out stringValue))
if (!MatchStringEqualityComparison(condition, switchValueVar, out stringValue, out bool isVBCompareString))
return false;
if (isVBCompareString) {
ExtensionMethods.Swap(ref exitBranch, ref bodyBranch);
emptyStringEqualsNull = true;
}
if (!(exitBranch.MatchBranch(out defaultOrExitBlock) || exitBranch.MatchLeave(out _)))
return false;
if (bodyBranch.MatchLeave(out _)) {
@ -1054,25 +1075,45 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -1054,25 +1075,45 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))'
/// or 'comp(ldloc(variable) == ldnull)'
/// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue)
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue, out bool isVBCompareString)
{
return MatchStringEqualityComparison(condition, out var v, out stringValue) && v == variable;
return MatchStringEqualityComparison(condition, out var v, out stringValue, out isVBCompareString) && v == variable;
}
/// <summary>
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))'
/// or 'comp(ldloc(variable) == ldnull)'
/// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue)
bool MatchStringEqualityComparison(ILInstruction condition, out ILVariable variable, out string stringValue, out bool isVBCompareString)
{
stringValue = null;
variable = null;
isVBCompareString = false;
while (condition is Comp comp && comp.Kind == ComparisonKind.Inequality && comp.Right.MatchLdcI4(0)) {
// if (x != 0) == if (x)
condition = comp.Left;
}
if (condition is Call c) {
ILInstruction left, right;
if (condition is Call c && c.Method.IsOperator && c.Method.Name == "op_Equality"
&& c.Method.DeclaringType.IsKnownType(KnownTypeCode.String) && c.Arguments.Count == 2)
{
if (c.Method.IsOperator && c.Method.Name == "op_Equality"
&& c.Method.DeclaringType.IsKnownType(KnownTypeCode.String) && c.Arguments.Count == 2) {
left = c.Arguments[0];
right = c.Arguments[1];
} else if (c.Method.IsStatic && c.Method.Name == "CompareString"
&& c.Method.DeclaringType.FullName == "Microsoft.VisualBasic.CompilerServices.Operators"
&& c.Arguments.Count == 3) {
left = c.Arguments[0];
right = c.Arguments[1];
// VB CompareString(): return 0 on equality -> condition is effectively negated.
// Also, the empty string is considered equal to null.
isVBCompareString = true;
if (!c.Arguments[2].MatchLdcI4(0)) {
// Option Compare Text: case insensitive comparison is not supported in C#
return false;
}
} else {
return false;
}
return left.MatchLdLoc(out variable) && right.MatchLdStr(out stringValue);
} else if (condition.MatchCompEqualsNull(out var arg)) {
stringValue = null;

Loading…
Cancel
Save