This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 0.0.2 - Controlling expressions should not be invariant
[IEC 61508-7] / C.5.9
[DO-178C] / 6.4.4.3.c
[ISO 26262-6] / 9.4.5
Category: Advisory
Analysis: Undecidable,System
Amplification
This rule applies to:
- Controlling expressions of
if, while, for, do ... while and switch statements;
and
- The first operand of the conditional operator (
?:); and
- The left hand operand of the logical AND (
&&) and logical OR (||) operators.
It does not apply to controlling expressions of constexpr if statements.
A function’s compliance with this rule is determined independently of the context in which the function is called. For example, a Boolean parameter
is treated as if it may have a value of true or false, even if all the calls expressed in the current program use a value of
true.
Rationale
If a controlling expression has an invariant value, it is possible that there is a programming error. Any code in an infeasible path [1]
may be removed by the compiler, which might have the effect of removing code that has been introduced for defensive purposes.
This rule does not apply to constexpr if, as this is intended to be evaluated at compile time and requires a constant expression.
Exception
- A
while statement with a constant expression evaluating to true is permitted as this is commonly used in real time
systems.
- Macros are permitted to expand to a do-while statement of the form
do { ... } while ( false ), allowing a macro expansion
to be used as a statement that includes a local scope.
Example
s8a = ( u16a < 0u ) ? 0 : 1; // Non-compliant - u16a always >= 0
if ( u16a <= 0xffffu ) { } // Non-compliant - always true
if ( 2 > 3 ) { } // Non-compliant - always false
if ( ( s8a < 10 ) && ( s8a > 20 ) ) { } // Non-compliant - always false
if ( ( s8a < 10 ) || ( s8a > 5 ) ) { } // Non-compliant - always true
if ( ( s8a < 10 ) &&
( s8a > 20 ) || // Non-compliant - left operand of ||
( s8b == 5 ) ) { } // always false
const uint8_t N = 4u;
if ( N == 4u ) // Non-compliant - compiler is permitted
{ // to assume that N always has value 4
}
extern const volatile uint8_t M;
if ( M == 4u ) // Compliant - compiler assumes M may
{ // change, even though the program
} // cannot modify its value
while ( s8a > 10 )
{
if ( s8a > 5 ) { } // Non-compliant - s8a always > 5
--s8a;
}
for ( s8a = 0; s8a < 130; ++s8a ) { } // Non-compliant - always true
while ( true ) { /* Do something */ } // Compliant by exception #1
do { } while ( false ); // Compliant by exception #2
// - if expanded from a macro
uint16_t n; // Assume 10 <= n <= 100
uint16_t sum;
sum = 0;
for ( uint16_t i = ( n - 6u ); i < n; ++i )
{
sum += i;
}
if ( ( sum % 2u ) == 0u )
{
// Non-compliant - the sum of six, consecutive, non-negative integers is always
// an odd number, so the controlling expression will always be false.
}
template< typename T >
void foo()
{
if constexpr ( std::is_integral< T >() ) // Rule does not apply
{
// Handle integral case
}
else
{
// Handle other case
}
}
template void foo< int >();
template void foo< float >();
Glossary
[1] Infeasible path
Infeasible paths occur where there is a syntactic path to a code fragment, but the semantics ensure that the control flow path will not be
executed. For example:
if ( u32 < 0 )
{
// An unsigned value will never be negative,
// so code in this block will never be executed.
}
Copyright The MISRA Consortium Limited © 2023
How to fix it
If a controlling expression can be computed statically, use if constexpr to suppress issues of this rule. For example, consider this
feature flag:
const int FeatureConst = 18; // Determined from the compilation paramters
inline bool const_feature_is_big() {
if (10 < FeatureConst) { // Noncompliant: This condition is always true when evaluated
return true;
}
return false;
}
Here is the corrected example:
constexpr int FeatureConst = 18; // Determined from the compilation paramters
inline bool const_feature_is_big() {
if constexpr (10 < FeatureConst) { // Compliant: if constexpr is an exception to the rule
return true;
}
return false;
}
A typical case of statically computed condition happens when testing the value of a template argument. Our analyzer considers each template
instantiation independently. If an instantiation happens to have a statically invariant controlling expression for the given combination of arguments,
the analyzer reports a violation.
This might lead to seemingly contradictory reports, as in the following example:
template<int X>
void template_with_compile_check() {
// Noncompliant: This condition is always false when evaluated
// Noncompliant: This condition is always true when evaluated
if (X == 18) {
}
}
void instantiate() {
template_with_compile_check<18>();
template_with_compile_check<19>();
}
These reports do not actually contradict each other because they refer to different instantiations of the template function, sharing the same code
location.
Again, if constexpr allows you to ensure that a condition is evaluated at compile-time, and is probably more appropriate when testing
the value of template arguments. No issue would be created in that case.
template<int X>
void template_with_compile_check() {
// Compliant: if constexpr is an exception to the rule
if constexpr (X == 18) {
}
}
void instantiate() {
template_with_compile_check<18>();
template_with_compile_check<19>();
}