1+ import python as python
2+ import qtil.parameterization.SignatureTypes
3+ import qtil.parameterization.Finalize
4+
5+ /**
6+ * A module for dealing with pairs of exclusive operands in C++ ASTs.
7+ *
8+ * For instance, to find cases where one operand is an integer and the other is a constant, you
9+ * will want to to perform checks on each operand separately and consistently without worrying about
10+ * order. This module makes this common pattern easy to implement.
11+ *
12+ * This module takes two type parameters:
13+ * - `Operand`: the type of the operands (e.g. `Expr`)
14+ * - `HasOperands`: a type that has operands of type `Operand` (e.g. `BinaryExpr`)
15+ *
16+ * ```ql
17+ * // Using this module:
18+ * predicate myBinaryTestNew(BinaryExpr e) {
19+ * exists(TwoOperands<BinaryExpr>::Set set |
20+ * set.getOperation() = e and
21+ * set.someOperand().isInteger() and
22+ * set.otherOperand().isConstant()
23+ * )
24+ * }
25+ *
26+ * // Is roughly equivalent to:
27+ * predicate myBinaryTestOld(BinaryExpr e) {
28+ * exists(Expr a, Expr b |
29+ * e.getAnOperand() = a and
30+ * e.getAnOperand() = b and
31+ * a != b and
32+ * a.isInteger() and
33+ * b.isConstant()
34+ * )
35+ * }
36+ * ```
37+ *
38+ * Some caution about using this module: for each use, two `Set` objects exst. If you do not
39+ * properly constrain the usage of `someOperand()` and `otherOperand()`, then these members could
40+ * hold for different `Set`s. Therefore, `someOperand()` and `otherOperand()` may be the same
41+ * operand. This will not happen if the `Set` is properly constrained across the two member
42+ * invocations.
43+ *
44+ * ```ql
45+ * predicate bug(BinaryExpr e) {
46+ * // Bad: the two sets are not constrained to the same instance, therefore the operands not
47+ * // guaranteed to be different.
48+ * TwoOperands<BinaryExpr>::getASet(e).someOperand().isInteger() and
49+ * TwoOperands<BinaryExpr>::getASet(e).otherOperand().isConstant()
50+ * }
51+ * ```
52+ */
53+ module TwoOperands< Signature< python:: BinaryExpr > :: Type BinOp> {
54+ private import qtil.ast.TwoOperands as Make
55+
56+ // The `TwoOperands` module expects a `getAnOperand` member to be defined in the `BinOp` type, so
57+ // we define it here. The `TwoOperands` module should still work for any `BinaryExpr`.
58+ private class AddGetAnOperand extends Final< BinOp > :: Type {
59+ python:: Expr getAnOperand ( ) { result = getLeft ( ) or result = getRight ( ) }
60+ }
61+
62+ import Make:: TwoOperands< python:: Expr , AddGetAnOperand >
63+ }
0 commit comments