Skip to content

Commit aa35fca

Browse files
authored
Merge pull request github#5018 from tamasvajk/feature/csharp9-binary-pattern-cfg
C#: Extract 'and' and 'or' patterns
2 parents 064d897 + 7e9913a commit aa35fca

32 files changed

+7592
-2794
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lgtm,codescanning
2+
* The `BinaryPatternExpr` class has been added to support C# 9 `and` and `or`
3+
patterns.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.CSharp;
3+
using Microsoft.CodeAnalysis.CSharp.Syntax;
4+
using Semmle.Extraction.Entities;
5+
using Semmle.Extraction.Kinds;
6+
7+
namespace Semmle.Extraction.CSharp.Entities.Expressions
8+
{
9+
internal class BinaryPattern : Expression
10+
{
11+
public BinaryPattern(Context cx, BinaryPatternSyntax syntax, IExpressionParentEntity parent, int child) :
12+
base(new ExpressionInfo(cx, null, cx.Create(syntax.GetLocation()), GetKind(syntax.OperatorToken, syntax), parent, child, false, null))
13+
{
14+
Pattern.Create(cx, syntax.Left, this, 0);
15+
Pattern.Create(cx, syntax.Right, this, 1);
16+
}
17+
18+
private static ExprKind GetKind(SyntaxToken operatorToken, BinaryPatternSyntax syntax)
19+
{
20+
return operatorToken.Kind() switch
21+
{
22+
SyntaxKind.AndKeyword => ExprKind.AND_PATTERN,
23+
SyntaxKind.OrKeyword => ExprKind.OR_PATTERN,
24+
_ => throw new InternalError(syntax, $"Operator '{operatorToken.Kind()}' is not supported in binary patterns.")
25+
};
26+
}
27+
}
28+
}

csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Patterns/Pattern.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ internal static Expression Create(Context cx, PatternSyntax syntax, IExpressionP
2323
case UnaryPatternSyntax unaryPattern:
2424
return new UnaryPattern(cx, unaryPattern, parent, child);
2525

26+
case BinaryPatternSyntax binaryPattern:
27+
return new BinaryPattern(cx, binaryPattern, parent, child);
28+
2629
case DeclarationPatternSyntax declPattern:
2730
// Creates a single local variable declaration.
2831
{

csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ public enum ExprKind
121121
LE_PATTERN = 124,
122122
GE_PATTERN = 125,
123123
NOT_PATTERN = 126,
124+
AND_PATTERN = 127,
125+
OR_PATTERN = 128,
124126
FUNCTION_POINTER_INVOCATION = 129,
125127
}
126128
}

csharp/ql/src/semmle/code/csharp/controlflow/internal/Completion.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,17 @@ private predicate mustHaveMatchingCompletion(Expr e, PatternExpr pe) {
551551
or
552552
pe = any(IsExpr ie | inBooleanContext(ie) and e = ie.getExpr()).getPattern()
553553
or
554+
pe = any(RecursivePatternExpr rpe | mustHaveMatchingCompletion(e, rpe)).getPositionalPatterns()
555+
or
556+
pe = any(RecursivePatternExpr rpe | mustHaveMatchingCompletion(e, rpe)).getPropertyPatterns()
557+
or
558+
pe = any(PositionalPatternExpr ppe | mustHaveMatchingCompletion(e, ppe)).getPattern(_)
559+
or
560+
pe = any(PropertyPatternExpr ppe | mustHaveMatchingCompletion(e, ppe)).getPattern(_)
561+
or
554562
pe = any(UnaryPatternExpr upe | mustHaveMatchingCompletion(e, upe)).getPattern()
563+
or
564+
pe = any(BinaryPatternExpr bpe | mustHaveMatchingCompletion(e, bpe)).getAnOperand()
555565
}
556566

557567
/**

csharp/ql/src/semmle/code/csharp/controlflow/internal/ControlFlowGraphImpl.qll

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,21 @@ private import Splitting
5151
private import semmle.code.csharp.ExprOrStmtParent
5252

5353
/** An element that defines a new CFG scope. */
54-
class CfgScope extends Element, @top_level_exprorstmt_parent { }
54+
class CfgScope extends Element, @top_level_exprorstmt_parent {
55+
CfgScope() { not this instanceof Attribute }
56+
}
5557

5658
module ControlFlowTree {
5759
private class Range_ = @callable or @control_flow_element;
5860

59-
class Range extends Element, Range_ { }
61+
class Range extends Element, Range_ {
62+
Range() { this = getAChild*(any(CfgScope scope)) }
63+
}
64+
65+
Element getAChild(Element p) {
66+
result = p.getAChild() or
67+
result = p.(AssignOperation).getExpandedAssignment()
68+
}
6069

6170
private predicate id(Range x, Range y) { x = y }
6271

@@ -360,7 +369,9 @@ module Expressions {
360369
not this instanceof SwitchExpr and
361370
not this instanceof SwitchCaseExpr and
362371
not this instanceof ConstructorInitializer and
363-
not this instanceof NotPatternExpr
372+
not this instanceof NotPatternExpr and
373+
not this instanceof OrPatternExpr and
374+
not this instanceof AndPatternExpr
364375
}
365376

366377
final override ControlFlowElement getChildElement(int i) { result = getExprChild(this, i) }
@@ -902,6 +913,56 @@ module Expressions {
902913
c instanceof NormalCompletion
903914
}
904915
}
916+
917+
private class AndPatternExprTree extends PostOrderTree, AndPatternExpr {
918+
final override predicate propagatesAbnormal(ControlFlowElement child) {
919+
child = this.getAnOperand()
920+
}
921+
922+
final override predicate first(ControlFlowElement first) { first(this.getLeftOperand(), first) }
923+
924+
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
925+
// Flow from last element of left operand to first element of right operand
926+
last(this.getLeftOperand(), pred, c) and
927+
c.(MatchingCompletion).getValue() = true and
928+
first(this.getRightOperand(), succ)
929+
or
930+
// Post-order: flow from last element of left operand to element itself
931+
last(this.getLeftOperand(), pred, c) and
932+
c.(MatchingCompletion).getValue() = false and
933+
succ = this
934+
or
935+
// Post-order: flow from last element of right operand to element itself
936+
last(this.getRightOperand(), pred, c) and
937+
c instanceof MatchingCompletion and
938+
succ = this
939+
}
940+
}
941+
942+
private class OrPatternExprTree extends PostOrderTree, OrPatternExpr {
943+
final override predicate propagatesAbnormal(ControlFlowElement child) {
944+
child = this.getAnOperand()
945+
}
946+
947+
final override predicate first(ControlFlowElement first) { first(this.getLeftOperand(), first) }
948+
949+
final override predicate succ(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
950+
// Flow from last element of left operand to first element of right operand
951+
last(this.getLeftOperand(), pred, c) and
952+
c.(MatchingCompletion).getValue() = false and
953+
first(this.getRightOperand(), succ)
954+
or
955+
// Post-order: flow from last element of left operand to element itself
956+
last(this.getLeftOperand(), pred, c) and
957+
c.(MatchingCompletion).getValue() = true and
958+
succ = this
959+
or
960+
// Post-order: flow from last element of right operand to element itself
961+
last(this.getRightOperand(), pred, c) and
962+
c instanceof MatchingCompletion and
963+
succ = this
964+
}
965+
}
905966
}
906967

907968
module Statements {
@@ -1273,7 +1334,7 @@ module Statements {
12731334
/** Gets a child of `cfe` that is in CFG scope `scope`. */
12741335
pragma[noinline]
12751336
private ControlFlowElement getAChildInScope(ControlFlowElement cfe, Callable scope) {
1276-
result = [cfe.getAChild(), cfe.(AssignOperation).getExpandedAssignment()] and
1337+
result = ControlFlowTree::getAChild(cfe) and
12771338
scope = result.getEnclosingCallable()
12781339
}
12791340

csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,12 @@ module ConditionalCompletionSplitting {
471471
or
472472
last(succ.(IsExpr).getPattern(), pred, c) and
473473
completion.(BooleanCompletion).getValue() = c.(MatchingCompletion).getValue()
474+
or
475+
last(succ.(AndPatternExpr).getAnOperand(), pred, c) and
476+
completion = c
477+
or
478+
last(succ.(OrPatternExpr).getAnOperand(), pred, c) and
479+
completion = c
474480
)
475481
}
476482

@@ -1535,11 +1541,7 @@ predicate succEntrySplits(CfgScope pred, ControlFlowElement succ, Splits succSpl
15351541
exists(int rnk |
15361542
scopeFirst(pred, succ) and
15371543
t instanceof NormalSuccessor and
1538-
succEntrySplitsFromRank(pred, succ, succSplits, rnk) and
1539-
// Attribute arguments in assemblies are represented as expressions, even though
1540-
// they are not from source. We are not interested in constructing a CFG for such
1541-
// expressions.
1542-
succ.fromSource()
1544+
succEntrySplitsFromRank(pred, succ, succSplits, rnk)
15431545
|
15441546
rnk = 0 and
15451547
not any(SplitImpl split).hasEntryScope(pred, succ)

csharp/ql/src/semmle/code/csharp/exprs/Expr.qll

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,17 +290,32 @@ private predicate hasChildPattern(ControlFlowElement pm, Expr child) {
290290
or
291291
exists(Expr mid |
292292
hasChildPattern(pm, mid) and
293-
mid instanceof @recursive_pattern_expr
294-
|
295-
child = mid.getChild(2).getAChildExpr() or
296-
child = mid.getChild(3).getAChildExpr()
293+
mid instanceof @property_pattern_expr and
294+
child = mid.getAChildExpr()
295+
)
296+
or
297+
exists(Expr mid |
298+
hasChildPattern(pm, mid) and
299+
mid instanceof @positional_pattern_expr and
300+
child = mid.getAChildExpr()
301+
)
302+
or
303+
exists(Expr mid |
304+
hasChildPattern(pm, mid) and
305+
mid instanceof @recursive_pattern_expr and
306+
child = mid.getChild([2, 3])
297307
)
298308
or
299309
exists(Expr mid |
300310
hasChildPattern(pm, mid) and
301311
mid instanceof @unary_pattern_expr and
302312
child = mid.getChild(0)
303313
)
314+
or
315+
exists(Expr mid | hasChildPattern(pm, mid) and mid instanceof @binary_pattern_expr |
316+
child = mid.getChild(0) or
317+
child = mid.getChild(1)
318+
)
304319
}
305320

306321
/**
@@ -457,7 +472,7 @@ class RecursivePatternExpr extends BindingPatternExpr, @recursive_pattern_expr {
457472
}
458473

459474
/** A property pattern. For example, `{ Length: 5 }`. */
460-
class PropertyPatternExpr extends Expr, @property_pattern_expr {
475+
class PropertyPatternExpr extends PatternExpr, @property_pattern_expr {
461476
override string toString() { result = "{ ... }" }
462477

463478
/** Gets the `n`th pattern. */
@@ -480,7 +495,7 @@ class LabeledPatternExpr extends PatternExpr {
480495
}
481496

482497
/** A positional pattern. For example, `(int x, int y)`. */
483-
class PositionalPatternExpr extends Expr, @positional_pattern_expr {
498+
class PositionalPatternExpr extends PatternExpr, @positional_pattern_expr {
484499
override string toString() { result = "( ... )" }
485500

486501
/** Gets the `n`th pattern. */
@@ -502,6 +517,32 @@ class NotPatternExpr extends UnaryPatternExpr, @not_pattern_expr {
502517
override string getAPrimaryQlClass() { result = "NotPatternExpr" }
503518
}
504519

520+
/** A binary pattern. For example, `1 or 2`. */
521+
class BinaryPatternExpr extends PatternExpr, @binary_pattern_expr {
522+
/** Gets a pattern. */
523+
PatternExpr getAnOperand() { result = getLeftOperand() or result = getRightOperand() }
524+
525+
/** Gets the left pattern. */
526+
PatternExpr getLeftOperand() { result = this.getChild(0) }
527+
528+
/** Gets the right pattern. */
529+
PatternExpr getRightOperand() { result = this.getChild(1) }
530+
}
531+
532+
/** A binary `or` pattern. For example, `1 or 2`. */
533+
class OrPatternExpr extends BinaryPatternExpr, @or_pattern_expr {
534+
override string toString() { result = "... or ..." }
535+
536+
override string getAPrimaryQlClass() { result = "OrPatternExpr" }
537+
}
538+
539+
/** A binary `and` pattern. For example, `< 1 and > 2`. */
540+
class AndPatternExpr extends BinaryPatternExpr, @and_pattern_expr {
541+
override string toString() { result = "... and ..." }
542+
543+
override string getAPrimaryQlClass() { result = "AndPatternExpr" }
544+
}
545+
505546
/**
506547
* An expression or statement that matches the value of an expression against
507548
* a pattern. Either an `is` expression or a `case` expression/statement.

csharp/ql/src/semmlecode.csharp.dbscheme

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,8 @@ case @expr.kind of
10321032
| 124 = @le_pattern_expr
10331033
| 125 = @ge_pattern_expr
10341034
| 126 = @not_pattern_expr
1035+
| 127 = @and_pattern_expr
1036+
| 128 = @or_pattern_expr
10351037
| 129 = @function_pointer_invocation_expr
10361038
;
10371039

@@ -1040,6 +1042,7 @@ case @expr.kind of
10401042
@pattern_match = @case | @is_expr;
10411043
@unary_pattern_expr = @not_pattern_expr;
10421044
@relational_pattern_expr = @gt_pattern_expr | @lt_pattern_expr | @ge_pattern_expr | @le_pattern_expr;
1045+
@binary_pattern_expr = @and_pattern_expr | @or_pattern_expr;
10431046

10441047
@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr;
10451048
@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr;

0 commit comments

Comments
 (0)