-
Notifications
You must be signed in to change notification settings - Fork 396
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #10487 - Paths containing characters other than 7-bit ASCII cause failure on windows #10619
Conversation
…adJson too though
…+ avoid using path.string() in that file (need to do it for more)
…wide string to string on windows. Supports 's' (string()) or 'g' (generic_string())presentations
src/EnergyPlus/FileSystem.hh
Outdated
std::string toString(fs::path const &p); | ||
|
||
std::string toGenericString(fs::path const &p); | ||
|
||
fs::path appendSuffixToPath(fs::path const& outputFilePrefixFullPath, const std::string &suffix); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New helpers
// Add a custom formatter for fmt | ||
template <> struct fmt::formatter<fs::path> | ||
{ | ||
// Presentation format: 's' - string, 'g' - generic_string. | ||
char presentation = 's'; | ||
|
||
// Parses format specifications of the form ['s' | 'g']. | ||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) | ||
{ | ||
// Parse the presentation format and store it in the formatter: | ||
auto it = ctx.begin(), end = ctx.end(); | ||
if (it != end && (*it == 's' || *it == 'g')) { | ||
presentation = *it++; | ||
} | ||
|
||
// Check if reached the end of the range: | ||
if (it != end && *it != '}') { | ||
throw format_error("invalid format"); | ||
}; | ||
|
||
// Return an iterator past the end of the parsed range: | ||
return it; | ||
} | ||
|
||
template <typename FormatContext> auto format(const fs::path &p, FormatContext &ctx) -> decltype(ctx.out()) | ||
{ | ||
return format_to(ctx.out(), "{}", presentation == 'g' ? EnergyPlus::FileSystem::toGenericString(p) : EnergyPlus::FileSystem::toString(p)); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new formatter for fs::path, instead of using the std::string one.
Two presentation types are accepted: s
(default), and g
.
s
uses toString (and is similar to fs::path::string() but with narrowing) and g
uses toGenericString (and is sililar to fs::path::generic_string())
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me. I'm like 23% surprised you had to make your own formatter for this. So 77% not surprised because of wide char nonsense.
#ifdef _WIN32 | ||
result.make_preferred(); | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in makeNativePath, avoid going to a string() if not needed (and will potentially crash)
#ifdef _WIN32 | ||
std::wstring pathStr = path.native(); | ||
if (!pathStr.empty()) { | ||
while ((pathStr.back() == DataStringGlobals::pathChar) || (pathStr.back() == DataStringGlobals::altpathChar)) { | ||
pathStr.erase(pathStr.size() - 1); | ||
} | ||
} | ||
#else |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same in getParentDirectoryPath...
const std::uintmax_t file_size = fs::file_size(filePath); | ||
std::ifstream file(filePath, mode); | ||
if (!file.is_open()) { | ||
throw FatalError(fmt::format("Could not open file: {}", filePath)); | ||
} | ||
std::string result(file_size, '\0'); | ||
file.read(result.data(), file_size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change readFile and readJSON to use ifstream (that handles fs::path) + avoid using path.string()
std::string toString(fs::path const &p) | ||
{ | ||
if constexpr (std::is_same_v<typename fs::path::value_type, wchar_t>) { | ||
return CLI::narrow(p.wstring()); | ||
} else { | ||
return p.string(); | ||
} | ||
} | ||
|
||
std::string toGenericString(fs::path const &p) | ||
{ | ||
if constexpr (std::is_same_v<typename fs::path::value_type, wchar_t>) { | ||
return CLI::narrow(p.generic_wstring()); | ||
} else { | ||
return p.generic_string(); | ||
} | ||
} | ||
|
||
fs::path appendSuffixToPath(fs::path const &outputFilePrefixFullPath, const std::string &suffix) | ||
{ | ||
if constexpr (std::is_same_v<typename fs::path::value_type, wchar_t>) { | ||
return {outputFilePrefixFullPath.wstring() + CLI::widen(suffix)}; | ||
} else { | ||
return {outputFilePrefixFullPath.string() + suffix}; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New helpers definitions
src/EnergyPlus/SQLiteProcedures.cc
Outdated
if constexpr (debug) { | ||
// std::cout << "errorStream=" << errorStream << ", dbName=" << dbName << std::endl; | ||
// std::cout << "dbName.string()=" << dbName.string() << std::endl; | ||
// std::cout << "dbName.generic_string()=" << dbName.generic_string() << std::endl; | ||
std::wcout << "dbName.generic_wstring()=" << dbName.generic_wstring() << std::endl; | ||
std::cout << "narrow(dbName.generic_wstring())=" << FileSystem::toGenericString(dbName) << std::endl; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to clean that up...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 48eb2eb
if (fs::is_regular_file(dbName)) { | ||
std::error_code ec; | ||
if (!fs::remove(dbName, ec)) { | ||
// File operation failed. SQLite connection is not in an error state. | ||
*m_errorStream << "SQLite3 message, can't remove old database. code=" << ec.value() << ", error: " << ec.message() | ||
<< std::endl; | ||
ok = false; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of calling the C stdio.h remove, use the fs::remove one
@@ -2665,7 +2696,7 @@ SQLiteProcedures::SQLiteProcedures(std::shared_ptr<std::ostream> const &errorStr | |||
|
|||
if (ok) { | |||
// Now open the output db for the duration of the simulation | |||
rc = sqlite3_open_v2(dbName.string().c_str(), &m_connection, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); | |||
rc = sqlite3_open_v2(dbName_utf8.c_str(), &m_connection, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When opening the sqlite database, same thing: don't call string(), but our narrowing one.
@@ -490,7 +490,7 @@ namespace ScheduleManager { | |||
ShowSevereError(state, error); | |||
} | |||
} | |||
ShowContinueError(state, fmt::format("Error Occurred in {}", state.files.TempFullFilePath.filePath.string())); | |||
ShowContinueError(state, fmt::format("Error Occurred in {}", state.files.TempFullFilePath.filePath)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use the new fmt::formatterfs::path everywhere, so we don't call fs::path::string()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, I'll pull develop and test things out. :) :g :)
// Add a custom formatter for fmt | ||
template <> struct fmt::formatter<fs::path> | ||
{ | ||
// Presentation format: 's' - string, 'g' - generic_string. | ||
char presentation = 's'; | ||
|
||
// Parses format specifications of the form ['s' | 'g']. | ||
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) | ||
{ | ||
// Parse the presentation format and store it in the formatter: | ||
auto it = ctx.begin(), end = ctx.end(); | ||
if (it != end && (*it == 's' || *it == 'g')) { | ||
presentation = *it++; | ||
} | ||
|
||
// Check if reached the end of the range: | ||
if (it != end && *it != '}') { | ||
throw format_error("invalid format"); | ||
}; | ||
|
||
// Return an iterator past the end of the parsed range: | ||
return it; | ||
} | ||
|
||
template <typename FormatContext> auto format(const fs::path &p, FormatContext &ctx) -> decltype(ctx.out()) | ||
{ | ||
return format_to(ctx.out(), "{}", presentation == 'g' ? EnergyPlus::FileSystem::toGenericString(p) : EnergyPlus::FileSystem::toString(p)); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me. I'm like 23% surprised you had to make your own formatter for this. So 77% not surprised because of wide char nonsense.
Everything is totally happy locally, but I had to apply Clang Format. I'll push that up real quick and let CI confirm that, and then merge. |
CI seems all happy with it, and the only thing I did was apply Clang Format, so I'm not waiting on Decent to finish. Merging. Thanks @jmarrec |
Pull request overview
I have made a test release at https://github.com/jmarrec/EnergyPlus/releases/tag/v24.2.0-windows_non_ascii and @DaveInCaz tried it.
(
namespace fs = std::filesystem
)As a reminder, Windows behaves differently than the rest of the world (Shocking, I know). Anyways, on all platforms we target, fs::path::value_type is
char
, except on windows where it'swchar_t
.With a char that has a 2 bytes unicode such as Б (Unicode 0411), it just plain crashes everytime you call
fs::path::string()
.The fix here is to avoid ever doing that. I narrow the path manually when either printing or passing it to for eg sqlite3 is needed.
Pull Request Author
Add to this list or remove from it as applicable. This is a simple templated set of guidelines.
Reviewer
This will not be exhaustively relevant to every PR.