Bug 109548 - Detect c_str() dangling problems
Summary: Detect c_str() dangling problems
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 13.0
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks: new-warning, new_warning
  Show dependency treegraph
 
Reported: 2023-04-18 23:19 UTC by Paul Fee
Modified: 2023-04-19 12:38 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2023-04-19 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Paul Fee 2023-04-18 23:19:46 UTC
Thanks to bug 106393, G++ 13 can now catch simple dangling references, such as:

const int& f(const int& i) { return i; }
const int& i = f(10);

However, more complex examples do not trigger warnings:

#include <string>
std::string foo();
auto ptr = foo().c_str();

ASAN can detect stack-use-after-scope at runtime.  Clang can catch the issue at build time.

$ clang++ -c small.cpp 
small.cpp:3:12: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
auto ptr = foo().c_str();
           ^~~~~
1 warning generated.

Another example:

std::stringstream ss;
ss << "foo";
auto ptr = ss.str().c_str();

We're warned against this in the notes on cppreference.com, however it would be better if GCC could detect such issues and warn during compilation.
https://en.cppreference.com/w/cpp/io/basic_stringstream/str
Comment 1 Jonathan Wakely 2023-04-19 08:59:42 UTC
We could special-case std::basic_string::c_str() here but maybe it would be better to add an attribute for describing the lifetime of pointers/references returned from member functions.

We could annotate string::c_str() and string::data() and string::begin() etc. to indicate that they share the lifetime of *this:

    [[gnu::lifetime(this)]]
    const charT*
    basic_string<charT, traits, Alloc>::c_str() const noexcept;

And maybe annotate member functions of view-like types to say that they _don't_ share the lifetime of *this:

    [[gnu::lifetime(!this)]]
    T*
    span<T, E>::data() const noexcept;

(!this) is not visually distinct from (this) though, so a different syntax would be better. Maybe lifetime(nullptr) or lifetime(0)?

It might not be possible to use lifetime(this) annotations for non-trivial analysis, because of cases like this:

    std::string s = "abc";
    auto p = s.c_str();  // OK
    s.clear();           // p now dangles
    *p;                  // ERR

Without creating a DSL for describing iterator invalidation of members like string::clear() and string::insert() we can probably only use such an attribute to allow the front-end to diagnose the simplest cases like foo().c_str() in comment 0.
Comment 2 Richard Biener 2023-04-19 09:45:39 UTC
David possibly has inputs on how this should be done so the analyzer can do this kind of diagnostic.  Generally I'd say we want sth like

 T * __attribute__ ((return_lifetime(1))) foo (U *p, ...);

where we specify the lifetime of the returned referenced object to be
the same as a referenced object in the argument list?  So yes, the
[[gnu::lifetime(this)]] attribution would maybe do that but can
the attribute refer to another explicitely named argument as well?

Btw, is there an API to std::move the (possibly) allocated string out of
the std::string and make it available as C string pointer?
Comment 3 Jonathan Wakely 2023-04-19 12:38:53 UTC
(In reply to Richard Biener from comment #2)
> Btw, is there an API to std::move the (possibly) allocated string out of
> the std::string and make it available as C string pointer?

No, ownership always belongs to a std::string object.