Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading