|
| 1 | +# CodeQL Design Patterns |
| 2 | + |
| 3 | +A list of design patterns you are recommended to follow. |
| 4 | + |
| 5 | +## `::Range` for extensibility and refinement |
| 6 | + |
| 7 | +To allow both extensibility and refinement of classes, we use what is commonly referred to as the `::Range` pattern (since https://github.com/github/codeql/pull/727), but the actual implementation can use different names. |
| 8 | + |
| 9 | +<details> |
| 10 | +<summary>Generic example of how to define classes with ::Range</summary> |
| 11 | + |
| 12 | +Instead of |
| 13 | +```ql |
| 14 | +/** <QLDoc...> */ |
| 15 | +abstract class MySpecialExpr extends Expr { |
| 16 | + /** <QLDoc...> */ |
| 17 | + abstract int memberPredicate(); |
| 18 | +} |
| 19 | +``` |
| 20 | +with |
| 21 | +```ql |
| 22 | +class ConcreteSubclass extends MySpecialExpr { ... } |
| 23 | +``` |
| 24 | + |
| 25 | +use |
| 26 | + |
| 27 | +```ql |
| 28 | +/** |
| 29 | + * <QLDoc...> |
| 30 | + * |
| 31 | + * Extend this class to refine existing API models. If you want to model new APIs, |
| 32 | + * extend `MySpecialExpr::Range` instead. |
| 33 | + */ |
| 34 | +class MySpecialExpr extends Expr { |
| 35 | + MySpecialExpr::Range self; |
| 36 | +
|
| 37 | + MySpecialExpr() { this = self } |
| 38 | +
|
| 39 | + /** <QLDoc...> */ |
| 40 | + int memberPredicate() { result = self.memberPredicate() } |
| 41 | +} |
| 42 | +
|
| 43 | +/** Provides a class for modeling new <...> APIs. */ |
| 44 | +module MySpecialExpr { |
| 45 | + /** |
| 46 | + * <QLDoc...> |
| 47 | + * |
| 48 | + * Extend this class to model new APIs. If you want to refine existing API models, |
| 49 | + * extend `MySpecialExpr` instead. |
| 50 | + */ |
| 51 | + abstract class Range extends Expr { |
| 52 | + /** <QLDoc...> */ |
| 53 | + abstract int memberPredicate(); |
| 54 | + } |
| 55 | +} |
| 56 | +``` |
| 57 | +with |
| 58 | +```ql |
| 59 | +class ConcreteSubclass extends MySpecialExpr::Range { ... } |
| 60 | +``` |
| 61 | + |
| 62 | +</details> |
| 63 | + |
| 64 | +### Rationale |
| 65 | + |
| 66 | +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 |
| 67 | + |
| 68 | +`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. |
| 69 | +`EscapeFunction::Range` is subclassed to model various APIs, and `kind()` is implemented accordingly. |
| 70 | +But we can also subclass `EscapeFunction` to, as in the above example, talk about all JS-escaping functions. |
| 71 | + |
| 72 | +You can, of course, do the same without the `::Range` pattern, but it's a little cumbersome: |
| 73 | +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`. |
| 74 | + |
| 75 | + |
| 76 | +## Importing all subclasses of abstract base class |
| 77 | + |
| 78 | +When providing an abstract class, you should ensure that all subclasses are included when the abstract class is (unless you have good reason not to). Otherwise you risk having different meanings of the abstract class depending on what you happen to import. |
0 commit comments