diff --git a/change_notes/2026-02-22-shared-default-initialization-logic.md b/change_notes/2026-02-22-shared-default-initialization-logic.md new file mode 100644 index 0000000000..84792904b4 --- /dev/null +++ b/change_notes/2026-02-22-shared-default-initialization-logic.md @@ -0,0 +1,3 @@ + - `A3-3-2` - `StaticOrThreadLocalObjectsNonConstantInit`: + - The checks for non-constant initialization have been moved to be usable in other queries, such as MISRA C++23 Rule 6.7.2. + - No visible changes in query results expected. \ No newline at end of file diff --git a/cpp/autosar/src/rules/A3-3-2/StaticOrThreadLocalObjectsNonConstantInit.ql b/cpp/autosar/src/rules/A3-3-2/StaticOrThreadLocalObjectsNonConstantInit.ql index eff7e0f529..6b37aff0d2 100644 --- a/cpp/autosar/src/rules/A3-3-2/StaticOrThreadLocalObjectsNonConstantInit.ql +++ b/cpp/autosar/src/rules/A3-3-2/StaticOrThreadLocalObjectsNonConstantInit.ql @@ -15,100 +15,7 @@ import cpp import codingstandards.cpp.autosar - -/** - * Holds if `e` is a full expression or `AggregateLiteral` in the initializer of a - * `StaticStorageDurationVariable`. - * - * Although `AggregateLiteral`s are expressions according to our model, they are not considered - * expressions from the perspective of the standard. Therefore, we should consider components of - * aggregate literals within static initializers to also be full expressions. - */ -private predicate isFullExprOrAggregateInStaticInitializer(Expr e) { - exists(StaticStorageDurationVariable var | e = var.getInitializer().getExpr()) - or - isFullExprOrAggregateInStaticInitializer(e.getParent().(AggregateLiteral)) -} - -/** - * Holds if `e` is a non-constant full expression in a static initializer, for the given `reason` - * and `reasonElement`. - */ -private predicate nonConstantFullExprInStaticInitializer( - Expr e, Element reasonElement, string reason -) { - isFullExprOrAggregateInStaticInitializer(e) and - if e instanceof AggregateLiteral - then - // If this is an aggregate literal, we apply this recursively - nonConstantFullExprInStaticInitializer(e.getAChild(), reasonElement, reason) - else ( - // Otherwise we check this component to determine if it is constant - not ( - e.getFullyConverted().isConstant() or - e.(Call).getTarget().isConstexpr() or - e.(VariableAccess).getTarget().isConstexpr() - ) and - reason = "uses a non-constant element in the initialization" and - reasonElement = e - ) -} - -/** - * A `ConstructorCall` that does not represent a constant initializer for an object according to - * `[basic.start.init]`. - * - * In addition to identifying `ConstructorCall`s which are not constant initializers, this also - * provides an explanatory "reason" for why this constructor is not considered to be a constant - * initializer. - */ -predicate isNotConstantInitializer(ConstructorCall cc, Element reasonElement, string reason) { - // Must call a constexpr constructor - not cc.getTarget().isConstexpr() and - reason = - "calls the " + cc.getTarget().getName() + "(..) constructor which is not marked as constexpr" and - reasonElement = cc - or - // And all arguments must either be constant, or themselves call constexpr constructors - cc.getTarget().isConstexpr() and - exists(Expr arg | arg = cc.getAnArgument() | - isNotConstantInitializer(arg, reasonElement, reason) - or - not arg instanceof ConstructorCall and - not arg.getFullyConverted().isConstant() and - not arg.(Call).getTarget().isConstexpr() and - not arg.(VariableAccess).getTarget().isConstexpr() and - reason = "includes a non constant " + arg.getType() + " argument to a constexpr constructor" and - reasonElement = arg - ) -} - -/** - * Identifies if a `StaticStorageDurationVariable` is not constant initialized according to - * `[basic.start.init]`. - */ -predicate isNotConstantInitialized( - StaticStorageDurationVariable v, string reason, Element reasonElement -) { - if v.getInitializer().getExpr() instanceof ConstructorCall - then - // (2.2) if initialized by a constructor call, then that constructor call must be a constant - // initializer for the variable to be constant initialized - isNotConstantInitializer(v.getInitializer().getExpr(), reasonElement, reason) - else - // (2.3) If it is not initialized by a constructor call, then it must be the case that every full - // expr in the initializer is a constant expression or that the object was "value initialized" - // but without a constructor call. For value initialization, there are two non-constructor call - // cases to consider: - // - // 1. The object was zero initialized - in which case, the extractor does not include a - // constructor call - instead, it has a blank aggregate literal, or no initializer. - // 2. The object is an array, which will be initialized by an aggregate literal. - // - // In both cases it is sufficient for us to find a non-constant full expression in the static - // initializer - nonConstantFullExprInStaticInitializer(v.getInitializer().getExpr(), reasonElement, reason) -} +import codingstandards.cpp.orderofevaluation.Initialization from StaticStorageDurationVariable staticOrThreadLocalVar, string reason, Element reasonElement where diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/Banned1.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Banned1.qll new file mode 100644 index 0000000000..7a0cbfbdef --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/Banned1.qll @@ -0,0 +1,26 @@ +//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/ +import cpp +import RuleMetadata +import codingstandards.cpp.exclusions.RuleMetadata + +newtype Banned1Query = TGlobalVariableUsedQuery() + +predicate isBanned1QueryMetadata(Query query, string queryId, string ruleId, string category) { + query = + // `Query` instance for the `globalVariableUsed` query + Banned1Package::globalVariableUsedQuery() and + queryId = + // `@id` for the `globalVariableUsed` query + "cpp/misra/global-variable-used" and + ruleId = "RULE-6-7-2" and + category = "required" +} + +module Banned1Package { + Query globalVariableUsedQuery() { + //autogenerate `Query` type + result = + // `Query` type for `globalVariableUsed` query + TQueryCPP(TBanned1PackageQuery(TGlobalVariableUsedQuery())) + } +} diff --git a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll index 10f4029904..33728c1a0d 100644 --- a/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll +++ b/cpp/common/src/codingstandards/cpp/exclusions/cpp/RuleMetadata.qll @@ -3,6 +3,7 @@ import cpp import codingstandards.cpp.exclusions.RuleMetadata //** Import packages for this language **/ import Allocations +import Banned1 import BannedAPIs import BannedFunctions import BannedLibraries @@ -76,6 +77,7 @@ import VirtualFunctions /** The TQuery type representing this language * */ newtype TCPPQuery = TAllocationsPackageQuery(AllocationsQuery q) or + TBanned1PackageQuery(Banned1Query q) or TBannedAPIsPackageQuery(BannedAPIsQuery q) or TBannedFunctionsPackageQuery(BannedFunctionsQuery q) or TBannedLibrariesPackageQuery(BannedLibrariesQuery q) or @@ -149,6 +151,7 @@ newtype TCPPQuery = /** The metadata predicate * */ predicate isQueryMetadata(Query query, string queryId, string ruleId, string category) { isAllocationsQueryMetadata(query, queryId, ruleId, category) or + isBanned1QueryMetadata(query, queryId, ruleId, category) or isBannedAPIsQueryMetadata(query, queryId, ruleId, category) or isBannedFunctionsQueryMetadata(query, queryId, ruleId, category) or isBannedLibrariesQueryMetadata(query, queryId, ruleId, category) or diff --git a/cpp/common/src/codingstandards/cpp/orderofevaluation/Initialization.qll b/cpp/common/src/codingstandards/cpp/orderofevaluation/Initialization.qll new file mode 100644 index 0000000000..19ec9151e4 --- /dev/null +++ b/cpp/common/src/codingstandards/cpp/orderofevaluation/Initialization.qll @@ -0,0 +1,95 @@ +import cpp + +/** + * Holds if `e` is a full expression or `AggregateLiteral` in the initializer of a + * `StaticStorageDurationVariable`. + * + * Although `AggregateLiteral`s are expressions according to our model, they are not considered + * expressions from the perspective of the standard. Therefore, we should consider components of + * aggregate literals within static initializers to also be full expressions. + */ +private predicate isFullExprOrAggregateInStaticInitializer(Expr e) { + exists(StaticStorageDurationVariable var | e = var.getInitializer().getExpr()) + or + isFullExprOrAggregateInStaticInitializer(e.getParent().(AggregateLiteral)) +} + +/** + * Holds if `e` is a non-constant full expression in a static initializer, for the given `reason` + * and `reasonElement`. + */ +private predicate nonConstantFullExprInStaticInitializer( + Expr e, Element reasonElement, string reason +) { + isFullExprOrAggregateInStaticInitializer(e) and + if e instanceof AggregateLiteral + then + // If this is an aggregate literal, we apply this recursively + nonConstantFullExprInStaticInitializer(e.getAChild(), reasonElement, reason) + else ( + // Otherwise we check this component to determine if it is constant + not ( + e.getFullyConverted().isConstant() or + e.(Call).getTarget().isConstexpr() or + e.(VariableAccess).getTarget().isConstexpr() + ) and + reason = "uses a non-constant element in the initialization" and + reasonElement = e + ) +} + +/** + * A `ConstructorCall` that does not represent a constant initializer for an object according to + * `[basic.start.init]`. + * + * In addition to identifying `ConstructorCall`s which are not constant initializers, this also + * provides an explanatory "reason" for why this constructor is not considered to be a constant + * initializer. + */ +predicate isNotConstantInitializer(ConstructorCall cc, Element reasonElement, string reason) { + // Must call a constexpr constructor + not cc.getTarget().isConstexpr() and + reason = + "calls the " + cc.getTarget().getName() + "(..) constructor which is not marked as constexpr" and + reasonElement = cc + or + // And all arguments must either be constant, or themselves call constexpr constructors + cc.getTarget().isConstexpr() and + exists(Expr arg | arg = cc.getAnArgument() | + isNotConstantInitializer(arg, reasonElement, reason) + or + not arg instanceof ConstructorCall and + not arg.getFullyConverted().isConstant() and + not arg.(Call).getTarget().isConstexpr() and + not arg.(VariableAccess).getTarget().isConstexpr() and + reason = "includes a non constant " + arg.getType() + " argument to a constexpr constructor" and + reasonElement = arg + ) +} + +/** + * Identifies if a `StaticStorageDurationVariable` is not constant initialized according to + * `[basic.start.init]`. + */ +predicate isNotConstantInitialized( + StaticStorageDurationVariable v, string reason, Element reasonElement +) { + if v.getInitializer().getExpr() instanceof ConstructorCall + then + // (2.2) if initialized by a constructor call, then that constructor call must be a constant + // initializer for the variable to be constant initialized + isNotConstantInitializer(v.getInitializer().getExpr(), reasonElement, reason) + else + // (2.3) If it is not initialized by a constructor call, then it must be the case that every full + // expr in the initializer is a constant expression or that the object was "value initialized" + // but without a constructor call. For value initialization, there are two non-constructor call + // cases to consider: + // + // 1. The object was zero initialized - in which case, the extractor does not include a + // constructor call - instead, it has a blank aggregate literal, or no initializer. + // 2. The object is an array, which will be initialized by an aggregate literal. + // + // In both cases it is sufficient for us to find a non-constant full expression in the static + // initializer + nonConstantFullExprInStaticInitializer(v.getInitializer().getExpr(), reasonElement, reason) +} diff --git a/cpp/misra/src/rules/RULE-6-7-2/GlobalVariableUsed.ql b/cpp/misra/src/rules/RULE-6-7-2/GlobalVariableUsed.ql new file mode 100644 index 0000000000..5a265d42ba --- /dev/null +++ b/cpp/misra/src/rules/RULE-6-7-2/GlobalVariableUsed.ql @@ -0,0 +1,43 @@ +/** + * @id cpp/misra/global-variable-used + * @name RULE-6-7-2: Global variables shall not be used + * @description Global variables can be accessed and modified from distant and unclear points in the + * code, creating a risk of data races and unexpected behavior. + * @kind problem + * @precision very-high + * @problem.severity error + * @tags external/misra/id/rule-6-7-2 + * scope/single-translation-unit + * maintainability + * external/misra/enforcement/decidable + * external/misra/obligation/required + */ + +import cpp +import codingstandards.cpp.misra +import codingstandards.cpp.Linkage +import codingstandards.cpp.orderofevaluation.Initialization + +class GlobalVariable extends Variable { + GlobalVariable() { + hasNamespaceScope(this) or + this.(MemberVariable).isStatic() + } +} + +from GlobalVariable var, string suffix, Element reasonElement, string reasonString +where + not isExcluded(var, Banned1Package::globalVariableUsedQuery()) and + not var.isConstexpr() and + ( + not var.isConst() and + suffix = "as non-const" and + // Placeholder is not used, but they must be bound. + reasonString = "" and + reasonElement = var + or + var.isConst() and + isNotConstantInitialized(var, reasonString, reasonElement) and + suffix = "as const, but is not constant initialized because it $@" + ) +select var, "Global variable " + var.getName() + " declared " + suffix, reasonElement, reasonString diff --git a/cpp/misra/test/rules/RULE-6-7-2/GlobalVariableUsed.expected b/cpp/misra/test/rules/RULE-6-7-2/GlobalVariableUsed.expected new file mode 100644 index 0000000000..72e9a7fa9e --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-7-2/GlobalVariableUsed.expected @@ -0,0 +1,8 @@ +| test.cpp:7:14:7:15 | g1 | Global variable g1 declared as non-const | test.cpp:7:14:7:15 | g1 | | +| test.cpp:8:20:8:21 | g2 | Global variable g2 declared as const, but is not constant initialized because it $@ | test.cpp:9:5:9:6 | g1 | uses a non-constant element in the initialization | +| test.cpp:14:14:14:15 | g5 | Global variable g5 declared as non-const | test.cpp:14:14:14:15 | g5 | | +| test.cpp:26:19:26:20 | g9 | Global variable g9 declared as const, but is not constant initialized because it $@ | test.cpp:26:22:26:22 | call to ComplexInit | calls the ComplexInit(..) constructor which is not marked as constexpr | +| test.cpp:27:14:27:16 | g10 | Global variable g10 declared as non-const | test.cpp:27:14:27:16 | g10 | | +| test.cpp:28:20:28:22 | g11 | Global variable g11 declared as const, but is not constant initialized because it $@ | test.cpp:28:24:28:25 | call to f1 | uses a non-constant element in the initialization | +| test.cpp:32:23:32:24 | m2 | Global variable m2 declared as non-const | test.cpp:32:23:32:24 | m2 | | +| test.cpp:38:14:38:29 | m3 | Global variable m3 declared as non-const | test.cpp:38:14:38:29 | m3 | | diff --git a/cpp/misra/test/rules/RULE-6-7-2/GlobalVariableUsed.qlref b/cpp/misra/test/rules/RULE-6-7-2/GlobalVariableUsed.qlref new file mode 100644 index 0000000000..8d019f61e3 --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-7-2/GlobalVariableUsed.qlref @@ -0,0 +1 @@ +rules/RULE-6-7-2/GlobalVariableUsed.ql \ No newline at end of file diff --git a/cpp/misra/test/rules/RULE-6-7-2/test.cpp b/cpp/misra/test/rules/RULE-6-7-2/test.cpp new file mode 100644 index 0000000000..33055c71df --- /dev/null +++ b/cpp/misra/test/rules/RULE-6-7-2/test.cpp @@ -0,0 +1,44 @@ +#include + +// Non-compliant global variables (namespace scope, dynamically initialized or +// mutable) +std::int32_t f1(); + +std::int32_t g1{f1()}; // NON_COMPLIANT - dynamic initialization +const std::int32_t g2{ + g1}; // NON_COMPLIANT - dynamic initialization (depends on g1) +const std::int32_t g3{42}; // COMPLIANT - const with static initialization +constexpr std::int32_t g4{0}; // COMPLIANT - constexpr + +namespace { +std::int32_t g5{0}; // NON_COMPLIANT - mutable namespace scope variable +constexpr std::int32_t g6{100}; // COMPLIANT - constexpr +const std::int32_t g7{100}; // COMPLIANT - const with static initialization + +constexpr std::int32_t f2() { return 42; } +constexpr std::int32_t g8{f2()}; // COMPLIANT - constexpr +} // namespace + +struct ComplexInit { + ComplexInit() {} +}; + +const ComplexInit g9{}; // NON_COMPLIANT - dynamic initialization +std::int32_t g10{0}; // NON_COMPLIANT - mutable namespace scope variable +const std::int32_t g11{f1()}; // NON_COMPLIANT - dynamic initialization + +class StaticMember { + std::int32_t m1; + static std::int32_t m2; // NON_COMPLIANT - class static data member + static std::int32_t m3; // marked non_compliant at definition below + static constexpr std::int32_t m4{0}; // COMPLIANT - constexpr static member + static const std::int32_t m5; +}; + +std::int32_t StaticMember::m3 = + 0; // NON_COMPLIANT - class static data member definition +const std::int32_t StaticMember::m5 = + 42; // COMPLIANT - const with static initialization + +constexpr auto g12 = // COMPLIANT - constexpr lambda + [](auto x, auto y) { return x + y; }; \ No newline at end of file diff --git a/rule_packages/cpp/Banned1.json b/rule_packages/cpp/Banned1.json new file mode 100644 index 0000000000..74c38b3015 --- /dev/null +++ b/rule_packages/cpp/Banned1.json @@ -0,0 +1,25 @@ +{ + "MISRA-C++-2023": { + "RULE-6-7-2": { + "properties": { + "enforcement": "decidable", + "obligation": "required" + }, + "queries": [ + { + "description": "Global variables can be accessed and modified from distant and unclear points in the code, creating a risk of data races and unexpected behavior.", + "kind": "problem", + "name": "Global variables shall not be used", + "precision": "very-high", + "severity": "error", + "short_name": "GlobalVariableUsed", + "tags": [ + "scope/single-translation-unit", + "maintainability" + ] + } + ], + "title": "Global variables shall not be used" + } + } +} \ No newline at end of file diff --git a/rules.csv b/rules.csv index 08fa09a573..239c72d0a6 100644 --- a/rules.csv +++ b/rules.csv @@ -863,7 +863,7 @@ cpp,MISRA-C++-2023,RULE-6-4-3,Yes,Required,Decidable,Single Translation Unit,A n cpp,MISRA-C++-2023,RULE-6-5-1,Yes,Advisory,Decidable,Single Translation Unit,A function or object with external linkage should be introduced in a header file,A3-3-1,Linkage1,Import, cpp,MISRA-C++-2023,RULE-6-5-2,Yes,Advisory,Decidable,Single Translation Unit,Internal linkage should be specified appropriately,,Linkage2,Medium, cpp,MISRA-C++-2023,RULE-6-7-1,Yes,Required,Decidable,Single Translation Unit,Local variables shall not have static storage duration,,Declarations2,Easy, -cpp,MISRA-C++-2023,RULE-6-7-2,Yes,Required,Decidable,Single Translation Unit,Global variables shall not be used,,Banned,Easy, +cpp,MISRA-C++-2023,RULE-6-7-2,Yes,Required,Decidable,Single Translation Unit,Global variables shall not be used,,Banned1,Easy, cpp,MISRA-C++-2023,RULE-6-8-1,Yes,Required,Undecidable,System,An object shall not be accessed outside of its lifetime,A3-8-1,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-6-8-2,Yes,Mandatory,Decidable,Single Translation Unit,A function must not return a reference or a pointer to a local variable with automatic storage duration,M7-5-1,ImportMisra23,Import, cpp,MISRA-C++-2023,RULE-6-8-3,Yes,Required,Decidable,Single Translation Unit,An assignment operator shall not assign the address of an object with automatic storage duration to an object with a greater lifetime,,Lifetime,Medium,