Browse Source

Merge pull request #887 from icsharpcode/switch

Switch
pull/909/merge
Daniel Grunwald 8 years ago committed by GitHub
parent
commit
d59e722752
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 6
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  3. 89
      ICSharpCode.Decompiler.Tests/Switch.cs
  4. 4
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/LINQRaytracer.cs
  5. 76
      ICSharpCode.Decompiler.Tests/TestCases/Correctness/Switch.cs
  6. 415
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.cs
  7. 1529
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.il
  8. 1306
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.il
  9. 1414
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.roslyn.il
  10. 1741
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.roslyn.il
  11. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs
  12. 14
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  13. 24
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  14. 131
      ICSharpCode.Decompiler/CSharp/StatementBuilder.cs
  15. 6
      ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs
  16. 172
      ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
  17. 2
      ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs
  18. 1
      ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinderExtensions.cs
  19. 63
      ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs
  20. 8
      ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs
  21. 7
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  22. 11
      ICSharpCode.Decompiler/IL/BlockBuilder.cs
  23. 51
      ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs
  24. 30
      ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs
  25. 151
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  26. 4
      ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs
  27. 61
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs
  28. 45
      ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs
  29. 2
      ICSharpCode.Decompiler/IL/DetectedLoop.cs
  30. 5
      ICSharpCode.Decompiler/IL/ILReader.cs
  31. 109
      ICSharpCode.Decompiler/IL/Instructions.cs
  32. 8
      ICSharpCode.Decompiler/IL/Instructions.tt
  33. 3
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  34. 12
      ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs
  35. 15
      ICSharpCode.Decompiler/IL/Instructions/Branch.cs
  36. 32
      ICSharpCode.Decompiler/IL/Instructions/LockInstruction.cs
  37. 26
      ICSharpCode.Decompiler/IL/Instructions/PatternMatching.cs
  38. 45
      ICSharpCode.Decompiler/IL/Instructions/StringToInt.cs
  39. 76
      ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs
  40. 53
      ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs
  41. 8
      ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs
  42. 20
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  43. 12
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  44. 95
      ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs
  45. 41
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  46. 165
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs
  47. 614
      ICSharpCode.Decompiler/IL/Transforms/SwitchOnStringTransform.cs
  48. 2
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
  49. 76
      ICSharpCode.Decompiler/Util/CollectionExtensions.cs
  50. 221
      ICSharpCode.Decompiler/Util/GraphVizGraph.cs
  51. 10
      ILSpy.BamlDecompiler/BamlResourceEntryNode.cs
  52. 31
      ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs
  53. 4
      ILSpy/Languages/ILAstLanguage.cs
  54. 5
      ILSpy/LoadedAssembly.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -105,6 +105,7 @@ @@ -105,6 +105,7 @@
<Compile Include="TestCases\Pretty\PropertiesAndEvents.cs" />
<Compile Include="TestCases\Pretty\QueryExpressions.cs" />
<Compile Include="TestCases\Pretty\ShortCircuit.cs" />
<Compile Include="TestCases\Pretty\Switch.cs" />
<Compile Include="TestCases\Pretty\TypeAnalysisTests.cs" />
<Compile Include="TestCases\Pretty\Using.cs" />
<Compile Include="TestTraceListener.cs" />

6
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -97,6 +97,12 @@ namespace ICSharpCode.Decompiler.Tests @@ -97,6 +97,12 @@ namespace ICSharpCode.Decompiler.Tests
Run(cscOptions: cscOptions);
}
[Test]
public void Switch([ValueSource("defaultOptions")] CompilerOptions cscOptions)
{
Run(cscOptions: cscOptions);
}
[Test]
public void DelegateConstruction([ValueSource("defaultOptions")] CompilerOptions cscOptions)
{

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;
}
}
}

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

@ -10,8 +10,8 @@ namespace RayTracer @@ -10,8 +10,8 @@ namespace RayTracer
{
static void Main()
{
const int width = 600;
const int height = 600;
const int width = 50;
const int height = 50;
RayTracer rayTracer = new RayTracer(width, height, (int x, int y, Color color) => {
Console.Write("{0},{1}:{2};", x, y, color);

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

@ -26,10 +26,17 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -26,10 +26,17 @@ 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));
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)
@ -41,6 +48,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -41,6 +48,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public static string SparseIntegerSwitch(int i)
{
Console.WriteLine("SparseIntegerSwitch: " + i);
switch (i) {
case -10000000: return "-10 mln";
case -100: return "-hundred";
@ -59,6 +67,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -59,6 +67,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public static string ShortSwitchOverString(string text)
{
Console.WriteLine("ShortSwitchOverString: " + text);
switch (text) {
case "First case":
return "Text";
@ -67,8 +76,38 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -67,8 +76,38 @@ 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);
switch (text) {
case "First case":
return "Text1";
@ -92,6 +131,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -92,6 +131,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public static string SwitchOverString2()
{
Console.WriteLine("SwitchOverString2:");
switch (Environment.UserName) {
case "First case":
return "Text1";
@ -105,6 +145,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -105,6 +145,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";
}
@ -112,6 +162,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -112,6 +162,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public static string SwitchOverBool(bool b)
{
Console.WriteLine("SwitchOverBool: " + b);
switch (b) {
case true:
return bool.TrueString;
@ -124,6 +175,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -124,6 +175,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness
public static void SwitchInLoop(int i)
{
Console.WriteLine("SwitchInLoop: " + i);
while (true) {
switch (i) {
case 1:
@ -141,10 +193,32 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness @@ -141,10 +193,32 @@ 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)
{
Console.WriteLine("SwitchWithGoto: " + 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;
}
}
}
}

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

@ -0,0 +1,415 @@ @@ -0,0 +1,415 @@
// 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;
using System.Collections.Generic;
using System.Reflection;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public static class Switch
{
public class SetProperty
{
public readonly PropertyInfo Property;
public int Set {
get;
set;
}
public SetProperty(PropertyInfo property)
{
this.Property = property;
}
}
public static string SparseIntegerSwitch(int i)
{
Console.WriteLine("SparseIntegerSwitch: " + i);
switch (i) {
case -10000000:
return "-10 mln";
case -100:
return "-hundred";
case -1:
return "-1";
case 0:
return "0";
case 1:
return "1";
case 2:
return "2";
case 4:
return "4";
case 100:
return "hundred";
case 10000:
return "ten thousand";
case 10001:
return "ten thousand and one";
case 2147483647:
return "int.MaxValue";
default:
return "something else";
}
}
public static string SwitchOverNullableInt(int? i)
{
switch (i) {
case null:
return "null";
case 0:
return "zero";
case 5:
return "five";
case 10:
return "ten";
default:
return "large";
}
}
public static string SwitchOverNullableIntNullCaseCombined(int? i)
{
switch (i) {
case null:
case 0:
return "zero";
case 5:
return "five";
case 10:
return "ten";
default:
return "large";
}
}
public static string SwitchOverNullableIntShifted(int? i)
{
switch (i + 5) {
case null:
return "null";
case 0:
return "zero";
case 5:
return "five";
case 10:
return "ten";
default:
return "large";
}
}
public static string SwitchOverNullableIntShiftedNullCaseCombined(int? i)
{
switch (i + 5) {
case null:
case 0:
return "zero";
case 5:
return "five";
case 10:
return "ten";
default:
return "large";
}
}
public static string SwitchOverNullableIntNoNullCase(int? i)
{
switch (i) {
case 0:
return "zero";
case 5:
return "five";
case 10:
return "ten";
default:
return "other";
}
}
public static string SwitchOverNullableIntNoNullCaseShifted(int? i)
{
switch (i + 5) {
case 0:
return "zero";
case 5:
return "five";
case 10:
return "ten";
default:
return "other";
}
}
public static void SwitchOverInt(int i)
{
switch (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 string ShortSwitchOverString(string text)
{
Console.WriteLine("ShortSwitchOverString: " + text);
switch (text) {
case "First case":
return "Text1";
case "Second case":
return "Text2";
case "Third case":
return "Text3";
default:
return "Default";
}
}
public static string ShortSwitchOverStringWithNullCase(string text)
{
Console.WriteLine("ShortSwitchOverStringWithNullCase: " + text);
switch (text) {
case "First case":
return "Text1";
case "Second case":
return "Text2";
case null:
return "null";
default:
return "Default";
}
}
public static string SwitchOverString1(string text)
{
Console.WriteLine("SwitchOverString1: " + 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()
{
Console.WriteLine("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";
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";
}
}
public static string SwitchOverBool(bool b)
{
Console.WriteLine("SwitchOverBool: " + b.ToString());
switch (b) {
case true:
return bool.TrueString;
case false:
return bool.FalseString;
default:
return null;
}
}
public static void SwitchInLoop(int i)
{
Console.WriteLine("SwitchInLoop: " + i);
while (true) {
switch (i) {
case 1:
Console.WriteLine("one");
break;
case 2:
Console.WriteLine("two");
break;
//case 3:
// Console.WriteLine("three");
// continue;
case 4:
Console.WriteLine("four");
return;
default:
Console.WriteLine("default");
Console.WriteLine("more code");
return;
}
i++;
}
}
public static void SwitchWithGoto(int i)
{
Console.WriteLine("SwitchWithGoto: " + 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;
}
Console.WriteLine("End of method");
}
private static SetProperty[] GetProperties()
{
return new SetProperty[0];
}
public static void SwitchOnStringInForLoop()
{
List<SetProperty> list = new List<SetProperty>();
List<SetProperty> list2 = new List<SetProperty>();
SetProperty[] properties = Switch.GetProperties();
for (int i = 0; i < properties.Length; i++) {
SetProperty setProperty = properties[i];
switch (setProperty.Property.Name) {
case "Name1":
setProperty.Set = 1;
list.Add(setProperty);
break;
case "Name2":
setProperty.Set = 2;
list.Add(setProperty);
break;
case "Name3":
setProperty.Set = 3;
list.Add(setProperty);
break;
case "Name4":
setProperty.Set = 4;
list.Add(setProperty);
break;
case "Name5":
case "Name6":
list.Add(setProperty);
break;
default:
list2.Add(setProperty);
break;
}
}
}
public static void SwitchWithComplexCondition(string[] args)
{
switch ((args.Length == 0) ? "dummy" : args[0]) {
case "a":
Console.WriteLine("a");
break;
case "b":
Console.WriteLine("b");
break;
case "c":
Console.WriteLine("c");
break;
case "d":
Console.WriteLine("d");
break;
}
Console.WriteLine("end");
}
public static void SwitchWithArray(string[] args)
{
switch (args[0]) {
case "a":
Console.WriteLine("a");
break;
case "b":
Console.WriteLine("b");
break;
case "c":
Console.WriteLine("c");
break;
case "d":
Console.WriteLine("d");
break;
}
Console.WriteLine("end");
}
}
}

1529
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.il

File diff suppressed because it is too large Load Diff

1306
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.il

File diff suppressed because it is too large Load Diff

1414
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.opt.roslyn.il

File diff suppressed because it is too large Load Diff

1741
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Switch.roslyn.il

File diff suppressed because it is too large Load Diff

2
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Using.cs

@ -58,7 +58,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -58,7 +58,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public void SimpleUsingExpressionStatementWithDeclaration()
{
using (MemoryStream memoryStream = new MemoryStream()) {
memoryStream.WriteByte((byte)42);
memoryStream.WriteByte(42);
Console.WriteLine("using-body: " + memoryStream.Position);
}
}

14
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -71,6 +71,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -71,6 +71,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ILInlining(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new InlineReturnTransform(),
new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
new DetectCatchWhenConditionBlocks(), // must run after inlining but before loop detection
@ -81,6 +82,9 @@ namespace ICSharpCode.Decompiler.CSharp @@ -81,6 +82,9 @@ 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 SwitchOnNullableTransform(),
new SplitVariables(), // split variables once again, because SwitchOnNullableTransform eliminates ldloca
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
// Even though it's a post-order block-transform as most other transforms,
@ -140,7 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -140,7 +144,7 @@ namespace ICSharpCode.Decompiler.CSharp
new IntroduceExtensionMethods(), // must run after IntroduceUsingDeclarations
new IntroduceQueryExpressions(), // must run after IntroduceExtensionMethods
new CombineQueryExpressions(),
//new FlattenSwitchBlocks(),
new FlattenSwitchBlocks(),
new FixNameCollisions(),
new AddXmlDocumentationTransform(),
};
@ -197,8 +201,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -197,8 +201,8 @@ namespace ICSharpCode.Decompiler.CSharp
if (settings.AsyncAwait && AsyncAwaitDecompiler.IsCompilerGeneratedStateMachine(type))
return true;
} else if (type.IsCompilerGenerated()) {
// if (type.Name.StartsWith("<PrivateImplementationDetails>", StringComparison.Ordinal))
// return true;
if (type.Name.StartsWith("<PrivateImplementationDetails>", StringComparison.Ordinal))
return true;
if (settings.AnonymousTypes && type.IsAnonymousType())
return true;
}
@ -211,8 +215,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -211,8 +215,8 @@ namespace ICSharpCode.Decompiler.CSharp
return true;
if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field))
return true;
// if (settings.SwitchStatementOnString && IsSwitchOnStringCache(field))
// return true;
if (settings.SwitchStatementOnString && IsSwitchOnStringCache(field))
return true;
}
// event-fields are not [CompilerGenerated]
if (settings.AutomaticEvents && field.DeclaringType.Events.Any(ev => ev.Name == field.Name))

24
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -93,19 +93,21 @@ namespace ICSharpCode.Decompiler.CSharp @@ -93,19 +93,21 @@ namespace ICSharpCode.Decompiler.CSharp
return astType;
}
public ExpressionWithResolveResult ConvertConstantValue(ResolveResult rr)
public ExpressionWithResolveResult ConvertConstantValue(ResolveResult rr, bool allowImplicitConversion = false)
{
var expr = astBuilder.ConvertConstantValue(rr);
if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) {
expr = new CastExpression(ConvertType(rr.Type), expr);
} else {
switch (rr.Type.GetDefinition()?.KnownTypeCode) {
case KnownTypeCode.SByte:
case KnownTypeCode.Byte:
case KnownTypeCode.Int16:
case KnownTypeCode.UInt16:
expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr);
break;
if (!allowImplicitConversion) {
if (expr is NullReferenceExpression && rr.Type.Kind != TypeKind.Null) {
expr = new CastExpression(ConvertType(rr.Type), expr);
} else {
switch (rr.Type.GetDefinition()?.KnownTypeCode) {
case KnownTypeCode.SByte:
case KnownTypeCode.Byte:
case KnownTypeCode.Int16:
case KnownTypeCode.UInt16:
expr = new CastExpression(new PrimitiveType(KnownTypeReference.GetCSharpNameByTypeCode(rr.Type.GetDefinition().KnownTypeCode)), expr);
break;
}
}
}
var exprRR = expr.Annotation<ResolveResult>();

131
ICSharpCode.Decompiler/CSharp/StatementBuilder.cs

@ -83,42 +83,134 @@ namespace ICSharpCode.Decompiler.CSharp @@ -83,42 +83,134 @@ namespace ICSharpCode.Decompiler.CSharp
return new IfElseStatement(condition, trueStatement, falseStatement);
}
CaseLabel CreateTypedCaseLabel(long i, IType type)
ConstantResolveResult 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);
} else if (type.IsKnownType(KnownTypeCode.NullableOfT)) {
var nullableType = NullableType.GetUnderlyingType(type);
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(nullableType), i, false);
} else {
value = CSharpPrimitiveCast.Cast(ReflectionHelper.GetTypeCode(type), i, false);
}
return new CaseLabel(exprBuilder.ConvertConstantValue(new ConstantResolveResult(type, value)));
return new ConstantResolveResult(type, value);
}
protected internal override Statement VisitSwitchInstruction(SwitchInstruction inst)
{
return TranslateSwitch(null, inst);
}
SwitchStatement TranslateSwitch(BlockContainer switchContainer, SwitchInstruction inst)
{
Debug.Assert(switchContainer.EntryPoint.IncomingEdgeCount == 1);
var oldBreakTarget = breakTarget;
breakTarget = null; // 'break' within a switch would only leave the switch
var value = exprBuilder.Translate(inst.Value);
var stmt = new SwitchStatement() { Expression = value };
breakTarget = switchContainer; // 'break' within a switch would only leave the switch
var oldCaseLabelMapping = caseLabelMapping;
caseLabelMapping = new Dictionary<Block, ConstantResolveResult>();
TranslatedExpression value;
var strToInt = inst.Value as StringToInt;
if (strToInt != null) {
value = exprBuilder.Translate(strToInt.Argument);
} else {
value = exprBuilder.Translate(inst.Value);
}
// Pick the section with the most labels as default section.
IL.SwitchSection defaultSection = inst.Sections.First();
foreach (var section in inst.Sections) {
var astSection = new Syntax.SwitchSection();
astSection.CaseLabels.AddRange(section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type)));
ConvertSwitchSectionBody(astSection, section.Body);
stmt.SwitchSections.Add(astSection);
if (section.Labels.Count() > defaultSection.Labels.Count()) {
defaultSection = section;
}
}
if (inst.DefaultBody.OpCode != OpCode.Nop) {
var stmt = new SwitchStatement() { Expression = value };
Dictionary<IL.SwitchSection, Syntax.SwitchSection> translationDictionary = new Dictionary<IL.SwitchSection, Syntax.SwitchSection>();
// initialize C# switch sections.
foreach (var section in inst.Sections) {
// This is used in the block-label mapping.
ConstantResolveResult firstValueResolveResult;
var astSection = new Syntax.SwitchSection();
astSection.CaseLabels.Add(new CaseLabel());
ConvertSwitchSectionBody(astSection, inst.DefaultBody);
// Create case labels:
if (section == defaultSection) {
astSection.CaseLabels.Add(new CaseLabel());
firstValueResolveResult = null;
} else {
var values = section.Labels.Values.Select(i => CreateTypedCaseLabel(i, value.Type, strToInt?.Map)).ToArray();
if (section.HasNullLabel) {
astSection.CaseLabels.Add(new CaseLabel(new NullReferenceExpression()));
firstValueResolveResult = new ConstantResolveResult(SpecialType.NullType, null);
} else {
Debug.Assert(values.Length > 0);
firstValueResolveResult = values[0];
}
astSection.CaseLabels.AddRange(values.Select(label => new CaseLabel(exprBuilder.ConvertConstantValue(label, allowImplicitConversion: true))));
}
switch (section.Body) {
case Branch br:
// we can only inline the block, if all branches are in the switchContainer.
if (br.TargetBlock.Parent == switchContainer && switchContainer.Descendants.OfType<Branch>().Where(b => b.TargetBlock == br.TargetBlock).All(b => BlockContainer.FindClosestSwitchContainer(b) == switchContainer))
caseLabelMapping.Add(br.TargetBlock, firstValueResolveResult);
break;
default:
break;
}
translationDictionary.Add(section, astSection);
stmt.SwitchSections.Add(astSection);
}
foreach (var section in inst.Sections) {
var astSection = translationDictionary[section];
switch (section.Body) {
case Branch br:
// we can only inline the block, if all branches are in the switchContainer.
if (br.TargetBlock.Parent == switchContainer && switchContainer.Descendants.OfType<Branch>().Where(b => b.TargetBlock == br.TargetBlock).All(b => BlockContainer.FindClosestSwitchContainer(b) == switchContainer))
ConvertSwitchSectionBody(astSection, br.TargetBlock);
else
ConvertSwitchSectionBody(astSection, section.Body);
break;
case Leave leave:
if (astSection.CaseLabels.Count == 1 && astSection.CaseLabels.First().Expression.IsNull && leave.TargetContainer == switchContainer) {
stmt.SwitchSections.Remove(astSection);
break;
}
goto default;
default:
ConvertSwitchSectionBody(astSection, section.Body);
break;
}
}
if (switchContainer != null) {
// Translate any remaining blocks:
var lastSectionStatements = stmt.SwitchSections.Last().Statements;
foreach (var block in switchContainer.Blocks.Skip(1)) {
if (caseLabelMapping.ContainsKey(block)) continue;
lastSectionStatements.Add(new LabelStatement { Label = block.Label });
foreach (var nestedInst in block.Instructions) {
var nestedStmt = Convert(nestedInst);
if (nestedStmt is BlockStatement b) {
foreach (var nested in b.Statements)
lastSectionStatements.Add(nested.Detach());
} else {
lastSectionStatements.Add(nestedStmt);
}
}
Debug.Assert(block.FinalInstruction.OpCode == OpCode.Nop);
}
if (endContainerLabels.TryGetValue(switchContainer, out string label)) {
lastSectionStatements.Add(new LabelStatement { Label = label });
lastSectionStatements.Add(new BreakStatement());
}
}
breakTarget = oldBreakTarget;
caseLabelMapping = oldCaseLabelMapping;
return stmt;
}
@ -141,6 +233,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -141,6 +233,8 @@ namespace ICSharpCode.Decompiler.CSharp
Block continueTarget;
/// <summary>Number of ContinueStatements that were created for the current continueTarget</summary>
int continueCount;
/// <summary>Maps blocks to cases.</summary>
Dictionary<Block, ConstantResolveResult> caseLabelMapping;
protected internal override Statement VisitBranch(Branch inst)
{
@ -148,6 +242,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -148,6 +242,11 @@ namespace ICSharpCode.Decompiler.CSharp
continueCount++;
return new ContinueStatement();
}
if (caseLabelMapping != null && caseLabelMapping.TryGetValue(inst.TargetBlock, out var label)) {
if (label == null)
return new GotoDefaultStatement();
return new GotoCaseStatement() { LabelExpression = exprBuilder.ConvertConstantValue(label, allowImplicitConversion: true) };
}
return new GotoStatement(inst.TargetLabel);
}
@ -608,6 +707,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -608,6 +707,8 @@ namespace ICSharpCode.Decompiler.CSharp
continueCount = oldContinueCount;
breakTarget = oldBreakTarget;
return loop;
} else if (container.EntryPoint.Instructions.Count == 1 && container.EntryPoint.Instructions[0] is SwitchInstruction switchInst) {
return TranslateSwitch(container, switchInst);
} else {
var blockStmt = ConvertBlockContainer(container, false);
blockStmt.AddAnnotation(container);

6
ICSharpCode.Decompiler/CSharp/Transforms/FlattenSwitchBlocks.cs

@ -2,15 +2,15 @@ @@ -2,15 +2,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory.CSharp;
using ICSharpCode.Decompiler.CSharp.Syntax;
namespace ICSharpCode.Decompiler.CSharp.Transforms
{
class FlattenSwitchBlocks : IAstTransform
{
public void Run(AstNode compilationUnit)
public void Run(AstNode rootNode, TransformContext context)
{
foreach (var switchSection in compilationUnit.Descendants.OfType<SwitchSection>())
foreach (var switchSection in rootNode.Descendants.OfType<SwitchSection>())
{
if (switchSection.Statements.Count != 1)
continue;

172
ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs

@ -96,11 +96,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -96,11 +96,6 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement)
{
if (context.Settings.SwitchStatementOnString) {
AstNode result = TransformSwitchOnString(ifElseStatement);
if (result != null)
return result;
}
AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement);
if (simplifiedIfElse != null)
return simplifiedIfElse;
@ -256,174 +251,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -256,174 +251,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return null;
}
#endregion
#region switch on strings
static readonly IfElseStatement switchOnStringPattern = new IfElseStatement {
Condition = new BinaryOperatorExpression {
Left = new AnyNode("switchExpr"),
Operator = BinaryOperatorType.InEquality,
Right = new NullReferenceExpression()
},
TrueStatement = new BlockStatement {
new IfElseStatement {
Condition = new BinaryOperatorExpression {
Left = new AnyNode("cachedDict"),
Operator = BinaryOperatorType.Equality,
Right = new NullReferenceExpression()
},
TrueStatement = new AnyNode("dictCreation")
},
new IfElseStatement {
Condition = new InvocationExpression(new MemberReferenceExpression(new Backreference("cachedDict").ToExpression(), "TryGetValue"),
new NamedNode("switchVar", new IdentifierExpression(Pattern.AnyString)),
new DirectionExpression {
FieldDirection = FieldDirection.Out,
Expression = new IdentifierExpression(Pattern.AnyString).WithName("intVar")
}),
TrueStatement = new BlockStatement {
Statements = {
new NamedNode(
"switch", new SwitchStatement {
Expression = new IdentifierExpressionBackreference("intVar"),
SwitchSections = { new Repeat(new AnyNode()) }
})
}
}
},
new Repeat(new AnyNode("nonNullDefaultStmt")).ToStatement()
},
FalseStatement = new OptionalNode("nullStmt", new BlockStatement { Statements = { new Repeat(new AnyNode()) } })
};
public SwitchStatement TransformSwitchOnString(IfElseStatement node)
{
Match m = switchOnStringPattern.Match(node);
if (!m.Success)
return null;
// switchVar must be the same as switchExpr; or switchExpr must be an assignment and switchVar the left side of that assignment
if (!m.Get("switchVar").Single().IsMatch(m.Get("switchExpr").Single())) {
AssignmentExpression assign = m.Get("switchExpr").Single() as AssignmentExpression;
if (!(assign != null && m.Get("switchVar").Single().IsMatch(assign.Left)))
return null;
}
FieldReference cachedDictField = m.Get<AstNode>("cachedDict").Single().Annotation<FieldReference>();
if (cachedDictField == null)
return null;
List<Statement> dictCreation = m.Get<BlockStatement>("dictCreation").Single().Statements.ToList();
List<KeyValuePair<string, int>> dict = BuildDictionary(dictCreation);
SwitchStatement sw = m.Get<SwitchStatement>("switch").Single();
sw.Expression = m.Get<Expression>("switchExpr").Single().Detach();
foreach (SwitchSection section in sw.SwitchSections) {
List<CaseLabel> labels = section.CaseLabels.ToList();
section.CaseLabels.Clear();
foreach (CaseLabel label in labels) {
PrimitiveExpression expr = label.Expression as PrimitiveExpression;
if (expr == null || !(expr.Value is int))
continue;
int val = (int)expr.Value;
foreach (var pair in dict) {
if (pair.Value == val)
section.CaseLabels.Add(new CaseLabel { Expression = new PrimitiveExpression(pair.Key) });
}
}
}
if (m.Has("nullStmt")) {
SwitchSection section = new SwitchSection();
section.CaseLabels.Add(new CaseLabel { Expression = new NullReferenceExpression() });
BlockStatement block = m.Get<BlockStatement>("nullStmt").Single();
block.Statements.Add(new BreakStatement());
section.Statements.Add(block.Detach());
sw.SwitchSections.Add(section);
} else if (m.Has("nonNullDefaultStmt")) {
sw.SwitchSections.Add(
new SwitchSection {
CaseLabels = { new CaseLabel { Expression = new NullReferenceExpression() } },
Statements = { new BlockStatement { new BreakStatement() } }
});
}
if (m.Has("nonNullDefaultStmt")) {
SwitchSection section = new SwitchSection();
section.CaseLabels.Add(new CaseLabel());
BlockStatement block = new BlockStatement();
block.Statements.AddRange(m.Get<Statement>("nonNullDefaultStmt").Select(s => s.Detach()));
block.Add(new BreakStatement());
section.Statements.Add(block);
sw.SwitchSections.Add(section);
}
node.ReplaceWith(sw);
return sw;
}
List<KeyValuePair<string, int>> BuildDictionary(List<Statement> dictCreation)
{
if (context.Settings.ObjectOrCollectionInitializers && dictCreation.Count == 1)
return BuildDictionaryFromInitializer(dictCreation[0]);
return BuildDictionaryFromAddMethodCalls(dictCreation);
}
static readonly Statement assignInitializedDictionary = new ExpressionStatement {
Expression = new AssignmentExpression {
Left = new AnyNode().ToExpression(),
Right = new ObjectCreateExpression {
Type = new AnyNode(),
Arguments = { new Repeat(new AnyNode()) },
Initializer = new ArrayInitializerExpression {
Elements = { new Repeat(new AnyNode("dictJumpTable")) }
}
},
},
};
private List<KeyValuePair<string, int>> BuildDictionaryFromInitializer(Statement statement)
{
List<KeyValuePair<string, int>> dict = new List<KeyValuePair<string, int>>();
Match m = assignInitializedDictionary.Match(statement);
if (!m.Success)
return dict;
foreach (ArrayInitializerExpression initializer in m.Get<ArrayInitializerExpression>("dictJumpTable")) {
KeyValuePair<string, int> pair;
if (TryGetPairFrom(initializer.Elements, out pair))
dict.Add(pair);
}
return dict;
}
private static List<KeyValuePair<string, int>> BuildDictionaryFromAddMethodCalls(List<Statement> dictCreation)
{
List<KeyValuePair<string, int>> dict = new List<KeyValuePair<string, int>>();
for (int i = 0; i < dictCreation.Count; i++) {
ExpressionStatement es = dictCreation[i] as ExpressionStatement;
if (es == null)
continue;
InvocationExpression ie = es.Expression as InvocationExpression;
if (ie == null)
continue;
KeyValuePair<string, int> pair;
if (TryGetPairFrom(ie.Arguments, out pair))
dict.Add(pair);
}
return dict;
}
private static bool TryGetPairFrom(AstNodeCollection<Expression> expressions, out KeyValuePair<string, int> pair)
{
PrimitiveExpression arg1 = expressions.ElementAtOrDefault(0) as PrimitiveExpression;
PrimitiveExpression arg2 = expressions.ElementAtOrDefault(1) as PrimitiveExpression;
if (arg1 != null && arg2 != null && arg1.Value is string && arg2.Value is int) {
pair = new KeyValuePair<string, int>((string)arg1.Value, (int)arg2.Value);
return true;
}
pair = default(KeyValuePair<string, int>);
return false;
}
#endregion
#region Automatic Properties
static readonly PropertyDeclaration automaticPropertyPattern = new PropertyDeclaration {
Attributes = { new Repeat(new AnyNode()) },

2
ICSharpCode.Decompiler/CSharp/TranslatedExpression.cs

@ -303,7 +303,7 @@ namespace ICSharpCode.Decompiler.CSharp @@ -303,7 +303,7 @@ namespace ICSharpCode.Decompiler.CSharp
}
var rr = expressionBuilder.resolver.WithCheckForOverflow(checkForOverflow).ResolveCast(targetType, ResolveResult);
if (rr.IsCompileTimeConstant && !rr.IsError) {
return expressionBuilder.ConvertConstantValue(rr)
return expressionBuilder.ConvertConstantValue(rr, allowImplicitConversion)
.WithILInstruction(this.ILInstructions);
}
if (targetType.Kind == TypeKind.Pointer && (0.Equals(ResolveResult.ConstantValue) || 0u.Equals(ResolveResult.ConstantValue))) {

1
ICSharpCode.Decompiler/DotNetCore/DotNetCorePathFinderExtensions.cs

@ -8,7 +8,6 @@ using Newtonsoft.Json.Linq; @@ -8,7 +8,6 @@ using Newtonsoft.Json.Linq;
namespace ICSharpCode.Decompiler
{
public static class DotNetCorePathFinderExtensions
{
public static string DetectTargetFrameworkId(this AssemblyDefinition assembly)

63
ICSharpCode.Decompiler/FlowAnalysis/ControlFlowNode.cs

@ -19,6 +19,7 @@ @@ -19,6 +19,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.FlowAnalysis
{
@ -118,35 +119,37 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -118,35 +119,37 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
}
return false;
}
//public static GraphVizGraph ExportGraph(IReadOnlyList<ControlFlowNode> nodes, Func<ControlFlowNode, string> labelFunc = null)
//{
// if (labelFunc == null) {
// labelFunc = node => {
// var block = node.UserData as IL.Block;
// return block != null ? block.Label : node.UserData?.ToString();
// };
// }
// GraphVizGraph g = new GraphVizGraph();
// GraphVizNode[] n = new GraphVizNode[nodes.Count];
// for (int i = 0; i < n.Length; i++) {
// n[i] = new GraphVizNode(nodes[i].UserIndex);
// n[i].shape = "box";
// n[i].label = labelFunc(nodes[i]);
// g.AddNode(n[i]);
// }
// foreach (var source in nodes) {
// foreach (var target in source.Successors) {
// g.AddEdge(new GraphVizEdge(source.UserIndex, target.UserIndex));
// }
// if (source.ImmediateDominator != null) {
// g.AddEdge(
// new GraphVizEdge(source.ImmediateDominator.UserIndex, source.UserIndex) {
// color = "green"
// });
// }
// }
// return g;
//}
#if DEBUG
internal static GraphVizGraph ExportGraph(IReadOnlyList<ControlFlowNode> nodes, Func<ControlFlowNode, string> labelFunc = null)
{
if (labelFunc == null) {
labelFunc = node => {
var block = node.UserData as IL.Block;
return block != null ? block.Label : node.UserData?.ToString();
};
}
GraphVizGraph g = new GraphVizGraph();
GraphVizNode[] n = new GraphVizNode[nodes.Count];
for (int i = 0; i < n.Length; i++) {
n[i] = new GraphVizNode(nodes[i].UserIndex);
n[i].shape = "box";
n[i].label = labelFunc(nodes[i]);
g.AddNode(n[i]);
}
foreach (var source in nodes) {
foreach (var target in source.Successors) {
g.AddEdge(new GraphVizEdge(source.UserIndex, target.UserIndex));
}
if (source.ImmediateDominator != null) {
g.AddEdge(
new GraphVizEdge(source.ImmediateDominator.UserIndex, source.UserIndex) {
color = "green"
});
}
}
return g;
}
#endif
}
}

8
ICSharpCode.Decompiler/FlowAnalysis/DataFlowVisitor.cs

@ -614,17 +614,17 @@ namespace ICSharpCode.Decompiler.FlowAnalysis @@ -614,17 +614,17 @@ namespace ICSharpCode.Decompiler.FlowAnalysis
DebugStartPoint(inst);
inst.Value.AcceptVisitor(this);
State beforeSections = state.Clone();
inst.DefaultBody.AcceptVisitor(this);
inst.Sections[0].AcceptVisitor(this);
State afterSections = state.Clone();
foreach (var section in inst.Sections) {
for (int i = 1; i < inst.Sections.Count; ++i) {
state.ReplaceWith(beforeSections);
section.AcceptVisitor(this);
inst.Sections[i].AcceptVisitor(this);
afterSections.JoinWith(state);
}
state = afterSections;
DebugEndPoint(inst);
}
protected internal override void VisitYieldReturn(YieldReturn inst)
{
DebugStartPoint(inst);

7
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -224,6 +224,7 @@ @@ -224,6 +224,7 @@
<Compile Include="CSharp\Resolver\RenameCallbackArguments.cs" />
<Compile Include="CSharp\Resolver\TypeInference.cs" />
<Compile Include="CSharp\Transforms\CombineQueryExpressions.cs" />
<Compile Include="CSharp\Transforms\FlattenSwitchBlocks.cs" />
<Compile Include="CSharp\Transforms\IntroduceExtensionMethods.cs" />
<Compile Include="CSharp\Transforms\IntroduceQueryExpressions.cs" />
<Compile Include="CSharp\Transforms\PrettifyAssignments.cs" />
@ -274,6 +275,11 @@ @@ -274,6 +275,11 @@
<Compile Include="IL\Instructions\CallIndirect.cs" />
<Compile Include="IL\Transforms\EarlyExpressionTransforms.cs" />
<Compile Include="IL\Transforms\ProxyCallReplacer.cs" />
<Compile Include="IL\Instructions\StringToInt.cs" />
<Compile Include="IL\Instructions\UsingInstruction.cs" />
<Compile Include="IL\Transforms\InlineReturnTransform.cs" />
<Compile Include="IL\Transforms\SwitchOnNullableTransform.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" />
@ -295,6 +301,7 @@ @@ -295,6 +301,7 @@
<Compile Include="IL\Transforms\StatementTransform.cs" />
<Compile Include="IL\Transforms\TransformCollectionAndObjectInitializers.cs" />
<Compile Include="Output\TextTokenWriter.cs" />
<Compile Include="Util\GraphVizGraph.cs" />
<Compile Include="Util\KeyComparer.cs" />
<Compile Include="Util\LongDict.cs" />
<Compile Include="Util\UnicodeNewline.cs" />

11
ICSharpCode.Decompiler/IL/BlockBuilder.cs

@ -163,8 +163,15 @@ namespace ICSharpCode.Decompiler.IL @@ -163,8 +163,15 @@ namespace ICSharpCode.Decompiler.IL
if (currentBlock == null)
return;
currentBlock.ILRange = new Interval(currentBlock.ILRange.Start, currentILOffset);
if (fallthrough)
currentBlock.Instructions.Add(new Branch(currentILOffset));
if (fallthrough) {
if (currentBlock.Instructions.LastOrDefault() is SwitchInstruction switchInst && switchInst.Sections.Last().Body.MatchNop()) {
// Instead of putting the default branch after the switch instruction
switchInst.Sections.Last().Body = new Branch(currentILOffset);
Debug.Assert(switchInst.HasFlag(InstructionFlags.EndPointUnreachable));
} else {
currentBlock.Instructions.Add(new Branch(currentILOffset));
}
}
currentBlock = null;
}

51
ICSharpCode.Decompiler/IL/ControlFlow/ConditionDetection.cs

@ -63,18 +63,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -63,18 +63,14 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Last instruction is one with unreachable endpoint
// (guaranteed by combination of BlockContainer and Block invariants)
Debug.Assert(block.Instructions.Last().HasFlag(InstructionFlags.EndPointUnreachable));
ILInstruction exitInst = block.Instructions.Last();
ILInstruction exitInst = block.Instructions.Last();
// Previous-to-last instruction might have conditional control flow,
// usually an IfInstruction with a branch:
IfInstruction ifInst = block.Instructions.SecondToLastOrDefault() as IfInstruction;
if (ifInst != null && ifInst.FalseInst.OpCode == OpCode.Nop) {
HandleIfInstruction(cfgNode, block, ifInst, ref exitInst);
} else {
SwitchInstruction switchInst = block.Instructions.SecondToLastOrDefault() as SwitchInstruction;
if (switchInst != null) {
HandleSwitchInstruction(cfgNode, block, switchInst, ref exitInst);
}
}
if (IsUsableBranchToChild(cfgNode, exitInst)) {
// "...; goto usableblock;"
@ -288,48 +284,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -288,48 +284,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
&& targetBlock.IncomingEdgeCount == 1 && targetBlock.FinalInstruction.OpCode == OpCode.Nop
&& cfgNode.Dominates(context.ControlFlowGraph.GetNode(targetBlock));
}
private void HandleSwitchInstruction(ControlFlowNode cfgNode, Block block, SwitchInstruction sw, ref ILInstruction exitInst)
{
Debug.Assert(sw.DefaultBody is Nop);
// First, move blocks into the switch section
foreach (var section in sw.Sections) {
if (IsUsableBranchToChild(cfgNode, section.Body)) {
// case ...: goto targetBlock;
var targetBlock = ((Branch)section.Body).TargetBlock;
targetBlock.Remove();
section.Body = targetBlock;
}
}
// Move the code following the switch into the default section
if (IsUsableBranchToChild(cfgNode, exitInst)) {
// switch(...){} goto targetBlock;
// ---> switch(..) { default: { targetBlock } }
var targetBlock = ((Branch)exitInst).TargetBlock;
targetBlock.Remove();
sw.DefaultBody = targetBlock;
if (IsBranchOrLeave(targetBlock.Instructions.Last())) {
exitInst = block.Instructions[block.Instructions.Count - 1] = targetBlock.Instructions.Last();
targetBlock.Instructions.RemoveAt(targetBlock.Instructions.Count - 1);
} else {
exitInst = null;
block.Instructions.RemoveAt(block.Instructions.Count - 1);
}
}
// Remove compatible exitInsts from switch sections:
foreach (var section in sw.Sections) {
Block sectionBlock = section.Body as Block;
if (sectionBlock != null && exitInst == null && IsBranchOrLeave(sectionBlock.Instructions.Last())) {
exitInst = sectionBlock.Instructions.Last();
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
block.Instructions.Add(exitInst);
} else if (sectionBlock != null && DetectExitPoints.CompatibleExitInstruction(exitInst, sectionBlock.Instructions.Last())) {
sectionBlock.Instructions.RemoveAt(sectionBlock.Instructions.Count - 1);
}
}
sw.Sections.ReplaceList(sw.Sections.OrderBy(s => s.Body.ILRange.Start));
}
private bool IsBranchOrLeave(ILInstruction inst)
{
switch (inst) {

30
ICSharpCode.Decompiler/IL/ControlFlow/ControlFlowSimplification.cs

@ -19,6 +19,7 @@ using System.Collections.Generic; @@ -19,6 +19,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.IL.ControlFlow
{
@ -52,7 +53,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -52,7 +53,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
SwitchDetection.SimplifySwitchInstruction(block);
}
SimplifyBranchChains(function, context);
CleanUpEmptyBlocks(function);
CleanUpEmptyBlocks(function, context);
}
void InlineVariableInReturnBlock(Block block, ILTransformContext context)
@ -129,13 +130,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -129,13 +130,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
void CleanUpEmptyBlocks(ILFunction function)
void CleanUpEmptyBlocks(ILFunction function, ILTransformContext context)
{
foreach (var container in function.Descendants.OfType<BlockContainer>()) {
foreach (var block in container.Blocks) {
if (block.Instructions.Count == 0)
continue; // block is already marked for deletion
while (CombineBlockWithNextBlock(container, block)) {
while (CombineBlockWithNextBlock(container, block, context)) {
// repeat combining blocks until it is no longer possible
// (this loop terminates because a block is deleted in every iteration)
}
@ -153,7 +154,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -153,7 +154,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return targetBlock.Instructions[0].MatchReturn(out var value) && value is LdLoc;
}
static bool CombineBlockWithNextBlock(BlockContainer container, Block block)
static bool CombineBlockWithNextBlock(BlockContainer container, Block block, ILTransformContext context)
{
Debug.Assert(container == block.Parent);
// Ensure the block will stay a basic block -- we don't want extended basic blocks prior to LoopDetection.
@ -165,12 +166,31 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -165,12 +166,31 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return false;
if (br.TargetBlock == block)
return false; // don't inline block into itself
context.Step("CombineBlockWithNextBlock", br);
var targetBlock = br.TargetBlock;
if (targetBlock.ILRange.Start < block.ILRange.Start && IsDeadTrueStore(block)) {
// The C# compiler generates a dead store for the condition of while (true) loops.
block.Instructions.RemoveRange(block.Instructions.Count - 3, 2);
}
block.Instructions.Remove(br);
block.Instructions.AddRange(targetBlock.Instructions);
targetBlock.Instructions.Clear(); // mark targetBlock for deletion
return true;
}
/// <summary>
/// Returns true if the last two instructions before the branch are storing the value 'true' into an unused variable.
/// </summary>
private static bool IsDeadTrueStore(Block block)
{
if (block.Instructions.Count < 3) return false;
if (!(block.Instructions.SecondToLastOrDefault() is StLoc deadStore && block.Instructions[block.Instructions.Count - 3] is StLoc tempStore))
return false;
if (!(deadStore.Variable.LoadCount == 0 && deadStore.Variable.AddressCount == 0))
return false;
if (!(deadStore.Value.MatchLdLoc(tempStore.Variable) && tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1))
return false;
return tempStore.Value.MatchLdcI4(1) && deadStore.Variable.Type.IsKnownType(KnownTypeCode.Boolean);
}
}
}

151
ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
// 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.Diagnostics;
using System.Linq;
@ -57,6 +58,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -57,6 +58,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Because this is a post-order block transform, we can assume that
// any nested loops within this loop have already been constructed.
if (block.Instructions.Last() is SwitchInstruction switchInst) {
// Switch instructions support "break;" just like loops
DetectSwitchBody(block, switchInst);
}
ControlFlowNode h = context.ControlFlowNode; // CFG node for our potential loop head
Debug.Assert(h.UserData == block);
Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited));
@ -101,7 +107,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -101,7 +107,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ConstructLoop(loop, exitPoint);
}
}
/// <summary>
/// Recurse into the dominator tree and find back edges/natural loops.
/// </summary>
@ -202,9 +208,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -202,9 +208,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
///
/// Precondition: Requires that a node is marked as visited iff it is contained in the loop.
/// </remarks>
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, out ControlFlowNode exitPoint)
void ExtendLoop(ControlFlowNode loopHead, List<ControlFlowNode> loop, out ControlFlowNode exitPoint, bool isSwitch=false)
{
exitPoint = FindExitPoint(loopHead, loop);
exitPoint = FindExitPoint(loopHead, loop, isSwitch);
Debug.Assert(!loop.Contains(exitPoint), "Cannot pick an exit point that is part of the natural loop");
if (exitPoint != null) {
// Either we are in case 1 and just picked an exit that maximizes the amount of code
@ -227,9 +233,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -227,9 +233,15 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// Finds a suitable single exit point for the specified loop.
/// </summary>
/// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop)
ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop, bool treatBackEdgesAsExits)
{
if (!context.ControlFlowGraph.HasReachableExit(loopHead)) {
bool hasReachableExit = context.ControlFlowGraph.HasReachableExit(loopHead);
if (!hasReachableExit && treatBackEdgesAsExits) {
// If we're analyzing the switch, there's no reachable exit, but the loopHead (=switchHead) block
// is also a loop head, we consider the back-edge a reachable exit for the switch.
hasReachableExit = loopHead.Predecessors.Any(p => loopHead.Dominates(p));
}
if (!hasReachableExit) {
// Case 1:
// There are no nodes n so that loopHead dominates a predecessor of n but not n itself
// -> we could build a loop with zero exit points.
@ -244,7 +256,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -244,7 +256,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// We need to pick our exit point so that all paths from the loop head
// to the reachable exits run through that exit point.
var cfg = context.ControlFlowGraph.cfg;
var revCfg = PrepareReverseCFG(loopHead);
var revCfg = PrepareReverseCFG(loopHead, treatBackEdgesAsExits);
//ControlFlowNode.ExportGraph(cfg).Show("cfg");
//ControlFlowNode.ExportGraph(revCfg).Show("rev");
ControlFlowNode commonAncestor = revCfg[loopHead.UserIndex];
@ -255,22 +267,63 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -255,22 +267,63 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
commonAncestor = Dominance.FindCommonDominator(commonAncestor, revNode);
}
}
// All paths from within the loop to a reachable exit run through 'commonAncestor'.
// However, this doesn't mean that 'commonAncestor' is valid as an exit point.
// We walk up the post-dominator tree until we've got a valid exit point:
ControlFlowNode exitPoint;
while (commonAncestor.UserIndex >= 0) {
exitPoint = cfg[commonAncestor.UserIndex];
Debug.Assert(exitPoint.Visited == naturalLoop.Contains(exitPoint));
if (exitPoint.Visited) {
commonAncestor = commonAncestor.ImmediateDominator;
continue;
} else {
// It's possible that 'commonAncestor' is itself part of the natural loop.
// If so, it's not a valid exit point.
if (!exitPoint.Visited && ValidateExitPoint(loopHead, exitPoint)) {
// we found an exit point
return exitPoint;
}
commonAncestor = commonAncestor.ImmediateDominator;
}
// least common dominator is the artificial exit node
// least common post-dominator is the artificial exit node
return null;
}
}
/// <summary>
/// Validates an exit point.
///
/// An exit point is invalid iff there is a node reachable from the exit point that
/// is dominated by the loop head, but not by the exit point.
/// (i.e. this method returns false iff the exit point's dominance frontier contains
/// a node dominated by the loop head. but we implement this the slow way because
/// we don't have dominance frontiers precomputed)
/// </summary>
/// <remarks>
/// We need this because it's possible that there's a return block (thus reverse-unreachable node ignored by post-dominance)
/// that is reachable both directly from the loop, and from the exit point.
/// </remarks>
bool ValidateExitPoint(ControlFlowNode loopHead, ControlFlowNode exitPoint)
{
var cfg = context.ControlFlowGraph;
return IsValid(exitPoint);
bool IsValid(ControlFlowNode node)
{
if (!cfg.HasReachableExit(node)) {
// Optimization: if the dominance frontier is empty, we don't need
// to check every node.
return true;
}
foreach (var succ in node.Successors) {
if (loopHead != succ && loopHead.Dominates(succ) && !exitPoint.Dominates(succ))
return false;
}
foreach (var child in node.DominatorTreeChildren) {
if (!IsValid(child))
return false;
}
return true;
}
}
/// <summary>
/// Pick exit point by picking any node that has no reachable exits.
///
@ -308,7 +361,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -308,7 +361,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
}
ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead)
ControlFlowNode[] PrepareReverseCFG(ControlFlowNode loopHead, bool treatBackEdgesAsExits)
{
ControlFlowNode[] cfg = context.ControlFlowGraph.cfg;
ControlFlowNode[] rev = new ControlFlowNode[cfg.Length + 1];
@ -322,7 +375,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -322,7 +375,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
continue;
// Add reverse edges for all edges in cfg
foreach (var succ in cfg[i].Successors) {
if (loopHead.Dominates(succ)) {
if (loopHead.Dominates(succ) && (!treatBackEdgesAsExits || loopHead != succ)) {
rev[succ.UserIndex].AddEdgeTo(rev[i]);
} else {
exitNode.AddEdgeTo(rev[i]);
@ -412,13 +465,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -412,13 +465,25 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Move contents of oldEntryPoint to newEntryPoint
// (we can't move the block itself because it might be the target of branch instructions outside the loop)
newEntryPoint.Instructions.ReplaceList(oldEntryPoint.Instructions);
newEntryPoint.FinalInstruction = oldEntryPoint.FinalInstruction;
newEntryPoint.ILRange = oldEntryPoint.ILRange;
oldEntryPoint.Instructions.ReplaceList(new[] { loopContainer });
if (exitTargetBlock != null)
oldEntryPoint.Instructions.Add(new Branch(exitTargetBlock));
oldEntryPoint.FinalInstruction = new Nop();
MoveBlocksIntoContainer(loop, loopContainer);
// Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
foreach (var branch in loopContainer.Descendants.OfType<Branch>()) {
if (branch.TargetBlock == oldEntryPoint) {
branch.TargetBlock = newEntryPoint;
} else if (branch.TargetBlock == exitTargetBlock) {
branch.ReplaceWith(new Leave(loopContainer) { ILRange = branch.ILRange });
}
}
}
private void MoveBlocksIntoContainer(List<ControlFlowNode> loop, BlockContainer loopContainer)
{
// Move other blocks into the loop body: they're all dominated by the loop header,
// and thus cannot be the target of branch instructions outside the loop.
for (int i = 1; i < loop.Count; i++) {
@ -440,12 +505,56 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -440,12 +505,56 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Block block = (Block)loop[i].UserData;
Debug.Assert(block.IsDescendantOf(loopContainer));
}
}
private void DetectSwitchBody(Block block, SwitchInstruction switchInst)
{
Debug.Assert(block.Instructions.Last() == switchInst);
ControlFlowNode h = context.ControlFlowNode; // CFG node for our switch head
Debug.Assert(h.UserData == block);
Debug.Assert(!TreeTraversal.PreOrder(h, n => n.DominatorTreeChildren).Any(n => n.Visited));
var nodesInSwitch = new List<ControlFlowNode>();
nodesInSwitch.Add(h);
h.Visited = true;
ExtendLoop(h, nodesInSwitch, out var exitPoint, isSwitch: true);
if (exitPoint != null && exitPoint.Predecessors.Count == 1 && !context.ControlFlowGraph.HasReachableExit(exitPoint)) {
// If the exit point is reachable from just one single "break;",
// it's better to move the code into the switch.
// (unlike loops which should not be nested unless necessary,
// nesting switches makes it clearer in which cases a piece of code is reachable)
nodesInSwitch.AddRange(TreeTraversal.PreOrder(exitPoint, p => p.DominatorTreeChildren));
exitPoint = null;
}
context.Step("Create BlockContainer for switch", switchInst);
// Sort blocks in the loop in reverse post-order to make the output look a bit nicer.
// (if the loop doesn't contain nested loops, this is a topological sort)
nodesInSwitch.Sort((a, b) => b.PostOrderNumber.CompareTo(a.PostOrderNumber));
Debug.Assert(nodesInSwitch[0] == h);
foreach (var node in nodesInSwitch) {
node.Visited = false; // reset visited flag so that we can find outer loops
Debug.Assert(h.Dominates(node) || !node.IsReachable, "The switch body must be dominated by the switch head");
}
BlockContainer switchContainer = new BlockContainer();
Block newEntryPoint = new Block();
newEntryPoint.ILRange = switchInst.ILRange;
switchContainer.Blocks.Add(newEntryPoint);
newEntryPoint.Instructions.Add(switchInst);
block.Instructions[block.Instructions.Count - 1] = switchContainer;
Block exitTargetBlock = (Block)exitPoint?.UserData;
if (exitTargetBlock != null) {
block.Instructions.Add(new Branch(exitTargetBlock));
}
MoveBlocksIntoContainer(nodesInSwitch, switchContainer);
// Rewrite branches within the loop from oldEntryPoint to newEntryPoint:
foreach (var branch in loopContainer.Descendants.OfType<Branch>()) {
if (branch.TargetBlock == oldEntryPoint) {
branch.TargetBlock = newEntryPoint;
} else if (branch.TargetBlock == exitTargetBlock) {
branch.ReplaceWith(new Leave(loopContainer) { ILRange = branch.ILRange });
foreach (var branch in switchContainer.Descendants.OfType<Branch>()) {
if (branch.TargetBlock == exitTargetBlock) {
branch.ReplaceWith(new Leave(switchContainer) { ILRange = branch.ILRange });
}
}
}

4
ICSharpCode.Decompiler/IL/ControlFlow/StateRangeAnalysis.cs

@ -140,7 +140,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -140,7 +140,6 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
SymbolicValue val = evalContext.Eval(switchInst.Value);
if (val.Type != SymbolicValueType.State)
goto default;
List<LongInterval> allSectionLabels = new List<LongInterval>();
List<LongInterval> exitIntervals = new List<LongInterval>();
foreach (var section in switchInst.Sections) {
// switch (state + Constant)
@ -148,12 +147,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -148,12 +147,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// iff (state + Constant == value)
// iff (state == value - Constant)
var effectiveLabels = section.Labels.AddOffset(unchecked(-val.Constant));
allSectionLabels.AddRange(effectiveLabels.Intervals);
var result = AssignStateRanges(section.Body, stateRange.IntersectWith(effectiveLabels));
exitIntervals.AddRange(result.Intervals);
}
var defaultSectionLabels = stateRange.ExceptWith(new LongSet(allSectionLabels));
exitIntervals.AddRange(AssignStateRanges(switchInst.DefaultBody, defaultSectionLabels).Intervals);
// exitIntervals = union of exits of all sections
return new LongSet(exitIntervals);
case IfInstruction ifInst:

61
ICSharpCode.Decompiler/IL/ControlFlow/SwitchAnalysis.cs

@ -42,8 +42,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -42,8 +42,18 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// </summary>
public readonly List<KeyValuePair<LongSet, ILInstruction>> Sections = new List<KeyValuePair<LongSet, ILInstruction>>();
/// <summary>
/// Used to de-duplicate sections with a branch instruction.
/// Invariant: (Sections[targetBlockToSectionIndex[branch.TargetBlock]].Instruction as Branch).TargetBlock == branch.TargetBlock
/// </summary>
readonly Dictionary<Block, int> targetBlockToSectionIndex = new Dictionary<Block, int>();
/// <summary>
/// Used to de-duplicate sections with a value-less leave instruction.
/// Invariant: (Sections[targetBlockToSectionIndex[leave.TargetContainer]].Instruction as Leave).TargetContainer == leave.TargetContainer
/// </summary>
readonly Dictionary<BlockContainer, int> targetContainerToSectionIndex = new Dictionary<BlockContainer, int>();
/// <summary>
/// Blocks that can be deleted if the tail of the initial block is replaced with a switch instruction.
/// </summary>
@ -61,6 +71,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -61,6 +71,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
switchVar = null;
rootBlock = block;
targetBlockToSectionIndex.Clear();
targetContainerToSectionIndex.Clear();
Sections.Clear();
InnerBlocks.Clear();
ContainsILSwitch = false;
@ -83,25 +94,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -83,25 +94,26 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
/// If false, analyze the whole block.</param>
bool AnalyzeBlock(Block block, LongSet inputValues, bool tailOnly = false)
{
if (block.Instructions.Count == 0) {
// might happen if the block was already marked for deletion in SwitchDetection
return false;
}
if (tailOnly) {
Debug.Assert(block == rootBlock);
if (block.Instructions.Count < 2)
return false;
} else {
Debug.Assert(switchVar != null); // switchVar should always be determined by the top-level call
if (block.IncomingEdgeCount != 1 || block == rootBlock)
return false; // for now, let's only consider if-structures that form a tree
if (block.Instructions.Count != 2)
return false;
if (block.Parent != rootBlock.Parent)
return false; // all blocks should belong to the same container
}
var inst = block.Instructions[block.Instructions.Count - 2];
ILInstruction condition, trueInst;
LongSet trueValues;
if (inst.MatchIfInstruction(out condition, out trueInst)
if (block.Instructions.Count >= 2
&& block.Instructions[block.Instructions.Count - 2].MatchIfInstruction(out var condition, out var trueInst)
&& AnalyzeCondition(condition, out trueValues)
) {
if (!(tailOnly || block.Instructions.Count == 2))
return false;
trueValues = trueValues.IntersectWith(inputValues);
Block trueBlock;
if (trueInst.MatchBranch(out trueBlock) && AnalyzeBlock(trueBlock, trueValues)) {
@ -111,9 +123,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -111,9 +123,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Create switch section for trueInst.
AddSection(trueValues, trueInst);
}
} else if (inst.OpCode == OpCode.SwitchInstruction) {
if (AnalyzeSwitch((SwitchInstruction)inst, inputValues, out trueValues)) {
} else if (block.Instructions.Last() is SwitchInstruction switchInst) {
if (!(tailOnly || block.Instructions.Count == 1))
return false;
if (AnalyzeSwitch(switchInst, inputValues)) {
ContainsILSwitch = true; // OK
return true;
} else { // switch analysis failed (e.g. switchVar mismatch)
return false;
}
@ -134,10 +149,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -134,10 +149,9 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return true;
}
private bool AnalyzeSwitch(SwitchInstruction inst, LongSet inputValues, out LongSet anyMatchValues)
private bool AnalyzeSwitch(SwitchInstruction inst, LongSet inputValues)
{
Debug.Assert(inst.DefaultBody is Nop);
anyMatchValues = LongSet.Empty;
Debug.Assert(!inst.IsLifted);
long offset;
if (MatchSwitchVar(inst.Value)) {
offset = 0;
@ -163,8 +177,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -163,8 +177,11 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
}
foreach (var section in inst.Sections) {
var matchValues = section.Labels.AddOffset(offset).IntersectWith(inputValues);
AddSection(matchValues, section.Body);
anyMatchValues = anyMatchValues.UnionWith(matchValues);
if (matchValues.Count() > 1 && section.Body.MatchBranch(out var targetBlock) && AnalyzeBlock(targetBlock, matchValues)) {
InnerBlocks.Add(targetBlock);
} else {
AddSection(matchValues, section.Body);
}
}
return true;
}
@ -180,10 +197,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -180,10 +197,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (values.IsEmpty) {
return;
}
Block targetBlock;
if (inst.MatchBranch(out targetBlock)) {
int index;
if (targetBlockToSectionIndex.TryGetValue(targetBlock, out index)) {
if (inst.MatchBranch(out Block targetBlock)) {
if (targetBlockToSectionIndex.TryGetValue(targetBlock, out int index)) {
Sections[index] = new KeyValuePair<LongSet, ILInstruction>(
Sections[index].Key.UnionWith(values),
inst
@ -192,6 +207,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -192,6 +207,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
targetBlockToSectionIndex.Add(targetBlock, Sections.Count);
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst));
}
} else if (inst.MatchLeave(out BlockContainer targetContainer)) {
if (targetContainerToSectionIndex.TryGetValue(targetContainer, out int index)) {
Sections[index] = new KeyValuePair<LongSet, ILInstruction>(
Sections[index].Key.UnionWith(values),
inst
);
} else {
targetContainerToSectionIndex.Add(targetContainer, Sections.Count);
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst));
}
} else {
Sections.Add(new KeyValuePair<LongSet, ILInstruction>(values, inst));
}

45
ICSharpCode.Decompiler/IL/ControlFlow/SwitchDetection.cs

@ -60,16 +60,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -60,16 +60,20 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
var sw = new SwitchInstruction(new LdLoc(analysis.SwitchVariable));
foreach (var section in analysis.Sections) {
if (!section.Key.SetEquals(defaultSection.Key)) {
sw.Sections.Add(new SwitchSection
{
Labels = section.Key,
Body = section.Value
});
}
sw.Sections.Add(new SwitchSection {
Labels = section.Key,
Body = section.Value
});
}
if (block.Instructions.Last() is SwitchInstruction) {
// we'll replace the switch
} else {
Debug.Assert(block.Instructions.SecondToLastOrDefault() is IfInstruction);
// Remove branch/leave after if; it's getting moved into a section.
block.Instructions.RemoveAt(block.Instructions.Count - 1);
}
block.Instructions[block.Instructions.Count - 2] = sw;
block.Instructions[block.Instructions.Count - 1] = defaultSection.Value;
block.Instructions[block.Instructions.Count - 1] = sw;
// mark all inner blocks that were converted to the switch statement for deletion
foreach (var innerBlock in analysis.InnerBlocks) {
Debug.Assert(innerBlock.Parent == block.Parent);
@ -77,6 +81,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -77,6 +81,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
innerBlock.Instructions.Clear();
}
blockContainerNeedsCleanup = true;
SortSwitchSections(sw);
} else {
// 2nd pass of SimplifySwitchInstruction (after duplicating return blocks),
// (1st pass was in ControlFlowSimplification)
@ -87,8 +92,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -87,8 +92,8 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
internal static void SimplifySwitchInstruction(Block block)
{
// due to our of of basic blocks at this point,
// switch instructions can only appear as second-to-last insturction
var sw = block.Instructions.SecondToLastOrDefault() as SwitchInstruction;
// switch instructions can only appear as last insturction
var sw = block.Instructions.LastOrDefault() as SwitchInstruction;
if (sw == null)
return;
@ -96,19 +101,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -96,19 +101,13 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// Any switch instructions will only have branch instructions in the sections.
// Combine sections with identical branch target:
block.Instructions.Last().MatchBranch(out Block defaultTarget);
var dict = new Dictionary<Block, SwitchSection>(); // branch target -> switch section
sw.Sections.RemoveAll(
section => {
Block target;
if (section.Body.MatchBranch(out target)) {
SwitchSection primarySection;
if (target == defaultTarget) {
// This section is just an alternative for 'default'.
Debug.Assert(sw.DefaultBody is Nop);
return true; // remove this section
} else if (dict.TryGetValue(target, out primarySection)) {
if (section.Body.MatchBranch(out Block target)) {
if (dict.TryGetValue(target, out SwitchSection primarySection)) {
primarySection.Labels = primarySection.Labels.UnionWith(section.Labels);
primarySection.HasNullLabel |= section.HasNullLabel;
return true; // remove this section
} else {
dict.Add(target, section);
@ -117,6 +116,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -117,6 +116,12 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
return false;
});
AdjustLabels(sw);
SortSwitchSections(sw);
}
static void SortSwitchSections(SwitchInstruction sw)
{
sw.Sections.ReplaceList(sw.Sections.OrderBy(s => (s.Body as Branch)?.TargetILOffset).ThenBy(s => s.Labels.Values.FirstOrDefault()));
}
static void AdjustLabels(SwitchInstruction sw)

2
ICSharpCode.Decompiler/IL/DetectedLoop.cs

@ -76,7 +76,7 @@ namespace ICSharpCode.Decompiler.IL @@ -76,7 +76,7 @@ namespace ICSharpCode.Decompiler.IL
conditions.Add(ifInst.Condition);
i--;
}
if (i == -1) {
if (i == -1 && conditions.Any()) {
return b;
}
}

5
ICSharpCode.Decompiler/IL/ILReader.cs

@ -1264,7 +1264,10 @@ namespace ICSharpCode.Decompiler.IL @@ -1264,7 +1264,10 @@ namespace ICSharpCode.Decompiler.IL
section.Body = new Branch(target);
instr.Sections.Add(section);
}
var defaultSection = new SwitchSection();
defaultSection.Labels = new LongSet(new LongInterval(0, labels.Length)).Invert();
defaultSection.Body = new Nop();
instr.Sections.Add(defaultSection);
return instr;
}

109
ICSharpCode.Decompiler/IL/Instructions.cs

@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL @@ -164,6 +164,8 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty.
/// Also used to convert a string to a reference to the first character.</summary>
ArrayToPointer,
/// <summary>Maps a string value to an integer. This is used in switch(string).</summary>
StringToInt,
/// <summary>Push a typed reference of type class onto the stack.</summary>
MakeRefAny,
/// <summary>Push the type token stored in a typed reference.</summary>
@ -1018,6 +1020,15 @@ namespace ICSharpCode.Decompiler.IL @@ -1018,6 +1020,15 @@ namespace ICSharpCode.Decompiler.IL
public sealed partial class Branch : SimpleInstruction
{
public override StackType ResultType { get { return StackType.Void; } }
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.EndPointUnreachable | InstructionFlags.MayBranch;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.EndPointUnreachable | InstructionFlags.MayBranch;
}
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitBranch(this);
@ -1323,7 +1334,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1323,7 +1334,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as SwitchInstruction;
return o != null && Value.PerformMatch(o.Value, ref match) && DefaultBody.PerformMatch(o.DefaultBody, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match);
return o != null && IsLifted == o.IsLifted && Value.PerformMatch(o.Value, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match);
}
}
}
@ -1395,7 +1406,7 @@ namespace ICSharpCode.Decompiler.IL @@ -1395,7 +1406,7 @@ namespace ICSharpCode.Decompiler.IL
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as SwitchSection;
return o != null && this.body.PerformMatch(o.body, ref match) && this.Labels.Intervals.SequenceEqual(o.Labels.Intervals);
return o != null && this.body.PerformMatch(o.body, ref match) && this.Labels.SetEquals(o.Labels) && this.HasNullLabel == o.HasNullLabel;
}
}
}
@ -4332,6 +4343,87 @@ namespace ICSharpCode.Decompiler.IL @@ -4332,6 +4343,87 @@ namespace ICSharpCode.Decompiler.IL
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Maps a string value to an integer. This is used in switch(string).</summary>
public sealed partial class StringToInt : ILInstruction
{
public static readonly SlotInfo ArgumentSlot = new SlotInfo("Argument", canInlineInto: true);
ILInstruction argument;
public ILInstruction Argument {
get { return this.argument; }
set {
ValidateChild(value);
SetChildInstruction(ref this.argument, value, 0);
}
}
protected sealed override int GetChildCount()
{
return 1;
}
protected sealed override ILInstruction GetChild(int index)
{
switch (index) {
case 0:
return this.argument;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override void SetChild(int index, ILInstruction value)
{
switch (index) {
case 0:
this.Argument = value;
break;
default:
throw new IndexOutOfRangeException();
}
}
protected sealed override SlotInfo GetChildSlot(int index)
{
switch (index) {
case 0:
return ArgumentSlot;
default:
throw new IndexOutOfRangeException();
}
}
public sealed override ILInstruction Clone()
{
var clone = (StringToInt)ShallowClone();
clone.Argument = this.argument.Clone();
return clone;
}
public override StackType ResultType { get { return StackType.I4; } }
protected override InstructionFlags ComputeFlags()
{
return argument.Flags;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.None;
}
}
public override void AcceptVisitor(ILVisitor visitor)
{
visitor.VisitStringToInt(this);
}
public override T AcceptVisitor<T>(ILVisitor<T> visitor)
{
return visitor.VisitStringToInt(this);
}
public override T AcceptVisitor<C, T>(ILVisitor<C, T> visitor, C context)
{
return visitor.VisitStringToInt(this, context);
}
protected internal override bool PerformMatch(ILInstruction other, ref Patterns.Match match)
{
var o = other as StringToInt;
return o != null && this.argument.PerformMatch(o.argument, ref match);
}
}
}
namespace ICSharpCode.Decompiler.IL
{
/// <summary>Push a typed reference of type class onto the stack.</summary>
public sealed partial class MakeRefAny : UnaryInstruction
@ -4956,6 +5048,10 @@ namespace ICSharpCode.Decompiler.IL @@ -4956,6 +5048,10 @@ namespace ICSharpCode.Decompiler.IL
{
Default(inst);
}
protected internal virtual void VisitStringToInt(StringToInt inst)
{
Default(inst);
}
protected internal virtual void VisitMakeRefAny(MakeRefAny inst)
{
Default(inst);
@ -5254,6 +5350,10 @@ namespace ICSharpCode.Decompiler.IL @@ -5254,6 +5350,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst);
}
protected internal virtual T VisitStringToInt(StringToInt inst)
{
return Default(inst);
}
protected internal virtual T VisitMakeRefAny(MakeRefAny inst)
{
return Default(inst);
@ -5552,6 +5652,10 @@ namespace ICSharpCode.Decompiler.IL @@ -5552,6 +5652,10 @@ namespace ICSharpCode.Decompiler.IL
{
return Default(inst, context);
}
protected internal virtual T VisitStringToInt(StringToInt inst, C context)
{
return Default(inst, context);
}
protected internal virtual T VisitMakeRefAny(MakeRefAny inst, C context)
{
return Default(inst, context);
@ -5644,6 +5748,7 @@ namespace ICSharpCode.Decompiler.IL @@ -5644,6 +5748,7 @@ namespace ICSharpCode.Decompiler.IL
"ldlen",
"ldelema",
"array.to.pointer",
"string.to.int",
"mkrefany",
"refanytype",
"refanyval",

8
ICSharpCode.Decompiler/IL/Instructions.tt

@ -73,7 +73,7 @@ @@ -73,7 +73,7 @@
new OpCode("bit.not", "Bitwise NOT", Unary, CustomConstructor, MatchCondition("IsLifted == o.IsLifted && UnderlyingResultType == o.UnderlyingResultType")),
new OpCode("arglist", "Retrieves the RuntimeArgumentHandle.", NoArguments, ResultType("O")),
new OpCode("br", "Unconditional branch. <c>goto target;</c>",
CustomClassName("Branch"), NoArguments, CustomConstructor, UnconditionalBranch, MayBranch, CustomComputeFlags,
CustomClassName("Branch"), NoArguments, CustomConstructor, UnconditionalBranch, MayBranch,
MatchCondition("this.TargetBlock == o.TargetBlock")),
new OpCode("leave", "Unconditional branch to end of block container. Return is represented using IsLeavingFunction and an (optional) return value. The block container evaluates to the value produced by the argument of the leave instruction.",
CustomConstructor, CustomArguments("value"), UnconditionalBranch, MayBranch, CustomWriteTo, CustomComputeFlags,
@ -93,11 +93,11 @@ @@ -93,11 +93,11 @@
}), CustomConstructor, CustomComputeFlags, CustomWriteTo),
new OpCode("switch", "Switch statement",
CustomClassName("SwitchInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"),
MatchCondition("Value.PerformMatch(o.Value, ref match) && DefaultBody.PerformMatch(o.DefaultBody, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")),
MatchCondition("IsLifted == o.IsLifted && Value.PerformMatch(o.Value, ref match) && Patterns.ListMatch.DoMatch(this.Sections, o.Sections, ref match)")),
new OpCode("switch.section", "Switch section within a switch statement",
CustomClassName("SwitchSection"), CustomChildren(new [] { new ChildInfo("body") }),
CustomConstructor, CustomComputeFlags, CustomWriteTo, ResultType("Void"),
MatchCondition("this.Labels.Intervals.SequenceEqual(o.Labels.Intervals)")),
MatchCondition("this.Labels.SetEquals(o.Labels) && this.HasNullLabel == o.HasNullLabel")),
new OpCode("try.catch", "Try-catch statement.",
BaseClass("TryInstruction"), CustomConstructor, CustomComputeFlags, CustomWriteTo,
MatchCondition("TryBlock.PerformMatch(o.TryBlock, ref match)"),
@ -234,6 +234,8 @@ @@ -234,6 +234,8 @@
new OpCode("array.to.pointer", "Converts an array pointer (O) to a reference to the first element, or to a null reference if the array is null or empty." + Environment.NewLine
+ "Also used to convert a string to a reference to the first character.",
CustomArguments("array"), ResultType("Ref")),
new OpCode("string.to.int", "Maps a string value to an integer. This is used in switch(string).",
CustomArguments("argument"), CustomConstructor, CustomWriteTo, ResultType("I4")),
new OpCode("mkrefany", "Push a typed reference of type class onto the stack.",
CustomClassName("MakeRefAny"), Unary, HasTypeOperand, ResultType("O")),

3
ICSharpCode.Decompiler/IL/Instructions/Block.cs

@ -108,6 +108,9 @@ namespace ICSharpCode.Decompiler.IL @@ -108,6 +108,9 @@ namespace ICSharpCode.Decompiler.IL
// only the last instruction may have an unreachable endpoint
Debug.Assert(!Instructions[i].HasFlag(InstructionFlags.EndPointUnreachable));
}
if (this.Type == BlockType.ControlFlow) {
Debug.Assert(finalInstruction.OpCode == OpCode.Nop);
}
}
public override StackType ResultType {

12
ICSharpCode.Decompiler/IL/Instructions/BlockContainer.cs

@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.IL @@ -154,7 +154,7 @@ namespace ICSharpCode.Decompiler.IL
Debug.Assert(EntryPoint == Blocks[0]);
Debug.Assert(!IsConnected || EntryPoint.IncomingEdgeCount >= 1);
Debug.Assert(Blocks.All(b => b.HasFlag(InstructionFlags.EndPointUnreachable)));
Debug.Assert(Blocks.All(b => b.FinalInstruction.OpCode == OpCode.Nop));
Debug.Assert(Blocks.All(b => b.Type == BlockType.ControlFlow)); // this also implies that the blocks don't use FinalInstruction
}
protected override InstructionFlags ComputeFlags()
@ -226,5 +226,15 @@ namespace ICSharpCode.Decompiler.IL @@ -226,5 +226,15 @@ namespace ICSharpCode.Decompiler.IL
}
return null;
}
public static BlockContainer FindClosestSwitchContainer(ILInstruction inst)
{
while (inst != null) {
if (inst is BlockContainer bc && bc.entryPoint.Instructions.FirstOrDefault() is SwitchInstruction)
return bc;
inst = inst.Parent;
}
return null;
}
}
}

15
ICSharpCode.Decompiler/IL/Instructions/Branch.cs

@ -39,23 +39,10 @@ namespace ICSharpCode.Decompiler.IL @@ -39,23 +39,10 @@ namespace ICSharpCode.Decompiler.IL
public Branch(Block targetBlock) : base(OpCode.Branch)
{
if (targetBlock == null)
throw new ArgumentNullException(nameof(targetBlock));
this.targetBlock = targetBlock;
this.targetBlock = targetBlock ?? throw new ArgumentNullException(nameof(targetBlock));
this.targetILOffset = targetBlock.ILRange.Start;
}
protected override InstructionFlags ComputeFlags()
{
return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
public override InstructionFlags DirectFlags {
get {
return InstructionFlags.MayBranch | InstructionFlags.EndPointUnreachable;
}
}
public int TargetILOffset {
get { return targetBlock != null ? targetBlock.ILRange.Start : targetILOffset; }
}

32
ICSharpCode.Decompiler/IL/Instructions/LockInstruction.cs

@ -38,36 +38,4 @@ namespace ICSharpCode.Decompiler.IL @@ -38,36 +38,4 @@ namespace ICSharpCode.Decompiler.IL
output.Write("}");
}
}
/// <summary>
/// IL using instruction.
/// Equivalent to:
/// <code>
/// stloc v(resourceExpression)
/// try {
/// body
/// } finally {
/// v?.Dispose();
/// }
/// </code>
/// </summary>
/// <remarks>
/// The value of v is undefined after the end of the body block.
/// </remarks>
partial class UsingInstruction
{
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("using (");
Variable.WriteTo(output);
output.Write(" = ");
ResourceExpression.WriteTo(output, options);
output.WriteLine(") {");
output.Indent();
Body.WriteTo(output, options);
output.Unindent();
output.WriteLine();
output.Write("}");
}
}
}

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

@ -27,6 +27,11 @@ namespace ICSharpCode.Decompiler.IL @@ -27,6 +27,11 @@ namespace ICSharpCode.Decompiler.IL
return OpCode == OpCode.LdcI4 && ((LdcI4)this).Value == val;
}
public bool MatchLdcF(double value)
{
return MatchLdcF(out var v) && v == value;
}
/// <summary>
/// Matches either LdcI4 or LdcI8.
/// </summary>
@ -321,6 +326,27 @@ namespace ICSharpCode.Decompiler.IL @@ -321,6 +326,27 @@ namespace ICSharpCode.Decompiler.IL
}
}
/// <summary>
/// Matches 'comp(arg == ldnull)'
/// </summary>
public bool MatchCompEqualsNull(out ILInstruction arg)
{
if (!MatchCompEquals(out var left, out var right)) {
arg = null;
return false;
}
if (right.MatchLdNull()) {
arg = left;
return true;
} else if (left.MatchLdNull()) {
arg = right;
return true;
} else {
arg = null;
return false;
}
}
/// <summary>
/// Matches comp(left != right) or logic.not(comp(left == right)).
/// </summary>

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

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
// 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.
namespace ICSharpCode.Decompiler.IL
{
partial class StringToInt
{
public string[] Map { get; }
public StringToInt(ILInstruction argument, string[] map)
: base(OpCode.StringToInt)
{
this.Argument = argument;
this.Map = map;
}
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("string.to.int (");
Argument.WriteTo(output, options);
output.Write(", { ");
for (int i = 0; i < Map.Length; i++) {
if (i > 0) output.Write(", ");
output.Write($"[{i}] = \"{Map[i]}\"");
}
output.Write(" })");
}
}
}

76
ICSharpCode.Decompiler/IL/Instructions/SwitchInstruction.cs

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.Util;
@ -32,15 +33,19 @@ namespace ICSharpCode.Decompiler.IL @@ -32,15 +33,19 @@ namespace ICSharpCode.Decompiler.IL
partial class SwitchInstruction
{
public static readonly SlotInfo ValueSlot = new SlotInfo("Value", canInlineInto: true);
public static readonly SlotInfo DefaultBodySlot = new SlotInfo("DefaultBody");
public static readonly SlotInfo SectionSlot = new SlotInfo("Section", isCollection: true);
/// <summary>
/// If the switch instruction is lifted, the value instruction produces a value of type <c>Nullable{T}</c> for some
/// integral type T. The section with <c>SwitchSection.HasNullLabel</c> is called if the value is null.
/// </summary>
public bool IsLifted;
public SwitchInstruction(ILInstruction value)
: base(OpCode.SwitchInstruction)
{
this.Value = value;
this.DefaultBody = new Nop();
this.Sections = new InstructionCollection<SwitchSection>(this, 2);
this.Sections = new InstructionCollection<SwitchSection>(this, 1);
}
ILInstruction value;
@ -52,21 +57,11 @@ namespace ICSharpCode.Decompiler.IL @@ -52,21 +57,11 @@ namespace ICSharpCode.Decompiler.IL
}
}
ILInstruction defaultBody;
public ILInstruction DefaultBody {
get { return this.defaultBody; }
set {
ValidateChild(value);
SetChildInstruction(ref this.defaultBody, value, 1);
}
}
public readonly InstructionCollection<SwitchSection> Sections;
protected override InstructionFlags ComputeFlags()
{
var sectionFlags = defaultBody.Flags;
var sectionFlags = InstructionFlags.EndPointUnreachable; // neutral element for CombineBranches()
foreach (var section in Sections) {
sectionFlags = SemanticHelper.CombineBranches(sectionFlags, section.Flags);
}
@ -81,15 +76,15 @@ namespace ICSharpCode.Decompiler.IL @@ -81,15 +76,15 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("switch (");
output.Write("switch");
if (IsLifted)
output.Write(".lifted");
output.Write(" (");
value.WriteTo(output, options);
output.Write(") ");
output.MarkFoldStart("{...}");
output.WriteLine("{");
output.Indent();
output.Write("default: ");
defaultBody.WriteTo(output, options);
output.WriteLine();
foreach (var section in this.Sections) {
section.WriteTo(output, options);
output.WriteLine();
@ -101,34 +96,28 @@ namespace ICSharpCode.Decompiler.IL @@ -101,34 +96,28 @@ namespace ICSharpCode.Decompiler.IL
protected override int GetChildCount()
{
return 2 + Sections.Count;
return 1 + Sections.Count;
}
protected override ILInstruction GetChild(int index)
{
if (index == 0)
return value;
else if (index == 1)
return defaultBody;
return Sections[index - 2];
return Sections[index - 1];
}
protected override void SetChild(int index, ILInstruction value)
{
if (index == 0)
Value = value;
else if (index == 1)
DefaultBody = value;
else
Sections[index - 2] = (SwitchSection)value;
Sections[index - 1] = (SwitchSection)value;
}
protected override SlotInfo GetChildSlot(int index)
{
if (index == 0)
return ValueSlot;
else if (index == 1)
return DefaultBodySlot;
return SectionSlot;
}
@ -137,7 +126,6 @@ namespace ICSharpCode.Decompiler.IL @@ -137,7 +126,6 @@ namespace ICSharpCode.Decompiler.IL
var clone = new SwitchInstruction(value.Clone());
clone.ILRange = this.ILRange;
clone.Value = value.Clone();
this.DefaultBody = defaultBody.Clone();
clone.Sections.AddRange(this.Sections.Select(h => (SwitchSection)h.Clone()));
return clone;
}
@ -145,12 +133,19 @@ namespace ICSharpCode.Decompiler.IL @@ -145,12 +133,19 @@ namespace ICSharpCode.Decompiler.IL
internal override void CheckInvariant(ILPhase phase)
{
base.CheckInvariant(phase);
bool expectNullSection = this.IsLifted;
LongSet sets = LongSet.Empty;
foreach (var section in Sections) {
Debug.Assert(!section.Labels.IsEmpty);
if (section.HasNullLabel) {
Debug.Assert(expectNullSection, "Duplicate 'case null' or 'case null' in non-lifted switch.");
expectNullSection = false;
}
Debug.Assert(!section.Labels.IsEmpty || section.HasNullLabel);
Debug.Assert(!section.Labels.Overlaps(sets));
sets = sets.UnionWith(section.Labels);
}
Debug.Assert(sets.SetEquals(LongSet.Universe), "switch does not handle all possible cases");
Debug.Assert(!expectNullSection, "Lifted switch is missing 'case null'");
}
}
@ -162,6 +157,14 @@ namespace ICSharpCode.Decompiler.IL @@ -162,6 +157,14 @@ namespace ICSharpCode.Decompiler.IL
this.Labels = LongSet.Empty;
}
/// <summary>
/// If true, serves as 'case null' in a lifted switch.
/// </summary>
public bool HasNullLabel { get; set; }
/// <summary>
/// The set of labels that cause execution to jump to this switch section.
/// </summary>
public LongSet Labels { get; set; }
protected override InstructionFlags ComputeFlags()
@ -177,8 +180,17 @@ namespace ICSharpCode.Decompiler.IL @@ -177,8 +180,17 @@ namespace ICSharpCode.Decompiler.IL
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("case ");
output.Write(Labels.ToString());
output.WriteDefinition("case", this, isLocal: true);
output.Write(' ');
if (HasNullLabel) {
output.Write("null");
if (!Labels.IsEmpty) {
output.Write(", ");
output.Write(Labels.ToString());
}
} else {
output.Write(Labels.ToString());
}
output.Write(": ");
body.WriteTo(output, options);

53
ICSharpCode.Decompiler/IL/Instructions/UsingInstruction.cs

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
// 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.
namespace ICSharpCode.Decompiler.IL
{
/// <summary>
/// IL using instruction.
/// Equivalent to:
/// <code>
/// stloc v(resourceExpression)
/// try {
/// body
/// } finally {
/// v?.Dispose();
/// }
/// </code>
/// </summary>
/// <remarks>
/// The value of v is undefined after the end of the body block.
/// </remarks>
partial class UsingInstruction
{
public override void WriteTo(ITextOutput output, ILAstWritingOptions options)
{
output.Write("using (");
Variable.WriteTo(output);
output.Write(" = ");
ResourceExpression.WriteTo(output, options);
output.WriteLine(") {");
output.Indent();
Body.WriteTo(output, options);
output.Unindent();
output.WriteLine();
output.Write("}");
}
}
}

8
ICSharpCode.Decompiler/IL/Transforms/DelegateConstruction.cs

@ -45,9 +45,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -45,9 +45,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (f != null) {
call.Arguments[0].ReplaceWith(new Nop());
call.Arguments[1].ReplaceWith(f);
if (target is IInstructionWithVariableOperand && !target.MatchLdThis())
targetsToReplace.Add((IInstructionWithVariableOperand)target);
}
if (target is IInstructionWithVariableOperand && !target.MatchLdThis())
targetsToReplace.Add((IInstructionWithVariableOperand)target);
}
}
if (block.Instructions[i].MatchStLoc(out ILVariable targetVariable, out ILInstruction value)) {
var newObj = value as NewObj;
@ -314,7 +314,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -314,7 +314,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
base.VisitCompoundAssignmentInstruction(inst);
if (inst.Target.MatchLdLoc(out var v)) {
inst.ReplaceWith(new StLoc(v, new BinaryNumericInstruction(inst.Operator, inst.Target, inst.Value, inst.CheckForOverflow, inst.Sign)));
}
}
}
}
#endregion

20
ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs

@ -111,16 +111,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -111,16 +111,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
&& inst.Sign == Sign.Unsigned
&& (inst.Kind == ComparisonKind.GreaterThan || inst.Kind == ComparisonKind.LessThanOrEqual))
{
ILInstruction array;
if (inst.Left.MatchLdLen(StackType.I, out array)) {
// comp.unsigned(ldlen array > conv i4->i(ldc.i4 0))
// => comp(ldlen.i4 array > ldc.i4 0)
// This is a special case where the C# compiler doesn't generate conv.i4 after ldlen.
context.Step("comp(ldlen.i4 array > ldc.i4 0)", inst);
inst.InputType = StackType.I4;
inst.Left.ReplaceWith(new LdLen(StackType.I4, array) { ILRange = inst.Left.ILRange });
inst.Right = rightWithoutConv;
}
if (inst.Kind == ComparisonKind.GreaterThan) {
context.Step("comp.unsigned(left > ldc.i4 0) => comp(left != ldc.i4 0)", inst);
inst.Kind = ComparisonKind.Inequality;
@ -132,6 +122,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -132,6 +122,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
VisitComp(inst);
return;
}
} else if (rightWithoutConv.MatchLdcI4(0) && inst.Kind.IsEqualityOrInequality()) {
if (inst.Left.MatchLdLen(StackType.I, out ILInstruction array)) {
// comp.unsigned(ldlen array == conv i4->i(ldc.i4 0))
// => comp(ldlen.i4 array == ldc.i4 0)
// This is a special case where the C# compiler doesn't generate conv.i4 after ldlen.
context.Step("comp(ldlen.i4 array == ldc.i4 0)", inst);
inst.InputType = StackType.I4;
inst.Left.ReplaceWith(new LdLen(StackType.I4, array) { ILRange = inst.Left.ILRange });
inst.Right = rightWithoutConv;
}
}
if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out arg, out var type) && type.Kind == TypeKind.TypeParameter) {

12
ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs

@ -293,6 +293,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -293,6 +293,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
parent = parent.Parent;
}
return parent == next;
case OpCode.BlockContainer:
if (((BlockContainer)next).EntryPoint.Instructions[0] is SwitchInstruction switchInst) {
next = switchInst;
goto case OpCode.SwitchInstruction;
} else {
return false;
}
case OpCode.SwitchInstruction:
return parent == next || (parent.MatchBinaryNumericInstruction(BinaryNumericOperator.Sub) && parent.Parent == next);
default:
@ -334,6 +341,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -334,6 +341,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
default:
return false;
}
} else if (expr is BlockContainer container && container.EntryPoint.IncomingEdgeCount == 1) {
// Possibly a switch-container, allow inlining into the switch instruction:
return FindLoadInNext(container.EntryPoint.Instructions[0], v, expressionBeingMoved, out loadInst) ?? false;
// If FindLoadInNext() returns null, we still can't continue searching
// because we can't inline over the remainder of the blockcontainer.
}
foreach (var child in expr.Children) {
if (!child.SlotInfo.CanInlineInto)

95
ICSharpCode.Decompiler/IL/Transforms/InlineReturnTransform.cs

@ -0,0 +1,95 @@ @@ -0,0 +1,95 @@
// 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;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// This transform duplicates return blocks if they return a local variable that was assigned right before the return.
/// </summary>
class InlineReturnTransform : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
var instructionsToModify = new List<(BlockContainer, Block, Branch)>();
// Process all leave instructions in a leave-block, that is a block consisting solely of a leave instruction.
foreach (var leave in function.Descendants.OfType<Leave>()) {
if (!(leave.Parent is Block leaveBlock && leaveBlock.Instructions.Count == 1))
continue;
// Skip, if the leave instruction has no value or the value is not a load of a local variable.
if (!leave.Value.MatchLdLoc(out var returnVar) || returnVar.Kind != VariableKind.Local)
continue;
// If all instructions can be modified, add item to the global list.
if (CanModifyInstructions(returnVar, leaveBlock, out var list))
instructionsToModify.AddRange(list);
}
foreach (var (container, b, br) in instructionsToModify) {
Block block = b;
// if there is only one branch to this return block, move it to the matching container.
// otherwise duplicate the return block.
if (block.IncomingEdgeCount == 1) {
block.Remove();
} else {
block = (Block)block.Clone();
}
container.Blocks.Add(block);
// adjust the target of the branch to the newly created block.
br.TargetBlock = block;
}
}
/// <summary>
/// Determines a list of all store instructions that write to a given <paramref name="returnVar"/>.
/// Returns false if any of these instructions does not meet the following criteria:
/// - must be a stloc
/// - must be a direct child of a block
/// - must be the penultimate instruction
/// - must be followed by a branch instruction to <paramref name="leaveBlock"/>
/// - must have a BlockContainer as ancestor.
/// Returns true, if all instructions meet these criteria, and <paramref name="instructionsToModify"/> contains a list of 3-tuples.
/// Each tuple consists of the target block container, the leave block, and the branch instruction that should be modified.
/// </summary>
static bool CanModifyInstructions(ILVariable returnVar, Block leaveBlock, out List<(BlockContainer, Block, Branch)> instructionsToModify)
{
instructionsToModify = new List<(BlockContainer, Block, Branch)>();
foreach (var inst in returnVar.StoreInstructions) {
if (!(inst is StLoc store))
return false;
if (!(store.Parent is Block storeBlock))
return false;
if (store.ChildIndex + 2 != storeBlock.Instructions.Count)
return false;
if (!(storeBlock.Instructions[store.ChildIndex + 1] is Branch br))
return false;
if (br.TargetBlock != leaveBlock)
return false;
var targetBlockContainer = BlockContainer.FindClosestContainer(store);
if (targetBlockContainer == null)
return false;
instructionsToModify.Add((targetBlockContainer, leaveBlock, br));
}
return true;
}
}
}

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

@ -102,7 +102,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -102,7 +102,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
#region AnalyzeCondition
bool AnalyzeCondition(ILInstruction condition)
{
if (MatchHasValueCall(condition, out var v)) {
if (MatchHasValueCall(condition, out ILVariable v)) {
if (nullableVars == null)
nullableVars = new List<ILVariable>();
nullableVars.Add(v);
@ -387,9 +387,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -387,9 +387,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// Comparing two nullables: HasValue comparison must be the same operator as the Value comparison
if ((hasValueTestNegated ? hasValueComp.Kind.Negate() : hasValueComp.Kind) != newComparisonKind)
return null;
if (!MatchHasValueCall(hasValueComp.Left, out var leftVar))
if (!MatchHasValueCall(hasValueComp.Left, out ILVariable leftVar))
return null;
if (!MatchHasValueCall(hasValueComp.Right, out var rightVar))
if (!MatchHasValueCall(hasValueComp.Right, out ILVariable rightVar))
return null;
nullableVars = new List<ILVariable> { leftVar };
var (left, leftBits) = DoLift(valueComp.Left);
@ -401,7 +401,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -401,7 +401,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
context.Step("NullableLiftingTransform: C# (in)equality comparison", valueComp.Instruction);
return valueComp.MakeLifted(newComparisonKind, left, right);
}
} else if (newComparisonKind == ComparisonKind.Equality && !hasValueTestNegated && MatchHasValueCall(hasValueTest, out var v)) {
} else if (newComparisonKind == ComparisonKind.Equality && !hasValueTestNegated && MatchHasValueCall(hasValueTest, out ILVariable v)) {
// Comparing nullable with non-nullable -> we can fall back to the normal comparison code.
nullableVars = new List<ILVariable> { v };
return LiftCSharpComparison(valueComp, newComparisonKind);
@ -461,13 +461,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -461,13 +461,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// else
// ldc.i4 0
if (!MatchHasValueCall(hasValueComp.Left, out var nullable1))
if (!MatchHasValueCall(hasValueComp.Left, out ILVariable nullable1))
return null;
if (!MatchHasValueCall(hasValueComp.Right, out var nullable2))
if (!MatchHasValueCall(hasValueComp.Right, out ILVariable nullable2))
return null;
if (!nestedIfInst.MatchIfInstructionPositiveCondition(out var condition, out var trueInst, out var falseInst))
return null;
if (!MatchHasValueCall(condition, out var nullable))
if (!MatchHasValueCall(condition, out ILVariable nullable))
return null;
if (nullable != nullable1 && nullable != nullable2)
return null;
@ -516,7 +516,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -516,7 +516,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (trueInst.MatchIfInstructionPositiveCondition(out var nestedCondition, out var nestedTrue, out var nestedFalse)) {
// Sometimes Roslyn generates pointless conditions like:
// if (nullable.HasValue && (!nullable.HasValue || nullable.GetValueOrDefault() == b))
if (MatchHasValueCall(nestedCondition, out var v) && nullableVars.Contains(v)) {
if (MatchHasValueCall(nestedCondition, out ILVariable v) && nullableVars.Contains(v)) {
trueInst = nestedTrue;
}
}
@ -758,11 +758,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -758,11 +758,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
#region Match...Call
/// <summary>
/// Matches 'call get_HasValue(ldloca v)'
/// Matches 'call get_HasValue(arg)'
/// </summary>
internal static bool MatchHasValueCall(ILInstruction inst, out ILVariable v)
internal static bool MatchHasValueCall(ILInstruction inst, out ILInstruction arg)
{
v = null;
arg = null;
if (!(inst is Call call))
return false;
if (call.Arguments.Count != 1)
@ -771,7 +771,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -771,7 +771,20 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (call.Method.DeclaringTypeDefinition?.KnownTypeCode != KnownTypeCode.NullableOfT)
return false;
return call.Arguments[0].MatchLdLoca(out v);
arg = call.Arguments[0];
return true;
}
/// <summary>
/// Matches 'call get_HasValue(ldloca v)'
/// </summary>
internal static bool MatchHasValueCall(ILInstruction inst, out ILVariable v)
{
if (MatchHasValueCall(inst, out ILInstruction arg)) {
return arg.MatchLdLoca(out v);
}
v = null;
return false;
}
/// <summary>
@ -779,7 +792,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -779,7 +792,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary>
internal static bool MatchHasValueCall(ILInstruction inst, ILVariable v)
{
return MatchHasValueCall(inst, out var v2) && v == v2;
return MatchHasValueCall(inst, out ILVariable v2) && v == v2;
}
/// <summary>
@ -811,7 +824,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -811,7 +824,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// <summary>
/// Matches 'call Nullable{T}.GetValueOrDefault(arg)'
/// </summary>
static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction arg)
internal static bool MatchGetValueOrDefault(ILInstruction inst, out ILInstruction arg)
{
arg = null;
if (!(inst is Call call))

165
ICSharpCode.Decompiler/IL/Transforms/SwitchOnNullableTransform.cs

@ -0,0 +1,165 @@ @@ -0,0 +1,165 @@
// 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 ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Detects switch-on-nullable patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction.
/// </summary>
class SwitchOnNullableTransform : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.LiftNullables)
return;
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
foreach (var block in function.Descendants.OfType<Block>()) {
bool changed = false;
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
SwitchInstruction newSwitch;
if (MatchSwitchOnNullable(block.Instructions, i, out newSwitch)) {
block.Instructions[i + 1].ReplaceWith(newSwitch);
block.Instructions.RemoveRange(i - 2, 3);
i -= 2;
changed = true;
continue;
}
if (MatchRoslynSwitchOnNullable(block.Instructions, i, out newSwitch)) {
block.Instructions[i - 1].ReplaceWith(newSwitch);
block.Instructions.RemoveRange(i, 2);
i--;
changed = true;
continue;
}
}
if (!changed) continue;
SwitchDetection.SimplifySwitchInstruction(block);
if (block.Parent is BlockContainer container)
changedContainers.Add(container);
}
foreach (var container in changedContainers)
container.SortBlocks(deleteUnreachableBlocks: true);
}
/// <summary>
/// Matches legacy C# switch on nullable.
/// </summary>
bool MatchSwitchOnNullable(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
{
newSwitch = null;
// match first block:
// stloc tmp(ldloca switchValueVar)
// stloc switchVariable(call GetValueOrDefault(ldloc tmp))
// if (logic.not(call get_HasValue(ldloc tmp))) br nullCaseBlock
// br switchBlock
if (i < 2) return false;
if (!instructions[i - 2].MatchStLoc(out var tmp, out var ldloca) ||
!instructions[i - 1].MatchStLoc(out var switchVariable, out var getValueOrDefault) ||
!instructions[i].MatchIfInstruction(out var condition, out var trueInst))
return false;
if (!tmp.IsSingleDefinition || tmp.LoadCount != 2)
return false;
if (!switchVariable.IsSingleDefinition || switchVariable.LoadCount != 1)
return false;
if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
return false;
if (!ldloca.MatchLdLoca(out var switchValueVar))
return false;
if (!condition.MatchLogicNot(out var getHasValue))
return false;
if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, out ILInstruction getValueOrDefaultArg))
return false;
if (!NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILInstruction getHasValueArg))
return false;
if (!(getHasValueArg.MatchLdLoc(tmp) && getValueOrDefaultArg.MatchLdLoc(tmp)))
return false;
// match second block: switchBlock
// switch (ldloc switchVariable) {
// case [0..1): br caseBlock1
// ... more cases ...
// case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
// }
if (switchBlock.Instructions.Count != 1 || switchBlock.IncomingEdgeCount != 1)
return false;
if (!(switchBlock.Instructions[0] is SwitchInstruction switchInst))
return false;
newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, new LdLoc(switchValueVar));
return true;
}
static SwitchInstruction BuildLiftedSwitch(Block nullCaseBlock, SwitchInstruction switchInst, ILInstruction switchValue)
{
SwitchInstruction newSwitch = new SwitchInstruction(switchValue);
newSwitch.IsLifted = true;
newSwitch.Sections.AddRange(switchInst.Sections);
newSwitch.Sections.Add(new SwitchSection { Body = new Branch(nullCaseBlock), HasNullLabel = true });
return newSwitch;
}
/// <summary>
/// Matches Roslyn C# switch on nullable.
/// </summary>
bool MatchRoslynSwitchOnNullable(InstructionCollection<ILInstruction> instructions, int i, out SwitchInstruction newSwitch)
{
newSwitch = null;
// match first block:
// stloc tmp(ldloc switchValueVar)
// if (logic.not(call get_HasValue(ldloca tmp))) br nullCaseBlock
// br switchBlock
if (i < 1) return false;
if (!instructions[i - 1].MatchStLoc(out var tmp, out var switchValue) ||
!instructions[i].MatchIfInstruction(out var condition, out var trueInst))
return false;
if (tmp.StoreCount != 1 || tmp.AddressCount != 2 || tmp.LoadCount != 0)
return false;
if (!instructions[i + 1].MatchBranch(out var switchBlock) || !trueInst.MatchBranch(out var nullCaseBlock))
return false;
if (!condition.MatchLogicNot(out var getHasValue) || !NullableLiftingTransform.MatchHasValueCall(getHasValue, out ILVariable target1) || target1 != tmp)
return false;
// match second block: switchBlock
// stloc switchVar(call GetValueOrDefault(ldloca tmp))
// switch (ldloc switchVar) {
// case [0..1): br caseBlock1
// ... more cases ...
// case [long.MinValue..0),[1..5),[6..10),[11..long.MaxValue]: br defaultBlock
// }
if (switchBlock.Instructions.Count != 2 || switchBlock.IncomingEdgeCount != 1)
return false;
if (!switchBlock.Instructions[0].MatchStLoc(out var switchVar, out var getValueOrDefault))
return false;
if (!switchVar.IsSingleDefinition || switchVar.LoadCount != 1)
return false;
if (!NullableLiftingTransform.MatchGetValueOrDefault(getValueOrDefault, tmp))
return false;
if (!(switchBlock.Instructions[1] is SwitchInstruction switchInst))
return false;
newSwitch = BuildLiftedSwitch(nullCaseBlock, switchInst, switchValue);
return true;
}
}
}

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

@ -0,0 +1,614 @@ @@ -0,0 +1,614 @@
// 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 ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Detects switch-on-string patterns employed by the C# compiler and transforms them to an ILAst-switch-instruction.
/// </summary>
class SwitchOnStringTransform : IILTransform
{
public void Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.SwitchStatementOnString)
return;
HashSet<BlockContainer> changedContainers = new HashSet<BlockContainer>();
foreach (var block in function.Descendants.OfType<Block>()) {
bool changed = false;
for (int i = block.Instructions.Count - 1; i >= 0; i--) {
if (SimplifyCascadingIfStatements(block.Instructions, ref i)) {
changed = true;
continue;
}
if (MatchLegacySwitchOnStringWithHashtable(block.Instructions, ref i)) {
changed = true;
continue;
}
if (MatchLegacySwitchOnStringWithDict(block.Instructions, ref i)) {
changed = true;
continue;
}
if (MatchRoslynSwitchOnString(block.Instructions, ref i)) {
changed = true;
continue;
}
}
if (!changed) continue;
SwitchDetection.SimplifySwitchInstruction(block);
if (block.Parent is BlockContainer container)
changedContainers.Add(container);
}
foreach (var container in changedContainers)
container.SortBlocks(deleteUnreachableBlocks: true);
}
bool SimplifyCascadingIfStatements(InstructionCollection<ILInstruction> instructions, ref int i)
{
if (i < 1) return false;
// match first block: checking switch-value for null or first value (Roslyn)
// if (call op_Equality(ldloc switchValueVar, ldstr value)) br firstBlock
// -or-
// if (comp(ldloc switchValueVar == ldnull)) br defaultBlock
if (!(instructions[i].MatchIfInstruction(out var condition, out var firstBlockJump)))
return false;
if (!firstBlockJump.MatchBranch(out var firstBlock))
return false;
List<(string, Block)> values = new List<(string, Block)>();
ILInstruction switchValue = null;
// match call to operator ==(string, string)
if (!MatchStringEqualityComparison(condition, out var switchValueVar, out string firstBlockValue))
return false;
values.Add((firstBlockValue, firstBlock));
bool extraLoad = false;
if (instructions[i - 1].MatchStLoc(switchValueVar, out switchValue)) {
// stloc switchValueVar(switchValue)
// if (call op_Equality(ldloc switchValueVar, ldstr value)) br firstBlock
} else if (instructions[i - 1] is StLoc stloc && stloc.Value.MatchLdLoc(switchValueVar)) {
// in case of optimized legacy code there are two stlocs:
// stloc otherSwitchValueVar(ldloc switchValue)
// stloc switchValueVar(ldloc otherSwitchValueVar)
// if (call op_Equality(ldloc otherSwitchValueVar, ldstr value)) br firstBlock
var otherSwitchValueVar = switchValueVar;
switchValueVar = stloc.Variable;
if (i >= 2 && instructions[i - 2].MatchStLoc(otherSwitchValueVar, out switchValue)
&& otherSwitchValueVar.IsSingleDefinition && otherSwitchValueVar.LoadCount == 2)
{
extraLoad = true;
} else {
switchValue = new LdLoc(otherSwitchValueVar);
}
} else {
switchValue = new LdLoc(switchValueVar);
}
// if instruction must be followed by a branch to the next case
if (!(instructions.ElementAtOrDefault(i + 1) is Branch nextCaseJump))
return false;
// extract all cases and add them to the values list.
Block currentCaseBlock = nextCaseJump.TargetBlock;
Block nextCaseBlock;
while ((nextCaseBlock = MatchCaseBlock(currentCaseBlock, switchValueVar, out string value, out Block block)) != null) {
values.Add((value, block));
currentCaseBlock = nextCaseBlock;
}
// We didn't find enough cases, exit
if (values.Count < 3)
return false;
// if the switchValueVar is used in other places as well, do not eliminate the store.
bool keepAssignmentBefore = false;
if (switchValueVar.LoadCount > values.Count) {
keepAssignmentBefore = true;
switchValue = new LdLoc(switchValueVar);
}
var sections = new List<SwitchSection>(values.SelectWithIndex((index, b) => new SwitchSection { Labels = new LongSet(index), Body = new Branch(b.Item2) }));
sections.Add(new SwitchSection { Labels = new LongSet(new LongInterval(0, sections.Count)).Invert(), Body = new Branch(currentCaseBlock) });
var stringToInt = new StringToInt(switchValue, values.SelectArray(item => item.Item1));
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
if (extraLoad) {
instructions[i - 2].ReplaceWith(inst);
instructions.RemoveRange(i - 1, 3);
i -= 2;
} else {
if (keepAssignmentBefore) {
instructions[i].ReplaceWith(inst);
instructions.RemoveAt(i + 1);
} else {
instructions[i - 1].ReplaceWith(inst);
instructions.RemoveRange(i, 2);
i--;
}
}
return true;
}
/// <summary>
/// Each case consists of two blocks:
/// 1. block:
/// if (call op_Equality(ldloc switchVariable, ldstr value)) br caseBlock
/// br nextBlock
/// -or-
/// if (comp(ldloc switchValueVar == ldnull)) br nextBlock
/// br caseBlock
/// 2. block is caseBlock
/// This method matches the above pattern or its inverted form:
/// the call to ==(string, string) is wrapped in logic.not and the branch targets are reversed.
/// Returns the next block that follows in the block-chain.
/// The <paramref name="switchVariable"/> is updated if the value gets copied to a different variable.
/// See comments below for more info.
/// </summary>
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;
Block nextBlock;
if (condition.MatchLogicNot(out var inner)) {
condition = inner;
nextBlock = caseBlock;
if (!currentBlock.Instructions[1].MatchBranch(out caseBlock))
return null;
} else {
if (!currentBlock.Instructions[1].MatchBranch(out nextBlock))
return null;
}
if (!MatchStringEqualityComparison(condition, switchVariable, out value)) {
return null;
}
return nextBlock;
}
/// <summary>
/// Matches the C# 2.0 switch-on-string pattern, which uses Dictionary&lt;string, int&gt;.
/// </summary>
bool MatchLegacySwitchOnStringWithDict(InstructionCollection<ILInstruction> instructions, ref int i)
{
if (i < 1) return false;
// match first block: checking switch-value for null
// stloc switchValueVar(switchValue)
// if (comp(ldloc switchValueVar == ldnull)) br nullCase
// br nextBlock
if (!(instructions[i].MatchIfInstruction(out var condition, out var exitBlockJump) &&
instructions[i - 1].MatchStLoc(out var switchValueVar, out var switchValue) && switchValueVar.Type.IsKnownType(KnownTypeCode.String)))
return false;
if (!switchValueVar.IsSingleDefinition)
return false;
if (!exitBlockJump.MatchBranch(out var nullValueCaseBlock))
return false;
if (!(condition.MatchCompEquals(out var left, out var right) && right.MatchLdNull()
&& ((SemanticHelper.IsPure(switchValue.Flags) && left.Match(switchValue).Success) || left.MatchLdLoc(switchValueVar))))
return false;
var nextBlockJump = instructions.ElementAtOrDefault(i + 1) as Branch;
if (nextBlockJump == null || nextBlockJump.TargetBlock.IncomingEdgeCount != 1)
return false;
// match second block: checking compiler-generated Dictionary<string, int> for null
// if (comp(volatile.ldobj System.Collections.Generic.Dictionary`2[[System.String],[System.Int32]](ldsflda $$method0x600000c-1) != ldnull)) br caseNullBlock
// br dictInitBlock
var nextBlock = nextBlockJump.TargetBlock;
if (nextBlock.Instructions.Count != 2 || !nextBlock.Instructions[0].MatchIfInstruction(out condition, out var tryGetValueBlockJump))
return false;
if (!tryGetValueBlockJump.MatchBranch(out var tryGetValueBlock))
return false;
if (!nextBlock.Instructions[1].MatchBranch(out var dictInitBlock) || dictInitBlock.IncomingEdgeCount != 1)
return false;
if (!(condition.MatchCompNotEquals(out left, out right) && right.MatchLdNull() &&
MatchDictionaryFieldLoad(left, IsStringToIntDictionary, out var dictField, out var dictionaryType)))
return false;
// match third block: initialization of compiler-generated Dictionary<string, int>
// stloc dict(newobj Dictionary..ctor(ldc.i4 valuesLength))
// call Add(ldloc dict, ldstr value, ldc.i4 index)
// ... more calls to Add ...
// volatile.stobj System.Collections.Generic.Dictionary`2[[System.String],[System.Int32]](ldsflda $$method0x600003f-1, ldloc dict)
// br switchHeadBlock
if (dictInitBlock.IncomingEdgeCount != 1 || dictInitBlock.Instructions.Count < 3)
return false;
if (!ExtractStringValuesFromInitBlock(dictInitBlock, out var stringValues, tryGetValueBlock, dictionaryType, dictField))
return false;
// match fourth block: TryGetValue on compiler-generated Dictionary<string, int>
// if (logic.not(call TryGetValue(volatile.ldobj System.Collections.Generic.Dictionary`2[[System.String],[System.Int32]](ldsflda $$method0x600000c-1), ldloc switchValueVar, ldloca switchIndexVar))) br defaultBlock
// br switchBlock
if (tryGetValueBlock.IncomingEdgeCount != 2 || tryGetValueBlock.Instructions.Count != 2)
return false;
if (!tryGetValueBlock.Instructions[0].MatchIfInstruction(out condition, out var defaultBlockJump))
return false;
if (!defaultBlockJump.MatchBranch(out var defaultBlock))
return false;
if (!(condition.MatchLogicNot(out var arg) && arg is Call c && c.Method.Name == "TryGetValue" &&
MatchDictionaryFieldLoad(c.Arguments[0], IsStringToIntDictionary, out var dictField2, out _) && dictField2.Equals(dictField)))
return false;
if (!c.Arguments[1].MatchLdLoc(switchValueVar) || !c.Arguments[2].MatchLdLoca(out var switchIndexVar))
return false;
if (!tryGetValueBlock.Instructions[1].MatchBranch(out var switchBlock))
return false;
// match fifth block: switch-instruction block
// switch (ldloc switchVariable) {
// case [0..1): br caseBlock1
// ... more cases ...
// case [long.MinValue..0),[13..long.MaxValue]: br defaultBlock
// }
if (switchBlock.IncomingEdgeCount != 1 || switchBlock.Instructions.Count != 1)
return false;
if (!(switchBlock.Instructions[0] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(switchIndexVar)))
return false;
var sections = new List<SwitchSection>(switchInst.Sections);
// switch contains case null:
if (nullValueCaseBlock != defaultBlock) {
if (!AddNullSection(sections, stringValues, nullValueCaseBlock)) {
return false;
}
}
bool keepAssignmentBefore = false;
if (switchValueVar.LoadCount > 2) {
switchValue = new LdLoc(switchValueVar);
keepAssignmentBefore = true;
}
var stringToInt = new StringToInt(switchValue, stringValues.ToArray());
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
instructions[i + 1].ReplaceWith(inst);
if (keepAssignmentBefore) {
// delete if (comp(ldloc switchValueVar == ldnull))
instructions.RemoveAt(i);
i--;
} else {
// delete both the if and the assignment before
instructions.RemoveRange(i - 1, 2);
i -= 2;
}
return true;
}
private bool AddNullSection(List<SwitchSection> sections, List<string> stringValues, Block nullValueCaseBlock)
{
var label = new LongSet(sections.Count);
var possibleConflicts = sections.Where(sec => sec.Labels.Overlaps(label)).ToArray();
if (possibleConflicts.Length > 1)
return false;
else if (possibleConflicts.Length == 1) {
if (possibleConflicts[0].Labels.Count() == 1)
return false; // cannot remove only label
possibleConflicts[0].Labels = possibleConflicts[0].Labels.ExceptWith(label);
}
stringValues.Add(null);
sections.Add(new SwitchSection() { Labels = label, Body = new Branch(nullValueCaseBlock) });
return true;
}
/// <summary>
/// Matches 'volatile.ldobj dictionaryType(ldsflda dictField)'
/// </summary>
bool MatchDictionaryFieldLoad(ILInstruction inst, Func<IType, bool> typeMatcher, out IField dictField, out IType dictionaryType)
{
dictField = null;
dictionaryType = null;
return inst.MatchLdObj(out var dictionaryFieldLoad, out dictionaryType) &&
typeMatcher(dictionaryType) &&
dictionaryFieldLoad.MatchLdsFlda(out dictField) &&
(dictField.IsCompilerGeneratedOrIsInCompilerGeneratedClass() || dictField.Name.StartsWith("$$method", StringComparison.Ordinal));
}
/// <summary>
/// Matches and extracts values from Add-call sequences.
/// </summary>
bool ExtractStringValuesFromInitBlock(Block block, out List<string> values, Block targetBlock, IType dictionaryType, IField dictionaryField)
{
values = null;
// stloc dictVar(newobj Dictionary..ctor(ldc.i4 valuesLength))
// -or-
// stloc dictVar(newobj Hashtable..ctor(ldc.i4 capacity, ldc.f loadFactor))
if (!(block.Instructions[0].MatchStLoc(out var dictVar, out var newObjDict) && newObjDict is NewObj newObj))
return false;
if (!newObj.Method.DeclaringType.Equals(dictionaryType))
return false;
int valuesLength = 0;
if (newObj.Arguments.Count == 2) {
if (!newObj.Arguments[0].MatchLdcI4(out valuesLength))
return false;
if (!newObj.Arguments[1].MatchLdcF(0.5))
return false;
} else if (newObj.Arguments.Count == 1) {
if (!newObj.Arguments[0].MatchLdcI4(out valuesLength))
return false;
}
values = new List<string>(valuesLength);
int i = 0;
while (MatchAddCall(dictionaryType, block.Instructions[i + 1], dictVar, i, out var value)) {
values.Add(value);
i++;
}
// final store to compiler-generated variable:
// volatile.stobj dictionaryType(ldsflda dictionaryField, ldloc dictVar)
if (!(block.Instructions[i + 1].MatchStObj(out var loadField, out var dictVarLoad, out var dictType) &&
dictType.Equals(dictionaryType) && loadField.MatchLdsFlda(out var dictField) && dictField.Equals(dictionaryField) &&
dictVarLoad.MatchLdLoc(dictVar)))
return false;
return block.Instructions[i + 2].MatchBranch(targetBlock);
}
/// <summary>
/// call Add(ldloc dictVar, ldstr value, ldc.i4 index)
/// -or-
/// call Add(ldloc dictVar, ldstr value, box System.Int32(ldc.i4 index))
/// </summary>
bool MatchAddCall(IType dictionaryType, ILInstruction inst, ILVariable dictVar, int index, out string value)
{
value = null;
if (!(inst is Call c && c.Method.Name == "Add" && c.Arguments.Count == 3))
return false;
if (!(c.Arguments[0].MatchLdLoc(dictVar) && c.Arguments[1].MatchLdStr(out value)))
return false;
if (!(c.Method.DeclaringType.Equals(dictionaryType) && !c.Method.IsStatic))
return false;
return (c.Arguments[2].MatchLdcI4(index) || (c.Arguments[2].MatchBox(out var arg, out _) && arg.MatchLdcI4(index)));
}
bool IsStringToIntDictionary(IType dictionaryType)
{
if (dictionaryType.FullName != "System.Collections.Generic.Dictionary")
return false;
if (dictionaryType.TypeArguments.Count != 2)
return false;
return dictionaryType.TypeArguments[0].IsKnownType(KnownTypeCode.String) &&
dictionaryType.TypeArguments[1].IsKnownType(KnownTypeCode.Int32);
}
bool IsNonGenericHashtable(IType dictionaryType)
{
if (dictionaryType.FullName != "System.Collections.Hashtable")
return false;
if (dictionaryType.TypeArguments.Count != 0)
return false;
return true;
}
bool MatchLegacySwitchOnStringWithHashtable(InstructionCollection<ILInstruction> instructions, ref int i)
{
// match first block: checking compiler-generated Hashtable for null
// if (comp(volatile.ldobj System.Collections.Hashtable(ldsflda $$method0x600003f-1) != ldnull)) br switchHeadBlock
// br tableInitBlock
if (!(instructions[i].MatchIfInstruction(out var condition, out var branchToSwitchHead) && i + 1 < instructions.Count))
return false;
if (!instructions[i + 1].MatchBranch(out var tableInitBlock) || tableInitBlock.IncomingEdgeCount != 1)
return false;
if (!(condition.MatchCompNotEquals(out var left, out var right) && right.MatchLdNull() &&
MatchDictionaryFieldLoad(left, IsNonGenericHashtable, out var dictField, out var dictionaryType)))
return false;
if (!branchToSwitchHead.MatchBranch(out var switchHead))
return false;
// match second block: initialization of compiler-generated Hashtable
// stloc table(newobj Hashtable..ctor(ldc.i4 capacity, ldc.f loadFactor))
// call Add(ldloc table, ldstr value, box System.Int32(ldc.i4 index))
// ... more calls to Add ...
// volatile.stobj System.Collections.Hashtable(ldsflda $$method0x600003f - 1, ldloc table)
// br switchHeadBlock
if (tableInitBlock.IncomingEdgeCount != 1 || tableInitBlock.Instructions.Count < 3)
return false;
if (!ExtractStringValuesFromInitBlock(tableInitBlock, out var stringValues, switchHead, dictionaryType, dictField))
return false;
// match third block: checking switch-value for null
// stloc tmp(ldloc switch-value)
// stloc switchVariable(ldloc tmp)
// if (comp(ldloc tmp == ldnull)) br nullCaseBlock
// br getItemBlock
if (switchHead.Instructions.Count != 4 || switchHead.IncomingEdgeCount != 2)
return false;
if (!switchHead.Instructions[0].MatchStLoc(out var tmp, out var switchValue))
return false;
if (!switchHead.Instructions[1].MatchStLoc(out var switchVariable, out var tmpLoad) || !tmpLoad.MatchLdLoc(tmp))
return false;
if (!switchHead.Instructions[2].MatchIfInstruction(out condition, out var nullCaseBlockBranch))
return false;
if (!switchHead.Instructions[3].MatchBranch(out var getItemBlock) || !nullCaseBlockBranch.MatchBranch(out var nullCaseBlock))
return false;
if (!(condition.MatchCompEquals(out left, out right) && right.MatchLdNull() && left.MatchLdLoc(tmp)))
return false;
// match fourth block: get_Item on compiler-generated Hashtable
// stloc tmp2(call get_Item(volatile.ldobj System.Collections.Hashtable(ldsflda $$method0x600003f - 1), ldloc switchVariable))
// stloc switchVariable(ldloc tmp2)
// if (comp(ldloc tmp2 == ldnull)) br defaultCaseBlock
// br switchBlock
if (getItemBlock.IncomingEdgeCount != 1 || getItemBlock.Instructions.Count != 4)
return false;
if (!(getItemBlock.Instructions[0].MatchStLoc(out var tmp2, out var getItem) && getItem is Call getItemCall && getItemCall.Method.Name == "get_Item"))
return false;
if (!getItemBlock.Instructions[1].MatchStLoc(out var switchVariable2, out var tmp2Load) || !tmp2Load.MatchLdLoc(tmp2))
return false;
if (!ILVariableEqualityComparer.Instance.Equals(switchVariable, switchVariable2))
return false;
if (!getItemBlock.Instructions[2].MatchIfInstruction(out condition, out var defaultBlockBranch))
return false;
if (!getItemBlock.Instructions[3].MatchBranch(out var switchBlock) || !defaultBlockBranch.MatchBranch(out var defaultBlock))
return false;
if (!(condition.MatchCompEquals(out left, out right) && right.MatchLdNull() && left.MatchLdLoc(tmp2)))
return false;
if (!(getItemCall.Arguments.Count == 2 && MatchDictionaryFieldLoad(getItemCall.Arguments[0], IsStringToIntDictionary, out var dictField2, out _) && dictField2.Equals(dictField)) &&
getItemCall.Arguments[1].MatchLdLoc(switchVariable2))
return false;
// match fifth block: switch-instruction block
// switch (ldobj System.Int32(unbox System.Int32(ldloc switchVariable))) {
// case [0..1): br caseBlock1
// ... more cases ...
// case [long.MinValue..0),[13..long.MaxValue]: br defaultBlock
// }
if (switchBlock.IncomingEdgeCount != 1 || switchBlock.Instructions.Count != 1)
return false;
if (!(switchBlock.Instructions[0] is SwitchInstruction switchInst && switchInst.Value.MatchLdObj(out var target, out var ldobjType) &&
target.MatchUnbox(out var arg, out var unboxType) && arg.MatchLdLoc(switchVariable2) && ldobjType.IsKnownType(KnownTypeCode.Int32) && unboxType.Equals(ldobjType)))
return false;
var sections = new List<SwitchSection>(switchInst.Sections);
// switch contains case null:
if (nullCaseBlock != defaultBlock) {
if (!AddNullSection(sections, stringValues, nullCaseBlock)) {
return false;
}
}
var stringToInt = new StringToInt(switchValue, stringValues.ToArray());
var inst = new SwitchInstruction(stringToInt);
inst.Sections.AddRange(sections);
instructions[i + 1].ReplaceWith(inst);
instructions.RemoveAt(i);
return true;
}
bool MatchRoslynSwitchOnString(InstructionCollection<ILInstruction> instructions, ref int i)
{
if (i < 1) return false;
// stloc switchValueVar(call ComputeStringHash(switchValue))
// switch (ldloc switchValueVar) {
// case [211455823..211455824): br caseBlock1
// ... more cases ...
// case [long.MinValue..-365098645),...,[1697255802..long.MaxValue]: br defaultBlock
// }
if (!(instructions[i] is SwitchInstruction switchInst && switchInst.Value.MatchLdLoc(out var switchValueVar) &&
MatchComputeStringHashCall(instructions[i - 1], switchValueVar, out LdLoc switchValueLoad)))
return false;
var stringValues = new List<(int, string, Block)>();
int index = 0;
SwitchSection defaultSection = switchInst.Sections.MaxBy(s => s.Labels.Count());
foreach (var section in switchInst.Sections) {
if (section == defaultSection) continue;
// extract target block
if (!section.Body.MatchBranch(out Block target))
return false;
if (!MatchRoslynCaseBlockHead(target, switchValueLoad.Variable, out Block body, out string stringValue))
return false;
stringValues.Add((index++, stringValue, body));
}
ILInstruction switchValueInst = switchValueLoad;
// stloc switchValueLoadVariable(switchValue)
// stloc switchValueVar(call ComputeStringHash(ldloc switchValueLoadVariable))
// switch (ldloc switchValueVar) {
bool keepAssignmentBefore;
// if the switchValueLoad.Variable is only used in the compiler generated case equality checks, we can remove it.
if (i > 1 && instructions[i - 2].MatchStLoc(switchValueLoad.Variable, out var switchValueTmp) &&
switchValueLoad.Variable.IsSingleDefinition && switchValueLoad.Variable.LoadCount == switchInst.Sections.Count)
{
switchValueInst = switchValueTmp;
keepAssignmentBefore = false;
} else {
keepAssignmentBefore = true;
}
var defaultLabel = new LongSet(new LongInterval(0, index)).Invert();
var newSwitch = new SwitchInstruction(new StringToInt(switchValueInst, stringValues.Select(item => item.Item2).ToArray()));
newSwitch.Sections.AddRange(stringValues.Select(section => new SwitchSection { Labels = new Util.LongSet(section.Item1), Body = new Branch(section.Item3) }));
newSwitch.Sections.Add(new SwitchSection { Labels = defaultLabel, Body = defaultSection.Body });
instructions[i].ReplaceWith(newSwitch);
if (keepAssignmentBefore) {
instructions.RemoveAt(i - 1);
i--;
} else {
instructions.RemoveRange(i - 2, 2);
i -= 2;
}
return true;
}
/// <summary>
/// Matches and the negated version:
/// if (call op_Equality(ldloc V_0, ldstr "Fifth case")) br body
/// br exit
/// </summary>
bool MatchRoslynCaseBlockHead(Block target, ILVariable switchValueVar, out Block body, out string stringValue)
{
body = null;
stringValue = null;
if (target.Instructions.Count != 2)
return false;
if (!target.Instructions[0].MatchIfInstruction(out var condition, out var bodyBranch))
return false;
if (!bodyBranch.MatchBranch(out body))
return false;
if (MatchStringEqualityComparison(condition, switchValueVar, out stringValue)) {
return body != null;
} else if (condition.MatchLogicNot(out condition) && MatchStringEqualityComparison(condition, switchValueVar, out stringValue)) {
if (!target.Instructions[1].MatchBranch(out Block exit))
return false;
body = exit;
return true;
} else {
return false;
}
}
/// <summary>
/// Matches 'stloc(targetVar, call ComputeStringHash(ldloc switchValue))'
/// </summary>
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;
}
/// <summary>
/// Matches 'call string.op_Equality(ldloc(variable), ldstr(stringValue))'
/// or 'comp(ldloc(variable) == ldnull)'
/// </summary>
bool MatchStringEqualityComparison(ILInstruction condition, ILVariable variable, out string stringValue)
{
return MatchStringEqualityComparison(condition, out var v, out stringValue) && 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)
{
stringValue = null;
variable = null;
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)
{
left = c.Arguments[0];
right = c.Arguments[1];
return left.MatchLdLoc(out variable) && right.MatchLdStr(out stringValue);
} else if (condition.MatchCompEqualsNull(out var arg)) {
stringValue = null;
return arg.MatchLdLoc(out variable);
} else {
return false;
}
}
}
}

2
ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

@ -188,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -188,7 +188,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (objVar.Type.IsKnownType(KnownTypeCode.NullableOfT)) {
if (!entryPoint.Instructions[checkIndex].MatchIfInstruction(out var condition, out var disposeInst))
return false;
if (!(NullableLiftingTransform.MatchHasValueCall(condition, out var v) && v == objVar))
if (!NullableLiftingTransform.MatchHasValueCall(condition, objVar))
return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false;

76
ICSharpCode.Decompiler/Util/CollectionExtensions.cs

@ -114,5 +114,81 @@ namespace ICSharpCode.Decompiler.Util @@ -114,5 +114,81 @@ namespace ICSharpCode.Decompiler.Util
moreB = enumB.MoveNext();
}
}
/// <summary>
/// Returns the minimum element.
/// </summary>
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
public static T MinBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable<K>
{
return source.MinBy(keySelector, Comparer<K>.Default);
}
/// <summary>
/// Returns the minimum element.
/// </summary>
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
public static T MinBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> keyComparer)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (keyComparer == null)
keyComparer = Comparer<K>.Default;
using (var enumerator = source.GetEnumerator()) {
if (!enumerator.MoveNext())
throw new InvalidOperationException("Sequence contains no elements");
T minElement = enumerator.Current;
K minKey = keySelector(minElement);
while (enumerator.MoveNext()) {
T element = enumerator.Current;
K key = keySelector(element);
if (keyComparer.Compare(key, minKey) < 0) {
minElement = element;
minKey = key;
}
}
return minElement;
}
}
/// <summary>
/// Returns the maximum element.
/// </summary>
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
public static T MaxBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector) where K : IComparable<K>
{
return source.MaxBy(keySelector, Comparer<K>.Default);
}
/// <summary>
/// Returns the maximum element.
/// </summary>
/// <exception cref="InvalidOperationException">The input sequence is empty</exception>
public static T MaxBy<T, K>(this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> keyComparer)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (keyComparer == null)
keyComparer = Comparer<K>.Default;
using (var enumerator = source.GetEnumerator()) {
if (!enumerator.MoveNext())
throw new InvalidOperationException("Sequence contains no elements");
T maxElement = enumerator.Current;
K maxKey = keySelector(maxElement);
while (enumerator.MoveNext()) {
T element = enumerator.Current;
K key = keySelector(element);
if (keyComparer.Compare(key, maxKey) > 0) {
maxElement = element;
maxKey = key;
}
}
return maxElement;
}
}
}
}

221
ICSharpCode.Decompiler/Util/GraphVizGraph.cs

@ -0,0 +1,221 @@ @@ -0,0 +1,221 @@
// Copyright (c) 2010-2013 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
namespace ICSharpCode.Decompiler.Util
{
#if DEBUG
/// <summary>
/// GraphViz graph.
/// </summary>
sealed class GraphVizGraph
{
List<GraphVizNode> nodes = new List<GraphVizNode>();
List<GraphVizEdge> edges = new List<GraphVizEdge>();
public string rankdir;
public string Title;
public void AddEdge(GraphVizEdge edge)
{
edges.Add(edge);
}
public void AddNode(GraphVizNode node)
{
nodes.Add(node);
}
public void Save(string fileName)
{
using (StreamWriter writer = new StreamWriter(fileName))
Save(writer);
}
public void Show()
{
Show(null);
}
public void Show(string name)
{
if (name == null)
name = Title;
if (name != null)
foreach (char c in Path.GetInvalidFileNameChars())
name = name.Replace(c, '-');
string fileName = name != null ? Path.Combine(Path.GetTempPath(), name) : Path.GetTempFileName();
Save(fileName + ".gv");
Process.Start("dot", "\"" + fileName + ".gv\" -Tpng -o \"" + fileName + ".png\"").WaitForExit();
Process.Start(fileName + ".png");
}
static string Escape(string text)
{
if (Regex.IsMatch(text, @"^[\w\d]+$")) {
return text;
} else {
return "\"" + text.Replace("\\", "\\\\").Replace("\r", "").Replace("\n", "\\n").Replace("\"", "\\\"") + "\"";
}
}
static void WriteGraphAttribute(TextWriter writer, string name, string value)
{
if (value != null)
writer.WriteLine("{0}={1};", name, Escape(value));
}
internal static void WriteAttribute(TextWriter writer, string name, double? value, ref bool isFirst)
{
if (value != null) {
WriteAttribute(writer, name, value.Value.ToString(CultureInfo.InvariantCulture), ref isFirst);
}
}
internal static void WriteAttribute(TextWriter writer, string name, bool? value, ref bool isFirst)
{
if (value != null) {
WriteAttribute(writer, name, value.Value ? "true" : "false", ref isFirst);
}
}
internal static void WriteAttribute(TextWriter writer, string name, string value, ref bool isFirst)
{
if (value != null) {
if (isFirst)
isFirst = false;
else
writer.Write(',');
writer.Write("{0}={1}", name, Escape(value));
}
}
public void Save(TextWriter writer)
{
if (writer == null)
throw new ArgumentNullException("writer");
writer.WriteLine("digraph G {");
writer.WriteLine("node [fontsize = 16];");
WriteGraphAttribute(writer, "rankdir", rankdir);
foreach (GraphVizNode node in nodes) {
node.Save(writer);
}
foreach (GraphVizEdge edge in edges) {
edge.Save(writer);
}
writer.WriteLine("}");
}
}
sealed class GraphVizEdge
{
public readonly string Source, Target;
/// <summary>edge stroke color</summary>
public string color;
/// <summary>use edge to affect node ranking</summary>
public bool? constraint;
public string label;
public string style;
/// <summary>point size of label</summary>
public int? fontsize;
public GraphVizEdge(string source, string target)
{
if (source == null)
throw new ArgumentNullException("source");
if (target == null)
throw new ArgumentNullException("target");
this.Source = source;
this.Target = target;
}
public GraphVizEdge(int source, int target)
{
this.Source = source.ToString(CultureInfo.InvariantCulture);
this.Target = target.ToString(CultureInfo.InvariantCulture);
}
public void Save(TextWriter writer)
{
writer.Write("{0} -> {1} [", Source, Target);
bool isFirst = true;
GraphVizGraph.WriteAttribute(writer, "label", label, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "style", style, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "fontsize", fontsize, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "color", color, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "constraint", constraint, ref isFirst);
writer.WriteLine("];");
}
}
sealed class GraphVizNode
{
public readonly string ID;
public string label;
public string labelloc;
/// <summary>point size of label</summary>
public int? fontsize;
/// <summary>minimum height in inches</summary>
public double? height;
/// <summary>space around label</summary>
public string margin;
/// <summary>node shape</summary>
public string shape;
public GraphVizNode(string id)
{
if (id == null)
throw new ArgumentNullException("id");
this.ID = id;
}
public GraphVizNode(int id)
{
this.ID = id.ToString(CultureInfo.InvariantCulture);
}
public void Save(TextWriter writer)
{
writer.Write(ID);
writer.Write(" [");
bool isFirst = true;
GraphVizGraph.WriteAttribute(writer, "label", label, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "labelloc", labelloc, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "fontsize", fontsize, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "margin", margin, ref isFirst);
GraphVizGraph.WriteAttribute(writer, "shape", shape, ref isFirst);
writer.WriteLine("];");
}
}
#endif
}

10
ILSpy.BamlDecompiler/BamlResourceEntryNode.cs

@ -10,6 +10,7 @@ using System.Threading.Tasks; @@ -10,6 +10,7 @@ using System.Threading.Tasks;
using System.Xml.Linq;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.Decompiler.Util;
using ICSharpCode.ILSpy;
using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.TreeNodes;
@ -135,7 +136,7 @@ namespace ILSpy.BamlDecompiler @@ -135,7 +136,7 @@ namespace ILSpy.BamlDecompiler
}
}
static void RemoveConnectionIds(XElement element, Dictionary<long, EventRegistration[]> eventMappings)
static void RemoveConnectionIds(XElement element, List<(LongSet key, EventRegistration[] value)> eventMappings)
{
foreach (var child in element.Elements())
RemoveConnectionIds(child, eventMappings);
@ -143,10 +144,9 @@ namespace ILSpy.BamlDecompiler @@ -143,10 +144,9 @@ namespace ILSpy.BamlDecompiler
var removableAttrs = new List<XAttribute>();
var addableAttrs = new List<XAttribute>();
foreach (var attr in element.Attributes(XName.Get("ConnectionId", XmlBamlReader.XWPFNamespace))) {
int id;
if (int.TryParse(attr.Value, out id) && eventMappings.ContainsKey(id)) {
var map = eventMappings[id];
foreach (var entry in map) {
int id, index;
if (int.TryParse(attr.Value, out id) && (index = eventMappings.FindIndex(item => item.key.Contains(id))) > -1) {
foreach (var entry in eventMappings[index].value) {
string xmlns = ""; // TODO : implement xmlns resolver!
addableAttrs.Add(new XAttribute(xmlns + entry.EventName, entry.MethodName));
}

31
ILSpy.BamlDecompiler/ConnectMethodDecompiler.cs

@ -10,6 +10,7 @@ using ICSharpCode.Decompiler.CSharp; @@ -10,6 +10,7 @@ using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.IL.Transforms;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using Mono.Cecil;
namespace ILSpy.BamlDecompiler
@ -34,9 +35,9 @@ namespace ILSpy.BamlDecompiler @@ -34,9 +35,9 @@ namespace ILSpy.BamlDecompiler
this.assembly = assembly;
}
public Dictionary<long, EventRegistration[]> DecompileEventMappings(string fullTypeName, CancellationToken cancellationToken)
public List<(LongSet, EventRegistration[])> DecompileEventMappings(string fullTypeName, CancellationToken cancellationToken)
{
var result = new Dictionary<long, EventRegistration[]>();
var result = new List<(LongSet, EventRegistration[])>();
TypeDefinition type = this.assembly.MainModule.GetType(fullTypeName);
if (type == null)
@ -65,13 +66,12 @@ namespace ILSpy.BamlDecompiler @@ -65,13 +66,12 @@ namespace ILSpy.BamlDecompiler
function.RunTransforms(CSharpDecompiler.GetILTransforms(), context);
var block = function.Body.Children.OfType<Block>().First();
var ilSwitch = block.Children.OfType<SwitchInstruction>().FirstOrDefault();
var ilSwitch = block.Descendants.OfType<SwitchInstruction>().FirstOrDefault();
if (ilSwitch != null) {
foreach (var section in ilSwitch.Sections) {
var events = FindEvents(section.Body);
foreach (long id in section.Labels.Values)
result.Add(id, events);
result.Add((section.Labels, events));
}
} else {
foreach (var ifInst in function.Descendants.OfType<IfInstruction>()) {
@ -82,7 +82,7 @@ namespace ILSpy.BamlDecompiler @@ -82,7 +82,7 @@ namespace ILSpy.BamlDecompiler
if (!comp.Right.MatchLdcI4(out id))
continue;
var events = FindEvents(comp.Kind == ComparisonKind.Inequality ? ifInst.FalseInst : ifInst.TrueInst);
result.Add(id, events);
result.Add((new LongSet(id), events));
}
}
return result;
@ -92,13 +92,18 @@ namespace ILSpy.BamlDecompiler @@ -92,13 +92,18 @@ namespace ILSpy.BamlDecompiler
{
var events = new List<EventRegistration>();
if (inst is Block) {
foreach (var node in ((Block)inst).Instructions) {
FindEvents(node, events);
}
FindEvents(((Block)inst).FinalInstruction, events);
} else {
FindEvents(inst, events);
switch (inst) {
case Block b:
foreach (var node in ((Block)inst).Instructions) {
FindEvents(node, events);
}
FindEvents(((Block)inst).FinalInstruction, events);
break;
case Branch br:
return FindEvents(br.TargetBlock);
default:
FindEvents(inst, events);
break;
}
return events.ToArray();
}

4
ILSpy/Languages/ILAstLanguage.cs

@ -169,6 +169,10 @@ namespace ICSharpCode.ILSpy @@ -169,6 +169,10 @@ namespace ICSharpCode.ILSpy
try {
il.RunTransforms(transforms, context);
} catch (StepLimitReachedException) {
} catch (Exception ex) {
output.WriteLine(ex.ToString());
output.WriteLine();
output.WriteLine("ILAst after the crash:");
} finally {
// update stepper even if a transform crashed unexpectedly
if (options.StepLimit == int.MaxValue) {

5
ILSpy/LoadedAssembly.cs

@ -51,13 +51,14 @@ namespace ICSharpCode.ILSpy @@ -51,13 +51,14 @@ namespace ICSharpCode.ILSpy
this.assemblyTask = Task.Factory.StartNew<ModuleDefinition>(LoadAssembly, stream); // requires that this.fileName is set
this.shortName = Path.GetFileNameWithoutExtension(fileName);
this.targetFrameworkId = new Lazy<string>(AssemblyDefinition.DetectTargetFrameworkId, false);
this.targetFrameworkId = new Lazy<string>(() => AssemblyDefinition?.DetectTargetFrameworkId(), false);
}
/// <summary>
/// Returns a target framework identifier in the form '&lt;framework&gt;Version=v&lt;version&gt;'.
/// Returns an empty string if no TargetFrameworkAttribute was found or the file doesn't contain an assembly header, i.e., is only a module.
/// </summary>
public string TargetFrameworkId => targetFrameworkId.Value;
public string TargetFrameworkId => targetFrameworkId.Value ?? string.Empty;
public Dictionary<string, UnresolvedAssemblyNameReference> LoadedAssemblyReferencesInfo => loadedAssemblyReferences;

Loading…
Cancel
Save