Pointers marked as "nonnull" may not be set to null, since they are typically not null-checked before use.
Why is this an issue?
A function’s return value and parameters may be decorated with attributes to convey additional information to the compiler and/or other
developers.
A commonly used attribute is nonnull
which can be used to mark a function’s return value and parameters as shown in the following:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__((returns_nonnull)) int *
make_array_copy(__attribute__((nonnull)) int *src, size_t len) {
int *dst = (int *)malloc(len * sizeof(int));
if (dst == NULL) {
perror("malloc failed");
exit(1);
}
memcpy(dst, src, len);
return dst;
}
The nonnull
attribute is meant for other developers and as a hint for compilers. Values marked as nonnull
are assumed to
have non-null values.
However, developers may accidentally break the nonnull
attribute as shown in the following code snippet:
__attribute__((returns_nonnull))
int* foo(__attribute__((nonnull)) int* x) {
x = 0; // Noncompliant: `x` is marked "nonnull" but is set to null
foo(0); // Noncompliant: null is passed as an argument marked as "nonnull"
return 0; // Noncompliant: return value is marked "nonnull" but null is returned
}
Failing to adhere to the attribute may introduce serious program errors. In particular, the compiler does not enforce that values marked as
nonnull
are indeed non-null at runtime; it is the developers' responsibility to adhere to the attribute. These values are typically
not null-checked before use. Setting a value marked as nonnull
to null (i.e., NULL
, 0
or
nullptr
) is hence likely to cause a null-pointer dereference. Compilers may even apply optimizations based on this attribute and might,
for instance, remove an explicit null-check if the parameter is declared as nonnull
— even in code outside of the function with
the attribute.
Note that the nonnull
attribute is a GNU extension (see nonnull and returns_nonnull) which many
compiler vendors have implemented.
What is the potential impact?
In case a program dereferences a null pointer, it’s behavior is undefined. For programs that exercise undefined behavior the compiler no longer
needs to adhere to the language standard and the program has no meaning assigned to it.
In practice, dereferencing a null pointer may lead to program crashes, or the application may appear to execute correctly while losing data or
producing incorrect results.
Besides affecting the application’s availability, null-pointer dereferences may lead to malicious code execution, in rare circumstances. If null is
equivalent to the 0x0 memory address that can be accessed by privileged code, writing, and reading memory is possible, which compromises the integrity
and confidentiality of the application.
Because compilers may apply optimizations based on the nonnull
attribute, not respecting nonnull
can also introduce more
complex bugs such as resource leaks or infinite loops as indicated in the following code snippet:
struct Node {
int data;
Node *next; // NULL for a tail node.
};
size_t len(__attribute__((nonnull)) Node *n) {
size_t l = 0;
while (n) {
++l;
n = n->next;
}
return l;
}
How to fix it
Ensure not to pass null values when non-null arguments are expected, do not return a null value when a non-null return value is expected, and do
not assign null to parameters marked as non-null. This especially holds for library functions, which frequently require nonnull
pointer
parameters.
On other occasions, it might be more appropriate to remove the attribute.
Code examples
Noncompliant code example
__attribute__((returns_nonnull))
int* foo(__attribute__((nonnull)) int* x) {
*x = 42;
return x;
}
void bar() {
int *p = nullptr;
int *q = foo(p); // Noncompliant: null value is passed as an argument marked "nonnull"
}
Compliant solution
__attribute__((returns_nonnull))
int* foo(__attribute__((nonnull)) int* x) {
*x = 42;
return x;
}
void bar() {
int i = 0;
int *p = &i;
int *q = foo(p); // Compliant: `p` points to a valid memory location
}
Noncompliant code example
__attribute__((returns_nonnull))
int* foo() {
return nullptr; // Noncompliant: function may not return a null pointer
}
Compliant solution
__attribute__((returns_nonnull))
int* foo() {
int *p = new int(0);
return p; // Compliant: `p` points to a valid memory location
}
Noncompliant code example
void process(int *p);
void foo(__attribute__((nonnull)) int *p) {
p = nullptr; // Noncompliant: `p` is marked "nonnull" but is set to null
process(p);
}
Compliant solution
void process(int *p);
void foo(__attribute__((nonnull)) int *p) {
process(p);
}
Going the extra mile
In C++, it is preferred to use reference parameters (Type const&
or Type&
), or pass objects by value
(Type
), instead of a pointer (Type const*
or Type*
), if the argument is expected to never be null.
// Precondition: graph != null
bool isDAG(Graph const* graph);
The preferred signature would be:
bool isDAG(Graph const& graph);
Resources
Standards
External coding guidelines
Related rules
- S2259 detects null-pointer dereferences
- S3807 detects calls to C library functions that require valid, non-null pointers with null pointer arguments