diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preconditions2.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preconditions2.qll new file mode 100644 index 000000000..76625d5cb --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Preconditions2.qll @@ -0,0 +1,26 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Preconditions2Query = TInappropriateArgumentTypePassedViaEllipsisQuery() + +predicate isPreconditions2QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `inappropriateArgumentTypePassedViaEllipsis` query + Preconditions2Package::inappropriateArgumentTypePassedViaEllipsisQuery() and + queryId = + // `@id` for the `inappropriateArgumentTypePassedViaEllipsis` query + "cpp/misra/inappropriate-argument-type-passed-via-ellipsis" and + ruleId = "RULE-8-2-11" and + category = "required" +} + +module Preconditions2Package { + Query inappropriateArgumentTypePassedViaEllipsisQuery() { + //autogenerate `Query` type + result = + // `Query` type for `inappropriateArgumentTypePassedViaEllipsis` query + TQueryCPP(TPreconditions2PackageQuery(TInappropriateArgumentTypePassedViaEllipsisQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index d0ac06886..4fa538143 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -82,6 +82,7 @@ import OrderOfEvaluation import OutOfBounds import Pointers import Preconditions1 +import Preconditions2 import Preconditions3 import Preconditions4 import Preconditions5 @@ -190,6 +191,7 @@ newtype TCPPQuery = TOutOfBoundsPackageQuery(OutOfBoundsQuery q) or TPointersPackageQuery(PointersQuery q) or TPreconditions1PackageQuery(Preconditions1Query q) or + TPreconditions2PackageQuery(Preconditions2Query q) or TPreconditions3PackageQuery(Preconditions3Query q) or TPreconditions4PackageQuery(Preconditions4Query q) or TPreconditions5PackageQuery(Preconditions5Query q) or @@ -298,6 +300,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isOutOfBoundsQueryMetadata(query, queryId, ruleId, category) or isPointersQueryMetadata(query, queryId, ruleId, category) or isPreconditions1QueryMetadata(query, queryId, ruleId, category) or + isPreconditions2QueryMetadata(query, queryId, ruleId, category) or isPreconditions3QueryMetadata(query, queryId, ruleId, category) or isPreconditions4QueryMetadata(query, queryId, ruleId, category) or isPreconditions5QueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/misra/src/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.ql b/cpp/misra/src/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.ql new file mode 100644 index 000000000..85c134945 --- /dev/null +++ b/cpp/misra/src/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.ql @@ -0,0 +1,88 @@ +/** + * @id cpp/misra/inappropriate-argument-type-passed-via-ellipsis + * @name RULE-8-2-11: An argument passed via ellipsis shall have an appropriate type + * @description Passing arguments of certain class types via an ellipsis parameter is only + * conditionally-supported with implementation-defined behaviour. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-8-2-11 + * scope/single-translation-unit + * correctness + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.types.TrivialType + +/** + * Holds if `arg` is an argument passed via ellipsis in `call`. + */ +predicate isEllipsisArgument(FunctionCall call, Expr arg, int i) { + call.getTarget().isVarargs() and + arg = call.getArgument(i) and + i >= call.getTarget().getNumberOfParameters() +} + +/** + * Holds if `c` has virtual member functions. + */ +predicate hasVirtualMemberFunctions(Class c) { c.getAMemberFunction().isVirtual() } + +/** + * Holds if `c` has non-trivial copy or move operations. + */ +predicate hasNonTrivialCopyOrMove(Class c) { + exists(CopyConstructor cc | cc = c.getAConstructor() | + not cc instanceof TrivialCopyOrMoveConstructor + ) + or + exists(MoveConstructor mc | mc = c.getAConstructor() | + not mc instanceof TrivialCopyOrMoveConstructor + ) + or + exists(CopyAssignmentOperator ca | ca = c.getAMemberFunction() | + not ca instanceof TrivialCopyOrMoveAssignmentOperator + ) + or + exists(MoveAssignmentOperator ma | ma = c.getAMemberFunction() | + not ma instanceof TrivialCopyOrMoveAssignmentOperator + ) +} + +/** + * Holds if `c` has a non-trivial destructor. + */ +predicate hasNonTrivialDestructor(Class c) { not hasTrivialDestructor(c) } + +/** + * Gets the primary reason why `c` is an inappropriate type to pass via ellipsis, prioritized + * by the order of conditions in the rule amplification. + */ +string getInappropriateReason(Class c) { + if hasVirtualMemberFunctions(c) + then result = "has virtual member functions" + else + if hasNonTrivialCopyOrMove(c) + then result = "has non-trivial copy or move operations" + else ( + hasNonTrivialDestructor(c) and result = "has a non-trivial destructor" + ) +} + +from FunctionCall call, Expr arg, int i, Class argType, string reason +where + not isExcluded(arg, Preconditions2Package::inappropriateArgumentTypePassedViaEllipsisQuery()) and + isEllipsisArgument(call, arg, i) and + // `arg.getType()` is void for some constructor calls. These seem to have a conversion of class + // `TemporaryObjectExpr`, which seems to have the correct type. + argType = arg.getFullyConverted().getUnspecifiedType() and + reason = getInappropriateReason(argType) and + // Exclude unevaluated contexts + not arg.isUnevaluated() and + not call.isUnevaluated() +select arg, + "Argument of type '" + argType.getName() + "' passed via ellipsis to $@ " + reason + ".", + call.getTarget(), call.getTarget().getName() diff --git a/cpp/misra/test/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.expected b/cpp/misra/test/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.expected new file mode 100644 index 000000000..21e8be751 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.expected @@ -0,0 +1,13 @@ +| test.cpp:90:20:90:39 | call to VirtualMemberClass | Argument of type 'VirtualMemberClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:91:20:91:43 | call to VirtualDestructorClass | Argument of type 'VirtualDestructorClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:92:20:92:40 | 0 | Argument of type 'NonTrivialCopyClass' passed via ellipsis to $@ has non-trivial copy or move operations. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:93:20:93:40 | 0 | Argument of type 'NonTrivialMoveClass' passed via ellipsis to $@ has non-trivial copy or move operations. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:94:20:94:46 | 0 | Argument of type 'NonTrivialCopyAssignClass' passed via ellipsis to $@ has non-trivial copy or move operations. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:95:20:95:46 | 0 | Argument of type 'NonTrivialMoveAssignClass' passed via ellipsis to $@ has non-trivial copy or move operations. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:96:20:96:46 | 0 | Argument of type 'NonTrivialDestructorClass' passed via ellipsis to $@ has a non-trivial destructor. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:97:20:97:39 | call to DerivedFromVirtual | Argument of type 'DerivedFromVirtual' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:98:20:98:40 | call to MultipleIssuesClass | Argument of type 'MultipleIssuesClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:99:20:99:33 | call to VirtualMemberClass | Argument of type 'VirtualMemberClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:100:20:100:39 | call to VirtualMemberClass | Argument of type 'VirtualMemberClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:102:36:102:55 | call to VirtualMemberClass | Argument of type 'VirtualMemberClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:81:5:81:17 | variadic_func | variadic_func | +| test.cpp:122:25:122:44 | call to VirtualMemberClass | Argument of type 'VirtualMemberClass' passed via ellipsis to $@ has virtual member functions. | test.cpp:118:6:118:26 | variadic_take_objects | variadic_take_objects | diff --git a/cpp/misra/test/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.qlref b/cpp/misra/test/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.qlref new file mode 100644 index 000000000..a96034c4a --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.qlref @@ -0,0 +1 @@ +rules/RULE-8-2-11/InappropriateArgumentTypePassedViaEllipsis.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-11/test.cpp b/cpp/misra/test/rules/RULE-8-2-11/test.cpp new file mode 100644 index 000000000..b095b7201 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-11/test.cpp @@ -0,0 +1,123 @@ +// Trivial class - no virtual functions, trivial special members +struct TrivialClass { + int m1; +}; + +// Class with virtual member function +struct VirtualMemberClass { + int m1; + virtual void f1() {} +}; + +// Class with virtual destructor +struct VirtualDestructorClass { + int m1; + virtual ~VirtualDestructorClass() = default; +}; + +// Class with user-defined (non-trivial) copy constructor +struct NonTrivialCopyClass { + int m1; + NonTrivialCopyClass() = default; + NonTrivialCopyClass(const NonTrivialCopyClass &other) {} +}; + +// Class with user-defined (non-trivial) move constructor +struct NonTrivialMoveClass { + int m1; + NonTrivialMoveClass() = default; + NonTrivialMoveClass(NonTrivialMoveClass &&other) {} +}; + +// Class with user-defined (non-trivial) copy assignment operator +struct NonTrivialCopyAssignClass { + int m1; + NonTrivialCopyAssignClass &operator=(const NonTrivialCopyAssignClass &other) { + return *this; + } +}; + +// Class with user-defined (non-trivial) move assignment operator +struct NonTrivialMoveAssignClass { + int m1; + NonTrivialMoveAssignClass &operator=(NonTrivialMoveAssignClass &&other) { + return *this; + } +}; + +// Class with user-defined (non-trivial) destructor +struct NonTrivialDestructorClass { + ~NonTrivialDestructorClass() {} +}; + +// Derived class inheriting virtual +struct DerivedFromVirtual : public VirtualMemberClass { + void f1() override {} +}; + +// Class with multiple inappropriate properties +struct MultipleIssuesClass { + virtual void f1() {} + MultipleIssuesClass() = default; + MultipleIssuesClass(const MultipleIssuesClass &) {} + ~MultipleIssuesClass() {} +}; + +// Class with defaulted special members (trivial) +struct DefaultedClass { + int m1; + DefaultedClass() = default; + DefaultedClass(const DefaultedClass &) = default; + DefaultedClass(DefaultedClass &&) = default; + DefaultedClass &operator=(const DefaultedClass &) = default; + DefaultedClass &operator=(DefaultedClass &&) = default; + ~DefaultedClass() = default; +}; + +// A typedef for an inappropriate type +using VirtualAlias = VirtualMemberClass; + +// User-defined variadic function +int variadic_func(int p1, ...); + +void f() { + variadic_func(0, 42); // COMPLIANT + variadic_func(0, 3.14); // COMPLIANT + variadic_func(0, (void *)nullptr); // COMPLIANT + + variadic_func(0, TrivialClass()); // COMPLIANT + variadic_func(0, DefaultedClass()); // COMPLIANT + variadic_func(0, VirtualMemberClass()); // NON_COMPLIANT + variadic_func(0, VirtualDestructorClass()); // NON_COMPLIANT + variadic_func(0, NonTrivialCopyClass()); // NON_COMPLIANT + variadic_func(0, NonTrivialMoveClass()); // NON_COMPLIANT + variadic_func(0, NonTrivialCopyAssignClass()); // NON_COMPLIANT + variadic_func(0, NonTrivialMoveAssignClass()); // NON_COMPLIANT + variadic_func(0, NonTrivialDestructorClass()); // NON_COMPLIANT + variadic_func(0, DerivedFromVirtual()); // NON_COMPLIANT + variadic_func(0, MultipleIssuesClass()); // NON_COMPLIANT + variadic_func(0, VirtualAlias()); // NON_COMPLIANT + variadic_func(0, VirtualMemberClass()); // NON_COMPLIANT + + variadic_func(0, TrivialClass(), VirtualMemberClass()); // NON_COMPLIANT +} + +void test_unevaluated_context() { + sizeof(variadic_func(0, VirtualMemberClass())); // COMPLIANT +} + +// Parameter pack is not ellipsis +template void parameter_pack_func(Args... p1) {} + +void test_parameter_pack() { + VirtualMemberClass l1; + NonTrivialCopyClass l2; + parameter_pack_func(l1, l2); // COMPLIANT +} + +void variadic_take_objects(VirtualMemberClass v, ...); +void test_named_parameter_of_variadic_function() { + variadic_take_objects(VirtualMemberClass()); // COMPLIANT + variadic_take_objects(VirtualMemberClass(), + VirtualMemberClass()); // NON_COMPLIANT +} diff --git a/rule_packages/cpp/Preconditions2.json b/rule_packages/cpp/Preconditions2.json new file mode 100644 index 000000000..405b78ce4 --- /dev/null +++ b/rule_packages/cpp/Preconditions2.json @@ -0,0 +1,25 @@ +{ + "MISRA-C++-2023": { + "RULE-8-2-11": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Passing arguments of certain class types via an ellipsis parameter is only conditionally-supported with implementation-defined behaviour.", + "kind": "problem", + "name": "An argument passed via ellipsis shall have an appropriate type", + "precision": "very-high", + "severity": "error", + "short_name": "InappropriateArgumentTypePassedViaEllipsis", + "tags": [ + "scope/single-translation-unit", + "correctness" + ] + } + ], + "title": "An argument passed via ellipsis shall have an appropriate type" + } + } +} \ No newline at end of file diff --git a/rules.csv b/rules.csv index c1fd20174..ec864e6d0 100644 --- a/rules.csv +++ b/rules.csv @@ -892,7 +892,7 @@ cpp,MISRA-C++-2023,RULE-8-2-7,Yes,Advisory,Decidable,Single Translation Unit,A c cpp,MISRA-C++-2023,RULE-8-2-8,Yes,Required,Decidable,Single Translation Unit,An object pointer type shall not be cast to an integral type other than std::uintptr_t or std::intptr_t,"RULE-11-6, INT36-C",Conversions2,Easy, cpp,MISRA-C++-2023,RULE-8-2-9,Yes,Required,Decidable,Single Translation Unit,The operand to typeid shall not be an expression of polymorphic class type,,Preconditions1,Easy, cpp,MISRA-C++-2023,RULE-8-2-10,Yes,Required,Undecidable,System,"Functions shall not call themselves, either directly or indirectly",A7-5-2,ImportMisra23,Import, -cpp,MISRA-C++-2023,RULE-8-2-11,Yes,Required,Decidable,Single Translation Unit,An argument passed via ellipsis shall have an appropriate type,,Preconditions,Easy, +cpp,MISRA-C++-2023,RULE-8-2-11,Yes,Required,Decidable,Single Translation Unit,An argument passed via ellipsis shall have an appropriate type,,Preconditions2,Easy, cpp,MISRA-C++-2023,RULE-8-3-1,Yes,Advisory,Decidable,Single Translation Unit,The built-in unary - operator should not be applied to an expression of unsigned type,M5-3-2,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-8-3-2,Yes,Advisory,Decidable,Single Translation Unit,The built-in unary + operator should not be used,,Banned8,Easy, cpp,MISRA-C++-2023,RULE-8-7-1,Yes,Required,Undecidable,System,Pointer arithmetic shall not form an invalid pointer,ARR30-C,Memory1,Easy,