llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: mark-zilber <details> <summary>Changes</summary> The checker finds placement new expressions that access the storage they are placed into through the old object while the new object is being constructed, e.g. `::new (p) T(*p)`. This is the `ctordtor.PlacementSelfCopy` entry of the [potential checkers list](https://clang-analyzer.llvm.org/potential_checkers.html). During construction, a value read through a glvalue that is not obtained from `this` is unspecified ([class.cdtor]/2). The checker marks the parameter that receives the old storage in the new-initializer and follows it through the variables it is stored in (also through `std::addressof`, `?:` and pointer arithmetic), into the functions it is passed to, the methods called on it and the lambdas that capture a pointer to it. It reports the first of these: - a read or a write of the target storage through it, or in the new-initializer itself; - passing it to a function, or calling its method, that is not inlined, except for calls that only return their argument such as `std::move` and `std::addressof`; - capturing it in a lambda, unless a pointer to it is captured. Taking, storing or shifting its address, accessing a member without reading it and calling an inlined method that reads nothing are allowed. Accesses to an object that only contains the target storage, such as a pool with an inline buffer, are not reported unless they overlap the target. The target is limited to the size of the placed type, so the rest of a larger buffer is not part of it. When placement new itself calls a copy or move constructor that is not inlined (a trivial copy, a constructor without a visible definition, or one that exceeded the analysis budget) and its source is in the target storage, it is reported as a self-copy. A report that would end inside the standard library, as for `std::construct_at`, is emitted at the call site. ```cpp struct Buffer { int *Data; int Size; Buffer(const Buffer &O) : Data(new int[O.Size]), // warn: placement new accesses the storage it // is placed into during construction of // the new object Size(O.Size) { for (int I = 0; I < Size; ++I) Data[I] = O.Data[I]; } }; void reconstruct(Buffer *B) { ::new (B) Buffer(*B); } ``` ### Testing - `clang/test/Analysis/placement-self-copy.cpp`: self-copies at the top level, in arrays, aggregates and `std::construct_at`; reads in the constructor and in the new-initializer; the source followed through pointers, locals, helpers, methods, lambdas, `?:`, pointer arithmetic and opaque calls such as `memcpy`; class templates and CRTP; base classes. It also covers cases that must stay silent: accesses through `this` and its aliases, adjacent objects, a constructor that ignores its source, storing the address of the source, members whose address is taken without a read, and a pool whose inline buffer holds the target. A second run checks a reserved placement `operator new` declared without a body. - All tests in `clang/test/Analysis` pass. - Real code, `-analyzer-checker=alpha.cplusplus.PlacementSelfCopy` on the translation units below: | Project | Revision | Translation units | Reports | | --- | --- | --- | --- | | abseil-cpp (without googletest) | `4cbb3d4` | 426 | 0 | | protobuf | `522b8d4` | 538 | 0 | | EASTL | `e323f67` | 126 | 0 | | ETL | `5fe4a6c` | 394 | 0 | | godot (without `thirdparty`) | `469659e` | 1516 | 0 | | LLVM (`llvm/lib/Support`, `llvm/lib/IR`, `llvm/lib/Analysis`, `llvm/lib/Transforms/Utils`, `llvm/unittests/ADT`, `llvm/unittests/Support`, `clang/lib/AST`, `clang/lib/Sema`) | `43b1971` | 920 | 0 | One abseil translation unit (`absl/log/log_format_test.cc`) hits an existing analyzer assertion in `createTemporaryRegionIfNeeded`. It crashes the same way with only the `core` checkers enabled. ### Limitations - The source is not followed through fields: storing its address in a member and reading through it is missed. - An old object that is larger than the new one cannot be told apart from an object that contains the target, so passing it whole to an opaque function is missed. - Init-captures (`[&R = O]`), user-defined placement allocation functions and pointers to the storage obtained before the placement new are not followed. - A function that is not inlined is assumed to access the old object passed to it, so one that ignores it is still reported, as is a lambda that captures the old object but is never called. - `B ? O : *this` is treated as the old object even when `this` is chosen. - Inside a lambda, a captured variable is looked up in the nearest frame of the function that defines the lambda, which may be the wrong one when that function is recursive. Part of #<!-- -->233519 --- Patch is 52.89 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/233525.diff 7 Files Affected: - (modified) clang/docs/ReleaseNotes.md (+6) - (modified) clang/docs/analyzer/checkers.md (+77) - (modified) clang/include/clang/StaticAnalyzer/Checkers/Checkers.td (+5) - (modified) clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt (+1) - (added) clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp (+541) - (modified) clang/test/Analysis/Inputs/system-header-simulator-cxx.h (+6) - (added) clang/test/Analysis/placement-self-copy.cpp (+655) ``````````diff diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 9e5d92cae727cc..0b8ea41e3a3826 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -1255,6 +1255,12 @@ features cannot lower the translation-unit ABI level; % - Moved checkers % - Diagnostic changes +#### New checkers and features + +- Added the ``alpha.cplusplus.PlacementSelfCopy`` checker, which reports + placement new expressions and ``std::construct_at`` calls that initialize the + new object from the storage it is placed into, e.g. ``::new (p) T(*p)``. + #### Improvements - The lock-order-reversal check in ``alpha.unix.PthreadLock`` is now disabled by default. diff --git a/clang/docs/analyzer/checkers.md b/clang/docs/analyzer/checkers.md index e2bbf52943bb87..b139470f47cb74 100644 --- a/clang/docs/analyzer/checkers.md +++ b/clang/docs/analyzer/checkers.md @@ -3503,6 +3503,83 @@ void bad_insert3(std::vector &v1, std::vector &v2) { } ``` +(alpha-cplusplus-placementselfcopy)= + +#### alpha.cplusplus.PlacementSelfCopy (C++) + +Check for placement new expressions that access the storage they are placed +into through the old object while the new object is being constructed, e.g. +`::new (p) T(*p)`. A value read during the construction through a glvalue not +obtained from `this` is unspecified ([class.cdtor]/2); writes through such a +glvalue are reported as well. Calls of +`std::construct_at` are checked as well; a report that would end inside the +standard library is emitted at the call site. + +The checker follows the reference or pointer that received the old storage +through variables, pointer arithmetic, the functions it is passed to and the +methods called on it, and reports the first of these operations on it: + +- a read or a write of the storage; +- passing it to a function or calling its method that is not inlined, except + for calls that only return their argument, such as `std::move`, + `std::forward`, `std::as_const` and `std::addressof`; +- capturing it in a lambda, unless a pointer to it is captured. + +Taking, storing, comparing and shifting its address, accessing members without +reading them and calling inlined methods that read nothing are allowed, and so +are all accesses through `this` or its copies. An object that only contains the +storage, such as a pool with an inline buffer, is not the old object; only its +accesses that overlap the storage are reported. + +```cpp +struct Buffer { + int *Data; + int Size; + Buffer(const Buffer &O) + : Data(new int[O.Size]), // warn: placement new accesses the storage it + // is placed into during construction of + // the new object + Size(O.Size) { + for (int I = 0; I < Size; ++I) + Data[I] = O.Data[I]; + } +}; + +void reconstruct(Buffer *B) { + ::new (B) Buffer(*B); +} +``` + +An access of the old object in the new-initializer itself is reported as well, +whatever the order of the elements. + +```cpp +struct Point { + int X, Y; +}; + +void swap_coordinates(Point *P) { + ::new (P) Point{P->Y, P->X}; // warn: placement new accesses the storage it + // is placed into during construction of + // the new object +} +``` + +When placement new itself calls a copy or move constructor that is not inlined, +such as a trivial copy or a constructor without a visible definition, and its +source is in the storage, it is reported as a self-copy. + +```cpp +struct Trivial { + int X; +}; + +void trivial_copy(Trivial *T) { + ::new (T) Trivial(*T); // warn: object constructed by placement new is + // initialized from the storage it is placed into +} +``` + (alpha-cplusplus-smartptr)= #### alpha.cplusplus.SmartPtr (C++) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 9905649801e85b..ce71e6e501d6b1 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -813,6 +813,11 @@ def MismatchedIteratorChecker : Checker<"MismatchedIterator">, Dependencies<[IteratorModeling]>, Documentation<HasDocumentation>; +def PlacementSelfCopyChecker : Checker<"PlacementSelfCopy">, + HelpText<"Check for placement new expressions that initialize the new " + "object from the storage it is placed into">, + Documentation<HasDocumentation>; + def SmartPtrChecker: Checker<"SmartPtr">, HelpText<"Find the dereference of null SmrtPtr">, Dependencies<[SmartPtrModeling]>, diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index f136bd5434113b..a1c2c604020214 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -91,6 +91,7 @@ add_clang_library(clangStaticAnalyzerCheckers OpaqueSTLFunctionsModeling.cpp OSObjectCStyleCast.cpp PaddingChecker.cpp + PlacementSelfCopyChecker.cpp PointerArithChecker.cpp PointerSubChecker.cpp PthreadLockChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp new file mode 100644 index 00000000000000..cc0da471642f3d --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/PlacementSelfCopyChecker.cpp @@ -0,0 +1,541 @@ +//===- PlacementSelfCopyChecker.cpp -----------------------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// Defines a checker that finds placement new expressions that access the +// target storage through the old object while the new object is being +// constructed, e.g. `::new (p) T(*p)`. A call that is not inlined is assumed +// to access the old object passed to it; when it is the copy or move +// constructor called by placement new itself, it is reported as a self-copy. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ParentMap.h" +#include "clang/Basic/Builtins.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +using PlacementKey = std::pair<const CXXNewExpr *, const StackFrame *>; +REGISTER_MAP_WITH_PROGRAMSTATE(PlacementTargets, PlacementKey, + const MemRegion *) +// Variables that refer to the old storage, mapped to the placement target. +REGISTER_MAP_WITH_PROGRAMSTATE(SourceVars, const MemRegion *, const MemRegion *) +// Frames of methods called on the old storage, mapped to the placement target. +REGISTER_MAP_WITH_PROGRAMSTATE(SourceFrames, const StackFrame *, + const MemRegion *) +REGISTER_TRAIT_WITH_PROGRAMSTATE(PendingSelfCopy, const MemRegion *) + +namespace { +class PlacementSelfCopyChecker + : public Checker<check::PostCall, check::PreCall, check::Bind, + check::Location, check::PostStmt<LambdaExpr>, + check::PostStmt<CXXNewExpr>> { + const BugType BT{this, "Placement new initialized from its own storage", + categories::MemoryError}; + const BugType AccessBT{this, "Placement new accesses its own storage", + categories::MemoryError}; + + void reportAccess(const Expr *E, CheckerContext &C) const; + void reportSelfCopy(const Expr *Arg, CheckerContext &C) const; + void reportPendingSelfCopy(const CallEvent &Call, CheckerContext &C) const; + +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkBind(SVal Loc, SVal Val, const Stmt *S, bool AtDeclInit, + CheckerContext &C) const; + void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &C) const; + void checkPostStmt(const LambdaExpr *LE, CheckerContext &C) const; + void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const; +}; +} // namespace + +namespace { +struct Extent { + const MemRegion *Base; + int64_t Begin; + std::optional<int64_t> End; +}; +} // namespace + +// Base class subobjects keep their own extent. +static const MemRegion *stripCasts(const MemRegion *R) { + return R->StripCasts(/*StripBaseAndDerivedCasts=*/false); +} + +static std::optional<Extent> getExtent(const MemRegion *R, ASTContext &Ctx) { + RegionOffset Offset = R->getAsOffset(); + if (!Offset.isValid() || Offset.hasSymbolicOffset()) + return std::nullopt; + Extent E{Offset.getRegion(), Offset.getOffset(), std::nullopt}; + QualType Ty; + if (const auto *TR = dyn_cast<TypedValueRegion>(R)) + Ty = TR->getValueType(); + else if (const auto *SR = dyn_cast<SymbolicRegion>(R)) + Ty = SR->getPointeeStaticType(); + if (!Ty.isNull() && !Ty->isIncompleteType()) + E.End = E.Begin + Ctx.getTypeSize(Ty); + return E; +} + +// Returns the extents of A and B if both are known within the same base. +static std::optional<std::pair<Extent, Extent>> +getKnownExtents(const MemRegion *A, const MemRegion *B, ASTContext &Ctx) { + std::optional<Extent> EA = getExtent(A, Ctx), EB = getExtent(B, Ctx); + if (!EA || !EB || !EA->End || !EB->End || EA->Base != EB->Base) + return std::nullopt; + return std::make_pair(*EA, *EB); +} + +static bool overlaps(const MemRegion *A, const MemRegion *B, ASTContext &Ctx) { + if (auto E = getKnownExtents(A, B, Ctx)) + return E->first.Begin < *E->second.End && E->second.Begin < *E->first.End; + A = stripCasts(A); + return A == stripCasts(B) || A->isSubRegionOf(B) || B->isSubRegionOf(A); +} + +static bool isWithin(const MemRegion *R, const MemRegion *Container, + ASTContext &Ctx) { + if (auto E = getKnownExtents(R, Container, Ctx)) + return E->second.Begin <= E->first.Begin && *E->first.End <= *E->second.End; + R = stripCasts(R); + return R == stripCasts(Container) || R->isSubRegionOf(Container); +} + +// Limits R to the placed object instead of the whole buffer it lives in. +static const MemRegion *getPlacedRegion(const CXXNewExpr *NE, + const MemRegion *R, CheckerContext &C) { + ASTContext &Ctx = C.getASTContext(); + QualType Ty = NE->getAllocatedType(); + if (NE->isArray()) { + std::optional<const Expr *> Size = NE->getArraySize(); + std::optional<llvm::APSInt> N = + Size && *Size ? (*Size)->getIntegerConstantExpr(Ctx) : std::nullopt; + if (!N) + return R; + Ty = + Ctx.getConstantArrayType(Ty, *N, nullptr, ArraySizeModifier::Normal, 0); + } + const auto *SR = dyn_cast<SubRegion>(R); + const auto *TR = dyn_cast<TypedValueRegion>(R); + if (!SR || (TR && Ctx.hasSameUnqualifiedType(TR->getValueType(), Ty))) + return R; + return C.getStoreManager().getRegionManager().getElementRegion( + Ty, C.getSValBuilder().makeArrayIndex(0), SR, Ctx); +} + +// Calls that return their argument or its address without accessing it, e.g. +// `std::move(X)`, `std::addressof(X)` or `&X` with an overloaded `operator&`. +static bool isPassThroughCall(const CallExpr *CE) { + if (!CE || CE->getNumArgs() != 1) + return false; + if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(CE)) + return OCE->getOperator() == OO_Amp; + const FunctionDecl *FD = CE->getDirectCallee(); + switch (FD ? FD->getBuiltinID() : 0) { + case Builtin::BIaddressof: + case Builtin::BI__addressof: + case Builtin::BI__builtin_addressof: + case Builtin::BIas_const: + case Builtin::BIforward: + case Builtin::BIforward_like: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + return true; + default: + return false; + } +} + +// Collects the variables that E accesses memory through, e.g. `O` for +// `O->X[1]`, `O.A + 1` or `O.data()`, and both `A` and `B` for +// `(C ? A : B).X`. +static void collectBaseVars(const Expr *E, + SmallVectorImpl<const VarDecl *> &Vars, + bool &ViaThis) { + while (E) { + E = E->IgnoreParenCasts(); + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) { + if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) + Vars.push_back(VD); + return; + } + if (isa<CXXThisExpr>(E)) { + ViaThis = true; + return; + } + if (const auto *CO = dyn_cast<AbstractConditionalOperator>(E)) { + collectBaseVars(CO->getTrueExpr(), Vars, ViaThis); + E = CO->getFalseExpr(); + } else if (const auto *ME = dyn_cast<MemberExpr>(E)) + E = ME->getBase(); + else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(E)) + E = ASE->getBase(); + else if (const auto *BO = dyn_cast<BinaryOperator>(E); + BO && BO->isAssignmentOp()) + E = BO->getLHS(); + else if (BO && BO->isAdditiveOp()) + E = BO->getLHS()->getType()->isPointerType() ? BO->getLHS() + : BO->getRHS(); + else if (const auto *UO = dyn_cast<UnaryOperator>(E); + UO && + (UO->getOpcode() == UO_Deref || UO->getOpcode() == UO_AddrOf || + UO->isIncrementDecrementOp())) + E = UO->getSubExpr(); + else if (const auto *CE = dyn_cast<CallExpr>(E); isPassThroughCall(CE)) + E = CE->getArg(0); + else if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(E)) + E = MCE->getImplicitObjectArgument(); + else + return; + } +} + +static bool isEmptyRecord(const MemRegion *R) { + const auto *TR = dyn_cast<TypedValueRegion>(R); + const CXXRecordDecl *RD = + TR ? TR->getValueType()->getAsCXXRecordDecl() : nullptr; + return RD && RD->hasDefinition() && RD->isEmpty(); +} + +static bool isInInitializer(const CXXNewExpr *NE, const Stmt *S, + CheckerContext &C) { + const Expr *Init = NE->getInitializer(); + const ParentMap &PM = C.getCurrentAnalysisDeclContext()->getParentMap(); + for (const Stmt *P = S; P && P != NE; P = PM.getParent(P)) + if (P == Init) + return true; + return false; +} + +// Returns the target whose new-initializer S uses Source in. +static const MemRegion *findInitializedTarget(const Stmt *S, + const MemRegion *Source, + CheckerContext &C) { + if (isEmptyRecord(Source)) + return nullptr; + for (const auto &[Key, Target] : C.getState()->get<PlacementTargets>()) + if (Key.second == C.getStackFrame() && isInInitializer(Key.first, S, C) && + overlaps(Source, Target, C.getASTContext())) + return Target; + return nullptr; +} + +// Captured variables and `this` in a lambda body belong to the frame of the +// function that defines the lambda. Only pointers and `this` get here: other +// captures of the old object are reported when the lambda is created. +static const StackFrame *getOwningFrame(const VarDecl *VD, + const StackFrame *Frame) { + while (Frame) { + const auto *MD = dyn_cast_if_present<CXXMethodDecl>(Frame->getDecl()); + if (!MD || !MD->getParent()->isLambda() || + (VD && VD->getDeclContext() == MD)) + return Frame; + const auto *Outer = dyn_cast<Decl>(MD->getParent()->getDeclContext()); + do + Frame = Frame->getParent(); + while (Frame && Frame->getDecl() != Outer); + } + return nullptr; +} + +// Returns the target that E accesses through a variable that refers to it, +// or through `this` of a method called on the old storage. +static const MemRegion *findSourceTarget(const Expr *E, const MemRegion *Source, + CheckerContext &C) { + if (isEmptyRecord(Source)) + return nullptr; + SmallVector<const VarDecl *, 2> Vars; + bool ViaThis = false; + collectBaseVars(E, Vars, ViaThis); + ProgramStateRef State = C.getState(); + auto Matches = [&](const MemRegion *const *Target) { + return Target && overlaps(Source, *Target, C.getASTContext()); + }; + for (const VarDecl *VD : Vars) { + const StackFrame *Owner = getOwningFrame(VD, C.getStackFrame()); + const MemRegion *Var = + Owner ? State->getLValue(VD, Owner).getAsRegion() : nullptr; + if (const MemRegion *const *T = Var ? State->get<SourceVars>(Var) : nullptr; + Matches(T)) + return *T; + } + const StackFrame *Owner = + ViaThis ? getOwningFrame(nullptr, C.getStackFrame()) : nullptr; + if (const MemRegion *const *T = + Owner ? State->get<SourceFrames>(Owner) : nullptr; + Matches(T)) + return *T; + return nullptr; +} + +// Returns the target that E refers to in call S, either through the old +// storage or directly in the new-initializer. +static const MemRegion *findTarget(const Expr *E, const MemRegion *Source, + const Stmt *S, CheckerContext &C) { + if (const MemRegion *Target = findSourceTarget(E, Source, C)) + return Target; + return findInitializedTarget(S, Source, C); +} + +// Returns whether E refers to a part of the old storage, not to an object that +// merely contains it, such as a pool with an inline buffer. +static bool refersToOldObject(const Expr *E, const MemRegion *Source, + const Stmt *S, CheckerContext &C) { + const MemRegion *Target = findTarget(E, Source, S, C); + if (!Target || !isWithin(Source, Target, C.getASTContext())) + return false; + // An enclosing object of the same size, e.g. `O->Inner` for a target `Inner`. + return stripCasts(Target) == stripCasts(Source) || + !Target->isSubRegionOf(Source); +} + +// Returns the argument that passes the old object to a call that is not +// inlined. Such a call may access it in any way. +static const Expr *findOpaqueSourceUse(const CallEvent &Call, + CheckerContext &C) { + if (isPassThroughCall(dyn_cast_if_present<CallExpr>(Call.getOriginExpr()))) + return nullptr; + if (const auto *IC = dyn_cast<CXXInstanceCall>(&Call)) { + const Expr *Obj = IC->getCXXThisExpr(); + const MemRegion *Source = IC->getCXXThisVal().getAsRegion(); + if (Obj && Source && + refersToOldObject(Obj, Source, Call.getOriginExpr(), C)) + return Obj; + } + for (unsigned I = 0, E = Call.getNumArgs(); I != E; ++I) { + const MemRegion *Source = Call.getArgSVal(I).getAsRegion(); + const Expr *Arg = Call.getArgExpr(I); + if (Source && Arg && + refersToOldObject(Arg, Source, Call.getOriginExpr(), C)) + return Arg; + } + return nullptr; +} + +void PlacementSelfCopyChecker::reportAccess(const Expr *E, + CheckerContext &C) const { + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return; + auto R = std::make_unique<PathSensitiveBugReport>( + AccessBT, + "Placement new accesses the storage it is placed into during " + "construction of the new object", + N); + R->addRange(E->getSourceRange()); + C.emitReport(std::move(R)); +} + +void PlacementSelfCopyChecker::reportSelfCopy(const Expr *Arg, + CheckerContext &C) const { + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return; + auto R = std::make_unique<PathSensitiveBugReport>( + BT, + "Object constructed by placement new is initialized from the storage " + "it is placed into", + N); + R->addRange(Arg->getSourceRange()); + bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(Arg), *R); + C.emitReport(std::move(R)); +} + +// Reports inside the standard library are suppressed; report at the caller. +void PlacementSelfCopyChecker::reportPendingSelfCopy(const CallEvent &Call, + CheckerContext &C) const { + const MemRegion *Target = C.getState()->get<PendingSelfCopy... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/233525 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
