-
Notifications
You must be signed in to change notification settings - Fork 76
Implement rule 28-6-1, only move non-const lvalues #992
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
MichaelRFairhurst
wants to merge
6
commits into
main
Choose a base branch
from
michaelrfairhurst/preconditions5-only-std-move-lvalues
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e8760e3
Implement preconditions rule 28-6-1, std::move of rvalue or const lva…
MichaelRFairhurst a89fab7
typo fix
MichaelRFairhurst 45a7ecc
Address feedback
MichaelRFairhurst d4231c3
Remove usage of `ResolvesTo`.
MichaelRFairhurst c376edf
Fix GLValue capitalization.
MichaelRFairhurst 10e8c30
Test improvements
MichaelRFairhurst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
cpp/common/src/codingstandards/cpp/ast/ValueCategory.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| import cpp | ||
|
|
||
| /** | ||
| * Get an expression's value category as a ValueCategory object. | ||
| * | ||
| * Note that the standard cpp library exposes `is{_}ValueCategory` predicates, but they do not | ||
| * necessarily work as expected due to how CodeQL handles reference adjustment and binding, which | ||
| * this predicate attempts to handle. There are additional unhandled cases involving | ||
| * lvalue-to-rvalue conversions as well. | ||
| * | ||
| * In C++17, an expression of type "reference to T" is adjusted to type T as stated in [expr]/5. | ||
| * This is not a conversion, but in CodeQL this is handled by `ReferenceDereferenceExpr`, a type of | ||
| * `Conversion`. Similarly, the binding of references to values is described in [dcl.init.ref], | ||
| * which is not a conversion, but in CodeQL this is handled by `ReferenceToExpr`, another type of | ||
| * `Conversion`. | ||
| * | ||
| * Furthermore, the `Expr` predicate `hasLValueToRValueConversion()` uses a different dbscheme table | ||
| * than the `Conversion` table, and it is possible to have expressions such that | ||
| * `hasLValueToRValueConversion()` holds, but there is no corresponding `Conversion` entry. | ||
| * | ||
| * Therefore, the value categories of expressions before `ReferenceDereferenceExpr` and after | ||
| * `ReferenceToExpr` conversions are therefore essentially unspecified, and the `is{_}ValueCategory` | ||
| * predicate results should not be relied upon. And types that are missing a | ||
| * `LvalueToRValueConversion` will also return incorrect value categories. | ||
| * | ||
| * For example, in CodeQL 2.21.4: | ||
| * | ||
| * ```cpp | ||
| * int &i = ...; | ||
| * auto r = std::move(i); // 1.) i is a `prvalue(load)` in CodeQL, not an lvalue. | ||
| * // 2.) i has a `ReferenceDereferenceExpr` conversion of lvalue category | ||
| * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion of prvalue | ||
| * // category. | ||
| * int i2 = ...; | ||
| * f(i2); // 1.) i2 is an lvalue. | ||
| * // 2.) i2 undergoes lvalue-to-rvalue conversion, but there is no corresponding `Conversion`. | ||
| * // 3.) i2 is treated at a prvalue by CodeQL, but `hasLValueToRValueConversion()` holds. | ||
| * | ||
| * int get_int(); | ||
| * auto r2 = std::move(get_int()); // 1.) get_int() itself is treated as a prvalue by CodeQL. | ||
| * // 2.) get_int() has a `TemporaryObjectExpr` conversion of lvalue | ||
| * // category. | ||
| * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion | ||
| * // of prvalue category. | ||
| * std::string get_str(); | ||
| * auto r3 = std::move(get_str()); // 1.) get_str() is treated as a prvalue by CodeQL. | ||
| * // 2.) get_str() has a `TemporaryObjectExpr` conversion of xvalue | ||
| * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion | ||
| * // of prvalue category. | ||
| * std::string &str_ref(); | ||
| * auto r3 = std::move(str_ref()); // 1.) str_ref() is treated as a prvalue by CodeQL. | ||
| * // 2.) str_ref() has a `ReferenceDereferenceExpr` conversion of | ||
| * // lvalue. | ||
| * // 3.) the conversion from 2 has a `ReferenceToExpr` conversion | ||
| * // of prvalue category. | ||
| * ``` | ||
| * | ||
| * As can be seen above, the value categories of expressions are correct between the | ||
| * `ReferenceDereferenceExpr` and `ReferenceToExpr`, but not necessarily before or after. | ||
| * | ||
| * We must also check for `hasLValueToRValueConversion()` and handle that appropriately. | ||
| */ | ||
| ValueCategory getValueCategory(Expr e) { | ||
| // If `e` is adjusted from a reference to a value (C++17 [expr]/5) then we want the value category | ||
| // of the expression after `ReferenceDereferenceExpr`. | ||
| if e.getConversion() instanceof ReferenceDereferenceExpr | ||
| then result = getValueCategory(e.getConversion()) | ||
| else ( | ||
| // If `hasLValueToRValueConversion()` holds, then ensure we have an lvalue category. | ||
| if e.hasLValueToRValueConversion() | ||
| then result.isLValue() | ||
| else | ||
| // Otherwise, get the value category from `is{_}ValueCategory` predicates as normal. | ||
| result = getDirectValueCategory(e) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Gets the value category of an expression using `is{_}ValueCategory` predicates, without looking | ||
| * through conversions. | ||
| */ | ||
| private ValueCategory getDirectValueCategory(Expr e) { | ||
| if e.isLValueCategory() | ||
| then result = LValue(e.getValueCategoryString()) | ||
| else | ||
| if e.isPRValueCategory() | ||
| then result = PRValue(e.getValueCategoryString()) | ||
| else | ||
| if e.isXValueCategory() | ||
| then result = XValue(e.getValueCategoryString()) | ||
| else none() | ||
| } | ||
|
|
||
| newtype TValueCategory = | ||
| LValue(string descr) { | ||
| exists(Expr e | e.isLValueCategory() and descr = e.getValueCategoryString()) | ||
| } or | ||
| PRValue(string descr) { | ||
| exists(Expr e | e.isPRValueCategory() and descr = e.getValueCategoryString()) | ||
| } or | ||
| XValue(string descr) { | ||
| exists(Expr e | e.isXValueCategory() and descr = e.getValueCategoryString()) | ||
| } | ||
|
|
||
| /** | ||
| * A value category, which can be an lvalue, prvalue, or xvalue. | ||
| * | ||
| * Note that prvalue has two possible forms: `prvalue` and `prvalue(load)`. | ||
| */ | ||
| class ValueCategory extends TValueCategory { | ||
| string description; | ||
|
|
||
| ValueCategory() { | ||
| this = LValue(description) or this = PRValue(description) or this = XValue(description) | ||
| } | ||
|
|
||
| predicate isLValue() { this instanceof LValue } | ||
|
|
||
| predicate isPRValue() { this instanceof PRValue } | ||
|
|
||
| predicate isXValue() { this instanceof XValue } | ||
|
|
||
| predicate isRValue() { this instanceof PRValue or this instanceof XValue } | ||
|
|
||
| predicate isGLValue() { this instanceof LValue or this instanceof XValue } | ||
|
|
||
| string toString() { result = description } | ||
| } | ||
26 changes: 26 additions & 0 deletions
26
cpp/common/src/codingstandards/cpp/exclusions/cpp/Preconditions5.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| //** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ | ||
| import cpp | ||
| import RuleMetadata | ||
| import codingstandards.cpp.exclusions.RuleMetadata | ||
|
|
||
| newtype Preconditions5Query = TStdMoveWithNonConstLvalueQuery() | ||
|
|
||
| predicate isPreconditions5QueryMetadata(Query query, string queryId, string ruleId, string category) { | ||
| query = | ||
| // `Query` instance for the `stdMoveWithNonConstLvalue` query | ||
| Preconditions5Package::stdMoveWithNonConstLvalueQuery() and | ||
| queryId = | ||
| // `@id` for the `stdMoveWithNonConstLvalue` query | ||
| "cpp/misra/std-move-with-non-const-lvalue" and | ||
| ruleId = "RULE-28-6-1" and | ||
| category = "required" | ||
| } | ||
|
|
||
| module Preconditions5Package { | ||
| Query stdMoveWithNonConstLvalueQuery() { | ||
| //autogenerate `Query` type | ||
| result = | ||
| // `Query` type for `stdMoveWithNonConstLvalue` query | ||
| TQueryCPP(TPreconditions5PackageQuery(TStdMoveWithNonConstLvalueQuery())) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
cpp/common/test/library/codingstandards/cpp/ast/ValueCategory/ValueCategoryTest.expected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| | test.cpp:14:3:14:9 | call to get_val | prvalue | | ||
| | test.cpp:15:3:15:9 | call to get_ref | lvalue | | ||
| | test.cpp:16:3:16:10 | call to get_rref | xvalue | | ||
| | test.cpp:19:12:19:14 | val | lvalue | | ||
| | test.cpp:20:12:20:14 | val | lvalue | | ||
| | test.cpp:21:13:21:16 | call to move | xvalue | | ||
| | test.cpp:21:18:21:20 | val | lvalue | | ||
| | test.cpp:22:12:22:14 | val | lvalue | | ||
| | test.cpp:23:12:23:15 | call to move | xvalue | | ||
| | test.cpp:23:17:23:19 | val | lvalue | | ||
| | test.cpp:25:12:25:18 | call to get_val | prvalue | | ||
| | test.cpp:26:13:26:19 | call to get_val | prvalue | | ||
| | test.cpp:27:13:27:16 | call to move | xvalue | | ||
| | test.cpp:27:18:27:24 | call to get_val | prvalue | | ||
| | test.cpp:28:12:28:18 | call to get_val | prvalue | | ||
| | test.cpp:29:12:29:15 | call to move | xvalue | | ||
| | test.cpp:29:17:29:23 | call to get_val | prvalue | | ||
| | test.cpp:31:14:31:16 | val | lvalue | | ||
| | test.cpp:32:12:32:14 | ref | lvalue | | ||
| | test.cpp:33:12:33:14 | ref | lvalue | | ||
| | test.cpp:34:13:34:16 | call to move | xvalue | | ||
| | test.cpp:34:18:34:20 | ref | lvalue | | ||
| | test.cpp:35:12:35:14 | ref | lvalue | | ||
| | test.cpp:36:12:36:15 | call to move | xvalue | | ||
| | test.cpp:36:17:36:19 | ref | lvalue | | ||
| | test.cpp:38:12:38:18 | call to get_ref | lvalue | | ||
| | test.cpp:39:12:39:18 | call to get_ref | lvalue | | ||
| | test.cpp:40:13:40:16 | call to move | xvalue | | ||
| | test.cpp:40:18:40:24 | call to get_ref | lvalue | | ||
| | test.cpp:41:12:41:18 | call to get_ref | lvalue | | ||
| | test.cpp:42:12:42:15 | call to move | xvalue | | ||
| | test.cpp:42:17:42:23 | call to get_ref | lvalue | | ||
| | test.cpp:44:16:44:19 | call to move | xvalue | | ||
| | test.cpp:44:21:44:23 | val | lvalue | | ||
| | test.cpp:45:12:45:15 | rref | lvalue | | ||
| | test.cpp:46:12:46:15 | rref | lvalue | | ||
| | test.cpp:47:13:47:16 | call to move | xvalue | | ||
| | test.cpp:47:18:47:21 | rref | lvalue | | ||
| | test.cpp:48:12:48:15 | rref | lvalue | | ||
| | test.cpp:49:12:49:15 | call to move | xvalue | | ||
| | test.cpp:49:17:49:20 | rref | lvalue | | ||
| | test.cpp:51:12:51:19 | call to get_rref | lvalue | | ||
| | test.cpp:52:13:52:20 | call to get_rref | xvalue | | ||
| | test.cpp:53:13:53:16 | call to move | xvalue | | ||
| | test.cpp:53:18:53:25 | call to get_rref | xvalue | | ||
| | test.cpp:54:12:54:19 | call to get_rref | xvalue | | ||
| | test.cpp:55:12:55:15 | call to move | xvalue | | ||
| | test.cpp:55:17:55:24 | call to get_rref | xvalue | |
15 changes: 15 additions & 0 deletions
15
cpp/common/test/library/codingstandards/cpp/ast/ValueCategory/ValueCategoryTest.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import cpp | ||
| import codingstandards.cpp.ast.ValueCategory | ||
|
|
||
| predicate isRelevant(Expr e) { | ||
| e.(VariableAccess).getTarget().hasName(["val", "ref", "rref"]) | ||
| or | ||
| e.(FunctionCall).getTarget().hasName(["get_val", "get_ref", "get_rref", "move"]) | ||
| } | ||
|
|
||
| from Expr e, ValueCategory cat | ||
| where | ||
| e.getEnclosingFunction().hasName("main") and | ||
| isRelevant(e) and | ||
| cat = getValueCategory(e) | ||
| select e, cat |
58 changes: 58 additions & 0 deletions
58
cpp/common/test/library/codingstandards/cpp/ast/ValueCategory/test.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| void take_val(int) {} | ||
| void take_ref(int &) {} | ||
| void take_rref(int &&) {} | ||
| template <typename T> void take_uni(T &&) {} | ||
|
|
||
| int get_val(); | ||
| int &get_ref(); | ||
| int &&get_rref(); | ||
|
|
||
| template <typename T> T &&move(T &t) { return static_cast<T &&>(t); } | ||
| template <typename T> T &&move(T &&t) { return static_cast<T &&>(t); } | ||
|
|
||
| int main() { | ||
| get_val(); | ||
| get_ref(); | ||
| get_rref(); | ||
|
|
||
| int val = 42; | ||
| take_val(val); | ||
| take_ref(val); | ||
| take_rref(move(val)); | ||
| take_uni(val); | ||
| take_uni(move(val)); | ||
|
|
||
| take_val(get_val()); | ||
| take_rref(get_val()); | ||
| take_rref(move(get_val())); | ||
| take_uni(get_val()); | ||
| take_uni(move(get_val())); | ||
|
|
||
| int &ref = val; | ||
| take_val(ref); | ||
| take_ref(ref); | ||
| take_rref(move(ref)); | ||
| take_uni(ref); | ||
| take_uni(move(ref)); | ||
|
|
||
| take_val(get_ref()); | ||
| take_ref(get_ref()); | ||
| take_rref(move(get_ref())); | ||
| take_uni(get_ref()); | ||
| take_uni(move(get_ref())); | ||
|
|
||
| int &&rref = move(val); | ||
| take_val(rref); | ||
| take_ref(rref); | ||
| take_rref(move(rref)); | ||
| take_uni(rref); | ||
| take_uni(move(rref)); | ||
|
|
||
| take_val(get_rref()); | ||
| take_rref(get_rref()); | ||
| take_rref(move(get_rref())); | ||
| take_uni(get_rref()); | ||
| take_uni(move(get_rref())); | ||
|
|
||
| return 0; | ||
| } |
56 changes: 56 additions & 0 deletions
56
cpp/misra/src/rules/RULE-28-6-1/StdMoveWithNonConstLvalue.ql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /** | ||
| * @id cpp/misra/std-move-with-non-const-lvalue | ||
| * @name RULE-28-6-1: The argument to std::move shall be a non-const lvalue | ||
| * @description Calling std::move on a const lvalue will not result in a move, and calling std::move | ||
| * on an rvalue is redundant. | ||
| * @kind problem | ||
| * @precision very-high | ||
| * @problem.severity error | ||
| * @tags external/misra/id/rule-28-6-1 | ||
| * scope/single-translation-unit | ||
| * correctness | ||
| * maintainability | ||
| * external/misra/enforcement/decidable | ||
| * external/misra/obligation/required | ||
| */ | ||
|
|
||
| import cpp | ||
| import codingstandards.cpp.standardlibrary.Utility | ||
| import codingstandards.cpp.ast.ValueCategory | ||
|
|
||
| predicate resolvesToConstOrConstRef(Type t) { | ||
| t.isConst() | ||
| or | ||
| resolvesToConstOrConstRef(t.(ReferenceType).getBaseType()) | ||
| or | ||
| resolvesToConstOrConstRef(t.(TypedefType).getBaseType()) | ||
| or | ||
| resolvesToConstOrConstRef(t.(Decltype).getBaseType()) | ||
| } | ||
|
|
||
| predicate isConstLvalue(Expr arg) { | ||
| getValueCategory(arg).isLValue() and | ||
| resolvesToConstOrConstRef(arg.getType()) | ||
| } | ||
|
|
||
| Type typeOfArgument(Expr e) { | ||
| // An xvalue may be a constructor, which has no return type. However, these xvalues act as values | ||
| // of the constructed type. | ||
| if e instanceof ConstructorCall | ||
| then result = e.(ConstructorCall).getTargetType() | ||
| else result = e.getType() | ||
| } | ||
|
|
||
| from StdMoveCall call, Expr arg, string description | ||
| where | ||
| arg = call.getArgument(0) and | ||
| ( | ||
| isConstLvalue(arg) and | ||
| description = "const " + getValueCategory(arg).toString() | ||
| or | ||
| getValueCategory(arg).isRValue() and | ||
| description = getValueCategory(arg).toString() | ||
| ) | ||
| select call, | ||
| "Call to 'std::move' with " + description + " argument of type '" + typeOfArgument(arg).toString() | ||
| + "'." |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.