Browse Source

Fix #905: improve exit point detection for foreach loops.

pull/976/head
Daniel Grunwald 8 years ago
parent
commit
7af15d27cd
  1. 10
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs
  2. 83
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.il
  3. 65
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.opt.il
  4. 61
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.opt.roslyn.il
  5. 70
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.roslyn.il
  6. 56
      ICSharpCode.Decompiler/IL/ControlFlow/LoopDetection.cs
  7. 2
      ICSharpCode.Decompiler/IL/Instructions.cs
  8. 2
      ICSharpCode.Decompiler/IL/Instructions.tt
  9. 4
      ICSharpCode.Decompiler/IL/Instructions/Block.cs
  10. 11
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  11. 9
      ICSharpCode.Decompiler/Util/ExtensionMethods.cs

10
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.cs

@ -430,6 +430,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -430,6 +430,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(new IntPtr(value));
}
}
public void ForEachBreakWhenFound(string name, ref StringComparison output)
{
foreach (StringComparison value in Enum.GetValues(typeof(StringComparison))) {
if (value.ToString() == name) {
output = value;
break;
}
}
}
#endregion
public void ForOverArray(string[] array)

83
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.il

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly vsvybxtk
.assembly n3qdq2uj
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
@ -20,15 +20,15 @@ @@ -20,15 +20,15 @@
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module vsvybxtk.dll
// MVID: {6C5236E6-826F-4D2D-AF03-574254796A89}
.module n3qdq2uj.dll
// MVID: {98B4F05B-5E30-48EB-AAF5-A90EA42A94A2}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x02F80000
// Image base: 0x02AD0000
// =============== CLASS MEMBERS DECLARATION ===================
@ -1594,6 +1594,81 @@ @@ -1594,6 +1594,81 @@
IL_003e: ret
} // end of method Loops::ForEachOverArrayOfPointers
.method public hidebysig instance void
ForEachBreakWhenFound(string name,
valuetype [mscorlib]System.StringComparison& output) cil managed
{
// Code size 106 (0x6a)
.maxstack 2
.locals init (valuetype [mscorlib]System.StringComparison V_0,
class [mscorlib]System.Collections.IEnumerator V_1,
bool V_2,
class [mscorlib]System.IDisposable V_3)
IL_0000: nop
IL_0001: nop
IL_0002: ldtoken [mscorlib]System.StringComparison
IL_0007: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000c: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
IL_0011: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator()
IL_0016: stloc.1
.try
{
IL_0017: br.s IL_0045
IL_0019: ldloc.1
IL_001a: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_001f: unbox.any [mscorlib]System.StringComparison
IL_0024: stloc.0
IL_0025: nop
IL_0026: ldloc.0
IL_0027: box [mscorlib]System.StringComparison
IL_002c: callvirt instance string [mscorlib]System.Object::ToString()
IL_0031: ldarg.1
IL_0032: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0037: ldc.i4.0
IL_0038: ceq
IL_003a: stloc.2
IL_003b: ldloc.2
IL_003c: brtrue.s IL_0044
IL_003e: nop
IL_003f: ldarg.2
IL_0040: ldloc.0
IL_0041: stind.i4
IL_0042: br.s IL_004f
IL_0044: nop
IL_0045: ldloc.1
IL_0046: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_004b: stloc.2
IL_004c: ldloc.2
IL_004d: brtrue.s IL_0019
IL_004f: leave.s IL_0068
} // end .try
finally
{
IL_0051: ldloc.1
IL_0052: isinst [mscorlib]System.IDisposable
IL_0057: stloc.3
IL_0058: ldloc.3
IL_0059: ldnull
IL_005a: ceq
IL_005c: stloc.2
IL_005d: ldloc.2
IL_005e: brtrue.s IL_0067
IL_0060: ldloc.3
IL_0061: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0066: nop
IL_0067: endfinally
} // end handler
IL_0068: nop
IL_0069: ret
} // end of method Loops::ForEachBreakWhenFound
.method public hidebysig instance void
ForOverArray(string[] 'array') cil managed
{

65
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.opt.il

@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly hrizu0iy
.assembly spdzj2hg
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
@ -20,15 +20,15 @@ @@ -20,15 +20,15 @@
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.module hrizu0iy.dll
// MVID: {8FC80EBC-E149-454A-A769-13F3C07EB46F}
.module spdzj2hg.dll
// MVID: {D8CD3A33-FD06-4791-A4E6-AC836DA34D06}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x02CE0000
// Image base: 0x002F0000
// =============== CLASS MEMBERS DECLARATION ===================
@ -1279,6 +1279,63 @@ @@ -1279,6 +1279,63 @@
IL_0034: ret
} // end of method Loops::ForEachOverArrayOfPointers
.method public hidebysig instance void
ForEachBreakWhenFound(string name,
valuetype [mscorlib]System.StringComparison& output) cil managed
{
// Code size 87 (0x57)
.maxstack 2
.locals init (valuetype [mscorlib]System.StringComparison V_0,
class [mscorlib]System.Collections.IEnumerator V_1,
class [mscorlib]System.IDisposable V_2)
IL_0000: ldtoken [mscorlib]System.StringComparison
IL_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000a: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
IL_000f: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator()
IL_0014: stloc.1
.try
{
IL_0015: br.s IL_003b
IL_0017: ldloc.1
IL_0018: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_001d: unbox.any [mscorlib]System.StringComparison
IL_0022: stloc.0
IL_0023: ldloc.0
IL_0024: box [mscorlib]System.StringComparison
IL_0029: callvirt instance string [mscorlib]System.Object::ToString()
IL_002e: ldarg.1
IL_002f: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0034: brfalse.s IL_003b
IL_0036: ldarg.2
IL_0037: ldloc.0
IL_0038: stind.i4
IL_0039: br.s IL_0043
IL_003b: ldloc.1
IL_003c: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0041: brtrue.s IL_0017
IL_0043: leave.s IL_0056
} // end .try
finally
{
IL_0045: ldloc.1
IL_0046: isinst [mscorlib]System.IDisposable
IL_004b: stloc.2
IL_004c: ldloc.2
IL_004d: brfalse.s IL_0055
IL_004f: ldloc.2
IL_0050: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0055: endfinally
} // end handler
IL_0056: ret
} // end of method Loops::ForEachBreakWhenFound
.method public hidebysig instance void
ForOverArray(string[] 'array') cil managed
{

61
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.opt.roslyn.il

@ -25,14 +25,14 @@ @@ -25,14 +25,14 @@
.ver 0:0:0:0
}
.module Loops.dll
// MVID: {30F4646B-D900-420C-89F2-283C178D2EAC}
// MVID: {B3F85A6A-79B4-41C0-9146-2088985BFC34}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00E90000
// Image base: 0x02550000
// =============== CLASS MEMBERS DECLARATION ===================
@ -1238,6 +1238,63 @@ @@ -1238,6 +1238,63 @@
IL_0032: ret
} // end of method Loops::ForEachOverArrayOfPointers
.method public hidebysig instance void
ForEachBreakWhenFound(string name,
valuetype [mscorlib]System.StringComparison& output) cil managed
{
// Code size 89 (0x59)
.maxstack 2
.locals init (class [mscorlib]System.Collections.IEnumerator V_0,
valuetype [mscorlib]System.StringComparison V_1,
class [mscorlib]System.IDisposable V_2)
IL_0000: ldtoken [mscorlib]System.StringComparison
IL_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000a: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
IL_000f: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator()
IL_0014: stloc.0
.try
{
IL_0015: br.s IL_003d
IL_0017: ldloc.0
IL_0018: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_001d: unbox.any [mscorlib]System.StringComparison
IL_0022: stloc.1
IL_0023: ldloca.s V_1
IL_0025: constrained. [mscorlib]System.StringComparison
IL_002b: callvirt instance string [mscorlib]System.Object::ToString()
IL_0030: ldarg.1
IL_0031: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0036: brfalse.s IL_003d
IL_0038: ldarg.2
IL_0039: ldloc.1
IL_003a: stind.i4
IL_003b: leave.s IL_0058
IL_003d: ldloc.0
IL_003e: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0043: brtrue.s IL_0017
IL_0045: leave.s IL_0058
} // end .try
finally
{
IL_0047: ldloc.0
IL_0048: isinst [mscorlib]System.IDisposable
IL_004d: stloc.2
IL_004e: ldloc.2
IL_004f: brfalse.s IL_0057
IL_0051: ldloc.2
IL_0052: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0057: endfinally
} // end handler
IL_0058: ret
} // end of method Loops::ForEachBreakWhenFound
.method public hidebysig instance void
ForOverArray(string[] 'array') cil managed
{

70
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Loops.roslyn.il

@ -25,14 +25,14 @@ @@ -25,14 +25,14 @@
.ver 0:0:0:0
}
.module Loops.dll
// MVID: {721D369B-DC6E-425B-AE0E-46C242DEFDF5}
// MVID: {8261F08F-23F6-43C1-9650-C5B6A10A0F30}
.custom instance void [mscorlib]System.Security.UnverifiableCodeAttribute::.ctor() = ( 01 00 00 00 )
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00480000
// Image base: 0x00720000
// =============== CLASS MEMBERS DECLARATION ===================
@ -1488,6 +1488,72 @@ @@ -1488,6 +1488,72 @@
IL_003a: ret
} // end of method Loops::ForEachOverArrayOfPointers
.method public hidebysig instance void
ForEachBreakWhenFound(string name,
valuetype [mscorlib]System.StringComparison& output) cil managed
{
// Code size 97 (0x61)
.maxstack 2
.locals init (class [mscorlib]System.Collections.IEnumerator V_0,
valuetype [mscorlib]System.StringComparison V_1,
bool V_2,
class [mscorlib]System.IDisposable V_3)
IL_0000: nop
IL_0001: nop
IL_0002: ldtoken [mscorlib]System.StringComparison
IL_0007: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000c: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
IL_0011: callvirt instance class [mscorlib]System.Collections.IEnumerator [mscorlib]System.Array::GetEnumerator()
IL_0016: stloc.0
.try
{
IL_0017: br.s IL_0044
IL_0019: ldloc.0
IL_001a: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_001f: unbox.any [mscorlib]System.StringComparison
IL_0024: stloc.1
IL_0025: nop
IL_0026: ldloca.s V_1
IL_0028: constrained. [mscorlib]System.StringComparison
IL_002e: callvirt instance string [mscorlib]System.Object::ToString()
IL_0033: ldarg.1
IL_0034: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0039: stloc.2
IL_003a: ldloc.2
IL_003b: brfalse.s IL_0043
IL_003d: nop
IL_003e: ldarg.2
IL_003f: ldloc.1
IL_0040: stind.i4
IL_0041: br.s IL_004c
IL_0043: nop
IL_0044: ldloc.0
IL_0045: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_004a: brtrue.s IL_0019
IL_004c: leave.s IL_0060
} // end .try
finally
{
IL_004e: ldloc.0
IL_004f: isinst [mscorlib]System.IDisposable
IL_0054: stloc.3
IL_0055: ldloc.3
IL_0056: brfalse.s IL_005f
IL_0058: ldloc.3
IL_0059: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_005e: nop
IL_005f: endfinally
} // end handler
IL_0060: ret
} // end of method Loops::ForEachBreakWhenFound
.method public hidebysig instance void
ForOverArray(string[] 'array') cil managed
{

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

@ -215,6 +215,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -215,6 +215,7 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
if (exitPoint != null) {
// Either we are in case 1 and just picked an exit that maximizes the amount of code
// outside the loop, or we are in case 2 and found an exit point via post-dominance.
// Note that if exitPoint == NoExitPoint, we end up adding all dominated blocks to the loop.
var ep = exitPoint;
foreach (var node in TreeTraversal.PreOrder(loopHead, n => (n != ep) ? n.DominatorTreeChildren : null)) {
if (node != exitPoint && !node.Visited) {
@ -228,10 +229,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -228,10 +229,21 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
ExtendLoopHeuristic(loopHead, loop, loopHead);
}
}
/// <summary>
/// Special control flow node (not part of any graph) that signifies that we want to construct a loop
/// without any exit point.
/// </summary>
static readonly ControlFlowNode NoExitPoint = new ControlFlowNode();
/// <summary>
/// Finds a suitable single exit point for the specified loop.
/// </summary>
/// <returns>
/// 1) If a suitable exit point was found: the control flow block that should be reached when breaking from the loop
/// 2) If the loop should not have any exit point (extend by all dominated blocks): NoExitPoint
/// 3) otherwise (exit point unknown, heuristically extend loop): null
/// </returns>
/// <remarks>This method must not write to the Visited flags on the CFG.</remarks>
ControlFlowNode FindExitPoint(ControlFlowNode loopHead, IReadOnlyList<ControlFlowNode> naturalLoop, bool treatBackEdgesAsExits)
{
@ -245,6 +257,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -245,6 +257,16 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
// 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.
if (IsPossibleForeachLoop((Block)loopHead.UserData, out var exitBranch)) {
if (exitBranch != null) {
// let's see if the target of the exit branch is a suitable exit point
var cfgNode = loopHead.Successors.FirstOrDefault(n => n.UserData == exitBranch.TargetBlock);
if (cfgNode != null && loopHead.Dominates(cfgNode) && !context.ControlFlowGraph.HasReachableExit(cfgNode)) {
return cfgNode;
}
}
return NoExitPoint;
}
ControlFlowNode exitPoint = null;
int exitPointILOffset = -1;
foreach (var node in loopHead.DominatorTreeChildren) {
@ -388,8 +410,38 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow @@ -388,8 +410,38 @@ namespace ICSharpCode.Decompiler.IL.ControlFlow
Dominance.ComputeDominance(exitNode, context.CancellationToken);
return rev;
}
static bool IsPossibleForeachLoop(Block loopHead, out Branch exitBranch)
{
exitBranch = null;
var container = (BlockContainer)loopHead.Parent;
if (!(container.SlotInfo == TryInstruction.TryBlockSlot && container.Parent is TryFinally))
return false;
if (loopHead.Instructions.Count != 2)
return false;
if (!loopHead.Instructions[0].MatchIfInstruction(out var condition, out var trueInst))
return false;
var falseInst = loopHead.Instructions[1];
while (condition.MatchLogicNot(out var arg)) {
condition = arg;
ExtensionMethods.Swap(ref trueInst, ref falseInst);
}
if (!(condition is CallInstruction call && call.Method.Name == "MoveNext"))
return false;
if (!(call.Arguments.Count == 1 && call.Arguments[0].MatchLdLocRef(out var enumeratorVar)))
return false;
exitBranch = falseInst as Branch;
// Check that loopHead is entry-point of try-block:
Block entryPoint = container.EntryPoint;
while (entryPoint.IncomingEdgeCount == 1 && entryPoint.Instructions.Count == 1 && entryPoint.Instructions[0].MatchBranch(out var targetBlock)) {
// skip blocks that only branch to another block
entryPoint = targetBlock;
}
return entryPoint == loopHead;
}
#endregion
#region ExtendLoop (fall-back heuristic)
/// <summary>
/// This function implements a heuristic algorithm that tries to reduce the number of exit

2
ICSharpCode.Decompiler/IL/Instructions.cs

@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL @@ -27,7 +27,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Enum representing the type of an <see cref="ILInstruction"/>.
/// </summary>
public enum OpCode
public enum OpCode : byte
{
/// <summary>Represents invalid IL. Semantically, this instruction is considered to throw some kind of exception.</summary>
InvalidBranch,

2
ICSharpCode.Decompiler/IL/Instructions.tt

@ -271,7 +271,7 @@ namespace ICSharpCode.Decompiler.IL @@ -271,7 +271,7 @@ namespace ICSharpCode.Decompiler.IL
/// <summary>
/// Enum representing the type of an <see cref="ILInstruction"/>.
/// </summary>
public enum OpCode
public enum OpCode : byte
{
<# foreach (OpCode opCode in opCodes) { #>
/// <summary><#=opCode.Description.Replace("\n", "\n\t\t/// ")#></summary>

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

@ -37,10 +37,6 @@ namespace ICSharpCode.Decompiler.IL @@ -37,10 +37,6 @@ namespace ICSharpCode.Decompiler.IL
/// 1) Blocks in block containers. Used as targets for Branch instructions.
/// 2) Blocks to group a bunch of statements, e.g. the TrueInst of an IfInstruction.
/// 3) Inline blocks that evaluate to a value, e.g. for array initializers.
///
/// TODO: consider splitting inline blocks (with FinalInstruction) from those
/// used in containers for control flow purposes -- these are very different things
/// which should not share a class.
/// </summary>
partial class Block : ILInstruction
{

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

@ -126,7 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -126,7 +126,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
ILInstruction condition = ifInst.Condition;
while (condition.MatchLogicNot(out var arg)) {
condition = arg;
Swap(ref trueInst, ref falseInst);
ExtensionMethods.Swap(ref trueInst, ref falseInst);
}
if (AnalyzeCondition(condition)) {
// (v1 != null && ... && vn != null) ? trueInst : falseInst
@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -140,7 +140,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// for equality/inequality, the HasValue bits must also compare equal/inequal
if (comp.Kind == ComparisonKind.Inequality) {
// handle inequality by swapping one last time
Swap(ref trueInst, ref falseInst);
ExtensionMethods.Swap(ref trueInst, ref falseInst);
}
if (falseInst.MatchLdcI4(0)) {
// (a.GetValueOrDefault() == b.GetValueOrDefault()) ? (a.HasValue == b.HasValue) : false
@ -282,13 +282,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -282,13 +282,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
return MatchHasValueCall(arg, nullable1);
}
static void Swap<T>(ref T a, ref T b)
{
T tmp = a;
a = b;
b = tmp;
}
#endregion
#region CSharpComp

9
ICSharpCode.Decompiler/Util/ExtensionMethods.cs

@ -22,7 +22,7 @@ using System.Collections.Generic; @@ -22,7 +22,7 @@ using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Util
{
/// <summary>
/// Contains extension methods for use within NRefactory.
/// Contains extension methods for internal use within the decompiler.
/// </summary>
static class ExtensionMethods
{
@ -34,5 +34,12 @@ namespace ICSharpCode.Decompiler.Util @@ -34,5 +34,12 @@ namespace ICSharpCode.Decompiler.Util
return filter1;
return m => filter1(m) && filter2(m);
}
public static void Swap<T>(ref T a, ref T b)
{
T tmp = a;
a = b;
b = tmp;
}
}
}

Loading…
Cancel
Save