Browse Source

Merge pull request #2461 from icsharpcode/pattern-matching

Pattern matching
pull/2521/head
Siegfried Pammer 4 years ago committed by GitHub
parent
commit
1f92b44b3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
  2. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  3. 8
      ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
  4. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
  5. 2
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs
  6. 248
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  7. 6
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs
  8. 4
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  9. 42
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  10. 13
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  11. 2
      ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
  12. 10
      ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
  13. 1
      ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
  14. 21
      ICSharpCode.Decompiler/DecompilerSettings.cs
  15. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  16. 9
      ICSharpCode.Decompiler/IL/ILVariable.cs
  17. 16
      ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
  18. 54
      ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
  19. 51
      ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
  20. 10
      ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
  21. 1
      ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs
  22. 122
      ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
  23. 350
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs
  24. 55
      ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
  25. 117
      ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
  26. 4
      ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
  27. 2
      ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs
  28. 125
      ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs

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

@ -308,6 +308,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
if (flags.HasFlag(CompilerOptions.UseRoslyn2_10_0) if (flags.HasFlag(CompilerOptions.UseRoslyn2_10_0)
|| flags.HasFlag(CompilerOptions.UseRoslynLatest)) || flags.HasFlag(CompilerOptions.UseRoslynLatest))
{ {
preprocessorSymbols.Add("ROSLYN2");
preprocessorSymbols.Add("CS70"); preprocessorSymbols.Add("CS70");
preprocessorSymbols.Add("CS71"); preprocessorSymbols.Add("CS71");
preprocessorSymbols.Add("CS72"); preprocessorSymbols.Add("CS72");
@ -315,6 +316,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
} }
if (flags.HasFlag(CompilerOptions.UseRoslynLatest)) if (flags.HasFlag(CompilerOptions.UseRoslynLatest))
{ {
preprocessorSymbols.Add("ROSLYN3");
preprocessorSymbols.Add("CS73"); preprocessorSymbols.Add("CS73");
preprocessorSymbols.Add("CS80"); preprocessorSymbols.Add("CS80");
preprocessorSymbols.Add("VB16"); preprocessorSymbols.Add("VB16");

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

@ -108,6 +108,7 @@
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" /> <Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\DynamicTests.cs" /> <Compile Include="TestCases\Correctness\DynamicTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" /> <Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="TestCases\Pretty\PatternMatching.cs" />
<None Include="TestCases\Pretty\CovariantReturns.cs" /> <None Include="TestCases\Pretty\CovariantReturns.cs" />
<Compile Include="TestCases\VBPretty\VBPropertiesTest.cs" /> <Compile Include="TestCases\VBPretty\VBPropertiesTest.cs" />
<None Include="TestCases\ILPretty\Issue2260SwitchString.cs" /> <None Include="TestCases\ILPretty\Issue2260SwitchString.cs" />

8
ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs

@ -304,6 +304,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions); RunForLibrary(cscOptions: cscOptions);
} }
[Test]
public void PatternMatching([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions)
{
RunForLibrary(cscOptions: cscOptions);
}
[Test] [Test]
public void InitializerTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions) public void InitializerTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{ {
@ -480,7 +486,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test] [Test]
public void Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions) public void Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{ {
RunForLibrary(cscOptions: cscOptions); RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6));
} }
[Test] [Test]

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

@ -367,7 +367,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
Console.WriteLine(); Console.WriteLine();
} }
#if ROSLYN #if ROSLYN2
// Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b' // Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b'
if (!(a == b)) if (!(a == b))
{ {

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

@ -21,7 +21,7 @@ using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{ {
public class PatternMatching public class OutVariables
{ {
public static void OutVarInShortCircuit(Dictionary<int, string> d) public static void OutVarInShortCircuit(Dictionary<int, string> d)
{ {

248
ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs

@ -0,0 +1,248 @@
using System;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public class PatternMatching
{
public void SimpleTypePattern(object x)
{
if (x is string value)
{
Console.WriteLine(value);
}
}
public void TypePatternWithShortcircuit(object x)
{
Use(F() && x is string text && text.Contains("a"));
if (F() && x is string text2 && text2.Contains("a"))
{
Console.WriteLine(text2);
}
}
public void TypePatternWithShortcircuitAnd(object x)
{
if (x is string text && text.Contains("a"))
{
Console.WriteLine(text);
}
else
{
Console.WriteLine();
}
}
public void TypePatternWithShortcircuitOr(object x)
{
if (!(x is string text) || text.Contains("a"))
{
Console.WriteLine();
}
else
{
Console.WriteLine(text);
}
}
public void TypePatternWithShortcircuitOr2(object x)
{
if (F() || !(x is string value))
{
Console.WriteLine();
}
else
{
Console.WriteLine(value);
}
}
public void TypePatternValueTypesCondition(object x)
{
if (x is int num)
{
Console.WriteLine("Integer: " + num);
}
else
{
Console.WriteLine("else");
}
}
public void TypePatternValueTypesCondition2()
{
if (GetObject() is int num)
{
Console.WriteLine("Integer: " + num);
}
else
{
Console.WriteLine("else");
}
}
public void TypePatternValueTypesWithShortcircuitAnd(object x)
{
if (x is int num && num.GetHashCode() > 0)
{
Console.WriteLine("Positive integer: " + num);
}
else
{
Console.WriteLine("else");
}
}
public void TypePatternValueTypesWithShortcircuitOr(object x)
{
if (!(x is int value) || value.GetHashCode() > 0)
{
Console.WriteLine();
}
else
{
Console.WriteLine(value);
}
}
#if ROSLYN3 || OPT
// Roslyn 2.x generates a complex infeasible path in debug builds, which RemoveInfeasiblePathTransform
// currently cannot handle. Because this would increase the complexity of that transform, we ignore
// this case.
public void TypePatternValueTypesWithShortcircuitOr2(object x)
{
if (F() || !(x is int value))
{
Console.WriteLine();
}
else
{
Console.WriteLine(value);
}
}
#endif
public void TypePatternGenerics<T>(object x)
{
if (x is T val)
{
Console.WriteLine(val.GetType().FullName);
}
else
{
Console.WriteLine("not a " + typeof(T).FullName);
}
}
public void TypePatternGenericRefType<T>(object x) where T : class
{
if (x is T val)
{
Console.WriteLine(val.GetType().FullName);
}
else
{
Console.WriteLine("not a " + typeof(T).FullName);
}
}
public void TypePatternGenericValType<T>(object x) where T : struct
{
if (x is T val)
{
Console.WriteLine(val.GetType().FullName);
}
else
{
Console.WriteLine("not a " + typeof(T).FullName);
}
}
public void TypePatternValueTypesWithShortcircuitAndMultiUse(object x)
{
if (x is int num && num.GetHashCode() > 0 && num % 2 == 0)
{
Console.WriteLine("Positive integer: " + num);
}
else
{
Console.WriteLine("else");
}
}
public void TypePatternValueTypesWithShortcircuitAndMultiUse2(object x)
{
if ((x is int num && num.GetHashCode() > 0 && num % 2 == 0) || F())
{
Console.WriteLine("true");
}
else
{
Console.WriteLine("else");
}
}
public void TypePatternValueTypesWithShortcircuitAndMultiUse3(object x)
{
if (F() || (x is int num && num.GetHashCode() > 0 && num % 2 == 0))
{
Console.WriteLine("true");
}
else
{
Console.WriteLine("else");
}
}
public void TypePatternValueTypes()
{
Use(F() && GetObject() is int num && num.GetHashCode() > 0 && num % 2 == 0);
}
public static void NotTypePatternVariableUsedOutsideTrueBranch(object x)
{
string text = x as string;
if (text != null && text.Length > 5)
{
Console.WriteLine("pattern matches");
}
if (text != null && text.Length > 10)
{
Console.WriteLine("other use!");
}
}
public static void NotTypePatternBecauseVarIsNotDefAssignedInCaseOfFallthrough(object x)
{
#if OPT
string obj = x as string;
if (obj == null)
{
Console.WriteLine("pattern doesn't match");
}
Console.WriteLine(obj == null);
#else
string text = x as string;
if (text == null)
{
Console.WriteLine("pattern doesn't match");
}
Console.WriteLine(text == null);
#endif
}
private bool F()
{
return true;
}
private object GetObject()
{
throw new NotImplementedException();
}
private void Use(bool x)
{
}
}
}

6
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs

@ -320,15 +320,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
if (!F(1)) if (!F(1))
{ {
} }
if (F(2) && F(3))
{
}
if (F(4) || F(5)) if (F(4) || F(5))
{ {
} }
if (F(0) && F(1) && !F(2) && (F(3) || F(4)))
{
}
E(); E();
} }
#endif #endif

4
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -89,6 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(), new SplitVariables(),
new ILInlining(), new ILInlining(),
new InlineReturnTransform(), // must run before DetectPinnedRegions new InlineReturnTransform(), // must run before DetectPinnedRegions
new RemoveInfeasiblePathTransform(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new YieldReturnDecompiler(), // must run after inlining but before loop detection new YieldReturnDecompiler(), // must run after inlining but before loop detection
new AsyncAwaitDecompiler(), // must run after inlining but before loop detection new AsyncAwaitDecompiler(), // must run after inlining but before loop detection
@ -96,10 +97,10 @@ namespace ICSharpCode.Decompiler.CSharp
new DetectExitPoints(), new DetectExitPoints(),
new LdLocaDupInitObjTransform(), new LdLocaDupInitObjTransform(),
new EarlyExpressionTransforms(), new EarlyExpressionTransforms(),
new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements
// RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...) // RemoveDeadVariableInit must run after EarlyExpressionTransforms so that stobj(ldloca V, ...)
// is already collapsed into stloc(V, ...). // is already collapsed into stloc(V, ...).
new RemoveDeadVariableInit(), new RemoveDeadVariableInit(),
new SplitVariables(), // split variables once again, because the stobj(ldloca V, ...) may open up new replacements
new ControlFlowSimplification(), //split variables may enable new branch to leave inlining new ControlFlowSimplification(), //split variables may enable new branch to leave inlining
new DynamicCallSiteTransform(), new DynamicCallSiteTransform(),
new SwitchDetection(), new SwitchDetection(),
@ -119,6 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp
}, },
// re-run DetectExitPoints after loop detection // re-run DetectExitPoints after loop detection
new DetectExitPoints(), new DetectExitPoints(),
new PatternMatchingTransform(), // must run after LoopDetection and before ConditionDetection
new BlockILTransform { // per-block transforms new BlockILTransform { // per-block transforms
PostOrderTransforms = { PostOrderTransforms = {
new ConditionDetection(), new ConditionDetection(),

42
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4338,6 +4338,48 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context)
{
var left = Translate(inst.TestedOperand);
var right = TranslatePattern(inst);
return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right)
.WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.Boolean)))
.WithILInstruction(inst);
}
ExpressionWithILInstruction TranslatePattern(ILInstruction pattern)
{
switch (pattern)
{
case MatchInstruction matchInstruction:
if (!matchInstruction.CheckType)
throw new NotImplementedException();
if (matchInstruction.IsDeconstructCall)
throw new NotImplementedException();
if (matchInstruction.IsDeconstructTuple)
throw new NotImplementedException();
if (matchInstruction.SubPatterns.Any())
throw new NotImplementedException();
if (matchInstruction.HasDesignator)
{
SingleVariableDesignation designator = new SingleVariableDesignation { Identifier = matchInstruction.Variable.Name };
designator.AddAnnotation(new ILVariableResolveResult(matchInstruction.Variable));
return new DeclarationExpression {
Type = ConvertType(matchInstruction.Variable.Type),
Designation = designator
}.WithILInstruction(matchInstruction);
}
else
{
return new TypeReferenceExpression(ConvertType(matchInstruction.Variable.Type))
.WithILInstruction(matchInstruction);
}
default:
throw new NotImplementedException();
}
}
protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context) protected internal override TranslatedExpression VisitInvalidBranch(InvalidBranch inst, TranslationContext context)
{ {
string message = "Error"; string message = "Error";

13
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -822,6 +822,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
spacePolicy = policy.SpaceAroundShiftOperator; spacePolicy = policy.SpaceAroundShiftOperator;
break; break;
case BinaryOperatorType.NullCoalescing: case BinaryOperatorType.NullCoalescing:
case BinaryOperatorType.IsPattern:
spacePolicy = true; spacePolicy = true;
break; break;
case BinaryOperatorType.Range: case BinaryOperatorType.Range:
@ -831,7 +832,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }
Space(spacePolicy); Space(spacePolicy);
WriteToken(BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator)); TokenRole tokenRole = BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator);
if (tokenRole == BinaryOperatorExpression.IsKeywordRole)
{
WriteKeyword(tokenRole);
}
else
{
WriteToken(tokenRole);
}
Space(spacePolicy); Space(spacePolicy);
binaryOperatorExpression.Right.AcceptVisitor(this); binaryOperatorExpression.Right.AcceptVisitor(this);
EndNode(binaryOperatorExpression); EndNode(binaryOperatorExpression);
@ -2788,7 +2797,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation) public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation)
{ {
StartNode(singleVariableDesignation); StartNode(singleVariableDesignation);
writer.WriteIdentifier(singleVariableDesignation.IdentifierToken); WriteIdentifier(singleVariableDesignation.IdentifierToken);
EndNode(singleVariableDesignation); EndNode(singleVariableDesignation);
} }

2
ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs

@ -143,6 +143,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return PrecedenceLevel.ConditionalOr; return PrecedenceLevel.ConditionalOr;
case BinaryOperatorType.NullCoalescing: case BinaryOperatorType.NullCoalescing:
return PrecedenceLevel.NullCoalescing; return PrecedenceLevel.NullCoalescing;
case BinaryOperatorType.IsPattern:
return PrecedenceLevel.RelationalAndTypeTesting;
default: default:
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }

10
ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs

@ -25,7 +25,6 @@
// THE SOFTWARE. // THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
namespace ICSharpCode.Decompiler.CSharp.Syntax namespace ICSharpCode.Decompiler.CSharp.Syntax
@ -55,6 +54,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public readonly static TokenRole ShiftRightRole = new TokenRole(">>"); public readonly static TokenRole ShiftRightRole = new TokenRole(">>");
public readonly static TokenRole NullCoalescingRole = new TokenRole("??"); public readonly static TokenRole NullCoalescingRole = new TokenRole("??");
public readonly static TokenRole RangeRole = new TokenRole(".."); public readonly static TokenRole RangeRole = new TokenRole("..");
public readonly static TokenRole IsKeywordRole = IsExpression.IsKeywordRole;
public readonly static Role<Expression> LeftRole = new Role<Expression>("Left", Expression.Null); public readonly static Role<Expression> LeftRole = new Role<Expression>("Left", Expression.Null);
public readonly static Role<Expression> RightRole = new Role<Expression>("Right", Expression.Null); public readonly static Role<Expression> RightRole = new Role<Expression>("Right", Expression.Null);
@ -155,6 +155,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return NullCoalescingRole; return NullCoalescingRole;
case BinaryOperatorType.Range: case BinaryOperatorType.Range:
return RangeRole; return RangeRole;
case BinaryOperatorType.IsPattern:
return IsKeywordRole;
default: default:
throw new NotSupportedException("Invalid value for BinaryOperatorType"); throw new NotSupportedException("Invalid value for BinaryOperatorType");
} }
@ -265,6 +267,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
NullCoalescing, NullCoalescing,
/// <summary>left .. right</summary> /// <summary>left .. right</summary>
/// <remarks>left and right are optional = may be Expression.Null</remarks> /// <remarks>left and right are optional = may be Expression.Null</remarks>
Range Range,
/// <summary>left is right</summary>
/// <remarks>right must be a pattern</remarks>
IsPattern,
} }
} }

1
ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs

@ -355,6 +355,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
case VariableKind.ExceptionStackSlot: case VariableKind.ExceptionStackSlot:
case VariableKind.UsingLocal: case VariableKind.UsingLocal:
case VariableKind.ForeachLocal: case VariableKind.ForeachLocal:
case VariableKind.PatternLocal:
return false; return false;
default: default:
return true; return true;

21
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -104,6 +104,7 @@ namespace ICSharpCode.Decompiler
discards = false; discards = false;
localFunctions = false; localFunctions = false;
deconstruction = false; deconstruction = false;
patternMatching = false;
} }
if (languageVersion < CSharp.LanguageVersion.CSharp7_2) if (languageVersion < CSharp.LanguageVersion.CSharp7_2)
{ {
@ -159,7 +160,7 @@ namespace ICSharpCode.Decompiler
return CSharp.LanguageVersion.CSharp7_2; return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing // C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions if (outVariables || throwExpressions || tupleTypes || tupleConversions
|| discards || localFunctions) || discards || localFunctions || deconstruction || patternMatching)
return CSharp.LanguageVersion.CSharp7; return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers || stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers
@ -1466,6 +1467,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool patternMatching = true;
/// <summary>
/// Gets/Sets whether C# 7.0 pattern matching should be detected.
/// </summary>
[Category("C# 7.0 / VS 2017")]
[Description("DecompilerSettings.PatternMatching")]
public bool PatternMatching {
get { return patternMatching; }
set {
if (patternMatching != value)
{
patternMatching = value;
OnPropertyChanged();
}
}
}
bool staticLocalFunctions = true; bool staticLocalFunctions = true;
/// <summary> /// <summary>

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -98,6 +98,8 @@
<Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" /> <Compile Include="IL\ControlFlow\AwaitInFinallyTransform.cs" />
<Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" /> <Compile Include="IL\Transforms\IntroduceNativeIntTypeOnLocals.cs" />
<Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" /> <Compile Include="IL\Transforms\LdLocaDupInitObjTransform.cs" />
<Compile Include="IL\Transforms\PatternMatchingTransform.cs" />
<Compile Include="IL\Transforms\RemoveInfeasiblePathTransform.cs" />
<Compile Include="Metadata\ReferenceLoadInfo.cs" /> <Compile Include="Metadata\ReferenceLoadInfo.cs" />
<Compile Include="Semantics\OutVarResolveResult.cs" /> <Compile Include="Semantics\OutVarResolveResult.cs" />
<Compile Include="SingleFileBundle.cs" /> <Compile Include="SingleFileBundle.cs" />

9
ICSharpCode.Decompiler/IL/ILVariable.cs

@ -101,6 +101,7 @@ namespace ICSharpCode.Decompiler.IL
case VariableKind.ExceptionLocal: case VariableKind.ExceptionLocal:
case VariableKind.ForeachLocal: case VariableKind.ForeachLocal:
case VariableKind.UsingLocal: case VariableKind.UsingLocal:
case VariableKind.PatternLocal:
case VariableKind.PinnedLocal: case VariableKind.PinnedLocal:
case VariableKind.PinnedRegionLocal: case VariableKind.PinnedRegionLocal:
case VariableKind.DisplayClassLocal: case VariableKind.DisplayClassLocal:
@ -173,6 +174,7 @@ namespace ICSharpCode.Decompiler.IL
{ {
case VariableKind.Local: case VariableKind.Local:
case VariableKind.ForeachLocal: case VariableKind.ForeachLocal:
case VariableKind.PatternLocal:
case VariableKind.PinnedLocal: case VariableKind.PinnedLocal:
case VariableKind.PinnedRegionLocal: case VariableKind.PinnedRegionLocal:
case VariableKind.UsingLocal: case VariableKind.UsingLocal:
@ -372,6 +374,11 @@ namespace ICSharpCode.Decompiler.IL
/// </summary> /// </summary>
public IField? StateMachineField; public IField? StateMachineField;
/// <summary>
/// If enabled, remove dead stores to this variable as if the "Remove dead code" option is enabled.
/// </summary>
internal bool RemoveIfRedundant;
public ILVariable(VariableKind kind, IType type, int? index = null) public ILVariable(VariableKind kind, IType type, int? index = null)
{ {
if (type == null) if (type == null)
@ -536,6 +543,8 @@ namespace ICSharpCode.Decompiler.IL
return false; return false;
if (x.Kind == VariableKind.StackSlot || y.Kind == VariableKind.StackSlot) if (x.Kind == VariableKind.StackSlot || y.Kind == VariableKind.StackSlot)
return false; return false;
if (x.Kind == VariableKind.PatternLocal || y.Kind == VariableKind.PatternLocal)
return false;
if (!(x.Function == y.Function && x.Kind == y.Kind)) if (!(x.Function == y.Function && x.Kind == y.Kind))
return false; return false;
if (x.Index != null) if (x.Index != null)

16
ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs

@ -84,7 +84,7 @@ namespace ICSharpCode.Decompiler.IL
comp(deconstruct.result1(c) == 1), comp(deconstruct.result1(c) == 1),
match.type[D].deconstruct(d = deconstruct.result2(c)) { match.type[D].deconstruct(d = deconstruct.result2(c)) {
comp(deconstruct.result1(d) == 2), comp(deconstruct.result1(d) == 2),
comp(deconstruct.result2(d) == 2), comp(deconstruct.result2(d) == 3),
} }
} }
*/ */
@ -126,10 +126,15 @@ namespace ICSharpCode.Decompiler.IL
testedOperand = m.testedOperand; testedOperand = m.testedOperand;
return true; return true;
case Comp comp: case Comp comp:
testedOperand = comp.Left; if (comp.MatchLogicNot(out var operand))
return IsConstant(comp.Right); {
case ILInstruction logicNot when logicNot.MatchLogicNot(out var operand): return IsPatternMatch(operand, out testedOperand);
return IsPatternMatch(operand, out testedOperand); }
else
{
testedOperand = comp.Left;
return IsConstant(comp.Right);
}
default: default:
testedOperand = null; testedOperand = null;
return false; return false;
@ -145,6 +150,7 @@ namespace ICSharpCode.Decompiler.IL
OpCode.LdcI4 => true, OpCode.LdcI4 => true,
OpCode.LdcI8 => true, OpCode.LdcI8 => true,
OpCode.LdNull => true, OpCode.LdNull => true,
OpCode.LdStr => true,
_ => false _ => false
}; };
} }

54
ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs

@ -40,6 +40,60 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
protected internal override void VisitComp(Comp inst)
{
base.VisitComp(inst);
FixComparisonKindLdNull(inst, context);
}
internal static void FixComparisonKindLdNull(Comp inst, ILTransformContext context)
{
if (inst.IsLifted)
{
return;
}
if (inst.Right.MatchLdNull())
{
if (inst.Kind == ComparisonKind.GreaterThan)
{
context.Step("comp(left > ldnull) => comp(left != ldnull)", inst);
inst.Kind = ComparisonKind.Inequality;
}
else if (inst.Kind == ComparisonKind.LessThanOrEqual)
{
context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst);
inst.Kind = ComparisonKind.Equality;
}
}
else if (inst.Left.MatchLdNull())
{
if (inst.Kind == ComparisonKind.LessThan)
{
context.Step("comp(ldnull < right) => comp(ldnull != right)", inst);
inst.Kind = ComparisonKind.Inequality;
}
else if (inst.Kind == ComparisonKind.GreaterThanOrEqual)
{
context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst);
inst.Kind = ComparisonKind.Equality;
}
}
if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out var arg, out var type) && type.Kind == TypeKind.TypeParameter)
{
if (inst.Kind == ComparisonKind.Equality)
{
context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst);
inst.Left = arg;
}
if (inst.Kind == ComparisonKind.Inequality)
{
context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst);
inst.Left = arg;
}
}
}
protected internal override void VisitStObj(StObj inst) protected internal override void VisitStObj(StObj inst)
{ {
base.VisitStObj(inst); base.VisitStObj(inst);

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

@ -113,32 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
return; return;
} }
if (inst.Right.MatchLdNull()) EarlyExpressionTransforms.FixComparisonKindLdNull(inst, context);
{
if (inst.Kind == ComparisonKind.GreaterThan)
{
context.Step("comp(left > ldnull) => comp(left != ldnull)", inst);
inst.Kind = ComparisonKind.Inequality;
}
else if (inst.Kind == ComparisonKind.LessThanOrEqual)
{
context.Step("comp(left <= ldnull) => comp(left == ldnull)", inst);
inst.Kind = ComparisonKind.Equality;
}
}
else if (inst.Left.MatchLdNull())
{
if (inst.Kind == ComparisonKind.LessThan)
{
context.Step("comp(ldnull < right) => comp(ldnull != right)", inst);
inst.Kind = ComparisonKind.Inequality;
}
else if (inst.Kind == ComparisonKind.GreaterThanOrEqual)
{
context.Step("comp(ldnull >= right) => comp(ldnull == right)", inst);
inst.Kind = ComparisonKind.Equality;
}
}
var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend); var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend);
if (rightWithoutConv.MatchLdcI4(0) if (rightWithoutConv.MatchLdcI4(0)
@ -183,20 +158,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
inst.Right.AddILRange(rightWithoutConv); inst.Right.AddILRange(rightWithoutConv);
} }
} }
if (inst.Right.MatchLdNull() && inst.Left.MatchBox(out arg, out var type) && type.Kind == TypeKind.TypeParameter)
{
if (inst.Kind == ComparisonKind.Equality)
{
context.Step("comp(box T(..) == ldnull) -> comp(.. == ldnull)", inst);
inst.Left = arg;
}
if (inst.Kind == ComparisonKind.Inequality)
{
context.Step("comp(box T(..) != ldnull) -> comp(.. != ldnull)", inst);
inst.Left = arg;
}
}
} }
protected internal override void VisitConv(Conv inst) protected internal override void VisitConv(Conv inst)
@ -589,11 +550,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return; return;
} }
} }
if (MatchInstruction.IsPatternMatch(inst.Condition, out _)
&& inst.TrueInst.MatchLdcI4(1) && inst.FalseInst.MatchLdcI4(0))
{
context.Step("match(x) ? true : false -> match(x)", inst);
inst.Condition.AddILRange(inst);
inst.ReplaceWith(inst.Condition);
return;
}
} }
IfInstruction HandleConditionalOperator(IfInstruction inst) IfInstruction HandleConditionalOperator(IfInstruction inst)
{ {
// if (cond) stloc (A, V1) else stloc (A, V2) --> stloc (A, if (cond) V1 else V2) // if (cond) stloc A(V1) else stloc A(V2) --> stloc A(if (cond) V1 else V2)
Block trueInst = inst.TrueInst as Block; Block trueInst = inst.TrueInst as Block;
if (trueInst == null || trueInst.Instructions.Count != 1) if (trueInst == null || trueInst.Instructions.Count != 1)
return inst; return inst;

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

@ -589,8 +589,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
//case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot: //case OpCode.BinaryNumericInstruction when parent.SlotInfo == SwitchInstruction.ValueSlot:
case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot: case OpCode.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot:
return true; return true;
case OpCode.MatchInstruction when ((MatchInstruction)parent).IsDeconstructTuple: case OpCode.MatchInstruction:
return true; var match = (MatchInstruction)parent;
if (match.IsDeconstructTuple
|| (match.CheckType && match.Variable.Type.IsReferenceType != true))
{
return true;
}
break;
} }
// decide based on the top-level target instruction into which we are inlining: // decide based on the top-level target instruction into which we are inlining:
switch (next.OpCode) switch (next.OpCode)

1
ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs

@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.IL
{ {
if (variable.Kind != VariableKind.Local && if (variable.Kind != VariableKind.Local &&
variable.Kind != VariableKind.StackSlot && variable.Kind != VariableKind.StackSlot &&
variable.Kind != VariableKind.PatternLocal &&
variable.Kind != VariableKind.ForeachLocal && variable.Kind != VariableKind.ForeachLocal &&
variable.Kind != VariableKind.UsingLocal) variable.Kind != VariableKind.UsingLocal)
{ {

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

@ -137,6 +137,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
return false; return false;
} }
bool AnalyzeNegatedCondition(ILInstruction condition)
{
return condition.MatchLogicNot(out var arg) && AnalyzeCondition(arg);
}
#endregion #endregion
#region Main lifting logic #region Main lifting logic
@ -211,11 +216,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// => comp.lifted[C#](lhs, rhs) // => comp.lifted[C#](lhs, rhs)
return LiftCSharpComparison(comp, comp.Kind); return LiftCSharpComparison(comp, comp.Kind);
} }
else if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst)) if (trueInst.MatchLdcI4(0) && AnalyzeCondition(falseInst))
{ {
// comp(lhs, rhs) ? false : (v1 != null && ... && vn != null) // comp(lhs, rhs) ? false : (v1 != null && ... && vn != null)
return LiftCSharpComparison(comp, comp.Kind.Negate()); return LiftCSharpComparison(comp, comp.Kind.Negate());
} }
if (falseInst.MatchLdcI4(1) && AnalyzeNegatedCondition(trueInst))
{
// comp(lhs, rhs) ? !(v1 != null && ... && vn != null) : true
// => !comp.lifted[C#](lhs, rhs)
ILInstruction result = LiftCSharpComparison(comp, comp.Kind);
if (result == null)
return result;
return Comp.LogicNot(result);
}
if (trueInst.MatchLdcI4(1) && AnalyzeNegatedCondition(falseInst))
{
// comp(lhs, rhs) ? true : !(v1 != null && ... && vn != null)
ILInstruction result = LiftCSharpComparison(comp, comp.Kind.Negate());
if (result == null)
return result;
return Comp.LogicNot(result);
}
} }
} }
ILVariable v; ILVariable v;
@ -544,7 +566,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null; return null;
} }
Call LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst) ILInstruction LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst)
{ {
// User-defined equality operator: // User-defined equality operator:
// if (comp(call get_HasValue(ldloca nullable1) == call get_HasValue(ldloca nullable2))) // if (comp(call get_HasValue(ldloca nullable1) == call get_HasValue(ldloca nullable2)))
@ -576,11 +598,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null; return null;
if (!falseInst.MatchLdcI4(newComparisonKind == ComparisonKind.Equality ? 1 : 0)) if (!falseInst.MatchLdcI4(newComparisonKind == ComparisonKind.Equality ? 1 : 0))
return null; return null;
bool trueInstNegated = false;
while (trueInst.MatchLogicNot(out var arg))
{
trueInstNegated = !trueInstNegated;
trueInst = arg;
}
if (!(trueInst is Call call)) if (!(trueInst is Call call))
return null; return null;
if (!(call.Method.IsOperator && call.Arguments.Count == 2)) if (!(call.Method.IsOperator && call.Arguments.Count == 2))
return null; return null;
if (call.Method.Name != (newComparisonKind == ComparisonKind.Equality ? "op_Equality" : "op_Inequality")) bool expectEqualityOperator = (newComparisonKind == ComparisonKind.Equality) ^ trueInstNegated;
if (call.Method.Name != (expectEqualityOperator ? "op_Equality" : "op_Inequality"))
return null; return null;
var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method); var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
if (liftedOperator == null) if (liftedOperator == null)
@ -594,13 +623,66 @@ namespace ICSharpCode.Decompiler.IL.Transforms
) )
{ {
context.Step("NullableLiftingTransform: C# user-defined (in)equality comparison", nestedIfInst); context.Step("NullableLiftingTransform: C# user-defined (in)equality comparison", nestedIfInst);
return new Call(liftedOperator) { ILInstruction replacement = new Call(liftedOperator) {
Arguments = { left, right }, Arguments = { left, right },
ConstrainedTo = call.ConstrainedTo, ConstrainedTo = call.ConstrainedTo,
ILStackWasEmpty = call.ILStackWasEmpty, ILStackWasEmpty = call.ILStackWasEmpty,
IsTail = call.IsTail, IsTail = call.IsTail,
}.WithILRange(call); }.WithILRange(call);
if (trueInstNegated)
{
replacement = Comp.LogicNot(replacement);
}
return replacement;
}
return null;
}
ILInstruction LiftCSharpUserComparison(ILInstruction trueInst, ILInstruction falseInst)
{
// (v1 != null && ... && vn != null) ? trueInst : falseInst
bool trueInstNegated = false;
while (trueInst.MatchLogicNot(out var arg))
{
trueInstNegated = !trueInstNegated;
trueInst = arg;
}
if (trueInst is Call call && !call.IsLifted
&& CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method)
&& falseInst.MatchLdcI4((call.Method.Name == "op_Inequality") ^ trueInstNegated ? 1 : 0))
{
// (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0)
var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
if ((call.Method.Name == "op_Equality" || call.Method.Name == "op_Inequality") && nullableVars.Count != 1)
{
// Equality is special (returns true if both sides are null), only handle it
// in the normal code path if we're dealing with only a single nullable var
// (comparing nullable with non-nullable).
return null;
}
if (liftedOperator == null)
{
return null;
}
context.Step("Lift user-defined comparison operator", trueInst);
var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1],
call.Method.Parameters[0].Type, call.Method.Parameters[1].Type);
if (left != null && right != null && bits.All(0, nullableVars.Count))
{
ILInstruction result = new Call(liftedOperator) {
Arguments = { left, right },
ConstrainedTo = call.ConstrainedTo,
ILStackWasEmpty = call.ILStackWasEmpty,
IsTail = call.IsTail
}.WithILRange(call);
if (trueInstNegated)
{
result = Comp.LogicNot(result);
}
return result;
}
} }
return null; return null;
} }
#endregion #endregion
@ -641,35 +723,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType() UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType()
}; };
} }
else if (trueInst is Call call && !call.IsLifted ILInstruction result = LiftCSharpUserComparison(trueInst, falseInst);
&& CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method) if (result != null)
&& falseInst.MatchLdcI4(call.Method.Name == "op_Inequality" ? 1 : 0)) return result;
{
// (v1 != null && ... && vn != null) ? call op_LessThan(lhs, rhs) : ldc.i4(0)
var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
if ((call.Method.Name == "op_Equality" || call.Method.Name == "op_Inequality") && nullableVars.Count != 1)
{
// Equality is special (returns true if both sides are null), only handle it
// in the normal code path if we're dealing with only a single nullable var
// (comparing nullable with non-nullable).
liftedOperator = null;
}
if (liftedOperator != null)
{
context.Step("Lift user-defined comparison operator", trueInst);
var (left, right, bits) = DoLiftBinary(call.Arguments[0], call.Arguments[1],
call.Method.Parameters[0].Type, call.Method.Parameters[1].Type);
if (left != null && right != null && bits.All(0, nullableVars.Count))
{
return new Call(liftedOperator) {
Arguments = { left, right },
ConstrainedTo = call.ConstrainedTo,
ILStackWasEmpty = call.ILStackWasEmpty,
IsTail = call.IsTail
}.WithILRange(call);
}
}
}
} }
ILInstruction lifted; ILInstruction lifted;
if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0])) if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0]))

350
ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs

@ -0,0 +1,350 @@
// Copyright (c) 2021 Daniel Grunwald, 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.
#nullable enable
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ICSharpCode.Decompiler.IL.ControlFlow;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.IL.Transforms
{
class PatternMatchingTransform : IILTransform
{
void IILTransform.Run(ILFunction function, ILTransformContext context)
{
if (!context.Settings.PatternMatching)
return;
foreach (var container in function.Descendants.OfType<BlockContainer>())
{
ControlFlowGraph? cfg = null;
foreach (var block in container.Blocks)
{
if (PatternMatchValueTypes(block, container, context, ref cfg))
{
continue;
}
if (PatternMatchRefTypes(block, container, context, ref cfg))
{
continue;
}
}
}
}
/// Block {
/// ...
/// stloc V(isinst T(testedOperand))
/// if (comp.o(ldloc V == ldnull)) br falseBlock
/// br trueBlock
/// }
///
/// All other uses of V are in blocks dominated by trueBlock.
/// =>
/// Block {
/// ...
/// if (match.type[T].notnull(V = testedOperand)) br trueBlock
/// br falseBlock
/// }
///
/// - or -
///
/// Block {
/// stloc s(isinst T(testedOperand))
/// stloc v(ldloc s)
/// if (logic.not(comp.o(ldloc s != ldnull))) br falseBlock
/// br trueBlock
/// }
/// =>
/// Block {
/// ...
/// if (match.type[T].notnull(V = testedOperand)) br trueBlock
/// br falseBlock
/// }
///
/// All other uses of V are in blocks dominated by trueBlock.
private bool PatternMatchRefTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
{
if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
return false;
int pos = block.Instructions.Count - 3;
if (condition.MatchLdLoc(out var conditionVar))
{
// stloc conditionVar(comp.o(ldloc s == ldnull))
// if (logic.not(ldloc conditionVar)) br trueBlock
if (pos < 0)
return false;
if (!(conditionVar.IsSingleDefinition && conditionVar.LoadCount == 1
&& conditionVar.Kind == VariableKind.StackSlot))
{
return false;
}
if (!block.Instructions[pos].MatchStLoc(conditionVar, out condition))
return false;
pos--;
}
if (condition.MatchCompEqualsNull(out var loadInNullCheck))
{
ExtensionMethods.Swap(ref trueInst, ref falseInst);
}
else if (condition.MatchCompNotEqualsNull(out loadInNullCheck))
{
// do nothing
}
else
{
return false;
}
if (!loadInNullCheck.MatchLdLoc(out var s))
return false;
if (!s.IsSingleDefinition)
return false;
if (s.Kind is not (VariableKind.Local or VariableKind.StackSlot))
return false;
if (pos < 0)
return false;
// stloc V(isinst T(testedOperand))
ILInstruction storeToV = block.Instructions[pos];
if (!storeToV.MatchStLoc(out var v, out var value))
return false;
if (value.MatchLdLoc(s))
{
// stloc v(ldloc s)
pos--;
if (!block.Instructions[pos].MatchStLoc(s, out value))
return false;
if (v.Kind is not (VariableKind.Local or VariableKind.StackSlot))
return false;
if (s.LoadCount != 2)
return false;
}
else
{
if (v != s)
return false;
}
IType? unboxType;
if (value is UnboxAny unboxAny)
{
// stloc S(unbox.any T(isinst T(testedOperand)))
unboxType = unboxAny.Type;
value = unboxAny.Argument;
}
else
{
unboxType = null;
}
if (value is not IsInst { Argument: var testedOperand, Type: var type })
return false;
if (type.IsReferenceType != true)
return false;
if (!(unboxType == null || type.Equals(unboxType)))
return false;
if (!v.Type.Equals(type))
return false;
if (!CheckAllUsesDominatedBy(v, container, trueInst, storeToV, loadInNullCheck, context, ref cfg))
return false;
context.Step($"Type pattern matching {v.Name}", block);
// if (match.type[T].notnull(V = testedOperand)) br trueBlock
var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!;
ifInst.Condition = new MatchInstruction(v, testedOperand) {
CheckNotNull = true,
CheckType = true
}.WithILRange(ifInst.Condition);
ifInst.TrueInst = trueInst;
block.Instructions[block.Instructions.Count - 1] = falseInst;
block.Instructions.RemoveRange(pos, ifInst.ChildIndex - pos);
v.Kind = VariableKind.PatternLocal;
return true;
}
private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILInstruction trueInst,
ILInstruction storeToV, ILInstruction? loadInNullCheck, ILTransformContext context, ref ControlFlowGraph? cfg)
{
var targetBlock = trueInst as Block;
if (targetBlock == null && !trueInst.MatchBranch(out targetBlock))
{
return false;
}
if (targetBlock.Parent != container)
return false;
if (targetBlock.IncomingEdgeCount != 1)
return false;
cfg ??= new ControlFlowGraph(container, context.CancellationToken);
var targetBlockNode = cfg.GetNode(targetBlock);
var uses = v.LoadInstructions.Concat<ILInstruction>(v.AddressInstructions)
.Concat(v.StoreInstructions.Cast<ILInstruction>());
foreach (var use in uses)
{
if (use == storeToV || use == loadInNullCheck)
continue;
Block? found = null;
for (ILInstruction? current = use; current != null; current = current.Parent)
{
if (current.Parent == container)
{
found = (Block)current;
break;
}
}
if (found == null)
return false;
var node = cfg.GetNode(found);
if (!targetBlockNode.Dominates(node))
return false;
}
return true;
}
/// Block {
/// ...
/// [stloc temp(ldloc testedOperand)]
/// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock
/// br unboxBlock
/// }
///
/// Block unboxBlock (incoming: 1) {
/// stloc V(unbox.any T(ldloc temp))
/// ...
/// }
/// =>
/// Block {
/// ...
/// if (match.type[T].notnull(V = testedOperand)) br unboxBlock
/// br falseBlock
/// }
private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
{
if (!MatchIsInstBlock(block, out var type, out var testedOperand,
out var unboxBlock, out var falseBlock))
{
return false;
}
StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc;
if (tempStore == null || !tempStore.Value.MatchLdLoc(testedOperand.Variable))
{
tempStore = null;
}
if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var v, out var storeToV))
{
return false;
}
if (unboxOperand == testedOperand.Variable)
{
// do nothing
}
else if (unboxOperand == tempStore?.Variable)
{
if (!(tempStore.Variable.IsSingleDefinition && tempStore.Variable.LoadCount == 1))
return false;
}
else
{
return false;
}
if (!CheckAllUsesDominatedBy(v, container, unboxBlock, storeToV, null, context, ref cfg))
return false;
context.Step($"PatternMatching with {v.Name}", block);
var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!;
ifInst.Condition = new MatchInstruction(v, testedOperand) {
CheckNotNull = true,
CheckType = true
};
((Branch)ifInst.TrueInst).TargetBlock = unboxBlock;
((Branch)block.Instructions.Last()).TargetBlock = falseBlock;
unboxBlock.Instructions.RemoveAt(0);
if (unboxOperand == tempStore?.Variable)
{
block.Instructions.Remove(tempStore);
}
// HACK: condition detection uses StartILOffset of blocks to decide which branch of if-else
// should become the then-branch. Change the unboxBlock StartILOffset from an offset inside
// the pattern matching machinery to an offset belonging to an instruction in the then-block.
unboxBlock.SetILRange(unboxBlock.Instructions[0]);
v.Kind = VariableKind.PatternLocal;
return true;
}
/// ...
/// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock
/// br unboxBlock
private bool MatchIsInstBlock(Block block,
[NotNullWhen(true)] out IType? type,
[NotNullWhen(true)] out LdLoc? testedOperand,
[NotNullWhen(true)] out Block? unboxBlock,
[NotNullWhen(true)] out Block? falseBlock)
{
type = null;
testedOperand = null;
unboxBlock = null;
falseBlock = null;
if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
return false;
if (condition.MatchCompEqualsNull(out var arg))
{
ExtensionMethods.Swap(ref trueInst, ref falseInst);
}
else if (condition.MatchCompNotEqualsNull(out arg))
{
// do nothing
}
else
{
return false;
}
if (!arg.MatchIsInst(out arg, out type))
return false;
testedOperand = arg as LdLoc;
if (testedOperand == null)
return false;
return trueInst.MatchBranch(out unboxBlock) && falseInst.MatchBranch(out falseBlock)
&& unboxBlock.Parent == block.Parent && falseBlock.Parent == block.Parent;
}
/// Block unboxBlock (incoming: 1) {
/// stloc V(unbox.any T(ldloc testedOperand))
/// ...
/// }
private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedOperand,
[NotNullWhen(true)] out ILVariable? v, [NotNullWhen(true)] out ILInstruction? storeToV)
{
v = null;
storeToV = null;
testedOperand = null;
if (unboxBlock.IncomingEdgeCount != 1)
return false;
storeToV = unboxBlock.Instructions[0];
if (!storeToV.MatchStLoc(out v, out var value))
return false;
if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(out testedOperand)))
return false;
return true;
}
}
}

55
ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs

@ -42,41 +42,44 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler. // This is necessary to remove useless stores generated by some compilers, e.g., the F# compiler.
// In yield return + async, the C# compiler tends to store null/default(T) to variables // In yield return + async, the C# compiler tends to store null/default(T) to variables
// when the variable goes out of scope. // when the variable goes out of scope.
if (function.IsAsync || function.IsIterator || context.Settings.RemoveDeadStores) bool removeDeadStores = function.IsAsync || function.IsIterator || context.Settings.RemoveDeadStores;
var variableQueue = new Queue<ILVariable>(function.Variables);
while (variableQueue.Count > 0)
{ {
var variableQueue = new Queue<ILVariable>(function.Variables); var v = variableQueue.Dequeue();
while (variableQueue.Count > 0) if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
continue;
if (!(v.RemoveIfRedundant || removeDeadStores))
continue;
// Skip variables that are captured in a mcs yield state-machine
// loads of these will only be visible after DelegateConstruction step.
if (function.StateMachineCompiledWithMono && v.StateMachineField != null)
continue;
if (v.LoadCount != 0 || v.AddressCount != 0)
continue;
foreach (var stloc in v.StoreInstructions.OfType<StLoc>().ToArray())
{ {
var v = variableQueue.Dequeue(); if (stloc.Parent is Block block)
if (v.Kind != VariableKind.Local && v.Kind != VariableKind.StackSlot)
continue;
// Skip variables that are captured in a mcs yield state-machine
// loads of these will only be visible after DelegateConstruction step.
if (function.StateMachineCompiledWithMono && v.StateMachineField != null)
continue;
if (v.LoadCount != 0 || v.AddressCount != 0)
continue;
foreach (var stloc in v.StoreInstructions.OfType<StLoc>().ToArray())
{ {
if (stloc.Parent is Block block) context.Step($"Dead store to {v.Name}", stloc);
if (SemanticHelper.IsPure(stloc.Value.Flags))
{
block.Instructions.Remove(stloc);
}
else
{ {
if (SemanticHelper.IsPure(stloc.Value.Flags)) stloc.ReplaceWith(stloc.Value);
{ }
block.Instructions.Remove(stloc); if (stloc.Value is LdLoc ldloc)
} {
else variableQueue.Enqueue(ldloc.Variable);
{
stloc.ReplaceWith(stloc.Value);
}
if (stloc.Value is LdLoc ldloc)
{
variableQueue.Enqueue(ldloc.Variable);
}
} }
} }
} }
} }
// Try to infer IType of stack slots that are of StackType.Ref: // Try to infer IType of stack slots that are of StackType.Ref:
foreach (var v in function.Variables) foreach (var v in function.Variables)
{ {

117
ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs

@ -0,0 +1,117 @@
// Copyright (c) 2021 Daniel Grunwald, 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.
#nullable enable
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace ICSharpCode.Decompiler.IL.Transforms
{
/// <summary>
/// Block IL_0018 (incoming: *) {
/// stloc s(ldc.i4 1)
/// br IL_0019
/// }
///
/// Block IL_0019 (incoming: > 1) {
/// if (logic.not(ldloc s)) br IL_0027
/// br IL_001d
/// }
///
/// replace br IL_0019 with br IL_0027
/// </summary>
class RemoveInfeasiblePathTransform : IILTransform
{
void IILTransform.Run(ILFunction function, ILTransformContext context)
{
foreach (var container in function.Descendants.OfType<BlockContainer>())
{
bool changed = false;
foreach (var block in container.Blocks)
{
changed |= DoTransform(block, context);
}
if (changed)
{
container.SortBlocks(deleteUnreachableBlocks: true);
}
}
}
private bool DoTransform(Block block, ILTransformContext context)
{
if (!MatchBlock1(block, out var s, out int value, out var br))
return false;
if (!MatchBlock2(br.TargetBlock, s, value, out var exitInst))
return false;
context.Step("RemoveInfeasiblePath", br);
br.ReplaceWith(exitInst.Clone());
s.RemoveIfRedundant = true;
return true;
}
// Block IL_0018 (incoming: *) {
// stloc s(ldc.i4 1)
// br IL_0019
// }
private bool MatchBlock1(Block block, [NotNullWhen(true)] out ILVariable? variable, out int constantValue, [NotNullWhen(true)] out Branch? branch)
{
variable = null;
constantValue = 0;
branch = null;
if (block.Instructions.Count != 2)
return false;
if (block.Instructions[0] is not StLoc
{
Variable: { Kind: VariableKind.StackSlot } s,
Value: LdcI4 { Value: 0 or 1 } valueInst
})
{
return false;
}
if (block.Instructions[1] is not Branch br)
return false;
variable = s;
constantValue = valueInst.Value;
branch = br;
return true;
}
// Block IL_0019 (incoming: > 1) {
// if (logic.not(ldloc s)) br IL_0027
// br IL_001d
// }
bool MatchBlock2(Block block, ILVariable s, int constantValue, [NotNullWhen(true)] out ILInstruction? exitInst)
{
exitInst = null;
if (block.Instructions.Count != 2)
return false;
if (block.IncomingEdgeCount <= 1)
return false;
if (!block.MatchIfAtEndOfBlock(out var load, out var trueInst, out var falseInst))
return false;
if (!load.MatchLdLoc(s))
return false;
exitInst = constantValue != 0 ? trueInst : falseInst;
return exitInst is Branch or Leave { Value: Nop };
}
}
}

4
ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs

@ -64,7 +64,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case VariableKind.StackSlot: case VariableKind.StackSlot:
// stack slots: are already split by construction, // stack slots: are already split by construction,
// except for the locals-turned-stackslots in async functions // except for the locals-turned-stackslots in async functions
if (v.Function.IsAsync) // or stack slots handled by the infeasible path transform
if (v.Function.IsAsync || v.RemoveIfRedundant)
goto case VariableKind.Local; goto case VariableKind.Local;
else else
return false; return false;
@ -270,6 +271,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
v.HasGeneratedName = inst.Variable.HasGeneratedName; v.HasGeneratedName = inst.Variable.HasGeneratedName;
v.StateMachineField = inst.Variable.StateMachineField; v.StateMachineField = inst.Variable.StateMachineField;
v.HasInitialValue = false; // we'll set HasInitialValue when we encounter an uninit load v.HasInitialValue = false; // we'll set HasInitialValue when we encounter an uninit load
v.RemoveIfRedundant = inst.Variable.RemoveIfRedundant;
newVariables.Add(representative, v); newVariables.Add(representative, v);
inst.Variable.Function.Variables.Add(v); inst.Variable.Function.Variables.Add(v);
} }

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

@ -16,6 +16,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
#nullable enable
using System; using System;
using System.Diagnostics; using System.Diagnostics;

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

@ -37,12 +37,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
this.context = context; this.context = context;
for (int i = block.Instructions.Count - 1; i >= 0; i--) for (int i = block.Instructions.Count - 1; i >= 0; i--)
{ {
if (!TransformUsing(block, i) && !TransformUsingVB(block, i) && !TransformAsyncUsing(block, i)) if (TransformUsing(block, i))
{
continue;
}
if (TransformUsingVB(block, i))
{
continue;
}
if (TransformAsyncUsing(block, i))
{
continue; continue;
// This happens in some cases: }
// Use correct index after transformation.
if (i >= block.Instructions.Count)
i = block.Instructions.Count;
} }
} }
@ -74,9 +80,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
bool TransformUsing(Block block, int i) bool TransformUsing(Block block, int i)
{ {
if (i < 1) if (i + 1 >= block.Instructions.Count)
return false; return false;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) if (!(block.Instructions[i + 1] is TryFinally tryFinally) || !(block.Instructions[i] is StLoc storeInst))
return false; return false;
if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type))) if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type)))
return false; return false;
@ -88,12 +94,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false; return false;
if (storeInst.Variable.StoreInstructions.Count > 1) if (storeInst.Variable.StoreInstructions.Count > 1)
return false; return false;
if (!(tryFinally.FinallyBlock is BlockContainer container) || !MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull())) if (!(tryFinally.FinallyBlock is BlockContainer container))
return false;
if (!MatchDisposeBlock(container, storeInst.Variable, storeInst.Value.MatchLdNull()))
return false; return false;
context.Step("UsingTransform", tryFinally); context.Step("UsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal; storeInst.Variable.Kind = VariableKind.UsingLocal;
block.Instructions.RemoveAt(i); block.Instructions.RemoveAt(i + 1);
block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) { block.Instructions[i] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) {
IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike IsRefStruct = context.Settings.IntroduceRefModifiersOnStructs && storeInst.Variable.Type.Kind == TypeKind.Struct && storeInst.Variable.Type.IsByRefLike
}.WithILRange(storeInst); }.WithILRange(storeInst);
return true; return true;
@ -127,6 +135,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
bool TransformUsingVB(Block block, int i) bool TransformUsingVB(Block block, int i)
{ {
if (i >= block.Instructions.Count)
return false;
if (!(block.Instructions[i] is TryFinally tryFinally)) if (!(block.Instructions[i] is TryFinally tryFinally))
return false; return false;
if (!(tryFinally.TryBlock is BlockContainer tryContainer && tryContainer.EntryPoint.Instructions.FirstOrDefault() is StLoc storeInst)) if (!(tryFinally.TryBlock is BlockContainer tryContainer && tryContainer.EntryPoint.Instructions.FirstOrDefault() is StLoc storeInst))
@ -175,41 +185,64 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return true; return true;
} }
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull, in string disposeMethodFullName = "System.IDisposable.Dispose", KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable) /// finally BlockContainer {
/// Block IL_0012(incoming: 1) {
/// if (comp(ldloc obj != ldnull)) Block IL_001a {
/// callvirt Dispose(obj)
/// }
/// leave IL_0012(nop)
/// }
/// }
bool MatchDisposeBlock(BlockContainer container, ILVariable objVar, bool usingNull,
in string disposeMethodFullName = "System.IDisposable.Dispose",
KnownTypeCode disposeTypeCode = KnownTypeCode.IDisposable)
{ {
var entryPoint = container.EntryPoint; var entryPoint = container.EntryPoint;
if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1) if (entryPoint.IncomingEdgeCount != 1)
return false; return false;
int leaveIndex = entryPoint.Instructions.Count == 2 ? 1 : 2; int pos = 0;
int checkIndex = entryPoint.Instructions.Count == 2 ? 0 : 1;
int castIndex = entryPoint.Instructions.Count == 3 ? 0 : -1;
var checkInst = entryPoint.Instructions[checkIndex];
bool isReference = objVar.Type.IsReferenceType != false; bool isReference = objVar.Type.IsReferenceType != false;
if (castIndex > -1) // optional:
// stloc temp(isinst TDisposable(ldloc obj))
if (entryPoint.Instructions.ElementAtOrDefault(pos).MatchStLoc(out var tempVar, out var isinst))
{ {
if (!entryPoint.Instructions[castIndex].MatchStLoc(out var tempVar, out var isinst)) if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar)
return false; || !disposableType.IsKnownType(disposeTypeCode))
if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(disposeTypeCode)) {
return false; return false;
}
if (!tempVar.IsSingleDefinition) if (!tempVar.IsSingleDefinition)
return false; return false;
isReference = true; isReference = true;
if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode)) pos++;
return false; objVar = tempVar;
if (tempVar.LoadCount != numObjVarLoadsInCheck)
return false;
} }
else // if (comp(ldloc obj != ldnull)) Block IL_001a {
// callvirt Dispose(obj)
// }
var checkInst = entryPoint.Instructions.ElementAtOrDefault(pos);
if (checkInst == null)
return false;
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull,
out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode))
{ {
if (!MatchDisposeCheck(objVar, checkInst, isReference, usingNull, out _, disposeMethodFullName, disposeTypeCode)) return false;
return false;
} }
if (!entryPoint.Instructions[leaveIndex].MatchLeave(container, out var returnValue) || !returnValue.MatchNop()) // make sure the (optional) temporary is used only in the dispose check
if (pos > 0 && objVar.LoadCount != numObjVarLoadsInCheck)
return false; return false;
return true; pos++;
// make sure, the finally ends in a leave(nop) instruction.
if (!entryPoint.Instructions.ElementAtOrDefault(pos).MatchLeave(container, out var returnValue))
return false;
if (!returnValue.MatchNop())
return false;
// leave is the last instruction
return (pos + 1) == entryPoint.Instructions.Count;
} }
bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull, out int numObjVarLoadsInCheck, string disposeMethodFullName, KnownTypeCode disposeTypeCode) bool MatchDisposeCheck(ILVariable objVar, ILInstruction checkInst, bool isReference, bool usingNull,
out int numObjVarLoadsInCheck, string disposeMethodFullName, KnownTypeCode disposeTypeCode)
{ {
numObjVarLoadsInCheck = 2; numObjVarLoadsInCheck = 2;
ILInstruction disposeInvocation; ILInstruction disposeInvocation;
@ -283,7 +316,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// reference types have a null check. // reference types have a null check.
if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst)) if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst))
return false; return false;
if (!condition.MatchCompNotEquals(out var left, out var right) || !left.MatchLdLoc(objVar) || !right.MatchLdNull()) if (!MatchNullCheckOrTypeCheck(condition, ref objVar, disposeTypeCode))
return false; return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1) if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
return false; return false;
@ -348,6 +381,32 @@ namespace ICSharpCode.Decompiler.IL.Transforms
} }
} }
bool MatchNullCheckOrTypeCheck(ILInstruction condition, ref ILVariable objVar, KnownTypeCode disposeType)
{
if (condition.MatchCompNotEquals(out var left, out var right))
{
if (!left.MatchLdLoc(objVar) || !right.MatchLdNull())
return false;
return true;
}
if (condition is MatchInstruction
{
CheckNotNull: true,
CheckType: true,
TestedOperand: LdLoc { Variable: var v },
Variable: var newObjVar
})
{
if (v != objVar)
return false;
if (!newObjVar.Type.IsKnownType(disposeType))
return false;
objVar = newObjVar;
return true;
}
return false;
}
/// <summary> /// <summary>
/// stloc test(resourceExpression) /// stloc test(resourceExpression)
/// .try BlockContainer { /// .try BlockContainer {
@ -371,7 +430,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// </summary> /// </summary>
private bool TransformAsyncUsing(Block block, int i) private bool TransformAsyncUsing(Block block, int i)
{ {
if (i < 1 || !context.Settings.AsyncUsingAndForEachStatement) if (!context.Settings.AsyncUsingAndForEachStatement)
return false;
if (i < 1 || i >= block.Instructions.Count)
return false; return false;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst)) if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst))
return false; return false;

Loading…
Cancel
Save