CPP-4792 Create rule S6871: All the elements of an aggregate should be provided with an initial value

This commit is contained in:
github-actions[bot] 2024-01-31 11:18:24 +01:00 committed by GitHub
parent d8063fa808
commit 529611e08b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 409 additions and 13 deletions

View File

@ -0,0 +1,24 @@
{
"title": "All the elements of an aggregate should be provided with an initial value",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "5min"
},
"tags": [
"pitfall"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-6871",
"sqKey": "S6871",
"scope": "All",
"defaultQualityProfiles": [],
"quickfix": "infeasible",
"code": {
"impacts": {
"MAINTAINABILITY": "MEDIUM"
},
"attribute": "CLEAR"
}
}

View File

@ -0,0 +1,368 @@
When an array or an object of aggregate type is non-zero initialized,
values for each element or field should be provided.
== Why is this an issue?
In C or {cpp}, it is possible to provide an initial value for the elements of an array.
When fewer values are provided than the size of the array,
the last elements of the array are zero-initialized for builtin-types (like `int` or pointers),
and _value-initialized_ otherwise.
However, as soon as some values are provided, it is clearer to provide them all and not rely on these default initializations.
[source,c]
----
int a1[5] = {1, 2, 3}; // Noncompliant, last two elements are initialized with 0
int a2[4] = {1, 2, 3, 4, 5}; // Compliant
int* a3[3] = {a1, a1 + 1}; // Noncompliant, the last pointer is null
int* a4[3] = {a1, a1 + 1, nullptr}; // Compliant
----
Similarly, when an aggregate class or struct is initialized,
an initial value may be provided for each field.
All remaining fields are initialized in the same manner as
elements of an array, but this rule requests explicit initialization:
[source,cpp]
----
struct Pod {
int x;
int y;
};
Pod p1{1}; // Noncompliant, `y` does not have an initial value
Pod p2{1, 0}; // Compliant
----
This behavior applies recursively when arrays and aggregates are nested:
[source,c]
----
struct PodPair {
Pod first;
Pod second;
};
struct ArrayMember {
int id;
int vals[4];
};
int c1[2][2] = {{1}, {2}}; // Noncompliant, the second elements of each nested array do not have an initial value provided
Pod c2[3] = {{1, 2}, {2}}; // Noncompliant, field `y` of `c2[1]` and whole `c2[2]` object do not have an initial value provided
PodPair c3 = {{1}}; // Noncompliant, field `y` of `c3.first` and whole `c3.second` object do not have an initial value provided,
ArrayMember c4 = {1, 2, 3}; // Noncompliant, the last two elements of `c4.vals` do not have an initial value provided
----
This rule raises an issue when a non-zero initialization of an aggregate (array or class/struct),
does not provide values for all its elements or fields.
=== What is the potential impact?
The intent of the code is unclear when the initializer omits the values for some of the elements or fields:
Is the initial value skipped on purpose or is it an oversight?
Is it because, after the initial code was written, the array size was changed, or a new field was added, and the initialization was not updated?
In that case, the zero or default value may not be handled properly,
leading to unexpected program behavior.
As an illustration, if the field or element has a pointer type, it will be initialized with a null-pointer value,
and may lead to null-pointer dereference.
=== What about zero initialization?
When initializing an array with a large number of elements, or a complex structure,
it is often desired to set all the elements or fields to zero, or to their default values (for classes with default constructors).
Such situations are usually indicated by an empty set of braces `{}` for {cpp},
or braces with `{0}` or `{NULL}` in the case of C.
The issue is not raised in such a situation.
[source,cpp]
----
int a1[10] = {0}; // Compliant
int a2[10] = {}; // Compliant
Pod p1 = {0}; // Compliant
PodPair c1 = {0}; // Compliant
----
This exception also applies when the nested aggregate is zero-initialized:
[source,cpp]
----
int c1[2][2]{{1, 2}, {}}; // Compliant
Pod c2[3] = {{1, 2}, {}, {}}; // Compliant
PodPair c3 = {{}, {1, 0}}; // Compliant
ArrayMember c4{1, {}}; // Compliant
----
=== What if designated initializers are used?
The C standard provides a designated initialization syntax
that explicitly denotes the field or element of the aggregate for which value is provided.
This rule also raises an issue if the initial value for an element or field is not provided.
[source,c]
----
int a1[3] = { [1] = 1, [2] = 2 }; // Noncompliant, first element do not have initial value provided
int a2[3] = { [0] = 0, [1] = 1, [2] = 2 }; // Compliant
Pod p1 = {.y = 10}; // Noncompliant, `p.x` do not have initial value provided
Pod p2 = {.x = 0, .y = 10}; // Compliant
----
{cpp}20 has adopted a limited version of this feature.
This rule also raises an issue in the case of incomplete initializers.
=== What if the field has a default member initializer?
{cpp}11 allows class fields to specify a default value
that will be used if no other value is provided during construction.
Such default member initializers cannot be used in aggregate types until {cpp}14,
which guarantees that the default value is used if the initializer list does not provide a value for the field.
The issue is not raised when a field with a default value is not explicitly initialized, as an initial value was provided by the class author,
and should be handled properly.
[source,cpp]
----
struct Aggr {
int a;
int b;
int c = 0;
};
Aggr a0{}; // Compliant, zero-initialization
Aggr a1{10}; // Noncompliant, no initial value for field `b`, which does not have a default value
Aggr a2{10, 20}; // Compliant, field `c` has a default value specified in `Aggr` definition
Aggr a3{10, 20, 30}; // Compliant, all fields have initial values
----
With the use of a designated initializer, such default value is meaningful for non-trailing fields:
[source,cpp]
----
struct Mid {
int a;
int b = 10;
int c;
};
Mid m1{.a = 10, .b = 10}; // Noncompliant, no initial value for field `c`, which does not have a default value
Mid m1{.a = 10, .c = 2}; // Compliant, field `b` has a default value specified in `Mid` definition
----
=== Can the issue be raised if I use parenthesis?
{cpp}20 allows aggregate types to be initialized using the parenthesis (`()`) in addition to the braces (`{}`),
which simplifies writing generic code that creates an object (see S6872 for more details).
This syntax also allows not to specify all values during initialization and this rule raises an issue in that case too.
[source,cpp]
----
int a1[5](1, 2, 3); // Noncompliant, the last two elements do not have an initial value
int a2[4](1, 2, 3, 4, 5); // Compliant
Pod p1(1); // Noncompliant, `y` does not have an inital value
Pod p2(1, 0); // Compliant
----
== How to fix it
This issue can be addressed by either:
* providing an initial value for the elements or fields in the initializer or using default member initializer ({cpp}14 and later);
* using the idiomatic zero-initialization syntax.
=== Code examples
Provide values for all elements of field in intializer.
==== Noncompliant code example
[source,cpp,diff-id=1,diff-type=noncompliant]
----
struct Pod {
int x;
int y;
};
struct PodPair {
Pod first;
Pod second;
};
struct ArrayMember {
int id;
int vals[4];
};
int a1[5] = {1, 2, 3}; // Noncompliant
Pod p1{1}; // Noncompliant
int c1[2][2] = {{1}, {2}}; // Noncompliant
Pod c2[3] = {{1, 2}, {2}}; // Noncompliant
PodPair c3 = {{1}}; // Noncompliant
ArrayMember c4 = {1, {2, 3}}; // Noncompliant
----
==== Compliant solution
[source,c,diff-id=1,diff-type=compliant]
----
struct Pod {
int x;
int y;
};
struct PodPair {
Pod first;
Pod second;
};
struct ArrayMember {
int id;
int vals[4];
};
int a1[5] = {1, 2, 3, 0, 0}; // Compliant
Pod p1{1, 0}; // Compliant
int c1[2][2] = {{1, 0}, {2, 0}}; // Compliant
Pod c2[3] = {{1, 2}, {2, 0}, {0, 0}}; // Compliant
PodPair c3 = {{1, 0}, {0, 0}}; // Compliant
ArrayMember c4 = {1, {2, 3, 0, 0, 0}}; // Compliant
----
Or use zero-initialization syntax for `c2` and `c3`:
[source,cpp]
----
Pod c2[3] = {{1, 2}, {2, 0}, {}}; // Compliant
PodPair c3{{1, 0}, {}}; // Compliant
----
Use idomatic syntax for zero-intialization.
==== Noncompliant code example
[source,c,diff-id=2,diff-type=noncompliant]
----
struct ArrayMember {
int id;
int vals[4];
};
int a1[5] = {0, 0, 0}; // Noncompliant
int c1[2][3] = {{0, 0}, {0, 0}}; // Noncompliant
ArrayMember c2 = {11}; // Noncompliant
----
==== Compliant solution
[source,c,diff-id=2,diff-type=compliant]
----
struct ArrayMember {
int id;
int vals[4];
};
int a1[5] = {0}; // Compliant
int c1[2][3] = {0}; // Compliant
ArrayMember c2 = {11, {0}}; // Compliant
----
The previous solution works for both C and {cpp}.
Here is an alternative {cpp}-specific version:
[source,cpp]
----
int a1[5]{}; // Compliant
int c1[2][3] = {{}, {}}; // Compliant
ArrayMember c2 = {11, {}}; // Compliant
----
For {cpp}14 or later, provide default value for the field in class.
==== Noncompliant code example
[source,cpp,diff-id=3,diff-type=noncompliant]
----
struct Pod {
int x;
int y;
};
struct ArrayMember {
int id;
int vals[4];
};
Pod p1{1}; // Noncompliant
ArrayMember m1{11}; // Noncompliant
----
==== Compliant solution
[source,cpp,diff-id=3,diff-type=compliant]
----
struct Pod {
int x;
int y = 2;
};
struct ArrayMember {
int id;
int vals[4]{1, 2, 3, 4};
};
Pod p1{1}; // Compliant
ArrayMember m1{11}; // Compliant
----
=== Going the extra mile
When initializing a nested aggregate type (e.g., an array of aggregates),
braces around nested object initial values may be omitted.
This feature is referred to as _brace elision_.
[source,c]
----
struct Pod {
int x;
int y;
};
int e1[3][2] = {1, 2, 3}; // Noncompliant, `e1[1][2]` and `e1[2]` do not have initial valus
Pod e2[4] = {1, 2, 3}; // Noncompliant, `e2[1].y`, `e[2]`, and `e[3]` do not have initial value
----
To comply with this rule, supplying an initial value to all elements is required:
[source,c]
----
int e1[3][2] = {1, 2, 3, 0, 0, 0}; // Compliant
Pod e2[2] = {1, 2, 3, 0, 0, 0, 0, 0}; // Compliant
----
However, for the sake of readability, it is recommended to use nested braces to reflect the structure of the object (see S835):
[source,c]
----
int e1[3][2] = {{1, 2}, {3, 0}, {0, 0}}; // Compliant
Pod e2[2] = {{1, 2}, {3, 0}, {0, 0}, {0, 0}}; // Compliant
----
Or use zero initialization, when appropriate:
[source,cpp]
----
int e1[3][2] = {{1, 2}, {3, 0}, {}}; // Compliant
Pod e2[2] = {{1, 2}, {3, 0}, {}, {}}; // Compliant
----
== Resources
=== Documentation
* {cpp} reference -- https://en.cppreference.com/w/cpp/language/aggregate_initialization[Aggregate initialization]
* {cpp} reference -- https://en.cppreference.com/w/cpp/language/value_initialization[Value-initialization]
=== External coding guidelines
* MISRA C:2012, 9.3 - Arrays shall not be partially initialized.
=== Related rules
* S835 - Braces should be used to indicate and match the structure in the non-zero initialization of arrays and structures
* S6872 - Aggregates should be initialized with braces in non-generic code

View File

@ -0,0 +1,2 @@
{
}

View File

@ -4,23 +4,27 @@ ISO/IEC 14882:2003 [1] requires initializer lists for arrays, structures and uni
This forces the developer to explicitly consider and demonstrate the order in which elements of complex data types are initialized (e.g. multi-dimensional arrays).
The zero initialization of arrays or structures shall only be applied at the top level.
The non-zero initialization of arrays or structures requires an explicit initializer for each element.
A similar principle applies to structures, and nested combinations of structures, arrays and other types.
Note also that all the elements of arrays or structures can be initialized (to zero or NULL) by giving an explicit initializer for the first element only. If this method of initialization is chosen then the first element should be initialized to zero (or NULL), and nested braces need not be used.
=== Noncompliant code example
[source,cpp]
----
struct Pod {
int x;
int y;
};
struct PodPair {
Pod first;
Pod second;
};
int a1[3][2] = { 1, 2, 3, 4, 5, 6 }; // Noncompliant
int a2[5] = { 1, 2, 3 }; // Noncompliant, partial initialization
int a3[2][2] = { { }, { 1, 2 } }; // Noncompliant, zero initialization at sub-level
Pod a2[2] = { 1, 2, 3, 4 }; // Noncompliant
PodPair a3 = { 1, 2 }; // Noncompliant, also missing initializers for `a3.second`
----
@ -28,10 +32,9 @@ int a3[2][2] = { { }, { 1, 2 } }; // Noncompliant, zero initialization at sub-le
[source,cpp]
----
int a1[3][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 } }; // Compliant
int a2[5] = { 1, 2, 3, 0, 0 }; // Compliant, Non-zero initialization
int a2[5] = { 0 }; // Compliant, zero initialization
int a3[2][2] = { }; // Compliant, zero initialization
int a1[3][2] = { {1, 2}, {3, 4}, {5, 6} }; // Compliant
Pod a2[2] = { {1, 2}, {3, 4} }; // Compliant
PodPair a3 = { {1, 2}, {} }; // Compliant
----
@ -40,7 +43,6 @@ int a3[2][2] = { }; // Compliant, zero initialization
* MISRA C:2004, 9.2 - Braces shall be used to indicate and match the structure in the non-zero initialization of arrays and structures.
* MISRA {cpp}:2008, 8-5-2 - Braces shall be used to indicate and match the structure in the nonzero initialization of arrays and structures.
* MISRA C:2012, 9.2 - The initializer of an aggregate or union shall be enclosed in braces.
* MISRA C:2012, 9.3 - Arrays shall not be partially initialized.
ifdef::env-github,rspecator-view[]