Skip to content

Commit

Permalink
Fix #2613: Detect pattern matching on variables of generic type with …
Browse files Browse the repository at this point in the history
…value types.
  • Loading branch information
siegfriedpammer committed Aug 4, 2023
1 parent 3de29c8 commit 3d8cda5
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 21 deletions.
72 changes: 72 additions & 0 deletions ICSharpCode.Decompiler.Tests/TestCases/Pretty/PatternMatching.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,78 @@ public static void NotTypePatternBecauseVarIsNotDefAssignedInCaseOfFallthrough(o
#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()
{
return true;
Expand Down
9 changes: 9 additions & 0 deletions ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4537,6 +4537,15 @@ void ReplaceAssignmentTarget(ILInstruction target)
protected internal override TranslatedExpression VisitMatchInstruction(MatchInstruction inst, TranslationContext context)
{
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);

return new BinaryOperatorExpression(left, BinaryOperatorType.IsPattern, right)
Expand Down
82 changes: 61 additions & 21 deletions ICSharpCode.Decompiler/IL/Transforms/PatternMatchingTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,21 +240,25 @@ private bool CheckAllUsesDominatedBy(ILVariable v, BlockContainer container, ILI
/// }
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))
if (!MatchIsInstBlock(block, out var type, out var testedOperand, out var testedVariable,
out var boxType1, 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))
if (tempStore == null || !tempStore.Value.MatchLdLoc(testedVariable))
{
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;
}
if (unboxOperand == testedOperand.Variable)
if (!object.Equals(boxType1, boxType2))
{
return false;
}
if (unboxOperand == testedVariable)
{
// do nothing
}
Expand All @@ -267,11 +271,11 @@ private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTra
{
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;
context.Step($"PatternMatching with {v.Name}", block);
context.Step($"PatternMatching with {storeToV.Variable.Name}", block);
var ifInst = (IfInstruction)block.Instructions.SecondToLastOrDefault()!;
ifInst.Condition = new MatchInstruction(v, testedOperand) {
ifInst.Condition = new MatchInstruction(storeToV.Variable, testedOperand) {
CheckNotNull = true,
CheckType = true
};
Expand All @@ -286,25 +290,35 @@ private bool PatternMatchValueTypes(Block block, BlockContainer container, ILTra
// 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;
storeToV.Variable.Kind = VariableKind.PatternLocal;
return true;
}

/// ...
/// if (comp.o(isinst T(ldloc testedOperand) == ldnull)) br falseBlock
/// br unboxBlock
/// - or -
/// ...
/// if (comp.o(isinst T(box ``0(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 ILInstruction? testedOperand,
[NotNullWhen(true)] out ILVariable? testedVariable,
out IType? boxType,
[NotNullWhen(true)] out Block? unboxBlock,
[NotNullWhen(true)] out Block? falseBlock)
{
type = null;
testedOperand = null;
testedVariable = null;
boxType = 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);
Expand All @@ -317,33 +331,59 @@ private bool MatchIsInstBlock(Block block,
{
return false;
}
if (!arg.MatchIsInst(out arg, out type))
if (!arg.MatchIsInst(out testedOperand, out type))
{
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 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))
/// ...
/// - or -
/// stloc V(unbox.any T(isinst T(box ``0(ldloc testedOperand))))
/// ...
/// }
private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedOperand,
[NotNullWhen(true)] out ILVariable? v, [NotNullWhen(true)] out ILInstruction? storeToV)
private bool MatchUnboxBlock(Block unboxBlock, IType type, [NotNullWhen(true)] out ILVariable? testedVariable,
out IType? boxType, [NotNullWhen(true)] out StLoc? storeToV)
{
v = null;
boxType = null;
storeToV = null;
testedOperand = null;
testedVariable = null;
if (unboxBlock.IncomingEdgeCount != 1)
return false;
storeToV = unboxBlock.Instructions[0];
if (!storeToV.MatchStLoc(out v, out var value))
storeToV = unboxBlock.Instructions[0] as StLoc;
if (storeToV == null)
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;

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;
}
}
Expand Down

0 comments on commit 3d8cda5

Please sign in to comment.