Opened 5 years ago
Last modified 5 years ago
#12987 new Bugs
boost::filesystem::exists crashes
Reported by: | Leinad | Owned by: | Beman Dawes |
---|---|---|---|
Milestone: | To Be Determined | Component: | filesystem |
Version: | Boost 1.64.0 | Severity: | Problem |
Keywords: | Cc: | mika.fischer@…, Marcel.Raad@… |
Description
bool dummy = boost::filesystem::exists("C:\\alma.txt"); //1 class Alma { public: ~Alma() { std::cout << boost::filesystem::path("c:\\alma.txt").string() << std::endl; } }; Alma a; //2 int main(int argc, char* argv[]) { Alma(); //3 return 0; }
The reason for the crash is that "dummy" is initialised before "equal_string_ordinal_ic" in the unnamed namespace in operations.cpp. The fix (or a possible fix) is easy. I made operations.cpp a bit uglier by moving static stuff to "perms make_permissions(const path&, DWORD)":
perms make_permissions(const path& p, DWORD attr) { //this was in the unnamed namespace: static PtrRtlEqualUnicodeString rtl_equal_unicode_string_api = PtrRtlEqualUnicodeString( ::GetProcAddress( ::GetModuleHandleW(L"ntdll.dll"), "RtlEqualUnicodeString")); //this was in the unnamed namespace: static bool(*equal_string_ordinal_ic)(const wchar_t*, const wchar_t*, PtrRtlEqualUnicodeString) = rtl_equal_unicode_string_api ? equal_string_ordinal_ic_1 : equal_string_ordinal_ic_2; perms prms = fs::owner_read | fs::group_read | fs::others_read; if ((attr & FILE_ATTRIBUTE_READONLY) == 0) prms |= fs::owner_write | fs::group_write | fs::others_write; path ext = p.extension(); if (equal_string_ordinal_ic(ext.c_str(), L".exe", rtl_equal_unicode_string_api) || equal_string_ordinal_ic(ext.c_str(), L".com", rtl_equal_unicode_string_api) || equal_string_ordinal_ic(ext.c_str(), L".bat", rtl_equal_unicode_string_api) || equal_string_ordinal_ic(ext.c_str(), L".cmd", rtl_equal_unicode_string_api)) prms |= fs::owner_exe | fs::group_exe | fs::others_exe; return prms; }
I also changed the signatures of two locally used functions:
bool equal_string_ordinal_ic_1(const wchar_t* s1, const wchar_t* s2, PtrRtlEqualUnicodeString rtl_equal_unicode_string_api) bool equal_string_ordinal_ic_2(const wchar_t* s1, const wchar_t* s2, PtrRtlEqualUnicodeString)
And now that the static stuff is moved from the unnamed namespace to the make_permissions method there is no crash anymore.
At least until I comment out (1). Please note that (3) is needed for the second type of crash. This second type of crash is not entirely new. There is a bug filed 5 years ago. I think someone who is involved in developing boost::filesystem with this info (that the second type of crash does not occur if we call (1) (supposed make_permissions has been fixed)) could sort out that old bug as well. https://svn.boost.org/trac/boost/ticket/6638
This static initialisation/destruction looks a nasty business.
compiler: vc14, static lib, statically linked runtime
Change History (18)
comment:1 by , 5 years ago
comment:2 by , 5 years ago
I will work on a solution. We also need to ensure that static local function initialization is thread-safe, which is not meet before VS 2017, therefore your suggested workaround has some limitations.
comment:3 by , 5 years ago
Correction: I meant VS 2013 instead of VS 2017 in regard to thread-safe function statics.
comment:4 by , 5 years ago
Cc: | added |
---|
follow-up: 7 comment:5 by , 5 years ago
Yes that's correct. I think it's a c++11 feature, but we use this already in path.cpp:
std::locale& path_locale() // std::locale("") construction, needed on non-Apple POSIX systems, can throw // (if environmental variables LC_MESSAGES or LANG are wrong, for example), so // path_locale() provides lazy initialization via a local static to ensure that any // exceptions occur after main() starts and so can be caught. Furthermore, // path_locale() is only called if path::codecvt() or path::imbue() are themselves // actually called, ensuring that an exception will only be thrown if std::locale("") // is really needed. { // [locale] paragraph 6: Once a facet reference is obtained from a locale object by // calling use_facet<>, that reference remains usable, and the results from member // functions of it may be cached and re-used, as long as some locale object refers // to that facet. static std::locale loc(default_locale()); #ifdef BOOST_FILESYSTEM_DEBUG std::cout << "***** path_locale() called" << std::endl; #endif return loc; }
follow-up: 8 comment:6 by , 5 years ago
I currently tend to favour to accept this pull request:
https://github.com/boostorg/filesystem/pull/43
It has the advantage to localize the algorithm decision logic and doesn't need to change the calling signature of equal_string_ordinal_ic
in such an ugly way. I would like to test it tomorrow on different systems.
comment:7 by , 5 years ago
follow-up: 9 comment:8 by , 5 years ago
Replying to daniel.kruegler@…:
I currently tend to favour to accept this pull request:
https://github.com/boostorg/filesystem/pull/43
It has the advantage to localize the algorithm decision logic and doesn't need to change the calling signature of
equal_string_ordinal_ic
in such an ugly way. I would like to test it tomorrow on different systems.
I agree. That solution (pull request 43) eliminates the ugliness of the signature of equal_string_ordinal_ic
, but is it any different in terms of local static initialisation thread safeness? Would it have the same limitations?
follow-up: 10 comment:9 by , 5 years ago
Replying to Leinad:
I agree. That solution (pull request 43) eliminates the ugliness of the signature of
equal_string_ordinal_ic
, but is it any different in terms of local static initialisation thread safeness? Would it have the same limitations?
IMO it has similar limitations and I'm working on a variant of that proposal that fixes the thread-safeness problem.
comment:10 by , 5 years ago
Replying to daniel.kruegler@…:
Replying to Leinad:
I agree. That solution (pull request 43) eliminates the ugliness of the signature of
equal_string_ordinal_ic
, but is it any different in terms of local static initialisation thread safeness? Would it have the same limitations?IMO it has similar limitations and I'm working on a variant of that proposal that fixes the thread-safeness problem.
Daniela has now provided a revised resolution (very similar to her original one), which realizes that initialization happens in the (dynamic) program initialization phase (as the original approach did) and doesn't depend on local statics:
https://github.com/boostorg/filesystem/pull/43/commits/24240ed577229eb9568fd8050b9ca4233be92116
It has been successfully tested against Visual Studio versions 2008, 2010, 2012, 2013, 2015, and 2017 and against MinGW with gcc 8 HEAD. I recommend to accept that PR.
comment:11 by , 5 years ago
Is it thread safe?
#include <boost\filesystem.hpp> const int thread_num = 10; boost::barrier barrier(thread_num + 1); bool c = [] { std::vector<std::thread> threads; for (int i = 0; i < thread_num; ++i) { threads.emplace_back([] { barrier.wait(); boost::filesystem::exists("c:"); }); } barrier.wait(); for (auto& thread: threads) thread.join(); return true; }(); int main(int argc, char* argv[]) { return 0; }
c
could be (probably is) initialised before compare_function_initializer
in the unnamed namespace in operations.cpp. All those threads will call compare()
and there might be race condition at reading/setting compare_function
.
I guess on many platforms reading/assigning a pointer is atomic in practice, and GetProcAddress
and GetModuleHandle
are thread-safe, too, so this is not an issue here.
comment:13 by , 5 years ago
Cc: | added |
---|
comment:16 by , 5 years ago
We switched from 1.63 to 1.65.1 recently and we also encounter this issue. Is there a workaround? When can we expect a patch for this issue?
comment:18 by , 5 years ago
Nop. But will probably be in 1.67 See https://github.com/boostorg/filesystem/pull/43
I encountered the same problem using vc2010 on win10.