Skip to content

Commit 0703b88

Browse files
authored
Merge pull request #10404 from RasmusWL/update-range-pattern
Docs: Use `instanceof` in `::Range` pattern description
2 parents e140f04 + 0e3821d commit 0703b88

File tree

1 file changed

+7
-15
lines changed

1 file changed

+7
-15
lines changed

docs/ql-design-patterns.md

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,9 @@ Applying the `::Range` pattern yields the following:
3131
* Extend this class to refine existing API models. If you want to model new APIs,
3232
* extend `MySpecialExpr::Range` instead.
3333
*/
34-
class MySpecialExpr extends Expr {
35-
MySpecialExpr::Range range;
36-
37-
MySpecialExpr() { this = range }
38-
34+
class MySpecialExpr extends Expr instanceof MySpecialExpr::Range {
3935
/** <QLDoc...> */
40-
int memberPredicate() { result = range.memberPredicate() }
36+
int memberPredicate() { result = super.memberPredicate() }
4137
}
4238
4339
/** Provides a class for modeling new <...> APIs. */
@@ -56,22 +52,18 @@ module MySpecialExpr {
5652
```
5753
Now, a concrete subclass can derive from `MySpecialExpr::Range` if it wants to extend the set of values in `MySpecialExpr`, and it will be required to implement the abstract `memberPredicate()`. Conversely, if it wants to refine `MySpecialExpr` and override `memberPredicate` for all extensions, it can do so by deriving from `MySpecialExpr` directly.
5854

59-
The key element of the pattern is to provide a field of type `MySpecialExpr::Range`, equating it to `this` in the characteristic predicate of `MySpecialExpr`. In member predicates, we can use either `this` or `range`, depending on which type has the API we need.
60-
6155
</details>
6256

63-
Note that in some libraries, the `range` field is in fact called `self`. While we do recommend using `range` for consistency, the name of the field does not matter (and using `range` avoids confusion in contexts like Python analysis that has strong usage of `self`).
64-
6557
### Rationale
6658

67-
Let's use an example from the Go libraries: https://github.com/github/codeql-go/blob/2ba9bbfd8ba1818b5ee9f6009c86a605189c9ef3/ql/src/semmle/go/Concepts.qll#L119-L157
59+
Let's use an example from the Python libraries: https://github.com/github/codeql/blob/46751e515c40c6b4c9b61758cc840eec1894a624/python/ql/lib/semmle/python/Concepts.qll#L601-L683
6860

69-
`EscapeFunction`, as the name suggests, models various APIs that escape meta-characters. It has a member-predicate `kind()` that tells you what sort of escaping the modelled function does. For example, if the result of that predicate is `"js"`, then this means that the escaping function is meant to make things safe to embed inside JavaScript.
70-
`EscapeFunction::Range` is subclassed to model various APIs, and `kind()` is implemented accordingly.
71-
But we can also subclass `EscapeFunction` to, as in the above example, talk about all JS-escaping functions.
61+
`Escaping`, as the name suggests, models various APIs that escape meta-characters. It has a member-predicate `getKind()` that tells you what sort of escaping the modeled function does. For example, if the result of that predicate is `"html"`, then this means that the escaping function is meant to make things safe to embed inside HTML.
62+
`Escaping::Range` is subclassed to model various APIs, and `kind()` is implemented accordingly (this typically happens in library models).
63+
But we can also subclass `Escaping`, as in the above example, where `HtmlEscaping` represents all HTML-escaping functions.
7264

7365
You can, of course, do the same without the `::Range` pattern, but it's a little cumbersome:
74-
If you only had an `abstract class EscapeFunction { ... }`, then `JsEscapeFunction` would need to be implemented in a slightly tricky way to prevent it from extending `EscapeFunction` (instead of refining it). You would have to give it a charpred `this instanceof EscapeFunction`, which looks useless but isn't. And additionally, you'd have to provide trivial `none()` overrides of all the abstract predicates defined in `EscapeFunction`. This is all pretty awkward, and we can avoid it by distinguishing between `EscapeFunction` and `EscapeFunction::Range`.
66+
If you only had an `abstract class Escaping { ... }`, then `HtmlEscaping` would need to be implemented in a slightly tricky way to prevent it from extending `Escaping` (instead of refining it). You would have to give it a charpred `this instanceof Escaping`, which looks useless but isn't. And additionally, you'd have to provide trivial `none()` overrides of all the abstract predicates defined in `Escaping`. This is all pretty awkward, and we can avoid it by distinguishing between `Escaping` and `Escaping::Range`.
7567

7668

7769
## Importing all subclasses of a class

0 commit comments

Comments
 (0)