diff --git a/change_notes/2024-02-16-fix-fps-a5-1-1.md b/change_notes/2024-02-16-fix-fps-a5-1-1.md new file mode 100644 index 0000000000..11831f70ea --- /dev/null +++ b/change_notes/2024-02-16-fix-fps-a5-1-1.md @@ -0,0 +1,7 @@ +- `A5-1-1` - `LiteralValueUsedOutsideTypeInit.ql`: + - Address FP reported in #371. Exclude literals generated by uses of constexpr variables. + - Exclude literals used in class template instantiations. + - Update the alert message to adhere to the style-guide. + - Exclude boolean literals used as template arguments. + - Exclude `u` and `U` prefixed char literals. + - Exclude literals part of a class aggregate literal. diff --git a/cpp/autosar/src/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.ql b/cpp/autosar/src/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.ql index a83e3ade5d..a14681d95b 100644 --- a/cpp/autosar/src/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.ql +++ b/cpp/autosar/src/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.ql @@ -18,6 +18,7 @@ import cpp import codingstandards.cpp.autosar import codingstandards.cpp.LoggingOperation import codingstandards.cpp.Literals +import codingstandards.cpp.Cpp14Literal from Literal l where @@ -35,11 +36,11 @@ where // Exclude literal 0 not l.getValue() = "0" and // Exclude character literals - not l instanceof CharLiteral and + not l instanceof Cpp14Literal::CharLiteral and // Exclude `nullptr` not l.getType() instanceof NullPointerType and // Exclude boolean `true` and `false` - not l.getType() instanceof BoolType and + not l instanceof BoolLiteral and // Exclude empty string not l.getValue() = "" and // Template functions use literals to represent calls which are unknown @@ -51,7 +52,14 @@ where // Aggregate literal not l = any(ArrayOrVectorAggregateLiteral aal).getAnElementExpr(_).getAChild*() and // Ignore x - 1 expressions - not exists(SubExpr se | se.getRightOperand() = l and l.getValue() = "1") -select l, - "Literal value " + getTruncatedLiteralText(l) + " used outside of type initialization " + - l.getAPrimaryQlClass() + not exists(SubExpr se | se.getRightOperand() = l and l.getValue() = "1") and + // Exclude compile time computed integral literals as they can appear as integral literals + // when used as non-type template arguments. + // We limit ourselves to integral literals, because floating point literals as non-type + // template arguments are not supported in C++ 14. Those are supported shince C++ 20. + not l instanceof CompileTimeComputedIntegralLiteral and + // Exclude literals to instantiate a class template per example in the standard + // where an type of std::array is intialized with size 5. + not l = any(ClassTemplateInstantiation cti).getATemplateArgument() and + not l = any(ClassAggregateLiteral cal).getAFieldExpr(_) +select l, "Literal value '" + getTruncatedLiteralText(l) + "' used outside of type initialization." diff --git a/cpp/autosar/test/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.expected b/cpp/autosar/test/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.expected index 3212f14efb..22300512fc 100644 --- a/cpp/autosar/test/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.expected +++ b/cpp/autosar/test/rules/A5-1-1/LiteralValueUsedOutsideTypeInit.expected @@ -1,5 +1,8 @@ -| test.cpp:5:9:5:25 | constant string | Literal value "constant string" used outside of type initialization StringLiteral | -| test.cpp:14:23:14:25 | 100 | Literal value 100 used outside of type initialization Literal | -| test.cpp:54:7:54:7 | 1 | Literal value 1 used outside of type initialization Literal | -| test.cpp:75:23:75:28 | test | Literal value "test" used outside of type initialization StringLiteral | -| test.cpp:76:19:76:28 | not okay | Literal value "not okay" used outside of type initialization StringLiteral | +| test.cpp:5:9:5:25 | constant string | Literal value '"constant string"' used outside of type initialization. | +| test.cpp:14:23:14:25 | 100 | Literal value '100' used outside of type initialization. | +| test.cpp:54:7:54:7 | 1 | Literal value '1' used outside of type initialization. | +| test.cpp:75:23:75:28 | test | Literal value '"test"' used outside of type initialization. | +| test.cpp:76:19:76:28 | not okay | Literal value '"not okay"' used outside of type initialization. | +| test.cpp:104:8:104:8 | 4 | Literal value '4' used outside of type initialization. | +| test.cpp:104:8:104:8 | 4 | Literal value '4' used outside of type initialization. | +| test.cpp:104:8:104:8 | 4 | Literal value '4' used outside of type initialization. | diff --git a/cpp/autosar/test/rules/A5-1-1/test.cpp b/cpp/autosar/test/rules/A5-1-1/test.cpp index 4c4ad4fb30..65e691fd32 100644 --- a/cpp/autosar/test/rules/A5-1-1/test.cpp +++ b/cpp/autosar/test/rules/A5-1-1/test.cpp @@ -80,10 +80,46 @@ void test_not_wrapper_stream(std::ostream &os, const char *str) noexcept { #define MACRO_LOG(test_str) \ do { \ struct test_struct { \ - static const char *get_str() { return static_cast(test_str); } \ + static const char *get_str() { \ + return static_cast(test_str); \ + } \ }; \ } while (false) void f() { MACRO_LOG("test"); // COMPLIANT - exclusion +} + +template struct S1 { static constexpr size_t value(); }; + +template <> struct S1 { + static constexpr size_t value() { return sizeof(int); }; +}; + +constexpr size_t g1 = S1::value(); +constexpr size_t f1() { return sizeof(int); } + +template struct S2 { + T m1[size]; // COMPLIANT + T m2[4]; // NON_COMPLIANT +}; + +template struct S3 { + static constexpr T value = val; // COMPLIANT; +}; + +void test_fp_reported_in_371() { + struct S2 l1; // COMPLIANT + struct S2 l2; // COMPLIANT + struct S2 l3; // COMPLIANT + + S3 l4; // COMPLIANT + S3::value> l5; // COMPLIANT + S3 l6; // COMPLIANT + S3::value> l7; // COMPLIANT + + constexpr float l8 = 3.14159f; +#define delta 0.1f + for (float i = 0.0f; i < l8; i += delta) { // COMPLIANT + } } \ No newline at end of file diff --git a/cpp/common/src/codingstandards/cpp/Cpp14Literal.qll b/cpp/common/src/codingstandards/cpp/Cpp14Literal.qll index c3908008ef..c974ec7eb8 100644 --- a/cpp/common/src/codingstandards/cpp/Cpp14Literal.qll +++ b/cpp/common/src/codingstandards/cpp/Cpp14Literal.qll @@ -82,4 +82,26 @@ module Cpp14Literal { override string getAPrimaryQlClass() { result = "FloatingLiteral" } } + + /** + * A character literal. For example: + * ``` + * char c1 = 'a'; + * char16_t c2 = u'a'; + * char32_t c3 = U'a'; + * wchar_t c4 = L'b'; + * ``` + */ + class CharLiteral extends StandardLibrary::TextLiteral { + CharLiteral() { this.getValueText().regexpMatch("(?s)\\s*(L|u|U)?'.*") } + + override string getAPrimaryQlClass() { result = "CharLiteral" } + + /** + * Gets the character of this literal. For example `L'a'` has character `"a"`. + */ + string getCharacter() { + result = this.getValueText().regexpCapture("(?s)\\s*(L|u|U)?'(.*)'", 1) + } + } } diff --git a/cpp/common/src/codingstandards/cpp/Literals.qll b/cpp/common/src/codingstandards/cpp/Literals.qll index d4e11154fa..38f2fb0e8b 100644 --- a/cpp/common/src/codingstandards/cpp/Literals.qll +++ b/cpp/common/src/codingstandards/cpp/Literals.qll @@ -3,6 +3,7 @@ */ import cpp +import codingstandards.cpp.Cpp14Literal /** Gets `Literal.getValueText()` truncated to at most 20 characters. */ string getTruncatedLiteralText(Literal l) { @@ -28,3 +29,38 @@ class Utf16StringLiteral extends StringLiteral { class Utf32StringLiteral extends StringLiteral { Utf32StringLiteral() { this.getValueText().regexpMatch("(?s)\\s*U\".*") } } + +/** + * A literal resulting from the use of a constexpr + * variable, or macro expansion. + * We rely on the fact that the value text of a literal is equal to the + * `constexpr` variable or macro name. + */ +class CompileTimeComputedIntegralLiteral extends Literal { + CompileTimeComputedIntegralLiteral() { + this.getUnspecifiedType() instanceof IntegralType and + // Exclude bool, whose value text is true or false, but the value itself + // is 1 or 0. + not this instanceof BoolLiteral and + // Exclude character literals, whose value text is the quoted character, but the value + // is the numeric value of the character. + not this instanceof Cpp14Literal::CharLiteral and + not this.getValueText() + .trim() + .regexpMatch("([0-9][0-9']*|0[xX][0-9a-fA-F']+|0b[01']+)[uU]?([lL]{1,2}|[zZ])?") and + // Exclude class field initializers whose value text equals the initializer expression, e.g., `x(0)` + not any(ConstructorFieldInit cfi).getExpr() = this + } +} + +class BoolLiteral extends Literal { + BoolLiteral() { + this.getType() instanceof BoolType + or + // When used as non-type template arguments, bool literals might + // have been converted to a non-bool type. + this.getValue() = "1" and this.getValueText() = "true" + or + this.getValue() = "0" and this.getValueText() = "false" + } +}