@@ -4,18 +4,81 @@ import cpp
44 * Get an expression's value category as a ValueCategory object.
55 *
66 * Note that the standard cpp library exposes `is{_}ValueCategory` predicates, but they do not
7- * correctly work with conversions. This function is intended to give the correct answer in the
8- * presence of conversions such as lvalue-to-rvalue conversion.
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.
962 */
1063ValueCategory getValueCategory ( Expr e ) {
11- not exists ( e .getConversion ( ) ) and
12- result = getDirectValueCategory ( e )
13- or
14- if e .getConversion ( ) instanceof ReferenceToExpr
15- then result = getDirectValueCategory ( e )
16- else result = getDirectValueCategory ( e .getConversion ( ) )
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+ )
1776}
1877
78+ /**
79+ * Gets the value category of an expression using `is{_}ValueCategory` predicates, without looking
80+ * through conversions.
81+ */
1982private ValueCategory getDirectValueCategory ( Expr e ) {
2083 if e .isLValueCategory ( )
2184 then result = LValue ( e .getValueCategoryString ( ) )
@@ -39,6 +102,11 @@ newtype TValueCategory =
39102 exists ( Expr e | e .isXValueCategory ( ) and descr = e .getValueCategoryString ( ) )
40103 }
41104
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+ */
42110class ValueCategory extends TValueCategory {
43111 string description ;
44112
0 commit comments