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

  9. Kino1994 referenced this in commit 8fe6ed91c6 on Sep 20, 2026
  10. hodlinator deleted the branch on Sep 21, 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-10-14 17:51 UTC

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