mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
C++: Detect non-allocating placement new
This adds a `NewOrNewArrayExpr.getPlacementPointer` predicate and uses it in `Alloc.qll` to detect when a `new`-expression is not an allocation. User-defined replacements for `operator new` may not be allocations either, but the code continues to assume that they are. It's possible that we want to change this assumption in the future or leave it up to individual queries to decide on which side to err. It's hard to statically tell whether `operator new` has been overloaded in a particular file because it can be overloaded by a definition that is not in scope but is only linked together with that file.
This commit is contained in:
@@ -78,8 +78,8 @@ predicate isStdLibAllocationExpr(Expr e)
|
||||
*/
|
||||
predicate isAllocationExpr(Expr e) {
|
||||
allocationCall(e)
|
||||
or e instanceof NewExpr
|
||||
or e instanceof NewArrayExpr
|
||||
or
|
||||
e = any(NewOrNewArrayExpr new | not exists(new.getPlacementPointer()))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -664,6 +664,16 @@ class NewOrNewArrayExpr extends Expr, @any_new_expr {
|
||||
* For `new int[5]` the result is `int[5]`.
|
||||
*/
|
||||
abstract Type getAllocatedType();
|
||||
|
||||
/**
|
||||
* Gets the pointer `p` if this expression is of the form `new(p) T...`.
|
||||
* Invocations of this form are non-allocating `new` expressions that may
|
||||
* call the constructor of `T` but will not allocate memory.
|
||||
*/
|
||||
Expr getPlacementPointer() {
|
||||
isStandardPlacementNewAllocator(this.getAllocator()) and
|
||||
result = this.getAllocatorCall().getArgument(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -961,3 +971,9 @@ private predicate convparents(Expr child, int idx, Element parent) {
|
||||
child = astChild.getFullyConverted()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isStandardPlacementNewAllocator(Function operatorNew) {
|
||||
operatorNew.getName().matches("operator new%") and
|
||||
operatorNew.getNumberOfParameters() = 2 and
|
||||
operatorNew.getParameter(1).getType() instanceof VoidPointerType
|
||||
}
|
||||
|
||||
@@ -109,3 +109,31 @@ void TestFailedInit(int n) {
|
||||
new(1.0f) FailedInitOveraligned();
|
||||
new(1.0f) FailedInitOveraligned[10];
|
||||
}
|
||||
|
||||
// --- non-allocating placement new ---
|
||||
|
||||
namespace std {
|
||||
typedef unsigned long size_t;
|
||||
struct nothrow_t {};
|
||||
extern const nothrow_t nothrow;
|
||||
}
|
||||
|
||||
void* operator new (std::size_t size, void* ptr) noexcept;
|
||||
void* operator new[](std::size_t size, void* ptr) noexcept;
|
||||
void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
|
||||
void* operator new[](std::size_t size, const std::nothrow_t&) noexcept;
|
||||
|
||||
int overloadedNew() {
|
||||
char buf[sizeof(int)];
|
||||
|
||||
new(&buf[0]) int(5);
|
||||
int five = *(int*)buf;
|
||||
|
||||
new(buf) int[1];
|
||||
*(int*)buf = 4;
|
||||
|
||||
new(std::nothrow) int(3); // memory leak
|
||||
new(std::nothrow) int[2]; // memory leak
|
||||
|
||||
return five;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ newExprs
|
||||
| allocators.cpp:55:3:55:25 | new | Overaligned | operator new(size_t, align_val_t, float) -> void * | 256 | 128 | aligned |
|
||||
| allocators.cpp:107:3:107:18 | new | FailedInit | FailedInit::operator new(size_t) -> void * | 1 | 1 | |
|
||||
| allocators.cpp:109:3:109:35 | new | FailedInitOveraligned | FailedInitOveraligned::operator new(size_t, align_val_t, float) -> void * | 128 | 128 | aligned |
|
||||
| allocators.cpp:129:3:129:21 | new | int | operator new(size_t, void *) -> void * | 4 | 4 | |
|
||||
| allocators.cpp:135:3:135:26 | new | int | operator new(size_t, const nothrow_t &) -> void * | 4 | 4 | |
|
||||
newArrayExprs
|
||||
| allocators.cpp:68:3:68:12 | new[] | int | operator new[](unsigned long) -> void * | 4 | 4 | |
|
||||
| allocators.cpp:69:3:69:18 | new[] | int | operator new[](size_t, float) -> void * | 4 | 4 | |
|
||||
@@ -16,6 +18,8 @@ newArrayExprs
|
||||
| allocators.cpp:72:3:72:16 | new[] | String | operator new[](unsigned long) -> void * | 8 | 8 | |
|
||||
| allocators.cpp:108:3:108:19 | new[] | FailedInit | FailedInit::operator new[](size_t) -> void * | 1 | 1 | |
|
||||
| allocators.cpp:110:3:110:37 | new[] | FailedInitOveraligned | FailedInitOveraligned::operator new[](size_t, align_val_t, float) -> void * | 128 | 128 | aligned |
|
||||
| allocators.cpp:132:3:132:17 | new[] | int | operator new[](size_t, void *) -> void * | 4 | 4 | |
|
||||
| allocators.cpp:136:3:136:26 | new[] | int | operator new[](size_t, const nothrow_t &) -> void * | 4 | 4 | |
|
||||
newExprDeallocators
|
||||
| allocators.cpp:52:3:52:14 | new | String | operator delete(void *, unsigned long) -> void | 8 | 8 | sized |
|
||||
| allocators.cpp:53:3:53:27 | new | String | operator delete(void *, float) -> void | 8 | 8 | |
|
||||
|
||||
2
cpp/ql/test/library-tests/allocators/placement.expected
Normal file
2
cpp/ql/test/library-tests/allocators/placement.expected
Normal file
@@ -0,0 +1,2 @@
|
||||
| allocators.cpp:129:3:129:21 | new | allocators.cpp:129:7:129:13 | & ... |
|
||||
| allocators.cpp:132:3:132:17 | new[] | allocators.cpp:132:7:132:9 | buf |
|
||||
4
cpp/ql/test/library-tests/allocators/placement.ql
Normal file
4
cpp/ql/test/library-tests/allocators/placement.ql
Normal file
@@ -0,0 +1,4 @@
|
||||
import cpp
|
||||
|
||||
from NewOrNewArrayExpr new
|
||||
select new, new.getPlacementPointer() as placement
|
||||
@@ -8,7 +8,5 @@
|
||||
| test.cpp:42:18:42:23 | call to malloc | This memory is never freed |
|
||||
| test.cpp:73:18:73:23 | call to malloc | This memory is never freed |
|
||||
| test.cpp:89:18:89:23 | call to malloc | This memory is never freed |
|
||||
| test.cpp:150:3:150:21 | new | This memory is never freed |
|
||||
| test.cpp:153:3:153:17 | new[] | This memory is never freed |
|
||||
| test.cpp:156:3:156:26 | new | This memory is never freed |
|
||||
| test.cpp:157:3:157:26 | new[] | This memory is never freed |
|
||||
|
||||
@@ -147,10 +147,10 @@ void* operator new[](std::size_t size, const std::nothrow_t&) noexcept;
|
||||
int overloadedNew() {
|
||||
char buf[sizeof(int)];
|
||||
|
||||
new(&buf[0]) int(5); // GOOD [FALSE POSITIVE]
|
||||
new(&buf[0]) int(5); // GOOD
|
||||
int five = *(int*)buf;
|
||||
|
||||
new(buf) int[1]; // GOOD [FALSE POSITIVE]
|
||||
new(buf) int[1]; // GOOD
|
||||
*(int*)buf = 4;
|
||||
|
||||
new(std::nothrow) int(3); // BAD
|
||||
|
||||
Reference in New Issue
Block a user