ISO/IEC JTC 1/SC 22/WG23 N1012
Date: 2020-11-23
ISO/IEC TR 24772–10
Notes on this document
Effective 23 November 2020, this document is being moved to “github”. Contact Stephen.michell@maurya.on.ca to gain access.
This document is a draft of a Guidance to avoiding programming language vulnerabilities in C++.
At this point in time, the following clauses are essentially completed first pass.
6.2 type system6.3 Bit representation6.4 Floating Point6.5 Enumerator issues \[CCB\],6.6 Conversion errors6.7 String termination6.8 Buffer boundary violation6.9 Unchecked array indexing6.10 Unchecked array copying (needs to be revisited)6.11 Pointer type conversions6.12 Pointer arithmetic6.13 Null pointer dereference \[XYH\],6.14 Dangling reference to heap6.15 Arithmetic wrap-around error6.16 Using shift operations for multiplication and division6.17 Choice of clear names \[NAI\]6.18 Dead Store6.19 Unused variables6.20 Identifier name reuse6.21 Namespace Issues6.22 Initialization of variables \[LAV\]6.23 Operator precedence and associativity6.24 Side effects and order of evaluation6.25 Likely incorrect expression6.26 Dead store,6.27 Switch statements and static analysis6.28 Demarcation of control flow6.29 Loop control variables6.30 Off-by-one errors6.31 Structured programming6.32 Passing parameters and return values6.33 Dangling references to stack frames6.34 Subprogram signature mismatch6.35 Recursion6.36 Ignored error status and unhandled exceptions6.37 Type breaking reinterpretation of data6.38 Deep vs shallow copying \[YAN\]6.39 Memory leak and heap fragmentation6.41 Inheritance6.42 Violations of the Liskov substitution principle6.43 Redispatching6.44 Polymorphic variables6.45 Extra intrinsics6.46 Argument passing to library functions6.47 Inter-language calling6.48 Dynamically-linked code and self-modifying code \[NYY\]6.49 Library Signature6.50 Unanticipated exceptions from library routines6.51 Pre-processor directives6.52 Suppression of language-defined run-time checking6.53 Provision of inherently unsafe operations6.54 Obscure language features6.55 Unspecified behaviour6.56 Undefined behaviour6.57 Implementation-defined behaviour6.58 Deprecated language features6.59 Concurrency \-- Activation6.60 Concurrency -- Directed termination6.64 Uncontrolled format stringTBD
6.2 Type system -- issues being fed from 6.40 and elsewhere6.61 Concurrent data access6.62 Concurrency -- Premature termination6.63 Protocol lock errorsParticipants at meeting 23 November 2020
Stephen Michell
Paul Preney
Peter Sommerlad
Richard Corden
Erhard Ploedereder
Clive Pygott
Michael Wong
Edition 1
ISO/IEC JTC 1/SC 22/WG 23
Secretariat: ANSI
Information Technology — Programming languages — Guidance to avoiding vulnerabilities in programming languages – Part 10 – Vulnerability descriptions for the programming language C++
Document type: International standard
Document subtype: if applicable
Document stage: (10) development stage
Document language: E
Élément introductif — Élément principal — Partie n: Titre de la partie
Warning
This document is not an ISO International Standard. It is distributed for review and comment. It is subject to change without notice and may not be referred to as an International Standard.
Recipients of this draft are invited to submit, with their comments, notification of any relevant patent rights of which they are aware and to provide supporting documentation.
Copyright notice
This ISO document is a working draft or committee draft and is copyright-protected by ISO. While the reproduction of working drafts or committee drafts in any form for use by participants in the ISO standards development process is permitted without prior permission from ISO, neither this document nor any extract from it may be reproduced, stored or transmitted in any form for any other purpose without prior written permission from ISO.
Requests for permission to reproduce this document for the purpose of selling it should be addressed as shown below or to ISO’s member body in the country of the requester:
ISO copyright office
Case postale 56, CH-1211 Geneva 20
Tel. + 41 22 749 01 11
Fax + 41 22 749 09 47
E-mail copyright@iso.org
Web www.iso.org
Reproduction for sales purposes may be subject to royalty payments or a licensing agreement.
Violators may be prosecuted.
Contents Page
ISO (the International Organization for Standardization) and IEC (the International Electrotechnical Commission) form the specialized system for worldwide standardization. National bodies that are members of ISO or IEC participate in the development of International Standards through technical committees established by the respective organization to deal with particular fields of technical activity. ISO and IEC technical committees collaborate in fields of mutual interest. Other international organizations, governmental and non-governmental, in liaison with ISO and IEC, also take part in the work. In the field of information technology, ISO and IEC have established a joint technical committee, ISO/IEC JTC 1.
International Standards are drafted in accordance with the rules given in the ISO/IEC Directives, Part 2.
The main task of the joint technical committee is to prepare International Standards. Draft International Standards adopted by the joint technical committee are circulated to national bodies for voting. Publication as an International Standard requires approval by at least 75 % of the national bodies casting a vote.
In exceptional circumstances, when the joint technical committee has collected data of a different kind from that which is normally published as an International Standard (“state of the art”, for example), it may decide to publish a Technical Report. A Technical Report is entirely informative in nature and shall be subject to review every five years in the same manner as an International Standard.
Attention is drawn to the possibility that some of the elements of this document may be the subject of patent rights. ISO and IEC shall not be held responsible for identifying any or all such patent rights.
ISO/IEC TR 24772-10, was prepared by Joint Technical Committee ISO/IEC JTC 1, Information technology, Subcommittee SC 22, Programming languages, their environments and system software interfaces.
This Technical Report provides guidance for the programming language C++, so that application developers using or considering C++ will be better able to avoid the programming constructs that lead to vulnerabilities in software written in the C++ language and their attendant consequences. This guidance can also be used by developers to select source code evaluation tools that can discover and eliminate some constructs that could lead to vulnerabilities in their software. This report can also be used in comparison with companion Technical Reports and with the language-independent report, TR 24772–1, to select a programming language that provides the appropriate level of confidence that anticipated problems can be avoided.
This technical report part is intended to be used with TR 24772–1, which discusses programming language vulnerabilities in a language independent fashion. It is also intended to be used with TR 24772-3, which discusses how the vulnerabilities introduced in TR 24772-1 are manifested in C, which is a subset of C++.
It should be noted that this Technical Report is inherently incomplete. It is not possible to provide a complete list of programming language vulnerabilities because new weaknesses are discovered continually. Any such report can only describe those that have been found, characterized, and determined to have sufficient probability and consequence.
Information Technology — Programming Languages — Guidance to avoiding vulnerabilities in programming languages — Vulnerability descriptions for the programming language C++
This Standard itemizes programming language vulnerabilities to be avoided in the development of systems where assured behaviour is required for security, safety, mission-critical and business-critical software.
In general, this guidance is applicable to the software developed, reviewed, or maintained for any application.
This document explains how the vulnerabilities described in the language-independent writeup, ISO/IEC 24772-1:2024 “Programming languages – Avoiding vulnerabilities in programming languages – Part 1: “Language-independent catalogue of vulnerabilities”, manifested in the C++ programming language and documents mechanisms that can be used to avoid those vulnerabilities.
The following referenced documents are indispensable for the application of this document. For dated references, only the edition cited applies. For undated references, the latest edition of the referenced document (including any amendments) applies.
ISO/IEC 14882:2014 — Programming Languages—C ++
ISO/IEC TR24772–3 -- Information Technology — Programming Languages — Guidance to avoiding vulnerabilities in programming languages — Vulnerability descriptions for the programming language C
ISO/IEC 24772–1 -- Programming Languages — Avoiding vulnerabilities in programming languages — Vulnerability descriptions for the programming language C++
For the purposes of this document, the terms and definitions given in ISO/IEC 2382, in ISO IEC 24772-1, in ISO IEC 14882:2023 and the following apply. Other terms are defined where they appear in italic type.
The following terms are in alphabetical order, with general topics referencing the relevant specific terms.
3.1
TBD
3.2
access:
an execution-time action to read or modify the value of an object.
Note 1: Where only one of two actions is meant, read or modify. Modify includes the case where the new value being stored is the same as the previous value. Expressions that are not evaluated do not access objects
3.3
alignment
requirement that objects of a particular type be located on storage boundaries with addresses that are particular multiples of a byte address
3.4
argument
expression in the comma-separated list bounded by the parentheses in a function call expression, or a sequence of preprocessing tokens in the comma-separated list bounded by the parentheses in a function-like macro invocation
Note 1: Also called actual argument
Note 2: An argument replaces a formal parameter as the call is realized.
3.5
argument dependent lookup
lookup that finds additional overloads from the namespaces of the types of the arguments used in unqualified function calls
3.6
class
a user-defined type declared with the class-key ‘class’ or ‘struct’ {.ul}
3.7
correctly rounded result
representation in the result format that is nearest in value, subject to the current rounding mode, to what the result would be given unlimited range and precision
3.8
diagnostic message
informational message that is either an error or a warning about an issue detected by the implementation
3.9
[erroneous behaviour]
well-defined behavior that the implementation is recommended to diagnose
3.10
erroneous value
value of an object not declared [[indeterminate]] and who’s value has not yet been set and can lead to erroneous behaviour if produced by an evaluation
3.11
explicit template specialization
a template specialization that defines a distinct implementation for the specified arguments
[3.12]
formal parameter
object declared as part of a function declaration or definition that acquires a value on entry to the function, or an identifier from the comma-separated list bounded by the parentheses immediately following the macro name in a function-like macro definition.
[3.13] friend{.ul}
function or class that can access the private and protected members of a specific class
[3.14] hidden friend{.ul}
friend function that is only declared within a class or class template definition and hence is only found by ADL
3.15
Implementation
specific toolchain that is used to build and support the execution of the C++ program
3.16
implementation-defined behaviour
behaviour, for a well-formed program construct and correct data, that depends on the implementation and that each implementation documents
3.17
implementation limit
capacity restriction imposed upon programs by the implementation.
3.18
indeterminate value
value of an object declared [[indeterminate]] and who’s value has not yet been set and can lead to undefined behaviour if produced by an evaluation
3.19
indeterminately sequenced
sequenced in a way that one of two evaluations will be executed before the other but in an unspecified order
3.20
locale-specific behaviour
behaviour that depends on local conventions of nationality, culture, and language that each implementation documents
3.21
namespace
optionally-named entity that can contain scoped declarations
[ensure that this is put into clause 4 on general concepts]
3.22
object
region of data storage in the execution environment that has a type
3.23
overload
a function or operator sharing a name with others but distinguished by having a different namespace, parameter list, qualifier set or other relevant property
3.24
overload resolution
a mechanism for selecting the best function to call given a list of expressions that are to be the arguments of the call and a set of functions that can be called based on the context of the call.
[Need a clause 4 discussion?]
3.25
override
replace the implementation of a virtual (inheritable??) function in a derived class
3.26
pointer-like-type
set of types whose values depend upon the existence and value of another object, such as references, pointers, iterators and views
3.27
protected
member of a class that is visible only to the class itself, derived classes and friends
3.28
private
member of a class that is visible only to the class itself and friends
3.29
public
visible without restriction
3.30
storage duration
property of an object that defines the minimum potential lifetime of the storage containing the object, including static storage duration, thread storage duration, automatic storage duration and dynamic storage duration
3.31
standard library
[TBD] [Is this really necessary?]
3.32
template
family of classes, functions or variables, or an alias for a family of types, or a concept
[also template specialization or explicit template specialization]
3.33
template specialization
a named template together with a set of arguments that produces a concrete instance of the entity
3.34
undefined behaviour
use of a non-portable or erroneous program construct or erroneous data, for which the language standard imposes no requirements
Note: Undefined behaviour ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). An example of, undefined behaviour is the behaviour on integer overflow.
3.35
unspecified behaviour
use of an unspecified value or other behaviour where the language standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance, such as the order in which the arguments to a function are evaluated
3.36
unspecified value
valid value of the relevant type where the language standard imposes no requirements on which value is chosen in any instance
3.37
value
precise meaning of the contents of an object when interpreted as having a specific type
3.38
virtual
TBD
3.39
wide character
bit representation capable of representing any character in the current locale
This clause requires a rewrite. See C++ Core Guidelines CPL for a good explanation of the differences.
THIS REQUIRES MORE WORK
Define unchecked (random) access in clause 3 or explain C++ approach. Likely needs a new subclause. Indexing into raw memory is a random access with no checking. In the standard library, the [] operator does random access without checking. The function at was added to provide range checking, including throwing an exceptiion if the check fails.
C++ is a strongly- and statically-typed language: all variables and expressions must have a type. C++ also permits implicit and explicit conversions between types.
C++ has a rich type system with many nuances. In addition to the C base types (int, long, float, double, char, and arrays with their C-style vulnerabilities), C++ provides the following:
Distinct types for char, signed char, unsigned charbool with a true Boolean contextenum with fixed underlying typeClasses as the primary user-defined typesArraysReferences in addition to pointersParameterized typesC++ provides library acilities that mitigate many vulnerabilities that are in the base language types, such as std::string rather than char
Use of the `explicit`{.cpp} keyword for constructors and conversion
operators`operator bool()`{.cpp} discussionmany built-in implicit conversions, refer to TR 24772-3 clause 6.2
and other clauses (C)conversion to bool and null pointer conversionslegacy code operator void\* - change to explicit operator boolC-style casts break type safety.static_castexplicit casts highlight mismatches between the design and
implementation.const and volatileconstexpr -- needs a writeup -- (in C++:11 , encouraged heavy stack
use and possible exhaustion).Narrowly tailored number-like class types, such as time_point and duration, improve safety by providing only safe and appropriate operations. User-defined types tailored to a particular use case can provide additional safety.
C++ was initially defined as a syntactic superset of the C programming language: adding object oriented features such as classes, encapsulation, dynamic dispatch, namespaces and templates. It was a “syntactic superset” because whilst there is a core of C++ that is syntactically identical to C, it has always been the case that there are subtle semantic differences between the two, for example:
Historically, C permitted the use of a function before its declaration (though this is now deprecated in C) . This is illegal in C++
Where a struct is defined within another struct, in C the inner declaration is in effect made at file scope, so the definition is available for use later in the program. In C++, the inner declaration name is qualified by that of the parent, so without qualification, the inner struct cannot be used later in the program, as in the following example
struct S1 {
struct S2 {...} m1;
...
};
struct S2 v1; /\* legal in C not C++ \*/
S1::S2 v2 // legal in C++ not CSubsequently, the two languages have diverged, both adding features not present in the other. Not withstanding that, there is still a significant syntactic and semantic overlap between C and C++, so the starting point for this report has been the equivalent for C. However, in many cases, the additional features of C++ provide mechanisms for avoiding the vulnerabilities inherited from C, and these are reflected in the following sections.
Include discussions of Object orientation, static, and const, scoped enumerations
Mix-ins (6.20, 6.40, 6.41)
C++ supports user-defined class types with one or more base classes. Dynamic polymorphism requires public bases defining virtual member functions.
C++ provides access specifiers that allow inheritance to restrict the visibility of inherited members in their subclasses.
A member declared in a class hides all members of the same name in any of its base classes. Such names can be reintroduced by the using declaration. For example:
struct Base {
int f(int i);
};
struct Derived : public Base {
// using Base::f;
int f(char c);
int g() {
return f(123); // Surprise, f(char) is called
}
};
If the using declaration is uncommented above, then Base::f(int) is called.
For multiple inheritance users can request virtual inheritance, which causes data members inherited from the same ancestor class along multiple inheritance paths to be present only once; otherwise the data members are replicated and referring to them is ambiguous unless qualified by the name of the base class from which they are inherited. When members of equal name are inherited from multiple sources, C++ rejects an unqualified use of the common name as ambiguous as long as they are not hidden.
THIS REQUIRES MORE WORK
A particular area that is misunderstood is integral promotion. It can be confusing because promotion can result in changing the internal representation of an unsigned type to/from a signed type. For expressions formed with operands of unscoped enumeration type or integral types with a conversion rank smaller than int, integral promotion occurs before further implicit conversions happen. Integral promotion on unsigned types can convert these to the signed type int. Undefined behavior [EWF] can occur due to signed integer arithmetic overflow even when the operands are of an unsigned type. Assume for example, - signed and unsigned short occupies 16 bits - signed and unsigned int occupies 32 bits then the following code causes undefined behavior:
unsigned short const x = 0xfff0;
x * x; // signed integer overflow, result will not fit in signed 32 bit intNote: C++ also uses the term promotion to apply to a subset of conversion that apply to intergral and floating point types.
Implicit, i.e., automatic, conversions to a type T can be performed, for example, in the following situations:
If the declaration, T t=e;, is defined for some expression, e, and some invented variable, t [C++17, Clause 7 [conv], para 3];
In expressions involving operands of operators (e.g., +, -, *, /, etc.) subject to the requirements of each operators' operands [C++20, Clause 7.3 [conv], para 2.1];
For example, the expression, 5 + 6.5, has operands of type int and double. Per language rules, the integer operand will be implicitly converted to double, i.e., the expression becomes double(5) + 6.5, i.e., 5.0 + 6.5.
In boolean contexts, such as
operator bool(). [C++17, Clause 7 [conv], para 4];In the expression of a switch statement: the implicit conversion will be to an integral type [C++17, Clause 7 [conv], para 2.3];
In an expression that initializes an object (e.g., an argument to a function call, the expression in a return statement) [C++17, Clause 7 [conv], para 2.4];
When a non-explicit class/struct/union constructor can be invoked on an object resulting in some desired type, T, from initial objects passed to the constructor; and
When a conversion operator has not been declared explicit, it can be implicitly invoked on an object resulting in some desired type, T, from an initial type.
Each name used in a C++ program must be declared before it can be used. A declaration denotes the existence of an name and specifies the kind of language entity the name denotes. Declarations may introduce entities, associate them with names and define their properties. For example, variables, types, functions, templates, or namespaces are entities. The declarations that define all properties required to use an entity are definitions.
If an entity is defined in multiple translation units all definitions must be the same after preprocessing. For non-inline variables with static storage duration and non-inline functions, only a single translation unit can provide a definition. These conditions form the one-definition rule (ODR). Violations of the one-definition rule are not required to be diagnosed by the compiler, they are ill-formed no diagnostic required (IFNDR).
Each declaration that appears in a C++ program is only visible in some possibly discontiguous scopes. Starting from the global scope :: namespaces form potentially nested scopes for declarations. Some declarations form their own scopes nested in other scopes. Within a function’s scope, nested compound-statements introduce nested scopes.
Within a scope, unqualified name lookup can be used to associate a name with its declaration. Within a class template unqualified name lookup will not find names that depend on a template parameter, such as members of a base class template instantiated with the class template parameters.
A qualified name is created by prefixing a name of an entity with the scope-resolution operator and the name of its surrounding scope, as in std::begin, or by accessing members of an object of class type, such as this->member. The prefix for the global scope or a surrounding namespace is often omitted, when not required, e.g., ::std::begin, but might be needed to avoid ambiguities, such as ::read.
Unqualified name lookup leading to an ambiguity is in general ill-formed.
A using declaration makes all declarations of the qualified name accessible in the current scope for unqualified lookup. This includes inheriting constructors in a derived class, that makes constructors of a base class appear to be declared as constructors of the derived class.
A using directive makes all declarations from the given namespace accessible for unqualified lookup in the current scope.
Overload resolution for functions and function templates
A name denoting the called function in a function call expression can refer to multiple function (template) declarations, known as the overload set. Overload resolution is the process used to select the best matching function from the overload set, based on the number and types of the arguments in the function call expression. Overload resolution does not take default arguments into account. After overload resolution C++ mandates there be a single candidate function (template) as the best match, which is called.
If an unqualified name is used in a function call expression and the resulting overload set does not refer to a member function (template), the overload set is extended to include functions declared in namespaces associated with the types of the arguments of the function call expression. In such a context friend functions defined within a class will be considered when an argument of the call is of that class type (hidden friends). This mechanism is called Argument-Dependent Lookup (ADL).
If an expression with an operand of user-declared type uses an overloadable operator, the resulting expression is treated like an unqualified function-call expression calling the corresponding overloaded operator functions, e.g., operator+.
Using hidden friends is a means to limit the size of overload sets for frequently defined function names, such as overloaded operators.
Compiler-provided definitions
In some circumstances C++ implementations are required to implicitly define the destructor, constructors and assigment operators for a class.
Any expression with a relational or equality operator such as a != b a C++ implementation can rewrite the expression to call a user-defined operator== or operator<=>. This mechanism appears to provide the corresponding operator function definitions, but is implemented as a rewrite of the expression.
TODO copie from generics:* TODO: most of the following should go into section 4…
C++ provides the facility Templates to support the generic programming methodology. C++ provides templates for functions, classes(types), and variables (constants). In addition one can form alias templates for class templates. Template parameters can be types, values (including addresses of global variables), and templates. C++ Templates can have variadic template parameters, that mean any number of arguments of a given kind can be used. Concepts are templates that describe constraints on template arguments and can be used to define template parameters or other deduced contexts.
At compile-time, templates are instantiated with concrete template arguments. Function templates as well as class template constructors can deduce the concrete template argument from the types of the function arguments used in a call. For class templates in addition to the implicit deduction guides provided by its constructors, explicit deduction guides can be specified. This mechanism of template-argument deduction allows one to use templates without explicitly mentioning a template argument for each template parameter. For class templates, only those member functions that are actually used get instantiated. Each template instantiation is checked for syntax, concept and type errors.
Each instantiation of a template is compiled separately, which can cause different instantiations from the same original source code to call different actual functions because of overload resolution.
When a template is instantiated, different specializations that match the template arguments are possible. There is an ranking of the different (partial) specializations that the compiler uses for selection. In case of ambiguities, the compiler will fail. Concepts are also considered during this ranking.
Templates add another level of complexity to overload resolution. If the chosen specialization compiles but is not the one expected by the programmer, the program will behave differently than expected.
Overload resolution happens before template specialization, if such occurs. This means that even though an explicit function template specialization appears to be a better match to the programmer, it is not considered by overload resolution as only the primary template is considered.
template <typename T> T foo (T);
template <> int* foo<int*>(int*); // explicit specialization of foo(T)
template <typename T> T * foo (T*) = delete;
int i;
auto a = foo(&i); // calls foo(T*), not the explicit specializationTO BE CONTINUED.
Class template and variable template specializations can provide specific code for a given set of template arguments. Such specializations must be defined in the namespace of the primary template. To prevent confusion and different compilation of identical looking template instantiations, a specialization should either be defined in the same file as the generic template, or in case of a specialization for a specific template argument type, in the file of the definition of that type.
Functions and lambdas that define parameters with the use of auto are implicitly templates without using the template keyword.
Variables defined with the use of auto keyword get their concrete type deduced from their initializer, as if they were function template parameters.
A constructor template or assignment operator template is never a copy or move operation and hence does not prevent the implicit definition of a copy or move operations even if it looks similar.
Due to the two phase compilation model of templates, name lookup can be surprising in class templates with dependent base classes. A name used in the derived class that is defined in the base might be found in an outer namespace instead.
double foo{0};
template <typename T>
struct base {
int foo;
};
template <typename T>
struct d : base<T>{
auto bar() {
return foo; // matches global foo not base<T>::foo [1]
}
};
In the above example line [1], in place of foo, either this->foo or the fully qualified name d::foo would refer to the member of the base class.
When used appropriately, templates are suitable for embedded and safety critical systems;
void *-based or macro-based genericity;While using template greatly increases type safety, there can be requirements on template arguments that can neither be specified by concepts nor checked by a compiler. For example, sorting elements requires the comparison function to provide a strict weak ordering which is a property of the values of the type to be sorted by which are impossible to check at compile time for all possible value combinations.
C++ provides means to restrict template arguments. One is to use concepts, that can prevent instantiating a template, but allow for substituting it with an alternative. A second means is to use static_assert in a template’s definition to prevent certain instantiations.
template<typename T>
struct wrapper {
T x;
static_assert(not (std::is_pointer_v<T> || std::is_reference_v<T>));
};
template<typename T>
wrapper(T )->wrapper<T>;
wrapper<int> w{42};
wrapper x{&w}; // compile error due to static_assert
wrapper<int&>{}; // compile error due to static_assertThe generic nature of templates require a more elaborate approach to unit tests. Such tests should provide instantiations of the base template and all provided explicit template specializations to ensure that each code path is actually tested. Tests for non-compilability of suppressed instantiations, i.e., through concepts or static_assert, are also beneficial.
Templates allow to reduce the amount of boilerplate code to write, e.g., by providing consistent definitions of operators. However, defining operator function templates in namespace scope can greatly influence compile times due to potential participation in the overload set, whenever the operator is used in code. In addition such generic operator functions might be picked up in inappropriate places causing programmer confusion. Implementing them as hidden friends in a CRTP base class instead makes using operator function templates feasible (see 6.20 Identifier Name Reuse [YOW]).
template <typename T>
struct Plus {
friend constexpr auto operator+(T l, T const &r) {
return l += r;
}
};
struct Int: Plus<Int> {
constexpr auto operator+=(Int const &r) {
val += r.val;
return *this;
}
constexpr Int(int v):val{v}{}
int val;
};
struct Short: Plus<Short> {
constexpr auto operator+=(Short const &r) {
val += r.val;
return *this;
}
constexpr Short(short v):val{v}{}
short val;
};
auto x = Int{4} + Int{38};
auto y = Short{4} + Short{2};
<!--
(*We may wish to summarize)*
-->
In principle, the lifetime of an object (recursively including subobjects) begins immediately after its constructor completes creating the object and ends immediately before its destructor is called.
Accessing an object outside of its lifetime is undefined behaviour, see 4.7.
C++ distinguishes references from objects. A reference has a lifetime that begins immediately after it is initialized and ends when the scope or the object containing it ends. If the lifetime of an object designated by a reference ends before the lifetime of the reference ends, the reference is dangling and further access to its referent is undefined behaviour. See [6.56] (EWF)
An object of implicit lifetime type is implicitly created when declared or allocated. For class types that are not implicit lifetime types at least one constructor must complete for the lifetime of the object to begin.
Each object has one of the following types of storage durations which influences its lifetime: - static - includes global and static variables; once lifetime has started the object is guaranteed to exist until the program ends. - thread - once lifetime has started, exists on a per-thread basis until the containing thread ends. - automatic - includes non-static local variables and temporary objects; its context defines its lifetime. - dynamic - storage for objects is acquired from dynamic memory allocation and its lifetime is explicitly controlled.
The lifetime of an object can be prematurely terminated by explicitly - calling its destructor - reusing its storage, for example, in a placement new expression - releasing its storage
Reusing or releasing an object’s storage does not call its destructor: this must be done explicitly. The correct behaviour of a program often depends on the destructor being invoked for each object of class type. Improper management of objects with dynamic storage duration can cause memory leaks.
C++ provides a number of ways that an object can be initialized, e.g.,
std::string s1{};std::string s2{"hello"};std::string s3 = s2;std::string s4{std::move(s3)};std::string s5{'a', 'b', 'c'};char a[3] = {'a', 'b'};char& c = a[0];int i{};std::string s6;These many forms of initialization typically guarantee that subsequent access to the object will be well-defined. However, in some cases the value of an object can be indeterminate after its lifetime has started unless an initializer is specified, e.g., a local int variable without an initializer (see 6.22 Missing initialization of variables [LAV]).
When a function is called, each function parameter is initialized with its corresponding argument. The initialization of a function parameter, including every associated value computation and side effect, is indeterminately sequenced with respect to that of any other function parameter (see 6.24 Side-effects and Order of Evaluation of Operands [SAM]. On the other hand, the value computation and side effects of arguments in list initialization are evaluated in the order they appear from left to right.
For objects with static storage duration and dynamic initialization in namespace scope, there is no defined global order of their initialization across translation units. Using the constexpr or constinit keyword when defining a variable guarantees its initialization at compile-time.
4.6 Inheritance
C++ supports object-oriented programming of classes. Classes may be derived from single or multiple base classes, called inheritance.
Access specifiers allow inheritance to restrict the visibility of inherited members in their subclasses; if specified, any of the three access specifiers, public, protected, and private can be used. The visibility of inherited members in a derived class defined with the keyword class and no access specifier is as if the access specifier private was used. The visibility of inherited members in a derived class defined with the keyword struct and no access specifier is as if the access specifier public was used.
A derived class can inherit from its base class using virtual inheritance, which causes data members inherited from the same ancestor class along multiple virtual inheritance paths to be present only once. If a base class is inherited more than once in a hierarchy and at least one inheritance is not virtual, its data members are duplicated and referring to them is ambiguous unless qualified by the name of the base class.
When members names are inherited from multiple sources, unqualified use of the common name is rejected as ambiguous. Note that multiple inheritance from a virtually inherited class does not constitute inheritance from multiple sources. Furthermore, any name hidden in one virtual inheritance path is hidden on all such paths. See 6.41 or 6.42 for vulnerabilities related to this issue.
Virtual functions C++ permits virtual private member functions to be overridden. (ensure this is addressed in 6.41) C++ permits pure virtual functions to have a definition outside the class that defined it, plus the ability to explicitly invoke it.
The C++ standard defines undefined behaviour as behavior for which the C++ standard document imposes no requirements. A program that uses a mechanism that invokes undefined behaviour can see any result ranging from the expected result up to and including a loss of the entire system. Even when a program behaviour based on a situation that includes undefined behaviour is acceptable during testing, its behaviour can change on the next execution with potentially disasterous results. Compilers are not required to identify constructs with undefined behaviour, yet avoidance of such constructs is essential.
Subclause 6.56 documents associated vulnerabilities for undefined behaviour.
According to the C++ standard, unspecfied behaviour is behaviour, for a well-formed program construct with correct data, that depends on the implementation. The implementation is not required to document the behaviour and the result could be unexpected. For example, the order of evaluation of function parameters is unspecified, but irrelevant if there is no dependency between the actual parameters of a call. Another class of unspecified behaviour are ones that by themselves seem innocuous but subsequent use of unspecified results can lead to undefined behaviour.
The following shows (move to 6.55)
char data[1 + int(1 + 0.2 - 0.1 - 0.1)] = { };
int size = 1 + int(1 + 0.2 - 0.1 - 0.1);
char f() { return data[size-1];} // Possible buffer overflow
This example permits the value of size to be different than the length of data because the size definition is executed at runtime and the definition of data must be completed at compile time, which is one source of unspecified behaviour. Subclause 6.55 documents associated vulnerabilities for unspecified behaviour.
According to the C++ standard, implementation-defined behaviour is behaviour, for a well-formed program construct with correct data, that depends on the implementation and each implementation is required to document. Any change in toolchain, version of the implementation, or switches used in program construction can result in changes to runtime behaviour.
Subclause 6.57 documents associated vulnerabilities for implementation-defined behaviour.
The C++ language and standard library provides several mechanisms for error handling:
std::string::npos,or combining the return value with an error indication in a sum type, e.g., std::expected<T,E> or std::optional<T> for a return type T.std::istream’s flags), or setting the thread-local variable errno. Note that the standard library provides std::error_code that can be used to map errno values for further processing.Many parts of the standard library specify preconditions and undefined behavior (4.7) results if those aren’t met, for example, std::vector<int>::front() requires a non-empty vector, when called.
Exceptions allow errors to be propagated up the call chain to a matching handler, or to program termination if unhandled. Throwing an exception due to a detected error situation allows the error to be handled at an appropriate level in a corresponding catch block. Even though the standard library provides a type hierarchy derived from std::exception, the value of any copyable type can be thrown. As an exception propagates to its handler, local objects are destroyed appropriately in reverse order of their construction in a mechanism known as stack unwinding. A search for a matching handler stops at a matching catch handler, or if unsuccessful the exception remains unhandled and the search stops at a function declared noexcept or the thread’s top-level function or main() causing program termination by calling std::terminate(). An exception propagated from constructors of non-local variables and destructors of variables with static storage duration can never have a matching handler.
Calls to any non-returning program termination functions such as abort(), std::terminate(), or exit() results in program termination without stack unwinding.
The POSIX error numbers of the thread-local variable errno are set by operating system functions as well as some parts of the C++ standard library. The C++ standard library provides additional functionality for mapping errno values to other representations.
For floating point computational errors an implementation’s floating point environment can define global side effects that can be checked by the functions declared in the header <cfenv> or by employing errno.
In addition, raising a POSIX signal can notify a running C++ program of potentially erroneous events. Using the signal() library function, a signal-handler can be defined to act asynchronously upon a raised signal, or a signal can be ignored that would otherwise cause program termination.
C++ includes concurrency through the language and library constructs, expressed by threads, tasks and coroutines. Threads are sequences of execution that can be executed concurrently with the entity (thread) that created them, and with each other. Threads in a C++ program can be used as follows:
The running program or a running thread blocks for real-world events, such as awaiting input, awaiting completion of a system-level event, or communications with non-local systems and other parts of the program can continue execution while the main program thread or other threads are blocked;
Letting a program thread await and respond to sets of events in the order that they are received.
Letting the program make effective use of multiple cores, proving significantly more computing power to a program.
Threads are initiated by calling a constructor of standard thread class such as std::thread, as documented in 6.59.
A thread in C++ runs until completion, either a normal completion or as the result of an unhandled exception. There is no mechanism in the language to directly terminate another thread.
Once declared, a thread is initialized and begins execution on its sequence of instructions. A thread can be joined, i.e. the joining thread awaits the completion of the joined thread, or a thread can be detached.
C++ threads use a fork-join model. This means that the initiating thread will wait for the completion of the initiated thread at the join place; otherwise the initiating thread will have no indication of when the created thread completes.
Threads share data and events via atomic variables, condition_variables, futures, and mutexes.
A thread terminates when it completes the execution of its top-level function.
C++ also has the notion of light weight concurrency in the form of tasks. Tasks are created by calling std:packaged_task with a function, lambda expression, bind expression or another function object to be executed. It is expected that the result of each task execution is collected by the initiator of the task at the end of that execution by calling get_future (t) and waiting for that completion.
In addition, C++ programs can interact with other programs executing in a system using operating system-level calls to initiate, schedule, communicate and destroy/terminate itself or others cooperating programs.
There vulnerabilities associated with concurrency are documented in 6.59 through 6.63 of this document.
In addition to the generic programming rules from ISO/IECTR 24772-1 clause 5.4, additional rules from this section apply specifically to the C++ programming language. The recommendations of this section are restatements of recommendations from clause 6, but represent ones stated frequently, or that are considered as particularly noteworthy by the authors. Clause 6 of this document contains the full set of recommendations, as well as explanations of the problems that led to the recommendations made.
Every guidance provided in this section, and in the corresponding Part section, is supported by material in Clause 6 of this document, as well as other important recommendations.
TBD
| Index | Reference[]{custom- style=“annotation reference”} | |
|---|---|---|
| 1 | ||
| 2 | ||
| 3 | ||
| 4 | ||
| 5 | ||
| 6 |
Need to consider C++-11, 14 and 17.
This clause contains specific advice for C++ about the possible presence of vulnerabilities as described in TR 24772-1, and provides specific guidance on how to avoid them in C++ code. This section mirrors TR 24772-1 clause 6 in that the vulnerability “Type System [IHN]” is found in 6.2 of TR 24772–1, and C++ specific guidance is found in clause 6.2 and subclauses in this TR.
As part of its design (and with few exceptions), C++ has a common subset with the complete C language. For code portions written in the common subset, the vulnerabilities described and the advice given in ISO/IEC TR 24772-3:2020, Part 3 – Vulnerability descriptions for the programming language C, apply, except when this document provides refined advice. The following subclauses usually do not further acknowledge the issues from the subset since those have been adequately addressed in the referenced document. However, C++ provides mechanisms to mitigate many of the problems that arise. Please refer to the respective clauses of this document for these mitigations and related guidelines.
The vulneability documented in ISO/IEC 24772-1:2024 applies to C++.
C++ is a statically typed language. In some ways, C++ is both strongly and weakly typed, as it requires all objects/expressions to have a type, but allows for some implicit conversions of values from one type to another type. The following cases require special consideration:
The implicit conversions between arithmetic types (including bool) that can silently truncate values or produce objects with less precision (for details see Conversion Errors [FLC]), such as integral promotion from small (unsigned) integer types (e.g. unsigned char) to the signed integer type int that can lead to operations with undefined behavior [EWF], where unsigned arithmetic is defined with modulo semantics.
The standard types used to represent text characters ( char, char8_t, wchar_t, char16_t, char32_t) are treated as arithmetic types even when used to represent text and thus are subject to implicit conversions to and from other arithmetic types. Using values of character types in arithmetic expressions, especially together with other types can be confusing. Furthermore, using values of such types or sequences of such values with their corresponding string types with relational operators might not provide an expected sorting order, depending on the language or the encoding the text represents. Libraries for text processing might take such locale-specific or text-representation-specific behavior into account, whereas the built-in operators of the language don’t.
Conversions during function overload resolution are considered explicit conversions, when there is not a direct match of argument type with parameter type. Such conversions can lead to ambiguities that result in a compile error, but there can also be a single best matching overload that does not correspond to developer’s expectation including conversion of argument types (see Identifier Name Reuse [YOW]). Even where the code presently corresponds to programmer expectation, a later addition of code can create a new “better match”, potentially resulting in a silent change of behaviour (see Namespace issues [BJL], and Conversion Errors [FLC]).
Values of an unscoped enumeration convert to their underlying integral type in arithmetic expressions (see Enumerator Issues [CCB]).
Built-in array types as well as function types will decay to a pointer type. For array types this is problematic, because the array’s extent is lost (see Unchecked Array Indexing [XYZ] and Pointer Arithmetic [RVG])
Pointer types implicitly convert to pointer-to-void (see Pointer Type Conversions [HFC]).
Pointer and reference types referring a derived class object can implicitly convert to a pointer or reference to one of its public base classes respectively (see Polymorphic Variables [BKK]).
User-defined class types can define conversion operators and converting constructors that can be called with a single argument. If these member functions are not declared with the keyword explicit they are eligible for use in implicit conversions.
Implicit conversions from an arithmetic type to bool often indicate a missing comparison. Conversions from bool in arithmetic expression can obfuscate code. For example, the following code relies on false converting to/from zero and true to one and from non-zero:
double fluxcompensation(double flux, bool compensate){
if (flux) { // double to bool conversion
double delta = compute_delta();
double const compensate_v = 1.4;
return flux + delta * compensate; // bool to double conversion
}
return 1.;
}
explicit operator bool() conversion operator. These conversions are idiomatic C++ in boolean contexts; however, some prefer explicit tests to show intent, for example, if (p) { *p = 42; } vs. if (p != nullptr) { *p = 42; }.Note that type aliases (using, and typedef) do not define a different type from their alias just a different name and thus do not incur any conversion between the alias and the aliased type.
Instead of using the built-in arithmetic types or generic library types such as std::string for your domain values, C++ permmitts wrapping them in user-defined-class types as so-called strong types. For integral values, enum class types can also be used. Strong types provide only those operator overloads and conversions for each such type that make sense in the application domain. User-defined-literal operators help with providing constants of appropriate strong types. Such strong types provide full control of conversions and operations available, avoiding semantically unsound operations that the built-in or other generic types might provide.
For example, a very simple strong type representation of temperature values can be implemented as follows:
struct Celsius {
double value;
};
struct Fahrenheit {
double value;
};
Fahrenheit convert_to_fahrenheit(Celsius c){
return { 9*c.value/5+32};
}
//...
Celsius wrong = convert_to_fahrenheit({20.}); // doesn't compile
In a realistic scenario using a library for strong type support eases the definition and use of strong types.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.2.5. and provided in the related clauses of this document.
Be aware of the rules of the type system, overload resolution, and implicit conversions to avoid vulnerabilities.
Enable compiler warnings regarding implicit conversions and use static analysis tools that provide such warnings.
Define as explicit any constructor of a class that can be called with a single argument of a different type .
Define any conversion operator as explicit
Use strong types instead of the built-in types for domain values and on system boundaries, e.g., for input, convert a read value immediately to the appropriate strong type.
Use a braced-initializer when defining variables of an arithmetic type to prevent a potential narrowing conversion from the initial value’s type.
Avoid the use of the following text-representing character types,
char,char8_t,wchar_t,char16_t andchar32_t, in arithmetic or relational expressions; use an appropriate text processing library instead for character classification, conversion, comparison, and further processing.Use a consistent style in a project with respect to implicit pointer conversions in boolean contexts.
The vulnerabilities documented in ISO/IEC 24772-1:2024 6.3 applies to C++.
The “endianness” of integer types and packing of bit fields are implementation-defined properties and not portable.
The standard library type std::endian provides a portable check the endianness of a platform that code can use to operate on individual bytes of a machine word in the correct order.
There is no portable mapping from bitfields in a struct to individual bits in a machine word. Therefore, C++ bitfields should not be used to directly map to bits in hardware, even though the compiler provides suitable mapping and manipulation operations. A further complication is that accessing a bitfield cannot easily be performed atomically, because any non-participating bits of a memory location need to be read before the relevant bits can be mutated through masking, and the complete memory location must be written again. It is possible to simulate bitfields with a defined layout through library class types that implement the required masking operations.
For individual bits, std::bitset<N> and std::vector<bool> can provide suitable representations at run time, but do not support a direct mapping to machine words. In addition, be aware that std::vector<bool> does not in general behave like a std::vector which can cause generic code to behave in unexpected ways.
The shift operations can result in undefined behavior [EWF] when shifting by a negative or too large value, or when shifting a signed operand. Bit operations should only be used on appropriate unsigned integral types with a known width, while being careful of potential integral promotion that might change a small unsigned operand type to be promoted to a signed integer type. C++ provides a rich set of bitwise operators that can be used to address the issues of bit manipulation in a portable way. When bitwise memory operations are needed, it is good practice to encapsulate such operations in a class type’s member functions.
When raw bit pattrens are used to mask or manipulate memory locations, as is permitted in C++, the reader or maintainer is left to guess the intent of the original programmer. Often such patterns are then used in different code units and finding and updating such bit masks is problematic. As an alternative, C++ supports named constants in enumeration types with the appropriate underlying type and value.
The use of a bit shift of an integral value produces a multiplication or division by a power of two in hardware but should not be used in arithmetic expressions to implement such an operation as it obfuscates the arithmentic operation. Compilers automatically implement multiplication in the most efficient way.
Except for specific situations (trivial types), objects of class type can not be assumed to have a layout appropriate to be manipulated on a byte or bitwise level. Depending on the size and alignment of its data members a class type can have padding bytes between members. The absence of padding in a trivially copyable type T can be checked with a static_assert(std::has_unique_object_representations_v<T>).
std::bit_cast can be used to reinterpret suitably sized trivial types on a bitwise level, i.e., for accessing the binary representation of a floating point value. However, for types with padding bits that do do not participate in an object’s value representation, the corresponding bits in a bit_cast result have indeterminate values. If those bits are used to compare for equality, with the function memcmp for instance, the padding bits may differ and cause false negatives. Malicious code could use such padding bits as a secret channel which might be accessed through copying the underlying bytes.
See C++ Core Guidelines ES101 use unsigned types for bit manipulation.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-3 6.3.2
Prohibit the use of shift operations to implement multiplication or division by powers of two.
Use built-in bitwise operations only with operands of unsigned integral types or enums with an unsigned underlying type.
When performing bitwise operations on operands of an unsigned type that gets promoted to a signed integral type, cast the result to the corresponding unsigned type immediately.
Ensure that the right-hand operand of a shift operation is not negative or greater than the number of bits the left-hand operand. Use static analysis tools or other means to prove that the right hand operand of the shift is not out of bounds.
Prefer std::bit_cast over other means such as reinterpret_cast to access the binary representation of an object.
Prefer abstractions in a class type encapsulating bit manipulations using unsigned types of well-defined width, such as std::bitset, over non-portable bitfields.
Forbid the use of std::memcmp, std::memcpy, or std::memmove on objects of type T unless std::has_unique_object_representations_v<T> is true.
The vulnerability as described in ISO/IEC 24772-1:2024 6.4 applies to C++.
The C++ standard assumes IEC 60559 if std::numeric_limits\<T>::is_iec559 is true for the types in use. In the absence of this, C++ makes few guarantees about the behaviour of floating point numbers. In particular - std::less is not a total order, and - std::equal is not equivalent to substitutability since NaNs compare unequal to themselves but neither less nor greater, and negative zero compares equal to positive zero.
Sorting floating point numbers with the built-in operators violates the preconditions of sorting predicates in the presence of NaN values and can raise floating point errors. The default sorting predicate std::less is suspect to this precondition violation, resulting in undefined behavior [EWF] when sorting a range of floating point values that contain NaNs. This situation can be detected when the return type of operator<=> for a floating point type is std::partial_ordering.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1 6.4.5
Verify compliance to ISO/IEC/IEEE 60559:2011 at compile time through std::numeric_limits<T>::is_iec559. Other numeric characteristics such as min(), max(), existence of NaNs, has_denorm, and infinities can be determined in this class template.
Be aware that the default comparisons in the standard library can produce wrong results when used on floating point numbers.
Check the ordering properties of the floating-point types at compile time to prevent erroneous ordering operations.
The vulnerability documented in ISO IEC 24772-1:2024 6.5 applies to C++, as C++ allows the specification of the values underlying a member of an enumeration type.
C++ provides scoped (enum class) and unscoped (enum) enumeration types, where an underlying integral type can be specified. Unscoped enumeration types without an enum-base have a non-fixed underlying type that only guarantees that values in the range of the provided enumerators are valid. The latter can cause non-representable values to be assigned to a variable of such an unscoped enumeration type. For enumeration types with a fixed underlying type all values of the underlying integral type are valid. See ISO IEC 24772-3 for vulnerabilities associated with unscoped enums with no fixed underlying type, except an explicit conversion is required to convert an integer to an enumeration type object.
Casting a value to an enumeration type is undefined behavior [EWF] unless the source value is within the range of values of an enumeration type. See CERT INT50-CPP. (remove CERT reference???)
enum Color {red=1, green, blue};
short i = red; // implicit conversion
Color g { Color(green + blue) }; // Undefined-defined behaviour since 5 will not fit into
// the smallest bitfield to hold red, green and blueC++ does not support implicit conversion of a scoped enum to other types, hence, operations (such as arithmetic operations) must be explicitly provided.
In the following example, based upon the above definition,
void foo () {
i + red ; // well-formed for unscoped enum, ill-formed for scoped enum.
}Scoped enums, on the other hand, require that the conversions to/from the enumeration type must be explicit, as shown below:
enum class Color : short { red, green, blue };
short i = static_cast<short>(Color::red);
Color operator+(Color a, short b)
{
return static_cast<Color>((static_cast<short>(a) + b) % 3);
}Unscoped enumeration types implicitly promote to an integral type and can be used as the index of an array without a cast. Without a user-specified mapping to an underlying representation, there will be “holes” as documented in ISO IEC 24772-1:2024, 6.5 and ISO IEC TR 24772-3, 6.5.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Prefer scoped enumerations with an explicit fixed underlying type.
See CPP Core Guidelines Enum.3 "Prefer class enums over 'plain'
enums".
See AUTOSAR A7-2-3 "Enumerations shall be declared as scoped
enum classes"
See MISRA C++ 8.5.3 See MISRA C++ 28.5.5 If unscoped enumerations are used, follow the general advice of ISO/IEC TR 24772-3 6.5.2 as well as the following:
For an enumeration type with a fixed underlying type, prohibit the assignment of integer values that are outside the range of the enumeration’s underlying type.
For an enumeration type with no fixed underlying type, prohibit the assignment of integer values that cannot be represented by the enumeration type.
Avoid casting integer values to enumeration type values, but if unavoidable, ensure that it corresponds to a named enumerator.
The vulnerability as documented in ISO IEC 24772-1:2024 6.6 applies to C++. C++ includes some of the conversion mechanisms of C as documented in ISO/IEC TR 24772-3 6.6, however C++ type conversion mechanisms differ from the mechanisms of C, as documented in ISO IEC 14882 Annex C. This subclause highlights differences where C++ provides mitigations of potential vulnerabilities found in C.
In C++, some conversions are explicit while others are implicit. Conversions can change: - the size of a type, - whether or not the type is signed, and - possibly other properties of the type. A narrowing conversion is when the target type cannot represent all the values of the original type.
Many errors are associated with implicit conversions. For a comprehensive overview see 7.3 [conv] of [C++23]
Explicit conversions use one of the mechanisms provided by C++ through a
static_cast,const_cast,dynamic_cast,reinterpret_cast,double(42)) or(int)3.14).The C-style cast is problematic since: - It is not easily identified in code using search tools, and - Compiler checks for correctness are effectively nonexistant.
In C++, a C-style cast is defined in terms of the C++ cast operators const_cast, static_cast, and reinterpret_cast. In some cases, it is unspecified which cast is used, for example when a cast operation involves an incomplete type. A reinterpret_cast can be used for the conversion which can produce an incorrect result. If the C-style cast is used instead of one of the C++-style casts, the cast expected by the programmer cannot be guaranteed to be the cast actually performed, and hence should be avoided.
Unlike C++'s other cast notations, dynamic_cast relies on run-time type information generated by the compiler to ensure the requested conversion is valid. If the cast is not valid, then nullptr is returned for pointer types, otherwise an exception is thrown. [C++20, 8.2.7 [expr.dynamic.cast]] Thus, dynamic_cast is safer to use when converting down or across a hierarchy. (see Pointer Type Conversions [HFC] and Polymorphic Variables [BKK])
An implicit conversion to a class type can occur for a class with constructors that can be invoked with a single argument, as in the following example:
class C
{public:
C(int x=10, float y=0){...}
};
void foo(C param){...}
void bar( bool b){ foo(b);} In the example above, it can be surprising that foo() is called with a boolean.
This implicit conversion to a class object is the default behaviour of constructors that can be called with a single parameter. The explicit keyword can be used before the constructor to prevent this happening, as in:
explicit C(int x=10, float y=0){...}The call foo(b) would now be illegal.
Implications of casting away const using const_cast are described in Modifying Constants [UJO].
Other implicit conversions can result in data loss or erroneous values. This is an issue with implicit conversions since they are automatic: the programmer does not explicitly write code to perform the conversion. For example, a common problem is mixing signed and unsigned integral types in arithmetic expressions. This can be problematic since the ranges of signed and unsigned integer types differ and the behaviour of signed integer arithmetic on overflow is undefined [EWF] whereas unsigned integer arithmetic wraps on overflow. See subclause 4.2 for a discussion of integral promotions in C++.
The issues described are not restricted to narrowing conversions, as shown below:
long l_64 = i_32 + i_32; // '+' operation preformed in 32 bits
// widened after the operation completes (and potentially overflows). The uncertainty can be avoided by converting at least one operand to the wider type as part of the operation. Note that auto directs the compiler to use the appropriate type based on the initializer expression. Subsequent use of the auto object (such as in standard mathematical operations) can lead to implicit conversions that are not obvious in the context local to the expression. Additional problems arise as a result of implicit conversions between bool and other types, thus hiding the fact when a wrong operator is used accidentally:
auto f(unsigned i, unsigned j)
{
return (i > 1) & (j = 1); // (>>, &&, ==) ?
}In the example above, all combinations of the corresponding operators will compile with different resulting types and results.
Similar issues arise in conversions between character types (char, char8_t, …) and other types. Character types are provided to represent text in whatever character representation is needed.
void f(char c)
{
if (c < 0) // can be always false on some platforms.
{}
}In addition to the use of strong types (see Type System [IHN]), the implicit conversions and multitude of possible operations of integral types can be mitigated by using scoped enumeration types with the corresponding integer type as its underlying type. For example, std::byte is defined to address individual unsigned char elements (bytes) in memory without participating in arithmetic or bitwise operations.
Because C++ allows function and operator overloading, the effect of implicit conversions provides an additional mechanism of failure, by selecting an unwanted overload during overload resolution due to implicit conversions. This can influence failure modes with lookup as described in section Namespace Issues [BJL]. // Add overload resolution reference!!
C++ also provides a library function std::bit_cast. This function provides the ability to preserve the bit representation when converting between unrelated types.
If such is meaningful, then std::bit_cast reduces the risk of some undefined behaviours compared with other type punning approaches such as casts or unions.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024 6.6.5:
Prohibit C-style casts; use C++ type conversion operators instead, as they document intent and disallow some unsafe conversions. (see C++ Core Guidelines ES.49)
Avoid casts and type punning (see C++ Core guidelines ES.48), and treat every cast as a candidate for refactoring. If type punning is required, use std::bit_cast.
Avoid narrowing conversions. (see C++ Core Guidelines ES.46)
Prefer the use of braces over the use of parentheses for initialization or explicit conversions from numeric types to prevent narrowing. (see C++ Core Guidelines ES.23 and ES.64)
Declare explicit
Avoid casting away const. See subclause Modifying Constants [UJO]
(see C++ Core Guidelines ES.50)
Avoid arithmetic operations on operands that can be promoted. In partcular, be aware of the arithmetic operators that promote their operands, and the types are subject to promotion.
Avoid widening the result of an arithmetic operation; prefer instead to widen the type of one or more of the operands
Prohibit mixing signed and unsigned arithmetic (see C++ Core Guidelines ES100)
Avoid implicit conversions between bool and other types.
Avoid implicit conversions between character types and other types.
Use strong types (see subclause Type System [IHN]) to limit implicit conversions and unwanted operations.
When using auto ensure that you know the type that auto deduces type and resist using auto for the predefined types
The vulnerability as documented in ISO/IEC 24772-1:2024 6.7 exists in C++ when C-style strings are used. For example, passing a non-null-terminated character array to an interface that requires a null-terminated string can result in undefined behaviour [EWF]. C++ provides alternative string processing capabilities that do not exhibit those vulnerabilities.
C++ provides a class template for string processing, std::basic_string, that manages the space for the string and the string length and always includes a string termination character. For example, when concatenating, the std::basic_string object will increase in size to contain the resulting string. Futhermore, as the string is guaranteed to have a string termination character, using its underlying raw pointer as a C-style string will mitigate this vulnerability because the string termination character is present.
C++ provides the library class templates std::basic_stringview and std::span that implement reference semantics to non-owned buffers. These types do not rely on a string termination character to determine the length of the string, thus, use of these types avoids those vulnerabilities. However, using its underlying raw pointer as a C-style string can result in these vulnerabilities because the string termination character is not guaranteed to be present.
void foo(std::string const& s, std::string_view const& sv)
{
puts(s.data()); // okay string has termination character
puts(sv.data()); // not okay; string termination not guaranteed
}To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Avoid C-style strings.
If using C-style strings is unavoidable, apply the avoidance mechanisms of ISO/IEC TR 24772-3:2020 6.7.2.
Ensure that explicit overflow checks are performed for all string operations.
Prefer using a string type, e.g., std::string, that manages the memory of its string and handles termination in order to mitigate against this vulnerability.
Prohibit the use of the underlying raw pointer of a std::string_view or a std::span with interfaces that expect C-style strings.
Use static analysis tools to help identify occurrences of undefined behaviour.
The vulnerability as described in ISO/IEC 24772-1:2024 6.8 exists in C++ when arrays are managed using raw pointers or indexing. The range of valid raw pointers to a plain array a are from the first element to one past the last element of the array, i.e., in the range [std::begin(a)..std::end(a)). An object o can be treated as a single element array with respect to pointers referring to it.
C++ provides facilities to encapsulate code that is exposed to this vulnerability. The standard library defines features that mitigate or circumvent this vulnerability. For example, std::string, std::vector, std::deque, and iostreams manage buffers internally; using “range-for” such as for (auto &e :some container) and the algorithm library to access elements e of the container without the possibility of a buffer boundary violation.
However, the member function data() of the contiguous sequence containers returns a non-const pointer to the underlying elements. This allows manipulating the underlying memory directly, bypassing the safety features of the container leading to this vulnerability. For example, std::string::data() returns a non-const char*.
When working directly with iterators referring to a container, one need to ensure that those iterators are and remain valid. For example, for a container c, incrementing an iterator beyond the end(c) iterator or dereferencing the iterator denoted by end(c) are undefined behavior [EWF].
In general, validity of iterators requires programmer care to prevent out-of-bounds access of the underlying container:
For example, using algorithms and iterators correctly to convert an input string to lower case:
std::string to_lowercase(std::string_view s){
std::string result{};
transform(
begin(s), end(s), // input range #1
std::back_inserter(result), // output iterator #2
[](char c){ return std::tolower(c);});
return result;
}The above example passes two ranges of characters to the transform algorithm. Potential errors due to a boundary violation can be caused by the following changes:
begin(result) instead of back_inserter(result),The second problem occurs in the following code if the length of s is longer than 31:
std::string to_lowercase(std::string_view s){
std::string result{'\0', 31};
transform(
begin(s), end(s),
begin(result), // error, only space for 31 characters
[](char c){ return std::tolower(c);});
return result; // size(result) == 31
}An additional issue arises when performing an operation that invalidates an in-use iterator, such as the iterator internally used by the range-for statement below:
std::string to_lowercase(std::string s){
for (auto &c:s){
s.append(std::tolower(c)); // error, invalidates in-use iterator
}
return s;
}Overflows can also occur through the use of C-style strings, which can be treated as arrays of characters, but mishandling of the nul termination makes overflows possible. See clause 6.7 String Termination[CJM].
Since plain (C-style) arrays when passed as function arguments decay to pointers, the array dimension is lost, which leaves the developer responsible for including dimensionality as a sepearate parameter. If the passed value differs from the actual dimension of the array, many vulnerabilities arise within the function. C++ provides several means of keeping the array dimension available to the called function:
std::array as parameter type,std::views::counted or another view as parameter type,std::span as parameter type for plain arrays,std::string_view as parameter type in favour of char const*, orFor further explanation and examples, see
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024 6.8.5.
Avoid C-style arrays. If unavoidable, guidance for the use of C-style arrays is provided in ISO/IEC TR 24772-3:2020 6.8.2.
Avoid container functions, such as data(), that bypass the safety features of the respective containers.
To model a fixed-size array, use a library class such as std::array.
To model arrays with dynamically changing size, use containers of the standard library, such as std::vector or std::deque.
Avoid using a pointer parameter or a pointer-and-size parameter pair for representing a contiguous buffer; instead use a range parameter, for example, std::views::counted, std::span, or std::string_view.
Prefer using range-based or iterator-based algorithms, such as those of the standard library, over pointer-manipulating or indexing loops.
Use the range-based for statement to iterate within the defined bounds of a range.
Ensure that ranges and iterators used by range-based for are not invalidated for the duration of the loop.
Ensure that ranges and iterators passed to algorithms are not invalidated for the duration of the loop.
When performing random access by indexing, apply the avoidance mechanisms of clause 6.9.2.
Use static analysis tools to detect buffer boundary violations.
The vulnerability as documented in ISO/IEC 24772-1:2024 6.9 applies to C++ when an access is performed using operator[].
C-style arrays, C-style pointers, random-access iterators, and some standard library containers allow element access via operator[] which is unchecked. However, those standard library containers also provide an access function at() that behaves like operator[], but performs a check that the access is within the bounds of the container and throws an exception otherwise.
For issues associated with exception handling and error handling, see clause 6.36 Ignored error status and unhandled exceptions.
The parameter type for contiguous sequences std::span does not provide a checked version of indexing and therefore should only be used via its iterator/range API. With the introduction of the at() function in C++ 26, a checked version of indexing is available. For more information on the use of std::span see 6.8
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024 6.9.5.
Follow the advice from clause 6.8.2.
Prefer the at() member function of the standard library containers over operator[] and handle the bounds violation exceptions.
If indexing cannot be avoided, use static analysis or explicit checks to establish that bounds violations do not occur.
The vulnerability as described in ISO/IEC 24772-1:2024 6.10 applies to C++, but can be mitigated using features provided by the language.
A buffer overflow occurs when some number of elements is copied from one buffer to another and the amount being copied is greater than is allocated for the destination buffer. This is a special case of 6.8 Buffer Boundary Violation [HCB]. The C library functions or hand-written loops for copying bytes or C-style strings are especially prone to this vulnerability.
As with clause 6.8 [HCB], in most cases the vulnerability can be avoided by using library classes, such as std::vector or std::string, which provide a copy operations operator that adjust the size of the target to fit the object being copied.
The standard library algorithms that copy into a target range can suffer from this vulnerability. In the case of potential overflow, the programmer must either ensure automatic extension of the underlying container, such as by using std::back_inserter(container) as the output iterator, or ensure that the output range has sufficient space available. In the case of overlapping input and output ranges, the suitable copying algorithm must be selected, depending on the relative ordering of the ranges. In general, this situation can be avoided by using a more appropriate algorithm, for example, std::rotate.
For arrays with fixed sizes the assignment operator or copy-constructor of std::array is the means of safe array copying.
If a system requires its own container types with dynamic size, a naïve implementation might attempt to keep the copying external, like with C-style arrays. Such external copying should be avoided.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024 6.10.5.
Use standard library containers instead of C-style arrays, such as std::vector or std::array, that provide copying mechanisms that ensure the target array is large enough for the indicated source.
When using standard library algorithms that copy into an output range, ensure that either the output range is extended automatically or has sufficient space available.
Sidestep the need to use a copy algorithm on overlapping ranges, by selecting a more appropriate algorithm, such as std::rotate.
If a system requires its own container types with dynamic size, ensure that their copy operations are correct and do not require loops or algorithms for copying their content.
If copying C-style arrays is unavoidable, statically show that an access outside of the array cannot occur, or alternatively perform range checking before copying the array.
Avoid using the copying functions from the headers
The vulnerabilites as described in ISO/IEC 24772-1:2024 6.11.1 applies to C++. In addition to pointers, C++ references are also vulnerable and the issues below include references when pointers are mentioned. In places where references cannot be substituted the corresponding code won’t compile.
In general casting pointers breaks the type system and should be avoided.
In C++, a C-style cast is defined in terms of the C++ cast operators const_cast, static_cast, and reinterpret_cast. In addition, certain placement new expressions that do not initialize the storage can act like reinterpret_cast. In some cases, it is unspecified which cast is used, for example, when a cast operation involves an incomplete type, a reinterpret_cast may be used for the conversion which can produce an incorrect result.
Especially, reinterpret_cast has the problem that it takes the original pointer value as a pointer of the target type rather than the original type. The C++ standard defines most cases where that happens as undefined behavior [EWF]. For example, the lifetime model of C++ might result in accessing the target type object outside of its lifetime (see 4.4). Other run-time issues can be caused by alignment violations. Using reinterpret_cast<std::byte*> to access the underlying memory of an object by casting its address permits access to the raw memory. However, casting the address of a piece of raw memory with the correct alignment and size to an object pointer and accessing that object is undefined behavior for most types, because doing so, will not start the lifetime of the object (see 4.4).
static_cast only works, where conversion of the source type to the target type are related. However, with pointer types the compiler cannot always check that the actual object type corresponds to the desired target type, causing invalid casts. Naïvely assuming that addresses of a derived object and its base object are identical is wrong in most cases. For example, with multiple inheritance, the address of an object may be different than one of its base class sub-objects. Using the generic pointer type void* (which is common in C APIs) allows converting between arbitrary pointer types using static_cast. Most conversions via void * where the originial object type and the final target type are different are undefined behavior in C++. C++ allows reinterpret_cast to a pointer to an incomplete type or a static_cast from void * to a pointer to an incomplete type. Pointers to objects can implicitly convert to void * (cv-qualified accordingly).
In general, the C++ standard only defines reinterpret_cast to obtain the pointer back to the original type. It is implementation-defined if that bidirectional void * conversion also works for function pointers Implementation-defined behaviour [FAB]. A reinterpret_cast can be used to convert a pointer from the integral types std::uintptr_t/std::intptr_t, but only if the value of the integer value was previously obtained by converting a valid pointer to said integral type. Casting an arbitrary integral value to a pointer is undefined behavior.
Casting along the inheritance relationships with dynamic_cast is safe, but requires the dynamic type to be known, which is the case when the types declare virtual member functions. Within a constructor or destructor only the static type of the current class is relevant, because the lifetime of any derived class object hasn’t started or has already ended. See subclause Polymorphic Variables [BKK].
Conversions involving const and/or volatile properties of a type are permitted using const_cast (see Modifying constants [UJO]). Adding const with const_cast is safe.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Use the avoidance mechanisms of ISO/IEC 24772-1:2024 clause 6.11.5.
Avoid the C-style cast, reinterpret_cast, and casts to and from void*.
For conversions that remove the const qualification, see the guidance in clause Modifying constants [UJO].
When downcasting or crosscasting see the guidance in Polymorphic Variables [BKK].
If pointer and reference conversions cannot be avoided, treat compiler warnings that are issued for such conversions as hard errors.
Ensure through static analysis that arbitrary pointer casts return a type compatible with the source.
The vulnerabilites described in ISO/IEC 24772-1:2024 6.12.1 also apply to C++. The vulnerabilities caused by out-of-bounds access are covered in clause 6.8.
Pointers to functions, pointers to members, and pointers to void do not allow pointer arithmetic.
The set of valid pointers referring to an array consists of the pointers to each array element plus the pointer just past the end of the array, however dereferencing a pointer one past the end of the array is undefined behaviour [EWF]. A pointer to a single object is considered to point to an array of size one with respect to pointer arithmetic.
Adding or subtracting an integral value to a pointer value must yield a result that is a valid pointer refering to the same array, otherwise the behavior is undefined. Note: the built-in indexing operator is defined in terms of pointer arithmetic.
Subtraction of two pointers has undefined behaviour unless both pointers refer to the same array or are both null.
Comparison of two pointers with one of the operators < > <= >= <=> has unspecified behaviour unless both pointers refer to the same underlying array or object. The standard library function objects for comparison, like std::less<>, provide a strict total order of pointers of a given type.
Iterators as defined by the standard library suffer from similar vulnerabilities as pointer arithmetic. Comparison and subtraction of two iterators, or computing std::distance cause undefined behavior for iterators that refer to different ranges. Forming an iterator that is outside of its underlying range similarly is undefined behavior.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Avoid explicit pointer arithmetic, especially on a pointer to a single object.
Prefer calling range-based algorithms over pointer arithmetic.
Use the avoidance mechanisms of 6.8.2.
Compare arbitrary pointers to objects with the standard library comparison function objects instead of the operators < > <= >= <=>.
The vulnerability as described in ISO IEC 24772-1:2024 6.13 applies to C++. Dereferencing a pointer with the value of nullptr is undefined behavior [EWF].
Using pointers is inherently problematic especially for function parameters and return values, due to the following issues:
nullptr value valid in context?nullptr value?T[] as a function parameter type is identical to T*)Using values instead of pointers sidesteps all pointer vulnerabilities, especially when returned from a function. For example, standard library containers like std::vector have value semantics and do not suffer from this vulnerability.
C++ references cannot be null in a well-defined program and solve the null-dereferencing vulnerability. They are particularly useful as function parameters. Using a reference as function return type requires the caller to avoid accessing an object outisde of its lifetime (see [XYK], [DCM]).
If absence of a value is necessary, a class type for optional values such as std::optional provides well-defined behaviour and single-object ownership. In case of attempting to access the value of an empty std::optional an exception is thrown.
Note: Be aware that optional<T&> is not supported by the standard library. For representing optional references std::optional<std::reference_wrapper<T>> or a non-standard implementation of optional supporting references can be used.
If dynamically allocated objects are required, std::unique_ptr<T> can be used for lifetime-management and for transferring ownership. When shared ownership of such objects is necessary, std::shared_ptr<T> is a solution. Using std::shared_ptr<T const> provides value semantics for immutable heap-allocated objects thus sidestepping most of the issues of pointers above. Constructing a smart pointer through the factories std::make_unique or std::make_shared will return a non-null smart pointer or throw an exception and thus prevent the vulnerability of null pointers, in contrast to legacy allocation mechanisms and some overloads of operator new. However, in general dereferencing a std::unique_ptr or std::shared_ptr equal to nullptr causes undefined behaviour, for example, when such a smart pointer is default constructed or a std::unique_ptr is in a moved-from state. For further information see also [XYL].
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Avoid the need for pointers by using values.
Prefer references over pointers for parameters.
Use std::optional instead of a pointer to represent a potentially empty value.
Use containers with value semantics instead of plain arrays such as std::array, std::vector.
Use make_unique() or make_shared() for dynamically allocating objects.
If using objects of pointer-like types that may be a nullptr value is unavoidable, use the avoidance mechanisms of ISO/IEC 24772-1 clause 6.13.5.
Use static and dynamic analysis tools to detect potential violations of this guideline.
The vulnerability documented in ISO/IEC 24772-1:2024 6.14 and ISO/IEC TR 24772-3:2020 C applies to C++. C++, however, provides mechanisms to mitigate the vulnerability. In contrast to C, where the mere existance of reachable memory for an object is sufficient to access it, the lifetime model of C++ makes it undefined behaviour [EWF] to access an object outside of its lifetime See 4.4. This results in undefined behavior when an object access is attempted before one of its constructors is finished or after its destruction. For example, container types like std::vector or wrapper types like std::optional might have memory for an object available, that is not constructed or has ended its lifetime. For similar situations that result from accessing temporary objects or variables outside of their lifetime see [DCM]. If such a temporary or local object manages heap memory (e.g., std::vector) referring to an element after the manager’s lifetime ended falls into the category of this vulnerability, but is covered there.
C++ provides a rich set of pointer-like types (potentially referring to heap memory) whose values may dangle, e.g.,
std::string_viewstd::spanstd::reference_wrapper.In addition, a user-defined class type can be a pointer-like type, if a subobject is of pointer-like type and refers to an object (target) whose lifetime is different from and not managed by the current object. Sometimes, regular object types act as pointer-like types, e.g., indices into a container or operating system handles, and their validity can not be directly mapped to the C++ object lifetime model.
If the lifetime of a pointer-like value ends before the lifetime of its target, then the vulnerability does not apply to that pointer-like value. This is the primary C++ strategy for avoiding vulnerabilities of dangling pointer-like values. For example, an object argument passed as a function parameter of reference type persists throughout the function call. The lifetime guarantee of a function argument passed indirectly via a pointer-like type does not apply if * the target is destroyed explicitly by the called function (taking ownership of the target) or a concurrently executing operation, or if * copies of the pointer-like parameter outlive the function call, for example, as the return value, or in a coroutine or thread frame.
For objects directly allocated on the heap C++ provides smart pointers and corresponding factory functions (e.g., std::make_unique()) that allow transferring ownership or shared ownership to reduce the risk for dangling. However, storing the raw pointers managed by smart pointers can lead to accidental dangling, for example:
int * f(){
auto up = std::make_unique<int>(42);
return up.get(); // returned pointer dangles
}The C++ library containers, such as std::vector, manage the required heap memory for their elements. Referring to an element in a container via a pointer-like type is safe, as long as the container remains unchanged while the element object is accessed. In general, accessing an element in a mutated container via a pointer-like value obtained before the mutation is undefined behavior [EWF]. Different containers provide different validity guarantees of accessing an element via a pointer-like type that was obtained before a subsequent change in that container.
Hand-written loops are prone to attempt to access elements of a container that are non-existent, or have been relocated. Employing standard library algorithms to iterate over a range of elements from a container tends to be safer, as long as the underlying container is not accidentally changed. For example, the following code can cause a failure, due to the attempt to iterate over a changing std::vector:
std::vector v{1,2,3,4};
copy(begin(v),end(v),back_inserter(v)); // modifying v while iterating is undefined behaviour
copy(begin(v),end(v), std::ostream_iterator<int>(std::cout,", "));A C++-specific way of causing dangling_references to the heap is by means of a placement new expression (see 7.4 Placement new issues [XXX]).
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024 6.14.5.
Prefer value types that manage heap memory, for example, std::vector or std::string, and pass by value/return by value over the use of pointer-like types.
If value types are not feasible, adopt a style that makes explicit the ownership and lifetime of heap resources, by using std::unique_ptr or std::shared_ptr or similar manager types and allocate heap memory exclusively with their corresponding factory functions std::make_unique/std::make_shared.
Ensure all copies of any pointer-like value are no longer accessible or accessed after the referent’s lifetime ends.
Prohibit access of a container through an invalidated iterator, and similarly, access via other potentially dangling pointer-like values, e.g., views.
Avoid unmanaged heap allocation by new/delete expressions or calling C-library heap allocation functions.
Avoid manually ending the lifetime of an non-owned object by explicitly calling its destructor or std::destroy_at.
Use static and dynamic analysis tools to detect dangling.
The vulnerability as documented in ISO/IEC 24772-1:2024 6.15 applies to C++, since overflow situations are undefined behaviour [EWF] for signed integer arithemtic and wrap-around for unsigned integer arithmetic, which can lead to surprising results. C++ specifies that
For example, integral promotion happens when multiplying two unsigned short operands which can result in undefined behavior:
auto f(){
std::uint16_t x{50'000},y{50'000};
return x * y; // undefined behaviour due to overflow, returns int
}In the above, for a 16-bit short and a 32-bit int, i.e., std::numeric_limits<int>::max()==0x7fff'ffff, x and y are promoted to int and the multiplication then overflows which is undefined behaviour.
Even when operands have the same unsigned type, wrap-around arithmetic can be confusing, for example, 4U - 5U yields a large positive value.
Calling a function taking a parameter of integral type with an argument of different integral type works due to implicit conversions. If a different overload with a better match becomes visible the called function can change when re-compiled (see 6.21 Namespace Issues[BJL])
Using brace-initialization prevents implicit narrowing conversions in contrast to other forms of initialization. For example:
std::uint16_t x{500'000}; // won't compile due to narrowing
std::uint16_t y = 500'000; // compiles, but truncates valueThe mitigations for wrap-around errors in C++ are different than for C. The type system of C++ allows user-defined class and enum types with corresponding overloaded operators. Such user-defined types can individually control which implicit conversions or mixed type arithmetic they support, if any. For example, one can force arithmetic to be done with unsigned types:
enum class uint16: std::uint16_t{};
uint16 operator*(uint16 a, uint16 b){
return static_cast<uint16>(static_cast<unsigned>(a) * static_cast<unsigned>(b));
} // guarantee wrap-aroundHigh-integrity software using the built-in integral types should
<cstdint>),To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024 6.15.5.
Use appropriate user-defined types with well-defined range in place of built-in integral types, that mitigate against implicit conversions and undefined behaviour.
Use types with a well-defined range and have compile-time guarantees that the range of types used is sufficient, i.e., with static_assert.
Avoid implicit integral promotions and integral conversions, especially on function arguments.
Avoid mixing integral types with different size or different signedness in the same expression.
Ensure that the result of any mathematical operation fits within the constraints of the types involved within the expression.
Use unsigned types that do not promote to int to avoid undefined behavior due to signed integer overflow.
Consider the use of numeric_limits<T>::is_modulo to determine whether or not an integer type T wraps for the target system.
Document where wrap-around is expected for a type.
Use static and dynamic analysis tools to detect problematic expressions.
The vulnerability as documented in ISO/IEC 24772-1:2024 6.16 exists in C++. C++ complicates the discussion in 24772-1:2024 6.16 as a result of the integral promotion (see 6.06 [FLC]). A left-shift on an operand that gets promoted can result in a value outside the operand’s unpromoted type’s range.
Not every use of a shift operator is a bit-shift due to operator overloading.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
The vulnerability as described in ISO/IEC 24772-1:2024, 6.17 is applicable to C++, as it is susceptible to errors resulting from the use of similarly appearing names. However, the language rules prevent using an identifier that has not been declared. There are two possible issues: the use of the identical name for different purposes (see clause 6.20 Identifier Name Reuse [YOW]) and the use of similar names for different purposes.
Item and item as distinct declared identifiers with different meanings.BigDog and Big_Dog are different identifiers. Multiple underscores or a leading underscore followed by an upper-case letter cause undefined behaviour [EWF], thus Big__Dog and _BigDog should not be used.item and items. However, C++ lets the programmer use the identifier item for a single object of a type T and the identifier items for an object denoting a range of items of T. The use of item where items was intended or vice versa usually will be detected by the compiler because of the type mismatch.Long_IdentifierA and Long_IdentifierB are always different if the documented identifier limit is at least 16 characters.C++ permits the use of names such as x, xx, and xxx, possibly defined in non-obvious scopes, and a programmer can easily, by mistake, write xx where x or xxx was intended. Especially for overloaded functions, argument-dependent-lookup might find a function in a scope that the user did not consider. The use of the wrong name will typically result in a failure to compile so no vulnerability will arise. However, if the wrong name has a type compatible with the intended name’s type, then an incorrect executable program will be generated.
C++ defines reserves some names as context-specific keywords. While it is technically possible to use those names for other purposes, such use can be confusing.
In the global scope some namespaces (such as std, posix) are reserved and should not be used otherwise.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.17.5.
Follow the rules of ISO/IEC 14882:2023 clause [lex.name] regarding names to refrain from usage.
Avoid the use of similar names to denote different objects of the same type.
Use consistency in choosing names, especially for dealing with similar names.
Use static analysis and tooling to enforce project-specific naming rules and detect possible similar names, for example, homoglyphs and unexpected text-direction vulnerabilities.
Keep the scope of names as small as reasonable.
Ensure that the names in your program do not exceed the compiler’s documented limit.
Do not differentiate names through only a mixture of case or the presence/absence of an underscore character.
Do not use as identifiers the contextual keywords final , import, module and override and other reserved names.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.18 exists in C++.
The language definition permits the compiler to eliminate effects of the abstract machine that are not observable, in particular, dead stores. For example, the often-attempted write operations to non- volatile member variables in a destructor that are not subsequently used can be elided by the compiler.
C++ compilers and static analysis tools do exist that detect and generate warnings for dead stores.
The error in ISO/IEC 24772-1:2024 6.18.3 that the planned reader misspells the name of the store is possible but unlikely in C++ since the language specifies that all objects shall be declared and typed, and the existence of two objects with almost identical names and compatible types (for assignment) in the same scope would be readily detectable. See 6.17 [NAI] Choice of clear names, 6.20 Identifier name reuse [YOW], and 6.21 Namespace issues [BJL]
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.18.5.
Use compilers and static analysis tools to identify dead stores in the program.
Provide sufficient synchronization to non-const variables that are accessed by multiple execution agents. See clause 6.61 Concurrent data access [CGX]
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.19 exists in C++.
A common practice for resource management in C++ relies on what is called “RAII” or “SBRM” (scope-based resource management): employing a class’ destructor to release resources managed by the object. This can lead to code without visible use of a variable being present in the source code, because all work is done by the variable’s constructor and/or destructor.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.19.5.
Use compiler warnings and/or static analysis tools to indicate and eliminate unused variables.
The vulnerability as described in ISO/IEC 24772-1:2024, 6.20 exists in C++, except for the second issue of limited identifier length.
In C++ all characters in an identifier are significant.
In C++ the same name can be used for different functions as long as they have distinct parameters or different scopes where they are defined.
See also 4.3 Name Lookup and Overload Resolution.
Using directives don’t import a name to the current scope. Only names not found in the current or its surrounding scopes up to the common scope shared with the namespace in the using directive will be made accessible.
namespace NS1 {
int const a{1};
}
namespace NS2{
int const a{2};
void foo(){
using namespace NS1; // attempt to access NS1::a
std::cout << a; // prints 2
}
}
In a lambda expression only identifiers are accessible that captured from the surrounding scopes, because the scope of a lambda expression is not nested within its surrounding scope. Variables with static storage duration or constexpr variables are also accessible, because they allow the lambda to be returned from its current scope without dangling. Some special rules apply for lambdas in member functions when capturing this with respect to the member functions. Lambda capture defaults [&] or [=].{cpp} carry the risk to accidentally access an unintended variable from the outer scopes.
int a1{0};
void bar(){
constexpr int b{42};
int a2{3};
[](){ // ok to use std::cout because its global
std::cout << a1; // typo prints 0
std::cout << b; // constexpr OK
}();
auto lam=[=](){
std::cout << a2;
};
{
int a2{4};
[a2](){
std::cout << a2; // prints 4
}();
lam(); // prints 3
}
}
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.20.5, with the exclusion of guidance related to truncated identifiers.
Qualify names to disambiguate potential conflicts between names introduced from different scopes.
Use modern integrated development environments that inform about the declaration of any identifier occurrence.
Enable compiler diagnostics that inform about the hiding of declarations.
The vulnerability described in ISO/IEC 24772-1:2024, 6.21 exists in C++. It can occur in particular when a used library changes its API. See also 4.3 Name Lookup and Overload Resolution. The situations where it exists are related to the following cases:
In the case of template specialization or non-identical definitions of the same entity in different translation units (ODR-violation), ill-formed code can be the result, however, a C++ compiler is not obliged to diagnose that situation (IFNDR), leading to undefined behaviour [EWF]. Care must be taken, when a definition in a file included in multiple translation units can be modified through the preprocessor [#NMP].
In the case of overloading and overriding cases, C++ compilers are required to diagnose an ambiguity if it exists. A function object is never subject to overload resolution. Hiding a function with a function object ensures that in calls where the function object is visible, it is used.
Subsequent code changes, such as the addition of a function overload, can change the resulting function selected by overload resolution in case of a non-exact match previously found, e.g., found via implicit conversion of an argument. The call in question then changes its binding without warning upon its next compilation:
void foo (long);
// void foo (int);
void bar ()
{
foo (0); // The call to 'foo(long)' was intended
// but requires implicitly conversion from 'int' to 'long'.
// Once 'foo(int)' is introduced it is a match without
// the implicit conversion, hence is silently chosen
}A new declaration can impact existing code in a number of situations involving the addition of:
namespace NS1
{
void f1 (int);
void f2 (int); // Added later
}
namespace NS2
{
using namespace NS1; // 'f1' needed
void f2 (long);
void bar ()
{
f1(0); // Calls 'NS1::f1'
f2(0); // Unintentionally calls 'NS1::f2'
}
}
namespace NS3
{
using NS1::f1; // 'f1' needed
void f2 (long);
void bar ()
{
f1(0); // Calls 'NS1::f1'
f2(0); // Calls 'NS3::f2' as expected
}
}The following example demonstrates a situation (#1) where the late addition of a better matching overload causes a silent change in the semantics of an existing program.
struct A
{
};
template < typename T > T foo ( T t ) // general
{
return t;
}
// This code will be added later
// template < typename T > T * foo ( T * t ) // more specialized
// {
// return t;
// }
void bar()
{
A * a;
foo (a); // #1
}After the commented-out code is added, the binding of function call foo (a) (#1) changes silently from the general foo to the more specialized foo. This issue can be only be avoided by having these unrelated function templates in separate namespaces and explicit qualification at the call site.
Analogously, adding an explicit template specialization can cause different template instantiations, even indirectly, which can be surprising.
A similar situation can occur when a conflict arises between compiler-synthesized or rewritten operators and explicitly created versions of those operators, as in the following example.
struct A
{
bool operator==(A const &) const { return true; }
};
// Evil hijacking of !=
// bool operator != (A const &, A const &) { return true; } // #2
void bar (A const & a) {
a != a; // #3
}In the above example, the declaration of operator== will have a corresponding synthesised operator!= generated by the compiler, since there is no suitable user-declared !=. If the operator!= becomes visible, then the code at #3 uses the user-declared operator!= instead of the synthesized one, which can lead to a silent and unexpected change of behaviour. This is particularly risky when the operator is declared outside of the immediate visibility of the original definition.
Argument-Dependent Lookup (ADL) can lead to surprises when combined with generic code, because template instantiation happens only when needed. The following example demonstrates that problem:
#include <iostream>
#include <typeinfo>
namespace Y {
template <typename T>
void print(T i){
std::cout << typeid(T).name()<< ":" << i ;
}
template <typename T>
void println(T x){
print(x); // Unqualified call, expects to call Y::print
std::cout<<'\n';
}
}
namespace X {
struct A{
A(double){}
friend // make this a hidden friend
std::ostream & operator << (std::ostream & out, A const &a){
return out << "An A as expected\n";
}
};
void print(A a){ // not expected to be called by println
std::cout << "Surprise happens!";
}
}
int main(){
X::A a{3.14};
Y::println(42); // i:42 - calls Y::print
std::cout << a; // An A as expected - calls X::A::operator<<
Y::println(a); // Surprise happens! - calls X::print
Y::println(42u);// u:42 - calls Y::print
}The above code calls the overload X::print(A) from Y::println since it is pulled in by ADL. On the other hand, ADL is required for X::A::operator<< to work.
The consideration of implicit conversions together with ADL can be suppressed by defining operator overloads as class members or as hidden friends.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of 6.40.2 and 6.41.2 as applicable.
Avoid implicit conversions of function arguments.
Prefer operator overloads as member functions or hidden friends of the class; otherwise place overloaded operators that are not class members and cannot be provided as hidden friends together with their argument type in a namespace that is not the global namespace, so that they are picked up by ADL.
Consider using fully qualified names for calls that rely on an implicit conversions and in generic code.
Consider using named function objects instead of function names for calling a function to suppress overload resolution including functions found by argument dependent lookup (ADL).
Prefer using declarations to using directives.
For a function overload set, do not use default arguments in function declarations.
When adding a template specialization to existing code, verify that no template instantiation is silently changed in an unsafe manner.
Define an entity in only one file to prevent ODR-violations and ensure that inclusions of that file in different translation units don’t result in definition differences.
Ensure that no ODR-violations occur, i.e., through a static analysis tool.
Document argument-dependent lookup usage where name qualification is not desirable.
The vulnerability as described in ISO/IEC 24772-1:2024, 6.22 exists in C++, however, defining variables only when they can be initialized properly with an initializer in their definition avoids reading uninitialized memory.
Defining/allocating objects of trivial type with automatic/dynamic storage duration without initialization leaves the object with an indeterminate value. A subsequent read of such a variable before it has been written is undefined behavior [EWF]. In addition, sub-objects of trivial type that are omitted in a constructor’s member initializer list and not initialized by the constructor’s body or by a default member initializer will not be initialized by that constructor. For example, the following class definitions suffer from incomplete initialization of subobjects, even though class test defines a default constructor:
struct base { short num; };
struct test : base
{
enum E1 { e1a=100, e1b, e1c };
int one;
int two;
double ar1[2]{ 1.1, 2.2 };
double ar2[2];
E1 e1;
E1 e2;
test()
: // base unintialized
one{ 1 }
// two uninitialized
// ar1 initialized through default member initializer
// ar2 uninitialized
, e1{} // initializes to zero, not a named enumerator value
// e2 uninitialized
{ }
}; Dynamically allocating memory for an object using malloc, or some other C-style equivalent, does not initialize the object. Interpreting such memory as an object with trivial type will result in it having an indeterminate value.
An object with non-trivial type requires running a constructor for its lifetime to start correctly (see 4.4). Attempting to cast a pointer to allocated memory and using it as an object that is not implicitly defined is undefined behavior except for special sanctioned cases, see Conversion Errors [FLC] and Pointer Type Conversion [HFC].
Non-local variables with static storage duration that are dynamically initialized can cause undefined behavior if the initialization depends on other such variables. If the dependency is in the same translation unit the sequencing is defined in definition order, however, the order of initialization across translation unit boundaries is unspecified, and thus undefined behaviour can occur by accessing an uninitialized variable. For example:
struct A {
A (int i ) : i_ { i } { }
int i_;
};
struct B {
B (A const & a) : j_{a.i_} { }
int j_;
};
extern A a; // declare existance of variable 'a'
// defining variables with dynamic intialization:
B b { a }; // #1
A a { 42 }; // #2If #1 and #2 are in the same translation unit, then a in #1 is incompletely initialized (zero initialized). If #1 and #2 are in different translation units, then the order of initialization of a(#2) relative to b(#1) is indeterminate.
The constexpr-specifier for a variable ensures initialization at compile time. The constinit specifier ensures a variable is initialized at compile time, even if it is non-const.
Defining non-member variables as const or as constexpr, enforces initialization by the compiler and makes reasoning about code easier.
If determining the initial value of a variable requires complex logic, putting that logic into an immediately-invoked lambda expression that computes the initial value, permits the variable to be initialized when defined.
See C++ Core Guidelines ES.20 and CERT C++ Coding Guidelines EXP53-CPP. Note that ES.20 and EXP53 are complementary. Both point out that you should always initialize before reading, but ES.20 uses the narrow sense of initialize while EXP53 includes assignment.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
const to enforce initialization, otherwise defer the definition of the variable until a sensible initial value can be provided.constexpr or constinit whenever possible to avoid problems with dynamic initialization order.The vulnerability as described in ISO/IEC 24772-1:2024, 6.23 applies to C++.
Operator precedence and associativity in C++ are determined by the C++ grammar. There are four operators that cannot be overloaded (user-defined):
:: ;
. ;
.* ; and
?: .
Due to the large number of operators, one is recommended to consult an operator precedence table when needed, e.g., [https://en.cppreference.com/w/cpp/language/operator_precedence]. For example, in C++, the bitwise logical and shift operators are sometimes incorrectly treated as having the same precedence as arithmetic operations even though the bitwise operators have lower precedence. For example, the following (correct) expression subtracts one from x and then checks if the result is zero:
x - 1 == 0
which is equivalent to (x - 1) == 0,i.e., x - 1 is done first, then that result is compared to 0. Programmers mistakenly thinking the bitwise operations have the same precedence as arithmetic ones might write:
x & 1 == 0
intending to perform (x & 1) == 0, but precedence rules result in this evaluating x & (1 == 0) instead. This would have been easily fixed by using parenthesis to ensure the proper evaluation of an expression.
In addition to the aforementioned, C++ also permits operators to be overloaded when used with user-defined types. While it is not possible to change the precedence, associativity, and number of operands of overloaded operators [C++17, Clause 16.5 [over.oper], para. 6], overloaded operators can be executed differently than built-in operators. For example, overloaded operators lose any built-in operator short-circuiting properties and sequence order guarantees. Similarly overloaded operators and their arguments' evaluations behave as normal function calls, differing from built-in operator evaluation.
struct A { };
bool operator&&(A const &, int);
int foo ();
void bar (A const & a)
{
if (a && foo()); // 'foo()' always evaluated
if (false && foo()); // 'foo()' never evaluated
if (a.operator&& (false,foo())); // 'foo()' always evaluated
}Note that overloaded assignment falls into this category.
For issues related to the declaration of equality and relational operators see Clause 6.25 [KOA].
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.23.5 [JCW].
enable all C++ compiler/tool warnings and static analysis tool diagnostics concerning possible issues with precedence and associativity to help detect and avoid mistakes.
even if technically unnecessary, use parentheses around operator (sub)expressions that are known to be or felt likely to be sources of error.
avoid overloading the following operators &&, ||, , and their alternative spellings.
Break up complex expressions and use temporary variables to make complex expressions easier to understand and maintain.
The vulnerability as described in ISO/IEC 24772-1:2024, 6.24 applies to C++.
The evaluation of an expression includes: (i) its value computation; and (ii) its side-effects. The value computation is the value returned by the expression, e.g., the valuation of 3 * 2 + 1 is 7. The side-effect of an expression are
For example consider:
int i = 2;
int j = i++;the evaluation of i++ is 2 and the side-effects are the writing of 3 to i and the initialization of j.
Within an expression, one must ensure an object is stored only once to avoid undefined behaviour [EWF], e.g.,
i = i++ + 5; // undefined behaviour (before C++17)or
k = i++ + i\--; // undefined behaviour in all versions of C++and expressions modifying objects can only read the object to determine the value to be stored (e.g., ++i requires reading the value), i.e., other accesses are undefined behaviour, e.g.,
my_array\[i\] = i++; // undefined behaviour (before C++17)
Starting with C++17, the evaluation order of an expression involving
overloaded operators preserves the sequenced before behaviour of the
built-in operator:
```{.cpp}
my_array[i] = i++;
my_array[i++] = i++;say i = 10 before the expression
evaluate RHS i++ – i is 11
evaluate my_array[i++] //evaluates my_array\[11\], then assigns i to 12
my_array[11] is assigned 10
This occurs because assignment is sequenced after the value computation of the right and left operands and before the value computation of the assignment expression and, the right operand is sequenced before the left operand. [C++17, Clause 8.18 [expr.ass], para. 1] Since this is the built-in operator, this statement can be thought of as:
Compute value of right-hand-side:
i++(e.g., integer value).Compute value of left-hand-side:
my_array[i](e.g., memory address).Apply side-effects of
i++.Apply side-effects of the assignment.
In general, one should follow commonly-stated C/C++ advice of never reading from and writing to the same object within an expression to avoid potential vulnerabilities. Often breaking the expression into separate statements achieves clear and clean semantics, e.g.,
++i;
my_array[i] = i;or
my_array[i] = i;
++i;makes it unambiguous what the value of i is during the array assignment and eliminates the possibility of vulnerabilities.
In addition, it is important to note that overloading an operator disables short-circuiting behaviours (e.g., built-in boolean operators): those operators' operands are all evaluated before the operator itself. Similarly, overloading the comma operator disable the guaranteed order of evaluation.
The C++ built-in (two-argument) Boolean operators (e.g., && and \|\|) are short-circuiting, i.e., if the value of an earlier (from left-to-right) operand of an operation determines the result of the operation, then all remaining arguments are not evaluated.
Typically this allows one to write code like this, e.g.,
int *p;
// ...
if (p != nullptr && *p != 0) {
/* do something */
}i.e., if p is nullptr, then *p != 0 is never executed, thus, avoiding undefined behaviour. Only when p is not nullptr is *p != 0 is evaluated. It must be stressed that this only applies to the built-in && and || operators: user-defined operator overloads as functions always evaluate all operands first.
Consequently should one want to always evaluate all operands of a boolean expression, one should not write code like this:
bool x = foo() && bar();
where foo() and bar() are functions that return something convertible to bool. In this expression, if foo() returns false, then bar() will never be executed; –only when foo() returns true will bar() be executed. Similarly for ||:
bool y = foo() || bar();
i.e., only when foo() returns false will bar() be executed if foo() returns true then bar() will never be executed. Thus, if both foo() and bar() are both required to be executed, then execute them in separate statements first, e.g.,
bool foo_result = foo();
bool bar_result = bar();
bool x = foo_result && bar_result;
bool y = foo_result \|\| bar_result;The built-in comma operator evaluates its expressions left to right and sequences the left expression before the right. This is not the case for an overloaded comma operator, which follows the rule for a function call.
The order of evaluation of function arguments in C++ is unspecified. Therefore, a side-effect in one argument position can change the result of a different argument, for example:
int i = 0;
int get() {return i;}
void foo(int A, int B);
void bar () {
foo (get(), ++i);
}In the above example, inside foo, the value of the A can be 0 or 1 depending upon which argument is evaluated first. This can be avoided if the call to get or the increment of i is made before the call to foo, i.e. forcing the order of evaluation.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.24.5.
write expressions so that the same effects will occur under any order of evaluation that C++ permits since side effects can be dependent on an implementation specific order of evaluation.
avoid overloading logical operators (&& and ||), as these overloaded versions will not short-circuit.
avoid overloading the comma operator, as the overloaded version has unspecified order of evaluation of its arguments.
ensure that the ++, \--, @= operators are used as solitary items in a statement.
be aware to which C++ standard a compiler is compiling code against. This determines the semantics of all operator (overloaded and built-in) expressions.
avoid placing multiple operations with side effects in a single expression.
write simple code expressions and statements so that within any expression or statement an object is one of:
read from;
only modified once; or
only read and modified by: a single increment; a single decrement; a single simple assignment; or a single compound assignment.
be aware that different versions of C++ have different evaluation order specifications and program accordingly.
The vulnerability as described in ISO/IEC 24772-1:2024, 6.25 applies to C++.
C++ has several instances of operators which are similar in structure, but different in meaning. Examples of operators in C-based languages that can cause confusion are:
== and =;&& and &;|| and |;<, <<, and <<=; and>, >>, and >>=.The typographical similarity can lead to code like the following, where it is unclear if the expression as spelled is actually intended, or if the author has typos in it, meaning a different operator instead:
auto f(unsigned i, unsigned j)
{
return (i > 1) & (j = 1); // (>>, &&, ==)?
}The following code in a production phone OS caused the “bricking” of many users phones:
if (key_data_.has_value() & !key_data_->label().empty())instead of
if (key_data_.has_value() && !key_data_->label().empty())or the even clearer using the alternative operator representation and for &&
if (key_data_.has_value() and !key_data_->label().empty())As a general rule, the use of =, +=, -= in an expression when the operator is not the final assignment to a variable is unsafe since the assignment operator creates side-effects within the expression which are difficult to analyze by a human reader and can be have different results depending upon the order of evaluation of terms within the expression.
But even in assignment expression flipping the assignment symbol with the operator can itself lead to valid code that was not intended:
int i{42};
i += 22; // i becomes 64
i =+ 22; // i becomes 22
i =- 22; // i becomes -22C++ provides significant freedom in constructing statements. This freedom, if misused, can result in unexpected results and potential vulnerabilities.
Since the order of evaluation within expressions is only partially defined, sub-expressions with side effects on variables used within the overall expression can result in undefined behaviour [EWF].
The flexibility of C++ can obscure the intent of a programmer. Consider:
int x,y;
/* ... */
if (x = y){
/* ... */
}In the above example, significant analysis can need to determine whether the programmer intended to do an assignment as part of the if statement (valid in C++) or whether the programmer made the common mistake of using = (assignment) instead of == (equality).
This confusion can be corrected by moving assignments outside of Boolean contexts. This would change the example code to:
int x,y;
/* … */
x = y;
if (x == 0) {
/* ... */
}This would clearly state what the programmer meant and that the assignment of y to x was intended.
Additional confusion occurs in the use of the logical && or || operators and the bitwise & or | operators. The compiler will implicitly convert arithmetic expressions to bool for operands of the logical operators. Similarly, operands of bool type will be promoted to int for operands of the bitwise operators (see Conversion Errors [FLC]).
It is not be clear whether the programmer intended to use the logical operator && or bitwise operator & instead:
unsigned f(unsigned i, unsigned j)
{
return (i > 0) & j;
}Using the alternative tokens and / or in lieu of && and || reduces the possibility of confusion. Similarly, a not_eq b is preferable to a != b since the latter is easily confused with the equally valid expression a |= b.
Programmers can easily get in the habit of inserting the ; statement terminator at the end of statements. However, inadvertently doing this can drastically alter the meaning of code, even though the code is valid as in the following example:
int a,b;
/* … */
if (a == b); // the semi-colon terminates the `if`{.cpp} statement
// and make the following code always execute
{
/* ... */
}Because of the misplaced semi-colon, the code block following the if will always be executed. In this case, it is extremely likely that the programmer did not intend to put the semi-colon there.
Unary ‘+’{.cpp} on a variable is (almost) a no-op, and is possibly a mistype of ‘++’{.cpp}. A unary ‘-’{.cpp} on a variable will switch its sign, unless applied to a variable of an unsigned type, in which case the result is the value subtracted from 2^n where n is the number of bits in the unsigned type.
C++ overloading of operators can also cause confusion.
The language does not impose any restrictions on semantics of overloaded operators. This can cause (potentially generic) code to behave in completely unobvious ways, when such types with “unusual” operator semantics are used.
For example, the boost.spirit library allows code like the following to create parser rules:
r = real_p >> *(ch_p(',') >> real_p); // rule that accepts a comma-separated list of real numbersThis library uses C++ operator overloads to create an embedded domain-specific language for grammar rules, allowing the specification of parser rules as C++ expressions.
When related operators like the compound assignment with their base operator are overloaded, they are not longer guaranteed to keep their behavioral relationship that they have for built-in types. For example, a += b is not guaranteed to behave like a = a + b, or being defined at all.
Similarly for overloaded relational operators, the statement
a == bdoes not guarantee that
a != b
``` and
```{.cpp}
!(a == b)are equivalent if both are overloaded by the user.
Unless all relational operators for a type are defined either explicitly in a consistent way or implicitly, unexpected results can occur. A user-declared three-way comparison operator (<=>) is used by the compiler to synthesize the relational operators consistently. If operator<=> is defined as =default, the equality comparison operators will also be defined; and if operator== with return type bool is defined, a corresponding inequality operator!= is also defined implicitly.
In C++ if the syntax of a declaration can be interpreted as a function declaration in preference to an object declaration. In the following example, the intention is to create a scoped lock object, but the compiler interprets it as a function declaration lock().
std::scoped_lock<std::mutex> lock();std::vector
std::vector
!!! more explanation needed
The subscript operator[] for a non-const object of types such as std::map guarantees that the given key will exist when the method returns, known as add-or-update behaviour.
bool find_elem (std::map<std::string, std::string> & m, std::string key)
{
if ( m[key] != std::string { } )
{
return true; // key found in map
}
// not found
return false;
}In the above function, when a key is not already present in the map, it will be added. This can result in resource exhaustion.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
— apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.25.5;
simplify expressions to aid in code readability and help future maintainers understand the intent and nuances of the code. For example by:
prohibit assignments embedded within other statements and expressions;
spell unary operators (e.g., -) with a leading blank in expressions to avoid them being misread as combined operators;
avoid the use of unary plus, since it is almost always a no-op for built-in types;
prohibit mixing Boolean operators (&&, ||, !) with non-bool operands, e.g., operands of numeric types;
avoid bit operators (&, |, ~, <<, >> ) with anything except operands of non-bool unsigned types;
prefer using alternative tokens for the logical operators, such as and, or, and not, and comparison operators such as equal and not_eq;
for code structure that require an empty statement, use an empty code block {} instead of ;.
prohibit conflicting side-effects in sub-expressions;
avoid defining semantics of overloaded operators to deviate from the semantics of these operators for the built-in types;
prefer defaulted and synthesized comparison operators over individual overloads to ensure that all of the related comparison operators behave consistently.
Use find() or contains() to check if a key exists in associative containers.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.26 applies to C++ in function bodies compiled into the target program.
This clause does not consider other parts of the C++ source that do not result in the generation of code, such as: - discarded statements that are intentionally unreachable, e.g., through branches of the if constexpr'{.cpp} statement. - commented out code; - a function deactivated with=delete`{.cpp}; - an uninstantiated template; - code excluded using the C++ preprocessor.
An instantiated template follows the same reachability rules as would be followed for the non-template case.
C++ overload resolution rules can cause confusion (see 6.21 Namespace issues[BJL]). To prevent the selection of an unintented overload, a function can be defined with =delete. C++ provides std::unreachable() to mark branches that are intentionally dead, but care is necessary since calling std::unreachable() at runtime is undefined behaviour.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
= delete;if constexpr or static_assert to force compile-time processing, and;std::unreachable() to document code as intentionally deactivated.The vulnerability as described in ISO/IEC 24772-1:2024, 6.27 exists in C++.
Because of the way in which the switch-case statement in C++ is structured, it can be relatively easy to unintentionally omit the break statement between cases causing potential unintended execution of statements.
The switch statement has the form:
int abc = someExpression();
/* … */
switch (abc) {
case 1:
sval = “a”;
break;
case 2:
sval = “b”;
break;
case 3:
sval = “c”;
break;
default:
throw SomeException();
}If there isn’t a default case and the switched expression doesn’t match any of the cases, then control shifts to the next statement after the switch statement block. Unintentionally omitting a break statement between two cases will cause subsequent cases to be executed until a break or the end of the switch block is reached. This can cause unexpected results.
The attribute [[fallthrough]] expresses the programmer’s intent that the code where it is placed is intended to fall through. If this attribute is not used, compilers typically diagnose the absence of a break statement.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.27.5;
use [[fallthrough]] wherever fall-through is intended;
terminate every case with either a flow control transfer or [[fallthrough]] as illustrated in the following example:
```{.cpp}
int i;
. . .
switch (i) {
case 1:
[fallthrough]]; // documents the intended fallthrough.
case 2:
i++;
break;
case 3:
j++;
[[fallthrough]]; // documents the intended fallthrough.
case 4: //other code
return 42;
default: throw CaseNotFound();
}
```;adopt a style that permits your language processor and analysis tools to verify that all cases are covered, and where this is not possible, use a default clause that diagnoses the error.
See also the C++ Core Guidelines ES.78
The vulnerability as described in ISO/IEC 4772-1:2024, 6.28 exists in C++.
C++ lacks a keyword to be used as an explicit terminator. Therefore, it may not be readily apparent which statements are part of a loop construct or an if statement.
Consider the following sections of code:
int foo(int a, const int *b) {
int i=0;
// . . .
a = 0;
for (i=0; i<10; i++); // notice the ';' !!
{
a = a + b[i];
}
int c = 0;
int x = 0;
for (int j=0; j<10; j++)
c = c + b[j];
x += c;
}At first it may appear that, after the first loop, a will be a sum of the numbers b[0] to b[9]. However, even though the code is laid out so that the a = a + b[i] code appears to be within the for loop, the “;” at the end of the for statement causes the loop to be on a null statement (the “;”) and the
a = a + b[i];statement to only be executed once. Similarly, the indentation leads us to believe that that assignment to x is part of the second loop, but it is not. These mistakes may be readily apparent during development or testing. More subtle cases may not be as readily apparent leading to unexpected results.
if statements in C++ are also susceptible to control flow problems since there isn’t a requirement in C++ for there to be an else statement for every if statement. An else statement in C++ always belong to the most recent if statement without an else. However, the situation could occur where it is not readily apparent to which if statement an else belongs due to the way the code is indented or aligned.
Similar issues arise for if-statements, particularly during maintenance, for example:
```{.cpp}
int a,b,i;
// . . .
if (i == 10){
a = 5;
b = 10; // added later, but correct since within the {…}
}
else
a = 10;
b = 5; // added later, intended to be part
// of the else clause
```
If the assignments to b were added later and were expected to be part of each if and else clause (they are indented as such), the above code is incorrect: the assignment to b that was intended to be in the else clause is unconditionally executed.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.28.5;
enclose the bodies of if, else, while, for, and similar in braces. This will reduce confusion and potential problems when modifying the software;
declare loop variables in the initializer of the loop statement;
prefer the standard library algorithms over hand-crafted loops; and
Consider the use of source code auto-formatters before code reviews.
See also the C++ Core Guidelines ES.85, ES.71, ES.74, ES.1 and ES.2
The vulnerability as described in ISO/IEC 24772-1:2024, 6.29 exists in C++.
C++ allows the modification of loop control variables within non range-based loops. This is usually not considered good programming practice as it can cause unexpected problems. The flexibility of C++ expects the programmer to use this capability responsibly.
Since the modification of a loop control variable within a loop is infrequently encountered, reviewers of C++ code do expect it and hence miss noticing the modification. Modifying the loop control variable can cause unexpected results if not carefully done. In C++, the following is semantically correct, but is error-prone:
int a;
for (int i = 1; i < 10; i++){
...
if (a > 7)
i = 10;
...
}which will cause the for loop to exit once a is greater than 7 regardless of the number of iterations that have occurred.
for (int i : std::ranges::iota_view{1,10})
{
if (a > 7) {
i = 10; // This changes the local variable for this loop iteration's execution
// but subsequent iterations are not affected
}
}
for (int const i : std::ranges::iota_view{1,10})
{
if (a > 7) {
i = 10; // This is now illegal since the 'const int' prevents assign
}
}The range for example immediately above does not have the vulnerability of the C-like for loop above.
for (int i=1; i < 10; ++i) ...In a range-based for loop, the control variable is not available.
std::array a {3, 1, 4, 1, 5};
for (auto const x : a) {
std::cout << x << '\n';
}To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.29.5;
do not modify a loop control variable within a loop;
use a range-for loop in preference to general loops; and
prefer standard library generic algorithm functions like copy, reduce, transform, inner_product, etc. instead of general loops.
Note: See also the C++ Core Guidelines ES.71, ES.86.
The vulnerability as described in ISO/IEC 24772-1:2024, 6.30 exists in C++.
Arrays are a common place for off by one errors to manifest. In C++, arrays are indexed starting at 0, causing the common mistake of looping from 0 to the size of the array as in:
int foo() {
int a[10];
int i;
for (i=0, i<=10, i++)
...
return (0);
}C++ mitigates the issue of sentinel values in strings document in ISO/IEC 24772-1 clause 6.30 by providing the string class and the string_view class.
C++ does not flag accesses outside of array bounds, so an off by one error may not be as detectable in C++ as in some other languages. Several good and freely available tools can be used to help detect accesses beyond the bounds of arrays that are caused by an off by one error. However, such tools will not help in the case where only a portion of the array is used, and the access is still within the bounds of the array.
C++ mitigates these issues by providing
Range-based for loops
std algorithms
Iterator style loops terminated by !=
Container classes
gsl::span (soon to be std::span)
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.30.5;
Use careful programming, testing of border conditions, and static analysis tools to detect off-by-one errors in C++; and
use range-based for loops, “std” algorithms, iterator style loops terminated by !=, or container classes in preference to C-style arrays and structures.
See also the C++ Core guidelines ES.1, ES.42, ES.71, SL.con.3 (more to come)
The vulnerability as described in ISO/IEC 24772-1:2024, 6.31 exists in C++.
It is as easy to write structured programs in C++ as it is not to. C++ contains the goto statement, which can create unstructured code. It also has continue, break, and return that can create a complicated control flow, when used in an undisciplined manner. Spaghetti code can be more difficult for static analyzers to analyze and is sometimes used on purpose to intentionally obfuscate the functionality of software. Code that has been modified multiple times by an assortment of programmers to add or remove functionality or to fix problems can be prone to become unstructured.
Because unstructured code in can cause problems for analyzers, both automated and human, of code, problems with the code may not be detected as readily or at all as would be the case if the software was written in a structured manner.
In C++, the break and continue operations only act on the innermost loop. At times, escape from nested loops is required. In such cases, the use of goto may be simpler and easier to verify than a series of tests with break and/or continue operations.
The setjmp macro sets the current execution context into a variable, which can be use later to return to that current context using longjmp call. These calls originated from the C standard library to mimic goto across the call stack. They do not support the relevant additions to C++ such as destructors for automatic objects, exceptions, and concurrency, and hence are incompatible with modern C++ programming.
A coroutine is a function that can suspend execution for later resumption (optional).
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.31.5;
write clear and concise structured code to make code as understandable as possible;
avoid the use of setjmp and longjmp; and
consider the use of coroutines as an alternative to goto and setjmp/longjmp.
See also the C++ Core guidelines ES.76, ES.77, SL.C.1
The vulnerability as described in ISO/IEC 24772-1:2024, 6.32 exists in C++. However, the language also provides appropriate mitigation.
C++ provides both call by copy (aka call by value) and call by reference parameter passing. The argument is evaluated to initialize the formal parameter (in the first case) or bound to the formal parameter (in the second case) of the function that is being called. A formal parameter behaves like a local variable, however, changes to a non-const reference parameter affect the bound object.
The rich type system of C++ allows types that when passed by value still have call by reference semantics, for example, pointer types, std::reference_wrapper, or class types with pointer or reference member variables.
C++ assumes that pointer or reference parameters of different types never alias, even if the underlying object representations are identical, i.e., for a function declared as void f(int *pi, long *pl) the compiler will assume that pi and pl always refer to different objects, even if sizeof(int) == sizeof(long). Two parameters may refer to the same object if they have pointer or reference type, and the target types are the same or related. This means, aliasing between reference parameters or with a reference result needs to be taken into account in user code. For example, in an assignment expression the left and right hand side can refer to the same object. This implies that user-defined assignment operators must take precautions against self-assignment or document that it is forbidden.
Modern C++ ensures that in many cases the need for, and the overhead of copying value arguments or results is elided by the compiler, especially from temporary objects.
The use of const lvalue-reference parameters combines the efficiency of call by reference with the guarantee that the underlying input parameter is not changed (marking it as an in parameter). A non-const reference parameter must be considered an inout parameter. Rvalue-reference parameters are inout parameters that allow transfer-of-ownership semantics. At their call site it is best to assume that the argument object is in an indeterminate state and has to be reassigned before subsequent use. There is no language mechanism for marking out parameters, one would use the return mechanism. Instead of multiple out parameters a struct, std::pair, or std::tuple can be used as a return type and eventually decomposed at the call site to its constituents via a structured binding.
Member functions take the *this object as an implicit reference parameter. The kind of reference can be specified through qualification of the member function. However, in addition to lvalue-reference, const-lvalue-reference, and rvalue-reference qualification, there exists an oddity with respect to normal reference parameters:
This means, unqualified member functions are callable on temporaries (rvalues) and thus can have side effects, but also can return an lvalue-reference to said temporary by returning *this (or members of *this), which can lead to dangling if such a reference is used beyond the expression of the function call returning it. For example, the compiler-provided assignment operators of a class are unqualified member functions that return an lvalue-reference to *this.
Rvalue-reference parameters in a context where their actual type is deduced from the call site, are called forwarding references. A forwarding references will either be deduced to an lvalue-reference or an rvalue-reference depending on the argument at the call site.
Aliasing is expected and allowed in some cases, such as:
- Assignment and compound assignment operators: the right parameter may alias the left parameter. The function result always refers to the left parameter unless overloaded differently. In the case of self-assignment the stored value should not change.
- Functions that `swap`{.cpp} their parameters: The two parameters to be swapped may refer to the same object.
- Shift operators used for input and output: the result always refers to the left parameter.
- Prefix increment and decrement operators: the result always refers to the parameter.
The C++ preprocessor macros use a call by name parameter passing; a call to the macro replaces the macro by the body of the macro. This is called macro expansion. Macro expansion is applied to the program source text and amounts to the substitution of the formal parameters with the actual parameter expressions. Formal parameters are often parenthesized to avoid syntax issues after the expansion. Call by name parameter passing reevaluates the actual parameter expression each time the formal parameter is read.
The order of evaluation of function arguments is unspecied, see [SAM]
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1, 6.32.5.
Prefer pass-by-value and pass-by-const-reference over other means of parameter declaration.
Prefer reference parameters over corresponding pointer parameters.
Use pass by non-const reference only when side-effects on the argument are intended (inout-parameter).
Unless the moved-from state is well-defined, always reassign a variable passed by rvalue-reference before subsequent use after the call.
Use forwarding reference parameters only, if perfect forwarding is needed in the body of a function template or generic lambda.
Define member functions with the appropriate lvalue-ref-qualification when they have side effects on *this or return a pointer-like object referring to *this or its members.
Either delete the rvalue-ref-qualified overload or define it as returning by value, when a const-ref-qualified overload of a member function exists that returns a pointer-like object referring to *this or its members.
Document the referents of pointer-like objects created by or modified by a function if any pointer-like object outlives the invocation of that function.
Document any allowable aliasing between the referents of function parameters of pointer-like type. Absent such documentation, avoid passing aliased parameters.
Do not use function-like macros. If used, surround all macro parameters in the replacement text with parentheses.
The vulnerability documented in ISO/IEC 24772-1:2024, 6.33 and ISO/IEC TR 24772-3:2020, 6.33 exists in C++ by indirect access to variables with automatic storage duration or to temporary objects.
The lifetime model of C++ (4.4) makes it undefined behaviour [EWF] to access an object outside of its lifetime. This results in undefined behavior, when an object access is attempted after its destruction. C++ provides a rich set of pointer-like types whose values may refer to temporaries or variables with automatic storage duration and can dangle (see Subclause [XYK]).
A C++ class type with a pointer-like member will behave as a pointer-like type, unless the class itself manages the lifetime of the object referred to by its member.
In general, any caller storing the pointer-like object returned from a function call risks dangling; such situations require thorough lifetime analysis to ensure that access via the pointer-like object doesn’t dangle.
The efficiency of return-by-value, copy-elision, and move-semantics as specified by C++ reduces the incentive to return a pointer-like type from a function or bind a temporary to a local reference.
The lifetime of a temporary object usually ends at the end of a full expression where it was created. Dangling can occur, when an expression including the creation of a temporary object results in a pointer-like value referring to the temporary object. For example, std::max returns the const-reference given as parameter, which might be bound to a temporary argument:
int g(int i){
int const &m = std::max(i,20);
return m; // access dangling reference to temporary if i < 20
}In some situations binding a reference to a temporary will extend the lifetime of the temporary.
This lifetime extension is not transitive across function calls, therefore, changes in the code, such as replacing a data member access with an accessor member function, can silently lead to dangling in such lifetime-extension situations.
struct A{
int a;
int const &getA(){return a;}
};
void h(){
int && ra = A{42}.a; // lifetime extended
int const & cra = A{42}.getA(); // dangling
}The range-based for statement contains a subtle situation with lifetime extension.
A temporary in the range expression will have its lifetime extended, unless it is accessed indirectly. As a mitigation C++ permits the creation of a variable for such situations that has the scope of the range-for loop, as shown in the following example:
extern std::vector<std::string> make(); // creates a vector
for(char c : make().front()) { // attempt to iterate over first string in vector
// vector and thus contained string is already destroyed before C++23
}
for(auto range = make().front(); char c : range){ // mitigation, create a variable for the range to be iterated over
// string to be iterated over remaings valid throughout
}
This issue is no longer present from C++23 onwards, as temporaries within the for-range-initializer are lifetime extended until the end of the statement.
Returning a pointer-like object from a function is problematic, if the return value refers to a temporary or an object with automatic storage duration, either directly or indirectly. The following example show different situations with this problem:
int *bad_pointer() {
int a = 0;
return &a; // Returning the address of a local variable "a".
}
int& bad_reference(int b) {
return b; // Returning a reference to a local (parameter) variable "b" .
}
std::array<int,3>::iterator bad_iterator() {
std::array<int,3> c = { 1, 2, 3 };
return c.begin();
// Returning an iterator that refers the first element of the local array "c".
}
auto bad_lambda() {
int d = 0;
return [&] { return d = 1; };
// Returning a lambda that captures local variable "d" by reference
// and thus indirectly returns a reference to the local variable
}
decltype(auto) bad_assign(){ // deduces: std::string &
return std::string{} = "hello\n"s;
// Returns reference to temporary object returned from copy-assignment operator
}
void erroneous_use() {
std::cout << *bad_pointer();
std::cout << bad_reference(42);
std::cout << *bad_iterator();
std::cout << bad_lambda()();
std::cout << bad_assign();
}In the examples above, the function bad_assign returns a std::string & that was itself returned from the copy-assignement operator of std::string. Such an assignement operator (including the compiler-provied ones) can be called with a temporary as its left-hand operand, because it is an unqualified member function (for historical reasons).
Dangling can occur by calling a member function on a temporary that returns a pointer-like object referring to *this, a sub-object of *this, or an object managed by *this. This can be prevented by: - for a non-const member function: adding an lvalue ref-qualification (&), - for a const member function: adding an lvalue ref-qualification (const &) and declaring an rvalue ref-qualified overload (&&) either defined as =delete or declared to return by value.
In the following example, class nta declares its copy assignment with lvalue ref-qualification to avoid the situation created in the example function bad_assign:
struct ta{}; // default allows assignment to temporary
struct nta{
nta & operator=(nta const &) & = default; // lvalue-ref qualified
};
ta & check_ta(){
return ta{} = ta{}; // returns dangling reference to temporary
}
nta & check_nta(){
return nta{} = nta{}; // won't compile
};Referring to a variable with automatic storage duration from a pointer-like variable with static or thead-local storage duration usually means dangling, when the indirect access happens.
int const init{42};
std::reference_wrapper<int const> bad_ref = init; // static storage duration
void bad_global_assign(){
if (bad_ref == 42){ // undefined behavior on 2nd call
int local{44};
bad_ref = local; // Any further access of bad_ref dangles
}
}A class type with pointer-like members can lead to dangling when those members refer to constructor arguments.
struct X{
int const &rci;
X(int i):rci{i}{} // No lifetime extension of parameter object by binding reference to it
};Similarly, in the following example the vulnerability exists in the conversion operator string_view() of std::string, that returns a pointer-like type from a member function callable on a temporary object.
std::string_view bad_var("a string"s); // dangling view on temporary string objectA C++-specific way of causing dangling_references to the stack is by means of a placement new expression (see 7.4 Placement new issues [XXX]).
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
prefer value types, pass-by-value, and return-by-value over pointer-like types and passing or returning pointer-like objects;
prohibit the following uses of a pointer-like value referring to a variable with automatic storage duration or referring to a temporary object:
avoid capturing by reference in a lambda that will be used non-locally, that is
avoid relying on lifetime extension of temporaries by binding them to named references and use (local) variables instead;
show that the range-based for dangling vulnerability does not apply or take steps to avoid it, for example, using a variable representing the range and not an expression that yields a reference to a temporary;
consider making member functions ref-qualified, that return pointer-like types to members or objects managed by the class and if required, provide an rvalue-ref-qualified overload that either returns a copy by value, or is defined as =delete to prevent calling it on a temporary;
Perform lifetime analysis when using a pointer-like object beyond the expression that created it and ensure it is not used in a dangling situation; and
Employ static and dynamic analysis tools to detect dangling pointer-like objects.
The vulnerabilities documented in ISO IEC 24772-1:2024, 6.34 applies to C++ as described below.
In general in C++, there must be a match between the number of parameters in a function call and the number of arguments in the function definition. For issues related to macro signatures, see subclause Pre-processor directives[NMP].
The number of arguments can be different to the number of parameters in a function where: - a function template includes a function parameter pack, or - a function parameter includes a default argument, or - a function parameter-declaration-clause ends with an ellipsis, f(\...).
Calling a function template with a function parameter pack results in a specialization of the function with the parameter types matching the corresponding argument types.
The compiler will ensure for variadic templates that the type and number of arguments is correct.
A call to a function with default arguments can provide fewer arguments than parameters as long as the parameters for which no explicit argument is provided include a default argument.
Where a function parameter-declaration-clause ends with an ellipsis, additional arguments can be accessed through the mechanisms provided by <cstdarg>.
No information about the number or types of the parameters is supplied by the compiler. The use of this feature outside of special situations can be the basis for vulnerabilities. and undefined behavior [EWF] can arise, for example:
int and not short. The use of short with va_arg is therefore invalid. #include <cstdarg>
void f1 (int cnt, ...)
{
va_list ap;
va_start (ap, cnt);
short i = va_arg (ap, short); // Invalid type
va_end(ap);
}f1 assumes that there will be two parameters after the named parameter.#include <cstdarg>
void f1 (int cnt, ...)
{
va_list ap;
va_start (ap, cnt);
int i = va_arg (ap, int);
int j = va_arg (ap, int);
va_end(ap);
}
void f2 ()
{
f1 (1, 2, 3); // OK
f1 (1, 2); // results in undefined behaviour
}These issues cannot occur where default arguments or variadic function templates are used.
The C++ Name mangling ensures that function signatures match accross translation units.
This does not apply to other mangling schemes. For example, parameters do not form part of the mangled name for functions declared with the extern "C" linkage specification. Thus such a function can be invoked with incorrect parameter types due to an incorrect redeclaration of the function:
// library.cc
extern "C" void foo (unsigned, unsigned)
{
// ...
}// main.cc
extern "C" void foo (unsigned);
int main ()
{
foo (0xffffffff); // Calling function that is
// defined to take 2 parameters
}To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.34.5;
avoid using functionality from <cstdarg>; use variadic function templates instead; and
when using functions declared with extern "C" linkage specifications, use static analysis tools to ensure that all such declarations of equally named functions have identical signatures.
Note: See also C++ Core Guidelines F.55.
C++ permits recursion, hence is subject to the vulnerabilities documented in ISO/IEC 24772-1:2024, 6.35.
C++ allows recursive constexpr functions and consteval functions that are evaluated at compile time where such calls don’t contribute to the vulnerability.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.35.5.
The vulnerabilities described in ISO/IEC 24772-1:2024 6.36 apply to C++, however, C++ provides a mitigation.
C++ includes the C library, especially the header <cerrno> and thus shares C’s issues with the global error-reporting variable errno. See ISO IEC TR 24772-3:2019 6.36 for details and guidance.
In addition to errno, some C++ library features expose error conditions indirectly via a side-effect on the object the operation failed with or via side-effect on a reference parameter. For example, input stream objects will go into a fail state, when formatted input cannot be performed. Without resetting that fail-state of a stream, further input will continue to fail, that can cause further failures when a failure is ignored. Streams provide a non-default mode to throw exceptions on failure. Another example of error reporting via a side-effect is the filesystem library that provides overloads that take a non-const reference of std::error_code.
In general, reporting errors as side-effects, in the worst case via a global state, can easily or accidentally ignore by developers, leading to further consistency problems in the continued execution of the program.
By default, C++ has the C weakness of permitting the call to a function that returns an error code without capturing the return value in a variable.
errnum foo( int a, int b);
. . .
foo(x, y); // failure to capture the return error code.C++ offers as a mitigating mechanism the [[nodiscard]] attribute. This attribute indicates that the function result must not be discarded. Ignoring the result of a function marked [[nodiscard]] causes a compiler warning.
[[nodiscard]] errnum foo( int a, int b);
. . .
foo(x, y); // compile error.
if( auto e = foo(a,b); e == 0) { // no compile error
// success
}
else {
// handle errors
}In addition, the C++ library provides mechanism to extend the return type of a function with extra values for denoting an error. The simplest case is std::optional<T> that extends T with an “empty” state. Callers must check the result of functions returning an optional for the empty state, before accessing its value. This increases the chances that a reported error is detected by the caller. If additional information of the error has to be returned to the caller std::expected<T,AnErrorCode> or alternatively std::variant<T,AnErrorCode> can be used.
C++ offers a set of library-defined exceptions for error conditions that can be detected by checks that are performed by the standard library. In addition, the programmer or libraries that are called by the programmer can define exceptions that are appropriate for the application or for libraries that are used. These exceptions are handled using an exception handler in the environment where the exception occurs or can be propagated out to an enclosing scope.
Functions declared noexcept or noexcept(true) are called noexcept functions. In the majority of cases, destructors are noexcept functions (see ISO IEC 14882 for further information). Any attempt to propagate an exception from a noexcept function, main, or a thread entry point causes abnormal program termination. Marking non-throwing functions noexcept, that are not called from destructors or participate in move operations or swap() overloads, should be carefully considered, because the compiler needs to prepare each call site of such a function for potential abnormal program termination. In case of abnormal program termination, it is implementation-defined whether the destructors for local objects are called (stack unwinding).
Stack unwinding is often responsible for cleanup of external resources. Therefore, abnormal program termination is a vulnerability to be avoided.
See 6.62 Concurrency - Premature Termination [CGS] for issues related to thread or process termination.
An exception propagating out of a coroutine causes the coroutine to end in an unresumable state and the exception is not further propagated.
In C++, when dynamic initialization of a namespace-scoped object fails with an exception, the exception cannot be caught and the program is terminated. Function-scope static objects, in contrast, are initialized the first time execution passes through the declaration and therefore there is always an enclosing function that can catch the exception.
To avoid the vulnerability or mitigate its ill effects, C++ developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.36.5;
develop an error management strategy, including error-detection, error-reporting and error-handling, that, based on context, selects the most appropriate mechanism(s) from the different ones provided by C++;
prohibit error handling based on unsynchronized global state;
avoid the use of errno, but if it cannot be avoided, follow the avoidance mechanisms of ISO/IEC TR 24772-3 subclause 6.36.2;
use static analysis tools to detect and report missing or ineffective error detection or handling;
use [[nodiscard]] to prevent callers from ignoring error values and configure the compiler to report violations as errors, such as -Werror;
use a try-catch function body for main or any thread-entry function with a catch(...) handler to guarantee stack unwinding, which guarantees the destruction of all local objects and permits control of the ultimate termination of the thread or program;
use try-catch around code that can throw exceptions in noexcept functions;
use a try-catch body for a coroutine with a catch handler to prevent silent termination of the coroutine in the case of an exception, or alternatively use a coroutine type that properly supports the case where the coroutine ends with an exception;
if static objects with dynamic initialization cannot be avoided, prefer function-scope static objects to namespace-scope objects; and
prohibit the marking of a potentially throwing functions noexcept or noexcept(true).
Avoid declaring non-throwing functions noexcept other than move operations or swap overloads.
The vulnerability as documented in ISO IEC 24772-1:2024, 6.37 applies to C++. The language mechanisms where type-breaking reinterpretation of data can happen involve unions and std::bit_cast. For type-breaking reinterpretation involving pointers or references see 6.11 Pointer Type Conversions [HFC].
In C++, the issue of casting vulnerabilities are mitigated by the fact that the C++ named casts, i.e., reinterpret_cast, const_cast, static_cast, dynamic_cast, and std::bit_cast, are easily identified, e.g., by text search for more careful review. C-style casts do not share this property and therefore should be prohibited. In addition, C++ named casts include some compile-time checks, and in the case of dynamic_cast run-time checks, that help avoid some but not all vulnerabilities.
Of the available "_cast" operations, only std::bit_cast provides reinterpretation of data values, and only reinterpret_cast allow the reintepretation of data as a different type within limits. For uses of reinterpret_cast see 6.11 Pointer Type Conversions [HFC]. The named casts static_cast, dynamic_cast, and const_cast perform type conversions and not reintepretation of bits and thus don’t have this vulnerability but are subject to potential conversion errors (see 6.6 Conversion Errors [FLC]).
Reading a union member that was not previously written is undefined behaviour [EWF] except for a few cases described by ISO/IEC 14882:2020 clause [class.mem.general]. The reinterpretation of data values via different union members that is common practice in C is undefined behaviour in C++. The type std::variant provides a similar mechanism to union but prevents reading an inactive member.
If there is no value of the target type corresponding to the bit representation of the source type’s value, using std::bit_cast is undefined behaviour, such as in the following example.
enum E { one, two, four=4 };
constexpr auto x = std::bit_cast<E>(42); // 42 is not representable by EC++ also provides <type_traits>, such as std::is_layout_compatible, that can be used to ensure the legality of a specific std::bit_cast.
A legacy means of “bit casting” is the use of memcpy to transfer the bytes of an object’s representation to another object of a different type. Except for cases that are well-defined with std::bit_cast, such use of memcpy to reinterpret data values is undefined behaviour.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.37.5, and clauses 6.6 Conversion Errors [FLC] and 6.11 Pointer Type Conversions [HFC] of this document;
prohibit casts that reinterpret data values;
whenever possible use std::variant or a similar safe union type instead of union;
prefer std::bit_cast over memcpy when reinterpreting the bit-representation of an object to a different type is unavoidable;
use std::is_layout_compatible, and std::is_trivially_copyable and other <type_traits> to mitigate incorrect usage of std::bit_cast; and
Ensure through static analysis that no accidental type-breaking reinterpretation of data values occur.
The vulnerability described in ISO/IEC 24772-1:2024, 6.38 applies to C++ and only arises in C++ when there is a mismatch between a type’s copy semantics and the programmer’s intent.
On the language level, reference semantics that can lead to shallow copies, usually requires the use of pointer or reference types, however, an integral type can also have reference semantics, for example, when it is used as an index or as an operating system handle.
Such types with reference semantics are also called pointer-like types and they will suffer from the aliasing problematic of this vulnerability and additionally from potential dangling due to expired lifetime (4.4) of referred objects (see [XYK]).
In general, relationship types with an immutable referent, such as a const-reference, do not suffer the deep vs. shallow copying semantics problem, unless mixed with relationship types with a mutable reference to the same object. However, the lifetime of a const referent is still an issue to manage (see [XYK]).
The standard library type std::shared_ptr<T> has shallow copy semantics when the managed type is non-const, but in contrast to other relationship types guarantees the lifetime of the referent.
Class types that have relationship type members will become relationship types themselves, unless the class provides deep copy semantics or disables it and manages the lifetime of the referred object (manager type). Such relationship types and manager types will refer to their referred/managed resources via a data member with reference semantics.
A manager type defines a non-empty, non-deleted destructor in addition to providing appropriate copy and move operations. Examples of a manager types are the standard library container types such as std::vector that use pointers to the allocated space of their elements and copying a vector will also copy all contained elements not just the pointers. This management is achieved by replacing the compiler-provided copy-constructor and copy-assignment operator with implementations providing value semantics that perform the deep copy (general manager). An alternative to potentially expensive deep copies for manager types is the prevention of copying, either by defining move operations that transfer the ownership of a managed resources, like std::unique_ptr does (unique manager), or by preventing both copy and move operations (scoped manager), for example, by defining the move-assignment operator as deleted.
Without such replacement of copy and move operations a class type with relationship type members suffers from the potential confusion due to shallow copies. For example, the standard library types std::span, std::string_view, iterators, and the views of the standard ranges library are relationship types. Care must be taken to not only understand implications of their shallow copy semantics, but also about their validity depending on the lifetime of the referred ranges.
Using relationship types as function parameter types is usually safe, because language semantics guarantee the lifetime of parameter objects. Exceptions exists for thread functions and coroutines, where the initial calling context is not guaranteed to exist when parameters of relationship type are accessed.
Returning a relationship type from a function can be problematic, unless the lifetime of the referred object is clear. For example, returning a reference to a local variable will return a dangling reference (see [XYK] and [XYH]).
See also Core Guidelines C.20, C.22, C.32, C.67.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
The memory leak vulnerability documented in ISO/IEC 24772-1:2024, 6.39 applies to C++, unless the programmer takes steps to avoid it. A leak occurs when an object whose lifetime (4.4) has not ended but there are no more variables, pointers or references to that object.
See ISO/IEC TR 24772-3:2020 for the vulnerabilities associated with the C functions malloc(), calloc(), realloc() and free().
C++ has an additional vulnerability in that it provides multiple alternatives for allocation and deallocation and failing to match the deallocation to the corresponding allocation results in undefined behaviour [EWF]. For example, if an array new[] expression was used to allocate and create an array then array delete[] must be used for its destruction and release to avoid undefined behaviour.
C++ destructors allow scope-based resource management (RAII) (Note: - put in Terms and definitions) which mitigates memory leaks by automatically calling the destructor when the lifetime of an object ends, thereby providing the opportunity to release their memory. The standard library provides container classes that follow the scope-based resource management idiom for the management of memory resources. Additionally, the standard library provides the class templates std::unique_ptr and std::shared_ptr for managing heap-allocated objects.
Ignoring issues with constructors throwing exceptions during heap allocation with a new expression can cause leaks. Using smart pointers through the factory functions std::make_unique() and std::make_shared() assists in avoiding memory leaks.
If using functions that manage memory using the C library mechanisms is unavoidable, wrapping such a pointer immediately into a specialization of std::unique_ptr<> that uses free() in its deleter object ensures that memory is correctly released when the unique_ptr is destroyed, for example:
struct free_deleter{
template <typename T>
void operator()(T *p) const {
std::free(const_cast<std::remove_const_t<T>*>(p));
}
};
template <typename T>
using unique_C_ptr=std::unique_ptr<T,free_deleter>;
//...
// abi::__cxa_demangle() returns a pointer to be released with free()
inline auto plain_demangle(char const *name){
unique_C_ptr<char const> result {abi::__cxa_demangle(name,0,0,0)};
return result;
}Modern C++ provides a class hierarchy, std::pmr::memory_resource, that contains advanced allocation strategies and removes the need for explicit allocation/deallocation from the programmer, as shown below.
#include <array>
#include <cstdlib>
#include <iostream>
#include <memory_resource>
#include <string>
#include <vector>
int main()
{
using namespace std;
array<std::byte, 200'000> buf;
pmr::monotonic_buffer_resource pool{buf.data(), buf.size()};
pmr::vector<pmr::string> v{&pool};
for (int i=0; i != 1000; ++i)
v.emplace_back("yet another longer string");
}When using C++ allocators that are not derived from std::pmr::memory_resource or are not part of an allocator provided by a library, care is needed to avoid vulnerabilities due to memory leaks due to issues in allocator code.
The library functions std::construct_at() and std::destroy_at() are simpler than and preferrable to placement new and destructor calls.
STOPPED HERE.
ISSUES WITH SHARED_PTR AND weak ptr or std::make_shared().
A std::shared_ptr<T> is a reference-counted pointer to type T. Several shared_ptr objects can own the same object. An owned object is destroyed when the last shared_ptr that owns it is destroyed or otherwise released. A memory-leak vulnerability can occur when using shared pointers for data structures such as a doubly-linked list. In such circumstances the reference count can never drop to zero unless addition measures are taken, such as breaking the cycle using a weak pointer. (EXAMPLE HERE?)
In a concurrent context, the issues associated with access to data structures through std::shared_ptr and std::weak_ptr are complicated by concurrent access to such structures. If shared pointers are being used in a concurrent context, locking is required in order to manage concurrent access to a single object.
In a concurrent context, std::shared_ptr (and std::weak_ptr) store a pointer to the owned object and the associated control block.
std::atomic<std::shared_ptr<T>> and std::atomic<std::weak_ptr<T>> so that exclusive access to the pointer can be guaranteed.
Using std::shared_ptr() can cause memory leaks if it is used to create a cyclic data structure that is not internally connected using weak pointers. Y_shr -> A -> B -> C -> A X_shr -> A shared_ptr differentiates between ownership and reference to A Shared_ptr is a smart pointer that retains shared ownership f an object through a pointer. Shared_ptr can also own zero objects. A weak_ptr is a smart pointer that holds a non-owning reference to an object that is managed by shared_ptr. A weak_ptr must be converted to a shared_ptr in order to access the referenced object.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
malloc(), calloc(), realloc() and free() wherever possible;std::pmr::memory_resource;std::shared_ptr in potentially cyclic data structures, break cycles using std::weak_ptr;new and delete; andThe vulnerability as described in ISO/IEC 24772-1:2024, 6.40 exists in C++ by the language mechanism of templates. While templates are a pure compile-time mechanism, their use can result in programmer confusion because of the potentially deeply-nested nature or template instantiation. Actual errors during instantiation will fail to compile. Confusion can occur when programmer mistakes lead to successful compilation but their intent is not met due to misunderstanding of the language rules.
Similar to 6.21 Namespace Issues [BJL], adding a template specialization later can cause uses of the base template to pick up this new specialization. As long as no ambiguity occurs and the new specialization is a better match it will change the behaviour of existing code that previously instantiated the base template or a different specialization. If different translation units happen to use a template with different set of specialization declarations the mechanism might lead to different instantiations of the same template with the same arguments. This results in an ODR violation and is undefined behaviour (see 6.56 [EWF]). A common mitigation is to put all specializations of a template in the same file or module as the base template declaration, or alternatively a specialization of a template together with the definition of the user-defined entity for which it is specialised.
Due to the two phase compilation model of templates, name lookup can be surprising in class templates with dependent base classes. An unqualified name used in the derived class that is defined in the base will be found in an outer namespace instead.
double foo{0};
template <typename T>
struct base {
int foo;
};
template <typename T>
struct d : base<T>{
auto bar() {
return foo; // matches global foo not base<T>::foo [1]
}
};
In the above example line [1], in place of foo, name qualification either as this->foo or d::foo would refer to the member of the base class and avoid the name lookup confusion.
Function template specialization is syntactically possible, but doesn’t affect overload resolution. Overloading is the mechanism to select special-cases for functions based on their parameter types.
Generic parameters allow a mechanism to prevent accidental implicit type conversions applied by the compiler (see 6.06 [FLC]). See the following example, where the function taking an int could be called with almost any built-in type, because of implicit conversions to int. Adding a generic overload will be a better match instead.
void doit(auto x)=delete; // #1
void doit(int i) { /* only int */ }
doit(42); // compiles
doit(true); // compile error with generic overload #1Alternatively, implicit conversions can be disabled through a generic parameter with the concept std::same_as.
void doit(std::same_as<int> auto i){}Employing static_assert with a condition checking the appropriateness of a template parameter in a template body will cause a compile error if the condition is false. Alternatively, one can use concepts to limit template instantiations so that alternative specializations or overloads can to be considered.
In class templates a generic constructor or assignment operator never matches the compiler-provided definitions of copy or move operations.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
document the implicit concept of each template parameter.
use static_assert to prevent instantiation of a template with inappropriate template arguments;
consider using concepts to constrain template parameters;
prefer concepts for achieving template selection via SFINAE over legacy mechanisms such as std::enable_if_t;
be aware that a constructor template or assignment operator function template will not replace compiler-provided special member functions;
use name qualification for names that may be found in a dependent base class;
for template specialization definitions, ensure that specializations are declared as follows:
in the same file or module as the primary template; or
in the same file or module as the user-defined type for which the specialization is declared.
prohibit specializing function templates.
The vulnerability as described in ISO/IEC 24772-1:2024 6.41 applies to C++.
In C++, inheritance is defined differently than in most other languages that support inheritance, e.g.: - there is no implicit common base class; - defining a member with a name that is defined in a base class causes hiding the base class member in the derived class including all overloads with that name. (Full qualification via the base class name is required to access these hidden members); - dynamic polymorphism requires the use of references or pointers; and - private virtual member functions can be overridden in derived classes.
A vulnerability similar to namespace issues (see [BJL]) can arise if a new final overrider is defined in one of multiple parents, which can result in a silent change to the semantics of an existing program, as shown in the following example.
struct A {
virtual void foo ();
};
struct B : virtual A { };
struct C : virtual A {
void foo () override; // late addition
};
struct D : B, C
{
void bar () {
foo (); // calls the late addition
}
};The implementation of virtual member functions does not take access into account. This can lead to a potentially surprising situation where a virtual function overrides an otherwise inaccessible base class function. This is quite different than the situation for non-virtual private member functions, which cannot be accessed from outside the declaring class or any friends.
Inheriting from a non-polymorphic base class can be used to implement adaptor types, i.e., by limiting or extending the base’s functionality. Using private inheritance can prevent treating the derived type object as a base type object. Multiple inheritance adds an additional dimension since a class can inherit the same base class A indirectly via different direct base classes and can result in multiple base class sub-objects of type A, which can result in ambiguous code.
If all classes in a multiple inheritance hierarchy that directly inherit from A use the keyword virtual when inheriting from A, there will be only one base class sub-object of type A in the most derived object. Inconsistently inheriting from A with and without virtual is confusing, because multiple base objects of type A exist. Even when an ambiguity does not currently exist, refering to a base class member using its qualified name reduces the chance that a future modification silently changes the program’s behaviour through accessing a different member with the same name.
In C++, a name declared in a virtual base and inherited multiple times but hidden on any virtual path is considered to be hidden on all virtual paths. Consequently, a hiding declaration introduced later will silently alter the semantics of code refering to this name. The vulnerability does not arise if a multiply-inherited name is found on at least one non-virtual inheritance path, since the use of the name is diagnosed as ambiguous.
C++ favours value-semantics for copy and move operations as well as destruction that conflicts with object-oriented polymorphic behaviour.
Virtual Destructor: Base classes that define virtual member functions will need to also define a virtual destructor and in addition need to care about the copy and move operations, otherwise deleting a dynamically-allocated derived object via a base class pointer will cause undefined behaviour [EWF].
Slicing: A common failure is to not eliminate implicitly callable copy and move operations in base classes that can lead to accidental copying of a base class sub-object via a base class reference that actually refers to a derived object. Preventing implicit copy and move operations in base classes defining virtual member functions is a common mitigation.
Incomplete Copy: When a derived class defines its own non-defaulted, non-deleted copy or move operations, care must be taken to actually copy and move all base class sub-objects as well. Omitting a base class when defining copy or move constructors means the default construction of a base class object happens. Not invoking a base class assignment in the definition of copy and move assignment operators will cause the base class retaining its previous members and not obtaining the source object’s base members. None of these omissions are a compile error and none are an issue for empty bases.
If a “using-declaration” refers to a constructor of a direct base class, all constructors of that base are candidates for the initialization of the derived class. If a base class constructor is selected to initialize the derived type, any other bases or members of the derived type will be initialized as if by an = default default constructor. This can leave some members uninitialzed, that can result in undefined behaviour.
If a using-declaration refers to a protected member of a base class, its accessibility can be changed, which destroys the encapsulation of the member.
If a base class overloads operator new and operator delete, any derived classes will inherit those operations. If the base class’ operator new and/or operator delete assume the size of the objects being allocated are all the size of the base class and they are not all the same size, then this can result in undefined behaviour.
The mechanisms of failure from ISO/IEC 24772-1:2024 clause 6.41 can be mitigated in C++ as follows:
Execution of malicious redefinitions can be prevented by use of final on each member function to generate compiler diagnostics when overriding is not permitted.
Accidental redefinition can be mitigated by a project mandate to use the override or final special identifiers when overriding a virtual member functions.
Accidental failure of redefinition can be prevented by using override on each member function intended to be redefined to generate compiler diagnostics when overriding does not apply.
Breaking of class invariants can be avoided by proper initialization even with the default constructor and by defining data members private to ensure that the functions with the appropriate functionality are called and thus class invariants are preserved. If copy and move operations are user-defined in a derived class they must ensure to call the corresponding base class operations.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
=delete (see clause 6.38 Deep vs. Shallow Copying [YAN]);override whenever overriding a virtual member function;final to prevent an unexpected overriding of that function deeper in the hierarchy;final or override when overriding a member function;protected or private whenever appropriate.The vulnerability as documented in ISO/IEC 24772-1:2024, 6.42 applies to C++. C++ leaves verification of the correctness of an overridden call to the programmer.
The vulnerability can be mitigated by a style of programming that uses wrapper functions to check preconditions, calls a virtual function to perform the required functionality and subsequently checks the postconditions before returning.
An example is provided below.
class Base {
private:
virtual int function_to_override( int x ) = 0;
// ...
public:
int interface_to_overridden_function( int x ) {
check_preconditions( x );
const auto saved = data_saved_for_postcondition( x );
auto result = function_to_override( x );
check_postconditions( x, saved, result );
return result;
}
// ...
};A future version of the C++ standard is expected to contain “contracts”, which formalize the notion of preconditions, postconditions and contract assertions.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
See also C++ Core Guidelines C.120, C.121, C.122, C.126, C.127, and C.129 through C.133.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.43 applies to C++ for virtual functions, except for constructors and destructors which are not dispatching.
An example of the infinite recursion in C++ is:
#include <iostream>
class A {
public:
virtual void f() { std::cout << "A::f()\n"; }
virtual void g() { std::cout << "A::g()\n"; A::f(); } //call to f() will not dispatch.
virtual void h() { std::cout << "A::h()\n"; }
virtual void i() { std::cout << "A::i()\n"; h(); } //call to h() will dispatch
//showing the vulnerability
};
class B : public A {
public:
void f() override { std::cout << "B::f()\n"; g(); }
void h() override { std::cout << "B::h()\n"; i(); }
};
int main() {
B b;
A * pA = &b;
pA->f(); // no problem
std::cout << "---\n";
pA->h(); // infinite recursion
}In C++, the call to a member function can be qualified, as shown in the above example, and avoids the vulnerability.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
At a call site, consider whether virtual dispatch is desired: if not, construct the call using the qualified name; and
be suspicious of any call from a virtual member function of the derived class to any member function of any of its base classes.
This vulnerability as documented in ISO/IEC 24772-1:2024, 6.44 applies to C++. In addition to the upcast and downcast issues addressed in that document, this clause also addresses cross-casting, which is unique to C++. For further type system related issues see subclause Type System[IHN].
C++ provides language mitigations to help avoid the problems as follows:
Since C++ supports multiple inheritance, up-casting, down-casting, and cross-casting operations can be used to switch to different (pointer/reference) types in the inheritance hierarchy of a specific object, i.e.,
up-casting is casting an object to an ancestor type in the object's type inheritance hierarchy.
down-casting is casting an object to a descendent type in the object's type inheritance hierarchy, and,
cross-casting is casting an object to a sibling/cousin (possibly removed) type in the object's type inheritance hierarchy with multiple inheritance.
Unsafe casts, which include C-style casts and reinterpret_cast, can cast to unrelated arbitrarily structured types. This allows reading and modifying arbitrary memory areas. See subclause [[6.11 Pointer Casting and Pointer Type Changes] [HFC](#HFC) for more details.
Developers should be aware that virtual member functions can be overridden in derived classes, even if they are private.
Given the following:
struct Z { int z; virtual ~Z() { } };
struct Y { int y; virtual ~Y() { } };
struct A : Z { int a; };
struct B : virtual A { int b; };
struct C : virtual A, Y { int c; };
struct D : B, C { int d; };
D d_inst;then these examples demonstrate upcasts, downcasts, and crosscasts:
Upcasts:
B* b_ptr = &d_inst; // implicit
C& c_ref = d_inst; // implicit
Z* z_ptr = static_cast<Z*>(&d_inst);
Y* y_ptr = dynamic_cast<Y*>(&d_inst);Downcasts:
D& d_ref = dynamic_cast<D&>(*y_ptr);
D* d_ptr = static_cast<D*>(b_ptr);Crosscasts:
C* c_ptr = dynamic_cast<C*>(b_ptr);
Y* y_ptr2 = dynamic_cast<Y*>(b_ptr);
C* c_ptr = static_cast<C*> (static_cast<D*>(b_ptr));and notes the following about such:
Upcasts:
are the only ones that can be performed implicitly
can cause object slicing when a copy of a base class object is created from a derived class object.
can also be done with dynamic_cast or static_cast
Downcasts
are explicit;
can be done safely with dynamic_cast
dynamic_cast requires appropriate portions of inheritance to be polymorphic (i.e. has virtual members);
can be done using static_cast which is unchecked and may be unsafe;
Crosscasts:
are explicit
can be done safely with a single call to dynamic_cast which requires appropriate portions of inheritance to be polymorphic (i.e. has virtual members).
can often be done with a chain of static_casts traversing the inheritance hierarchy, which is almost always unsafe.
Deleting derived objects via a base class pointer is undefined behavior [EWF], unless the base class declares a virtual destructor.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.44.5;
in a base class declaring virtual member functions, define a virtual defaulted destructor (Core Guidelines C.35) and make the class non-copyable (Core Guidelines C.130);
avoid designs relying on downcasts or crosscasts and rely on proper virtual member functions in the base classes instead;
avoid explicit upcast and rely on implicit conversion;
if a downcast or a crosscast is needed, prefer using dynamic_cast since it is checked;
ensure that all invariants of a derived class are preserved by all public operations on its public base classes and, if this cannot be ensured, make the base class private, or avoid inheritance;
avoid attempting to navigate class hierarchies using C-style casts or reinterpret_cast; and
prohibit deletion of a polymorphic object without a virtual destructor, including using std::unique_ptr<base> from a unique pointer allocated with std::make_unique<derived>().
See also C++ Core Guidelines ES.48, ES.49, C.146, C.147, C.148 and C.153. source: OOP52-CPP?
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.45 applies to C++.
C++ implementations are allowed to provide built-in functionality but are restricted to a specific naming schema reserved by the standard. For example, names containing a double underscore or that begin with an underscore and a capital letter are reserved for that purpose. See ISO/IEC 14882 clause [Lex.name]. The use of such names by the programmer is forbidden by the language. Language processors are not required to prohibit such usage, hence the vulnerability exists.
The standard restricts definitions in reserved namespaces, such as std (see ISO/IEC 14882 clause [namespace.constraints]). In addition, specializing a template from namespace std is restricted (see ISO/IEC 14882 clause [namespace.std]) unless explicitly allowed, for example, see ISO/IEC 14882 clause [unord.hash].
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
__;_, except when used for a literal suffix;_;The vulnerability as described in ISO/IEC 24772-1:2024, 6.46 applies to C++.
Libraries that supply objects or functions are in most cases not required to check the validity of parameters passed to them. In those cases where parameter validation is required there might not be adequate parameter validation.
When calling a library, either the calling function or the library may make assumptions about parameters. For example, it may be assumed by a library that a parameter is non-zero so division by that parameter is performed without checking the value. Sometimes some validation is performed by the calling function, but the library may use the parameters in ways that were unanticipated by the calling function resulting in a potential vulnerability. Even when libraries do validate parameters, their response to an invalid parameter is usually undefined and can cause unanticipated results.
This vulnerability applies in particular to C++ libraries which are designed for high efficiency; responsibility for satisfying the preconditions for most functions rests with the caller. When these preconditions are not met, the result will be undefined behaviour [EWF]. In addition, error conditions are specified by the language for specific functions, such as raising an exception, returning an error code or a known value, such as NaN.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.47.5;
use translation modes provided by the implementation to perform addition analysis or checking, such as contracts checks, or instrumentation of executing code; and
pay attention to the distinction between precondition violation and error conditions in library documentation: the former results in undefined behaviour; the latter results in defined but possibly unwanted behaviour.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.47 applies to C++.
C++ is a multi-paradigm language with a number of features that do not interface simply with other language systems. It is left to the implementation team the task of converting the results of these paradigms to constructs that can cross an interface for further processing in other languages. Standard layout types assist in this task.
C++, like many languages, requires dynamic initialization of objects of static storage duration prior to execution of the main program. Building a program with inter-language calling requires that corresponding dynamic initialization happens properly. In particular, it is important that all subsystems bound to the main program complete initialization before the main program starts, else a call to such a component can produce unpredictable results, hence in a multilanguage environment, the programmer is required to ensure that the initialization code for an imported libary is invoked.
C++ compilers implement the “C++” language linkage and the “C” language linkage. It is implementation-defined what other language linkages the implementation supports. Typically, other language systems also provide linkages to C systems for interoperability, hence the “C” linkage mechanism is often used as the link between subsystems. The C-language linkage mangling process does not include a function signature, hence it is possible for functions with the same name but different signatures to be mapped to the same mangled name and result in an ill-formed program.
C++ compilers provide an application binary interface (ABI) that delineates areas of interoperability with other languages or other C++ compiler/runtime systems. An ABI includes calling conventions, data layout, error and exception handling and return conventions, name mangling, data model, initialization, and linkage to operating systems and libraries.
If parts of a system written in C++ are accessed through a extern "C" linkage interface, it is implementation defined if a C++ exception from the library part can be caught across the interface. This situation can be avoided by using a different error reporting mechanism for library interfaces across the “C” language linkage. Similar situations apply to C++ systems interfacing to non-C++ language systems with respect to error handling.
If parts of a system written in C++ are accessed through a extern "C" linkage interface from other parts written in C++, then using different compilers or compiler settings can cause ABI incompatibilities potentially resulting in ODR violations (see Name Lookup and Overload Resolution).
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.48 applies to C++. As for the C programming language, the process of linking and loading is outside the scope of the C++ standard.
In C++, the conversion of a function pointer to another type (to misdirect a pointer, for example) requires the use of a C-style cast or reinterpret_cast. Such casts can result in unspecified behaviour, undefined behaviour or implementation-defined behaviour.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
reinterpret_cast or C-style casts.The vulnerability as documented in ISO/IEC 24772-1:2024, 6.49 applies to C++.
As a mitigation, the C++ extern "C" linkage specifier usually provides simple interoperability with libraries using the C application binary interface (ABI).
If signatures are specified in C-compatible header file, conditional compilation based on the predefined preprocessor constant __cplusplus can ensure, that a C++ compiler is also able to process the header file.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.49.5.
Apply the avoidance mechanisms of clause 6.47 Inter-language Calling [DJS] as applicable.
Ensure compatiblity of C header files with the C++ language.
The vulnerability as documented in ISO IEC 24772-1:2024, 6.50 applies to C++. C++ supports both the exception mechanism and the error return mechanism. This clause assumes that any exceptions thrown by an external library are compatible with C++, otherwise the result is either implementation-defined or undefined (see Inter-language calling [DJS]).
When calling a function from a library whose source code is not visible, an unknown exception can be thrown. For such cases, the exception thrown from such a function is guaranteed to be caught by catch(...). See Ignored Error Status and Unhandled Exceptions [OYB].
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
try-catch with an appropriate catch(...) handler.The vulnerability as documented in ISO/IEC 24772-1:2024, 6.51 applies to C++.
The C++ pre-processor allows the use of macros that are text-replaced before compilation.
Function-like macros look similar to functions but have different semantics. Because the arguments are text-replaced, expressions passed to a function-like macro may be evaluated multiple times. This can result in unintended and undefined behaviour [EWF] if the arguments have side effects or are pre-processor directives. Additionally, the arguments and body of function-like macros should be fully parenthesized to avoid unintended and undefined behaviour.
The following code example demonstrates undefined behaviour when a function-like macro is called with arguments that have side-effects (in this case, the increment operator) .
#define CUBE(X) ((X) * (X) * (X))
// ...
int i = 2;
int a = 81 / CUBE(++i);The above example expands to:
int a = 81 / ((++i) * (++i) * (++i));which has undefined behaviour so this macro expansion is difficult to predict.
Another mechanism of failure can occur when the arguments within the body of a function-like macro are not fully parenthesized. The following example shows the CUBE macro without parenthesized arguments.
#define CUBE(X) (X * X * X)
// ...
int a = CUBE(2 + 1);This example expands to:
int a = (2 + 1 * 2 + 1 * 2 + 1)which evaluates to 7 instead of the intended 27.
Both issues shown above are a result of the text replacement mechanisms of preprocessing. Usually such function-like macros can be replaced by type-safe constexpr functions that do not suffer from the vulnerabilities caused by text replacement, as shown below:
constexpr auto CUBE(auto const x) { return x * x * x; }Historically, one use of function-like macros was to provide source-location information via the compiler-provided macros __LINE__, __FILE__, and __func__. Modern C++ provides std::source_location with the std::sources_location::current() default function argument that allows passing that information from a function’s call site without the need to use a macro.
Similar issues can apply to object-like macros:
#define THREE 1+2
auto x = THREE*THREEbecomes
auto x = 1+2*1+2 // 5, not 6 as expectedMost object-like macros can be replaced by constexpr variables that do not suffer from this vulnerability.
A remaining use case for function-like macros is the use of “stringification” (#) of macro arguments or joining macro arguments to form a new token (##). C++ does not specify the order of evaluation, hence care is necessary in macros that contain multiple instances of these operators.
double operator""_ms(const char *, unsigned long );
#define jstringify( x, y ) # x ## y
auto s = jstringify( 0, _ms ); //Expands to "0"_ms or "0_ms"An addition vulnerability can be mis-spelled preprocessor directives in code otherwise ignored by the preprocessor. The following example demonstrates a case, where a variable can remain uninitialized due to an undiagnosed mis-spelled preprocessor directive.
int a;
#if defined(A)
a = A;
#elif defined(B)
a = B;
#elif1 // mis-spelled preprocessor directive
a = 0;
#endif
return a; // potentially uninitializedTo avoid the vulnerability or mitigate its ill effects, C++ software developers can:
constexpr inline functions where possible, but if a function-like macro must be used, ensure that its parameters and body are parenthesized, each argument is evaluated at most once, and the replaced code does not have side effects;constexpr variables where possible;if constexpr where possible, e.g., in function bodies, including cases where compile-time define of a macro (as empty) controls if a macro definition is used to expand to an empty statement or another statement;import where possible;std::source_location mechanisms over macros that use __LINE__, __FILE__, or __func__;##.{.cpp}) or stringification (#) of macro arguments;## and # in the same macro body; andWith the exception of the macro assert, the vulnerability as documented in ISO/IEC 24772-1:2024, 6.52 does not apply to C++, since C++ provides no language-defined runtime checking. Macro assert is defined by the standard but is invoked by the programmer, hence is not a language-defined check by the language.
C++ libraries, however, often provide run-time checks which meet the criteria of this vulnerability. Also, compilers and other tools commonly provide means to perform such runtime checks.
A standard library implementation as well as a compiler can provide additional run-time checks, e.g., with a hardened standard library or in the form of “code sanitizers”. Having such checks in production code requires handling potential failures discovered.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.52.5.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.53 applies to C++. In particular, any construct described by ISO/IEC 14882:2023 as “undefined behaviour” [EWF] is unsafe and should be avoided.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.53.5 and ISO/IEC TR 24772-3:2020, 6.53.2.
Enable checks that warn about unsafe operations.
Apply static analysis tools to detect unsafe constructs.
The vulnerability as documented in ISO IEC 24772-1:2024, 6.54 applies to C++.
C++ is a rich language and provides facilities for a wide range of application areas with a long history of evolution. This evolution includes the language as well as the evolution of best and safe practices. Therefore, code can look obscure because it either uses obsolete or very modern language idioms.
A compiler often provides checks for obscure language use and give corresponding diagnostics, when those checks are enabled. Different compilers tend to have different sets of such checks. The use of more than one compiler can also help enforce standard conformance.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can: - Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.54.5. - Use static analysis tools, including multiple compilers from different sources and with appropriate levels of checking to help identify occurrences of obscure language features.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.55 and ISO/IEC 24772-1 4.7.2.
In the following example, the value of size and the length of data can differ since the extent of data must be calculated at compile time but the calculation of size can be determined either at compile time or during program execution, one source of unspecified behaviour.
char data[1 + int(1 + 0.2 - 0.1 - 0.1)] = { }; // compile-time evaluation
int size = 1 + int(1 + 0.2 - 0.1 - 0.1); // compile-time or run-time evaluation
char f() { return data[size-1];} // Possible buffer overflowSimilarly, the order of evaluation of parameters in functions calls is unspecified, for example:
int i = 0;
f(i, ++i); // can be f(0,1), or f(1,1), depending on evaluation order.To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.55.5.
Use static analysis tools and multiple compilers/tools from different sources to help identify occurrences of unspecified behaviour.
The vulnerability as described in ISO IEC 24772-1:2024, 6.56 applies to C++. See 4.7.1. In ISO/IEC 14882:2023, the terms “undefined behaviour” and “ill-formed, no diagnostic required” expose situations to be avoided.
For the special case of constant expressions, compilers are required to detect and reject undefined behaviour during the evaluation of constant expressions in the core language. It is unspecified whether or not undefined behaviour in the standard library is detected during constant expression evaluation.
C++ compilers and static analysis tools can often detect code that can lead to undefined behaviour and provide diagnostic messages to that effect. Use of multiple compiler tool chains during development increases the likelyhood that such behaviours will be detected. Similarly, runtime tools such as address sanitizers and thread sanitizers can be used during development to detect and report concrete instances of undefined behaviour. In addition, recent C++ language revisions have provided safer alternatives some standard library calls that can lead to undefined behaviours. For example,the calls std::stoi or std::from_chars provide equivalent functionality to std::atoi but without the the risk of undefined behaviour.
Code with undefined behaviour often appears to work correctly but masks situations that can result in serious consequences, for example:
Undefined behaviour often manifests in code distant from where it occured, resulting in bugs that are extremely hard to find. Therefore, retrospective detection of undefined behaviour is exceedingly difficult and resource intensive. Many undefined behaviour situations can be avoided through the use of explicit tests or assertions for undefined behaviour situations, such as null-pointer tests before assessing via a pointer, array-bounds checks before access to array members, and floating-integeral conversions that cannot be represented in the target type. Modern compilers can eliminate such tests when they know that the check will succeed.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.56.5.
Exploit compile-time evaluation whenever possible to detect undefined behaviour.
Use static analysis tools to help identify occurrences and consequences of undefined behaviour.
Use runtime tools such as an address sanitizer and related tools.
Use multiple compilers/tools and different optimization levels to increase the chance of identifying constructs that have undefined behaviours.
Where the C++ language or library provide both defined behaviour mechanisms and undefined behaviour mechanisms, mandate the use mechanisms with defined behaviours.
Explicitly test or use assertions for situations that can cause undefined behaviour.
The vulnerability as described in ISO IEC 24772-1:2024, 6.57 applies to C++. See 4.7.3. In ISO/IEC 14882, the term “implementation-defined” is used to describe implementation-defined behaviour. In addition, the C++ standard provides a dedicated index titled, “Index of implementation-defined behavior”.
In deciding whether or not to use a feature with implementation-defined behaviours, it is important be cognizant that:
A given implementation may change or remove an implementation-defined behaviour; and
Programs that are specifically intended for a particular implementation may in the future be ported to another environment or sections reused for future implementations.
In either situation, the implementation-defined behaviour will become problematic.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.57.5.
Eliminate to the extent possible any reliance on implementation-defined behaviour from programs in order to increase portability.
Use multiple compilers and static analysis tools to increase the likelyhood of identifying constructs that have implementation-defined behaviours.
Refer to “Index of implementation-defined behavior” (reference?) for the definitive list of C++ implementation-defined behaviours and understand the relevant compiler documentation for each of these behaviours.
The vulnerability as documented in ISO IEC 24772-1:2024, 6.58 applies to C++. Appendix D “Compatibility features” of ISO/IEC 14882:2024 enumerates the deprecated features. The C++ attribute [[deprecated]] allows library writers and users to mark deprecated declarations.
Although backward compatibility is sometimes offered as an option for compilers so one can avoid changes to code to be compliant with current language specifications, updating the legacy software to the current standard is a better option.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.58.5.
Enable compiler options or use static analysis tools that identify the use of deprecated features.
Apply the [[deprecated ("*reason*")]] attribute to obsolete declarations that exist only for backward compatibility.
The vulnerabilities as specified in ISO/IEC 24772-1:2024, 6.59 do not apply to C++, as long as the standard library facilities for creating threads are used. If the C standard library mechanisms or threading libraries from POSIX or other applications are used to create threads, then the vulnerabilities apply as described in ISO/IEC TR 24772-3:2020, 6.59. This subclause will examine the C++ standard mechanisms.
Creating a thread using the std::async function or the std::thread or std::jthread results in the constructor synchronized with the thread creation site. Unless the thread is detached, join() must be called when using std::thread or std::terminate will be invoked upon destruction of the thread object. This can be avoided by using std::jthread instead.
Failure to create or start a thread due to lack of system resources will cause an exception to be thrown to the creating thread thus the thread object never exists. For the vulnerabilities with unhandled exceptions see clause 6.36 Ignored error status and unhandled exceptions [OYB].
C++ provides other ways to create parallel executing functions via - the std::async() call or the std::packaged_task() functions; - Coroutines (describe and reference clause 7.5 and 7.6).
If std::async() threads are created without a launch policy, then system determines whether or not the thread is run lazily (in the current thread) or in a new thread. The launch policy std::launch::async or std::launch::deferred controls whether each asynch thread creation creates new threads or executes the call on demand. When a launch policy is std::launch::async then each call to std::async() will always create a new thread which can lead to resource exhaustion. When a launch policy is std::launch::deferred() the function will execute on the thread that executes the respective get() function for that async function.
C++ provides a mechanism to construct and control the execution of a function on a user-controlled potentially asynchronous thread via the std::packaged_task mechanism that allows access to the result via a std::future object. Each packaged_task object can be executed at most once and can be executed on the current thread or on a different thread.
There are a number of vulnerabilities that are possible once the thread is created using std::thread or std::jthread. See 6.63 lock protocol errors [CGM]. Each std::thread object must have its .join() member function called before the object is destroyed unless its .detach() member function was previously called. This is not required for threads created as std::jthread. If .detach() was called on thread object then any calls to .join() will result in an exception.
Any exception thrown within a thread’s function needs to be handled by that thread, otherwise such an exception will cause program termination. For handling such termination see clause 6.62 Concurrency - Premature termination [CGS]. Employing a std::packaged_task object as the thread function allows to pass an exception to the holder of its std::future object.
A detached thread will execute until its thread function ends or until the program is terminated. The std::jthread type allows to employ cooperative thread termination through std::stop_token See 6.60 Concurrency – Directed termination [CGT] or 6.62 Concurrency – Premature termination [CGS].
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
std::thread::native_handle, apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.59.5.std::jthread over std::thread for explicit thread creation.std::async or C++ parallel algorithms instead of explicit thread creation and management.std::packaged_task instead of using std::async() without specifying a launch policy.std::stop_token, especially for detached threads.This vulnerability as specified in ISO/IEC 24772-1:2024, 6.60 is mitigated in C++, as long as the standard C++ library facilities for threads are used. For issues associated with threades created using the standard C library, see ISO/IEC TR 24772-3:2020, 6.60.
C++ does not provide the means to terminate a C++ thread asynchronously, except in the case that std::terminate() is called. Instead C++ allows cooperative termination through the use of std::stop_token, however, a thread instructed by a stop request to cease execution can ignore such a request. For example, using std::jthread::request_stop() to send a stop request to the started thread, the created thread can have a thread function that never handles such a stop request.
void some_function(int value);
void stoppable_function(std::stop_token tok, int value);
int main()
{
using namespace std;
std::jthread t(some_function,10); // stop_token ignored since not provided
std::jthread t2(stoppable_function,100); // stop_token passed
t.request_stop(); // no-op as ignored in t
t2.request_stop(); // stop_token `tok`{.cpp} signalled in `t2`{.cpp},
// returns `true`{.cpp} showing `stop_token`{cpp} set
}
void stoppable_function(std::stop_token tok, int value) {
using namespace std::literals;
while (!tok.stop_requested()) //
{
std::osyncstream(std::cout) << value++ << '\n';
std::this_thread::sleep_for(200ms);
}
}
void some_function(int value)
{
using namespace std::literals;
for (int i =0; i<10; ++i)
{
std::osyncstream(std::cout) << value++ << '\n';
std::this_thread::sleep_for(200ms);
}
}
```{.cpp}
bool OK = t2.request_stop(); }
void stoppable_function(std::stop_token tok, int value) {
while (!stop_token.stop_requested()) //
{
std::cout << value++ << ' ' << std::flush;
std::this_thread::sleep_for(200ms);
}
void some_function(int value) {
for (int i = 0; i<10; i++) //
{
std::cout << value++ << ' ' << std::flush;
std::this_thread::sleep_for(200ms);
}
std::cout << std::endl;
}Note that in the while loop in ’stoppable_function(){.cpp}, if stop_token.stop_requested had not been checked, it would continue to run until the program itself is terminated. In the above example, at the end of the main function the destructors of the thread objectst{.cpp} andt2{.cpp} will callthis->request_stop(){.cpp} andthis->join(){.cpp}. If one of the thread functions never returns, then the correspondingjoin()`{.cpp} call will block.
Other programmed mechanisms can be constructed to cause another thread to complete, such as setting a shared variable to a known value that the target thread reads and then terminates itself.
A common programming mistake is to create threads with the intention that they will participate in a joint computation, but missing the join() operation in one or more threads. If jthreads are used for the threading, all threads are joined with the thread that created them.
Even if a thread is successfully terminated, the thread that initiated the termination should take action to clean up after the terminating thread,such as releasing memory
Discussion: Do we need to discuss termination of tasks?
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
For threads created using the C library, apply the avoidance mechanisms of ISO/IEC TR 24772-3:2020, 6.60.
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.60.5.
Use code reviews and static analysis tools if available, to ensure that threads correctly terminate.
The vulnerability as documented in ISO/IEC 24772-1:2024, 6.61 applies to C++ threads. Accessing mutable data from multiple threads without synchronization is undefined behaviour [EWF].
C++ provides atomic types, mutual exclusion primitives and higher-level synchronization mechanisms in the standard library to prevent data races. Sharing non-volatile constant data or not sharing data at all does not incur data races. For example, avoiding pointer-like types for thread function parameters guarantees that there is no sharing of the argument values.
The C++ memory model defines how memory accesses can be reordered. The default memory order guarantees sequential consistency. For increased parallel execution other memory orders can be used but require expert-level of care.
NOTE: The above sentence needs explanation or rewrite.
C++ programs that used multiple threads before the C++11 standard can employ volatile variables to guarantee race free data access. This practice is not sanctioned by current standard as volatile is not permitted for synchronization. The use of volatile std::sig_atomic_t only prevents a data race between a signal handler an the execution thread running the signal handler.
The C++ algorithm library allows specifying non-sequential execution policy for most algorithms. Employing parallel (std::par) or vectorized (std::unseq, std::par_unseq) execution policies may have additional requirements on data layout of the underlying ranges, especially to avoid data races.
Primitives requiring explicit locking and unlocking the pairwise application of these operations is supported implicitly by std::scoped_lock, even when an exception might be thrown. In addition to scope-based locking, std::scoped_lock guarantees deadlock-free locking of multiple mutexes.
Variables with thread storage duration cannot create data races, as long as their address is not shared with other threads.
Discuss sharing of memory between tasks!! i.e. Don’t do it!?
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Place all mutable data in memory accessible to only one thread at a time;
Use the appropriate standard library features for synchronization of shared mutable data, in particular use std::scoped_lock or the other scoped-based locking library facilities;
Prohibit calling lock and unlock facilities directly.
Forbid the use of volatile for synchronization;
Avoid specifying memory orders on atomic operations other than sequential consistency;
Use atomic variables or fences for synchronization of data shared from a signal handler, employ volatile sig_atomic_t only in a single-threaded environment;
Pass all thread function arguments by non-pointer-like values.
The vulnerability as described in ISO IEC 24772-1:2024, 6.62 applies to C++.
If the concurrency mechanisms of ISO/IEC 9899:2024 are used, then the vulnerabilities documented in ISO/IEC TR 24772-3:2020, apply.
C++ standard threads
Within C++ standard thread concurrency, the vulnerability as specified in ISO/IEC 24772-1, 6.62 can arise as follows: - as part of the termination of the complete program resulting from an explicit call to a program-terminating function in any thread in the program or an implicit call on std::terminate, for example, - as a result of an unhandled exception in any thread function when using thread or jthread - as a result of an unhandled exception in any thread function when using C++ parallel algorithms - as a result of faulty inter-thread asynchronous communications that results in unplanned termination of a communicating thread.
Errors relating to improper shutdown and cleanup are addressed in 6.36 Unhandled errors and exceptions(??) If non-standard C++ concurrency is used, the vulnerability and avoidance mechanisms are as specified in ISO/IEC 24772-1 and ISO/IEC 24772-3.
(see https://en.cppreference.com/w/cpp/error/terminate for more termination conditions)
Joining a thread causes the joining thread to await the joined thread’s termination before continuing. Useful for executing in parallel and then proceeding after the dispatched work is complete, but does not notify the joining task if the termination was premature.
C++ 2020 provides callbacks in the form of stop_callback to notify the setting thread when a thread of interest has been terminated. It also provides stop_token for a thread to query it is being instructed to terminate. (should go in 6.60 if not there already).
The terminating thread can pass back whatever is programmed back to the `join’ function, which can include notice of premature termination, for example by returning a pointer to the handled exception that identifies the error that caused premature termination.
The semantics of C++ is that all children of the main program will prematurely terminate if the main program terminates and final results from such threads will not be delivered . It is necessary to join the main program to all its children to ensure that children are not silently terminated prematurely.
Premature termination of C++ Tasks
Tasks can abort or terminate or cancelled (last one - by the scheduler) but these affect the whole program containing the thread. Therefore, the only way for a task to terminate is to receive a terminate request and transfer to the end of its code where the parent that initiated it will be waiting at the future.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO/IEC 24772-1:2024, 6.63.5.
Use low-level operating system primitives or other APIs where available to check that a required thread is still active.
Prohibit explicit or implicit calls to program-terminating functions whenever active threads are executing.
Check and obey the result of stop_token.stop_requested() periodically (see 6.60 Concurrency - Directed termination [CGT])
Return some indication of conditions resulting in premature termination
Ensure that the main program joins all created threads that need to complete normally, such as by using the std::jthread type.
For concurrent entities created using the C library, apply the avoidance mechanisms documented in ISO IEC TR 24772-3:2020, 6.62.
The vulnerability as described in ISO/IEC 24772-1:2024 applies to C++.
C++ inherits the C libraries which provide a large family of input and output functions that use a control string to interpret the data read or format the output. These strings include all the feature described in ISO/IEC TR 24772-1:2019 clause 6.64.1.
C++ provides type-safe alternatives for input/output, which do not use format strings and which should be used in preference, such as
int aNumber{};
while(std::cin){ // is input still available
std::cout << "Enter a whole number, please:";
if (std::cin >> aNumber) { // no format string needed
std::cout >> "Thank you, the number can be represented as "
std::cout << std::format("0b{0:b} {0:d} 0{0:o} 0x{0:x}", aNumber);
} else { // input failed
std::cin.clear(); // re-enable input
std::string line;
getline(std::cin,line); // skip to eol
}
}In addition, operator overloading of output operators allows to extend formatting abilities to user-defined types.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Forbid the use of the C library facilities provided by <cstdio>.
Apply the avoidance mechanisms provided by the standard library as described in ISO/IEC 14882 20.20 Formatting [format] or similar libraries, if printf-style formatting of output or strings is desired, or when the formatting facilities of std::ostream are inadequate.
Read untrusted input as plain characters and then check for validity, before any conversion to numbers or program entities occurs, using facilities that do not require format strings and that check and report error conditions.
The vulnerability as documented in ISO/IEC 24772-1:2024 6.65 exists in C++.
An object can be declared as const, denoting that its value will not change in its lifetime without invoking mechanisms which have undefined behaviour [EWF]. For example, an access path to an object can be declared as const, denoting that the value of the object will not change via this access path without invoking mechanisms which have undefined behaviour, e.g.,
int const i = 0; // the simplest access path
int& j = const_cast<int&>(i); // undefined behaviour
void foo(int* p) { *p += 43; }
//...
foo(const_cast<int*>(&i)); // undefined behaviour
foo(&i); // ill-formed, compiler errorIt is an illegal program or undefined behaviour to attempt to change a const object, such as i, above.
A object that is not const-qualified can be accessed through a path that is const-qualified.
The checking for the correctness of const is enforced based on the access-path and not the type of the target object. While it is possible to remove the const-qualification for an access path, attempting to modify a const object this way is undefined-behavior (see Undefined Behavior [EWF]) : const_cast<int&> (i) = 0; // undefined behavior
A constant can also be legitimately modified via a secondary access path. For example:
#include <cassert>
int k = 0;
void break_it()
{
k = 42; // legal
}
void test(int const volatile& j) // Volatile used only to guarantee observability
{
assert(j == 0); // will pass since k == 0
break_it();
assert(j == 0); // will fail since k != 0
}
test(k);When using pointers, confusion can occur between qualifications on the pointer’s type (pointer type) and qualifications on the type being referenced (pointee type). A common misconception is that a member function qualified with const cannot modify any of its members.
The following badly defined class introduces a non-const access path to a potentially const object:
struct A
{
A * pA;
int i{0};
A () : pA{this}{} // pA provides non-const access path
void f () const
{
// pA = nullptr; // ill-formed
// i = 0; // ill-formed
pA->i = 42; // compiles, but undefined behavior
// if executed on a const object
}
};
int main(){
A a; // mutable
A const b;
a.f(); // OK
b.f(); // undefined behaviour
}However, for C++ classes with members of a pointer-like type, programmers can establish the transitivity of const with const member functions that do not change the referred-to objects.
Within a const member function a mutable data member can still be modified, even if the containing object is const.
The use of mutable on a data member not contributing to the observable state of the object is preferable to removing the constness of the containing object (see Conversion Errors [FLC]).
Historically, classes with expensive computations in frequently-called const-member-functions, used mutable data members to cache results of those computations. In concurrent code this practice can lead to data races, unless access to those mutable data members is synchronised.
A safe use of mutable data members is for synchronisation primitives, allowing synchronisation in const member functions.
The following is a common example where a mutex member is declared mutable to allow locking in a const member function:
#include <mutex>
class MyQueue {
mutable std::mutex m_mutex;
int * m_head { nullptr };
public:
bool empty () const {
std::lock_guard sg {m_mutex}; // lock the mutex, which requires m_mutex to be writable
return m_head != nullptr;
}
// ...
};To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Apply the avoidance mechanisms of ISO IEC 24772-1:2024 6.65.5.
Forbid casting away const by const_cast or by any other means.
Be aware that, while const means “read only”, C++ permits values accessed by a const access path to be modified via a different access path.
Apply const on pointer-like parameters as a promise to callers and an obligation on the implementation to not modify the referred object.
Prohibit indirectly modifying the object’s state within any of its const member functions.
Apply mutable only to member variables that do not influence the observable state of an object.
Ensure in concurrent code, that mutable data members are implicitly or explicitly synchronized.
When designing C++ classes, provide const member functions to support const objects.
Ensure that C++ classes modelling pointer-like types provide transitivity of const within const member functions.
TBD
Future standardization efforts should consider:
Moving in the direction over time to being a more strongly typed
language. Much of the use of weak typing is simply convenience to the developer in not having to fully consider the types and uses of variables. Stronger typing forces good programming discipline and clarity about variables while at the same time removing many unexpected run time errors due to implicit conversions. This is not to say that C should be strictly a strongly typed language – some advantages of C are due to the flexibility that weaker typing provides. It is suggested that when enforcement of strong typing does not detract from the good flexibility that C offers (for example, adding an integer to a character to step through a sequence of characters) and is only a convenience for programmers (for example, adding an integer to a floating-point number), then the standard should specify the stronger typed solution.
A common warning in Annex I should be added for floating-point
expressions being used in a Boolean test for equality.
Modifying or deprecating many of the C standard library functions
that make assumptions about the occurrence of a string termination character.
Define a string construct that does not rely on the null termination
character.
Defining an array type that does automatic bounds checking.Deprecating less safe functions such as strcpy() and strcat() where
a more secure alternative is available.
Defining safer and more secure replacement functions such as
memncpy() and memncmp() to complement the memcpy() and memcmp() functions (see 6.11.6 Implications for standardization)
Defining an array type that does automatic bounds checking.Defining functions that contain an extra parameter in memcpy() and
memmove() for the maximum number of bytes to copy. In the past, some have suggested that the size of the destination buffer be used as an additional parameter. Some critics state that this solution is easy to circumvent by simply repeating the parameter that was used for the number of bytes to copy as the parameter for the size of the destination buffer. This analysis and criticism is correct. What is needed is a failsafe check as to the maximum number of bytes to copy. There are several reasons for creating new functions with an additional parameter. This would make it easier for static analysis to eliminate those cases where the memory copy could not be a problem (such as when the maximum number of bytes is demonstrably less than the capacity of the receiving buffer). Manual analysis or more involved static analysis could then be used for the remaining situations where the size of the destination buffer may not be sufficient for the maximum number of bytes to copy. This extra parameter may also help in determining which copies could take place among objects that overlap. Such copying is undefined according to the C standard. It is suggested that safer versions of functions that include a restriction max_n on the number of bytes n to copy (for example, void *memncpy(void * restrict s1,const void * restrict s2,size_t n), const size_t max_n) be added to the standard in addition to retaining the current corresponding functions (for example, memcpy(void * restrict s1,const void * restrict s2,size_t n))). The additional parameter would be consistent with the copying function pairs that have already been created such as strcpy()/strncpy() and strcat()/strncat(). This would allow a safer version of memory copying functions for those applications that want to use them in to facilitate both safer and more secure code and more efficient and accurate static code reviews1.
Restrictions on pointer arithmetic that could eliminate common
pitfalls. Pointer arithmetic is error-prone and the flexibility that it offers is useful, but some of the flexibility is simply a shortcut that if restricted could lessen the chance of a pointer arithmetic based error.
Defining a standard way of declaring an attribute to indicate that a
variable is intentionally unused.
A common warning in Annex I should be added for variables with the
same name in nested scopes.
Creating a few standardized precedence orders. Standardizing on a
few precedence orders will help to eliminate the confusing intricacies that exist between languages. This would not affect current languages as altering precedence orders in existing languages is too onerous. However, this would set a basis for the future as new languages are created and adopted. Stating that a language uses “ISO precedence order A” would be useful rather than having to spell out the entire precedence order that differs in a conceptually minor way from some other languages, but in a major way when programmers attempt to switch between languages.
Deprecating the goto statement. The use of the goto construct is
often spotlighted as the antithesis of good structured programming. Though its deprecation will not instantly make all C code structured, deprecating the goto and leaving in place the restricted goto variations (for example, break and continue) and possibly adding other restricted goto’s could assist in encouraging safer and more secure C programming in general.
Defining a "fallthru" construct that will explicitly bind multiple
switch cases together and eliminate the need for the break statement. The default would be for a case to break instead of falling through to the next case. Granted this is a major shift in concept, but if it could be accomplished, less unintentional errors would occur.
Defining an identifier type for loop control that cannot be modified
by anything other than the loop control construct would be a relatively minor addition to C that could make C code safer and encourage better structured programming.
Defining a standardized interface package for interfacing C with
many of the top programming languages and a reciprocal package should be developed of the other top languages to interface with C.
Joining with other languages in developing a standardized set of
mechanisms for detecting and treating error conditions so that all languages to the extent possible could use them. Note that this does not mean that all languages should use the same mechanisms as there should be a variety ( label parameters, auxiliary status variables), but each of the mechanisms should be standardized.
Since fault handling and exiting of a program is common to all
languages, it is suggested that common terminology such as the meaning of fail safe, fail hard, fail soft, and so on along with a core API set such as exit, abort, and so on be standardized and coordinated with other languages.
Deprecating unions. The primary reason for the use of unions to save
memory has been diminished considerably as memory has become cheaper and more available. Unions are not statically type safe and are historically known to be a common source of errors, leading to many C programming guidelines specifically prohibiting the use of unions.
Creating a recognizable naming standard for routines such that one
version of a library does parameter checking to the extent possible and another version does no parameter checking. The first version would be considered safer and more secure and the second could be used in certain situations where performance is critical and the checking is assumed to be done in the calling routine. A naming standard could be made such that the library that does parameter checking could be named as usual, say “library_xyz” and an equivalent version that does not do checking could have a “_p” appended, such as “library_xyz_p”. Without a naming standard such as this, a considerable number of wasted cycles will be conducted doing a double check of parameters or even worse, no checking will be done in both the calling and receiving routines as each is assuming the other is doing the checking.
Creating an Annex that lists deprecated features.[1] ISO/IEC Directives, Part 2, Rules for the structure and drafting of International Standards, 2004
[2] ISO/IEC TR 10000‑1, Information technology — Framework and taxonomy of International Standardized Profiles — Part 1: General principles and documentation framework
[3] ISO 10241 (all parts), International terminology standards
[4] ISO/IEC 9899:2011, Information technology — Programming languages — C
[5] ISO/IEC 9899:2011/Cor.1:2012, Technical Corrigendum 1
[6] ISO/IEC 30170:2012, Information technology — Programming languages — Ruby
[7] ISO/IEC/IEEE 60559:2011, Information technology – Microprocessor Systems – Floating-Point arithmetic
[8] ISO/IEC 1539-1:2010, Information technology — Programming languages — Fortran — Part 1: Base language
[9] ISO/IEC 8652:1995, Information technology — Programming languages — Ada
[10] ISO/IEC 14882:2011, Information technology — Programming languages — C++
[11] R. Seacord, The CERT C Secure Coding Standard. Boston,MA: Addison-Westley, 2008.
[12] Motor Industry Software Reliability Association. Guidelines for the Use of the C Language in Vehicle Based Software, 2012 (third edition)16F2.
[13] ISO/IEC TR24731–1, Information technology — Programming languages, their environments and system software interfaces — Extensions to the C library — Part 1: Bounds-checking interfaces
[14] ISO/IEC TR 15942:2000, Information technology — Programming languages — Guide for the use of the Ada programming language in high integrity systems
[15] Joint Strike Fighter Air Vehicle: C++ Coding Standards for the System Development and Demonstration Program. Lockheed Martin Corporation. December 2005.
[16] Motor Industry Software Reliability Association. Guidelines for the Use of the C++ Language in critical systems, June 2008
[17] ISO/IEC TR 24718: 2005, Information technology — Programming languages — Guide for the use of the Ada Ravenscar Profile in high integrity systems
[18] L. Hatton, Safer C: developing software for high-integrity and safety-critical systems. McGraw-Hill 1995
[19] ISO/IEC 15291:1999, Information technology — Programming languages — Ada Semantic Interface Specification (ASIS)
[20] Software Considerations in Airborne Systems and Equipment Certification. Issued in the USA by the Requirements and Technical Concepts for Aviation (document RTCA SC167/DO-178B) and in Europe by the European Organization for Civil Aviation Electronics (EUROCAE document ED-12B).December 1992.
[21] IEC 61508: Parts 1-7, Functional safety: safety-related systems. 1998. (Part 3 is concerned with software).
[22] ISO/IEC 15408: 1999 Information technology. Security techniques. Evaluation criteria for IT security.
[23] J Barnes, High Integrity Software - the SPARK Approach to Safety and Security. Addison-Wesley. 2002.
[25] Steve Christy, Vulnerability Type Distributions in CVE, V1.0, 2006/10/04
[26] ARIANE 5: Flight 501 Failure, Report by the Inquiry Board, July 19, 1996 http://esamultimedia.esa.int/docs/esa-x-1819eng.pdf
[27] Hogaboom, Richard, A Generic API Bit Manipulation in C, Embedded Systems Programming, Vol 12, No 7, July 1999 http://www.embedded.com/1999/9907/9907feat2.htm
[28] Carlo Ghezzi and Mehdi Jazayeri, Programming Language Concepts, 3rd edition, ISBN-0-471-10426-4, John Wiley & Sons, 1998
[29] Lions, J. L. ARIANE 5 Flight 501 Failure Report. Paris, France: European Space Agency (ESA) & National Center for Space Study (CNES) Inquiry Board, July 1996.
[30] Seacord, R. Secure Coding in C and C++. Boston, MA: Addison-Wesley, 2005. See http://www.cert.org/books/secure-coding for news and errata.
[31] John David N. Dionisio. Type Checking. http://myweb.lmu.edu/dondi/share/pl/type-checking-v02.pdf
[32] MISRA Limited. "MISRA C: 2012 Guidelines for the Use of the C Language in Critical Systems." Warwickshire, UK: MIRA Limited, March 2013 (ISBN 978-1-906400-10-1 and 978-1-906400-11-8).
[33] The Common Weakness Enumeration (CWE) Initiative, MITRE Corporation, (http://cwe.mitre.org/)
[34] Goldberg, David, What Every Computer Scientist Should Know About Floating-Point Arithmetic, ACM Computing Surveys, vol 23, issue 1 (March 1991), ISSN 0360-0300, pp 5-48.
[35] IEEE Standards Committee 754. IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Standard 754-2008. Institute of Electrical and Electronics Engineers, New York, 2008.
[36] Robert W. Sebesta, Concepts of Programming Languages, 8th edition, ISBN-13: 978-0-321-49362-0, ISBN-10: 0-321-49362-1, Pearson Education, Boston, MA, 2008
[37] Bo Einarsson, ed. Accuracy and Reliability in Scientific Computing, SIAM, July 2005 http://www.nsc.liu.se/wg25/book
[38] GAO Report, Patriot Missile Defense: Software Problem Led to System Failure at Dhahran, Saudi Arabia, B-247094, Feb. 4, 1992, http://archive.gao.gov/t2pbat6/145960.pdf
[39] Robert Skeel, Roundoff Error Cripples Patriot Missile, SIAM News, Volume 25, Number 4, July 1992, page 11, http://www.siam.org/siamnews/general/patriot.htm
[40] CERT. CERT C++ Secure Coding Standard. https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=637 (2009).
[41] Holzmann, Garard J., Computer, vol. 39, no. 6, pp 95-97, Jun., 2006, The Power of 10: Rules for Developing Safety-Critical Code
[42] P. V. Bhansali, A systematic approach to identifying a safe subset for safety-critical software, ACM SIGSOFT Software Engineering Notes, v.28 n.4, July 2003
[43] Ada 95 Quality and Style Guide, SPC-91061-CMC, version 02.01.01. Herndon, Virginia: Software Productivity Consortium, 1992. Available from: http://www.adaic.org/docs/95style/95style.pdf
[44] Ghassan, A., & Alkadi, I. (2003). Application of a Revised DIT Metric to Redesign an OO Design. Journal of Object Technology , 127-134.
[45] Subramanian, S., Tsai, W.-T., & Rayadurgam, S. (1998). Design Constraint Violation Detection in Safety-Critical Systems. The 3rd IEEE International Symposium on High-Assurance Systems Engineering , 109 - 116.
[46] Lundqvist, K and Asplund, L., “A Formal Model of a Run-Time Kernel for Ravenscar”, The 6th International Conference on Real-Time Computing Systems and Applications – RTCSA 1999
[47] ISO/IEC TS 17961, Information technology – Programming languages, their environments and system software interfaces – C secure coding rules
[48] GNU Project. GCC Bugs “Non-bugs” http://gcc.gnu.org/bugs.html#nonbugs_c (2009).
LHS (left-hand side), 22