Skip to content

Commit 6f0ab5d

Browse files
committed
feat(dlutils): Replace boolean returns with exceptions for error
- Changed DlLibBase::SelfDlOpen() and SelfDlSym() to throw - std::runtime_error instead of returning boolean Removed unused - MakeString vector overload and related tests Updated test - expectations to catch exceptions instead of checking boolean - returns Removed function cache tracking mechanism and related - methods Improved error messages with file, line, and detailed - context information Fixed minor formatting issues and removed - unnecessary includes
1 parent 0c5b6bf commit 6f0ab5d

File tree

5 files changed

+86
-187
lines changed

5 files changed

+86
-187
lines changed

include/dlutils/dlutils.hpp

Lines changed: 33 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
#include <functional>
2929
#include <sstream>
30-
#include <vector>
3130

3231
/// @brief A header-only C++ library for dynamic loading utilities
3332
///
@@ -85,24 +84,6 @@ template <typename... Args> std::string MakeString(const Args &...args) {
8584
return std::string(ss.str());
8685
}
8786

88-
/// @brief Creates a string from vector elements with optional delimiter
89-
/// @tparam T The type of elements in the vector
90-
/// @param v The vector of elements to convert to string
91-
/// @param delim The delimiter to use between elements (default: space)
92-
/// @return A string containing all vector elements separated by the delimiter
93-
template <typename T>
94-
std::string MakeString(const std::vector<T> &v,
95-
const std::string &delim = " ") {
96-
std::stringstream ss;
97-
for (auto it = v.begin(); it < v.end(); it++) {
98-
if (it != v.begin()) {
99-
MakeStringInternal(ss, delim);
100-
}
101-
MakeStringInternal(ss, *it);
102-
}
103-
return std::string(ss.str());
104-
}
105-
10687
// Specializations for already-a-string types.
10788
/// @brief Specialization for std::string objects
10889
/// @param str The string to return
@@ -189,7 +170,7 @@ class DlLibBase {
189170

190171
/// @brief Destructor (default)
191172
~DlLibBase() {
192-
if(libptr_ != nullptr) {
173+
if (libptr_ != nullptr) {
193174
dlclose(libptr_);
194175
}
195176
}
@@ -201,10 +182,15 @@ class DlLibBase {
201182
/// filename contains a slash ("/"), then it is interpreted as a (relative or
202183
/// absolute) pathname. Otherwise, the dynamic linker searches for the object.
203184
///
204-
/// @return true if the library was successfully loaded, false otherwise
205-
bool SelfDlOpen() {
185+
/// @throws std::runtime_error if the library fails to load
186+
void SelfDlOpen() {
206187
libptr_ = dlopen(libName_.data(), RTLD_NOW | RTLD_GLOBAL);
207-
return libptr_ != nullptr;
188+
if (libptr_ == nullptr) {
189+
throw std::runtime_error(
190+
internal::MakeString("[", __FILE__, ":", __LINE__,
191+
"] Fatal Error: failed to load library '",
192+
libName_, "'. dlopen error: ", dlerror()));
193+
}
208194
}
209195

210196
/// @brief Load a symbol from the dynamic library
@@ -221,47 +207,42 @@ class DlLibBase {
221207
/// @tparam Args The parameter types of the function
222208
/// @param funName The name of the function to load
223209
/// @param outFun The DlFun object to populate with the function
224-
/// @return true if the symbol lookup was attempted (even if it failed), false
225-
/// if preconditions were not met
210+
/// @throws std::runtime_error if the library is not loaded or symbol lookup
211+
/// fails
226212
template <class R, class... Args>
227-
bool SelfDlSym(std::string_view funName, DlFun<R, Args...> &outFun) {
228-
if (libptr_ == nullptr || funName.empty()) {
229-
return false;
213+
void SelfDlSym(std::string_view funName, DlFun<R, Args...> &outFun) {
214+
if (libptr_ == nullptr) {
215+
throw std::runtime_error(internal::MakeString(
216+
"[", __FILE__, ":", __LINE__, "] Fatal Error: cannot load symbol '",
217+
funName, "' because library '", libName_, "' is not loaded."));
218+
}
219+
220+
if (funName.empty()) {
221+
throw std::runtime_error(
222+
internal::MakeString("[", __FILE__, ":", __LINE__,
223+
"] Fatal Error: function name is empty."));
230224
}
231225

226+
// Clear dlerror before calling dlsym
227+
dlerror();
232228
void *funPtr = dlsym(libptr_, funName.data());
233-
funCache_.push_back(
234-
funPtr); // Store all function pointers, including failed ones
235-
outFun =
236-
DlFun<R, Args...>(funName, reinterpret_cast<R (*)(Args...)>(funPtr));
237-
return true;
238-
}
229+
char *error = dlerror();
239230

240-
/// @brief Check if all functions in the cache were successfully loaded
241-
/// @return true if all functions were successfully loaded, false otherwise
242-
bool CheckFunCache() const {
243-
for (const auto *p : funCache_) {
244-
if (p == nullptr) {
245-
return false;
246-
}
231+
if (error != nullptr || funPtr == nullptr) {
232+
throw std::runtime_error(internal::MakeString(
233+
"[", __FILE__, ":", __LINE__,
234+
"] Fatal Error: failed to load symbol '", funName, "' from library '",
235+
libName_, "'. dlsym error: ", error));
247236
}
248-
return true;
249-
}
250237

251-
/// @brief Get the number of functions in the cache
252-
/// @return The number of functions in the cache
253-
size_t GetFunCacheSize() const { return funCache_.size(); }
238+
outFun =
239+
DlFun<R, Args...>(funName, reinterpret_cast<R (*)(Args...)>(funPtr));
240+
}
254241

255242
private:
256243
void *libptr_ = nullptr; ///< Handle to the loaded library (default: nullptr)
257244
const std::string libName_ =
258245
"unknown"; ///< The name of the library to load (default: "unknown")
259-
260-
/// @brief Cache of function pointers obtained through dlsym
261-
///
262-
/// WARNING: DO NOT manually manipulate these pointers.
263-
/// The cache does NOT own these pointers; they should be read-only.
264-
std::vector<void *> funCache_;
265246
};
266247

267248
/// @brief Macro to simplify loading symbols from dynamic libraries

test/dllibase_extended_test.cpp

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -32,103 +32,55 @@ class ExtendedMockDlLib : public DlLibBase {
3232
public:
3333
explicit ExtendedMockDlLib(std::string_view lib) : DlLibBase(lib) {}
3434

35-
bool OpenLib() { return SelfDlOpen(); }
35+
void OpenLib() { SelfDlOpen(); }
3636

3737
template <class R, class... Args>
38-
bool LoadSymbol(std::string_view funName, DlFun<R, Args...> &outFun) {
39-
return SelfDlSym(funName, outFun);
38+
void LoadSymbol(std::string_view funName, DlFun<R, Args...> &outFun) {
39+
SelfDlSym(funName, outFun);
4040
}
41-
42-
bool CheckCache() { return CheckFunCache(); }
43-
44-
size_t CacheSize() { return GetFunCacheSize(); }
4541
};
4642

4743
// Tests for DlLibBase class with invalid library
4844
TEST(DlLibBaseExtendedTest, OpenInvalidLibrary) {
4945
ExtendedMockDlLib lib("libnonexistent.so");
50-
EXPECT_FALSE(lib.OpenLib());
46+
EXPECT_THROW(lib.OpenLib(), std::runtime_error);
5147
}
5248

5349
// Tests for DlLibBase class with invalid function name
5450
TEST(DlLibBaseExtendedTest, LoadInvalidFunction) {
5551
ExtendedMockDlLib lib("libnonexistent.so");
5652
DlFun<int, int, int> func;
57-
// This should return false because the library wasn't opened
58-
EXPECT_FALSE(lib.LoadSymbol("nonexistent_function", func));
59-
// The function name should still be "unknown" because LoadSymbol didn't
60-
// succeed
61-
EXPECT_EQ(func.GetName(), "unknown");
53+
// This should throw because the library wasn't opened
54+
EXPECT_THROW(lib.LoadSymbol("nonexistent_function", func), std::runtime_error);
6255
}
6356

6457
// Tests for DlLibBase class with null library pointer
6558
TEST(DlLibBaseExtendedTest, LoadSymbolWithNullLibPtr) {
6659
ExtendedMockDlLib lib("libnonexistent.so");
6760
// Not calling OpenLib, so libptr_ remains nullptr
6861
DlFun<int, int, int> func;
69-
EXPECT_FALSE(lib.LoadSymbol("some_function", func));
62+
EXPECT_THROW(lib.LoadSymbol("some_function", func), std::runtime_error);
7063
}
7164

7265
// Tests for DlLibBase class with empty function name
7366
TEST(DlLibBaseExtendedTest, LoadSymbolWithEmptyName) {
7467
ExtendedMockDlLib lib("libnonexistent.so");
7568
DlFun<int, int, int> func;
76-
EXPECT_FALSE(lib.LoadSymbol("", func));
77-
}
78-
79-
// Tests for DlLibBase class function cache size
80-
TEST(DlLibBaseExtendedTest, FunCacheSizeInitiallyZero) {
81-
ExtendedMockDlLib lib("libnonexistent.so");
82-
EXPECT_EQ(lib.CacheSize(), 0u);
83-
}
84-
85-
// Tests for DlLibBase class CheckFunCache initially true
86-
TEST(DlLibBaseExtendedTest, CheckFunCacheInitiallyTrue) {
87-
ExtendedMockDlLib lib("libnonexistent.so");
88-
89-
// Initially no functions loaded, so should return true
90-
EXPECT_TRUE(lib.CheckCache());
69+
EXPECT_THROW(lib.LoadSymbol("", func), std::runtime_error);
9170
}
9271

9372
// Additional extended tests for DlLibBase class
94-
TEST(DlLibBaseExtendedTest, FunCacheSizeAfterMultipleLoads) {
73+
TEST(DlLibBaseExtendedTest, MultipleFunctionLoadsWithoutLibrary) {
9574
ExtendedMockDlLib lib("libnonexistent.so");
9675
DlFun<int, int, int> func1;
9776
DlFun<double, double> func2;
9877
DlFun<void> func3;
9978

100-
// Cache size should be 0 initially
101-
EXPECT_EQ(lib.CacheSize(), 0u);
102-
10379
// Try to load functions without opening library
104-
// These should return false and not add to cache since preconditions aren't met
105-
EXPECT_FALSE(lib.LoadSymbol("func1", func1));
106-
EXPECT_FALSE(lib.LoadSymbol("func2", func2));
107-
EXPECT_FALSE(lib.LoadSymbol("func3", func3));
108-
109-
// Cache size should still be 0 since SelfDlSym returns false when preconditions aren't met
110-
EXPECT_EQ(lib.CacheSize(), 0u);
111-
112-
// CheckFunCache should return true since cache is empty
113-
EXPECT_TRUE(lib.CheckCache());
114-
}
115-
116-
TEST(DlLibBaseExtendedTest, FunCacheSizeAfterMixedSuccessFailure) {
117-
ExtendedMockDlLib lib("libnonexistent.so");
118-
DlFun<int, int, int> func1;
119-
DlFun<double, double> func2;
120-
121-
// Cache size should be 0 initially
122-
EXPECT_EQ(lib.CacheSize(), 0u);
123-
124-
// Try to load one function without opening library (should fail)
125-
EXPECT_FALSE(lib.LoadSymbol("func1", func1));
126-
127-
// Cache size should still be 0 since SelfDlSym returns false when preconditions aren't met
128-
EXPECT_EQ(lib.CacheSize(), 0u);
129-
130-
// CheckFunCache should return true since cache is empty
131-
EXPECT_TRUE(lib.CheckCache());
80+
// These should throw exceptions since library is not loaded
81+
EXPECT_THROW(lib.LoadSymbol("func1", func1), std::runtime_error);
82+
EXPECT_THROW(lib.LoadSymbol("func2", func2), std::runtime_error);
83+
EXPECT_THROW(lib.LoadSymbol("func3", func3), std::runtime_error);
13284
}
13385

13486
TEST(DlLibBaseExtendedTest, ConstructorWithLongLibraryName) {
@@ -143,8 +95,7 @@ TEST(DlLibBaseExtendedTest, LoadSymbolWithVeryLongFunctionName) {
14395
ExtendedMockDlLib lib("libnonexistent.so");
14496
DlFun<int, int, int> func;
14597
std::string longFuncName(1000, 'f');
146-
EXPECT_FALSE(lib.LoadSymbol(longFuncName, func));
147-
EXPECT_EQ(func.GetName(), "unknown");
98+
EXPECT_THROW(lib.LoadSymbol(longFuncName, func), std::runtime_error);
14899
}
149100

150101
} // namespace dlutils

test/dlutils_test.cpp

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
#include "gtest/gtest.h"
2525

2626
#include <string>
27-
#include <vector>
2827

2928
namespace dlutils {
3029
namespace internal {
@@ -51,23 +50,7 @@ TEST(MakeStringTest, WithNumbers) {
5150
EXPECT_EQ(result, "Value: 42 and 3.14");
5251
}
5352

54-
TEST(MakeStringTest, WithVector) {
55-
std::vector<int> vec = {1, 2, 3, 4, 5};
56-
std::string result = internal::MakeString(vec);
57-
EXPECT_EQ(result, "1 2 3 4 5");
58-
}
59-
60-
TEST(MakeStringTest, WithVectorAndCustomDelimiter) {
61-
std::vector<int> vec = {1, 2, 3, 4, 5};
62-
std::string result = internal::MakeString(vec, std::string(","));
63-
EXPECT_EQ(result, "1,2,3,4,5");
64-
}
6553

66-
TEST(MakeStringTest, WithEmptyVector) {
67-
std::vector<int> vec = {};
68-
std::string result = internal::MakeString(vec);
69-
EXPECT_EQ(result, "");
70-
}
7154

7255
// Additional tests for MakeStringInternal
7356
TEST(MakeStringTest, WithStdString) {
@@ -154,16 +137,12 @@ class MockDlLib : public DlLibBase {
154137
public:
155138
explicit MockDlLib(std::string_view lib) : DlLibBase(lib) {}
156139

157-
bool OpenLib() { return SelfDlOpen(); }
140+
void OpenLib() { SelfDlOpen(); }
158141

159142
template <class R, class... Args>
160-
bool LoadSymbol(std::string_view funName, DlFun<R, Args...> &outFun) {
161-
return SelfDlSym(funName, outFun);
143+
void LoadSymbol(std::string_view funName, DlFun<R, Args...> &outFun) {
144+
SelfDlSym(funName, outFun);
162145
}
163-
164-
bool CheckCache() { return CheckFunCache(); }
165-
166-
size_t CacheSize() { return GetFunCacheSize(); }
167146
};
168147

169148
// Tests for DlLibBase class
@@ -173,16 +152,7 @@ TEST(DlLibBaseTest, Constructor) {
173152
SUCCEED();
174153
}
175154

176-
TEST(DlLibBaseTest, GetFunCacheSizeInitiallyZero) {
177-
MockDlLib lib("libtest.so");
178-
EXPECT_EQ(lib.CacheSize(), 0u);
179-
}
180155

181-
TEST(DlLibBaseTest, CheckFunCacheInitiallyTrue) {
182-
MockDlLib lib("libtest.so");
183-
// Initially no functions loaded, so should return true
184-
EXPECT_TRUE(lib.CheckCache());
185-
}
186156

187157
// Additional tests for DlLibBase class
188158
TEST(DlLibBaseTest, ConstructorWithEmptyString) {
@@ -208,35 +178,24 @@ TEST(DlLibBaseTest, SelfDlOpenWithEmptyLibraryName) {
208178
TEST(DlLibBaseTest, SelfDlSymWithEmptyFunctionName) {
209179
MockDlLib lib("libtest.so");
210180
DlFun<int, int, int> func;
211-
EXPECT_FALSE(lib.LoadSymbol("", func));
212-
EXPECT_EQ(func.GetName(), "unknown");
181+
EXPECT_THROW(lib.LoadSymbol("", func), std::runtime_error);
213182
}
214183

215184
TEST(DlLibBaseTest, SelfDlSymWithoutOpeningLibrary) {
216185
MockDlLib lib("libtest.so");
217186
DlFun<int, int, int> func;
218-
EXPECT_FALSE(lib.LoadSymbol("test_function", func));
219-
EXPECT_EQ(func.GetName(), "unknown");
187+
EXPECT_THROW(lib.LoadSymbol("test_function", func), std::runtime_error);
220188
}
221189

222190
TEST(DlLibBaseTest, MultipleFunctionLoads) {
223191
MockDlLib lib("libtest.so");
224192
DlFun<int, int, int> func1;
225193
DlFun<double, double> func2;
226194

227-
// Cache size should be 0 initially
228-
EXPECT_EQ(lib.CacheSize(), 0u);
229-
230195
// Try to load functions without opening library
231-
// These should return false and not add to cache since preconditions aren't met
232-
EXPECT_FALSE(lib.LoadSymbol("func1", func1));
233-
EXPECT_FALSE(lib.LoadSymbol("func2", func2));
234-
235-
// Cache size should still be 0 since SelfDlSym returns false when preconditions aren't met
236-
EXPECT_EQ(lib.CacheSize(), 0u);
237-
238-
// CheckFunCache should return true since cache is empty
239-
EXPECT_TRUE(lib.CheckCache());
196+
// These should throw exceptions since library is not loaded
197+
EXPECT_THROW(lib.LoadSymbol("func1", func1), std::runtime_error);
198+
EXPECT_THROW(lib.LoadSymbol("func2", func2), std::runtime_error);
240199
}
241200

242201
} // namespace dlutils

test/openssl.hpp

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,15 @@ class LibCrypto : public DlLibBase {
4545
return instance;
4646
}
4747

48-
// Check wheter dlopen and dlsym has succeed
49-
bool CheckOk() { return CheckFunCache(); }
50-
5148
// Reload all functions
52-
bool Reload() {
53-
LoadAll();
54-
return CheckFunCache();
55-
}
56-
57-
// Size() function will not check if all functions are callable
58-
size_t Size() { return GetFunCacheSize(); }
49+
void Reload() { LoadAll(); }
5950

6051
// Declare all functions that you need
6152
// NOTE Please make sure the class instance is inited before calling those
6253
// functions
6354
DlFun<EVP_MD_CTX *> EVP_MD_CTX_new;
6455
DlFun<const EVP_MD *> EVP_sha256;
65-
DlFun<const EVP_MD *> EVP_sha1; // Additional hash function for testing
56+
DlFun<const EVP_MD *> EVP_sha1; // Additional hash function for testing
6657
DlFun<int, EVP_MD_CTX *, const EVP_MD *, ENGINE *> EVP_DigestInit_ex;
6758
DlFun<int, EVP_MD_CTX *, const void *, size_t> EVP_DigestUpdate;
6859
DlFun<int, EVP_MD_CTX *, unsigned char *, unsigned int *> EVP_DigestFinal_ex;

0 commit comments

Comments
 (0)