mirror of https://github.com/icsharpcode/ILSpy.git
54 changed files with 8466 additions and 588 deletions
@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -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"); |
||||
} |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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(" })"); |
||||
} |
||||
} |
||||
} |
||||
@ -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("}"); |
||||
} |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -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; |
||||
} |
||||
} |
||||
} |
||||
@ -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<string, int>.
|
||||
/// </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; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -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
|
||||
} |
||||
Loading…
Reference in new issue