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:
Jonas Jensen
2018-11-22 11:31:19 +01:00
parent a17debac3e
commit 75873bb4a6
8 changed files with 58 additions and 6 deletions

View File

@@ -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()))
}
/**

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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 | |

View 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 |

View File

@@ -0,0 +1,4 @@
import cpp
from NewOrNewArrayExpr new
select new, new.getPlacementPointer() as placement

View File

@@ -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 |

View File

@@ -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