diff --git a/packages/streamr-libstreamrproxyclient/include/streamrproxyclient.h b/packages/streamr-libstreamrproxyclient/include/streamrproxyclient.h index 63bb408f..d25573b7 100644 --- a/packages/streamr-libstreamrproxyclient/include/streamrproxyclient.h +++ b/packages/streamr-libstreamrproxyclient/include/streamrproxyclient.h @@ -43,8 +43,10 @@ EXTERN_C SHARED_EXPORT void proxyClientInitLibrary(void); /** * @brief Cleanup the library. This function MUST be called before the program exits. * Can be safely called multiple times. This is needed because the standard dynamic library - * destructor (__attribute__((destructor))) is called after static variables have already been destroyed, + * destructor (__attribute__((destructor))) is called after static variables have already been destroyed, * which makes it impossible to clean up the other objects that depend on the static variables. + * The library may be used again after a cleanup: any API call re-initializes it automatically + * (an explicit proxyClientInitLibrary() call is also fine). */ // NOLINTNEXTLINE EXTERN_C SHARED_EXPORT void proxyClientCleanupLibrary(void); diff --git a/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp b/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp index fa819a1b..c9c589ee 100644 --- a/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp +++ b/packages/streamr-libstreamrproxyclient/src/streamrproxyclient.cpp @@ -1375,6 +1375,12 @@ void proxyClientCleanupLibrary() { // NOLINT } static LibProxyClientApi& getProxyClientApi() { // NOLINT + // proxyClientCleanupLibrary() destroys the instance and only the + // load-time constructor re-creates it, so an API call made after a + // cleanup would dereference a null pointer here. Re-initialize on + // demand instead — the header documents that the library can be + // re-initialized after a cleanup. + proxyClientInitLibrary(); return *libProxyClientApi; } diff --git a/packages/streamr-libstreamrproxyclient/test/integration/StreamrProxyClientTest.cpp b/packages/streamr-libstreamrproxyclient/test/integration/StreamrProxyClientTest.cpp index abfdd600..8da3fedf 100644 --- a/packages/streamr-libstreamrproxyclient/test/integration/StreamrProxyClientTest.cpp +++ b/packages/streamr-libstreamrproxyclient/test/integration/StreamrProxyClientTest.cpp @@ -61,6 +61,47 @@ TEST_F(StreamrProxyClientTest, CanCreateAndDeleteProxyClient) { SLogger::info("test finished"); } +// Regression test for use-after-cleanup: the fixture destructor calls +// proxyClientCleanupLibrary() after every test, so any test that runs +// after another one in the same process starts with the library torn +// down. API entry points must re-initialize the library on demand +// instead of dereferencing the destroyed instance (this crashed with +// SIGSEGV before the fix; CI masked it by running every test in its +// own process). +TEST_F(StreamrProxyClientTest, ApiIsUsableAfterCleanupLibrary) { + // Implicit re-initialization: call the API right after a cleanup. + proxyClientCleanupLibrary(); + + const ProxyResult* result = nullptr; + uint64_t clientHandle = + proxyClientNew(&result, validEthereumAddress, validStreamPartId); + EXPECT_EQ(result->numErrors, 0); + EXPECT_NE(clientHandle, 0); + proxyClientResultDelete(result); + + const ProxyResult* result2 = nullptr; + proxyClientDelete(&result2, clientHandle); + EXPECT_EQ(result2->numErrors, 0); + proxyClientResultDelete(result2); + + // Explicit re-initialization (the path documented in + // streamrproxyclient.h): cleanup, init, use. + proxyClientCleanupLibrary(); + proxyClientInitLibrary(); + + const ProxyResult* result3 = nullptr; + uint64_t clientHandle2 = + proxyClientNew(&result3, validEthereumAddress, validStreamPartId); + EXPECT_EQ(result3->numErrors, 0); + EXPECT_NE(clientHandle2, 0); + proxyClientResultDelete(result3); + + const ProxyResult* result4 = nullptr; + proxyClientDelete(&result4, clientHandle2); + EXPECT_EQ(result4->numErrors, 0); + proxyClientResultDelete(result4); +} + TEST_F(StreamrProxyClientTest, InvalidEthereumAddress) { const ProxyResult* result = nullptr;