diff --git a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp --- a/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -36,6 +36,7 @@ #include "MisplacedPointerArithmeticInAllocCheck.h" #include "MisplacedWideningCastCheck.h" #include "MoveForwardingReferenceCheck.h" +#include "MultipleNewInOneExpressionCheck.h" #include "MultipleStatementMacroCheck.h" #include "NoEscapeCheck.h" #include "NonZeroEnumToBoolConversionCheck.h" @@ -133,6 +134,8 @@ "bugprone-misplaced-widening-cast"); CheckFactories.registerCheck( "bugprone-move-forwarding-reference"); + CheckFactories.registerCheck( + "bugprone-multiple-new-in-one-expression"); CheckFactories.registerCheck( "bugprone-multiple-statement-macro"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -31,6 +31,7 @@ MisplacedPointerArithmeticInAllocCheck.cpp MisplacedWideningCastCheck.cpp MoveForwardingReferenceCheck.cpp + MultipleNewInOneExpressionCheck.cpp MultipleStatementMacroCheck.cpp NoEscapeCheck.cpp NonZeroEnumToBoolConversionCheck.cpp diff --git a/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.h b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.h @@ -0,0 +1,35 @@ +//===--- MultipleNewInOneExpressionCheck.h - clang-tidy----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MULTIPLENEWINONEEXPRESSIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MULTIPLENEWINONEEXPRESSIONCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace bugprone { + +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/bugprone/multiple-new-in-one-expression.html +class MultipleNewInOneExpressionCheck : public ClangTidyCheck { +public: + MultipleNewInOneExpressionCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace bugprone +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_MULTIPLENEWINONEEXPRESSIONCHECK_H diff --git a/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/MultipleNewInOneExpressionCheck.cpp @@ -0,0 +1,162 @@ +//===--- MultipleNewInOneExpressionCheck.cpp - clang-tidy------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "MultipleNewInOneExpressionCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; +using namespace clang; + +namespace { + +// Determine if the result of an expression is "stored" in some way. +// It is true if the value is stored into a variable or used as initialization +// or passed to a function or constructor. +// For this use case compound assignments are not counted as a "store" (the 'E' +// expression should have pointer type). +bool isExprValueStored(const Expr *E, ASTContext &C) { + E = E->IgnoreParenCasts(); + // Get first non-paren, non-cast parent. + ParentMapContext &PMap = C.getParentMapContext(); + DynTypedNodeList P = PMap.getParents(*E); + if (P.size() != 1) + return false; + const Expr *ParentE; + while ((ParentE = P[0].get()) && ParentE->IgnoreParenCasts() == E) { + P = PMap.getParents(P[0]); + if (P.size() != 1) + return false; + } + + if (const auto *ParentVarD = P[0].get()) + return ParentVarD->getInit()->IgnoreParenCasts() == E; + + if (!ParentE) + return false; + + if (const auto *BinOp = dyn_cast(ParentE)) + return BinOp->getOpcode() == BO_Assign && + BinOp->getRHS()->IgnoreParenCasts() == E; + + return isa(ParentE); +} + +} // namespace + +namespace clang { +namespace tidy { +namespace bugprone { + +AST_MATCHER_P(CXXTryStmt, hasHandlerFor, + ast_matchers::internal::Matcher, InnerMatcher) { + for (unsigned NH = Node.getNumHandlers(), I = 0; I < NH; ++I) { + const CXXCatchStmt *CatchS = Node.getHandler(I); + // Check for generic catch handler (match anything). + if (CatchS->getCaughtType().isNull()) + return true; + ast_matchers::internal::BoundNodesTreeBuilder Result(*Builder); + if (InnerMatcher.matches(CatchS->getCaughtType(), Finder, &Result)) { + *Builder = std::move(Result); + return true; + } + } + return false; +} + +AST_MATCHER(CXXNewExpr, mayThrow) { + FunctionDecl *OperatorNew = Node.getOperatorNew(); + if (!OperatorNew) + return false; + return !OperatorNew->getType()->castAs()->isNothrow(); +} + +void MultipleNewInOneExpressionCheck::registerMatchers(MatchFinder *Finder) { + auto BadAllocType = + recordType(hasDeclaration(cxxRecordDecl(hasName("::std::bad_alloc")))); + auto ExceptionType = + recordType(hasDeclaration(cxxRecordDecl(hasName("::std::exception")))); + auto BadAllocReferenceType = referenceType(pointee(BadAllocType)); + auto ExceptionReferenceType = referenceType(pointee(ExceptionType)); + + auto CatchBadAllocType = + qualType(hasCanonicalType(anyOf(BadAllocType, BadAllocReferenceType, + ExceptionType, ExceptionReferenceType))); + auto BadAllocCatchingTryBlock = cxxTryStmt(hasHandlerFor(CatchBadAllocType)); + + auto NewExprMayThrow = cxxNewExpr(mayThrow()); + auto HasNewExpr1 = expr(anyOf(NewExprMayThrow.bind("new1"), + hasDescendant(NewExprMayThrow.bind("new1")))); + auto HasNewExpr2 = expr(anyOf(NewExprMayThrow.bind("new2"), + hasDescendant(NewExprMayThrow.bind("new2")))); + + Finder->addMatcher( + callExpr( + hasAnyArgument( + expr(HasNewExpr1).bind("arg1")), + hasAnyArgument( + expr(HasNewExpr2, unless(equalsBoundNode("arg1"))).bind("arg2")), + hasAncestor(BadAllocCatchingTryBlock)), + this); + Finder->addMatcher( + cxxConstructExpr( + hasAnyArgument( + expr(HasNewExpr1).bind("arg1")), + hasAnyArgument( + expr(HasNewExpr2, unless(equalsBoundNode("arg1"))).bind("arg2")), + unless(isListInitialization()), + hasAncestor(BadAllocCatchingTryBlock)), + this); + Finder->addMatcher(binaryOperator(hasLHS(HasNewExpr1), hasRHS(HasNewExpr2), + unless(hasAnyOperatorName("&&", "||", ",")), + hasAncestor(BadAllocCatchingTryBlock)), + this); + Finder->addMatcher( + cxxNewExpr(mayThrow(), + hasDescendant(NewExprMayThrow.bind("new2_in_new1")), + hasAncestor(BadAllocCatchingTryBlock)) + .bind("new1"), + this); +} + +void MultipleNewInOneExpressionCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *NewExpr1 = Result.Nodes.getNodeAs("new1"); + const auto *NewExpr2 = Result.Nodes.getNodeAs("new2"); + const auto *NewExpr2InNewExpr1 = + Result.Nodes.getNodeAs("new2_in_new1"); + if (!NewExpr2) + NewExpr2 = NewExpr2InNewExpr1; + assert(NewExpr1 && NewExpr2 && "Bound nodes not found."); + + // No warning if both allocations are not stored. + // The value may be intentionally not stored (no deallocations needed or + // self-destructing object). + if (!isExprValueStored(NewExpr1, *Result.Context) && + !isExprValueStored(NewExpr2, *Result.Context)) + return; + + // In C++17 sequencing of a 'new' inside constructor arguments of another + // 'new' is fixed. Still a leak can happen if the returned value from the + // first 'new' is not saved (yet) and the second fails. + if (getLangOpts().CPlusPlus17 && NewExpr2InNewExpr1) + diag(NewExpr1->getBeginLoc(), + "memory allocation may leak if an other allocation is sequenced after " + "it and throws an exception") + << NewExpr1->getSourceRange() << NewExpr2->getSourceRange(); + else + diag(NewExpr1->getBeginLoc(), + "memory allocation may leak if an other allocation is sequenced after " + "it and throws an exception; order of these allocations is undefined") + << NewExpr1->getSourceRange() << NewExpr2->getSourceRange(); +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -106,6 +106,12 @@ New checks ^^^^^^^^^^ +- New :doc:`bugprone-multiple-new-in-one-expression + ` check. + + 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. + - New :doc:`bugprone-non-zero-enum-to-bool-conversion ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone/multiple-new-in-one-expression.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/multiple-new-in-one-expression.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/multiple-new-in-one-expression.rst @@ -0,0 +1,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 + } diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -102,6 +102,7 @@ `bugprone-misplaced-pointer-arithmetic-in-alloc `_, "Yes" `bugprone-misplaced-widening-cast `_, `bugprone-move-forwarding-reference `_, "Yes" + `bugprone-multiple-new-in-one-expression `_, `bugprone-multiple-statement-macro `_, `bugprone-no-escape `_, `bugprone-non-zero-enum-to-bool-conversion `_, diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/multiple-new-in-one-expression.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/multiple-new-in-one-expression.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/multiple-new-in-one-expression.cpp @@ -0,0 +1,197 @@ +// RUN: %check_clang_tidy -std=c++11 -check-suffixes=ALL,CPP11 %s bugprone-multiple-new-in-one-expression %t +// RUN: %check_clang_tidy -std=c++17 -check-suffixes=ALL,CPP17 %s bugprone-multiple-new-in-one-expression %t + +namespace std { +typedef __typeof__(sizeof(0)) size_t; +enum class align_val_t : std::size_t {}; +class exception {}; +class bad_alloc : public exception {}; +struct nothrow_t {}; +extern const nothrow_t nothrow; +} // namespace std + +void *operator new(std::size_t, const std::nothrow_t &) noexcept; +void *operator new(std::size_t, std::align_val_t, const std::nothrow_t &) noexcept; +void *operator new(std::size_t, void *) noexcept; +void *operator new(std::size_t, char); + +struct B; + +struct A { int VarI; int *PtrI; B *PtrB; }; + +struct B { int VarI; }; + +struct G { + G(A*, B*) {} + int operator+=(A *) { return 3; }; +}; + +struct H { + int *a; + int *b; +}; + +int f(int); +int f(A*); +int f(A*, B*); +int f(int, B*); +int f(G, G); +int f(B*); +int f(const H &); +void f1(void *, void *); +A *g(A *); + +G operator+(const G&, const G&); + +void test_function_parameter(A *XA, B *XB) { + (void)f(new A, new B); + try { + (void)f(new A, new B); + } + catch (A) {}; + try { + (void)f(new A, new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:13: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; order of these allocations is undefined [ + (void)f(f(new A, new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + int X = f(new A, new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + X = f(new A, new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:11: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + X = 1 + f(new A, new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)f(g(new A), new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)f(1 + f(new A), new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:19: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (void)f(XA = new A, new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:18: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (void)f(1 + f(new A), XB = new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:19: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + } + catch (std::exception) {} +} + +void test_operator(G *G1) { + (void)(f(new A) + f(new B)); + try { + (void)(f(new A) + f(new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:14: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (void)f(f(new A) + f(new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + int X = f(new A) + f(new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + X = f(new A) + f(new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:11: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + X = 1 + f(new A) + 1 + f(new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:15: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)(f(g(new A)) + f(new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:16: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)(f(1 + f(new A)) + f(new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:20: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (void)(f(1 + f(new A)) + f(1 + f(new B))); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:20: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)((new A)->VarI + (new A)->VarI); + + (void)(f(new A) + ((*G1) += new A)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:14: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + } + catch (std::bad_alloc) {} +} + +void test_construct() { + (void)(G(new A, new B)); + try { + (void)(G(new A, new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:14: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (void)(G(new A, nullptr) + G(nullptr, new B)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:14: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + f(G(new A, nullptr), G(new A, nullptr)); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:9: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)new G(new A, nullptr); + // CHECK-MESSAGES-CPP11: :[[@LINE-1]]:11: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + // CHECK-MESSAGES-CPP17: :[[@LINE-2]]:11: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception [ + (void)new G(nullptr, (new A)->PtrB); + G *Z = new G(new A, nullptr); + // CHECK-MESSAGES-CPP11: :[[@LINE-1]]:12: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + // CHECK-MESSAGES-CPP17: :[[@LINE-2]]:12: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception [ + Z = new G(g(new A), nullptr); + // CHECK-MESSAGES-CPP11: :[[@LINE-1]]:9: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + // CHECK-MESSAGES-CPP17: :[[@LINE-2]]:9: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception [ + G *Z1, *Z2 = new G(nullptr, (new A)->PtrB), *Z3; + // CHECK-MESSAGES-CPP11: :[[@LINE-1]]:18: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + // CHECK-MESSAGES-CPP17: :[[@LINE-2]]:18: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception [ + } + catch (const std::bad_alloc &) {} +} + +void test_new_assign() { + A *X, *Y; + (X = new A)->VarI = (Y = new A)->VarI; + try { + (X = new A)->VarI = (Y = new A)->VarI; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:10: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (new A)->VarI = (Y = new A)->VarI; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (X = new A)->VarI = (new A)->VarI; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:10: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (new A)->VarI = (new A)->VarI; + (new A)->PtrI = new int; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:6: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + (X = new A)->VarI += (new A)->VarI; + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:10: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + } + catch (...) {} +} + +void test_operator_fixed_order(unsigned int L) { + (void)(f((f(new A) || f(0)) + f(new B[L]))); + try { + (void)(f(new A) || f(new B)); + (void)(f(new A) && f(new B)); + (void)(f(new A) || f(new B) || f(new A)); + + (void)(f(new A), f(new B)); + + int Y = f(0, new B) ? f(new A) : f(new B); + Y = f(new A) ? 1 : f(new B); + Y = f(new A) ? f(new B) : 1; + + G g{new A, new B}; + H h{new int, new int}; + f({new int, new int}); + (void)f({new A, new B}, {nullptr, nullptr}); + (void)f({new A, new B}, {new A, nullptr}); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:14: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + + (void)(f((f(new A) || f(0)) + f(new B[L]))); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:17: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + } + catch (std::bad_alloc) {} +} + +void test_cast() { + try { + f1(static_cast(new A), new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:28: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + } + catch (std::bad_alloc &) {} +} + +void test_nothrow(void *P) { + try { + (void)f(new(std::nothrow) A, new B); + (void)f(new A, new(std::nothrow) B); + (void)f(new(static_cast(8), std::nothrow) A, new B); + (void)f(new(P) A, new B); + (void)f(new('a') A, new B); + // CHECK-MESSAGES-ALL: :[[@LINE-1]]:13: warning: memory allocation may leak if an other allocation is sequenced after it and throws an exception; + } + catch (std::exception) {} +}