rspec/rules/S3590/cfamily/rule.adoc
Fred Tingaud d3cfe19d7e
Fix broken or dangerous backquotes
Co-authored-by: Marco Borgeaud <89914223+marco-antognini-sonarsource@users.noreply.github.com>
2023-10-30 10:33:56 +01:00

287 lines
7.2 KiB
Plaintext

Explicitly releasing non-heap memory leads to undefined behavior.
== Why is this an issue?
The `free` function and `delete` operator are used exclusively to release dynamically allocated memory.
Attempting to release any other type of memory is _undefined behavior_.
The following non-heap memory types may not be released:
* Stack allocated memory - local variables or memory allocated with the `alloca`, ``++_alloca++``, ``++_malloca++`` and ``++__builtin_alloca++`` functions.
* Executable program code - function pointers.
* Program data - global and static variables.
* Read-only program data - constants and strings.
=== What is the potential impact
Trying to release non-heap memory using `free` or `delete` results in undefined behavior.
When a program comprises undefined behavior, the compiler no longer needs to adhere to the language standard, and the program has no meaning assigned to it.
The application will usually just crash, but in the worst case, the application may appear to execute correctly, while losing data or producing incorrect results.
== How to fix it
Remove any calls to `free` or `delete` that aim at releasing non-heap memory.
=== Code examples
Stack allocated memory:
==== Noncompliant code example
[source,cpp,diff-type=noncompliant,diff-id=1]
----
void fun(int size) {
int number = 0;
char* name = (char*)alloca(size);
// ...
delete &number; // Noncompliant: memory is stack-allocated.
free(name); // Noncompliant: memory is stack-allocated.
}
----
==== Compliant solution
[source,cpp,diff-type=compliant,diff-id=1]
----
void fun(int size) {
int number = 0;
char* name = (char*)alloca(size);
// ...
// Compliant: stack memory is automatically released at the end of the function.
}
----
Executable program code:
==== Noncompliant code example
[source,cpp,diff-type=noncompliant,diff-id=2]
----
int getValue() {
return 10;
}
int main() {
// ...
free((void*) &getValue); // Noncompliant: memory is part of executable code.
return 0;
}
----
==== Compliant solution
[source,cpp,diff-type=compliant,diff-id=2]
----
int getValue() {
return 10;
}
int main() {
// ...
// Compliant: program code will be released at the end of the program's execution.
return 0;
}
----
Program data:
==== Noncompliant code example
[source,cpp,diff-type=noncompliant,diff-id=3]
----
struct S {
static inline int data = 64;
};
int main() {
static int x = 8;
// ...
free(&x); // Noncompliant: memory part of the program's data.
free(&S::data); // Noncompliant: memory part of the program's data.
return 0;
}
----
==== Compliant solution
[source,cpp,diff-type=compliant,diff-id=3]
----
struct S {
static inline int data = 64;
};
int main() {
static int x = 8;
// ...
// Compliant: globals and static variables are released at the end of the program's execution.
return 0;
}
----
Read-only program data:
==== Noncompliant code example
[source,cpp,diff-type=noncompliant,diff-id=4]
----
int const limit = 128;
int main() {
char* name = "string";
// ...
free((void*)&limit); // Noncompliant: memory part of program's read-only data.
free(name); // Noncompliant: memory part of read-only program data.
return 0;
}
----
==== Compliant solution
[source,cpp,diff-type=compliant,diff-id=4]
----
int const limit = 128;
int main() {
char const* name = "string";
// ...
// Compliant: read-only program data is freed at the end of the program's execution.
return 0;
}
----
=== Going the extra mile
The accidental release of non-heap memory usually occurs in practice if the same pointer variable is used to once reference heap and once non-heap memory.
This may lead to confusion and should be avoided.
These best practices help to avoid accidentally releasing non-heap memory:
* If accessing different memory types, use different pointer variables.
* When passing non-heap memory addresses to functions, ensure that the functions do not attempt to release the memory.
* If manually managing dynamic memory, release it in the same scope where it was acquired.
The following example shows a situation in which the same pointer variable is used to hold a stack or heap address.
This leads to a situation in which heap memory is accidentally released.
Noncompliant code example
[source,cpp,diff-type=noncompliant,diff-id=5]
----
void fun(int length) {
static char smallString[32];
char* usedString;
if (length < 31) {
usedString = smallString; // Pointer to stack memory assigned here
} else {
usedString = (char*)malloc(length + 1);
}
// ...
free(usedString); // Noncompliant: if length < 31, the freed memory will be located on the stack.
}
----
Compliant solution
[source,cpp,diff-type=compliant,diff-id=5]
----
void fun(int length) {
static char smallString[32];
char* stackOrHeapString;
char* heapString = nullptr;
if (length < 31) {
stackOrHeapString = smallString;
} else {
heapString = (char*)malloc(length + 1);
stackOrHeapString = heapString;
}
// ...
free(heapString); // Compliant: only the heap string will be freed if allocated.
}
----
The following example shows a situation in which dynamically allocated memory is acquired and released in different functions.
On top of this chain, a stack allocated buffer is introduced, leading to a call to `free` of stack memory.
Noncompliant code example
[source,cpp,diff-type=noncompliant,diff-id=6]
----
void use(char* string) {
// ...
free(string); // Noncompliant: pointer's origin is unknown. If non-heap, the program will crash.
}
void fun(int length) {
static char smallString[32];
char* usedString;
if (length < 31) {
usedString = smallString; // Pointer to stack memory assigned here
} else {
usedString = (char*)malloc(length + 1);
}
use(usedString); // If length < 31, the unsafe memory will free memory located on the stack.
}
----
Compliant solution
[source,cpp,diff-type=compliant,diff-id=6]
----
void use(char* string) {
// ...
// Compliant: memory no longer freed in the called function
}
void fun(int length) {
static char smallString[32];
if (length < 31) {
use(smallString);
} else {
heapString = (char*)malloc(length + 1);
use(heapString);
free(heapString); // Compliant: memory released in the scope it was acquired in.
}
}
----
ifdef::env-github,rspecator-view[]
'''
== Implementation Specification
(visible only on this page)
=== Message
Remove this "free" call; the memory will be released automatically.
=== Highlighting
* primary: ``++free(xxx)++``
* secondary: allocation
'''
== Comments And Links
(visible only on this page)
=== on 31 Mar 2016, 14:02:56 Ann Campbell wrote:
\[~massimo.paladin] what happens if you ``++free++`` this memory anyway? Crash? Memory corruption? Leak? The description should include at least a hint & I need to know to set the SQALE characteristic.
=== on 31 Mar 2016, 14:31:56 Massimo PALADIN wrote:
\[~ann.campbell.2] it is an undefined behavior, i.e. on my setup I am getting a crash.
=== on 31 Mar 2016, 16:23:09 Ann Campbell wrote:
Thanks [~massimo.paladin]. I've made some small updates.
=== on 27 Mar 2019, 16:51:29 Ann Campbell wrote:
FYI, [~massimo.paladin] the "raises an issue when" clause usually comes at the end of the descriptive text.
endif::env-github,rspecator-view[]