diff --git a/c/driver_manager/adbc_driver_manager.cc b/c/driver_manager/adbc_driver_manager.cc index 93bc787915..f097be6a4b 100644 --- a/c/driver_manager/adbc_driver_manager.cc +++ b/c/driver_manager/adbc_driver_manager.cc @@ -134,6 +134,35 @@ void AddSearchPathsToError(const SearchPaths& search_paths, std::string& error_m } } +// Generate a note for the error message if the library name has potentially +// non-printable (or really non-ASCII-printable-range) characters. Oblivious +// to Unicode and locales. +std::string CheckNonPrintableLibraryName(const std::string& name) { + // We could use std::isprint, but that requires locales; prefer a + // simpler check for out-of-ASCII-range. + bool has_non_printable = std::any_of(name.begin(), name.end(), [](char c) { + int v = static_cast(c); + return v < 32 || v > 127; + }); + if (!has_non_printable) return ""; + + std::string error_message = "Note: driver name may have non-printable characters: `"; + // TODO(lidavidm): we can simplify with C++20 + for (char c : name) { + int v = static_cast(c); + if (v < 32 || v > 127) { + error_message += "\\x"; + char buf[3]; + std::snprintf(buf, sizeof(buf), "%02x", v & 0xFF); + error_message += buf; + } else { + error_message += c; + } + } + error_message += "`"; + return error_message; +} + // Platform-specific helpers #if defined(_WIN32) @@ -230,6 +259,7 @@ struct OwnedError { #ifdef _WIN32 using char_type = wchar_t; +using string_type = std::wstring; std::string Utf8Encode(const std::wstring& wstr) { if (wstr.empty()) return std::string(); @@ -253,6 +283,7 @@ std::wstring Utf8Decode(const std::string& str) { #else using char_type = char; +using string_type = std::string; #endif // _WIN32 /// \brief The location and entrypoint of a resolved driver. @@ -586,7 +617,7 @@ struct ManagedLibrary { auto status = LoadDriverManifest(driver_path, info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } return status; } @@ -594,7 +625,7 @@ struct ManagedLibrary { // if the extension is not .toml, then just try to load the provided // path as if it was an absolute path to a driver library info.lib_path = driver_path; - return Load(driver_path.c_str(), {}, error); + return Load(driver_path.native(), {}, error); } if (driver_path.is_absolute()) { @@ -604,14 +635,14 @@ struct ManagedLibrary { if (std::filesystem::exists(driver_path)) { auto status = LoadDriverManifest(driver_path, info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } } driver_path.replace_extension(""); // otherwise just try to load the provided path as if it was an absolute path info.lib_path = driver_path; - return Load(driver_path.c_str(), {}, error); + return Load(driver_path.native(), {}, error); } if (driver_path.has_extension()) { @@ -629,7 +660,7 @@ struct ManagedLibrary { #endif // defined(_WIN32) if (HasExtension(driver_path, kPlatformLibrarySuffix)) { info.lib_path = driver_path; - return Load(driver_path.c_str(), {}, error); + return Load(driver_path.native(), {}, error); } SetError(error, "Driver name has unrecognized extension: " + @@ -674,7 +705,7 @@ struct ManagedLibrary { auto status = LoadDriverManifest(full_path, info, &intermediate_error.error); if (status == ADBC_STATUS_OK) { // Don't pass attempted_paths here; we'll generate the error at a higher level - status = Load(info.lib_path.c_str(), {}, &intermediate_error.error); + status = Load(info.lib_path.native(), {}, &intermediate_error.error); if (status == ADBC_STATUS_OK) { return status; } @@ -718,7 +749,7 @@ struct ManagedLibrary { // remove the .toml extension; Load will add the DLL/SO/DYLIB suffix full_path.replace_extension(""); // Don't pass error here - it'll be suppressed anyways - auto status = Load(full_path.c_str(), {}, nullptr); + auto status = Load(full_path.native(), {}, nullptr); if (status == ADBC_STATUS_OK) { info.lib_path = full_path; return status; @@ -797,7 +828,7 @@ struct ManagedLibrary { auto status = LoadDriverFromRegistry(HKEY_CURRENT_USER, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } if (error && error->message) { std::string message = "HKEY_CURRENT_USER\\"s; @@ -824,7 +855,7 @@ struct ManagedLibrary { auto status = LoadDriverFromRegistry(HKEY_LOCAL_MACHINE, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } if (error && error->message) { std::string message = "HKEY_LOCAL_MACHINE\\"s; @@ -848,7 +879,7 @@ struct ManagedLibrary { } info.lib_path = driver_path; - return Load(driver_path.c_str(), search_paths, error); + return Load(driver_path.native(), search_paths, error); #else // Otherwise, search the configured paths. SearchPaths more_search_paths = @@ -880,7 +911,7 @@ struct ManagedLibrary { search_paths.insert(search_paths.end(), more_search_paths.begin(), more_search_paths.end()); info.lib_path = driver_path; - return Load(driver_path.c_str(), search_paths, error); + return Load(driver_path.native(), search_paths, error); } return status; #endif // _WIN32 @@ -888,14 +919,15 @@ struct ManagedLibrary { /// \return ADBC_STATUS_NOT_FOUND if the driver shared library could not be /// found, ADBC_STATUS_OK otherwise - AdbcStatusCode Load(const char_type* library, const SearchPaths& attempted_paths, + AdbcStatusCode Load(const string_type& library, const SearchPaths& attempted_paths, struct AdbcError* error) { std::string error_message; #if defined(_WIN32) - HMODULE handle = LoadLibraryExW(library, NULL, 0); + HMODULE handle = LoadLibraryExW(library.c_str(), NULL, 0); if (!handle) { + error_message = "Could not load `"; error_message += Utf8Encode(library); - error_message += ": LoadLibraryExW() failed: "; + error_message += "`: LoadLibraryExW() failed: "; GetWinError(&error_message); std::wstring full_driver_name = library; @@ -909,6 +941,12 @@ struct ManagedLibrary { } } if (!handle) { + std::string name = Utf8Encode(library); + std::string message = CheckNonPrintableLibraryName(name); + if (!message.empty()) { + error_message += "\n"; + error_message += message; + } AddSearchPathsToError(attempted_paths, error_message); SetError(error, error_message); return ADBC_STATUS_NOT_FOUND; @@ -923,9 +961,11 @@ struct ManagedLibrary { static const std::string kPlatformLibrarySuffix = ".so"; #endif // defined(__APPLE__) - void* handle = dlopen(library, RTLD_NOW | RTLD_LOCAL); + void* handle = dlopen(library.c_str(), RTLD_NOW | RTLD_LOCAL); if (!handle) { - error_message = "dlopen() failed: "; + error_message = "Could not load `"; + error_message += library; + error_message += "`: dlopen() failed: "; error_message += dlerror(); // If applicable, append the shared library prefix/extension and @@ -955,6 +995,11 @@ struct ManagedLibrary { if (handle) { this->handle = handle; } else { + std::string message = CheckNonPrintableLibraryName(library); + if (!message.empty()) { + error_message += "\n"; + error_message += message; + } AddSearchPathsToError(attempted_paths, error_message); SetError(error, error_message); return ADBC_STATUS_NOT_FOUND; diff --git a/c/driver_manager/adbc_driver_manager_test.cc b/c/driver_manager/adbc_driver_manager_test.cc index 80a2fe77fd..e6315e4f88 100644 --- a/c/driver_manager/adbc_driver_manager_test.cc +++ b/c/driver_manager/adbc_driver_manager_test.cc @@ -1277,11 +1277,33 @@ shared = "adbc_driver_sqlite")"; ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList( &database.value, search_path.data(), &error), IsOkStatus(&error)); - ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), - IsStatus(ADBC_STATUS_OK, &error)); + ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), IsOkStatus(&error)); } ASSERT_TRUE(std::filesystem::remove(filepath)); } +TEST_F(DriverManifest, ControlCodes) { + const std::string uri = "\026sqlite:file::memory:"; + for (const auto& driver_option : {"driver", "uri"}) { + SCOPED_TRACE(driver_option); + SCOPED_TRACE(uri); + adbc_validation::Handle database; + ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error)); + ASSERT_THAT( + AdbcDatabaseSetOption(&database.value, driver_option, uri.c_str(), &error), + IsOkStatus(&error)); + std::string search_path = temp_dir.string(); + ASSERT_THAT(AdbcDriverManagerDatabaseSetAdditionalSearchPathList( + &database.value, search_path.data(), &error), + IsOkStatus(&error)); + ASSERT_THAT(AdbcDatabaseInit(&database.value, &error), + IsStatus(ADBC_STATUS_NOT_FOUND, &error)); + ASSERT_THAT( + error.message, + ::testing::HasSubstr( + "Note: driver name may have non-printable characters: `\\x16sqlite`")); + } +} + } // namespace adbc diff --git a/go/adbc/drivermgr/adbc_driver_manager.cc b/go/adbc/drivermgr/adbc_driver_manager.cc index 93bc787915..f097be6a4b 100644 --- a/go/adbc/drivermgr/adbc_driver_manager.cc +++ b/go/adbc/drivermgr/adbc_driver_manager.cc @@ -134,6 +134,35 @@ void AddSearchPathsToError(const SearchPaths& search_paths, std::string& error_m } } +// Generate a note for the error message if the library name has potentially +// non-printable (or really non-ASCII-printable-range) characters. Oblivious +// to Unicode and locales. +std::string CheckNonPrintableLibraryName(const std::string& name) { + // We could use std::isprint, but that requires locales; prefer a + // simpler check for out-of-ASCII-range. + bool has_non_printable = std::any_of(name.begin(), name.end(), [](char c) { + int v = static_cast(c); + return v < 32 || v > 127; + }); + if (!has_non_printable) return ""; + + std::string error_message = "Note: driver name may have non-printable characters: `"; + // TODO(lidavidm): we can simplify with C++20 + for (char c : name) { + int v = static_cast(c); + if (v < 32 || v > 127) { + error_message += "\\x"; + char buf[3]; + std::snprintf(buf, sizeof(buf), "%02x", v & 0xFF); + error_message += buf; + } else { + error_message += c; + } + } + error_message += "`"; + return error_message; +} + // Platform-specific helpers #if defined(_WIN32) @@ -230,6 +259,7 @@ struct OwnedError { #ifdef _WIN32 using char_type = wchar_t; +using string_type = std::wstring; std::string Utf8Encode(const std::wstring& wstr) { if (wstr.empty()) return std::string(); @@ -253,6 +283,7 @@ std::wstring Utf8Decode(const std::string& str) { #else using char_type = char; +using string_type = std::string; #endif // _WIN32 /// \brief The location and entrypoint of a resolved driver. @@ -586,7 +617,7 @@ struct ManagedLibrary { auto status = LoadDriverManifest(driver_path, info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } return status; } @@ -594,7 +625,7 @@ struct ManagedLibrary { // if the extension is not .toml, then just try to load the provided // path as if it was an absolute path to a driver library info.lib_path = driver_path; - return Load(driver_path.c_str(), {}, error); + return Load(driver_path.native(), {}, error); } if (driver_path.is_absolute()) { @@ -604,14 +635,14 @@ struct ManagedLibrary { if (std::filesystem::exists(driver_path)) { auto status = LoadDriverManifest(driver_path, info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } } driver_path.replace_extension(""); // otherwise just try to load the provided path as if it was an absolute path info.lib_path = driver_path; - return Load(driver_path.c_str(), {}, error); + return Load(driver_path.native(), {}, error); } if (driver_path.has_extension()) { @@ -629,7 +660,7 @@ struct ManagedLibrary { #endif // defined(_WIN32) if (HasExtension(driver_path, kPlatformLibrarySuffix)) { info.lib_path = driver_path; - return Load(driver_path.c_str(), {}, error); + return Load(driver_path.native(), {}, error); } SetError(error, "Driver name has unrecognized extension: " + @@ -674,7 +705,7 @@ struct ManagedLibrary { auto status = LoadDriverManifest(full_path, info, &intermediate_error.error); if (status == ADBC_STATUS_OK) { // Don't pass attempted_paths here; we'll generate the error at a higher level - status = Load(info.lib_path.c_str(), {}, &intermediate_error.error); + status = Load(info.lib_path.native(), {}, &intermediate_error.error); if (status == ADBC_STATUS_OK) { return status; } @@ -718,7 +749,7 @@ struct ManagedLibrary { // remove the .toml extension; Load will add the DLL/SO/DYLIB suffix full_path.replace_extension(""); // Don't pass error here - it'll be suppressed anyways - auto status = Load(full_path.c_str(), {}, nullptr); + auto status = Load(full_path.native(), {}, nullptr); if (status == ADBC_STATUS_OK) { info.lib_path = full_path; return status; @@ -797,7 +828,7 @@ struct ManagedLibrary { auto status = LoadDriverFromRegistry(HKEY_CURRENT_USER, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } if (error && error->message) { std::string message = "HKEY_CURRENT_USER\\"s; @@ -824,7 +855,7 @@ struct ManagedLibrary { auto status = LoadDriverFromRegistry(HKEY_LOCAL_MACHINE, driver_path.native(), info, error); if (status == ADBC_STATUS_OK) { - return Load(info.lib_path.c_str(), {}, error); + return Load(info.lib_path.native(), {}, error); } if (error && error->message) { std::string message = "HKEY_LOCAL_MACHINE\\"s; @@ -848,7 +879,7 @@ struct ManagedLibrary { } info.lib_path = driver_path; - return Load(driver_path.c_str(), search_paths, error); + return Load(driver_path.native(), search_paths, error); #else // Otherwise, search the configured paths. SearchPaths more_search_paths = @@ -880,7 +911,7 @@ struct ManagedLibrary { search_paths.insert(search_paths.end(), more_search_paths.begin(), more_search_paths.end()); info.lib_path = driver_path; - return Load(driver_path.c_str(), search_paths, error); + return Load(driver_path.native(), search_paths, error); } return status; #endif // _WIN32 @@ -888,14 +919,15 @@ struct ManagedLibrary { /// \return ADBC_STATUS_NOT_FOUND if the driver shared library could not be /// found, ADBC_STATUS_OK otherwise - AdbcStatusCode Load(const char_type* library, const SearchPaths& attempted_paths, + AdbcStatusCode Load(const string_type& library, const SearchPaths& attempted_paths, struct AdbcError* error) { std::string error_message; #if defined(_WIN32) - HMODULE handle = LoadLibraryExW(library, NULL, 0); + HMODULE handle = LoadLibraryExW(library.c_str(), NULL, 0); if (!handle) { + error_message = "Could not load `"; error_message += Utf8Encode(library); - error_message += ": LoadLibraryExW() failed: "; + error_message += "`: LoadLibraryExW() failed: "; GetWinError(&error_message); std::wstring full_driver_name = library; @@ -909,6 +941,12 @@ struct ManagedLibrary { } } if (!handle) { + std::string name = Utf8Encode(library); + std::string message = CheckNonPrintableLibraryName(name); + if (!message.empty()) { + error_message += "\n"; + error_message += message; + } AddSearchPathsToError(attempted_paths, error_message); SetError(error, error_message); return ADBC_STATUS_NOT_FOUND; @@ -923,9 +961,11 @@ struct ManagedLibrary { static const std::string kPlatformLibrarySuffix = ".so"; #endif // defined(__APPLE__) - void* handle = dlopen(library, RTLD_NOW | RTLD_LOCAL); + void* handle = dlopen(library.c_str(), RTLD_NOW | RTLD_LOCAL); if (!handle) { - error_message = "dlopen() failed: "; + error_message = "Could not load `"; + error_message += library; + error_message += "`: dlopen() failed: "; error_message += dlerror(); // If applicable, append the shared library prefix/extension and @@ -955,6 +995,11 @@ struct ManagedLibrary { if (handle) { this->handle = handle; } else { + std::string message = CheckNonPrintableLibraryName(library); + if (!message.empty()) { + error_message += "\n"; + error_message += message; + } AddSearchPathsToError(attempted_paths, error_message); SetError(error, error_message); return ADBC_STATUS_NOT_FOUND;