From de8ecb214f4c2dcd0c0223f19d7eff7b2ee430e5 Mon Sep 17 00:00:00 2001 From: Rasmus Lerchedahl Petersen Date: Tue, 18 Jan 2022 14:48:46 +0100 Subject: [PATCH] python: Wrappers for database classes - new syntactic category `Pattern` (in `Patterns.qll`) - subpatterns available on statments - new statements `MatchStmt` and `Case` (`Match` would conflict with the shared ReDoS library) - new expression `Guard` - support for pattern lists --- python/ql/lib/python.qll | 1 + python/ql/lib/semmle/python/AstExtended.qll | 9 ++ python/ql/lib/semmle/python/Exprs.qll | 6 + python/ql/lib/semmle/python/Patterns.qll | 123 ++++++++++++++++++++ python/ql/lib/semmle/python/Stmts.qll | 23 ++++ 5 files changed, 162 insertions(+) create mode 100644 python/ql/lib/semmle/python/Patterns.qll diff --git a/python/ql/lib/python.qll b/python/ql/lib/python.qll index 58908fa091f..9a34be8ebcc 100644 --- a/python/ql/lib/python.qll +++ b/python/ql/lib/python.qll @@ -10,6 +10,7 @@ import semmle.python.Class import semmle.python.Import import semmle.python.Stmts import semmle.python.Exprs +import semmle.python.Patterns import semmle.python.Keywords import semmle.python.Comprehensions import semmle.python.Flow diff --git a/python/ql/lib/semmle/python/AstExtended.qll b/python/ql/lib/semmle/python/AstExtended.qll index f4de256df3c..0a7fce5ebd6 100644 --- a/python/ql/lib/semmle/python/AstExtended.qll +++ b/python/ql/lib/semmle/python/AstExtended.qll @@ -82,6 +82,12 @@ library class StrListParent extends StrListParent_ { } /** Internal implementation class */ library class ExprParent extends ExprParent_ { } +/** Internal implementation class */ +library class PatternListParent extends PatternListParent_ { } + +/** Internal implementation class */ +library class PatternParent extends PatternParent_ { } + library class DictItem extends DictItem_, AstNode { override string toString() { result = DictItem_.super.toString() } @@ -162,6 +168,9 @@ class ExprList extends ExprList_ { /* syntax: Expr, ... */ } +/** A list of patterns */ +class PatternList extends PatternList_ { } + library class DictItemList extends DictItemList_ { } library class DictItemListParent extends DictItemListParent_ { } diff --git a/python/ql/lib/semmle/python/Exprs.qll b/python/ql/lib/semmle/python/Exprs.qll index 5afa651de22..766b9d03e56 100644 --- a/python/ql/lib/semmle/python/Exprs.qll +++ b/python/ql/lib/semmle/python/Exprs.qll @@ -718,6 +718,12 @@ class FormattedValue extends FormattedValue_ { } } +/** A guard in a case statement */ +class Guard extends Guard_ { + /* syntax: if Expr */ + override Expr getASubExpression() { result = this.getTest() } +} + /* Expression Contexts */ /** A context in which an expression used */ class ExprContext extends ExprContext_ { } diff --git a/python/ql/lib/semmle/python/Patterns.qll b/python/ql/lib/semmle/python/Patterns.qll new file mode 100644 index 00000000000..3dfe80f8251 --- /dev/null +++ b/python/ql/lib/semmle/python/Patterns.qll @@ -0,0 +1,123 @@ +/** + * Wrapping generated AST classes: `Pattern_` and subclasses. + */ + +import python + +/** A pattern in a match statement */ +class Pattern extends Pattern_, AstNode { + /** Gets the scope of this pattern */ + override Scope getScope() { + // TODO: Should it be defined as + // py_scopes(this, result) + // instead? + result = this.getCase().getScope() + } + + /** Gets the case statement containing this pattern */ + Case getCase() { result.contains(this) } + + override string toString() { result = "Pattern" } + + /** Gets the module enclosing this pattern */ + Module getEnclosingModule() { result = this.getScope().getEnclosingModule() } + + /** Whether the parenthesized property of this expression is true. */ + predicate isParenthesized() { Pattern_.super.isParenthesised() } + + override Location getLocation() { result = Pattern_.super.getLocation() } + + /** Gets an immediate (non-nested) sub-expression of this pattern */ + Expr getASubExpression() { none() } + + /** Gets an immediate (non-nested) sub-statement of this pattern */ + Stmt getASubStatement() { none() } + + /** Gets an immediate (non-nested) sub-pattern of this pattern */ + Pattern getASubPattern() { none() } + + override AstNode getAChildNode() { + result = this.getASubExpression() + or + result = this.getASubStatement() + or + result = this.getASubPattern() + } +} + +/** An as-pattern in a match statement: ` as alias` */ +class MatchAsPattern extends MatchAsPattern_ { + override Pattern getASubPattern() { result = this.getPattern() } + + override Expr getASubExpression() { result = this.getAlias() } + + override Name getAlias() { result = super.getAlias() } +} + +/** An or-pattern in a match statement: `(|)` */ +class MatchOrPattern extends MatchOrPattern_ { + override Pattern getASubPattern() { result = this.getAPattern() } +} + +/** A literal pattern in a match statement: `42` */ +class MatchLiteralPattern extends MatchLiteralPattern_ { + override Expr getASubExpression() { result = this.getLiteral() } +} + +/** A capture pattern in a match statement: `var` */ +class MatchCapturePattern extends MatchCapturePattern_ { + /* syntax: varname */ + override Expr getASubExpression() { result = this.getVariable() } + + /** Gets the variable that is bound by this capture pattern */ + override Name getVariable() { result = super.getVariable() } +} + +/** A wildcard pattern in a match statement: `_` */ +class MatchWildcardPattern extends MatchWildcardPattern_ { } + +/** A value pattern in a match statement: `Http.OK` */ +class MatchValuePattern extends MatchValuePattern_ { + override Expr getASubExpression() { result = this.getValue() } +} + +/** A sequence pattern in a match statement `, ` */ +class MatchSequencePattern extends MatchSequencePattern_ { + override Pattern getASubPattern() { result = this.getAPattern() } +} + +/** A star pattern in a match statement: `(..., *)` */ +class MatchStarPattern extends MatchStarPattern_ { + override Pattern getASubPattern() { result = this.getTarget() } +} + +/** A mapping pattern in a match statement: `{'a': var}` */ +class MatchMappingPattern extends MatchMappingPattern_ { + override Pattern getASubPattern() { result = this.getAMapping() } +} + +/** A double star pattern in a match statement: `{..., **}` */ +class MatchDoubleStarPattern extends MatchDoubleStarPattern_ { + override Pattern getASubPattern() { result = this.getTarget() } +} + +/** A key-value pattern inside a mapping pattern: `a: var` */ +class MatchKeyValuePattern extends MatchKeyValuePattern_ { + override Pattern getASubPattern() { result = this.getKey() or result = this.getValue() } +} + +/** A class pattern in a match statement: `Circle(radius = 3)` */ +class MatchClassPattern extends MatchClassPattern_ { + override Expr getASubExpression() { result = this.getClassName() } + + override Pattern getASubPattern() { + result = this.getAPositional() or result = this.getAKeyword() + } +} + +/** A keyword pattern inside a class pattern: `radius = 3` */ +class MatchKeywordPattern extends MatchKeywordPattern_ { + override Expr getASubExpression() { result = this.getAttribute() } + + override Pattern getASubPattern() { result = this.getValue() } +} diff --git a/python/ql/lib/semmle/python/Stmts.qll b/python/ql/lib/semmle/python/Stmts.qll index 3dc71e9d990..9ffaef81ab2 100644 --- a/python/ql/lib/semmle/python/Stmts.qll +++ b/python/ql/lib/semmle/python/Stmts.qll @@ -18,10 +18,15 @@ class Stmt extends Stmt_, AstNode { /** Gets an immediate (non-nested) sub-statement of this statement */ Stmt getASubStatement() { none() } + /** Gets an immediate (non-nested) sub-pattern of this statement */ + Pattern getASubPattern() { none() } + override AstNode getAChildNode() { result = this.getASubExpression() or result = this.getASubStatement() + or + result = this.getASubPattern() } private ControlFlowNode possibleEntryNode() { @@ -412,6 +417,24 @@ class With extends With_ { override Stmt getLastStatement() { result = this.getBody().getLastItem().getLastStatement() } } +/** A match statement */ +class MatchStmt extends MatchStmt_ { + /* syntax: match subject: */ + override Expr getASubExpression() { result = this.getSubject() } + + override Stmt getASubStatement() { result = this.getCase(_) } +} + +/** A case statement */ +class Case extends Case_ { + /* syntax: case pattern if guard: */ + override Expr getASubExpression() { result = this.getGuard() } + + override Stmt getASubStatement() { result = this.getStmt(_) } + + override Pattern getASubPattern() { result = this.getPattern() } +} + /** A plain text used in a template is wrapped in a TemplateWrite statement */ class TemplateWrite extends TemplateWrite_ { override Expr getASubExpression() { result = this.getValue() }