Skip to content

Commit c406936

Browse files
authored
Merge pull request github#5804 from MathiasVP/improve-detect-and-handle-memory-allocation-errors
C++: Improve qhelp and tests for cpp/detect-and-handle-memory-allocation-errors
2 parents 404a6c1 + c67ab8f commit c406936

File tree

5 files changed

+172
-117
lines changed

5 files changed

+172
-117
lines changed
Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,43 @@
1-
// BAD: on memory allocation error, the program terminates.
2-
void badFunction(const int *source, std::size_t length) noexcept {
3-
int * dest = new int[length];
1+
// BAD: the allocation will throw an unhandled exception
2+
// instead of returning a null pointer.
3+
void bad1(std::size_t length) noexcept {
4+
int* dest = new int[length];
5+
if(!dest) {
6+
return;
7+
}
48
std::memset(dest, 0, length);
5-
// ..
9+
// ...
610
}
7-
// GOOD: memory allocation error will be handled.
8-
void goodFunction(const int *source, std::size_t length) noexcept {
11+
12+
// BAD: the allocation won't throw an exception, but
13+
// instead return a null pointer.
14+
void bad2(std::size_t length) noexcept {
915
try {
10-
int * dest = new int[length];
11-
} catch(std::bad_alloc) {
16+
int* dest = new(std::nothrow) int[length];
17+
std::memset(dest, 0, length);
18+
// ...
19+
} catch(std::bad_alloc&) {
1220
// ...
1321
}
14-
std::memset(dest, 0, length);
15-
// ..
1622
}
17-
// BAD: memory allocation error will not be handled.
18-
void badFunction(const int *source, std::size_t length) noexcept {
23+
24+
// GOOD: the allocation failure is handled appropiately.
25+
void good1(std::size_t length) noexcept {
1926
try {
20-
int * dest = new (std::nothrow) int[length];
21-
} catch(std::bad_alloc) {
27+
int* dest = new int[length];
28+
std::memset(dest, 0, length);
29+
// ...
30+
} catch(std::bad_alloc&) {
2231
// ...
2332
}
24-
std::memset(dest, 0, length);
25-
// ..
2633
}
27-
// GOOD: memory allocation error will be handled.
28-
void goodFunction(const int *source, std::size_t length) noexcept {
29-
int * dest = new (std::nothrow) int[length];
30-
if (!dest) {
31-
return;
34+
35+
// GOOD: the allocation failure is handled appropiately.
36+
void good2(std::size_t length) noexcept {
37+
int* dest = new int[length];
38+
if(!dest) {
39+
return;
3240
}
3341
std::memset(dest, 0, length);
34-
// ..
42+
// ...
3543
}

cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
"qhelp.dtd">
44
<qhelp>
55
<overview>
6-
<p>When using the <code>new</code> operator to allocate memory, you need to pay attention to the different ways of detecting errors. <code>::operator new(std::size_t)</code> throws an exception on error, whereas <code>::operator new(std::size_t, const std::nothrow_t &amp;)</code> returns zero on error. The programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.</p>
6+
<p>Different overloads of the <code>new</code> operator handle allocation failures in different ways.
7+
If <code>new T</code> fails for some type <code>T</code>, it throws a <code>std::bad_alloc</code> exception,
8+
but <code>new(std::nothrow) T</code> returns a null pointer. If the programmer does not use the corresponding
9+
method of error handling, allocation failure may go unhandled and could cause the program to behave in
10+
unexpected ways.</p>
711

812
</overview>
913
<recommendation>
1014

11-
<p>Use the correct error detection method corresponding with the memory allocation.</p>
15+
<p>Make sure that exceptions are handled appropriately if <code>new T</code> is used. On the other hand,
16+
make sure to handle the possibility of null pointers if <code>new(std::nothrow) T</code> is used.</p>
1217

1318
</recommendation>
1419
<example>
15-
<p>The following example demonstrates various approaches to detecting memory allocation errors using the <code>new</code> operator.</p>
1620
<sample src="WrongInDetectingAndHandlingMemoryAllocationErrors.cpp" />
1721

1822
</example>

cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/**
22
* @name Detect And Handle Memory Allocation Errors
3-
* @description --::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error.
4-
* --the programmer can get confused when check the error that occurs when allocating memory incorrectly.
3+
* @description `operator new` throws an exception on allocation failures, while `operator new(std::nothrow)` returns a null pointer. Mixing up these two failure conditions can result in unexpected behavior.
54
* @kind problem
65
* @id cpp/detect-and-handle-memory-allocation-errors
76
* @problem.severity warning
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
| test.cpp:30:15:30:26 | call to operator new[] | memory allocation error check is incorrect or missing |
2-
| test.cpp:38:9:38:20 | call to operator new[] | memory allocation error check is incorrect or missing |
3-
| test.cpp:50:13:50:38 | call to operator new[] | memory allocation error check is incorrect or missing |
4-
| test.cpp:51:22:51:47 | call to operator new[] | memory allocation error check is incorrect or missing |
5-
| test.cpp:53:18:53:43 | call to operator new[] | memory allocation error check is incorrect or missing |
1+
| test.cpp:29:13:29:24 | call to operator new[] | memory allocation error check is incorrect or missing |
2+
| test.cpp:37:13:37:24 | call to operator new[] | memory allocation error check is incorrect or missing |
3+
| test.cpp:41:13:41:24 | call to operator new[] | memory allocation error check is incorrect or missing |
4+
| test.cpp:49:8:49:19 | call to operator new[] | memory allocation error check is incorrect or missing |
5+
| test.cpp:58:8:58:19 | call to operator new[] | memory allocation error check is incorrect or missing |
6+
| test.cpp:63:8:63:19 | call to operator new[] | memory allocation error check is incorrect or missing |
7+
| test.cpp:92:5:92:31 | call to operator new[] | memory allocation error check is incorrect or missing |
8+
| test.cpp:93:15:93:41 | call to operator new[] | memory allocation error check is incorrect or missing |
9+
| test.cpp:96:10:96:36 | call to operator new[] | memory allocation error check is incorrect or missing |
Lines changed: 125 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,137 @@
1-
#define NULL ((void*)0)
1+
#define NULL ((void *)0)
2+
3+
namespace std {
4+
struct nothrow_t {};
5+
typedef unsigned long size_t;
6+
27
class exception {};
8+
class bad_alloc : public exception {};
39

4-
namespace std{
5-
struct nothrow_t {};
6-
typedef unsigned long size_t;
7-
class bad_alloc{
8-
const char* what() const throw();
9-
};
10-
extern const std::nothrow_t nothrow;
11-
}
10+
extern const std::nothrow_t nothrow;
11+
} // namespace std
1212

1313
using namespace std;
1414

15-
void* operator new(std::size_t _Size);
16-
void* operator new[](std::size_t _Size);
17-
void* operator new( std::size_t count, const std::nothrow_t& tag ) noexcept;
18-
void* operator new[]( std::size_t count, const std::nothrow_t& tag ) noexcept;
19-
20-
void badNew_0_0()
21-
{
22-
while (true) {
23-
new int[100]; // BAD [NOT DETECTED]
24-
if(!(new int[100])) // BAD [NOT DETECTED]
25-
return;
26-
}
27-
}
28-
void badNew_0_1()
29-
{
30-
int * i = new int[100]; // BAD
31-
if(i == 0)
32-
return;
33-
if(!i)
34-
return;
35-
if(i == NULL)
36-
return;
37-
int * j;
38-
j = new int[100]; // BAD
39-
if(j == 0)
40-
return;
41-
if(!j)
42-
return;
43-
if(j == NULL)
44-
return;
15+
void *operator new(std::size_t);
16+
void *operator new[](std::size_t);
17+
void *operator new(std::size_t, const std::nothrow_t &) noexcept;
18+
void *operator new[](std::size_t, const std::nothrow_t &) noexcept;
19+
20+
void bad_new_in_condition() {
21+
if (!(new int)) { // BAD [NOT DETECTED]
22+
return;
23+
}
4524
}
46-
void badNew_1_0()
47-
{
48-
try {
49-
while (true) {
50-
new(std::nothrow) int[100]; // BAD
51-
int* p = new(std::nothrow) int[100]; // BAD
52-
int* p1;
53-
p1 = new(std::nothrow) int[100]; // BAD
54-
}
55-
} catch (const exception &){//const std::bad_alloc& e) {
56-
// std::cout << e.what() << '\n';
57-
}
25+
26+
void foo(int**);
27+
28+
void bad_new_missing_exception_handling() {
29+
int *p1 = new int[100]; // BAD
30+
if (p1 == 0)
31+
return;
32+
33+
int *p2 = new int[100]; // BAD [NOT DETECTED]
34+
if (!p2)
35+
return;
36+
37+
int *p3 = new int[100]; // BAD
38+
if (p3 == NULL)
39+
return;
40+
41+
int *p4 = new int[100]; // BAD
42+
if (p4 == nullptr)
43+
return;
44+
45+
int *p5 = new int[100]; // BAD [NOT DETECTED]
46+
if (p5) {} else return;
47+
48+
int *p6;
49+
p6 = new int[100]; // BAD
50+
if (p6 == 0) return;
51+
52+
int *p7;
53+
p7 = new int[100]; // BAD [NOT DETECTED]
54+
if (!p7)
55+
return;
56+
57+
int *p8;
58+
p8 = new int[100]; // BAD
59+
if (p8 == NULL)
60+
return;
61+
62+
int *p9;
63+
p9 = new int[100]; // BAD
64+
if (p9 != nullptr) {
65+
} else
66+
return;
67+
68+
int *p10;
69+
p10 = new int[100]; // BAD [NOT DETECTED]
70+
if (p10 != 0) {
71+
}
72+
73+
int *p11;
74+
do {
75+
p11 = new int[100]; // BAD [NOT DETECTED]
76+
} while (!p11);
77+
78+
int* p12 = new int[100];
79+
foo(&p12);
80+
if(p12) {} else return; // GOOD: p12 is probably modified in foo, so it's
81+
// not the return value of the new that's checked.
82+
83+
int* p13 = new int[100];
84+
foo(&p13);
85+
if(!p13) {
86+
return;
87+
} else { }; // GOOD: same as above.
5888
}
59-
void badNew_1_1()
60-
{
61-
while (true) {
62-
int* p = new(std::nothrow) int[100]; // BAD [NOT DETECTED]
63-
new(std::nothrow) int[100]; // BAD [NOT DETECTED]
64-
}
89+
90+
void bad_new_nothrow_in_exception_body() {
91+
try {
92+
new (std::nothrow) int[100]; // BAD
93+
int *p1 = new (std::nothrow) int[100]; // BAD
94+
95+
int *p2;
96+
p2 = new (std::nothrow) int[100]; // BAD
97+
} catch (const std::bad_alloc &) {
98+
}
6599
}
66100

67-
void goodNew_0_0()
68-
{
69-
try {
70-
while (true) {
71-
new int[100]; // GOOD
72-
}
73-
} catch (const exception &){//const std::bad_alloc& e) {
74-
// std::cout << e.what() << '\n';
75-
}
101+
void good_new_has_exception_handling() {
102+
try {
103+
int *p1 = new int[100]; // GOOD
104+
} catch (...) {
105+
}
76106
}
77107

78-
void goodNew_1_0()
79-
{
80-
while (true) {
81-
int* p = new(std::nothrow) int[100]; // GOOD
82-
if (p == nullptr) {
83-
// std::cout << "Allocation returned nullptr\n";
84-
break;
85-
}
86-
int* p1;
87-
p1 = new(std::nothrow) int[100]; // GOOD
88-
if (p1 == nullptr) {
89-
// std::cout << "Allocation returned nullptr\n";
90-
break;
91-
}
92-
if (new(std::nothrow) int[100] == nullptr) { // GOOD
93-
// std::cout << "Allocation returned nullptr\n";
94-
break;
95-
}
96-
}
108+
void good_new_handles_nullptr() {
109+
int *p1 = new (std::nothrow) int[100]; // GOOD
110+
if (p1 == nullptr)
111+
return;
112+
113+
int *p2;
114+
p2 = new (std::nothrow) int[100]; // GOOD
115+
if (p2 == nullptr)
116+
return;
117+
118+
int *p3;
119+
p3 = new (std::nothrow) int[100]; // GOOD
120+
if (p3 != nullptr) {
121+
}
122+
123+
int *p4;
124+
p4 = new (std::nothrow) int[100]; // GOOD
125+
if (p4) {
126+
} else
127+
return;
128+
129+
int *p5;
130+
p5 = new (std::nothrow) int[100]; // GOOD
131+
if (p5 != nullptr) {
132+
} else
133+
return;
134+
135+
if (new (std::nothrow) int[100] == nullptr)
136+
return; // GOOD
97137
}

0 commit comments

Comments
 (0)