Browse Source

Fix #2613: Detect pattern matching on variables of generic type with value types.

pull/3050/head
Siegfried Pammer 2 years ago
parent
commit
3d8cda5f88
  1. 72
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
  2. 9
      ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
  3. 82
      ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs

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

@ -231,6 +231,78 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
#endif #endif
} }
public void GenericTypePatternInt<T>(T x)
{
if (x is int value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("not an int");
}
}
public void GenericValueTypePatternInt<T>(T x) where T : struct
{
if (x is int value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("not an int");
}
}
public void GenericRefTypePatternInt<T>(T x) where T : class
{
if (x is int value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("not an int");
}
}
public void GenericTypePatternString<T>(T x)
{
if (x is string value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("not a string");
}
}
public void GenericRefTypePatternString<T>(T x) where T : class
{
if (x is string value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("not a string");
}
}
public void GenericValueTypePatternStringRequiresCastToObject<T>(T x) where T : struct
{
if ((object)x is string value)
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("not a string");
}
}
private bool F() private bool F()
{ {
return true; return true;

9
ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs

@ -4537,6 +4537,15 @@ namespace ICSharpCode.Decompiler.CSharp
protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context) protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context)
{ {
var left = Translate(inst.TestedOperand); var left = Translate(inst.TestedOperand);
// remove boxing conversion if possible, however, we still need a cast in
// test case PatternMatching.GenericValueTypePatternStringRequiresCastToObject
if (left.ResolveResult is ConversionResolveResult crr
&& crr.Conversion.IsBoxingConversion
&& left.Expression is CastExpression castExpr
&& (crr.Input.Type.IsReferenceType != false || inst.Variable.Type.IsReferenceType == false))
{
left = left.UnwrapChild(castExpr.Expression);
}
var right = TranslatePattern(inst); var right = TranslatePattern(inst);
return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right) return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right)

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

@ -240,21 +240,25 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// } /// }
private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg) private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTransformContext context, ref ControlFlowGraph? cfg)
{ {
if (!MatchIsInstBlock(block, out var type, out var testedOperand, if (!MatchIsInstBlock(block, out var type, out var testedOperand, out var testedVariable,
out var unboxBlock, out var falseBlock)) out var boxType1, out var unboxBlock, out var falseBlock))
{ {
return false; return false;
} }
StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc; StLoc? tempStore = block.Instructions.ElementAtOrDefault(block.Instructions.Count - 3) as StLoc;
if (tempStore == null || !tempStore.Value.MatchLdLoc(testedOperand.Variable)) if (tempStore == null || !tempStore.Value.MatchLdLoc(testedVariable))
{ {
tempStore = null; tempStore = null;
} }
if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var v, out var storeToV)) if (!MatchUnboxBlock(unboxBlock, type, out var unboxOperand, out var boxType2, out var storeToV))
{ {
return false; return false;
} }
if (unboxOperand == testedOperand.Variable) if (!object.Equals(boxType1, boxType2))
{
return false;
}
if (unboxOperand == testedVariable)
{ {
// do nothing // do nothing
} }
@ -267,11 +271,11 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
return false; return false;
} }
if (!CheckAllUsesDominatedBy(v, container, unboxBlock, storeToV, null, context, ref cfg)) if (!CheckAllUsesDominatedBy(storeToV.Variable, container, unboxBlock, storeToV, null, context, ref cfg))
return false; return false;
context.Step($"PatternMatching with {v.Name}", block); context.Step($"PatternMatching with {storeToV.Variable.Name}", block);
var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!; var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!;
ifInst.Condition = new MatchInstruction(v, testedOperand) { ifInst.Condition = new MatchInstruction(storeToV.Variable, testedOperand) {
CheckNotNull = true, CheckNotNull = true,
CheckType = true CheckType = true
}; };
@ -286,25 +290,35 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// should become the then-branch. Change the unboxBlock StartILOffset from an offset inside // 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. // the pattern matching machinery to an offset belonging to an instruction in the then-block.
unboxBlock.SetILRange(unboxBlock.Instructions[0]); unboxBlock.SetILRange(unboxBlock.Instructions[0]);
v.Kind = VariableKind.PatternLocal; storeToV.Variable.Kind = VariableKind.PatternLocal;
return true; return true;
} }
/// ... /// ...
/// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock /// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock
/// br unboxBlock /// br unboxBlock
/// - or -
/// ...
/// if (comp.o(isinst T(box ``0(ldloc testedOperand)) == ldnull)) br falseBlock
/// br unboxBlock
private bool MatchIsInstBlock(Block block, private bool MatchIsInstBlock(Block block,
[NotNullWhen(true)] out IType? type, [NotNullWhen(true)] out IType? type,
[NotNullWhen(true)] out LdLoc? testedOperand, [NotNullWhen(true)] out ILInstruction? testedOperand,
[NotNullWhen(true)] out ILVariable? testedVariable,
out IType? boxType,
[NotNullWhen(true)] out Block? unboxBlock, [NotNullWhen(true)] out Block? unboxBlock,
[NotNullWhen(true)] out Block? falseBlock) [NotNullWhen(true)] out Block? falseBlock)
{ {
type = null; type = null;
testedOperand = null; testedOperand = null;
testedVariable = null;
boxType = null;
unboxBlock = null; unboxBlock = null;
falseBlock = null; falseBlock = null;
if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst)) if (!block.MatchIfAtEndOfBlock(out var condition, out var trueInst, out var falseInst))
{
return false; return false;
}
if (condition.MatchCompEqualsNull(out var arg)) if (condition.MatchCompEqualsNull(out var arg))
{ {
ExtensionMethods.Swap(ref trueInst, ref falseInst); ExtensionMethods.Swap(ref trueInst, ref falseInst);
@ -317,11 +331,18 @@ namespace ICSharpCode.Decompiler.IL.Transforms
{ {
return false; return false;
} }
if (!arg.MatchIsInst(out arg, out type)) if (!arg.MatchIsInst(out testedOperand, out type))
{
return false; return false;
testedOperand = arg as LdLoc; }
if (testedOperand == null) if (!(testedOperand.MatchBox(out var boxArg, out boxType) && boxType.Kind == TypeKind.TypeParameter))
{
boxArg = testedOperand;
}
if (!boxArg.MatchLdLoc(out testedVariable))
{
return false; return false;
}
return trueInst.MatchBranch(out unboxBlock) && falseInst.MatchBranch(out falseBlock) return trueInst.MatchBranch(out unboxBlock) && falseInst.MatchBranch(out falseBlock)
&& unboxBlock.Parent == block.Parent && falseBlock.Parent == block.Parent; && unboxBlock.Parent == block.Parent && falseBlock.Parent == block.Parent;
} }
@ -329,21 +350,40 @@ namespace ICSharpCode.Decompiler.IL.Transforms
/// Block unboxBlock (incoming: 1) { /// Block unboxBlock (incoming: 1) {
/// stloc V(unbox.any T(ldloc testedOperand)) /// stloc V(unbox.any T(ldloc testedOperand))
/// ... /// ...
/// - or -
/// stloc V(unbox.any T(isinst T(box ``0(ldloc testedOperand))))
/// ...
/// } /// }
private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedOperand, private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedVariable,
[NotNullWhen(true)] out ILVariable? v, [NotNullWhen(true)] out ILInstruction? storeToV) out IType? boxType, [NotNullWhen(true)] out StLoc? storeToV)
{ {
v = null; boxType = null;
storeToV = null; storeToV = null;
testedOperand = null; testedVariable = null;
if (unboxBlock.IncomingEdgeCount != 1) if (unboxBlock.IncomingEdgeCount != 1)
return false; return false;
storeToV = unboxBlock.Instructions[0]; storeToV = unboxBlock.Instructions[0] as StLoc;
if (!storeToV.MatchStLoc(out v, out var value)) if (storeToV == null)
return false; return false;
if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type) && arg.MatchLdLoc(out testedOperand))) var value = storeToV.Value;
if (!(value.MatchUnboxAny(out var arg, out var t) && t.Equals(type)))
return false; return false;
if (arg.MatchIsInst(out var isinstArg, out var isinstType) && isinstType.Equals(type))
{
arg = isinstArg;
}
if (arg.MatchBox(out var boxArg, out boxType) && boxType.Kind == TypeKind.TypeParameter)
{
arg = boxArg;
}
if (!arg.MatchLdLoc(out testedVariable))
{
return false;
}
if (boxType != null && !boxType.Equals(testedVariable.Type))
{
return false;
}
return true; return true;
} }
} }

Loading…
Cancel
Save