Skip to content

Add bitcoin-{node,gui} to release binaries for IPC #31802

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

Sjors
Copy link
Member

@Sjors Sjors commented Feb 5, 2025

Have depends make libmultiprocess by default. This PR causes the following behavior changes:

  1. bitcoin-node and bitcoin-gui binaries are included in releases, due to ENABLE_IPC option being switched on by default in depends builds
  2. ENABLE_IPC is also switched on by default in non-depends builds
  3. Various changes to CI: switching on ENABLE_IPC on in most configurations and using bitcoin-node binary (bitcoin -m) for functional tests in two of them.
  4. The bitcoin-node and bitcoin-gui are added to Maintenance.cmake (since they're now in the release)
  5. macOS and Linux installation instructions are updated to install capnp by default

This PR doesn't need to do all of 3 things at once. However it's is simpler, avoids code churn (especially in CI), and probably less confusing to make all these changes in the same PR.

Windows is not supported yet, so ENABLE_IPC is off by default for it. It can be enabled after #32387.

The initial main use case for IPC is to enable experimental support for the Mining IPC interface. A working example of a Stratum v2 Template Provider client using this interface can be found here: Sjors#48.

See #31756 for discussion of when this should happen. Supersedes #30975.

Guix hashes:

find guix-build-$(git rev-parse --short=12 HEAD)/output/ -type f -print0 | env LC_ALL=C sort -z | xargs -r0 sha256sum

8d9bd51935813d645de547989cf269b9343e7ed0a0285be4998dde88eead7a9b  guix-build-314b4282e6b7/output/aarch64-linux-gnu/SHA256SUMS.part
f7ce45937daf32d0d98da4c31f09435cfe30f3b154b68318a941910012448be7  guix-build-314b4282e6b7/output/aarch64-linux-gnu/bitcoin-314b4282e6b7-aarch64-linux-gnu-debug.tar.gz
019314d6eb46d872448eedfdd1c71e32e480ae8d74ae3027e92818fb051c8575  guix-build-314b4282e6b7/output/aarch64-linux-gnu/bitcoin-314b4282e6b7-aarch64-linux-gnu.tar.gz
a5f172803559cd5346a0e4b5ee114455e43654c83382626ec1b1ba5262b7208a  guix-build-314b4282e6b7/output/arm-linux-gnueabihf/SHA256SUMS.part
e56433483eff55937660d0343a63402df12eef0828c7247caf47ee71d1707520  guix-build-314b4282e6b7/output/arm-linux-gnueabihf/bitcoin-314b4282e6b7-arm-linux-gnueabihf-debug.tar.gz
9d2c1c5027661b190bd5f4289a27df0f64ef63ab3743615168ed91b400e1f14d  guix-build-314b4282e6b7/output/arm-linux-gnueabihf/bitcoin-314b4282e6b7-arm-linux-gnueabihf.tar.gz
5f321008591b38cb8a0b5f6c34ea96bff216ce5e5b4f614f7db8a4bb44423394  guix-build-314b4282e6b7/output/arm64-apple-darwin/SHA256SUMS.part
132209273bbca549d3cf2a602c5c9782f8830cd8c743a19b44a786c762320c1a  guix-build-314b4282e6b7/output/arm64-apple-darwin/bitcoin-314b4282e6b7-arm64-apple-darwin-codesigning.tar.gz
133fa0b50d2ed289d76ec4760a36c9f87bae380f9e7c7707dbd6fbf770916ff9  guix-build-314b4282e6b7/output/arm64-apple-darwin/bitcoin-314b4282e6b7-arm64-apple-darwin-unsigned.tar.gz
e4e0f4d83b72d971d02a159d37b6dda5a4baf2802213e7bb9e0d237591c03bbc  guix-build-314b4282e6b7/output/arm64-apple-darwin/bitcoin-314b4282e6b7-arm64-apple-darwin-unsigned.zip
1c93cb9d2300d10dd25df23e64aa825f9c5fd9fab269747e03a23881154677cc  guix-build-314b4282e6b7/output/dist-archive/bitcoin-314b4282e6b7.tar.gz
0f8974e09e51d1a59726409d5c725e2bee86983661e0b9098f22c004de86ed88  guix-build-314b4282e6b7/output/powerpc64-linux-gnu/SHA256SUMS.part
085e4900cbff6971847b50b8ef6f8473688fa3e56ef65f20159bb2f2252f5ede  guix-build-314b4282e6b7/output/powerpc64-linux-gnu/bitcoin-314b4282e6b7-powerpc64-linux-gnu-debug.tar.gz
9a663918b86b8c29d93918f3697bcbf7a0cc80f4885fe283d348bcc7384e978c  guix-build-314b4282e6b7/output/powerpc64-linux-gnu/bitcoin-314b4282e6b7-powerpc64-linux-gnu.tar.gz
3defb3b271a9778bfa2bebd025ccd6ae3988d1cbb2a7a607eafac3adc0b35e15  guix-build-314b4282e6b7/output/riscv64-linux-gnu/SHA256SUMS.part
723a1c6a89b0cc9b774fbfa6857427faacb3b9f3a28bd72938dae8ba89b166f9  guix-build-314b4282e6b7/output/riscv64-linux-gnu/bitcoin-314b4282e6b7-riscv64-linux-gnu-debug.tar.gz
bf4bf38fd17ba827d65ba7951583f2c38c08f68e93d291cb1131cbf63479294a  guix-build-314b4282e6b7/output/riscv64-linux-gnu/bitcoin-314b4282e6b7-riscv64-linux-gnu.tar.gz
fb6d15d0fe5ea55333eef884f69df6d1f5be430fc39fd8aac250ebcb24cdbe74  guix-build-314b4282e6b7/output/x86_64-apple-darwin/SHA256SUMS.part
7de5838ff01c95936d393bd4d06742fc1cf8dbf2554e33dcea13c88cd387a4cc  guix-build-314b4282e6b7/output/x86_64-apple-darwin/bitcoin-314b4282e6b7-x86_64-apple-darwin-codesigning.tar.gz
9208da57891f086535d853fbbdc586bb0b44ed39f9176668713378c560cc0551  guix-build-314b4282e6b7/output/x86_64-apple-darwin/bitcoin-314b4282e6b7-x86_64-apple-darwin-unsigned.tar.gz
4c9ab82aa07a6e5e81db6d3eddf31b8037572459bcfd5c50c239d5f62b16dc03  guix-build-314b4282e6b7/output/x86_64-apple-darwin/bitcoin-314b4282e6b7-x86_64-apple-darwin-unsigned.zip
0d1fa42994ef77e6270998f77202aaece69aab21f50538ff4524f3fa17cad7ce  guix-build-314b4282e6b7/output/x86_64-linux-gnu/SHA256SUMS.part
11a7132cb17d9b0c01a60f01c787fa3e7585e7bef0c6a65fc4d2d38a054a92c2  guix-build-314b4282e6b7/output/x86_64-linux-gnu/bitcoin-314b4282e6b7-x86_64-linux-gnu-debug.tar.gz
8bb2becd3a3b877d46fcbd1faa44820792d5d10d21310f7ec58cda441b655ef8  guix-build-314b4282e6b7/output/x86_64-linux-gnu/bitcoin-314b4282e6b7-x86_64-linux-gnu.tar.gz
f71368af3b980382248fb3a06737bffbe50c348209ec2184127cccee171e096b  guix-build-314b4282e6b7/output/x86_64-w64-mingw32/SHA256SUMS.part
3183bd16614126a90ee540e2d4f74416b2921751b4d76411be70b1ca46c1af59  guix-build-314b4282e6b7/output/x86_64-w64-mingw32/bitcoin-314b4282e6b7-win64-codesigning.tar.gz
77b5662fe84454580541e33269a89ebf3c3b832e79336a1ca3b964e3a8c41bcd  guix-build-314b4282e6b7/output/x86_64-w64-mingw32/bitcoin-314b4282e6b7-win64-debug.zip
5c92b24ab15a38d69392080c655aff444857f5991fb95df75dc53caf71a7f9ce  guix-build-314b4282e6b7/output/x86_64-w64-mingw32/bitcoin-314b4282e6b7-win64-setup-unsigned.exe
4089ac96ea38eedc935231601d496e6456df19dfdf86b5e5b7f2d698c4767de5  guix-build-314b4282e6b7/output/x86_64-w64-mingw32/bitcoin-314b4282e6b7-win64-unsigned.zip

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 5, 2025

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

Code Coverage & Benchmarks

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

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK ryanofsky
Concept ACK TheCharlatan
Stale ACK vasild

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32162 (depends: Switch from multilib to platform-specific toolchains by hebasto)
  • #31349 (ci: detect outbound internet traffic generated while running tests by vasild)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@sipa
Copy link
Member

sipa commented Feb 7, 2025

Some chatter from IRC:

17:21:41 < darosior> It might be confusing to release both a bitcoin-wallet utility and a bitcoin-wallet binary as part of multiprocess?
17:24:08 < darosior> We could rename the utility, but then it would be nice to at least have one deprecation cycle. Given recent momentum i estimate it's possible we might release multiprocess in 
                     30.0, which means if we want to deprecate the bitcoin-wallet utility name we should do it.. now?
17:33:31 < sipa> bitcoin-wallet-util ?
...
23:38:57 < _aj_> darosior: bitcoin-wallet-process, bitcoin-gui-process, etc? it's multi *-process!
03:04:34 < Sjors[m]> darosior: at the moment there is no wallet binary if were to enable multiprocess. That won't happen until #19460.
...
03:05:18 < Sjors[m]> Or maybe already in #10102
...
03:05:50 < Sjors[m]> In any case #31802 only adds bitcoin-node and bitcoin-gui.
...
03:07:19 < Sjors[m]> Though if we want to rename the utility eventually, it's always better to do it early.

@Sjors
Copy link
Member Author

Sjors commented Feb 8, 2025

@sipa I opened #31827

onlinesipahimithu

This comment was marked as spam.

Sjors and others added 6 commits June 23, 2025 11:46
This causes IPC binaries (bitcoin-node, bitcoin-gui) to be included
in releases.

The effect on CI is that this causes more depends builds to build IPC
binaries, but still the only build running functional tests with them
is the i686_multiprocess one.

Except for Windows.
The bitcoin-node binary is built on all platforms which have
multiprocess enabled, but for functional tests it's only used in
CentOS native (depends) job. The next commit will also add a
non-depends job.
Install capnp on non-depends CI jobs.

Use the bitcoin-node binary in the macOS native non-depends job.

Co-authored-by: Ryan Ofsky <[email protected]>
@Sjors Sjors force-pushed the 2025/02/ipc-yea branch from 5a66729 to 314b428 Compare June 23, 2025 09:57
@Sjors
Copy link
Member Author

Sjors commented Jun 23, 2025

Rebased after the changes in 5a66729 were merged as part of #32781. No need to worry about commit ordering :-)

Added release note.

I tried to include the changes in bitcoin-core/libmultiprocess#184 to see if they resolve all tidy issues, but that requires additional changes on the Bitcoin Core side (will report on that PR).

Marking as ready for review, because the tidy fix doesn't seem blocking to me - now that a fix is being worked on. Happy to rebase after it lands though.

@Sjors Sjors marked this pull request as ready for review June 23, 2025 09:59
Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review ACK 314b428. Just added release notes and renamed CI job since last review.


re: #31802 (comment)

I tried to include the changes in bitcoin-core/libmultiprocess#184 to see if they resolve all tidy issues, but that requires additional changes on the Bitcoin Core side (will report on that PR).

Sorry, I think the changes needed are in d234efe from #32345, but I should open a separate PR to bump the subtree and include them. And based on your comment bitcoin-core/libmultiprocess#184 (comment) I probably need to extend them to work with macos & libc++. I'll try to make progress on these things and bitcoin-core/libmultiprocess#184 to avoid adding noise to the tidy output.

@@ -0,0 +1 @@
- Two new binaries are added to the release: `bitcoin-node` (or `bitcoin -m node`) and `bitcoin-gui` (or `bitcoin -m gui`). These enable IPC functionality, initially only for the (experimental) Mining interface. Building the binaries is controlled by `ENABLE_IPC` (enabled by default, except for Windows). When building from source without depends, `capnp` needs to be installed. (#31802)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "doc: add release note" (314b428)

This seems ok but may want to rephrase it at some point (#31679 could be a good place) to be more about the new IPC feature (-ipcbind) than the new binaries. The binaries are kind of an implementation detail, and there should be no need to use them directly if the bitcoin -m is used.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll tweak them a bit if I need to push again, but otherwise it can indeed be done later.

@DrahtBot DrahtBot requested a review from vasild June 23, 2025 13:13
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 23, 2025
This error should not be a real problem because code is taking an invalid
reference to an empty object that has no state and could never be used. But
taking the reference could technically be undefined behavior.

Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/proxy-types.h:134:5: error: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^
build/test/mp/test/foo.capnp.proxy-server.c++:51:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall>>>'
   51 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT | FIELD_BOXED>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 17>, mp::test::FooCustom, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>>, const mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooCustom>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooCustom, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test/mp/test/foo-types.h:51:12: note: Calling 'ReadDestEmplace::update'
   51 |     return read_dest.update([&](FooCustom& value) {
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   53 |         value.v2 = custom.getV2();
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
   54 |     });
      |     ~~
include/mp/proxy-types.h:112:23: note: 'is_const_v' is false
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:112:9: note: Taking false branch
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |         ^
include/mp/proxy-types.h:122:13: note: Calling 'operator()'
  122 |             update_fn(temp);
      |             ^~~~~~~~~~~~~~~
test/mp/test/foo-types.h:52:113: note: Calling 'ReadDestTemp<std::basic_string<char>>'
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |                                                                                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:134:5: note: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  135 |         return LocalType{std::forward<decltype(args)>(args)...};
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  136 |     }};
      |     ~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 23, 2025
Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/type-number.h:55:32: error: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum' [clang-analyzer-optin.core.EnumCastOutOfRange,-warnings-as-errors]
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^
test/mp/test/foo.h:26:12: note: enum declared here
   26 | enum class FooEnum : uint8_t { ONE = 1, TWO = 2, };
      | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy-server.c++:63:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 1>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   63 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 1>, mp::test::FooEnum, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>>, const 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooEnum>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooEnum, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::test::FooEnum, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Calling 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Calling 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Calling 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Calling 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:7: note: Assuming the condition is false
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:3: note: Taking false branch
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |   ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1102:5: note: Returning zero
 1102 |     return static_cast<T>(0);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Returning from 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:3: note: Returning zero
 7042 |   return _reader.getDataField< ::int32_t>(
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Returning from 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:75: note: Returning zero
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                           ^~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Returning from 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:34: note: Returning zero
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Returning from 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/type-number.h:55:32: note: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 23, 2025
Reported by fanquake <[email protected]>
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);
      |                        ~~~~~~~~~~~~ ^
@TheCharlatan
Copy link
Contributor

Tangentially related to the topic here, have we discussed adding the capnp files to the releases somewhere yet? I'd be interested what the thoughts are around that. I think at least for the mining interface, where some thought has been spent on how to make it usable for an external project this would make sense. The alternative of having to regularly copy them over to the target project seems less ideal to me.

ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
This error should not be a real problem because code is taking an invalid
reference to an empty object that has no state and could never be used. But
taking the reference could technically be undefined behavior.

Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/proxy-types.h:134:5: error: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^
build/test/mp/test/foo.capnp.proxy-server.c++:51:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall>>>'
   51 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT | FIELD_BOXED>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 17>, mp::test::FooCustom, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>>, const mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooCustom>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooCustom, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test/mp/test/foo-types.h:51:12: note: Calling 'ReadDestEmplace::update'
   51 |     return read_dest.update([&](FooCustom& value) {
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   53 |         value.v2 = custom.getV2();
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
   54 |     });
      |     ~~
include/mp/proxy-types.h:112:23: note: 'is_const_v' is false
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:112:9: note: Taking false branch
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |         ^
include/mp/proxy-types.h:122:13: note: Calling 'operator()'
  122 |             update_fn(temp);
      |             ^~~~~~~~~~~~~~~
test/mp/test/foo-types.h:52:113: note: Calling 'ReadDestTemp<std::basic_string<char>>'
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |                                                                                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:134:5: note: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  135 |         return LocalType{std::forward<decltype(args)>(args)...};
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  136 |     }};
      |     ~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/type-number.h:55:32: error: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum' [clang-analyzer-optin.core.EnumCastOutOfRange,-warnings-as-errors]
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^
test/mp/test/foo.h:26:12: note: enum declared here
   26 | enum class FooEnum : uint8_t { ONE = 1, TWO = 2, };
      | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy-server.c++:63:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 1>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   63 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 1>, mp::test::FooEnum, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>>, const 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooEnum>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooEnum, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::test::FooEnum, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Calling 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Calling 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Calling 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Calling 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:7: note: Assuming the condition is false
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:3: note: Taking false branch
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |   ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1102:5: note: Returning zero
 1102 |     return static_cast<T>(0);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Returning from 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:3: note: Returning zero
 7042 |   return _reader.getDataField< ::int32_t>(
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Returning from 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:75: note: Returning zero
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                           ^~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Returning from 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:34: note: Returning zero
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Returning from 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/type-number.h:55:32: note: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
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
This error should not be a real problem because code is taking an invalid
reference to an empty object that has no state and could never be used. But
taking the reference could technically be undefined behavior.

Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/proxy-types.h:134:5: error: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^
build/test/mp/test/foo.capnp.proxy-server.c++:51:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall>>>'
   51 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT | FIELD_BOXED>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 17>, mp::test::FooCustom, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>>, const mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooCustom>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooCustom, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test/mp/test/foo-types.h:51:12: note: Calling 'ReadDestEmplace::update'
   51 |     return read_dest.update([&](FooCustom& value) {
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   53 |         value.v2 = custom.getV2();
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
   54 |     });
      |     ~~
include/mp/proxy-types.h:112:23: note: 'is_const_v' is false
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:112:9: note: Taking false branch
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |         ^
include/mp/proxy-types.h:122:13: note: Calling 'operator()'
  122 |             update_fn(temp);
      |             ^~~~~~~~~~~~~~~
test/mp/test/foo-types.h:52:113: note: Calling 'ReadDestTemp<std::basic_string<char>>'
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |                                                                                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:134:5: note: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  135 |         return LocalType{std::forward<decltype(args)>(args)...};
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  136 |     }};
      |     ~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/type-number.h:55:32: error: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum' [clang-analyzer-optin.core.EnumCastOutOfRange,-warnings-as-errors]
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^
test/mp/test/foo.h:26:12: note: enum declared here
   26 | enum class FooEnum : uint8_t { ONE = 1, TWO = 2, };
      | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy-server.c++:63:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 1>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   63 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 1>, mp::test::FooEnum, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>>, const 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooEnum>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooEnum, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::test::FooEnum, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Calling 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Calling 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Calling 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Calling 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:7: note: Assuming the condition is false
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:3: note: Taking false branch
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |   ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1102:5: note: Returning zero
 1102 |     return static_cast<T>(0);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Returning from 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:3: note: Returning zero
 7042 |   return _reader.getDataField< ::int32_t>(
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Returning from 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:75: note: Returning zero
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                           ^~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Returning from 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:34: note: Returning zero
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Returning from 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/type-number.h:55:32: note: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
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
This error should not be a real problem because code is taking an invalid
reference to an empty object that has no state and could never be used. But
taking the reference could technically be undefined behavior.

Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/proxy-types.h:134:5: error: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^
build/test/mp/test/foo.capnp.proxy-server.c++:51:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall>>>'
   51 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT | FIELD_BOXED>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 17>, mp::test::FooCustom, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>>, const mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooCustom>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooCustom, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test/mp/test/foo-types.h:51:12: note: Calling 'ReadDestEmplace::update'
   51 |     return read_dest.update([&](FooCustom& value) {
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   53 |         value.v2 = custom.getV2();
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
   54 |     });
      |     ~~
include/mp/proxy-types.h:112:23: note: 'is_const_v' is false
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:112:9: note: Taking false branch
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |         ^
include/mp/proxy-types.h:122:13: note: Calling 'operator()'
  122 |             update_fn(temp);
      |             ^~~~~~~~~~~~~~~
test/mp/test/foo-types.h:52:113: note: Calling 'ReadDestTemp<std::basic_string<char>>'
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |                                                                                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:134:5: note: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  135 |         return LocalType{std::forward<decltype(args)>(args)...};
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  136 |     }};
      |     ~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/type-number.h:55:32: error: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum' [clang-analyzer-optin.core.EnumCastOutOfRange,-warnings-as-errors]
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^
test/mp/test/foo.h:26:12: note: enum declared here
   26 | enum class FooEnum : uint8_t { ONE = 1, TWO = 2, };
      | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy-server.c++:63:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 1>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   63 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 1>, mp::test::FooEnum, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>>, const 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooEnum>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooEnum, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::test::FooEnum, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Calling 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Calling 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Calling 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Calling 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:7: note: Assuming the condition is false
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:3: note: Taking false branch
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |   ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1102:5: note: Returning zero
 1102 |     return static_cast<T>(0);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Returning from 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:3: note: Returning zero
 7042 |   return _reader.getDataField< ::int32_t>(
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Returning from 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:75: note: Returning zero
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                           ^~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Returning from 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:34: note: Returning zero
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Returning from 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/type-number.h:55:32: note: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
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
This error should not be a real problem because code is taking an invalid
reference to an empty object that has no state and could never be used. But
taking the reference could technically be undefined behavior.

Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/proxy-types.h:134:5: error: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference [clang-analyzer-core.StackAddressEscape,-warnings-as-errors]
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^
build/test/mp/test/foo.capnp.proxy-server.c++:51:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall>>>'
   51 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN | FIELD_BOXED>>(Make<ServerRet, Accessor<foo_fields::Result, FIELD_OUT | FIELD_BOXED>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 17>, mp::test::FooCustom, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassCustomParams, mp::test::messages::FooInterface::PassCustomResults>>, const mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooCustom>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooCustom, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, const mp::test::messages::FooInterface::PassCustomParams::Reader>, mp::ReadDestEmplace<mp::test::FooCustom, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test/mp/test/foo-types.h:51:12: note: Calling 'ReadDestEmplace::update'
   51 |     return read_dest.update([&](FooCustom& value) {
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   53 |         value.v2 = custom.getV2();
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~
   54 |     });
      |     ~~
include/mp/proxy-types.h:112:23: note: 'is_const_v' is false
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:112:9: note: Taking false branch
  112 |         if constexpr (std::is_const_v<std::remove_reference_t<std::invoke_result_t<EmplaceFn>>>) {
      |         ^
include/mp/proxy-types.h:122:13: note: Calling 'operator()'
  122 |             update_fn(temp);
      |             ^~~~~~~~~~~~~~~
test/mp/test/foo-types.h:52:113: note: Calling 'ReadDestTemp<std::basic_string<char>>'
   52 |         value.v1 = ReadField(TypeList<std::string>(), invoke_context, mp::Make<mp::ValueField>(custom.getV1()), ReadDestTemp<std::string>());
      |                                                                                                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:134:5: note: Address of stack memory associated with temporary object of type '(lambda at include/mp/proxy-types.h:134:51)' is still referred to by a temporary object on the stack upon returning to the caller.  This will be a dangling reference
  134 |     return ReadDestEmplace{TypeList<LocalType>(), [&](auto&&... args) -> decltype(auto) {
      |     ^                                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  135 |         return LocalType{std::forward<decltype(args)>(args)...};
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  136 |     }};
      |     ~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
bitcoin/bitcoin#31802 (comment)
https://cirrus-ci.com/task/6552721135763456
https://api.cirrus-ci.com/v1/task/6552721135763456/logs/ci.log

include/mp/type-number.h:55:32: error: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum' [clang-analyzer-optin.core.EnumCastOutOfRange,-warnings-as-errors]
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^
test/mp/test/foo.h:26:12: note: enum declared here
   26 | enum class FooEnum : uint8_t { ONE = 1, TWO = 2, };
      | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy-server.c++:63:12: note: Calling 'serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 1>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 2>, mp::ServerCall>>>'
   63 |     return serverInvoke(*this, call_context, MakeServerField<1, Accessor<foo_fields::Arg, FIELD_IN>>(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 true
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |                   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:700:5: note: Taking true branch
  700 |     if constexpr (std::is_same_v<decltype(fn()), void>) {
      |     ^
include/mp/proxy-types.h:701:9: note: Calling 'operator()'
  701 |         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::Arg, 1>, mp::test::FooEnum, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassEnumParams, mp::test::messages::FooInterface::PassEnumResults>>, const 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/proxy-types.h:304:5: note: Calling 'MaybeReadField<mp::TypeList<mp::test::FooEnum>, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  304 |     MaybeReadField(std::integral_constant<bool, Accessor::in>(), TypeList<ArgType>(), invoke_context,
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  305 |         Make<StructField, Accessor>(params), ReadDestEmplace(TypeList<ArgType>(), [&](auto&&... args) -> auto& {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  306 |             param.emplace(std::forward<decltype(args)>(args)...);
      |             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  307 |             return *param;
      |             ~~~~~~~~~~~~~~
  308 |         }));
      |         ~~~
include/mp/proxy-types.h:276:5: note: Calling 'ReadField<mp::test::FooEnum, mp::InvokeContext &, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  276 |     ReadField(std::forward<Args>(args)...);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:175:12: note: Calling 'CustomReadField<mp::test::FooEnum, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 1>, const mp::test::messages::FooInterface::PassEnumParams::Reader>, mp::ReadDestEmplace<mp::test::FooEnum, (lambda at include/mp/proxy-types.h:305:83)>>'
  175 |     return CustomReadField(TypeList<RemoveCvRef<LocalTypes>...>(), Priority<2>(), std::forward<Args>(args)...);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Calling 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Calling 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Calling 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Calling 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:7: note: Assuming the condition is false
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1099:3: note: Taking false branch
 1099 |   if ((offset + ONE * ELEMENTS) * capnp::bitsPerElement<T>() <= dataSize) {
      |   ^
/nix/store/46kiq9naswgbqfc14kc9nxcbgd0rv0m2-capnproto-1.1.0/include/capnp/layout.h:1102:5: note: Returning zero
 1102 |     return static_cast<T>(0);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:10: note: Returning from 'StructReader::getDataField'
 7042 |   return _reader.getDataField< ::int32_t>(
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.h:7042:3: note: Returning zero
 7042 |   return _reader.getDataField< ::int32_t>(
      |   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 7043 |       ::capnp::bounded<0>() * ::capnp::ELEMENTS);
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:82: note: Returning from 'Reader::getArg'
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                                  ^~~~~~~~~~
build/test/mp/test/foo.capnp.proxy.h:198:75: note: Returning zero
  198 |     template<typename S> static auto get(S&& s) -> decltype(s.getArg()) { return s.getArg(); }
      |                                                                           ^~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:41: note: Returning from 'Arg::get'
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/proxy-types.h:40:34: note: Returning zero
   40 |     decltype(auto) get() const { return Accessor::get(this->m_struct); }
      |                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/mp/type-number.h:55:55: note: Returning from 'StructField::get'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                                       ^~~~~~~~~~~
include/mp/type-number.h:55:32: note: The value '0' provided to the cast expression is not in the valid range of values for 'FooEnum'
   55 |     return read_dest.construct(static_cast<LocalType>(input.get()));
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ryanofsky added a commit to ryanofsky/libmultiprocess that referenced this pull request Jun 25, 2025
Reported by fanquake <[email protected]>
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 bitcoin-core/libmultiprocess that referenced this pull request Jun 26, 2025
757e13a ci: add gnu32 cross-compiled 32-bit build (Ryan Ofsky)
15bf349 doc: fix typo found by DrahtBot (Ryan Ofsky)
1a598d5 clang-tidy: drop 'bitcoin-*' check (Ryan Ofsky)
cbb1e43 ci: test libc++ instead of libstdc++ in one job (Ryan Ofsky)
7631345 type-context: disable clang-tidy UndefinedBinaryOperatorResult error (Ryan Ofsky)
4896e7f proxy-types: fix clang-tidy EnumCastOutOfRange error (Ryan Ofsky)
060a739 proxy-types: fix clang-tidy StackAddressEscape error (Ryan Ofsky)
977d721 ci: add github actions jobs testing gcc, clang-20, clang-tidy, and iwyu (Ryan Ofsky)
0d5f1fa iwyu: fix add/remove include errors (Ryan Ofsky)
753d2b1 util: fix clang-tidy modernize-use-equals-default error (Ryan Ofsky)
ae4f1dc type-number: fix clang-tidy modernize-use-nullptr error (Ryan Ofsky)
07a741b proxy-types: fix clang-tidy bugprone-use-after-move error (Ryan Ofsky)
3673114 proxy-types: fix clang-tidy bugprone-use-after-move error (Ryan Ofsky)
422923f proxy-types: fix clang-tidy bugprone-use-after-move error (Ryan Ofsky)
c6784c6 mpgen: disable clang-tidy misc-no-recursion error (Ryan Ofsky)
c5498aa tidy: copy clang-tidy file from bitcoin core (Ryan Ofsky)

Pull request description:

  The main thing this change does is add a simple github actions CI job to build the project, run tests, and do checks with iwyu and clang-tidy.

  It also fixes bitcoin CI warnings reported by fanquake in bitcoin/bitcoin#31802 (comment) that show up in the logs but do not cause errors, as well as a number of other warnings that do not seem to show up in bitcoin CI. Individual commit messages describe the fixes in detail.

ACKs for top commit:
  Sjors:
    ACK 757e13a

Tree-SHA512: 3adc3fa03ba432bc46370ec78118962dd9701a0a0ad09dbaab6ab4be2b9f48784a636ba95efde53957505ddc5307a1afac48e62040604b120480f3f698fa98af
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.