Files
TheRock/compiler/amd-llvm/clang/lib/Analysis/LifetimeSafety/LifetimeAnnotations.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

479 lines
18 KiB
C++

//===- LifetimeAnnotations.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
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Type.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/StringSet.h"
namespace clang::lifetimes {
const FunctionDecl *
getDeclWithMergedLifetimeBoundAttrs(const FunctionDecl *FD) {
return FD != nullptr ? FD->getMostRecentDecl() : nullptr;
}
const CXXMethodDecl *
getDeclWithMergedLifetimeBoundAttrs(const CXXMethodDecl *CMD) {
const FunctionDecl *FD = CMD;
return cast_if_present<CXXMethodDecl>(
getDeclWithMergedLifetimeBoundAttrs(FD));
}
bool isNormalAssignmentOperator(const FunctionDecl *FD) {
OverloadedOperatorKind OO = FD->getDeclName().getCXXOverloadedOperator();
bool IsAssignment = OO == OO_Equal || isCompoundAssignmentOperator(OO);
if (!IsAssignment)
return false;
QualType RetT = FD->getReturnType();
if (!RetT->isLValueReferenceType())
return false;
ASTContext &Ctx = FD->getASTContext();
QualType LHST;
auto *MD = dyn_cast<CXXMethodDecl>(FD);
if (MD && MD->isCXXInstanceMember())
LHST = Ctx.getLValueReferenceType(MD->getFunctionObjectParameterType());
else
LHST = FD->getParamDecl(0)->getType();
return Ctx.hasSameType(RetT, LHST);
}
bool isAssignmentOperatorLifetimeBound(const CXXMethodDecl *CMD) {
CMD = getDeclWithMergedLifetimeBoundAttrs(CMD);
return CMD && isNormalAssignmentOperator(CMD) && CMD->param_size() == 1 &&
CMD->getParamDecl(0)->hasAttr<clang::LifetimeBoundAttr>();
}
/// Check if a function has a lifetimebound attribute on its function type
/// (which represents the implicit 'this' parameter for methods).
/// Returns the attribute if found, nullptr otherwise.
static const LifetimeBoundAttr *
getLifetimeBoundAttrFromFunctionType(const TypeSourceInfo &TSI) {
// Walk through the type layers looking for a lifetimebound attribute.
TypeLoc TL = TSI.getTypeLoc();
while (true) {
auto ATL = TL.getAsAdjusted<AttributedTypeLoc>();
if (!ATL)
break;
if (auto *LBAttr = ATL.getAttrAs<LifetimeBoundAttr>())
return LBAttr;
TL = ATL.getModifiedLoc();
}
return nullptr;
}
const LifetimeBoundAttr *
getDirectImplicitObjectLifetimeBoundAttr(const FunctionDecl *FD) {
if (const TypeSourceInfo *TSI = FD->getTypeSourceInfo())
if (const auto *Attr = getLifetimeBoundAttrFromFunctionType(*TSI))
return Attr;
return nullptr;
}
const LifetimeBoundAttr *
getImplicitObjectParamLifetimeBoundAttr(const FunctionDecl *FD) {
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
// Attribute merging doesn't work well with attributes on function types (like
// 'this' param). We need to check all redeclarations.
auto CheckRedecls = [](const FunctionDecl *F) -> const LifetimeBoundAttr * {
for (const FunctionDecl *Redecl : F->redecls())
if (const auto *Attr = getDirectImplicitObjectLifetimeBoundAttr(Redecl))
return Attr;
return nullptr;
};
if (const auto *Attr = CheckRedecls(FD))
return Attr;
if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern())
return CheckRedecls(Pattern);
return nullptr;
}
bool implicitObjectParamIsLifetimeBound(const FunctionDecl *FD) {
if (getImplicitObjectParamLifetimeBoundAttr(FD))
return true;
return isNormalAssignmentOperator(FD);
}
bool isInStlNamespace(const Decl *D) {
for (const DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
if (DC->isStdNamespace())
return true;
if (const auto *ND = dyn_cast<NamespaceDecl>(DC))
if (const IdentifierInfo *II = ND->getIdentifier()) {
StringRef Name = II->getName();
if (Name.size() >= 2 && Name.front() == '_' &&
(Name[1] == '_' || isUppercase(Name[1])))
return true;
}
}
return false;
}
bool isPointerLikeType(QualType QT) {
return isGslPointerType(QT) || QT->isPointerType() || QT->isNullPtrType();
}
static bool isReferenceOrPointerLikeType(QualType QT) {
return QT->isReferenceType() || isPointerLikeType(QT);
}
bool shouldTrackImplicitObjectArg(const Expr &ImplicitObjectArgument,
const CXXMethodDecl *Callee,
bool RunningUnderLifetimeSafety) {
if (!Callee)
return false;
// Check both the declaring class and the call-site object: a gsl::Owner
// may inherit its accessors from a non-Owner base (e.g. libc++ optional).
const bool IsGslOwnerImplicitObject =
isGslOwnerType(Callee->getFunctionObjectParameterType()) ||
(RunningUnderLifetimeSafety &&
isGslOwnerType(ImplicitObjectArgument.getBestDynamicClassType()));
if (auto *Conv = dyn_cast<CXXConversionDecl>(Callee))
if (isGslPointerType(Conv->getConversionType()) && IsGslOwnerImplicitObject)
return true;
if (!isGslPointerType(Callee->getFunctionObjectParameterType()) &&
!IsGslOwnerImplicitObject)
return false;
// Begin and end iterators.
static const llvm::StringSet<> IteratorMembers = {
"begin", "end", "rbegin", "rend", "cbegin", "cend", "crbegin", "crend"};
static const llvm::StringSet<> InnerPointerGetters = {
// Inner pointer getters.
"c_str", "data", "get"};
static const llvm::StringSet<> ContainerFindFns = {
// Map and set types.
"find", "equal_range", "lower_bound", "upper_bound"};
// Track dereference operator and transparent functions like begin(), get(),
// etc. for all GSL pointers. Only do so for lifetime safety analysis and not
// for Sema's statement-local analysis as it starts to have false-positives.
if (RunningUnderLifetimeSafety &&
isGslPointerType(Callee->getFunctionObjectParameterType()) &&
isReferenceOrPointerLikeType(Callee->getReturnType())) {
// Propagate origins through GSL pointer arithmetic and dereference
// operators.
switch (Callee->getOverloadedOperator()) {
case OO_Arrow:
case OO_Star:
case OO_Plus:
case OO_Minus:
case OO_PlusPlus:
case OO_MinusMinus:
return true;
default:
break;
}
if (Callee->getIdentifier() &&
(IteratorMembers.contains(Callee->getName()) ||
InnerPointerGetters.contains(Callee->getName())))
return true;
}
if (!isInStlNamespace(Callee->getParent()))
return false;
if (isPointerLikeType(Callee->getReturnType())) {
if (!Callee->getIdentifier())
// e.g., std::optional<T>::operator->() returns T*.
return RunningUnderLifetimeSafety
? IsGslOwnerImplicitObject &&
Callee->getOverloadedOperator() ==
OverloadedOperatorKind::OO_Arrow
: false;
return IteratorMembers.contains(Callee->getName()) ||
InnerPointerGetters.contains(Callee->getName()) ||
ContainerFindFns.contains(Callee->getName());
}
if (Callee->getReturnType()->isReferenceType()) {
if (!Callee->getIdentifier()) {
auto OO = Callee->getOverloadedOperator();
if (!IsGslOwnerImplicitObject)
return false;
return OO == OverloadedOperatorKind::OO_Subscript ||
OO == OverloadedOperatorKind::OO_Star;
}
return llvm::StringSwitch<bool>(Callee->getName())
.Cases({"front", "back", "at", "top", "value"}, true)
.Default(false);
}
return false;
}
bool shouldTrackFirstArgument(const FunctionDecl *FD) {
if (!FD->getIdentifier() || FD->getNumParams() < 1)
return false;
if (!FD->isInStdNamespace())
return false;
// Track std:: algorithm functions that return an iterator whose lifetime is
// bound to the first argument.
if (FD->getNumParams() >= 2 && FD->isInStdNamespace() &&
isGslPointerType(FD->getReturnType())) {
if (llvm::StringSwitch<bool>(FD->getName())
.Cases(
{
"find",
"find_if",
"find_if_not",
"find_first_of",
"adjacent_find",
"search",
"find_end",
"lower_bound",
"upper_bound",
"partition_point",
},
true)
.Default(false))
return true;
}
const auto *RD = FD->getParamDecl(0)->getType()->getPointeeCXXRecordDecl();
if (!RD || !RD->isInStdNamespace())
return false;
if (!RD->hasAttr<PointerAttr>() && !RD->hasAttr<OwnerAttr>())
return false;
if (FD->getNumParams() != 1)
return false;
if (FD->getReturnType()->isPointerType() ||
isGslPointerType(FD->getReturnType())) {
return llvm::StringSwitch<bool>(FD->getName())
.Cases({"begin", "rbegin", "cbegin", "crbegin"}, true)
.Cases({"end", "rend", "cend", "crend"}, true)
.Case("data", true)
.Default(false);
}
if (FD->getReturnType()->isReferenceType()) {
return llvm::StringSwitch<bool>(FD->getName())
.Cases({"get", "any_cast"}, true)
.Default(false);
}
return false;
}
bool shouldTrackSecondArgument(const FunctionDecl *FD) {
if (FD->getNumParams() < 2)
return false;
const auto *RD = FD->getParamDecl(1)->getType()->getAsCXXRecordDecl();
if (!RD)
return false;
// For free-standing `+`/`-` operators annotated with `gsl::Pointer`, track
// the second parameter when its type matches the return type.
return RD->hasAttr<PointerAttr>() &&
(FD->getOverloadedOperator() == OO_Plus ||
FD->getOverloadedOperator() == OO_Minus) &&
ASTContext::hasSameUnqualifiedType(FD->getParamDecl(1)->getType(),
FD->getReturnType()) &&
!isa<CXXMethodDecl>(FD);
}
template <typename T> static bool isRecordWithAttr(const CXXRecordDecl *RD) {
if (!RD)
return false;
// Generally, if a primary template class declaration is annotated with an
// attribute, all its specializations generated from template instantiations
// should inherit the attribute.
//
// However, since lifetime analysis occurs during parsing, we may encounter
// cases where a full definition of the specialization is not required. In
// such cases, the specialization declaration remains incomplete and lacks the
// attribute. Therefore, we fall back to checking the primary template class.
//
// Note: it is possible for a specialization declaration to have an attribute
// even if the primary template does not.
//
// FIXME: What if the primary template and explicit specialization
// declarations have conflicting attributes? We should consider diagnosing
// this scenario.
bool Result = RD->hasAttr<T>();
if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
Result |= CTSD->getSpecializedTemplate()->getTemplatedDecl()->hasAttr<T>();
return Result;
}
template <typename T> static bool isRecordWithAttr(QualType Type) {
return isRecordWithAttr<T>(Type->getAsCXXRecordDecl());
}
bool isGslPointerType(QualType QT) { return isRecordWithAttr<PointerAttr>(QT); }
bool isGslOwnerType(QualType QT) { return isRecordWithAttr<OwnerAttr>(QT); }
bool isGslOwnerType(const CXXRecordDecl *RD) {
return isRecordWithAttr<OwnerAttr>(RD);
}
static StringRef getName(const CXXRecordDecl &RD) {
if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(&RD))
return CTSD->getSpecializedTemplate()->getName();
if (RD.getIdentifier())
return RD.getName();
return "";
}
static StringRef getName(const FunctionDecl &FD) {
if (FD.getIdentifier())
return FD.getName();
return "";
}
static bool isStdUniquePtr(const CXXRecordDecl &RD) {
return RD.isInStdNamespace() && getName(RD) == "unique_ptr";
}
bool isUniquePtrRelease(const CXXMethodDecl &MD) {
return MD.getIdentifier() && MD.getName() == "release" &&
MD.getNumParams() == 0 && isStdUniquePtr(*MD.getParent());
}
bool isInvalidationMethod(const CXXMethodDecl &MD) {
const CXXRecordDecl *RD = MD.getParent();
if (!isInStlNamespace(RD))
return false;
// `pop_back` is excluded: it only invalidates references to the removed
// element, not to other elements.
static const llvm::StringSet<> Vector = {// Insertion
"insert", "emplace", "emplace_back",
"push_back", "insert_range",
"append_range",
// Removal
"erase", "clear",
// Memory management
"reserve", "resize", "shrink_to_fit",
// Assignment
"assign", "assign_range"};
// `pop_*` methods are excluded: they only invalidate references to the
// removed element, not to other elements.
static const llvm::StringSet<> Deque = {// Insertion
"insert", "emplace", "insert_range",
// Removal
"erase", "clear",
// Memory management
"resize", "shrink_to_fit",
// Assignment
"assign", "assign_range"};
static const llvm::StringSet<> String = {
// Insertion
"insert", "push_back", "append", "replace", "replace_with_range",
"insert_range", "append_range",
// Removal
"pop_back", "erase", "clear",
// Memory management
"reserve", "resize", "resize_and_overwrite", "shrink_to_fit",
// Assignment
"swap", "assign", "assign_range"};
// FIXME: Add queue and stack and check for underlying container
// (e.g. no invalidation for std::list).
static const llvm::StringSet<> PriorityQueue = {// Insertion
"push", "emplace",
"push_range",
// Removal
"pop"};
// `erase` and `extract` are excluded: they only affect the removed element,
// not to other elements.
static const llvm::StringSet<> NodeBased = {// Removal
"clear"};
// For `flat_*` container adaptors, `try_emplace` and `insert_or_assign`
// only exist on `flat_map`. Listing them here is harmless since the methods
// won't be found on other types.
static const llvm::StringSet<> Flat = {// Insertion
"insert", "emplace", "emplace_hint",
"try_emplace", "insert_or_assign",
"insert_range", "merge",
// Removal
"extract", "erase", "clear",
// Assignment
"replace"};
static const llvm::StringSet<> UniquePtr = {// Reallocation
"reset"};
const StringRef RecordName = getName(*RD);
// TODO: Consider caching this lookup by CXXMethodDecl pointer if this
// StringSwitch becomes a performance bottleneck.
const llvm::StringSet<> *InvalidatingMethods =
llvm::StringSwitch<const llvm::StringSet<> *>(RecordName)
.Case("vector", &Vector)
.Case("basic_string", &String)
.Case("deque", &Deque)
.Case("priority_queue", &PriorityQueue)
.Cases({"set", "multiset", "map", "multimap", "unordered_set",
"unordered_multiset", "unordered_map", "unordered_multimap"},
&NodeBased)
.Cases({"flat_map", "flat_set", "flat_multimap", "flat_multiset"},
&Flat)
.Case("unique_ptr", &UniquePtr)
.Default(nullptr);
if (!InvalidatingMethods)
return false;
// Handle Operators via OverloadedOperatorKind
OverloadedOperatorKind OO = MD.getOverloadedOperator();
if (OO != OO_None) {
switch (OO) {
case OO_Equal: // operator= : Always invalidates (Assignment)
case OO_PlusEqual: // operator+= : Append (String/Vector)
return true;
case OO_Subscript: // operator[] : Invalidation only for
// `flat_map` (Insert-or-access).
// `map` and `unordered_map` are excluded.
return RecordName == "flat_map";
default:
return false;
}
}
if (!MD.getIdentifier())
return false;
return InvalidatingMethods->contains(MD.getName());
}
bool destructsFirstArg(const FunctionDecl &FD) {
if (isa<CXXDestructorDecl>(FD))
return true;
return isInStlNamespace(&FD) && getName(FD) == "destroy_at";
}
bool isStdCallableWrapperType(const CXXRecordDecl *RD) {
if (!RD || !isInStlNamespace(RD))
return false;
StringRef Name = getName(*RD);
return Name == "function" || Name == "move_only_function";
}
bool isStdReferenceCast(const FunctionDecl *FD) {
if (!FD)
return false;
switch (FD->getBuiltinID()) {
case Builtin::BImove:
case Builtin::BImove_if_noexcept:
case Builtin::BIforward:
case Builtin::BIforward_like:
case Builtin::BIas_const:
return true;
default:
return false;
}
}
} // namespace clang::lifetimes