diff --git a/change_notes/2025-08-15-m5-2-2-virtual-base.md b/change_notes/2025-08-15-m5-2-2-virtual-base.md new file mode 100644 index 0000000000..f57b630ad9 --- /dev/null +++ b/change_notes/2025-08-15-m5-2-2-virtual-base.md @@ -0,0 +1,5 @@ + - `M5-2-2` - `PointerToAVirtualBaseClassCastToAPointer.ql`: + - Report casts where the from or to types are typedefs to virtual base classes or derived classes. + - Report casts to a reference type which is a derived type. + - Report casts where the base class is the parent of a virtual base class. + - The alert message has been updated to refer to the virtual base class derivation. \ No newline at end of file diff --git a/change_notes/2025-08-18-fix-alert-reporting.md b/change_notes/2025-08-18-fix-alert-reporting.md new file mode 100644 index 0000000000..fc1883ed7a --- /dev/null +++ b/change_notes/2025-08-18-fix-alert-reporting.md @@ -0,0 +1,3 @@ + - `RULE-1-2`, `RULE-23-3`, `RULE-23-5`, `RULE-23-6`: + - Results that occur in nested macro invocations are now reported in the macro that defines the contravening code, rather than the macro which is first expanded. + - Results the occur in arguments to macro invocations are now reported in at the macro invocation site, instead of the macro definition site. \ No newline at end of file diff --git a/cpp/autosar/src/rules/A5-2-2/TraditionalCStyleCastsUsed.ql b/cpp/autosar/src/rules/A5-2-2/TraditionalCStyleCastsUsed.ql index 66a289dfe0..7e2d19d336 100644 --- a/cpp/autosar/src/rules/A5-2-2/TraditionalCStyleCastsUsed.ql +++ b/cpp/autosar/src/rules/A5-2-2/TraditionalCStyleCastsUsed.ql @@ -16,6 +16,7 @@ import cpp import codingstandards.cpp.autosar import codingstandards.cpp.Macro +import codingstandards.cpp.CStyleCasts /** * Gets the macro (if any) that generated the given `CStyleCast`. @@ -61,18 +62,10 @@ Macro getGeneratedFrom(CStyleCast c) { * argument type is compatible with a single-argument constructor. */ -from CStyleCast c, string extraMessage, Locatable l, string supplementary +from ExplicitUserDefinedCStyleCast c, string extraMessage, Locatable l, string supplementary where not isExcluded(c, BannedSyntaxPackage::traditionalCStyleCastsUsedQuery()) and - not c.isImplicit() and - not c.getType() instanceof UnknownType and - // For casts in templates that occur on types related to a template parameter, the copy of th - // cast in the uninstantiated template is represented as a `CStyleCast` even if in practice all - // the instantiations represent it as a `ConstructorCall`. To avoid the common false positive case - // of using the functional cast notation to call a constructor we exclude all `CStyleCast`s on - // uninstantiated templates, and instead rely on reporting results within instantiations. - not c.isFromUninstantiatedTemplate(_) and - // Exclude casts created from macro invocations of macros defined by third parties + // Not generated from a library macro not getGeneratedFrom(c) instanceof LibraryMacro and // If the cast was generated from a user-provided macro, then report the macro that generated the // cast, as the macro itself may have generated the cast diff --git a/cpp/autosar/src/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.ql b/cpp/autosar/src/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.ql index 086aa40ae7..ad49d9e151 100644 --- a/cpp/autosar/src/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.ql +++ b/cpp/autosar/src/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.ql @@ -15,14 +15,11 @@ import cpp import codingstandards.cpp.autosar +import codingstandards.cpp.rules.pointertoavirtualbaseclasscasttoapointer.PointerToAVirtualBaseClassCastToAPointer -from Cast cast, VirtualBaseClass castFrom, Class castTo -where - not isExcluded(cast, PointersPackage::pointerToAVirtualBaseClassCastToAPointerQuery()) and - not cast instanceof DynamicCast and - castFrom = cast.getExpr().getType().(PointerType).getBaseType() and - cast.getType().(PointerType).getBaseType() = castTo and - castTo = castFrom.getADerivedClass+() -select cast, - "A pointer to virtual base class $@ is not cast to a pointer of derived class $@ using a dynamic_cast.", - castFrom, castFrom.getName(), castTo, castTo.getName() +class PointerToAVirtualBaseClassCastToAPointerQuery extends PointerToAVirtualBaseClassCastToAPointerSharedQuery +{ + PointerToAVirtualBaseClassCastToAPointerQuery() { + this = PointersPackage::pointerToAVirtualBaseClassCastToAPointerQuery() + } +} diff --git a/cpp/autosar/test/rules/A5-2-2/test.cpp b/cpp/autosar/test/rules/A5-2-2/test.cpp index fb39868560..d585efc37b 100644 --- a/cpp/autosar/test/rules/A5-2-2/test.cpp +++ b/cpp/autosar/test/rules/A5-2-2/test.cpp @@ -2,7 +2,7 @@ #include #include int foo() { return 1; } - +// A copy of 8.2,2 test.cpp, but with different cases compliant/non-compliant void test_c_style_cast() { double f = 3.14; std::uint32_t n1 = (std::uint32_t)f; // NON_COMPLIANT - C-style cast diff --git a/cpp/autosar/test/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.expected b/cpp/autosar/test/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.expected deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/cpp/autosar/test/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.qlref b/cpp/autosar/test/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.qlref deleted file mode 100644 index c48e9498cc..0000000000 --- a/cpp/autosar/test/rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.qlref +++ /dev/null @@ -1 +0,0 @@ -rules/M5-2-2/PointerToAVirtualBaseClassCastToAPointer.ql \ No newline at end of file diff --git a/cpp/autosar/test/rules/M5-2-2/test.cpp b/cpp/autosar/test/rules/M5-2-2/test.cpp deleted file mode 100644 index 59c0cb4b37..0000000000 --- a/cpp/autosar/test/rules/M5-2-2/test.cpp +++ /dev/null @@ -1,18 +0,0 @@ -class C1 { -public: - virtual ~C1() {} -}; -class C2 : public virtual C1 { -public: - C2() {} - ~C2() {} -}; - -void f1() { - C2 l1; - C1 *p1 = &l1; - - // C2 *p2 = static_cast(p1); // NON_COMPLIANT, prohibited by compiler - C2 *p3 = dynamic_cast(p1); // COMPLIANT - C2 &l2 = dynamic_cast(*p1); // COMPLIANT -} diff --git a/cpp/common/src/codingstandards/cpp/AlertReporting.qll b/cpp/common/src/codingstandards/cpp/AlertReporting.qll index 3ef5315906..a85ae4097b 100644 --- a/cpp/common/src/codingstandards/cpp/AlertReporting.qll +++ b/cpp/common/src/codingstandards/cpp/AlertReporting.qll @@ -14,15 +14,28 @@ module MacroUnwrapper { * Gets a macro invocation that applies to the result element. */ private MacroInvocation getAMacroInvocation(ResultElement re) { - result.getAnExpandedElement() = re + result.getAnAffectedElement() = re + } + + private MacroInvocation getASubsumedMacroInvocation(ResultElement re) { + result = getAMacroInvocation(re) and + // Only report cases where the element is not located at the macro expansion site + // This means we'll report results in macro arguments in the macro argument + // location, not within the macro itself. + // + // Do not join start column values. + pragma[only_bind_out](result.getLocation().getStartColumn()) = + pragma[only_bind_out](re.getLocation().getStartColumn()) } /** * Gets the primary macro invocation that generated the result element. + * + * Does not hold for cases where the result element is located at a macro argument site. */ MacroInvocation getPrimaryMacroInvocation(ResultElement re) { exists(MacroInvocation mi | - mi = getAMacroInvocation(re) and + mi = getASubsumedMacroInvocation(re) and // No other more specific macro that expands to element not exists(MacroInvocation otherMi | otherMi = getAMacroInvocation(re) and otherMi.getParentInvocation() = mi diff --git a/cpp/common/src/codingstandards/cpp/CStyleCasts.qll b/cpp/common/src/codingstandards/cpp/CStyleCasts.qll new file mode 100644 index 0000000000..b387f9898e --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/CStyleCasts.qll @@ -0,0 +1,18 @@ +import cpp +import codingstandards.cpp.Macro + +/** + * A C-style cast that is explicitly user written and has a known target type. + */ +class ExplicitUserDefinedCStyleCast extends CStyleCast { + ExplicitUserDefinedCStyleCast() { + not this.isImplicit() and + not this.getType() instanceof UnknownType and + // For casts in templates that occur on types related to a template parameter, the copy of th + // cast in the uninstantiated template is represented as a `CStyleCast` even if in practice all + // the instantiations represent it as a `ConstructorCall`. To avoid the common false positive case + // of using the functional cast notation to call a constructor we exclude all `CStyleCast`s on + // uninstantiated templates, and instead rely on reporting results within instantiations. + not this.isFromUninstantiatedTemplate(_) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Conversions2.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Conversions2.qll new file mode 100644 index 0000000000..a250b4b5b1 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Conversions2.qll @@ -0,0 +1,112 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Conversions2Query = + TVirtualBaseClassCastToDerivedQuery() or + TNoCStyleOrFunctionalCastsQuery() or + TIntToPointerCastProhibitedQuery() or + TNoPointerToIntegralCastQuery() or + TPointerToIntegralCastQuery() or + TNoStandaloneTypeCastExpressionQuery() + +predicate isConversions2QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `virtualBaseClassCastToDerived` query + Conversions2Package::virtualBaseClassCastToDerivedQuery() and + queryId = + // `@id` for the `virtualBaseClassCastToDerived` query + "cpp/misra/virtual-base-class-cast-to-derived" and + ruleId = "RULE-8-2-1" and + category = "required" + or + query = + // `Query` instance for the `noCStyleOrFunctionalCasts` query + Conversions2Package::noCStyleOrFunctionalCastsQuery() and + queryId = + // `@id` for the `noCStyleOrFunctionalCasts` query + "cpp/misra/no-c-style-or-functional-casts" and + ruleId = "RULE-8-2-2" and + category = "required" + or + query = + // `Query` instance for the `intToPointerCastProhibited` query + Conversions2Package::intToPointerCastProhibitedQuery() and + queryId = + // `@id` for the `intToPointerCastProhibited` query + "cpp/misra/int-to-pointer-cast-prohibited" and + ruleId = "RULE-8-2-6" and + category = "required" + or + query = + // `Query` instance for the `noPointerToIntegralCast` query + Conversions2Package::noPointerToIntegralCastQuery() and + queryId = + // `@id` for the `noPointerToIntegralCast` query + "cpp/misra/no-pointer-to-integral-cast" and + ruleId = "RULE-8-2-7" and + category = "advisory" + or + query = + // `Query` instance for the `pointerToIntegralCast` query + Conversions2Package::pointerToIntegralCastQuery() and + queryId = + // `@id` for the `pointerToIntegralCast` query + "cpp/misra/pointer-to-integral-cast" and + ruleId = "RULE-8-2-8" and + category = "required" + or + query = + // `Query` instance for the `noStandaloneTypeCastExpression` query + Conversions2Package::noStandaloneTypeCastExpressionQuery() and + queryId = + // `@id` for the `noStandaloneTypeCastExpression` query + "cpp/misra/no-standalone-type-cast-expression" and + ruleId = "RULE-9-2-1" and + category = "required" +} + +module Conversions2Package { + Query virtualBaseClassCastToDerivedQuery() { + //autogenerate `Query` type + result = + // `Query` type for `virtualBaseClassCastToDerived` query + TQueryCPP(TConversions2PackageQuery(TVirtualBaseClassCastToDerivedQuery())) + } + + Query noCStyleOrFunctionalCastsQuery() { + //autogenerate `Query` type + result = + // `Query` type for `noCStyleOrFunctionalCasts` query + TQueryCPP(TConversions2PackageQuery(TNoCStyleOrFunctionalCastsQuery())) + } + + Query intToPointerCastProhibitedQuery() { + //autogenerate `Query` type + result = + // `Query` type for `intToPointerCastProhibited` query + TQueryCPP(TConversions2PackageQuery(TIntToPointerCastProhibitedQuery())) + } + + Query noPointerToIntegralCastQuery() { + //autogenerate `Query` type + result = + // `Query` type for `noPointerToIntegralCast` query + TQueryCPP(TConversions2PackageQuery(TNoPointerToIntegralCastQuery())) + } + + Query pointerToIntegralCastQuery() { + //autogenerate `Query` type + result = + // `Query` type for `pointerToIntegralCast` query + TQueryCPP(TConversions2PackageQuery(TPointerToIntegralCastQuery())) + } + + Query noStandaloneTypeCastExpressionQuery() { + //autogenerate `Query` type + result = + // `Query` type for `noStandaloneTypeCastExpression` query + TQueryCPP(TConversions2PackageQuery(TNoStandaloneTypeCastExpressionQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index e5b683984b..e7b834bd88 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -14,6 +14,7 @@ import Concurrency import Conditionals import Const import Conversions +import Conversions2 import DeadCode import Declarations import ExceptionSafety @@ -71,6 +72,7 @@ newtype TCPPQuery = TConditionalsPackageQuery(ConditionalsQuery q) or TConstPackageQuery(ConstQuery q) or TConversionsPackageQuery(ConversionsQuery q) or + TConversions2PackageQuery(Conversions2Query q) or TDeadCodePackageQuery(DeadCodeQuery q) or TDeclarationsPackageQuery(DeclarationsQuery q) or TExceptionSafetyPackageQuery(ExceptionSafetyQuery q) or @@ -128,6 +130,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat isConditionalsQueryMetadata(query, queryId, ruleId, category) or isConstQueryMetadata(query, queryId, ruleId, category) or isConversionsQueryMetadata(query, queryId, ruleId, category) or + isConversions2QueryMetadata(query, queryId, ruleId, category) or isDeadCodeQueryMetadata(query, queryId, ruleId, category) or isDeclarationsQueryMetadata(query, queryId, ruleId, category) or isExceptionSafetyQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.qll b/cpp/common/src/codingstandards/cpp/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.qll new file mode 100644 index 0000000000..6993778a4d --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.qll @@ -0,0 +1,42 @@ +/** + * Provides a library with a `problems` predicate for the following issue: + * A pointer to a virtual base class shall only be cast to a pointer to a derived class + * by means of dynamic_cast, otherwise the cast has undefined behavior. + */ + +import cpp +import codingstandards.cpp.Customizations +import codingstandards.cpp.Exclusions + +abstract class PointerToAVirtualBaseClassCastToAPointerSharedQuery extends Query { } + +Query getQuery() { result instanceof PointerToAVirtualBaseClassCastToAPointerSharedQuery } + +query predicate problems( + Cast cast, string message, VirtualClassDerivation derivation, string derivationDescription +) { + exists(Class castFrom, VirtualBaseClass virtualBaseClass, Class castTo, string type | + not isExcluded(cast, getQuery()) and + not cast instanceof DynamicCast and + castTo = virtualBaseClass.getADerivedClass+() and + virtualBaseClass = castFrom.getADerivedClass*() and + derivation = virtualBaseClass.getAVirtualDerivation() and + derivation.getDerivedClass().getADerivedClass*() = castTo and + derivationDescription = "derived through virtual base class " + virtualBaseClass.getName() and + message = + "dynamic_cast not used for cast from " + type + " to base class " + castFrom.getName() + + " to derived class " + castTo.getName() + " which is $@." + | + // Pointer cast + castFrom = cast.getExpr().getType().stripTopLevelSpecifiers().(PointerType).getBaseType() and + cast.getType().stripTopLevelSpecifiers().(PointerType).getBaseType() = castTo and + type = "pointer" + or + // Reference type cast + castFrom = cast.getExpr().getType().stripTopLevelSpecifiers() and + // Not actually represented as a reference type in our model - instead as the + // type itself + cast.getType().stripTopLevelSpecifiers() = castTo and + type = "reference" + ) +} diff --git a/cpp/common/test/includes/standard-library/cstdint b/cpp/common/test/includes/standard-library/cstdint index f84fe3e4bb..cbaffaa8d0 100644 --- a/cpp/common/test/includes/standard-library/cstdint +++ b/cpp/common/test/includes/standard-library/cstdint @@ -16,5 +16,8 @@ using ::uint_fast32_t; using ::uint_fast64_t; using ::uint_fast8_t; using ::uintmax_t; + +using ::intptr_t; +using ::uintptr_t; } // namespace std #endif // _GHLIBCPP_CSTDINT \ No newline at end of file diff --git a/cpp/common/test/includes/standard-library/mutex.h b/cpp/common/test/includes/standard-library/mutex.h index 4c49819ddd..d21042dcf7 100644 --- a/cpp/common/test/includes/standard-library/mutex.h +++ b/cpp/common/test/includes/standard-library/mutex.h @@ -1,6 +1,6 @@ #ifndef _GHLIBCPP_MUTEX #define _GHLIBCPP_MUTEX -#include "chrono.h" +#include namespace std { @@ -77,6 +77,14 @@ template class lock_guard { mutex_type &_m; }; +template class scoped_lock { +public: + explicit scoped_lock(MutexTypes &...m); + scoped_lock(const scoped_lock &) = delete; + scoped_lock &operator=(const scoped_lock &) = delete; + ~scoped_lock(); +}; + } // namespace std #endif // _GHLIBCPP_MUTEX diff --git a/cpp/common/test/includes/standard-library/stdint.h b/cpp/common/test/includes/standard-library/stdint.h index ab57461648..75827a8068 100644 --- a/cpp/common/test/includes/standard-library/stdint.h +++ b/cpp/common/test/includes/standard-library/stdint.h @@ -17,4 +17,7 @@ typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; +typedef long intptr_t; +typedef unsigned long uintptr_t; + #endif // _GHLIBCPP_STDINT \ No newline at end of file diff --git a/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.expected b/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.expected new file mode 100644 index 0000000000..37e2557b81 --- /dev/null +++ b/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.expected @@ -0,0 +1,4 @@ +| test.cpp:41:12:41:37 | reinterpret_cast... | dynamic_cast not used for cast from pointer to base class C1 to derived class C2 which is $@. | test.cpp:11:12:11:28 | derivation | derived through virtual base class C1 | +| test.cpp:47:12:47:38 | reinterpret_cast... | dynamic_cast not used for cast from reference to base class C1 to derived class C2 which is $@. | test.cpp:11:12:11:28 | derivation | derived through virtual base class C1 | +| test.cpp:53:17:53:48 | reinterpret_cast... | dynamic_cast not used for cast from reference to base class C1 to derived class C2 which is $@. | test.cpp:11:12:11:28 | derivation | derived through virtual base class C1 | +| test.cpp:86:12:86:37 | reinterpret_cast... | dynamic_cast not used for cast from pointer to base class C0 to derived class C2 which is $@. | test.cpp:11:12:11:28 | derivation | derived through virtual base class C1 | diff --git a/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.ql b/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.ql new file mode 100644 index 0000000000..973b707e13 --- /dev/null +++ b/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.ql @@ -0,0 +1,4 @@ +// GENERATED FILE - DO NOT MODIFY +import codingstandards.cpp.rules.pointertoavirtualbaseclasscasttoapointer.PointerToAVirtualBaseClassCastToAPointer + +class TestFileQuery extends PointerToAVirtualBaseClassCastToAPointerSharedQuery, TestQuery { } diff --git a/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/test.cpp b/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/test.cpp new file mode 100644 index 0000000000..ab4666069e --- /dev/null +++ b/cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/test.cpp @@ -0,0 +1,87 @@ +class C0 { +public: + virtual ~C0() {} +}; + +class C1 : public C0 { +public: + virtual ~C1() {} +}; + +class C2 : public virtual C1 { +public: + C2() {} + ~C2() {} +}; + +typedef C1 Base; +typedef C2 Derived; + +void test_dynamic_cast_pointer_compliant() { + C2 l1; + C1 *p1 = &l1; + C2 *p2 = dynamic_cast(p1); // COMPLIANT +} + +void test_dynamic_cast_reference_compliant() { + C2 l1; + C1 *p1 = &l1; + C2 &l2 = dynamic_cast(*p1); // COMPLIANT +} + +void test_dynamic_cast_reference_compliant_typedefs() { + Derived l1; + Base *p1 = &l1; + Derived &l2 = dynamic_cast(*p1); // COMPLIANT +} + +void test_reinterpret_cast_pointer_non_compliant() { + C2 l1; + C1 *p1 = &l1; + C2 *p2 = reinterpret_cast(p1); // NON_COMPLIANT +} + +void test_reinterpret_cast_reference_non_compliant() { + C2 l1; + C1 *p1 = &l1; + C2 &l2 = reinterpret_cast(*p1); // NON_COMPLIANT +} + +void test_reinterpret_cast_typedefs_non_compliant() { + Derived l1; + Base *p1 = &l1; + Derived &l2 = reinterpret_cast(*p1); // NON_COMPLIANT +} + +void test_c_style_cast_pointer_non_compliant() { + C2 l1; + C1 *p1 = &l1; + // C2 *p2 = (C2 *)p1; // NON_COMPLIANT - prohibited by the compiler +} + +void test_c_style_cast_reference_non_compliant() { + C2 l1; + C1 *p1 = &l1; + // C2 &l2 = (C2 &)*p1; // NON_COMPLIANT - prohibited by the compiler +} + +void test_static_cast_pointer_non_compliant() { + C2 l1; + C1 *p1 = &l1; + // C2 *p2 = static_cast(p1); // NON_COMPLIANT - prohibited by the + // compiler +} + +void test_static_cast_reference_non_compliant() { + C2 l1; + C1 *p1 = &l1; + // C2 &l2 = static_cast(*p1); // NON_COMPLIANT - prohibited by the + // compiler +} + +void test_skipped_base_class() { + C2 l1; + C0 *p1 = &l1; + C2 *l2 = dynamic_cast(p1); // COMPLIANT + C2 *p2 = reinterpret_cast(p1); // NON_COMPLIANT +} \ No newline at end of file diff --git a/cpp/misra/src/rules/RULE-8-2-1/VirtualBaseClassCastToDerived.ql b/cpp/misra/src/rules/RULE-8-2-1/VirtualBaseClassCastToDerived.ql new file mode 100644 index 0000000000..858aaec42c --- /dev/null +++ b/cpp/misra/src/rules/RULE-8-2-1/VirtualBaseClassCastToDerived.ql @@ -0,0 +1,25 @@ +/** + * @id cpp/misra/virtual-base-class-cast-to-derived + * @name RULE-8-2-1: A virtual base class shall only be cast to a derived class by means of dynamic_cast + * @description Casting from a virtual base class to a derived class using anything other than + * dynamic_cast causes undefined behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-8-2-1 + * scope/single-translation-unit + * correctness + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.rules.pointertoavirtualbaseclasscasttoapointer.PointerToAVirtualBaseClassCastToAPointer + +class VirtualBaseClassCastToDerivedQuery extends PointerToAVirtualBaseClassCastToAPointerSharedQuery +{ + VirtualBaseClassCastToDerivedQuery() { + this = Conversions2Package::virtualBaseClassCastToDerivedQuery() + } +} diff --git a/cpp/misra/src/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.ql b/cpp/misra/src/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.ql new file mode 100644 index 0000000000..441720c5e4 --- /dev/null +++ b/cpp/misra/src/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.ql @@ -0,0 +1,35 @@ +/** + * @id cpp/misra/no-c-style-or-functional-casts + * @name RULE-8-2-2: C-style casts and functional notation casts shall not be used + * @description Using C-style casts or functional notation casts allows unsafe type conversions and + * makes code harder to maintain compared to using named casts like const_cast, + * dynamic_cast, static_cast and reinterpret_cast. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-8-2-2 + * scope/single-translation-unit + * readability + * correctness + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.CStyleCasts +import codingstandards.cpp.AlertReporting + +class MISRACPPProhibitedCStyleCasts extends ExplicitUserDefinedCStyleCast { + MISRACPPProhibitedCStyleCasts() { + // MISRA C++ permits casts to void types + not getType() instanceof VoidType + } +} + +from Element reportingElement, MISRACPPProhibitedCStyleCasts c +where + not isExcluded(c, Conversions2Package::noCStyleOrFunctionalCastsQuery()) and + reportingElement = MacroUnwrapper::unwrapElement(c) and + exists(reportingElement.getFile().getRelativePath()) // within user code - validated during testing +select reportingElement, "Use of explicit c-style cast to " + c.getType().getName() + "." diff --git a/cpp/misra/src/rules/RULE-8-2-6/IntToPointerCastProhibited.ql b/cpp/misra/src/rules/RULE-8-2-6/IntToPointerCastProhibited.ql new file mode 100644 index 0000000000..0a97dc5359 --- /dev/null +++ b/cpp/misra/src/rules/RULE-8-2-6/IntToPointerCastProhibited.ql @@ -0,0 +1,42 @@ +/** + * @id cpp/misra/int-to-pointer-cast-prohibited + * @name RULE-8-2-6: An object with integral, enumerated, or pointer to void type shall not be cast to a pointer type + * @description Casting from an integral type, enumerated type, or pointer to void type to a pointer + * type leads to unspecified behavior and is error prone. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-8-2-6 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from Cast cast, Type sourceType, PointerType targetType, string typeKind +where + not isExcluded(cast, Conversions2Package::intToPointerCastProhibitedQuery()) and + sourceType = cast.getExpr().getType().getUnspecifiedType() and + targetType = cast.getType().getUnspecifiedType() and + ( + // Integral types + sourceType instanceof IntegralType and + typeKind = "integral" + or + // Enumerated types + sourceType instanceof Enum and + typeKind = "enumerated" + or + // Pointer to void type + sourceType.(PointerType).getBaseType() instanceof VoidType and + typeKind = "pointer to void" and + // Exception: casts between void pointers are allowed + not targetType.getBaseType() instanceof VoidType + ) +select cast, + "Cast from " + typeKind + " type '" + cast.getExpr().getType() + "' to pointer type '" + + cast.getType() + "'." diff --git a/cpp/misra/src/rules/RULE-8-2-7/NoPointerToIntegralCast.ql b/cpp/misra/src/rules/RULE-8-2-7/NoPointerToIntegralCast.ql new file mode 100644 index 0000000000..dde10b0e40 --- /dev/null +++ b/cpp/misra/src/rules/RULE-8-2-7/NoPointerToIntegralCast.ql @@ -0,0 +1,26 @@ +/** + * @id cpp/misra/no-pointer-to-integral-cast + * @name RULE-8-2-7: A cast should not convert a pointer type to an integral type + * @description Casting between pointer types and integral types makes code behavior harder to + * understand and may cause pointer tracking tools to become unreliable. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-8-2-7 + * scope/single-translation-unit + * correctness + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/advisory + */ + +import cpp +import codingstandards.cpp.misra + +from Cast cast, Type sourceType, IntegralType targetType +where + not isExcluded(cast, Conversions2Package::noPointerToIntegralCastQuery()) and + sourceType = cast.getExpr().getType().getUnspecifiedType() and + targetType = cast.getType().getUnspecifiedType() and + (sourceType instanceof PointerType or sourceType instanceof FunctionPointerType) +select cast, "Cast converts pointer type to integral type '" + targetType + "'." diff --git a/cpp/misra/src/rules/RULE-8-2-8/PointerToIntegralCast.ql b/cpp/misra/src/rules/RULE-8-2-8/PointerToIntegralCast.ql new file mode 100644 index 0000000000..357513305b --- /dev/null +++ b/cpp/misra/src/rules/RULE-8-2-8/PointerToIntegralCast.ql @@ -0,0 +1,72 @@ +/** + * @id cpp/misra/pointer-to-integral-cast + * @name RULE-8-2-8: An object pointer type shall not be cast to an integral type other than std::uintptr_t or + * @description Casting object pointers to integral types other than std::uintptr_t or std::intptr_t + * can lead to implementation-defined behavior and potential loss of pointer + * information. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-8-2-8 + * scope/single-translation-unit + * correctness + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.types.Type + +class InvalidReinterpretCast extends ReinterpretCast { + InvalidReinterpretCast() { + this.getExpr().getType().stripTopLevelSpecifiers() instanceof PointerType and + this.getType().getUnspecifiedType() instanceof IntegralType and + not stripSpecifiers(this.getType()).(UserType).hasGlobalOrStdName(["uintptr_t", "intptr_t"]) + } +} + +from + InvalidReinterpretCast cast, string message, Element optionalTemplateUseSite, + string optionalTemplateUseSiteString +where + not isExcluded(cast, Conversions2Package::pointerToIntegralCastQuery()) and + // Where a result occurs in both the instantiated and uninstantiated template, + // prefer the uninstantiated version. + not exists(InvalidReinterpretCast otherCast | + otherCast.getLocation() = cast.getLocation() and + not otherCast = cast and + otherCast.isFromUninstantiatedTemplate(_) + ) and + if not cast.isFromTemplateInstantiation(_) + then + // Either not in a template, or appears in the uninstantiated template and is + // therefore not argument dependent. + message = + "Cast of object pointer type to integral type '" + cast.getType() + + "' instead of 'std::uintptr_t' or 'std::intptr_t'." and + optionalTemplateUseSite = cast and + optionalTemplateUseSiteString = "" + else + // This is from a template instantiation and is dependent on a template argument, + // so attempt to identify the locations which instantiate the template. + exists(Element instantiation | + cast.isFromTemplateInstantiation(instantiation) and + message = "Cast of object pointer type to integral type inside $@." + | + optionalTemplateUseSite = instantiation.(ClassTemplateInstantiation).getATypeNameUse() and + optionalTemplateUseSiteString = + "instantiation of class " + instantiation.(ClassTemplateInstantiation).getName() + or + optionalTemplateUseSite = + instantiation.(FunctionTemplateInstantiation).getACallToThisFunction() and + optionalTemplateUseSiteString = + "call to instantiated template f of " + + instantiation.(FunctionTemplateInstantiation).getName() + or + optionalTemplateUseSite = instantiation.(VariableTemplateInstantiation).getAnAccess() and + optionalTemplateUseSiteString = + "reference to instantiated template variable " + + instantiation.(VariableTemplateInstantiation).getName() + ) +select cast, message, optionalTemplateUseSite, optionalTemplateUseSiteString diff --git a/cpp/misra/src/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.ql b/cpp/misra/src/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.ql new file mode 100644 index 0000000000..e3be797b9d --- /dev/null +++ b/cpp/misra/src/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.ql @@ -0,0 +1,37 @@ +/** + * @id cpp/misra/no-standalone-type-cast-expression + * @name RULE-9-2-1: An explicit type conversion shall not be an expression statement + * @description Using an explicit type conversion as an expression statement creates a temporary + * object that is immediately discarded, which can lead to unintended premature + * resource cleanup. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-9-2-1 + * scope/single-translation-unit + * correctness + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra + +from ExprStmt stmt, Expr expr +where + not isExcluded(stmt, Conversions2Package::noStandaloneTypeCastExpressionQuery()) and + expr = stmt.getExpr() and + ( + // Explicit conversions which call a constructor + expr instanceof ConstructorCall + or + // Cast expressions using functional notation + expr instanceof Cast + ) and + // Exclude init-statements in if/for statements + // This is because the extractor has a bug as of 2.20.7 which means it does not parse + // these two cases separately. We choose to ignore if statements, which can cause false + // negatives, but will prevent false positives + not exists(IfStmt ifStmt | ifStmt.getInitialization() = stmt) +select stmt, + "Explicit type conversion used as expression statement creates temporary object that is immediately discarded." diff --git a/cpp/misra/test/rules/RULE-8-2-1/VirtualBaseClassCastToDerived.testref b/cpp/misra/test/rules/RULE-8-2-1/VirtualBaseClassCastToDerived.testref new file mode 100644 index 0000000000..475d3fd8be --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-1/VirtualBaseClassCastToDerived.testref @@ -0,0 +1 @@ +cpp/common/test/rules/pointertoavirtualbaseclasscasttoapointer/PointerToAVirtualBaseClassCastToAPointer.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.expected b/cpp/misra/test/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.expected new file mode 100644 index 0000000000..a2b3b522cc --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.expected @@ -0,0 +1,7 @@ +| test.cpp:8:22:8:37 | (uint32_t)... | Use of explicit c-style cast to uint32_t. | +| test.cpp:9:22:9:32 | (unsigned int)... | Use of explicit c-style cast to unsigned int. | +| test.cpp:70:1:70:31 | #define ADD_ONE(x) ((int)x) + 1 | Use of explicit c-style cast to int. | +| test.cpp:83:19:83:26 | (int)... | Use of explicit c-style cast to int. | +| test.cpp:84:27:84:34 | (int)... | Use of explicit c-style cast to int. | +| test.cpp:112:10:112:13 | (int)... | Use of explicit c-style cast to int. | +| test.cpp:147:12:147:26 | (unsigned int)... | Use of explicit c-style cast to unsigned int. | diff --git a/cpp/misra/test/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.qlref b/cpp/misra/test/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.qlref new file mode 100644 index 0000000000..cbd07ad124 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.qlref @@ -0,0 +1 @@ +rules/RULE-8-2-2/NoCStyleOrFunctionalCasts.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-2/test.cpp b/cpp/misra/test/rules/RULE-8-2-2/test.cpp new file mode 100644 index 0000000000..3d57d7f3c8 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-2/test.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +int foo() { return 1; } +// A copy of A5-2-2 test.cpp, but with different cases compliant/non-compliant +void test_c_style_cast() { + double f = 3.14; + std::uint32_t n1 = (std::uint32_t)f; // NON_COMPLIANT - C-style cast + std::uint32_t n2 = unsigned(f); // NON_COMPLIANT - functional notation + + std::uint8_t n3 = 1; + std::uint8_t n4 = 1; + std::uint8_t n5 = n3 + n4; // ignored, implicit casts + + (void)foo(); // COMPLIANT - permitted by MISRA C++ +} + +class A { +public: + virtual void f1() {} +}; + +class B : A { +public: + virtual void f1() {} +}; + +class C { + void f1() {} +}; + +void test_cpp_style_cast() { + // These cases may contravene other rules, but are marked as COMPLIANT for + // this rule + A a1; + const A *a2 = &a1; + A *a3 = const_cast(a2); // COMPLIANT + B *b = dynamic_cast(a3); // COMPLIANT + C *c = reinterpret_cast(a3); // COMPLIANT + std::int16_t n8 = 0; + std::int32_t n9 = static_cast(n8); // COMPLIANT + static_cast(foo()); // COMPLIANT +} + +class A5_2_2a { +public: + template + static void Foo(const std::string &name, As &&...rest) { + Fun(Log(std::forward( + rest)...)); // COMPLIANT - previously reported as a false positive + } + + template static std::string Log(As &&...tail) { + return std::string(); + } + + static void Fun(const std::string &message) {} +}; + +class A5_2_2 final { +public: + void f(const std::string &s) const { A5_2_2a::Foo("name", "x", "y", "z"); } +}; + +void a5_2_2_test() { + A5_2_2 a; + a.f(""); +} + +#define ADD_ONE(x) ((int)x) + 1 // NON_COMPLIANT +#define NESTED_ADD_ONE(x) ADD_ONE(x) // Not reported - reported at ADD_ONE +#define NO_CAST_ADD_ONE(x) x + 1 // COMPLIANT + +#include "macro_c_style_casts.h" + +void test_macro_cast() { + ADD_ONE(1); // Reported at macro definition site + NESTED_ADD_ONE(1); // reported at macro definition site + LIBRARY_ADD_TWO(1); // COMPLIANT - macro generating the cast is defined in a + // library, and is not modifiable by the user + LIBRARY_NESTED_ADD_TWO(1); // COMPLIANT - macro generating the cast is defined + // in a library, and is not modifiable by the user + NO_CAST_ADD_ONE((int)1.0); // NON_COMPLIANT - cast in argument to macro + LIBRARY_NO_CAST_ADD_TWO((int)1.0); // NON_COMPLIANT - library macro with + // c-style cast in argument, written by + // user so should be reported +} + +class D { +public: + D(int x) : fx(x), fy(0) {} + D(int x, int y) : fx(x), fy(y) {} + +private: + int fx; + int fy; +}; + +D testNonFunctionalCast() { + return (D)1; // COMPLIANT +} + +D testFunctionalCast() { + return D(1); // COMPLIANT +} + +D testFunctionalCastMulti() { + return D(1, 2); // COMPLIANT +} + +template T testFunctionalCastTemplate() { + return T(1); // NON_COMPLIANT - used with an int +} + +template T testFunctionalCastTemplateMulti() { + return T(1, 2); // COMPLIANT +} + +void testFunctionalCastTemplateUse() { + testFunctionalCastTemplate(); + testFunctionalCastTemplate(); + testFunctionalCastTemplateMulti(); +} + +template class E { +public: + class F { + public: + F(int x) : fx(x), fy(0) {} + F(int x, int y) : fx(x), fy(y) {} + + private: + int fx; + int fy; + }; + + F f() { + return F(1); // COMPLIANT + } + + D d() { + return D(1); // COMPLIANT + } + + int i() { + double f = 3.14; + return (unsigned int)f; // NON_COMPLIANT + } +}; + +class G {}; + +void testE() { + E e; + e.f(); + e.d(); + e.i(); +} diff --git a/cpp/misra/test/rules/RULE-8-2-6/IntToPointerCastProhibited.expected b/cpp/misra/test/rules/RULE-8-2-6/IntToPointerCastProhibited.expected new file mode 100644 index 0000000000..18accdc95e --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-6/IntToPointerCastProhibited.expected @@ -0,0 +1,11 @@ +| test.cpp:15:20:15:53 | reinterpret_cast... | Cast from integral type 'int32_t' to pointer type 'TestStruct *'. | +| test.cpp:16:20:16:53 | reinterpret_cast... | Cast from integral type 'int64_t' to pointer type 'TestStruct *'. | +| test.cpp:17:22:17:57 | reinterpret_cast... | Cast from integral type 'int64_t' to pointer type 'int32_t *'. | +| test.cpp:24:20:24:53 | reinterpret_cast... | Cast from enumerated type 'TestEnum' to pointer type 'TestStruct *'. | +| test.cpp:25:22:25:57 | reinterpret_cast... | Cast from enumerated type 'TestEnum' to pointer type 'int32_t *'. | +| test.cpp:32:20:32:48 | static_cast... | Cast from pointer to void type 'void *' to pointer type 'TestStruct *'. | +| test.cpp:33:20:33:53 | reinterpret_cast... | Cast from pointer to void type 'void *' to pointer type 'TestStruct *'. | +| test.cpp:34:22:34:52 | static_cast... | Cast from pointer to void type 'void *' to pointer type 'int32_t *'. | +| test.cpp:35:22:35:57 | reinterpret_cast... | Cast from pointer to void type 'void *' to pointer type 'int32_t *'. | +| test.cpp:43:14:43:41 | reinterpret_cast... | Cast from integral type 'int32_t' to pointer type 'void *'. | +| test.cpp:44:14:44:41 | reinterpret_cast... | Cast from integral type 'int64_t' to pointer type 'void *'. | diff --git a/cpp/misra/test/rules/RULE-8-2-6/IntToPointerCastProhibited.qlref b/cpp/misra/test/rules/RULE-8-2-6/IntToPointerCastProhibited.qlref new file mode 100644 index 0000000000..c6f30b00f6 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-6/IntToPointerCastProhibited.qlref @@ -0,0 +1 @@ +rules/RULE-8-2-6/IntToPointerCastProhibited.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-6/test.cpp b/cpp/misra/test/rules/RULE-8-2-6/test.cpp new file mode 100644 index 0000000000..5abd52242e --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-6/test.cpp @@ -0,0 +1,90 @@ +#include + +struct TestStruct { + std::int32_t m1; + std::int32_t m2; +}; + +enum TestEnum { VALUE1, VALUE2 }; + +void test_integral_to_pointer_cast() { + std::int32_t l1 = 42; + std::int64_t l2 = 0x1000; + + // Casting integral types to pointer types + TestStruct *l4 = reinterpret_cast(l1); // NON_COMPLIANT + TestStruct *l5 = reinterpret_cast(l2); // NON_COMPLIANT + std::int32_t *l7 = reinterpret_cast(l2); // NON_COMPLIANT +} + +void test_enumerated_to_pointer_cast() { + TestEnum l1 = VALUE1; + + // Casting enumerated types to pointer types + TestStruct *l3 = reinterpret_cast(l1); // NON_COMPLIANT + std::int32_t *l5 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_void_pointer_to_object_pointer_cast() { + void *l1 = nullptr; + + // Casting void pointer to object pointer types + TestStruct *l2 = static_cast(l1); // NON_COMPLIANT + TestStruct *l3 = reinterpret_cast(l1); // NON_COMPLIANT + std::int32_t *l4 = static_cast(l1); // NON_COMPLIANT + std::int32_t *l5 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_integral_to_void_pointer_cast() { + std::int32_t l1 = 42; + std::int64_t l2 = 0x1000; + + // Casting integral types to void pointer + void *l3 = reinterpret_cast(l1); // NON_COMPLIANT + void *l4 = reinterpret_cast(l2); // NON_COMPLIANT +} + +void test_compliant_void_pointer_casts() { + void *l1 = nullptr; + const void *l2 = nullptr; + + // Casts between void pointers are allowed + const void *l3 = const_cast(l1); // COMPLIANT + void *l4 = const_cast(l2); // COMPLIANT + volatile void *l5 = static_cast(l1); // COMPLIANT +} + +void f1() {} +void f2(int) {} + +void test_compliant_function_pointer_exceptions() { + std::int64_t l1 = 0x1000; + + // Function pointer casts are exceptions to this rule + void (*l2)() = reinterpret_cast(l1); // COMPLIANT + void (*l3)(int) = reinterpret_cast(l1); // COMPLIANT +} + +struct TestClass { + void memberFunc(); + std::int32_t m1; +}; + +void test_compliant_member_function_pointer_exceptions() { + void *l1 = nullptr; + + // Member function pointer casts are technically exceptions to this rule, but + // are prohibited by the compiler. + // void (TestClass::*l2)() = + // reinterpret_cast(l1); // COMPLIANT +} + +void test_compliant_regular_pointer_operations() { + TestStruct l1; + TestStruct *l2 = &l1; + std::int32_t *l3 = &l1.m1; + + // Regular pointer operations that don't involve forbidden casts + TestStruct *l4 = l2; // COMPLIANT + std::int32_t *l5 = l3; // COMPLIANT +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-7/NoPointerToIntegralCast.expected b/cpp/misra/test/rules/RULE-8-2-7/NoPointerToIntegralCast.expected new file mode 100644 index 0000000000..7a5f729206 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-7/NoPointerToIntegralCast.expected @@ -0,0 +1,17 @@ +| test.cpp:22:22:22:59 | reinterpret_cast... | Cast converts pointer type to integral type 'long'. | +| test.cpp:23:23:23:61 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned long'. | +| test.cpp:24:20:24:55 | reinterpret_cast... | Cast converts pointer type to integral type 'signed char'. | +| test.cpp:25:21:25:57 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned char'. | +| test.cpp:26:21:26:57 | reinterpret_cast... | Cast converts pointer type to integral type 'signed short'. | +| test.cpp:27:22:27:59 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned short'. | +| test.cpp:28:21:28:59 | reinterpret_cast... | Cast converts pointer type to integral type 'signed int'. | +| test.cpp:29:22:29:61 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned int'. | +| test.cpp:30:22:30:61 | reinterpret_cast... | Cast converts pointer type to integral type 'signed long'. | +| test.cpp:32:7:32:47 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned long'. | +| test.cpp:33:14:33:42 | reinterpret_cast... | Cast converts pointer type to integral type 'long'. | +| test.cpp:34:23:34:60 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned long'. | +| test.cpp:35:13:35:40 | reinterpret_cast... | Cast converts pointer type to integral type 'int'. | +| test.cpp:36:22:36:58 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned int'. | +| test.cpp:64:7:64:47 | reinterpret_cast... | Cast converts pointer type to integral type 'long'. | +| test.cpp:66:7:66:48 | reinterpret_cast... | Cast converts pointer type to integral type 'unsigned long'. | +| test.cpp:67:14:67:45 | reinterpret_cast... | Cast converts pointer type to integral type 'long'. | diff --git a/cpp/misra/test/rules/RULE-8-2-7/NoPointerToIntegralCast.qlref b/cpp/misra/test/rules/RULE-8-2-7/NoPointerToIntegralCast.qlref new file mode 100644 index 0000000000..c46a48360b --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-7/NoPointerToIntegralCast.qlref @@ -0,0 +1 @@ +rules/RULE-8-2-7/NoPointerToIntegralCast.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-7/test.cpp b/cpp/misra/test/rules/RULE-8-2-7/test.cpp new file mode 100644 index 0000000000..6f89118c21 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-7/test.cpp @@ -0,0 +1,76 @@ +#include + +struct S { + int m1; +}; + +class C { +public: + int m1; +}; + +void test_pointer_to_integral_casts() { + S s; + S *s_ptr = &s; + C c; + C *c_ptr = &c; + int l1 = 42; + int *int_ptr = &l1; + void *void_ptr = &l1; + + // Non-compliant cases - pointer to integral type casts + std::intptr_t l2 = reinterpret_cast(s_ptr); // NON_COMPLIANT + std::uintptr_t l3 = reinterpret_cast(s_ptr); // NON_COMPLIANT + std::int8_t l4 = reinterpret_cast(s_ptr); // NON_COMPLIANT + std::uint8_t l5 = reinterpret_cast(s_ptr); // NON_COMPLIANT + std::int16_t l6 = reinterpret_cast(c_ptr); // NON_COMPLIANT + std::uint16_t l7 = reinterpret_cast(c_ptr); // NON_COMPLIANT + std::int32_t l8 = reinterpret_cast(int_ptr); // NON_COMPLIANT + std::uint32_t l9 = reinterpret_cast(int_ptr); // NON_COMPLIANT + std::int64_t l10 = reinterpret_cast(void_ptr); // NON_COMPLIANT + std::uint64_t l11 = + reinterpret_cast(void_ptr); // NON_COMPLIANT + long l12 = reinterpret_cast(s_ptr); // NON_COMPLIANT + unsigned long l13 = reinterpret_cast(s_ptr); // NON_COMPLIANT + int l14 = reinterpret_cast(s_ptr); // NON_COMPLIANT + unsigned int l15 = reinterpret_cast(s_ptr); // NON_COMPLIANT +} + +void test_compliant_pointer_operations() { + S s; + S *s_ptr = &s; + C c; + C *c_ptr = &c; + int l1 = 42; + int *int_ptr = &l1; + + // Compliant cases - pointer to pointer casts + void *l16 = static_cast(s_ptr); // COMPLIANT + S *l17 = static_cast(static_cast(s_ptr)); // COMPLIANT + C *l18 = reinterpret_cast(s_ptr); // COMPLIANT + + // Compliant cases - using pointers without casting to integral + S *l19 = s_ptr; // COMPLIANT + if (s_ptr != nullptr) { // COMPLIANT + s_ptr->m1 = 10; // COMPLIANT + } +} + +void test_function_pointer_to_integral() { + void (*func_ptr)() = nullptr; + + // Non-compliant cases - function pointer to integral type casts + std::intptr_t l20 = + reinterpret_cast(func_ptr); // NON_COMPLIANT + std::uintptr_t l21 = + reinterpret_cast(func_ptr); // NON_COMPLIANT + long l22 = reinterpret_cast(func_ptr); // NON_COMPLIANT +} + +void test_member_pointer_to_integral() { + // Member pointer to integral type casts are forbidden by the rule, but also + // prohibited by the compiler, e.g. + // int S::*member_ptr = &S::m1; + // std::intptr_t l23 = reinterpret_cast(member_ptr); + // std::uintptr_t l24 = reinterpret_cast(member_ptr); +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-8/PointerToIntegralCast.expected b/cpp/misra/test/rules/RULE-8-2-8/PointerToIntegralCast.expected new file mode 100644 index 0000000000..dd85d003c2 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-8/PointerToIntegralCast.expected @@ -0,0 +1,13 @@ +| test.cpp:35:13:35:47 | reinterpret_cast... | Cast of object pointer type to integral type 'unsigned long' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:35:13:35:47 | reinterpret_cast... | | +| test.cpp:40:13:40:46 | reinterpret_cast... | Cast of object pointer type to integral type 'unsigned int' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:40:13:40:46 | reinterpret_cast... | | +| test.cpp:45:13:45:38 | reinterpret_cast... | Cast of object pointer type to integral type 'long' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:45:13:45:38 | reinterpret_cast... | | +| test.cpp:50:13:50:45 | reinterpret_cast... | Cast of object pointer type to integral type 'size_t' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:50:13:50:45 | reinterpret_cast... | | +| test.cpp:55:13:55:43 | reinterpret_cast... | Cast of object pointer type to integral type 'hashPtr_t' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:55:13:55:43 | reinterpret_cast... | | +| test.cpp:60:13:60:42 | reinterpret_cast... | Cast of object pointer type to integral type 'MyIntPtr' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:60:13:60:42 | reinterpret_cast... | | +| test.cpp:65:13:65:35 | reinterpret_cast... | Cast of object pointer type to integral type inside $@. | test.cpp:94:3:94:50 | call to test_non_compliant_template_cast | call to instantiated template f of test_non_compliant_template_cast | +| test.cpp:67:13:67:47 | reinterpret_cast... | Cast of object pointer type to integral type 'uint64_t' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:67:13:67:47 | reinterpret_cast... | | +| test.cpp:72:13:72:47 | reinterpret_cast... | Cast of object pointer type to integral type 'uint64_t' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:72:13:72:47 | reinterpret_cast... | | +| test.cpp:77:13:77:46 | reinterpret_cast... | Cast of object pointer type to integral type 'int64_t' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:77:13:77:46 | reinterpret_cast... | | +| test.cpp:84:15:84:37 | reinterpret_cast... | Cast of object pointer type to integral type inside $@. | test.cpp:95:48:95:48 | definition of x | instantiation of class TestNonCompliantTemplateCast | +| test.cpp:86:15:86:49 | reinterpret_cast... | Cast of object pointer type to integral type 'uint64_t' instead of 'std::uintptr_t' or 'std::intptr_t'. | test.cpp:86:15:86:49 | reinterpret_cast... | | +| test.cpp:91:23:91:45 | reinterpret_cast... | Cast of object pointer type to integral type inside $@. | test.cpp:96:3:96:35 | variable_template | reference to instantiated template variable variable_template | diff --git a/cpp/misra/test/rules/RULE-8-2-8/PointerToIntegralCast.qlref b/cpp/misra/test/rules/RULE-8-2-8/PointerToIntegralCast.qlref new file mode 100644 index 0000000000..ed46d08aeb --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-8/PointerToIntegralCast.qlref @@ -0,0 +1 @@ +rules/RULE-8-2-8/PointerToIntegralCast.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-8-2-8/test.cpp b/cpp/misra/test/rules/RULE-8-2-8/test.cpp new file mode 100644 index 0000000000..6b3dbd7685 --- /dev/null +++ b/cpp/misra/test/rules/RULE-8-2-8/test.cpp @@ -0,0 +1,97 @@ +#include +#include +struct S {}; +class C {}; + +S *g1 = nullptr; + +using hashPtr_t = std::uintptr_t; +using MyIntPtr = std::intptr_t; + +void test_compliant_uintptr_t_cast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // COMPLIANT +} + +void test_compliant_intptr_t_cast() { + C *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // COMPLIANT +} + +void test_compliant_void_pointer_cast() { + void *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // COMPLIANT + auto l3 = reinterpret_cast(l1); // COMPLIANT +} + +void test_compliant_const_pointer_cast() { + const int *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // COMPLIANT + auto l3 = reinterpret_cast(l1); // COMPLIANT +} + +void test_non_compliant_unsigned_long_cast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_unsigned_int_cast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_long_cast() { + C *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_size_t_cast() { + void *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_typedef_cast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_using_alias_cast() { + C *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +template void test_non_compliant_template_cast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT + auto l3 = reinterpret_cast(l1); // COMPLIANT + auto l4 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_uint64_t_cast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +void test_non_compliant_int64_t_cast() { + void *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT +} + +template class TestNonCompliantTemplateCast { +public: + TestNonCompliantTemplateCast() { + S *l1 = nullptr; + auto l2 = reinterpret_cast(l1); // NON_COMPLIANT + auto l3 = reinterpret_cast(l1); // COMPLIANT + auto l4 = reinterpret_cast(l1); // NON_COMPLIANT + } +}; + +template +T variable_template = reinterpret_cast(g1); // NON_COMPLIANT + +void test_instantiate_template() { + test_non_compliant_template_cast(); + TestNonCompliantTemplateCast x{}; + variable_template; +} \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.expected b/cpp/misra/test/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.expected new file mode 100644 index 0000000000..eb58109070 --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.expected @@ -0,0 +1,7 @@ +| test.cpp:20:3:20:35 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | +| test.cpp:21:3:21:23 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | +| test.cpp:22:3:22:27 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | +| test.cpp:23:3:23:34 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | +| test.cpp:42:8:42:40 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | +| test.cpp:66:3:66:17 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | +| test.cpp:67:3:67:17 | ExprStmt | Explicit type conversion used as expression statement creates temporary object that is immediately discarded. | diff --git a/cpp/misra/test/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.qlref b/cpp/misra/test/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.qlref new file mode 100644 index 0000000000..5aec9cdada --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-2-1/NoStandaloneTypeCastExpression.qlref @@ -0,0 +1 @@ +rules/RULE-9-2-1/NoStandaloneTypeCastExpression.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-9-2-1/test.cpp b/cpp/misra/test/rules/RULE-9-2-1/test.cpp new file mode 100644 index 0000000000..75aed0a90e --- /dev/null +++ b/cpp/misra/test/rules/RULE-9-2-1/test.cpp @@ -0,0 +1,74 @@ +#include +#include +#include + +std::mutex g1; +std::mutex g2; + +void f1(std::unique_lock l1) { + // Function parameter for testing compliant cases +} + +void test_explicit_type_conversion_expression_statement() { + // Compliant cases - declarations + std::unique_lock l1(g1); // COMPLIANT + std::unique_lock l2{g1}; // COMPLIANT + std::unique_lock(l3); // COMPLIANT - declaration with redundant + // parentheses around variable name + + // Non-compliant cases - explicit type conversions as expression statements + std::unique_lock{g1}; // NON_COMPLIANT + std::scoped_lock{g1}; // NON_COMPLIANT + std::scoped_lock(g1, g2); // NON_COMPLIANT + std::lock_guard{g1}; // NON_COMPLIANT + + // Compliant cases - type conversions not as expression statements + f1(std::unique_lock(g1)); // COMPLIANT + f1(std::unique_lock{g1}); // COMPLIANT + auto l4 = std::unique_lock(g1); // COMPLIANT + auto l5 = std::unique_lock{g1}; // COMPLIANT + + // The extractor has a bug as of 2.20.7 which means it does not parse + // these two cases separately. We choose to ignore if statements, which + // can cause false negatives, but will prevent false positives + if (std::unique_lock(g1); true) { // COMPLIANT - init-statement + } + if (std::unique_lock{g1}; true) { // NON_COMPLIANT[FALSE_NEGATIVE] + } + + for (std::unique_lock(g1);;) { // COMPLIANT - init-statement + break; + } + for (std::unique_lock{g1};;) { // NON_COMPLIANT - init-statement + break; + } +} + +void test_primitive_type_conversions() { + // Non-compliant cases with primitive types + std::int32_t(42); // NON_COMPLIANT + float(3.14); // NON_COMPLIANT + double(2.71); // NON_COMPLIANT + bool(true); // NON_COMPLIANT + + // Compliant cases + auto l1 = std::int32_t(42); // COMPLIANT + auto l2 = float(3.14); // COMPLIANT + std::int32_t l3(42); // COMPLIANT - declaration + std::int32_t l4{42}; // COMPLIANT - declaration +} + +struct CustomType { + CustomType(std::int32_t) {} +}; +void test_custom_types() { + // Non-compliant cases + CustomType(42); // NON_COMPLIANT + CustomType{42}; // NON_COMPLIANT + + // Compliant cases + CustomType l1(42); // COMPLIANT - declaration + CustomType l2{42}; // COMPLIANT - declaration + auto l3 = CustomType(42); // COMPLIANT + auto l4 = CustomType{42}; // COMPLIANT +} \ No newline at end of file diff --git a/rule_packages/cpp/Conversions2.json b/rule_packages/cpp/Conversions2.json new file mode 100644 index 0000000000..a4fc4f565d --- /dev/null +++ b/rule_packages/cpp/Conversions2.json @@ -0,0 +1,137 @@ +{ + "MISRA-C++-2023": { + "RULE-8-2-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Casting from a virtual base class to a derived class using anything other than dynamic_cast causes undefined behavior.", + "kind": "problem", + "name": "A virtual base class shall only be cast to a derived class by means of dynamic_cast", + "precision": "very-high", + "severity": "error", + "short_name": "VirtualBaseClassCastToDerived", + "tags": [ + "scope/single-translation-unit", + "correctness" + ], + "shared_implementation_short_name": "PointerToAVirtualBaseClassCastToAPointer" + } + ], + "title": "A virtual base class shall only be cast to a derived class by means of dynamic_cast" + }, + "RULE-8-2-2": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Using C-style casts or functional notation casts allows unsafe type conversions and makes code harder to maintain compared to using named casts like const_cast, dynamic_cast, static_cast and reinterpret_cast.", + "kind": "problem", + "name": "C-style casts and functional notation casts shall not be used", + "precision": "very-high", + "severity": "error", + "short_name": "NoCStyleOrFunctionalCasts", + "tags": [ + "scope/single-translation-unit", + "readability", + "correctness" + ] + } + ], + "title": "C-style casts and functional notation casts shall not be used" + }, + "RULE-8-2-6": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Casting from an integral type, enumerated type, or pointer to void type to a pointer type leads to unspecified behavior and is error prone.", + "kind": "problem", + "name": "An object with integral, enumerated, or pointer to void type shall not be cast to a pointer type", + "precision": "very-high", + "severity": "error", + "short_name": "IntToPointerCastProhibited", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ] + } + ], + "title": "An object with integral, enumerated, or pointer to void type shall not be cast to a pointer type" + }, + "RULE-8-2-7": { + "properties": { + "enforcement": "decidable", + "obligation": "advisory" + }, + "queries": [ + { + "description": "Casting between pointer types and integral types makes code behavior harder to understand and may cause pointer tracking tools to become unreliable.", + "kind": "problem", + "name": "A cast should not convert a pointer type to an integral type", + "precision": "very-high", + "severity": "error", + "short_name": "NoPointerToIntegralCast", + "tags": [ + "scope/single-translation-unit", + "correctness", + "maintainability" + ] + } + ], + "title": "A cast should not convert a pointer type to an integral type" + }, + "RULE-8-2-8": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Casting object pointers to integral types other than std::uintptr_t or std::intptr_t can lead to implementation-defined behavior and potential loss of pointer information.", + "kind": "problem", + "name": "An object pointer type shall not be cast to an integral type other than std::uintptr_t or", + "precision": "very-high", + "severity": "error", + "short_name": "PointerToIntegralCast", + "tags": [ + "scope/single-translation-unit", + "correctness" + ] + } + ], + "title": "An object pointer type shall not be cast to an integral type other than std::uintptr_t or std::intptr_t" + }, + "RULE-9-2-1": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Using an explicit type conversion as an expression statement creates a temporary object that is immediately discarded, which can lead to unintended premature resource cleanup.", + "kind": "problem", + "name": "An explicit type conversion shall not be an expression statement", + "precision": "very-high", + "severity": "error", + "short_name": "NoStandaloneTypeCastExpression", + "tags": [ + "scope/single-translation-unit", + "correctness" + ], + "implementation_scope": { + "description": "Expression statements in if statement initializers are not currently supported." + } + } + ], + "title": "An explicit type conversion shall not be an expression statement" + } + } +} \ No newline at end of file diff --git a/rule_packages/cpp/Pointers.json b/rule_packages/cpp/Pointers.json index fb1fbe2918..3815ba521c 100644 --- a/rule_packages/cpp/Pointers.json +++ b/rule_packages/cpp/Pointers.json @@ -306,7 +306,8 @@ "short_name": "PointerToAVirtualBaseClassCastToAPointer", "tags": [ "correctness" - ] + ], + "shared_implementation_short_name": "PointerToAVirtualBaseClassCastToAPointer" } ], "title": "A pointer to a virtual base class shall only be cast to a pointer to a derived class by means of dynamic_cast."