CertC++-DCL58¶
Do not modify the standard namespaces
Required inputs: IR
Namespaces introduce new declarative regions for declarations, reducing the likelihood of conflicting identifiers with other declarative regions. One feature of namespaces is that they can be further extended, even within separate translation units. For instance, the following declarations are well-formed.
namespace MyNamespace {
int length;
}
namespace MyNamespace {
int width;
}
void f() {
MyNamespace::length = MyNamespace::width = 12;
}
The standard library introduces the namespace
std for standards-provided
declarations such as
std::string,
std::vector, and
std::for_each. However, it is
undefined
behavior to introduce new declarations in namespace
std except under special
circumstances. The C++ Standard, [namespace.std], paragraphs 1 and 2
[
ISO/IEC
14882-2014], states the following:
1 The behavior of a C++ program is undefined if it adds declarations or definitions to namespace
stdor to a namespace within namespacestdunless otherwise specified. A program may add a template specialization for any standard library template to namespacestdonly if the declaration depends on a user-defined type and the specialization meets the standard library requirements for the original template and is not explicitly prohibited.2 The behavior of a C++ program is undefined if it declares
- an explicit specialization of any member function of a standard library class template, or
- an explicit specialization of any member function template of a standard library class or class template, or
- an explicit or partial specialization of any member class template of a standard library class or class template.
In addition to restricting extensions to the the namespace
std, the C++ Standard, [namespace.posix], paragraph 1,
further states the following:
The behavior of a C++ program is undefined if it adds declarations or definitions to namespace
posixor to a namespace within namespaceposixunless otherwise specified. The namespaceposixis reserved for use by ISO/IEC 9945 and other POSIX standards.
Do not add declarations or definitions to the standard namespaces
std or
posix, or to a namespace contained therein, except for a template
specialization that depends on a user-defined type that meets the standard
library requirements for the original template.
The Library Working Group, responsible for the wording of the Standard Library
section of the C++ Standard, has an unresolved
issue on the definition of user-defined type.
Although the Library Working Group has no official stance on the definition [
INCITS
2014], we define it to be any
class,
struct,
union, or
enum that is not defined within namespace
std or a namespace contained within namespace
std. Effectively, it is a user-provided type instead of a standard
library-provided type.
Noncompliant Code Example
In this noncompliant code example, the declaration of
x is added to the namespace
std, resulting in
undefined
behavior.
namespace std {
int x;
}
Compliant Solution
This compliant solution assumes the intention of the programmer was to place
the declaration of
x into a namespace to prevent collisions with other global
identifiers. Instead of placing the declaration into the namespace
std, the declaration is placed into a namespace without a reserved
name.
namespace nonstd {
int x;
}
Noncompliant Code Example
In this noncompliant code example, a template specialization of
std::plus is added to the namespace
std in an attempt to allow
std::plus to concatenate a
std::string and
MyString object. However, because the template specialization is
of a standard library-provided type (
std::string), this code results in undefined behavior.
#include <functional>
#include <iostream>
#include <string>
class MyString {
std::string data;
public:
MyString(const std::string &data) : data(data) {}
const std::string &get_data() const { return data; }
};
namespace std {
template <>
struct plus<string> : binary_function<string, MyString, string> {
string operator()(const string &lhs, const MyString &rhs) const {
return lhs + rhs.get_data();
}
};
}
void f() {
std::string s1("My String");
MyString s2(" + Your String");
std::plus<std::string> p;
std::cout << p(s1, s2) << std::endl;
}
Compliant Solution
The interface for
std::plus requires that both arguments to the function call
operator and the return type are of the same type. Because the attempted
specialization in the noncompliant code example results in
undefined
behavior, this compliant solution defines a new
std::binary_function derivative that can add a
std::string to a
MyString object without requiring modification of the
namespace
std.
#include <functional>
#include <iostream>
#include <string>
class MyString {
std::string data;
public:
MyString(const std::string &data) : data(data) {}
const std::string &get_data() const { return data; }
};
struct my_plus : std::binary_function<std::string, MyString, std::string> {
std::string operator()(const std::string &lhs, const MyString &rhs) const {
return lhs + rhs.get_data();
}
};
void f() {
std::string s1("My String");
MyString s2(" + Your String");
my_plus p;
std::cout << p(s1, s2) << std::endl;
}
Compliant Solution
In this compliant solution, a specialization of
std::plus is added to the
std namespace, but the specialization depends on a user-defined
type and meets the Standard Template Library requirements for the original
template, so it complies with this rule. However, because
MyString can be constructed from
std::string, this compliant solution involves invoking a
converting constructor whereas the previous compliant solution does not.
#include <functional>
#include <iostream>
#include <string>
class MyString {
std::string data;
public:
MyString(const std::string &data) : data(data) {}
const std::string &get_data() const { return data; }
};
namespace std {
template <>
struct plus<MyString> {
MyString operator()(const MyString &lhs, const MyString &rhs) const {
return lhs.get_data() + rhs.get_data();
}
};
}
void f() {
std::string s1("My String");
MyString s2(" + Your String");
std::plus<MyString> p;
std::cout << p(s1, s2).get_data() << std::endl;
}
Risk Assessment
Altering the standard namespace can cause undefined behavior in the C++ standard library.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| DCL58-CPP | High | Unlikely | Medium | P6 | L2 |
Related Guidelines
| SEI CERT C++ Coding Standard | DCL51-CPP. Do not declare or define a reserved identifier |
Bibliography
| [ INCITS 2014] | Issue 2139, "What Is a User-Defined Type?" |
| [ ISO/IEC 14882-2014] | Subclause 17.6.4.2.1, "Namespace
std"Subclause 17.6.4.2.2, "Namespace posix"
|
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
std_extension |
Invalid addition to std namespace |
None |
False |
std_specialization |
Invalid std template specialization |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
std_specialization_blacklist¶
std_specialization_blacklist
Templates in namespace std that must not be specialized.Type: set[bauhaus.analysis.config.QualifiedName]
Default:
{'std::add_const', 'std::add_cv', 'std::add_lvalue_reference', 'std::add_pointer', 'std::add_rvalue_reference', 'std::add_volatile', 'std::aligned_storage', 'std::aligned_union', 'std::alignment_of', 'std::binary_function', 'std::common_type', 'std::conditional', 'std::conjunction', 'std::decay', 'std::disjunction', 'std::enable_if', 'std::endian', 'std::extent', 'std::has_unique_object_representations', 'std::has_virtual_destructor', 'std::integral_constant', 'std::invoke_result', 'std::is_abstract', 'std::is_aggregate', 'std::is_arithmetic', 'std::is_array', 'std::is_assignable', 'std::is_base_of', 'std::is_class', 'std::is_compound', 'std::is_const', 'std::is_constructible', 'std::is_convertible', 'std::is_copy_assignable', 'std::is_copy_constructible', 'std::is_default_constructible', 'std::is_destructible', 'std::is_empty', 'std::is_enum', 'std::is_final', 'std::is_floating_point', 'std::is_function', 'std::is_fundamental', 'std::is_integral', 'std::is_invocable', 'std::is_invocable_r', 'std::is_literal_type', 'std::is_lvalue_reference', 'std::is_member_function_pointer', 'std::is_member_object_pointer', 'std::is_member_pointer', 'std::is_move_assignable', 'std::is_move_constructible', 'std::is_nothrow_assignable', 'std::is_nothrow_constructible', 'std::is_nothrow_convertible', 'std::is_nothrow_copy_assignable', 'std::is_nothrow_copy_constructible', 'std::is_nothrow_default_constructible', 'std::is_nothrow_destructible', 'std::is_nothrow_invocable', 'std::is_nothrow_invocable_r', 'std::is_nothrow_move_assignable', 'std::is_nothrow_move_constructible', 'std::is_nothrow_swappable', 'std::is_nothrow_swappable_with', 'std::is_null_pointer', 'std::is_object', 'std::is_pod', 'std::is_pointer', 'std::is_polymorphic', 'std::is_reference', 'std::is_rvalue_reference', 'std::is_same', 'std::is_scalar', 'std::is_signed', 'std::is_standard_layout', 'std::is_swappable', 'std::is_swappable_with', 'std::is_trivial', 'std::is_trivially_assignable', 'std::is_trivially_constructible', 'std::is_trivially_copy_assignable', 'std::is_trivially_copy_constructible', 'std::is_trivially_copyable', 'std::is_trivially_default_constructible', 'std::is_trivially_destructible', 'std::is_trivially_move_assignable', 'std::is_trivially_move_constructible', 'std::is_union', 'std::is_unsigned', 'std::is_void', 'std::is_volatile', 'std::make_signed', 'std::make_unsigned', 'std::negation', 'std::rank', 'std::remove_all_extents', 'std::remove_const', 'std::remove_cv', 'std::remove_cvref', 'std::remove_extent', 'std::remove_pointer', 'std::remove_reference', 'std::remove_volatile', 'std::result_of', 'std::unary_function', 'std::underlying_type', 'std::void_t'}
std_specialization_whitelist¶
std_specialization_whitelist : set[bauhaus.analysis.config.QualifiedName] = {'std::common_type'}