Fix windows libc++ fs::path fstream compile errors
What changed, and why it matters
This is a portability fix for compiling Bitcoin Core on Windows with newer versions of the libc++ standard library. It does not change runtime behavior on currently working platforms; it only ensures the code still builds on a less-common Windows toolchain. There is no security vulnerability being fixed here.
No security action required. Treat as a normal build-system/portability patch.
Security signals we found
No security-relevant signal: change is a build/portability fix
No memory safety, input validation, cryptography, or authorization changes
No bug or vulnerability described in commit message
Evidence from the diff
The commit adds a fs::path::std_path() accessor returning a std::filesystem::path& and replaces direct construction of std::ifstream/std::ofstream from fs::path with construction from .std_path(). It also deletes fs::path’s implicit operator string_type() to prevent future non-portable conversions. The root cause is LWG issue 3430, which removes implicit path conversion in newer libc++ versions, causing compile failures on Windows where path::string_type is std::wstring. The change is purely compile-time/portability.
Changed components
src/util/fs.hfile stream construction sites across bench, init, qt, wallet, and testsInspect captured patch +37 / −27
diff --git a/src/bench/bench.cpp b/src/bench/bench.cpp
index 26daff50..9cc1f30f 100644
--- a/src/bench/bench.cpp
+++ b/src/bench/bench.cpp
@@ -56,7 +56,7 @@ void GenerateTemplateResults(const std::vector<ankerl::nanobench::Result>& bench
// nothing to write, bail out
return;
}
- std::ofstream fout{file};
+ std::ofstream fout{file.std_path()};
if (fout.is_open()) {
ankerl::nanobench::render(tpl, benchmarkResults, fout);
std::cout << "Created " << file << std::endl;
diff --git a/src/common/config.cpp b/src/common/config.cpp
index 42f5e283..7216022c 100644
--- a/src/common/config.cpp
+++ b/src/common/config.cpp
@@ -135,7 +135,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
error = strprintf("Config file \"%s\" is a directory.", fs::PathToString(conf_path));
return false;
}
- stream = std::ifstream{conf_path};
+ stream = std::ifstream{conf_path.std_path()};
// If the file is explicitly specified, it must be readable
if (IsArgSet("-conf") && !stream.good()) {
error = strprintf("specified config file \"%s\" could not be opened.", fs::PathToString(conf_path));
@@ -187,7 +187,7 @@ bool ArgsManager::ReadConfigFiles(std::string& error, bool ignore_invalid_keys)
error = strprintf("Included config file \"%s\" is a directory.", fs::PathToString(include_conf_path));
return false;
}
- std::ifstream conf_file_stream{include_conf_path};
+ std::ifstream conf_file_stream{include_conf_path.std_path()};
if (conf_file_stream.good()) {
if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) {
return false;
diff --git a/src/init.cpp b/src/init.cpp
index dadd56d2..4f672751 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -179,7 +179,7 @@ static fs::path GetPidFile(const ArgsManager& args)
{
if (args.IsArgNegated("-pid")) return true;
- std::ofstream file{GetPidFile(args)};
+ std::ofstream file{GetPidFile(args).std_path()};
if (file) {
#ifdef WIN32
tfm::format(file, "%d\n", GetCurrentProcessId());
diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp
index 7bafb3db..28610db4 100644
--- a/src/qt/guiutil.cpp
+++ b/src/qt/guiutil.cpp
@@ -447,7 +447,7 @@ bool openBitcoinConf()
fs::path pathConfig = gArgs.GetConfigFilePath();
/* Create the file */
- std::ofstream configFile{pathConfig, std::ios_base::app};
+ std::ofstream configFile{pathConfig.std_path(), std::ios_base::app};
if (!configFile.good())
return false;
@@ -607,7 +607,7 @@ fs::path static GetAutostartFilePath()
bool GetStartOnSystemStartup()
{
- std::ifstream optionFile{GetAutostartFilePath()};
+ std::ifstream optionFile{GetAutostartFilePath().std_path()};
if (!optionFile.good())
return false;
// Scan through file for "Hidden=true":
@@ -639,7 +639,7 @@ bool SetStartOnSystemStartup(bool fAutoStart)
fs::create_directories(GetAutostartDir());
- std::ofstream optionFile{GetAutostartFilePath(), std::ios_base::out | std::ios_base::trunc};
+ std::ofstream optionFile{GetAutostartFilePath().std_path(), std::ios_base::out | std::ios_base::trunc};
if (!optionFile.good())
return false;
ChainType chain = gArgs.GetChainType();
diff --git a/src/qt/test/optiontests.cpp b/src/qt/test/optiontests.cpp
index f8a1da84..0d522e98 100644
--- a/src/qt/test/optiontests.cpp
+++ b/src/qt/test/optiontests.cpp
@@ -70,7 +70,7 @@ void OptionTests::migrateSettings()
QVERIFY(!settings.contains("fUseSeparateProxyTor"));
QVERIFY(!settings.contains("addrSeparateProxyTor"));
- std::ifstream file(gArgs.GetDataDirNet() / "settings.json");
+ std::ifstream file((gArgs.GetDataDirNet() / "settings.json").std_path());
std::string default_warning = strprintf("This file is automatically generated and updated by %s. Please do not edit this file while the node "
"is running, as any changes might be ignored or overwritten.",
CLIENT_NAME);
diff --git a/src/test/fs_tests.cpp b/src/test/fs_tests.cpp
index c237963a..28fcf952 100644
--- a/src/test/fs_tests.cpp
+++ b/src/test/fs_tests.cpp
@@ -51,37 +51,37 @@ BOOST_AUTO_TEST_CASE(fsbridge_fstream)
fs::path tmpfile1 = tmpfolder / fs::u8path("fs_tests_₿_🏃");
fs::path tmpfile2 = tmpfolder / fs::path(u8"fs_tests_₿_🏃");
{
- std::ofstream file{tmpfile1};
+ std::ofstream file{tmpfile1.std_path()};
file << "bitcoin";
}
{
- std::ifstream file{tmpfile2};
+ std::ifstream file{tmpfile2.std_path()};
std::string input_buffer;
file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "bitcoin");
}
{
- std::ifstream file{tmpfile1, std::ios_base::in | std::ios_base::ate};
+ std::ifstream file{tmpfile1.std_path(), std::ios_base::in | std::ios_base::ate};
std::string input_buffer;
file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "");
}
{
- std::ofstream file{tmpfile2, std::ios_base::out | std::ios_base::app};
+ std::ofstream file{tmpfile2.std_path(), std::ios_base::out | std::ios_base::app};
file << "tests";
}
{
- std::ifstream file{tmpfile1};
+ std::ifstream file{tmpfile1.std_path()};
std::string input_buffer;
file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "bitcointests");
}
{
- std::ofstream file{tmpfile2, std::ios_base::out | std::ios_base::trunc};
+ std::ofstream file{tmpfile2.std_path(), std::ios_base::out | std::ios_base::trunc};
file << "bitcoin";
}
{
- std::ifstream file{tmpfile1};
+ std::ifstream file{tmpfile1.std_path()};
std::string input_buffer;
file >> input_buffer;
BOOST_CHECK_EQUAL(input_buffer, "bitcoin");
@@ -116,12 +116,12 @@ BOOST_AUTO_TEST_CASE(rename)
const std::string path2_contents{"2222"};
{
- std::ofstream file{path1};
+ std::ofstream file{path1.std_path()};
file << path1_contents;
}
{
- std::ofstream file{path2};
+ std::ofstream file{path2.std_path()};
file << path2_contents;
}
@@ -131,7 +131,7 @@ BOOST_AUTO_TEST_CASE(rename)
BOOST_CHECK(!fs::exists(path1));
{
- std::ifstream file{path2};
+ std::ifstream file{path2.std_path()};
std::string contents;
file >> contents;
BOOST_CHECK_EQUAL(contents, path1_contents);
diff --git a/src/test/logging_tests.cpp b/src/test/logging_tests.cpp
index 2e3b1c39..3cf261a4 100644
--- a/src/test/logging_tests.cpp
+++ b/src/test/logging_tests.cpp
@@ -40,7 +40,7 @@ static void ResetLogger()
static std::vector<std::string> ReadDebugLogLines()
{
std::vector<std::string> lines;
- std::ifstream ifs{LogInstance().m_file_path};
+ std::ifstream ifs{LogInstance().m_file_path.std_path()};
for (std::string line; std::getline(ifs, line);) {
lines.push_back(std::move(line));
}
@@ -421,7 +421,7 @@ void TestLogFromLocation(Location location, const std::string& message,
using Status = BCLog::LogRateLimiter::Status;
if (!suppressions_active) assert(status == Status::UNSUPPRESSED); // developer error
- std::ofstream ofs(LogInstance().m_file_path, std::ios::out | std::ios::trunc); // clear debug log
+ std::ofstream ofs(LogInstance().m_file_path.std_path(), std::ios::out | std::ios::trunc); // clear debug log
LogFromLocation(location, message);
auto log_lines{ReadDebugLogLines()};
BOOST_TEST_INFO_SCOPE(log_lines.size() << " log_lines read: \n" << util::Join(log_lines, "\n"));
diff --git a/src/test/script_assets_tests.cpp b/src/test/script_assets_tests.cpp
index 0e2fec87..af96987f 100644
--- a/src/test/script_assets_tests.cpp
+++ b/src/test/script_assets_tests.cpp
@@ -159,7 +159,7 @@ BOOST_AUTO_TEST_CASE(script_assets_test)
bool exists = fs::exists(path);
BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
if (!exists) return;
- std::ifstream file{path};
+ std::ifstream file{path.std_path()};
BOOST_CHECK(file.is_open());
file.seekg(0, std::ios::end);
size_t length = file.tellg();
diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp
index f9038c4f..13821db4 100644
--- a/src/test/util_tests.cpp
+++ b/src/test/util_tests.cpp
@@ -1619,7 +1619,7 @@ BOOST_AUTO_TEST_CASE(util_ReadBinaryFile)
expected_text += "0123456789";
}
{
- std::ofstream file{tmpfile};
+ std::ofstream file{tmpfile.std_path()};
file << expected_text;
}
{
@@ -1650,7 +1650,7 @@ BOOST_AUTO_TEST_CASE(util_WriteBinaryFile)
std::string expected_text = "bitcoin";
auto valid = WriteBinaryFile(tmpfile, expected_text);
std::string actual_text;
- std::ifstream file{tmpfile};
+ std::ifstream file{tmpfile.std_path()};
file >> actual_text;
BOOST_CHECK(valid);
BOOST_CHECK_EQUAL(actual_text, expected_text);
diff --git a/src/util/fs.h b/src/util/fs.h
index 7c313e20..8ff30482 100644
--- a/src/util/fs.h
+++ b/src/util/fs.h
@@ -34,6 +34,10 @@ class path : public std::filesystem::path
public:
using std::filesystem::path::path;
+ // Convenience method for accessing standard path type without needing a cast.
+ std::filesystem::path& std_path() { return *this; }
+ const std::filesystem::path& std_path() const { return *this; }
+
// Allow path objects arguments for compatibility.
path(std::filesystem::path path) : std::filesystem::path::path(std::move(path)) {}
path& operator=(std::filesystem::path path) { std::filesystem::path::operator=(std::move(path)); return *this; }
@@ -54,6 +58,12 @@ public:
// Disallow std::string conversion method to avoid locale-dependent encoding on windows.
std::string string() const = delete;
+ // Disallow implicit string conversion to ensure code is portable.
+ // `string_type` may be `string` or `wstring` depending on the platform, so
+ // using this conversion could result in code that compiles on unix but
+ // fails to compile on windows, or vice versa.
+ operator string_type() const = delete;
+
/**
* Return a UTF-8 representation of the path as a std::string, for
* compatibility with code using std::string. For code using the newer
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 781b6fa4..cad3d7b5 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -102,7 +102,7 @@ bool IsBDBFile(const fs::path& path)
if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
if (size < 4096) return false;
- std::ifstream file{path, std::ios::binary};
+ std::ifstream file{path.std_path(), std::ios::binary};
if (!file.is_open()) return false;
file.seekg(12, std::ios::beg); // Magic bytes start at offset 12
@@ -126,7 +126,7 @@ bool IsSQLiteFile(const fs::path& path)
if (ec) LogWarning("Error reading file_size: %s [%s]", ec.message(), fs::PathToString(path));
if (size < 512) return false;
- std::ifstream file{path, std::ios::binary};
+ std::ifstream file{path.std_path(), std::ios::binary};
if (!file.is_open()) return false;
// Magic is at beginning and is 16 bytes long
diff --git a/src/wallet/dump.cpp b/src/wallet/dump.cpp
index 6b193ad7..312753dd 100644
--- a/src/wallet/dump.cpp
+++ b/src/wallet/dump.cpp
@@ -134,7 +134,7 @@ bool CreateFromDump(const ArgsManager& args, const std::string& name, const fs::
error = strprintf(_("Dump file %s does not exist."), fs::PathToString(dump_path));
return false;
}
- std::ifstream dump_file{dump_path};
+ std::ifstream dump_file{dump_path.std_path()};
// Compute the checksum
HashWriter hasher{};
diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp
index 5bdf36ec..a01561df 100644
--- a/src/wallet/test/init_test_fixture.cpp
+++ b/src/wallet/test/init_test_fixture.cpp
@@ -36,7 +36,7 @@ InitWalletDirTestingSetup::InitWalletDirTestingSetup(const ChainType chainType)
fs::create_directories(m_walletdir_path_cases["default"]);
fs::create_directories(m_walletdir_path_cases["custom"]);
fs::create_directories(m_walletdir_path_cases["relative"]);
- std::ofstream f{m_walletdir_path_cases["file"]};
+ std::ofstream f{m_walletdir_path_cases["file"].std_path()};
f.close();
}
Why this scored 21/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.