Skip to content

PtmfHelper: Use NOLINT to disable UndefinedBinaryOperatorResult error#2334

Merged
kentonv merged 1 commit into
capnproto:masterfrom
ryanofsky:pr/lint
Jun 21, 2025
Merged

PtmfHelper: Use NOLINT to disable UndefinedBinaryOperatorResult error#2334
kentonv merged 1 commit into
capnproto:masterfrom
ryanofsky:pr/lint

Conversation

@ryanofsky

Copy link
Copy Markdown
Contributor

Disable clang-analyzer-core.UndefinedBinaryOperatorResult error in PtmfHelper using a NOLINT comment to appease clang-tidy.

A complete description of the error and rationale for the change is in the commit message.

Disable clang-analyzer-core.UndefinedBinaryOperatorResult error in PtmfHelper to
appease clang-tidy.

This project does not use clang-tidy, but downstream projects may. Normally
errors from Cap'n Proto headers will not show up in downstream projects as long
as they are included via -isystem, but in some cases it is impossible to hide
errors even from system headers and other excluded headers if clang-tidy decides
the errors are associated with the "main file" of the translation unit. This
seems to be a longstanding issue that's discussed in
https://reviews.llvm.org/D26418.

Adding NOLINT here is helpful to downstream projects that don't currently have a
way of suppressing this error without turning it off more globally, and could
be helpful to Cap'n Proto if it uses clang-tidy or clang-analyzer in the future.

This particular tidy error is a false positive from clang-analzyer
(https://clang-analyzer.llvm.org/) warning that the vtable pointer
`*(char**)obj` is a garbage value rather than a valid memory address. The
analyzer has no way of knowing it could be a valid address since it's only valid
due to assumptions about the ABI.

The complete error message from a downstream project looks like:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 20, 2025
Reported by fanquake <fanquake@gmail.com>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

Error is spurious and comes from kj/async-inl.h and it seems to be shown
because clang-tidy considers this error to come from "main file" of the
translation unit (see https://clang.llvm.org/extra/clang-tidy/,
https://stackoverflow.com/a/47611238, https://reviews.llvm.org/D26418) even
though this is not the case.

Even though the header is included via -isystem and clang-tidy
--dump-config shows "SystemHeaders: false" the error is still shown.

I also tried to suppress the error by editing .clang-tidy to include:

  HeaderFilterRegex: '.*'
  ExcludeHeaderFilterRegex: '.*/include/kj/async-inl\.h$'

and many variations but the error is always shown, I assume because
ExcludeHeaderFilterRegex does not ignore the isInMainFile condition
(https://github.com/llvm/llvm-project/pull/91400/files).

Adding NOLINT to the getLocalServer() line in types-context.h at the boundary
between libmultiprocess and Cap'n Proto code also does not suppress the error.
It does suppress clang-tidy "note:" lines below the NOLINT point in the call
stack, making the error messages shorter, but the only way of suppressing the
error completely seems to be either adding NOLINT inside the Cap'n Proto
header, which requires a patch, or adding it to all top-level callers of the
getLocalServer() function in .cpp files, which seems impractical and overbroad,
and I didn't attempt.

The actual warning is a false positive that comes from ABI-specific code in
Cap'n Proto that gets the starting function address (that can be passed to
addr2line) from a lambda or function object. This code calls a helper to get
the starting function address from a pointer-to-member-function, which in this
case is the the operator() member function. That code handles pointers to
virtual member functions, so it checks if the pointer is virtual by testing its
low-order bit, and if set, assumes the first bytes of the object are a vtable
pointer, and does pointer arithmetic with the vtable address. Clang-tidy
complains about this because it does not know the vtable address is valid,
assuming incorrectly it is a "garbage value".

I wrote patch adding NOLINT to Cap'n Proto in
capnproto/capnproto#2334 which could be good long-term
fix for this issue if clang-tidy does not provide a better way of suppressing
the error.

Complete error output is:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
@kentonv kentonv merged commit a0120f1 into capnproto:master Jun 21, 2025
15 of 16 checks passed
@kentonv

kentonv commented Jun 21, 2025

Copy link
Copy Markdown
Member

Thanks!

ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 23, 2025
Reported by fanquake <fanquake@gmail.com>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

Error is spurious and comes from kj/async-inl.h and should be suppressed in the
next version of capnproto capnproto/capnproto#2334

The error is a clang-analyzer false positive that comes from ABI-specific code
in Cap'n Proto that gets the starting function address (that can be passed to
addr2line) from a lambda or function object. This code calls a helper to get
the starting function address from a pointer-to-member-function, which in this
case is the the operator() member function. That code handles pointers to
virtual member functions, so it checks if the pointer is virtual by testing its
low-order bit, and if set, assumes the first bytes of the object are a vtable
pointer, and does pointer arithmetic with the vtable address. Clang-tidy
complains about this because it does not know the vtable address is valid,
assuming incorrectly it is a "garbage value".

This change turns off the UndefinedBinaryOperatorResult altogether instead of
suppressing this one instance because clang-tidy incorrectly considers this
error to come from "main file" of the translation unit (see
https://clang.llvm.org/extra/clang-tidy/, https://stackoverflow.com/a/47611238,
https://reviews.llvm.org/D26418). So it is not suppressed even though the
header is included via -isystem and clang-tidy --dump-config shows
"SystemHeaders: false". It is also not suppressed when exclude patterns are
added to the .clang-tidy configuration like:

  HeaderFilterRegex: '.*'
  ExcludeHeaderFilterRegex: '.*/include/kj/async-inl\.h$'

This has no effect because ExcludeHeaderFilterRegex does not override the
isInMainFile condition (https://github.com/llvm/llvm-project/pull/91400/files).

Adding NOLINT to the getLocalServer() line in types-context.h at the boundary
between libmultiprocess and Cap'n Proto code also does not suppress the error.
It does suppress clang-tidy "note:" lines below the NOLINT point in the call
stack, making the error messages shorter, but the only way of suppressing the
error completely seems to be either adding NOLINT inside the Cap'n Proto
header, which requires a patch, or adding it to all top-level callers of the
getLocalServer() function in .cpp files, which seems impractical and overbroad,
and I didn't attempt.

Complete error output is:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <fanquake@gmail.com>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

Error is spurious and comes from kj/async-inl.h and should be suppressed in the
next version of capnproto capnproto/capnproto#2334

The error is a clang-analyzer false positive that comes from ABI-specific code
in Cap'n Proto that gets the starting function address (that can be passed to
addr2line) from a lambda or function object. This code calls a helper to get
the starting function address from a pointer-to-member-function, which in this
case is the the operator() member function. That code handles pointers to
virtual member functions, so it checks if the pointer is virtual by testing its
low-order bit, and if set, assumes the first bytes of the object are a vtable
pointer, and does pointer arithmetic with the vtable address. Clang-tidy
complains about this because it does not know the vtable address is valid,
assuming incorrectly it is a "garbage value".

This change turns off the UndefinedBinaryOperatorResult altogether instead of
suppressing this one instance because clang-tidy incorrectly considers this
error to come from "main file" of the translation unit (see
https://clang.llvm.org/extra/clang-tidy/, https://stackoverflow.com/a/47611238,
https://reviews.llvm.org/D26418). So it is not suppressed even though the
header is included via -isystem and clang-tidy --dump-config shows
"SystemHeaders: false". It is also not suppressed when exclude patterns are
added to the .clang-tidy configuration like:

  HeaderFilterRegex: '.*'
  ExcludeHeaderFilterRegex: '.*/include/kj/async-inl\.h$'

This has no effect because ExcludeHeaderFilterRegex does not override the
isInMainFile condition (https://github.com/llvm/llvm-project/pull/91400/files).

Adding NOLINT to the getLocalServer() line in types-context.h at the boundary
between libmultiprocess and Cap'n Proto code also does not suppress the error.
It does suppress clang-tidy "note:" lines below the NOLINT point in the call
stack, making the error messages shorter, but the only way of suppressing the
error completely seems to be either adding NOLINT inside the Cap'n Proto
header, which requires a patch, or adding it to all top-level callers of the
getLocalServer() function in .cpp files, which seems impractical and overbroad,
and I didn't attempt.

Complete error output is:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <fanquake@gmail.com>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

Error is spurious and comes from kj/async-inl.h and should be suppressed in the
next version of capnproto capnproto/capnproto#2334

The error is a clang-analyzer false positive that comes from ABI-specific code
in Cap'n Proto that gets the starting function address (that can be passed to
addr2line) from a lambda or function object. This code calls a helper to get
the starting function address from a pointer-to-member-function, which in this
case is the the operator() member function. That code handles pointers to
virtual member functions, so it checks if the pointer is virtual by testing its
low-order bit, and if set, assumes the first bytes of the object are a vtable
pointer, and does pointer arithmetic with the vtable address. Clang-tidy
complains about this because it does not know the vtable address is valid,
assuming incorrectly it is a "garbage value".

This change turns off the UndefinedBinaryOperatorResult altogether instead of
suppressing this one instance because clang-tidy incorrectly considers this
error to come from "main file" of the translation unit (see
https://clang.llvm.org/extra/clang-tidy/, https://stackoverflow.com/a/47611238,
https://reviews.llvm.org/D26418). So it is not suppressed even though the
header is included via -isystem and clang-tidy --dump-config shows
"SystemHeaders: false". It is also not suppressed when exclude patterns are
added to the .clang-tidy configuration like:

  HeaderFilterRegex: '.*'
  ExcludeHeaderFilterRegex: '.*/include/kj/async-inl\.h$'

This has no effect because ExcludeHeaderFilterRegex does not override the
isInMainFile condition (https://github.com/llvm/llvm-project/pull/91400/files).

Adding NOLINT to the getLocalServer() line in types-context.h at the boundary
between libmultiprocess and Cap'n Proto code also does not suppress the error.
It does suppress clang-tidy "note:" lines below the NOLINT point in the call
stack, making the error messages shorter, but the only way of suppressing the
error completely seems to be either adding NOLINT inside the Cap'n Proto
header, which requires a patch, or adding it to all top-level callers of the
getLocalServer() function in .cpp files, which seems impractical and overbroad,
and I didn't attempt.

Complete error output is:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <fanquake@gmail.com>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

Error is spurious and comes from kj/async-inl.h and should be suppressed in the
next version of capnproto capnproto/capnproto#2334

The error is a clang-analyzer false positive that comes from ABI-specific code
in Cap'n Proto that gets the starting function address (that can be passed to
addr2line) from a lambda or function object. This code calls a helper to get
the starting function address from a pointer-to-member-function, which in this
case is the the operator() member function. That code handles pointers to
virtual member functions, so it checks if the pointer is virtual by testing its
low-order bit, and if set, assumes the first bytes of the object are a vtable
pointer, and does pointer arithmetic with the vtable address. Clang-tidy
complains about this because it does not know the vtable address is valid,
assuming incorrectly it is a "garbage value".

This change turns off the UndefinedBinaryOperatorResult altogether instead of
suppressing this one instance because clang-tidy incorrectly considers this
error to come from "main file" of the translation unit (see
https://clang.llvm.org/extra/clang-tidy/, https://stackoverflow.com/a/47611238,
https://reviews.llvm.org/D26418). So it is not suppressed even though the
header is included via -isystem and clang-tidy --dump-config shows
"SystemHeaders: false". It is also not suppressed when exclude patterns are
added to the .clang-tidy configuration like:

  HeaderFilterRegex: '.*'
  ExcludeHeaderFilterRegex: '.*/include/kj/async-inl\.h$'

This has no effect because ExcludeHeaderFilterRegex does not override the
isInMainFile condition (https://github.com/llvm/llvm-project/pull/91400/files).

Adding NOLINT to the getLocalServer() line in types-context.h at the boundary
between libmultiprocess and Cap'n Proto code also does not suppress the error.
It does suppress clang-tidy "note:" lines below the NOLINT point in the call
stack, making the error messages shorter, but the only way of suppressing the
error completely seems to be either adding NOLINT inside the Cap'n Proto
header, which requires a patch, or adding it to all top-level callers of the
getLocalServer() function in .cpp files, which seems impractical and overbroad,
and I didn't attempt.

Complete error output is:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <fanquake@gmail.com>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

Error is spurious and comes from kj/async-inl.h and should be suppressed in the
next version of capnproto capnproto/capnproto#2334

The error is a clang-analyzer false positive that comes from ABI-specific code
in Cap'n Proto that gets the starting function address (that can be passed to
addr2line) from a lambda or function object. This code calls a helper to get
the starting function address from a pointer-to-member-function, which in this
case is the the operator() member function. That code handles pointers to
virtual member functions, so it checks if the pointer is virtual by testing its
low-order bit, and if set, assumes the first bytes of the object are a vtable
pointer, and does pointer arithmetic with the vtable address. Clang-tidy
complains about this because it does not know the vtable address is valid,
assuming incorrectly it is a "garbage value".

This change turns off the UndefinedBinaryOperatorResult altogether instead of
suppressing this one instance because clang-tidy incorrectly considers this
error to come from "main file" of the translation unit (see
https://clang.llvm.org/extra/clang-tidy/, https://stackoverflow.com/a/47611238,
https://reviews.llvm.org/D26418). So it is not suppressed even though the
header is included via -isystem and clang-tidy --dump-config shows
"SystemHeaders: false". It is also not suppressed when exclude patterns are
added to the .clang-tidy configuration like:

  HeaderFilterRegex: '.*'
  ExcludeHeaderFilterRegex: '.*/include/kj/async-inl\.h$'

This has no effect because ExcludeHeaderFilterRegex does not override the
isInMainFile condition (https://github.com/llvm/llvm-project/pull/91400/files).

Adding NOLINT to the getLocalServer() line in types-context.h at the boundary
between libmultiprocess and Cap'n Proto code also does not suppress the error.
It does suppress clang-tidy "note:" lines below the NOLINT point in the call
stack, making the error messages shorter, but the only way of suppressing the
error completely seems to be either adding NOLINT inside the Cap'n Proto
header, which requires a patch, or adding it to all top-level callers of the
getLocalServer() function in .cpp files, which seems impractical and overbroad,
and I didn't attempt.

Complete error output is:

/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: error: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult,-warnings-as-errors]
  609 |       return *(void**)(*(char**)obj + voff);
      |                                     ^
build/test/mp/test/foo.capnp.proxy-server.c++:93:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>, mp::ServerField<0, mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   93 |     return serverInvoke(*this, call_context, MakeServerField<0, Accessor<foo_fields::Context, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT>>(ServerCall())));
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:739:16: note: Calling 'ReplaceVoid<(lambda at include/mp/proxy-types.h:739:28), (lambda at include/mp/proxy-types.h:740:13)>'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  740 |             [&]() { return kj::Promise<CallContext>(kj::mv(call_context)); })
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:19: note: 'is_same_v' is false
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking false branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:704:16: note: Calling 'operator()'
  704 |         return fn();
      |                ^~~~
include/mp/proxy-types.h:739:43: note: Calling 'ServerField::invoke'
  739 |         return ReplaceVoid([&]() { return fn.invoke(server_context, ArgList()); },
      |                                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:563:16: note: Calling 'PassField<mp::Accessor<mp::foo_fields::Context, 17>, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooFn>, capnp::CallContext<mp::test::messages::FooFn::CallParams, mp::test::messages::FooFn::CallResults>>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>, mp::TypeList<>>'
  563 |         return PassField<Accessor>(Priority<2>(),
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  564 |             typename Split<argc, ArgTypes>::First(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  565 |             server_context,
      |             ~~~~~~~~~~~~~~~
  566 |             this->parent(),
      |             ~~~~~~~~~~~~~~~
  567 |             typename Split<argc, ArgTypes>::Second(),
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  568 |             std::forward<Args>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-context.h:151:12: note: Calling 'CapabilityServerSet::getLocalServer'
  151 |     return server.m_context.connection->m_threads.getLocalServer(thread_client)
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/capability.h:1274:10: note: Calling 'Promise::then'
 1274 |   return getLocalServerInternal(client)
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1275 |       .then([](void* server) -> kj::Maybe<typename T::Server&> {
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1276 |     if (server == nullptr) {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~
 1277 |       return nullptr;
      |       ~~~~~~~~~~~~~~~
 1278 |     } else {
      |     ~~~~~~~~
 1279 |       return *reinterpret_cast<typename T::Server*>(server);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1280 |     }
      |     ~
 1281 |   });
      |   ~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:1295:32: note: Calling 'GetFunctorStartAddress::apply'
 1295 |   void* continuationTracePtr = _::GetFunctorStartAddress<_::FixVoid<T>&&>::apply(func);
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:677:12: note: Calling 'PtmfHelper::apply'
  677 |     return PtmfHelper::from<ReturnType, Decay<Func>, ParamTypes...>(
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  678 |         &Decay<Func>::operator()).apply(&func);
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:9: note: Assuming the condition is true
  606 |     if (voff & 1) {
      |         ^~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:606:5: note: Taking true branch
  606 |     if (voff & 1) {
      |     ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/kj/async-inl.h:609:37: note: The left operand of '+' is a garbage value
  609 |       return *(void**)(*(char**)obj + voff);
      |                        ~~~~~~~~~~~~ ^
ryanofsky added a commit to ryanofsky/bitcoin that referenced this pull request Sep 2, 2025
Disable `clang-analyzer-core.UndefinedBinaryOperatorResult` warning because it
produces a false positive in a Cap'n Proto header:

/usr/include/kj/async-inl.h:609:37: warning: The left operand of '+' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult]
  609 |       return *(void**)(*(char**)obj + voff);

This was reported by fanquake in
bitcoin#33256 and was previously addressed in
bitcoin-core/libmultiprocess@7631345
which changed the analyzer result to be a warning instead of an error. PR
capnproto/capnproto#2334 was also merged upstream to
prevent this in future versions of Cap'n Proto.

Unfortunately it does not seem possible suppress the false-positive warning
from this header without turning off the check for bitcoin code as well. The
header included with -isystem so it is treated as a third party header, but
clang-tidy has a longstanding issue discussed in
https://reviews.llvm.org/D26418 where it ignores -isystem and ignores
HeaderFilterRegex and ExcludeHeaderFilterRegex directives if it decides that
errors are associated with the "main file" of the translation unit, which is
the case here. (See "isInMainFile" in
https://github.com/llvm/llvm-project/pull/91400/files)

The tidy error is a false positive from clang-analzyer
(https://clang-analyzer.llvm.org/) warning that the vtable pointer
`*(char**)obj` is a garbage value rather than a valid memory address. The
analyzer has no way of knowing it could be a valid address since it's only
valid due to assumptions about the ABI.

The purpose of the code is to get a starting function address that can be
passed to addr2line from a lambda or function object. The function
`GetFunctorStartAddress` calls a helper `PtmfHelper::apply` to get the starting
function address from a pointer-to-member-function value pointing at the
operator() method. The helper needs to handle virtual methods, so it checks if
the pointer is virtual by testing its low-order bit, and if set, assumes the
first bytes of the object are a vtable pointer, and does pointer arithmetic
with the vtable address. Clang-tidy complains about this because it does not
know the vtable address is valid, assuming incorrectly it is a "garbage value".
hebasto added a commit to hebasto/bitcoin that referenced this pull request Sep 4, 2025
The warnings are false positive and have been fixed upstream.
See: capnproto/capnproto#2334.

This change disables the `UndefinedBinaryOperatorResult` clang-tidy
check for source files generated by the `mpgen` tool.
achow101 added a commit to bitcoin/bitcoin that referenced this pull request Sep 8, 2025
…ck in `src/ipc`

589b65f clang-tidy: Disable `UndefinedBinaryOperatorResult` check in `src/ipc` (Hennadii Stepanov)

Pull request description:

  The warnings are false positive and have been fixed upstream. See: capnproto/capnproto#2334.

  This PR:

  1. Disables the `UndefinedBinaryOperatorResult` clang-tidy check for source files generated by the `mpgen` tool.

  2. Is an alternative to the draft #33281.

  3. Fixes #33256.

ACKs for top commit:
  Sjors:
    ACK 589b65f
  fjahr:
    ACK 589b65f
  achow101:
    ACK 589b65f
  ryanofsky:
    Code review ACK 589b65f. Thanks for the fix!

Tree-SHA512: 6d376a82641a5b85d4dd1fa164fdcbd8e15f1262e7d4f582f4d9959031d35852e28ff1b8268336e39ba6779fdd10ecdb986af42407d0545f4217f41d64556272
Kino1994 pushed a commit to Kino1994/bitcoin-full-history that referenced this pull request Jun 28, 2026
The warnings are false positive and have been fixed upstream.
See: capnproto/capnproto#2334.

This change disables the `UndefinedBinaryOperatorResult` clang-tidy
check for source files generated by the `mpgen` tool.
Kino1994 pushed a commit to Kino1994/bitcoin-full-history that referenced this pull request Jun 28, 2026
…atorResult` check in `src/ipc`

96d0687 clang-tidy: Disable `UndefinedBinaryOperatorResult` check in `src/ipc` (Hennadii Stepanov)

Pull request description:

  The warnings are false positive and have been fixed upstream. See: capnproto/capnproto#2334.

  This PR:

  1. Disables the `UndefinedBinaryOperatorResult` clang-tidy check for source files generated by the `mpgen` tool.

  2. Is an alternative to the draft bitcoin/bitcoin#33281.

  3. Fixes bitcoin/bitcoin#33256.

ACKs for top commit:
  Sjors:
    ACK 96d0687
  fjahr:
    ACK 96d0687
  achow101:
    ACK 96d0687
  ryanofsky:
    Code review ACK 96d0687. Thanks for the fix!

Tree-SHA512: 6d376a82641a5b85d4dd1fa164fdcbd8e15f1262e7d4f582f4d9959031d35852e28ff1b8268336e39ba6779fdd10ecdb986af42407d0545f4217f41d64556272
BigcoinBGC pushed a commit to BigcoinBGC/bigcoin that referenced this pull request Jun 30, 2026
The warnings are false positive and have been fixed upstream.
See: capnproto/capnproto#2334.

This change disables the `UndefinedBinaryOperatorResult` clang-tidy
check for source files generated by the `mpgen` tool.
BigcoinBGC pushed a commit to BigcoinBGC/bigcoin that referenced this pull request Jun 30, 2026
…atorResult` check in `src/ipc`

c15da4f clang-tidy: Disable `UndefinedBinaryOperatorResult` check in `src/ipc` (Hennadii Stepanov)

Pull request description:

  The warnings are false positive and have been fixed upstream. See: capnproto/capnproto#2334.

  This PR:

  1. Disables the `UndefinedBinaryOperatorResult` clang-tidy check for source files generated by the `mpgen` tool.

  2. Is an alternative to the draft bitcoin/bitcoin#33281.

  3. Fixes bitcoin/bitcoin#33256.

ACKs for top commit:
  Sjors:
    ACK c15da4f
  fjahr:
    ACK c15da4f
  achow101:
    ACK c15da4f
  ryanofsky:
    Code review ACK c15da4f. Thanks for the fix!

Tree-SHA512: 6d376a82641a5b85d4dd1fa164fdcbd8e15f1262e7d4f582f4d9959031d35852e28ff1b8268336e39ba6779fdd10ecdb986af42407d0545f4217f41d64556272
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants