http: Add missing LIFETIMEBOUND annotations #36198

pull hodlinator wants to merge 1 commits into bitcoin:master from hodlinator:2026/09/http_dangling changing 1 files +6 −6
  1. hodlinator commented at 8:06 AM on September 9, 2026: contributor

    Helps Clang detect certain dangling reference issues, in a similar vein as #36164.

    Known limitations

    It doesn't catch invalidation nor brace-initialization.

    <details><summary>Diff illustrating limitations</summary>

    --- a/src/test/httpserver_tests.cpp
    +++ b/src/test/httpserver_tests.cpp
    @@ -80,6 +80,15 @@ BOOST_AUTO_TEST_CASE(test_query_parameters)
     
     BOOST_AUTO_TEST_CASE(http_headers_tests)
     {
    +    auto foo = HTTPHeaders{}.FindAll("needle"); // Emits warning
    +    (void)foo;
    +    auto bar{HTTPHeaders{}.FindAll("needle")}; // No warning with Clang 22.1.8 :/
    +    (void)bar;
    +
    +    HTTPHeaders test;
    +    auto baz = test.FindAll("needle");
    +    test.Write("needle", "mutation"); // No warning with Clang 22.1.8 :/
    +
         {
             // Writing response headers
             HTTPHeaders headers{};
    

    </details>

    Clang 24 has experimental invalidation detection so maybe that could be used in the far future: https://clang.llvm.org/docs/LifetimeSafety.html#use-after-invalidation-experimental

    Alternative solution A)

    Return by copy everywhere. Might introduce more heap activity, especially in the case of HTTPRemoteClient::GetRequest().

    Alternative solution B)

    Refactor the methods to minimize copying while still making things more memory-safe. Replacing HTTPHeaders::FindAll() with an Iterate()-function taking a lambda which gets to process each header. Gets rid of the heap activity of building a vector but introduces copying of first.

    <details><summary>Diff of httpserver.cpp/h</summary>

    --- a/src/httpserver.cpp
    +++ b/src/httpserver.cpp
    @@ -272,15 +272,11 @@ std::optional<std::string> HTTPHeaders::FindFirst(const std::string_view key) co
         return std::nullopt;
     }
     
    -std::vector<std::string_view> HTTPHeaders::FindAll(const std::string_view key) const
    +void HTTPHeaders::Iterate(std::function<void(const std::string& key, const std::string& value)> fn) const
     {
    -    std::vector<std::string_view> ret;
         for (const auto& item : m_headers) {
    -        if (CaseInsensitiveEqual(key, item.first)) {
    -            ret.push_back(item.second);
    -        }
    +        fn(item.first, item.second);
         }
    -    return ret;
     }
     
     void HTTPHeaders::Write(std::string&& key, std::string&& value)
    @@ -504,18 +500,21 @@ bool HTTPRequest::LoadBody(LineReader& reader)
             // We read all the chunks but never got the last chunk, wait for client to send more
             return false;
         } else {
    +        std::optional<std::string> first;
    +        m_headers.Iterate([&first] (const std::string& key, const std::string& value) {
    +            if (!CaseInsensitiveEqual(key, "Content-Length")) return;
    +            if (!first.has_value()) {
    +                first = value;
    +            } else if (first != value) {
    +                // Duplicate Content-Length headers are allowed only if they all have the same value
    +                // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3
    +                throw std::runtime_error("Differing Content-Length values");
    +            }
    +        });
             // No Content-length or Transfer-Encoding header means no body, see libevent evhttp_get_body()
    -        auto content_length_values{m_headers.FindAll("Content-Length")};
    -        if (content_length_values.empty()) return true;
    -
    -        // Duplicate Content-Length headers are allowed only if they all have the same value
    -        // https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3
    -        const auto& first_content_length_value{content_length_values[0]};
    -        for (size_t i = 1; i < content_length_values.size(); ++i) {
    -            if (content_length_values[i] != first_content_length_value) throw std::runtime_error("Differing Content-Length values");
    -        }
    +        if (!first.has_value()) return true;
     
    -        const auto content_length{ToIntegral<uint64_t>(first_content_length_value)};
    +        const auto content_length{ToIntegral<uint64_t>(first.value())};
             if (!content_length) throw std::runtime_error("Cannot parse Content-Length value");
     
             if (*content_length > MAX_BODY_SIZE) throw ContentTooLargeError("Max body size exceeded");
    --- a/src/httpserver.h
    +++ b/src/httpserver.h
    @@ -97,10 +97,9 @@ public:
          */
         std::optional<std::string> FindFirst(std::string_view key) const;
         /**
    -     * [@param](/bitcoin-bitcoin/contributor/param/)[in] key The field-name of the header to search for
    -     * [@returns](/bitcoin-bitcoin/contributor/returns/) Views into all values matching the provided key (valid while this object is alive)
    +     * [@param](/bitcoin-bitcoin/contributor/param/)[in] fn Receives each header as they are iterated through.
          */
    -    std::vector<std::string_view> FindAll(std::string_view key) const LIFETIMEBOUND;
    +    void Iterate(std::function<void(const std::string& key, const std::string& value)> fn) const;
         void Write(std::string&& key, std::string&& value);
         /**
          * [@param](/bitcoin-bitcoin/contributor/param/)[in] key The field-name of the header to search for and delete
    

    </details>

    Rationale

    The methods are not called in many places so risk of misuse is low, and we avoid any risk of performance degradation (such as the one found in #35182 (review)). Returning copies without adding mutexes or other thread safety measures does not considerably increase thread-safety.

  2. http: Add missing LIFETIMEBOUND annotations
    Decreases footguns without any hit to runtime performance.
    0b46fc9c59
  3. DrahtBot added the label RPC/REST/ZMQ on Sep 9, 2026
  4. DrahtBot commented at 8:06 AM on September 9, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36198.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK maflcko, l0rinc

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. maflcko commented at 8:46 AM on September 18, 2026: member

    lgtm ACK 0b46fc9c5935068e7068a27fa39fa11f32af7a3f

    They probably won't find any real issues, but I guess it can't hurt as an annotation

  6. l0rinc commented at 5:25 PM on September 18, 2026: contributor

    code review ACK 0b46fc9c5935068e7068a27fa39fa11f32af7a3f

    Agree that it's not the most important change, but I appreciate the consistency. (The PR description could be simplified and some typos adjusted)

  7. fanquake merged this on Sep 19, 2026
  8. fanquake closed this on Sep 19, 2026


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-21 02:52 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me