File: multiple-new-in-one-expression.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (99 lines) | stat: -rw-r--r-- 4,585 bytes parent folder | download | duplicates (12)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
.. 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
  }