|
| 1 | +import cpp |
| 2 | + |
| 3 | +/** |
| 4 | + * Get an expression's value category as a ValueCategory object. |
| 5 | + * |
| 6 | + * Note that the standard cpp library exposes `is{_}ValueCategory` predicates, but they do not |
| 7 | + * necessarily work as expected due to how CodeQL handles reference adjustment and binding, which |
| 8 | + * this predicate attempts to handle. There are additional unhandled cases involving |
| 9 | + * lvalue-to-rvalue conversions as well. |
| 10 | + * |
| 11 | + * In C++17, an expression of type "reference to T" is adjusted to type T as stated in [expr]/5. |
| 12 | + * This is not a conversion, but in CodeQL this is handled by `ReferenceDereferenceExpr`, a type of |
| 13 | + * `Conversion`. Similarly, the binding of references to values is described in [dcl.init.ref], |
| 14 | + * which is not a conversion, but in CodeQL this is handled by `ReferenceToExpr`, another type of |
| 15 | + * `Conversion`. |
| 16 | + * |
| 17 | + * Furthermore, the `Expr` predicate `hasLValueToRValueConversion()` uses a different dbscheme table |
| 18 | + * than the `Conversion` table, and it is possible to have expressions such that |
| 19 | + * `hasLValueToRValueConversion()` holds, but there is no corresponding `Conversion` entry. |
| 20 | + * |
| 21 | + * Therefore, the value categories of expressions before `ReferenceDereferenceExpr` and after |
| 22 | + * `ReferenceToExpr` conversions are therefore essentially unspecified, and the `is{_}ValueCategory` |
| 23 | + * predicate results should not be relied upon. And types that are missing a |
| 24 | + * `LvalueToRValueConversion` will also return incorrect value categories. |
| 25 | + * |
| 26 | + * For example, in CodeQL 2.21.4: |
| 27 | + * |
| 28 | + * ```cpp |
| 29 | + * int &i = ...; |
| 30 | + * auto r = std::move(i); // 1.) i is a `prvalue(load)` in CodeQL, not an lvalue. |
| 31 | + * // 2.) i has a `ReferenceDereferenceExpr` conversion of lvalue category |
| 32 | + * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion of prvalue |
| 33 | + * // category. |
| 34 | + * int i2 = ...; |
| 35 | + * f(i2); // 1.) i2 is an lvalue. |
| 36 | + * // 2.) i2 undergoes lvalue-to-rvalue conversion, but there is no corresponding `Conversion`. |
| 37 | + * // 3.) i2 is treated at a prvalue by CodeQL, but `hasLValueToRValueConversion()` holds. |
| 38 | + * |
| 39 | + * int get_int(); |
| 40 | + * auto r2 = std::move(get_int()); // 1.) get_int() itself is treated as a prvalue by CodeQL. |
| 41 | + * // 2.) get_int() has a `TemporaryObjectExpr` conversion of lvalue |
| 42 | + * // category. |
| 43 | + * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion |
| 44 | + * // of prvalue category. |
| 45 | + * std::string get_str(); |
| 46 | + * auto r3 = std::move(get_str()); // 1.) get_str() is treated as a prvalue by CodeQL. |
| 47 | + * // 2.) get_str() has a `TemporaryObjectExpr` conversion of xvalue |
| 48 | + * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion |
| 49 | + * // of prvalue category. |
| 50 | + * std::string &str_ref(); |
| 51 | + * auto r3 = std::move(str_ref()); // 1.) str_ref() is treated as a prvalue by CodeQL. |
| 52 | + * // 2.) str_ref() has a `ReferenceDereferenceExpr` conversion of |
| 53 | + * // lvalue. |
| 54 | + * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion |
| 55 | + * // of prvalue category. |
| 56 | + * ``` |
| 57 | + * |
| 58 | + * As can be seen above, the value categories of expressions are correct between the |
| 59 | + * `ReferenceDereferenceExpr` and `ReferenceToExpr`, but not necessarily before or after. |
| 60 | + * |
| 61 | + * We must also check for `hasLValueToRValueConversion()` and handle that appropriately. |
| 62 | + */ |
| 63 | +ValueCategory getValueCategory(Expr e) { |
| 64 | + // If `e` is adjusted from a reference to a value (C++17 [expr]/5) then we want the value category |
| 65 | + // of the expression after `ReferenceDereferenceExpr`. |
| 66 | + if e.getConversion() instanceof ReferenceDereferenceExpr |
| 67 | + then result = getValueCategory(e.getConversion()) |
| 68 | + else ( |
| 69 | + // If `hasLValueToRValueConversion()` holds, then ensure we have an lvalue category. |
| 70 | + if e.hasLValueToRValueConversion() |
| 71 | + then result.isLValue() |
| 72 | + else |
| 73 | + // Otherwise, get the value category from `is{_}ValueCategory` predicates as normal. |
| 74 | + result = getDirectValueCategory(e) |
| 75 | + ) |
| 76 | +} |
| 77 | + |
| 78 | +/** |
| 79 | + * Gets the value category of an expression using `is{_}ValueCategory` predicates, without looking |
| 80 | + * through conversions. |
| 81 | + */ |
| 82 | +private ValueCategory getDirectValueCategory(Expr e) { |
| 83 | + if e.isLValueCategory() |
| 84 | + then result = LValue(e.getValueCategoryString()) |
| 85 | + else |
| 86 | + if e.isPRValueCategory() |
| 87 | + then result = PRValue(e.getValueCategoryString()) |
| 88 | + else |
| 89 | + if e.isXValueCategory() |
| 90 | + then result = XValue(e.getValueCategoryString()) |
| 91 | + else none() |
| 92 | +} |
| 93 | + |
| 94 | +newtype TValueCategory = |
| 95 | + LValue(string descr) { |
| 96 | + exists(Expr e | e.isLValueCategory() and descr = e.getValueCategoryString()) |
| 97 | + } or |
| 98 | + PRValue(string descr) { |
| 99 | + exists(Expr e | e.isPRValueCategory() and descr = e.getValueCategoryString()) |
| 100 | + } or |
| 101 | + XValue(string descr) { |
| 102 | + exists(Expr e | e.isXValueCategory() and descr = e.getValueCategoryString()) |
| 103 | + } |
| 104 | + |
| 105 | +/** |
| 106 | + * A value category, which can be an lvalue, prvalue, or xvalue. |
| 107 | + * |
| 108 | + * Note that prvalue has two possible forms: `prvalue` and `prvalue(load)`. |
| 109 | + */ |
| 110 | +class ValueCategory extends TValueCategory { |
| 111 | + string description; |
| 112 | + |
| 113 | + ValueCategory() { |
| 114 | + this = LValue(description) or this = PRValue(description) or this = XValue(description) |
| 115 | + } |
| 116 | + |
| 117 | + predicate isLValue() { this instanceof LValue } |
| 118 | + |
| 119 | + predicate isPRValue() { this instanceof PRValue } |
| 120 | + |
| 121 | + predicate isXValue() { this instanceof XValue } |
| 122 | + |
| 123 | + predicate isRValue() { this instanceof PRValue or this instanceof XValue } |
| 124 | + |
| 125 | + predicate isGLValue() { this instanceof LValue or this instanceof XValue } |
| 126 | + |
| 127 | + string toString() { result = description } |
| 128 | +} |
0 commit comments