Why does boost :: filesystem break instead of throwing an exception?
I am porting some code from VS2010 (using boost 1.55) to VS 2015 (using boost 1.60).
I end up with "Microsoft Visual C ++ Runtime Library" telling abort() has been called
me that while boost rties is throwing an exception. However, I could get it to throw other exceptions without any problem (and it worked with VS2010 / boost1.55):
#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>
#include <iostream>
int main( int argc, char* argv[] )
{
// Stepping to folder:
try
{
boost::filesystem::current_path("B:/dev/msvc2015/vobs_bci/public/tst/base/cppunit/utlfile");
std::cout << "Worked" << std::endl; // works OK
}
catch (...)
{
}
// test throwing upon copy_directory because dource folder does not exist:
try
{
boost::filesystem::copy_directory("s", "b");
}
catch (...)
{
std::cout << "Caught" << std::endl; // works OK
}
// test throwing upon copy because target file already exists:
try
{
boost::filesystem::copy("./test.h", "./copied.cpp"); // works
boost::filesystem::copy("./test.h", "./copied.cpp"); // should throw and be caught
}
catch (...)
{
std::cout << "Caught" << std::endl; // never reached...
}
std::cout << "Done" << std::endl;
return 0;
}
Output:
Worked
Caught
-> then aborts!
With the debugger, I can see that abort is called when the error function below (in the /src/operations.cpp filesystem) calls BOOST_FILESYSTEM_THROW
:
bool error(err_t error_num, const path& p1, const path& p2, error_code* ec,
const char* message)
{
if (!error_num)
{
if (ec != 0) ec->clear();
}
else
{ // error
if (ec == 0)
BOOST_FILESYSTEM_THROW(filesystem_error(message,
p1, p2, error_code(error_num, system_category()))); // << Here!
else
ec->assign(error_num, system_category());
}
return error_num != 0;
}
I checked with the debugger and I reach the constructor filesystem_error
and can exit without any problem, the next step (pressing F11 in the debugger, throw
should now be called) calls abort()
.
The weird thing is that when it copy_directory
throws an exception, it also works and it calls the exact same error
function in filesystem/src/operations.cpp
.
Call stack after interruption:
> ntdll.dll!KiUserExceptionDispatcher() Inconnu
KernelBase.dll!RaiseException() Inconnu
vcruntime140d.dll!_CxxThrowException(void * pExceptionObject=0x000000000019f670, const _s__ThrowInfo * pThrowInfo=0x000000013fd01870) Ligne 136 C++
test_3rdparty_inprg_boost.exe!`anonymous namespace'::error(unsigned long error_num=80, const boost::filesystem::path & p1={...}, const boost::filesystem::path & p2={...}, boost::system::error_code * ec=0x0000000000000000, const char * message=0x000000013fcf6fb8) Ligne 321 C++
test_3rdparty_inprg_boost.exe!boost::filesystem::detail::copy_file(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}, boost::filesystem::detail::copy_option option=none, boost::system::error_code * ec=0x0000000000000000) Ligne 919 C++
test_3rdparty_inprg_boost.exe!boost::filesystem::copy_file(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}, boost::filesystem::copy_option option=none, boost::system::error_code & ec) Ligne 550 C++
test_3rdparty_inprg_boost.exe!boost::filesystem::detail::copy(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}, boost::system::error_code * ec=0x0000000000000000) Ligne 894 C++
test_3rdparty_inprg_boost.exe!boost::filesystem::copy(const boost::filesystem::path & from={...}, const boost::filesystem::path & to={...}) Ligne 524 C++
test_3rdparty_inprg_boost.exe!main(int argc=1, char * * argv=0x00000000003f3cc0) Ligne 35 C++
test_3rdparty_inprg_boost.exe!invoke_main() Ligne 75 C++
But I don't see the source code ntdll.dll!KiUserExceptionDispatcher()
and KernelBase.dll!RaiseException()
.
source to share
boost::filesystem::copy
- a huge mess. The function just calls boost::filesystem::detail::copy
, the third argument defaults to null:
BOOST_FILESYSTEM_DECL
void copy(const path& from, const path& to, system::error_code* ec)
{
file_status s(symlink_status(from, *ec));
if (ec != 0 && *ec) return;
if(is_symlink(s))
{
copy_symlink(from, to, *ec);
}
else if(is_directory(s))
{
copy_directory(from, to, *ec);
}
else if(is_regular_file(s))
{
copy_file(from, to, fs::copy_option::fail_if_exists, *ec);
}
else
{
if (ec == 0)
BOOST_FILESYSTEM_THROW(filesystem_error("boost::filesystem::copy",
from, to, error_code(BOOST_ERROR_NOT_SUPPORTED, system_category())));
ec->assign(BOOST_ERROR_NOT_SUPPORTED, system_category());
}
}
This function, in turn, is filled with invalid parsing of this potentially-null pointer, and also invokes error code variants for certain functions that are declared noexcept, passing in a false reference resulting from dereferencing a null pointer, which the compiler can evolve well as such (remember that we are already in UB territory here). These functions, in turn, take the reference address (which usually yields a null pointer again) and call their own verbose versions again, which use the error function, which will throw if the error code pointer is null.
Workaround:
- Don't use
copy()
, use a specific function for the type of thing you want if you know it (for examplecopy_file()
), or - Use a version
copy()
that acceptserror_code
and learns the code itself.
I see that you have already posted a bug report. This bug report is correct.
Edit jpo38:
Do not use
copy()
Please note that this is still the case in the first 1.65.1 release. You can prevent developers from using this feature by marking it as deprecated:
Create a file containing:
#ifdef __GNUC__
#define DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
#define DEPRECATED(func) __declspec(deprecated) func
#else
#pragma message("WARNING: You need to implement DEPRECATED for this compiler")
#define DEPRECATED(func) func
#endif
...
namespace boost
{
namespace filesystem
{
class path;
DEPRECATED( void copy(const path& from, const path& to) );
}
}
And then enable it for all cpp files with the option /FI
. Then you will get a warning if any code tries to use this messy function.
source to share
See the boost source code . According to this, BOOST_FILESYSTEM_THROW(EX)
simple throw EX
. So there must be a reason why challenge abort()
. This can be the case when an exception is thrown, when another exception is thrown - for example, in the exception constructor.
At the moment, my guess is a bug in boost::filesystem
. You can view the file with the error message.
source to share