99 lines
4.5 KiB
ReStructuredText
99 lines
4.5 KiB
ReStructuredText
.. title:: clang-tidy - bugprone-multiple-new-in-one-expression
|
|
|
|
bugprone-multiple-new-in-one-expression
|
|
=======================================
|
|
|
|
Finds multiple ``new`` operator calls in a single expression, where the
|
|
allocated memory by the first ``new`` may leak if the second allocation fails
|
|
and throws exception.
|
|
|
|
C++ does often not specify the exact order of evaluation of the operands of an
|
|
operator or arguments of a function. Therefore if a first allocation succeeds
|
|
and a second fails, in an exception handler it is not possible to tell which
|
|
allocation has failed and free the memory. Even if the order is fixed the result
|
|
of a first ``new`` may be stored in a temporary location that is not reachable
|
|
at the time when a second allocation fails. It is best to avoid any expression
|
|
that contains more than one ``operator new`` call, if exception handling is
|
|
used to check for allocation errors.
|
|
|
|
Different rules apply for are the short-circuit operators ``||`` and ``&&`` and
|
|
the ``,`` operator, where evaluation of one side must be completed before the
|
|
other starts. Expressions of a list-initialization (initialization or
|
|
construction using ``{`` and ``}`` characters) are evaluated in fixed order.
|
|
Similarly, condition of a ``?`` operator is evaluated before the branches are
|
|
evaluated.
|
|
|
|
The check reports warning if two ``new`` calls appear in one expression at
|
|
different sides of an operator, or if ``new`` calls appear in different
|
|
arguments of a function call (that can be an object construction with ``()``
|
|
syntax). These ``new`` calls can be nested at any level.
|
|
For any warning to be emitted the ``new`` calls should be in a code block where
|
|
exception handling is used with catch for ``std::bad_alloc`` or
|
|
``std::exception``. At ``||``, ``&&``, ``,``, ``?`` (condition and one branch)
|
|
operators no warning is emitted. No warning is emitted if both of the memory
|
|
allocations are not assigned to a variable or not passed directly to a function.
|
|
The reason is that in this case the memory may be intentionally not freed or the
|
|
allocated objects can be self-destructing objects.
|
|
|
|
Examples:
|
|
|
|
.. code-block:: c++
|
|
|
|
struct A {
|
|
int Var;
|
|
};
|
|
struct B {
|
|
B();
|
|
B(A *);
|
|
int Var;
|
|
};
|
|
struct C {
|
|
int *X1;
|
|
int *X2;
|
|
};
|
|
|
|
void f(A *, B *);
|
|
int f1(A *);
|
|
int f1(B *);
|
|
bool f2(A *);
|
|
|
|
void foo() {
|
|
A *PtrA;
|
|
B *PtrB;
|
|
try {
|
|
// Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated.
|
|
f(new A, new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined
|
|
|
|
// List (aggregate) initialization is used.
|
|
C C1{new int, new int}; // no warning
|
|
|
|
// Allocation of 'B'/'A' may fail after memory for 'A'/'B' was allocated but not yet passed to function 'f1'.
|
|
int X = f1(new A) + f1(new B); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined
|
|
|
|
// Allocation of 'B' may fail after memory for 'A' was allocated.
|
|
// From C++17 on memory for 'B' is allocated first but still may leak if allocation of 'A' fails.
|
|
PtrB = new B(new A); // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception
|
|
|
|
// 'new A' and 'new B' may be performed in any order.
|
|
// 'new B'/'new A' may fail after memory for 'A'/'B' was allocated but not assigned to 'PtrA'/'PtrB'.
|
|
(PtrA = new A)->Var = (PtrB = new B)->Var; // warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined
|
|
|
|
// Evaluation of 'f2(new A)' must be finished before 'f1(new B)' starts.
|
|
// If 'new B' fails the allocated memory for 'A' is supposedly handled correctly because function 'f2' could take the ownership.
|
|
bool Z = f2(new A) || f1(new B); // no warning
|
|
|
|
X = (f2(new A) ? f1(new A) : f1(new B)); // no warning
|
|
|
|
// No warning if the result of both allocations is not passed to a function
|
|
// or stored in a variable.
|
|
(new A)->Var = (new B)->Var; // no warning
|
|
|
|
// No warning if at least one non-throwing allocation is used.
|
|
f(new(std::nothrow) A, new B); // no warning
|
|
} catch(std::bad_alloc) {
|
|
}
|
|
|
|
// No warning if the allocation is outside a try block (or no catch handler exists for std::bad_alloc).
|
|
// (The fact if exceptions can escape from 'foo' is not taken into account.)
|
|
f(new A, new B); // no warning
|
|
}
|