Skip to content

refactor: memcpy to std::ranges::copy to work around ubsan warn#305

Merged
ryanofsky merged 1 commit into
bitcoin-core:masterfrom
maflcko:2607-ubsan-memcpy-null-warn
Jul 13, 2026
Merged

refactor: memcpy to std::ranges::copy to work around ubsan warn#305
ryanofsky merged 1 commit into
bitcoin-core:masterfrom
maflcko:2607-ubsan-memcpy-null-warn

Conversation

@maflcko

@maflcko maflcko commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Using std::ranges::copy from the C++ standard library has a few benefits here:

  • It has the additional benefit of being a bit more type safe and document any needed cast explicitly. (No cast is needed here)
  • It has defined semantics for empty source ranges.

This should also fix bitcoin/bitcoin#35118 (comment)

@DrahtBot

DrahtBot commented Jul 9, 2026

Copy link
Copy Markdown

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

Reviews

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

Type Reviewers
ACK Sjors, ViniciusCestarii, ryanofsky

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

@maflcko
maflcko force-pushed the 2607-ubsan-memcpy-null-warn branch from d4b028b to fac7b9b Compare July 9, 2026 18:06
@maflcko

maflcko commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Failure should look roughly like:

# cmake --build build-ubsan-gcc --target tests && UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=0:report_error_type=1" ./build-ubsan-gcc/test/mptest 
ninja: no work to do.
[ TEST ] listen_tests.cpp:214: ListenConnections accepts incoming connections
[ PASS ] listen_tests.cpp:214: ListenConnections accepts incoming connections (18477 μs)
[ TEST ] listen_tests.cpp:224: ListenConnections enforces a local connection limit
[ PASS ] listen_tests.cpp:224: ListenConnections enforces a local connection limit (18818 μs)
[ TEST ] listen_tests.cpp:262: ListenConnections accepts multiple connections
[ PASS ] listen_tests.cpp:262: ListenConnections accepts multiple connections (9224 μs)
[ TEST ] spawn_tests.cpp:46: SpawnProcess does not run callback in child
[ PASS ] spawn_tests.cpp:46: SpawnProcess does not run callback in child (138059 μs)
[ TEST ] test.cpp:143: Call FooInterface methods
/tmp/tmp.sFxQecaMfz/git_dir/include/mp/type-data.h:36:11: runtime error: null pointer passed as argument 2, which is declared to never be null
    #0 0x61583eb6352f in _ZN2mp16CustomBuildFieldISt6vectorIcSaIcEES3_NS_11StructFieldINS_8AccessorINS_10foo_fields3ArgELi17EEEN5capnp7RequestINS_4test8messages12FooInterface14PassDataParamsENSD_15PassDataResultsEEEEEEEvNS_8TypeListIJT_EEENS_8PriorityILi2EEERNS_13InvokeContextEOT0_OT1_Qaa9is_same_vIDTcldtfL0p3_3getEENS9_4Data7BuilderEE10IsByteSpanISJ_E /tmp/tmp.sFxQecaMfz/git_dir/include/mp/type-data.h:36
    #1 0x61583eb45ee2 in void mp::BuildField<std::vector<char, std::allocator<char> >, mp::ClientInvokeContext, std::vector<char, std::allocator<char> >, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> > >(mp::TypeList<std::vector<char, std::allocator<char> > >, mp::ClientInvokeContext&, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >&&, std::vector<char, std::allocator<char> >&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:253
    #2 0x61583eb1dcad in void mp::MaybeBuildField<mp::TypeList<std::vector<char, std::allocator<char> > >, mp::ClientInvokeContext&, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >, std::vector<char, std::allocator<char> > >(std::integral_constant<bool, true>, mp::TypeList<std::vector<char, std::allocator<char> > >&&, mp::ClientInvokeContext&, mp::StructField<mp::Accessor<mp::foo_fields::Arg, 17>, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >&&, std::vector<char, std::allocator<char> >&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:336
    #3 0x61583eaeacc0 in auto mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1}::operator()<std::vector<char, std::allocator<char> > >(std::vector<char, std::allocator<char> >&&) const /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:474
    #4 0x61583eb4601d in void std::__invoke_impl<void, mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1} const&, std::vector<char, std::allocator<char> > >(std::__invoke_other, mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1} const&, std::vector<char, std::allocator<char> >&&) /usr/include/c++/15/bits/invoke.h:63
    #5 0x61583eb1de59 in std::__invoke_result<mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1} const&, std::vector<char, std::allocator<char> > >::type std::__invoke<mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1} const&, std::vector<char, std::allocator<char> > >(capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&&, std::vector<char, std::allocator<char> >&&) /usr/include/c++/15/bits/invoke.h:98
    #6 0x61583eaeadc1 in decltype(auto) std::__apply_impl<mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1} const&, std::tuple<std::vector<char, std::allocator<char> >&&>, 0ul>(capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&&, std::tuple<std::vector<char, std::allocator<char> >&&>&&, std::integer_sequence<unsigned long, 0ul>) /usr/include/c++/15/tuple:2920
    #7 0x61583eaeaea0 in decltype(auto) std::apply<mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >)::{lambda<typename... $T0>(($T0&&)...)#1} const&, std::tuple<std::vector<char, std::allocator<char> >&&> >(capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&&, std::tuple<std::vector<char, std::allocator<char> >&&>&&) /usr/include/c++/15/tuple:2935
    #8 0x61583eaeafbc in void mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams::handleField<capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:485
    #9 0x61583eaaf470 in void mp::IterateFieldsHelper<mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams, 1ul>::handleChain<mp::ClientInvokeContext, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> > > >(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> > >) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:416
    #10 0x61583ea8b2b7 in void mp::IterateFieldsHelper<mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams, 1ul>::handleChain<mp::ClientInvokeContext, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> >, std::vector<char, std::allocator<char> > >, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&>::BuildParams>(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> >, std::vector<char, std::allocator<char> > >, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&>::BuildParams&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:408
    #11 0x61583ea6abc4 in void mp::IterateFieldsHelper<mp::IterateFields, 0ul>::handleChain<mp::ClientInvokeContext, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::TypeList<std::vector<char, std::allocator<char> >, std::vector<char, std::allocator<char> > >, mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&>::BuildParams>(mp::ClientInvokeContext&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::TypeList<std::vector<char, std::allocator<char> >, std::vector<char, std::allocator<char> > >, mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >::BuildParams&&, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&>::BuildParams&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:409
    #12 0x61583ea27feb in mp::clientInvoke<mp::ProxyClient<mp::test::messages::FooInterface>, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> (mp::test::messages::FooInterface::Client::*)(kj::Maybe<capnp::MessageSize>), mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&> >(mp::ProxyClient<mp::test::messages::FooInterface>&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> (mp::test::messages::FooInterface::Client::* const&)(kj::Maybe<capnp::MessageSize>), mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >&&, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&>&&)::{lambda()#1}::operator()() const /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:730
    #13 0x61583ebbb923 in kj::Function<void ()>::Impl<mp::clientInvoke<mp::ProxyClient<mp::test::messages::FooInterface>, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> (mp::test::messages::FooInterface::Client::*)(kj::Maybe<capnp::MessageSize>), mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&> >(mp::ProxyClient<mp::test::messages::FooInterface>&, capnp::Request<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> (mp::test::messages::FooInterface::Client::* const&)(kj::Maybe<capnp::MessageSize>), mp::ClientParam<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> > >&&, mp::ClientParam<mp::Accessor<mp::foo_fields::Result, 18>, std::vector<char, std::allocator<char> >&>&&)::{lambda()#1}>::operator()() /usr/include/kj/function.h:142
    #14 0x61583e95004c in kj::Function<void ()>::operator()() /usr/include/kj/function.h:119
    #15 0x61583e941e89 in void mp::Unlock<mp::Lock, kj::Function<void ()>&>(mp::Lock&, kj::Function<void ()>&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/util.h:216
    #16 0x61583f10d034 in operator() /tmp/tmp.sFxQecaMfz/git_dir/src/mp/proxy.cpp:254
    #17 0x61583f1176e2 in runCatchingExceptions<mp::EventLoop::loop()::<lambda()> > /usr/include/kj/exception.h:371
    #18 0x61583f10d926 in mp::EventLoop::loop() /tmp/tmp.sFxQecaMfz/git_dir/src/mp/proxy.cpp:254
    #19 0x61583e9153aa in mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /tmp/tmp.sFxQecaMfz/git_dir/test/mp/test/test.cpp:125
    #20 0x61583e97cacc in void std::__invoke_impl<void, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}>(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /usr/include/c++/15/bits/invoke.h:63
    #21 0x61583e97adaf in std::__invoke_result<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}>::type std::__invoke<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}>(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /usr/include/c++/15/bits/invoke.h:98
    #22 0x61583e979782 in void std::thread::_Invoker<std::tuple<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/include/c++/15/bits/std_thread.h:303
    #23 0x61583e977a97 in std::thread::_Invoker<std::tuple<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}> >::operator()() /usr/include/c++/15/bits/std_thread.h:310
    #24 0x61583e976029 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}> > >::_M_run() /usr/include/c++/15/bits/std_thread.h:255
    #25 0x7da055842ef8  (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xf5ef8) (BuildId: 3a7a8c6bd922e6314edb7a710fdc0293265c2c42)
    #26 0x7da054e5f0d9  (/usr/lib/x86_64-linux-gnu/libc.so.6+0xa40d9) (BuildId: 90ebd03ae9d9f42b23b4eb82fdf70352cf744198)
    #27 0x7da054ef27ab  (/usr/lib/x86_64-linux-gnu/libc.so.6+0x1377ab) (BuildId: 90ebd03ae9d9f42b23b4eb82fdf70352cf744198)

/tmp/tmp.sFxQecaMfz/git_dir/include/mp/type-data.h:36:11: runtime error: null pointer passed as argument 2, which is declared to never be null
    #0 0x61583ee75afc in _ZN2mp16CustomBuildFieldIOSt6vectorIcSaIcEES3_NS_11StructFieldINS_8AccessorINS_10foo_fields6ResultELi18EEENS_4test8messages12FooInterface15PassDataResults7BuilderEEEEEvNS_8TypeListIJT_EEENS_8PriorityILi2EEERNS_13InvokeContextEOT0_OT1_Qaa9is_same_vIDTcldtfL0p3_3getEEN5capnp4Data7BuilderEE10IsByteSpanISH_E /tmp/tmp.sFxQecaMfz/git_dir/include/mp/type-data.h:36
    #1 0x61583edf4f7a in void mp::BuildField<std::vector<char, std::allocator<char> >&&, mp::InvokeContext, std::vector<char, std::allocator<char> >, mp::StructField<mp::Accessor<mp::foo_fields::Result, 18>, mp::test::messages::FooInterface::PassDataResults::Builder> >(mp::TypeList<std::vector<char, std::allocator<char> >&&>, mp::InvokeContext&, mp::StructField<mp::Accessor<mp::foo_fields::Result, 18>, mp::test::messages::FooInterface::PassDataResults::Builder>&&, std::vector<char, std::allocator<char> >&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:253
    #2 0x61583ed74581 in void mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall>::invoke<mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >, std::vector<char, std::allocator<char> > >(mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >&, mp::TypeList<>, std::vector<char, std::allocator<char> >&&) const /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:577
    #3 0x61583ecdfeb2 in void mp::PassField<mp::Accessor<mp::foo_fields::Arg, 17>, std::vector<char, std::allocator<char> >, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> const&, mp::TypeList<> >(mp::Priority<0>, mp::TypeList<std::vector<char, std::allocator<char> > >, mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >&, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> const&, mp::TypeList<>&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:383
    #4 0x61583ec6b087 in decltype(auto) mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> >::invoke<mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >, mp::TypeList<std::vector<char, std::allocator<char> > >>(mp::ServerInvokeContext<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults> >&, mp::TypeList<std::vector<char, std::allocator<char> > >) const /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:642
    #5 0x61583ec6b01c in mp::serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> > >(mp::ProxyServer<mp::test::messages::FooInterface>&, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> >)::{lambda()#1}::operator()() const /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:823
    #6 0x61583ec6b234 in auto mp::ReplaceVoid<mp::serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> > >(mp::ProxyServer<mp::test::messages::FooInterface>&, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> >)::{lambda()#1}, mp::serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> > >(mp::ProxyServer<mp::test::messages::FooInterface>&, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> >)::{lambda()#2}>(mp::ProxyServer<mp::test::messages::FooInterface>&&, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&&) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:782
    #7 0x61583ec6c6df in kj::Promise<void> mp::serverInvoke<mp::ProxyServer<mp::test::messages::FooInterface>, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> > >(mp::ProxyServer<mp::test::messages::FooInterface>&, capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>&, mp::ServerField<1, mp::Accessor<mp::foo_fields::Arg, 17>, mp::ServerRet<mp::Accessor<mp::foo_fields::Result, 18>, mp::ServerCall> >) /tmp/tmp.sFxQecaMfz/git_dir/include/mp/proxy-types.h:823
    #8 0x61583ec161b5 in mp::ProxyServer<mp::test::messages::FooInterface>::passData(capnp::CallContext<mp::test::messages::FooInterface::PassDataParams, mp::test::messages::FooInterface::PassDataResults>) /tmp/tmp.sFxQecaMfz/git_dir/build-ubsan-gcc/test/mp/test/foo.capnp.proxy-server.c++:93
    #9 0x61583e991105 in mp::test::messages::FooInterface::Server::dispatchCallInternal(unsigned short, capnp::CallContext<capnp::AnyPointer, capnp::AnyPointer>) /tmp/tmp.sFxQecaMfz/git_dir/build-ubsan-gcc/test/mp/test/foo.capnp.c++:4642
    #10 0x61583e984728 in mp::test::messages::FooInterface::Server::dispatchCall(unsigned long, unsigned short, capnp::CallContext<capnp::AnyPointer, capnp::AnyPointer>) /tmp/tmp.sFxQecaMfz/git_dir/build-ubsan-gcc/test/mp/test/foo.capnp.c++:4452
    #11 0x7da055c49874  (/usr/lib/x86_64-linux-gnu/libcapnp-rpc-1.1.0.so+0x58874) (BuildId: 4bc3b60e77961fd7bf5b0656724a4b009a1cdc60)
    #12 0x7da055c58933  (/usr/lib/x86_64-linux-gnu/libcapnp-rpc-1.1.0.so+0x67933) (BuildId: 4bc3b60e77961fd7bf5b0656724a4b009a1cdc60)
    #13 0x7da055ab51bd in kj::_::TransformPromiseNodeBase::get(kj::_::ExceptionOrValue&) (/usr/lib/x86_64-linux-gnu/libkj-async-1.1.0.so+0x421bd) (BuildId: 11e33396b3aedb25ab73f8c546c7af5db001c789)
    #14 0x7da055abff57 in kj::_::ChainPromiseNode::fire() (/usr/lib/x86_64-linux-gnu/libkj-async-1.1.0.so+0x4cf57) (BuildId: 11e33396b3aedb25ab73f8c546c7af5db001c789)
    #15 0x7da055aac852 in kj::EventLoop::turn() (/usr/lib/x86_64-linux-gnu/libkj-async-1.1.0.so+0x39852) (BuildId: 11e33396b3aedb25ab73f8c546c7af5db001c789)
    #16 0x7da055ab2968 in kj::_::waitImpl(kj::Own<kj::_::PromiseNode, kj::_::PromiseDisposer>&&, kj::_::ExceptionOrValue&, kj::WaitScope&, kj::SourceLocation) (/usr/lib/x86_64-linux-gnu/libkj-async-1.1.0.so+0x3f968) (BuildId: 11e33396b3aedb25ab73f8c546c7af5db001c789)
    #17 0x61583f1239ea in kj::Promise<unsigned long>::wait(kj::WaitScope&, kj::SourceLocation) /usr/include/kj/async-inl.h:1357
    #18 0x61583f10d753 in mp::EventLoop::loop() /tmp/tmp.sFxQecaMfz/git_dir/src/mp/proxy.cpp:247
    #19 0x61583e9153aa in mp::test::TestSetup::TestSetup(bool)::{lambda()#1}::operator()() const /tmp/tmp.sFxQecaMfz/git_dir/test/mp/test/test.cpp:125
    #20 0x61583e97cacc in void std::__invoke_impl<void, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}>(std::__invoke_other, mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /usr/include/c++/15/bits/invoke.h:63
    #21 0x61583e97adaf in std::__invoke_result<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}>::type std::__invoke<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}>(mp::test::TestSetup::TestSetup(bool)::{lambda()#1}&&) /usr/include/c++/15/bits/invoke.h:98
    #22 0x61583e979782 in void std::thread::_Invoker<std::tuple<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) /usr/include/c++/15/bits/std_thread.h:303
    #23 0x61583e977a97 in std::thread::_Invoker<std::tuple<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}> >::operator()() /usr/include/c++/15/bits/std_thread.h:310
    #24 0x61583e976029 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<mp::test::TestSetup::TestSetup(bool)::{lambda()#1}> > >::_M_run() /usr/include/c++/15/bits/std_thread.h:255
    #25 0x7da055842ef8  (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xf5ef8) (BuildId: 3a7a8c6bd922e6314edb7a710fdc0293265c2c42)
    #26 0x7da054e5f0d9  (/usr/lib/x86_64-linux-gnu/libc.so.6+0xa40d9) (BuildId: 90ebd03ae9d9f42b23b4eb82fdf70352cf744198)
    #27 0x7da054ef27ab  (/usr/lib/x86_64-linux-gnu/libc.so.6+0x1377ab) (BuildId: 90ebd03ae9d9f42b23b4eb82fdf70352cf744198)

[ PASS ] test.cpp:143: Call FooInterface methods (627953 μs)
[ TEST ] test.cpp:274: Call IPC method after client connection is closed
[ PASS ] test.cpp:274: Call IPC method after client connection is closed (3349 μs)
[ TEST ] test.cpp:284: Calling IPC method after server connection is closed
[ PASS ] test.cpp:284: Calling IPC method after server connection is closed (4292 μs)
[ TEST ] test.cpp:294: Calling IPC method and disconnecting during the call
[ PASS ] test.cpp:294: Calling IPC method and disconnecting during the call (5566 μs)
[ TEST ] test.cpp:307: Calling IPC method, disconnecting and blocking during the call
[ PASS ] test.cpp:307: Calling IPC method, disconnecting and blocking during the call (11115 μs)
[ TEST ] test.cpp:349: Worker thread destroyed before it is initialized
[ PASS ] test.cpp:349: Worker thread destroyed before it is initialized (21340 μs)
[ TEST ] test.cpp:379: Calling async IPC method, with server disconnect racing the call
[ PASS ] test.cpp:379: Calling async IPC method, with server disconnect racing the call (20475 μs)
[ TEST ] test.cpp:404: Calling async IPC method, with server disconnect after cleanup
[ PASS ] test.cpp:404: Calling async IPC method, with server disconnect after cleanup (5119 μs)
[ TEST ] test.cpp:428: Destroying ProxyClient<> with destroy method after peer disconnect
[ PASS ] test.cpp:428: Destroying ProxyClient<> with destroy method after peer disconnect (14057 μs)
[ TEST ] test.cpp:454: Make simultaneous IPC calls on single remote thread
[ PASS ] test.cpp:454: Make simultaneous IPC calls on single remote thread (38471 μs)
[ TEST ] test.cpp:509: Call async IPC method dispatched to pool thread
[ PASS ] test.cpp:509: Call async IPC method dispatched to pool thread (16066 μs)
[ TEST ] test.cpp:553: Call async IPC method without thread or pool errors correctly
[ PASS ] test.cpp:553: Call async IPC method without thread or pool errors correctly (17293 μs)
16 test(s) passed 

@Sjors

Sjors commented Jul 10, 2026

Copy link
Copy Markdown
Member

Concept ACK on replacing memcpy.

@Sjors

Sjors commented Jul 10, 2026

Copy link
Copy Markdown
Member

ACK fac7b9b

I was also able to reproduce the error that the new test triggers.

@ViniciusCestarii

Copy link
Copy Markdown
Contributor

ACK fac7b9b

Reproduced the error and confirmed fac7b9b fixes it.

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code review ACK fac7b9b. Thanks for the fix!

It's still not completely clear to me if this purely a ubsan issue or if there could have been real problems here with compiler optimizations. For example https://developers.redhat.com/articles/2024/12/11/making-memcpynull-null-0-well-defined would seem to suggest that compilers could have used this memcpy to falsely assume result.begin() and data.init() are never null and skip null checks in nearby code. But avoiding memcpy is an improvement in any case.

Possible followups to this change:

  • There are also other uses of memcpy in the codebase that could be replaced
  • Code would be simpler without the result variable, just using copy(value, output.init(value.size())

@ryanofsky
ryanofsky merged commit e8de5c7 into bitcoin-core:master Jul 13, 2026
13 checks passed
@maflcko

maflcko commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

Code would be simpler without the result variable, just using copy(value, output.init(value.size())

I am sure the second arg is the beginning of the destination range, so it would have been:

  std::ranges::copy(value, output.init(value.size()).begin());

It's still not completely clear to me if this purely a ubsan issue or if there could have been real problems here with compiler optimizations.

In the past, this was certainly UB in theory (not sure if a compiler exists in practise to exploit this). I think it is fixed in C29, but it is not clear how fast compilers will apply this fix. (Ref: bitcoin/bitcoin#32016 (comment))

There are also other uses of memcpy in the codebase that could be replaced

Also checked them, but I think they are fine (no promise though).

There is https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-ranges.html that can be used? Happy to take a look there, if you find it worthwhile.

@maflcko
maflcko deleted the 2607-ubsan-memcpy-null-warn branch July 14, 2026 06:09
@maflcko

maflcko commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

There is https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-ranges.html that can be used? Happy to take a look there, if you find it worthwhile.

Actually, this is unrelated. That check is about moving iterator-based algos to range-based ones.

For the memcpy replacement, there is llvm/llvm-project#22583

fanquake pushed a commit to bitcoin/bitcoin that referenced this pull request Jul 14, 2026
e8de5c7b68 Merge bitcoin-core/libmultiprocess#305: refactor: memcpy to std::ranges::copy to work around ubsan warn
9307e68e5a Merge bitcoin-core/libmultiprocess#306: doc: Bump version 12 > 13
fac7b9b7f6 refactor: memcpy to std::ranges::copy to work around ubsan warn
1bd7025609 Merge bitcoin-core/libmultiprocess#297: test: add map serialization round-trip coverage
438fdd243d doc: Bump version 12 > 13
463d073cb8 test: rename vBool to vector_bool
85df233845 test: add mapStringInt to foo.capnp to cover map serialization and deserialization

git-subtree-dir: src/ipc/libmultiprocess
git-subtree-split: e8de5c7b68e0ae21c94ae92aa22e5c3b213f9c12
maflcko pushed a commit to maflcko/libmultiprocess that referenced this pull request Jul 17, 2026
maflcko pushed a commit to maflcko/libmultiprocess that referenced this pull request Jul 17, 2026
I think there is no issue of passing a nullptr here, but it makes sense to use the std-lib copy for consistency.

Recommended in bitcoin-core#305 (review)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants