diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
index 150f540bd..eea0ebd0c 100644
--- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
+++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs
@@ -308,6 +308,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
if (flags.HasFlag(CompilerOptions.UseRoslyn2_10_0)
|| flags.HasFlag(CompilerOptions.UseRoslynLatest))
{
+ preprocessorSymbols.Add("ROSLYN2");
preprocessorSymbols.Add("CS70");
preprocessorSymbols.Add("CS71");
preprocessorSymbols.Add("CS72");
@@ -315,6 +316,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers
}
if (flags.HasFlag(CompilerOptions.UseRoslynLatest))
{
+ preprocessorSymbols.Add("ROSLYN3");
preprocessorSymbols.Add("CS73");
preprocessorSymbols.Add("CS80");
preprocessorSymbols.Add("VB16");
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index d043cccf5..1e4e1bf2e 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -108,6 +108,7 @@
+
diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
index 30ee7b18d..e8984124b 100644
--- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
+++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs
@@ -304,6 +304,12 @@ namespace ICSharpCode.Decompiler.Tests
RunForLibrary(cscOptions: cscOptions);
}
+ [Test]
+ public void PatternMatching([ValueSource(nameof(roslyn2OrNewerOptions))] CompilerOptions cscOptions)
+ {
+ RunForLibrary(cscOptions: cscOptions);
+ }
+
[Test]
public void InitializerTests([ValueSource(nameof(defaultOptions))] CompilerOptions cscOptions)
{
@@ -480,7 +486,7 @@ namespace ICSharpCode.Decompiler.Tests
[Test]
public void Issue1080([ValueSource(nameof(roslynOnlyOptions))] CompilerOptions cscOptions)
{
- RunForLibrary(cscOptions: cscOptions);
+ RunForLibrary(cscOptions: cscOptions, decompilerSettings: new DecompilerSettings(CSharp.LanguageVersion.CSharp6));
}
[Test]
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
index 56f9c2729..4eacfacde 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/LiftedOperators.cs
@@ -367,7 +367,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
Console.WriteLine();
}
-#if ROSLYN
+#if ROSLYN2
// Roslyn 2.9 started invoking op_Equality even if the source code says 'a != b'
if (!(a == b))
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs
index cb884bf78..bdfc8cbfa 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/OutVariables.cs
@@ -21,7 +21,7 @@ using System.Collections.Generic;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
- public class PatternMatching
+ public class OutVariables
{
public static void OutVarInShortCircuit(Dictionary d)
{
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
new file mode 100644
index 000000000..064db32bc
--- /dev/null
+++ b/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(object x)
+ {
+ if (x is T val)
+ {
+ Console.WriteLine(val.GetType().FullName);
+ }
+ else
+ {
+ Console.WriteLine("not a " + typeof(T).FullName);
+ }
+ }
+
+ public void TypePatternGenericRefType(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(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)
+ {
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs
index f753d91c3..4e7f7e3b1 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ShortCircuit.cs
@@ -320,15 +320,9 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
if (!F(1))
{
}
- if (F(2) && F(3))
- {
- }
if (F(4) || F(5))
{
}
- if (F(0) && F(1) && !F(2) && (F(3) || F(4)))
- {
- }
E();
}
#endif
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 93700561c..42144c5e6 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -89,6 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp
new SplitVariables(),
new ILInlining(),
new InlineReturnTransform(), // must run before DetectPinnedRegions
+ new RemoveInfeasiblePathTransform(),
new DetectPinnedRegions(), // must run after inlining but before non-critical control flow transforms
new YieldReturnDecompiler(), // 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 LdLocaDupInitObjTransform(),
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, ...)
// is already collapsed into stloc(V, ...).
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 DynamicCallSiteTransform(),
new SwitchDetection(),
@@ -119,6 +120,7 @@ namespace ICSharpCode.Decompiler.CSharp
},
// re-run DetectExitPoints after loop detection
new DetectExitPoints(),
+ new PatternMatchingTransform(), // must run after LoopDetection and before ConditionDetection
new BlockILTransform { // per-block transforms
PostOrderTransforms = {
new ConditionDetection(),
diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
index 94d67b7c0..264361f63 100644
--- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
+++ b/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)
{
string message = "Error";
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 987677f7a..c7766b1e7 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -822,6 +822,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
spacePolicy = policy.SpaceAroundShiftOperator;
break;
case BinaryOperatorType.NullCoalescing:
+ case BinaryOperatorType.IsPattern:
spacePolicy = true;
break;
case BinaryOperatorType.Range:
@@ -831,7 +832,15 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
Space(spacePolicy);
- WriteToken(BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator));
+ TokenRole tokenRole = BinaryOperatorExpression.GetOperatorRole(binaryOperatorExpression.Operator);
+ if (tokenRole == BinaryOperatorExpression.IsKeywordRole)
+ {
+ WriteKeyword(tokenRole);
+ }
+ else
+ {
+ WriteToken(tokenRole);
+ }
Space(spacePolicy);
binaryOperatorExpression.Right.AcceptVisitor(this);
EndNode(binaryOperatorExpression);
@@ -2788,7 +2797,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
public virtual void VisitSingleVariableDesignation(SingleVariableDesignation singleVariableDesignation)
{
StartNode(singleVariableDesignation);
- writer.WriteIdentifier(singleVariableDesignation.IdentifierToken);
+ WriteIdentifier(singleVariableDesignation.IdentifierToken);
EndNode(singleVariableDesignation);
}
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
index 4b706e127..e279cf021 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/InsertParenthesesVisitor.cs
@@ -143,6 +143,8 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
return PrecedenceLevel.ConditionalOr;
case BinaryOperatorType.NullCoalescing:
return PrecedenceLevel.NullCoalescing;
+ case BinaryOperatorType.IsPattern:
+ return PrecedenceLevel.RelationalAndTypeTesting;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
index 579d92e53..f64752e06 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/BinaryOperatorExpression.cs
@@ -25,7 +25,6 @@
// THE SOFTWARE.
using System;
-using System.Collections.Generic;
using System.Linq.Expressions;
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 NullCoalescingRole = new TokenRole("??");
public readonly static TokenRole RangeRole = new TokenRole("..");
+ public readonly static TokenRole IsKeywordRole = IsExpression.IsKeywordRole;
public readonly static Role LeftRole = new Role("Left", Expression.Null);
public readonly static Role RightRole = new Role("Right", Expression.Null);
@@ -155,6 +155,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return NullCoalescingRole;
case BinaryOperatorType.Range:
return RangeRole;
+ case BinaryOperatorType.IsPattern:
+ return IsKeywordRole;
default:
throw new NotSupportedException("Invalid value for BinaryOperatorType");
}
@@ -265,6 +267,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
NullCoalescing,
/// left .. right
/// left and right are optional = may be Expression.Null
- Range
+ Range,
+
+ /// left is right
+ /// right must be a pattern
+ IsPattern,
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
index 5964caf42..510821104 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/DeclareVariables.cs
@@ -355,6 +355,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
case VariableKind.ExceptionStackSlot:
case VariableKind.UsingLocal:
case VariableKind.ForeachLocal:
+ case VariableKind.PatternLocal:
return false;
default:
return true;
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index b456b3203..fc3797092 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -104,6 +104,7 @@ namespace ICSharpCode.Decompiler
discards = false;
localFunctions = false;
deconstruction = false;
+ patternMatching = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp7_2)
{
@@ -159,7 +160,7 @@ namespace ICSharpCode.Decompiler
return CSharp.LanguageVersion.CSharp7_2;
// C# 7.1 missing
if (outVariables || throwExpressions || tupleTypes || tupleConversions
- || discards || localFunctions)
+ || discards || localFunctions || deconstruction || patternMatching)
return CSharp.LanguageVersion.CSharp7;
if (awaitInCatchFinally || useExpressionBodyForCalculatedGetterOnlyProperties || nullPropagation
|| stringInterpolation || dictionaryInitializers || extensionMethodsInCollectionInitializers
@@ -1466,6 +1467,24 @@ namespace ICSharpCode.Decompiler
}
}
+ bool patternMatching = true;
+
+ ///
+ /// Gets/Sets whether C# 7.0 pattern matching should be detected.
+ ///
+ [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;
///
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index fc8b7b9df..7ebd56f7c 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -98,6 +98,8 @@
+
+
diff --git a/ICSharpCode.Decompiler/IL/ILVariable.cs b/ICSharpCode.Decompiler/IL/ILVariable.cs
index 07dee70ef..f70707a36 100644
--- a/ICSharpCode.Decompiler/IL/ILVariable.cs
+++ b/ICSharpCode.Decompiler/IL/ILVariable.cs
@@ -101,6 +101,7 @@ namespace ICSharpCode.Decompiler.IL
case VariableKind.ExceptionLocal:
case VariableKind.ForeachLocal:
case VariableKind.UsingLocal:
+ case VariableKind.PatternLocal:
case VariableKind.PinnedLocal:
case VariableKind.PinnedRegionLocal:
case VariableKind.DisplayClassLocal:
@@ -173,6 +174,7 @@ namespace ICSharpCode.Decompiler.IL
{
case VariableKind.Local:
case VariableKind.ForeachLocal:
+ case VariableKind.PatternLocal:
case VariableKind.PinnedLocal:
case VariableKind.PinnedRegionLocal:
case VariableKind.UsingLocal:
@@ -372,6 +374,11 @@ namespace ICSharpCode.Decompiler.IL
///
public IField? StateMachineField;
+ ///
+ /// If enabled, remove dead stores to this variable as if the "Remove dead code" option is enabled.
+ ///
+ internal bool RemoveIfRedundant;
+
public ILVariable(VariableKind kind, IType type, int? index = null)
{
if (type == null)
@@ -536,6 +543,8 @@ namespace ICSharpCode.Decompiler.IL
return false;
if (x.Kind == VariableKind.StackSlot || y.Kind == VariableKind.StackSlot)
return false;
+ if (x.Kind == VariableKind.PatternLocal || y.Kind == VariableKind.PatternLocal)
+ return false;
if (!(x.Function == y.Function && x.Kind == y.Kind))
return false;
if (x.Index != null)
diff --git a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
index e5833cd16..6948c79f3 100644
--- a/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
+++ b/ICSharpCode.Decompiler/IL/Instructions/MatchInstruction.cs
@@ -84,7 +84,7 @@ namespace ICSharpCode.Decompiler.IL
comp(deconstruct.result1(c) == 1),
match.type[D].deconstruct(d = deconstruct.result2(c)) {
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;
return true;
case Comp comp:
- testedOperand = comp.Left;
- return IsConstant(comp.Right);
- case ILInstruction logicNot when logicNot.MatchLogicNot(out var operand):
- return IsPatternMatch(operand, out testedOperand);
+ if (comp.MatchLogicNot(out var operand))
+ {
+ return IsPatternMatch(operand, out testedOperand);
+ }
+ else
+ {
+ testedOperand = comp.Left;
+ return IsConstant(comp.Right);
+ }
default:
testedOperand = null;
return false;
@@ -145,6 +150,7 @@ namespace ICSharpCode.Decompiler.IL
OpCode.LdcI4 => true,
OpCode.LdcI8 => true,
OpCode.LdNull => true,
+ OpCode.LdStr => true,
_ => false
};
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
index a4fb74a3b..b0b4dfd02 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/EarlyExpressionTransforms.cs
+++ b/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)
{
base.VisitStObj(inst);
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
index 827b902a3..f9fa9a7b8 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ExpressionTransforms.cs
@@ -113,32 +113,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{
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;
- }
- }
+ EarlyExpressionTransforms.FixComparisonKindLdNull(inst, context);
var rightWithoutConv = inst.Right.UnwrapConv(ConversionKind.SignExtend).UnwrapConv(ConversionKind.ZeroExtend);
if (rightWithoutConv.MatchLdcI4(0)
@@ -183,20 +158,6 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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)
@@ -589,11 +550,19 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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)
{
- // 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;
if (trueInst == null || trueInst.Instructions.Count != 1)
return inst;
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs b/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
index c06741edb..0db949e94 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ILInlining.cs
+++ b/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.StringToInt when parent.SlotInfo == SwitchInstruction.ValueSlot:
return true;
- case OpCode.MatchInstruction when ((MatchInstruction)parent).IsDeconstructTuple:
- return true;
+ case OpCode.MatchInstruction:
+ 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:
switch (next.OpCode)
diff --git a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs
index aa78ecedd..910a4726a 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/IntroduceNativeIntTypeOnLocals.cs
@@ -36,6 +36,7 @@ namespace ICSharpCode.Decompiler.IL
{
if (variable.Kind != VariableKind.Local &&
variable.Kind != VariableKind.StackSlot &&
+ variable.Kind != VariableKind.PatternLocal &&
variable.Kind != VariableKind.ForeachLocal &&
variable.Kind != VariableKind.UsingLocal)
{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
index 45429d845..44f73e3e4 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/NullableLiftingTransform.cs
@@ -137,6 +137,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
}
return false;
}
+
+ bool AnalyzeNegatedCondition(ILInstruction condition)
+ {
+ return condition.MatchLogicNot(out var arg) && AnalyzeCondition(arg);
+ }
#endregion
#region Main lifting logic
@@ -211,11 +216,28 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// => comp.lifted[C#](lhs, rhs)
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)
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;
@@ -544,7 +566,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
}
- Call LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst)
+ ILInstruction LiftCSharpUserEqualityComparison(CompOrDecimal hasValueComp, ComparisonKind newComparisonKind, ILInstruction nestedIfInst)
{
// User-defined equality operator:
// if (comp(call get_HasValue(ldloca nullable1) == call get_HasValue(ldloca nullable2)))
@@ -576,11 +598,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return null;
if (!falseInst.MatchLdcI4(newComparisonKind == ComparisonKind.Equality ? 1 : 0))
return null;
+ bool trueInstNegated = false;
+ while (trueInst.MatchLogicNot(out var arg))
+ {
+ trueInstNegated = !trueInstNegated;
+ trueInst = arg;
+ }
if (!(trueInst is Call call))
return null;
if (!(call.Method.IsOperator && call.Arguments.Count == 2))
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;
var liftedOperator = CSharp.Resolver.CSharpOperators.LiftUserDefinedOperator(call.Method);
if (liftedOperator == null)
@@ -594,13 +623,66 @@ namespace ICSharpCode.Decompiler.IL.Transforms
)
{
context.Step("NullableLiftingTransform: C# user-defined (in)equality comparison", nestedIfInst);
- return new Call(liftedOperator) {
+ ILInstruction replacement = new Call(liftedOperator) {
Arguments = { left, right },
ConstrainedTo = call.ConstrainedTo,
ILStackWasEmpty = call.ILStackWasEmpty,
IsTail = call.IsTail,
}.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;
}
#endregion
@@ -641,35 +723,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
UnderlyingResultType = NullableType.GetUnderlyingType(nullableVars[0].Type).GetStackType()
};
}
- else if (trueInst is Call call && !call.IsLifted
- && CSharp.Resolver.CSharpOperators.IsComparisonOperator(call.Method)
- && falseInst.MatchLdcI4(call.Method.Name == "op_Inequality" ? 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).
- 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 result = LiftCSharpUserComparison(trueInst, falseInst);
+ if (result != null)
+ return result;
}
ILInstruction lifted;
if (nullableVars.Count == 1 && MatchGetValueOrDefault(exprToLift, nullableVars[0]))
diff --git a/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs
new file mode 100644
index 000000000..6eb2863f5
--- /dev/null
+++ b/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())
+ {
+ 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(v.AddressInstructions)
+ .Concat(v.StoreInstructions.Cast());
+ 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;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
index 6a95e4c3d..284914cfa 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/RemoveDeadVariableInit.cs
+++ b/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.
// In yield return + async, the C# compiler tends to store null/default(T) to variables
// 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(function.Variables);
+ while (variableQueue.Count > 0)
{
- var variableQueue = new Queue(function.Variables);
- while (variableQueue.Count > 0)
+ var v = variableQueue.Dequeue();
+ 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().ToArray())
{
- var v = variableQueue.Dequeue();
- 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().ToArray())
+ if (stloc.Parent is Block block)
{
- 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))
- {
- block.Instructions.Remove(stloc);
- }
- else
- {
- stloc.ReplaceWith(stloc.Value);
- }
- if (stloc.Value is LdLoc ldloc)
- {
- 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:
foreach (var v in function.Variables)
{
diff --git a/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/RemoveInfeasiblePathTransform.cs
new file mode 100644
index 000000000..3f4cbbefb
--- /dev/null
+++ b/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
+{
+ ///
+ /// 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
+ ///
+ class RemoveInfeasiblePathTransform : IILTransform
+ {
+ void IILTransform.Run(ILFunction function, ILTransformContext context)
+ {
+ foreach (var container in function.Descendants.OfType())
+ {
+ 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 };
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
index 621450a17..9ef159530 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/SplitVariables.cs
@@ -64,7 +64,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
case VariableKind.StackSlot:
// stack slots: are already split by construction,
// 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;
else
return false;
@@ -270,6 +271,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
v.HasGeneratedName = inst.Variable.HasGeneratedName;
v.StateMachineField = inst.Variable.StateMachineField;
v.HasInitialValue = false; // we'll set HasInitialValue when we encounter an uninit load
+ v.RemoveIfRedundant = inst.Variable.RemoveIfRedundant;
newVariables.Add(representative, v);
inst.Variable.Function.Variables.Add(v);
}
diff --git a/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs
index d19643ca6..51911595d 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/StatementTransform.cs
+++ b/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
// DEALINGS IN THE SOFTWARE.
+#nullable enable
+
using System;
using System.Diagnostics;
diff --git a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
index 53795a1d3..e59257f78 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/UsingTransform.cs
@@ -37,12 +37,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
this.context = context;
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;
- // 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
///
bool TransformUsing(Block block, int i)
{
- if (i < 1)
+ if (i + 1 >= block.Instructions.Count)
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;
if (!(storeInst.Value.MatchLdNull() || CheckResourceType(storeInst.Variable.Type)))
return false;
@@ -88,12 +94,14 @@ namespace ICSharpCode.Decompiler.IL.Transforms
return false;
if (storeInst.Variable.StoreInstructions.Count > 1)
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;
context.Step("UsingTransform", tryFinally);
storeInst.Variable.Kind = VariableKind.UsingLocal;
- block.Instructions.RemoveAt(i);
- block.Instructions[i - 1] = new UsingInstruction(storeInst.Variable, storeInst.Value, tryFinally.TryBlock) {
+ block.Instructions.RemoveAt(i + 1);
+ 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
}.WithILRange(storeInst);
return true;
@@ -127,6 +135,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
bool TransformUsingVB(Block block, int i)
{
+ if (i >= block.Instructions.Count)
+ return false;
if (!(block.Instructions[i] is TryFinally tryFinally))
return false;
if (!(tryFinally.TryBlock is BlockContainer tryContainer && tryContainer.EntryPoint.Instructions.FirstOrDefault() is StLoc storeInst))
@@ -175,41 +185,64 @@ namespace ICSharpCode.Decompiler.IL.Transforms
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;
- if (entryPoint.Instructions.Count < 2 || entryPoint.Instructions.Count > 3 || entryPoint.IncomingEdgeCount != 1)
+ if (entryPoint.IncomingEdgeCount != 1)
return false;
- int leaveIndex = entryPoint.Instructions.Count == 2 ? 1 : 2;
- int checkIndex = entryPoint.Instructions.Count == 2 ? 0 : 1;
- int castIndex = entryPoint.Instructions.Count == 3 ? 0 : -1;
- var checkInst = entryPoint.Instructions[checkIndex];
+ int pos = 0;
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))
- return false;
- if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar) || !disposableType.IsKnownType(disposeTypeCode))
+ if (!isinst.MatchIsInst(out var load, out var disposableType) || !load.MatchLdLoc(objVar)
+ || !disposableType.IsKnownType(disposeTypeCode))
+ {
return false;
+ }
if (!tempVar.IsSingleDefinition)
return false;
isReference = true;
- if (!MatchDisposeCheck(tempVar, checkInst, isReference, usingNull, out int numObjVarLoadsInCheck, disposeMethodFullName, disposeTypeCode))
- return false;
- if (tempVar.LoadCount != numObjVarLoadsInCheck)
- return false;
+ pos++;
+ objVar = tempVar;
}
- 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 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;
ILInstruction disposeInvocation;
@@ -283,7 +316,7 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// reference types have a null check.
if (!checkInst.MatchIfInstruction(out var condition, out var disposeInst))
return false;
- if (!condition.MatchCompNotEquals(out var left, out var right) || !left.MatchLdLoc(objVar) || !right.MatchLdNull())
+ if (!MatchNullCheckOrTypeCheck(condition, ref objVar, disposeTypeCode))
return false;
if (!(disposeInst is Block disposeBlock) || disposeBlock.Instructions.Count != 1)
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;
+ }
+
///
/// stloc test(resourceExpression)
/// .try BlockContainer {
@@ -371,7 +430,9 @@ namespace ICSharpCode.Decompiler.IL.Transforms
///
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;
if (!(block.Instructions[i] is TryFinally tryFinally) || !(block.Instructions[i - 1] is StLoc storeInst))
return false;