Skip to content

[clang] Diagnose dangling issues for the "Container<GSLPointer>" case. #107213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ Improvements to Clang's diagnostics

- Clang now warns for u8 character literals used in C23 with ``-Wpre-c23-compat`` instead of ``-Wpre-c++17-compat``.

- Clang now diagnoses cases where a dangling ``GSLOwner<GSLPointer>`` object is constructed, e.g. ``std::vector<string_view> v = {std::string()};`` (#GH100526).

Improvements to Clang's time-trace
----------------------------------

Expand Down
14 changes: 14 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -6690,6 +6690,20 @@ When the Owner's lifetime ends, it will consider the Pointer to be dangling.
P.getInt(); // P is dangling
}

If a template class is annotated with ``[[gsl::Owner]]``, and the first
instantiated template argument is a pointer type (raw pointer, or ``[[gsl::Pointer]]``),
the analysis will consider the instantiated class as a container of the pointer.
When constructing such an object from a GSL owner object, the analysis will
assume that the container holds a pointer to the owner object. Consequently,
when the owner object is destroyed, the pointer will be considered dangling.

.. code-block:: c++

int f() {
std::vector<std::string_view> v = {std::string()}; // v holds a dangling pointer.
std::optional<std::string_view> o = std::string(); // o holds a dangling pointer.
}

}];
}

Expand Down
42 changes: 33 additions & 9 deletions clang/lib/Sema/CheckExprLifetime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,26 @@ static bool isInStlNamespace(const Decl *D) {
return DC->isStdNamespace();
}

// Returns true if the given Record decl is a form of `GSLOwner<Pointer>`
// type, e.g. std::vector<string_view>, std::optional<string_view>.
static bool isContainerOfPointer(const RecordDecl *Container) {
if (const auto *CTSD =
dyn_cast_if_present<ClassTemplateSpecializationDecl>(Container)) {
if (!CTSD->hasAttr<OwnerAttr>()) // Container must be a GSL owner type.
return false;
const auto &TAs = CTSD->getTemplateArgs();
return TAs.size() > 0 && TAs[0].getKind() == TemplateArgument::Type &&
(isRecordWithAttr<PointerAttr>(TAs[0].getAsType()) ||
TAs[0].getAsType()->isPointerType());
}
return false;
}

static bool isGSLOwner(QualType T) {
return isRecordWithAttr<OwnerAttr>(T) &&
!isContainerOfPointer(T->getAsRecordDecl());
}

static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
if (auto *Conv = dyn_cast_or_null<CXXConversionDecl>(Callee))
if (isRecordWithAttr<PointerAttr>(Conv->getConversionType()))
Expand All @@ -275,7 +295,7 @@ static bool shouldTrackImplicitObjectArg(const CXXMethodDecl *Callee) {
return false;
if (!isRecordWithAttr<PointerAttr>(
Callee->getFunctionObjectParameterType()) &&
!isRecordWithAttr<OwnerAttr>(Callee->getFunctionObjectParameterType()))
!isGSLOwner(Callee->getFunctionObjectParameterType()))
return false;
if (Callee->getReturnType()->isPointerType() ||
isRecordWithAttr<PointerAttr>(Callee->getReturnType())) {
Expand Down Expand Up @@ -413,7 +433,7 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
// Once we initialized a value with a non gsl-owner reference, it can no
// longer dangle.
if (ReturnType->isReferenceType() &&
!isRecordWithAttr<OwnerAttr>(ReturnType->getPointeeType())) {
!isGSLOwner(ReturnType->getPointeeType())) {
for (const IndirectLocalPathEntry &PE : llvm::reverse(Path)) {
if (PE.Kind == IndirectLocalPathEntry::GslReferenceInit ||
PE.Kind == IndirectLocalPathEntry::LifetimeBoundCall)
Expand Down Expand Up @@ -468,12 +488,17 @@ static void visitFunctionCallArguments(IndirectLocalPath &Path, Expr *Call,
if (CheckCoroCall || Callee->getParamDecl(I)->hasAttr<LifetimeBoundAttr>())
VisitLifetimeBoundArg(Callee->getParamDecl(I), Args[I]);
else if (EnableGSLAnalysis && I == 0) {
// Perform GSL analysis for the first argument
if (shouldTrackFirstArgument(Callee)) {
VisitGSLPointerArg(Callee, Args[0]);
} else if (auto *CCE = dyn_cast<CXXConstructExpr>(Call);
CCE &&
CCE->getConstructor()->getParent()->hasAttr<PointerAttr>()) {
VisitGSLPointerArg(CCE->getConstructor(), Args[0]);
} else if (auto *Ctor = dyn_cast<CXXConstructExpr>(Call)) {
const auto *ClassD = Ctor->getConstructor()->getParent();
// Two cases:
// a GSL pointer, e.g. std::string_view
// a container of GSL pointer, e.g. std::vector<string_view>
if (ClassD->hasAttr<PointerAttr>() ||
(isContainerOfPointer(ClassD) && Callee->getNumParams() == 1))
VisitGSLPointerArg(Ctor->getConstructor(), Args[0]);
}
}
}
Expand Down Expand Up @@ -990,13 +1015,12 @@ static void checkExprLifetimeImpl(Sema &SemaRef,
// int &p = *localUniquePtr;
// someContainer.add(std::move(localUniquePtr));
// return p;
IsLocalGslOwner = isRecordWithAttr<OwnerAttr>(L->getType());
IsLocalGslOwner = isGSLOwner(L->getType());
if (pathContainsInit(Path) || !IsLocalGslOwner)
return false;
} else {
IsGslPtrValueFromGslTempOwner =
MTE && !MTE->getExtendingDecl() &&
isRecordWithAttr<OwnerAttr>(MTE->getType());
MTE && !MTE->getExtendingDecl() && isGSLOwner(MTE->getType());
// Skipping a chain of initializing gsl::Pointer annotated objects.
// We are looking only for the final source to find out if it was
// a local or temporary owner or the address of a local variable/param.
Expand Down
77 changes: 77 additions & 0 deletions clang/test/Sema/warn-lifetime-analysis-nocfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,17 +158,30 @@ auto begin(C &c) -> decltype(c.begin());
template<typename T, int N>
T *begin(T (&array)[N]);

using size_t = decltype(sizeof(0));

template<typename T>
struct initializer_list {
const T* ptr; size_t sz;
};
template <typename T>
struct vector {
typedef __gnu_cxx::basic_iterator<T> iterator;
iterator begin();
iterator end();
const T *data() const;
vector();
vector(initializer_list<T> __l);

template<typename InputIterator>
vector(InputIterator first, InputIterator __last);

T &at(int n);
};

template<typename T>
struct basic_string_view {
basic_string_view();
basic_string_view(const T *);
const T *begin() const;
};
Expand Down Expand Up @@ -203,11 +216,21 @@ template<typename T>
struct optional {
optional();
optional(const T&);

template<typename U = T>
optional(U&& t);

template<typename U>
optional(optional<U>&& __t);

T &operator*() &;
T &&operator*() &&;
T &value() &;
T &&value() &&;
};
template<typename T>
optional<__decay(T)> make_optional(T&&);


template<typename T>
struct stack {
Expand Down Expand Up @@ -553,3 +576,57 @@ void test() {
std::string_view svjkk1 = ReturnStringView(StrCat("bar", "x")); // expected-warning {{object backing the pointer will be destroyed at the end of the full-expression}}
}
} // namespace GH100549

namespace GH100526 {
void test() {
std::vector<std::string_view> v1({std::string()}); // expected-warning {{object backing the pointer will be destroyed at the end}}
std::vector<std::string_view> v2({
std::string(), // expected-warning {{object backing the pointer will be destroyed at the end}}
std::string_view()
});
std::vector<std::string_view> v3({
std::string_view(),
std::string() // expected-warning {{object backing the pointer will be destroyed at the end}}
});

std::optional<std::string_view> o1 = std::string(); // expected-warning {{object backing the pointer}}

std::string s;
// This is a tricky use-after-free case, what it does:
// 1. make_optional creates a temporary "optional<string>"" object
// 2. the temporary object owns the underlying string which is copied from s.
// 3. the t3 object holds the view to the underlying string of the temporary object.
std::optional<std::string_view> o2 = std::make_optional(s); // expected-warning {{object backing the pointer}}
std::optional<std::string_view> o3 = std::optional<std::string>(s); // expected-warning {{object backing the pointer}}
std::optional<std::string_view> o4 = std::optional<std::string_view>(s);

// FIXME: should work for assignment cases
v1 = {std::string()};
o1 = std::string();

// no warning on copying pointers.
std::vector<std::string_view> n1 = {std::string_view()};
std::optional<std::string_view> n2 = {std::string_view()};
std::optional<std::string_view> n3 = std::string_view();
std::optional<std::string_view> n4 = std::make_optional(std::string_view());
const char* b = "";
std::optional<std::string_view> n5 = std::make_optional(b);
std::optional<std::string_view> n6 = std::make_optional("test");
}

std::vector<std::string_view> test2(int i) {
std::vector<std::string_view> t;
if (i)
return t; // this is fine, no dangling
return std::vector<std::string_view>(t.begin(), t.end());
}

std::optional<std::string_view> test3(int i) {
std::string s;
std::string_view sv;
if (i)
return s; // expected-warning {{address of stack memory associated}}
return sv; // fine
}

} // namespace GH100526
Loading