Safer code in C++ with lifetime bounds

For better performance in software, we avoid unnecessary copies. To do so, we introduce references (or pointers). An example of this ideas in C++ is the std::string_view class. As the name suggests, a std::string_view instance is merely a ‘view’: it points at some string, but it does not own or otherwise manage the underlying memory.

The downside is that we must track ownership: when the owner of the memory is gone, we should not be left holding the std::string_view instance. With modern tools, it is trivial to detect such bugs (e.g., using a sanitizer). However, it would be nicer if the compiler could tell us right away.

A few C++ compilers (Visual Studio and LLVM) support lifetime-bound annotation to help us. Let us consider an example. Suppose you would like to parse a URL (e.g., ‘https://www.google.com/path’), but you are only interested in the host (e.g. ‘www.google.com’). You might write code like so using the ada-url/ada parsing library:

std::string_view my_get_host(std::string_view url_string) {
  auto url = ada::parse(url_string).value();
  return url.get_host();
}

This code is not generally safe. The parser will store the result of the parse inside a temporary object but you are returning an std::string_view which points at it. You have a dangling reference.

To get a recent version of LLVM/clang (18+) to warn us, we just need to annotate the function get_host like so:

#ifndef __has_cpp_attribute
    #define ada_lifetime_bound
#elif __has_cpp_attribute(msvc::lifetimebound)
    #define ada_lifetime_bound [[msvc::lifetimebound]]
#elif __has_cpp_attribute(clang::lifetimebound)
    #define ada_lifetime_bound [[clang::lifetimebound]]
#elif __has_cpp_attribute(lifetimebound)
    #define ada_lifetime_bound [[lifetimebound]]
#else
    #define ada_lifetime_bound
#endif

...

std::string_view get_host() const noexcept ada_lifetime_bound;

You can review the complete code update on GitHub.

And then we get a warning at compile time:

fun.cpp:8:10: warning: address of stack memory associated with local variable 'url' returned [-Wreturn-stack-address]
    8 |   return url.get_host();

It is hardly perfect at this point in time. It does not always warn you, but progress is being made. This feature and others will help us catch errors sooner.

Credit: Thanks to Denis Yaroshevskiy for making me aware of this new compiler feature.

Further reading: LLVM documentation.

Daniel Lemire, "Safer code in C++ with lifetime bounds," in Daniel Lemire's blog, July 26, 2024, https://lemire.me/blog/2024/07/26/safer-code-in-c-with-lifetime-bounds/.
[BibTeX]

Published by

Daniel Lemire

A computer science professor at the University of Quebec (TELUQ).

4 thoughts on “Safer code in C++ with lifetime bounds”

  1. You’ve changed the example function from one code snippet to the next, which confuses your point.

    – In the first example, it’s a standalone function taking a string_view parameter.
    – When you add the attribute to the declaration, it’s now declared as a method (of some unknown class), with no parameters, and const and noexcept attributes. We never get to see the implementation of this method.

    Moreover, in the first example it’s the _implementation_ of the function that in error due to UB, but later on the compiler warning you show is at a _call site_.

    I’ve read the docs of the lifetimebound attribute in the Clang documentation, and it’s more complex than your example implies; using it correctly involves reasoning about the lifetime of the result in relation to function arguments including the implicit “this”. IMHO a fuller explanation is needed in this post.

  2. I think the example is not very suitable. Any good implementation shall return a sub-string_view of the parameter to my_get_host(), so the lifetime will be independent of the implementation.

    I think, this would be something, that can’t be expressed in the moment, but could become useful? At least a lifetime checker would indirectly detect implementations not returning a sub-string_view

Leave a Reply

Your email address will not be published.

You can also subscribe by email to this blog (non-commercial, no ads, weekly email).

How to post code (C, C++, Java, Python, etc.):

Wrap your code in backticks, like this:

`int main() {
    return 0;
}`