4

I just encountered a case where an exception of type std::ios_base::failure was thrown inside a try block that catches this type of exception, but it does not get caught.

Minimal working example

#include <fstream>
#include <iostream>

int main()
{
    std::ofstream file;
    file.exceptions( std::ofstream::failbit );
    try {
        file.open( "no_write_access" );
    } catch ( const std::ios_base::failure& e ) {
        std::cout << "Look, Ma, I caught an error!" << std::endl;
    }
}

Compilation and execution

 % g++ --version
g++ (GCC) 6.1.0
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 % g++ main.cpp -std=c++11 -o test && ./test
terminate called after throwing an instance of 'std::ios_base::failure'
  what():  basic_ios::clear
[1]    9734 abort      ./test

Discussion

What's going on here? The "terminate" message explicitly specifies that the type of the exception was std::ios_base::failure, so I can't see any reason it would not get caught.

An interesting clue is that the catch block does work if I catch a const std::exception&, but it does not work if I catch a const std::runtime_error& (which, in C++11, is a base class of std::ios_base::failure, according to cppreference).

EDIT: I just tried g++ 9.2.0 and it works correctly. I tried a few different versions and the behavior changes sometime between 6.3.0 and 7.2.0. I'm still curious if anyone knows the reason, but it looks like it may have something to do with a bug in gcc itself.

sasquires
  • 356
  • 3
  • 15
  • 1) `std::ofstream` isn't the best at demonstrating the problem, since, if file doesn't exist - it simply creates it, and no exception gets thrown. 2) When I change `std::ofstream` to `std::ifstream`, everything [works as expected](https://wandbox.org/permlink/gtsyHihf8SQwMwIw). Please verify, that your [mcve], produces the problem, that you are claiming, that it does. – Algirdas Preidžius Sep 24 '19 at 22:14
  • What actually causes the error? The file exists in the current directory but is not owned by you, or you removed write permission on your own file, or the file doesn't exist and you don't have write permission in the current directory? Other? – aschepler Sep 24 '19 at 22:15
  • @AlgirdasPreidžius I called the file `no_write_access` for a reason. It has to be a file to which I do not have write access, so that an exception gets thrown. Your Wandbox example does not reproduce this part of the problem. – sasquires Sep 24 '19 at 22:16
  • @aschepler The problem is, as indicated by the file name, that I do not have write access to the file. In this particular case, I created the file and removed write access myself. But there could be other ways that I would not have write access to the file, in which case `failbit` would get set and an exception would be thrown. – sasquires Sep 24 '19 at 22:16
  • I wonder, if you catch as `const std::exception& e`, what `typeid(e).name()` is. – aschepler Sep 24 '19 at 22:22
  • 1
    @sasquires Technically, the reason why the `open` fails - doesn't matter. Since upon the failure, for any reason, it sets the `failbit`. Hence, if one tries to open the file with `ifstream`, with `open`, and the file doesn't exist - it throws the same exception. On a second note, I have found the following entry in [gcc7 changeset](https://gcc.gnu.org/gcc-7/changes.html): "_The type of exception thrown by iostreams, `std::ios_base::failure`, now uses the cxx11 ABI._" What does that mean, and how it relates to the ability of catching it - I don't know. But, something was changed. – Algirdas Preidžius Sep 24 '19 at 22:25
  • @AlgirdasPreidžius Aha, that may be the explanation. I have read about the cxx11 string change before. They had to change the code for `std::string` in a way that was not backwards compatible in `gcc` version 5 point something. What they effectively did was make two different `std::string` classes, but one of them has `_cxx11` in the actual class name, but set things up in such a way that everything is backwards compatible. Something like that. [Here is a link.](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html) Anyway, it may not recognize the class somehow due to this. – sasquires Sep 24 '19 at 22:31
  • @aschepler For the type name, I get `NSt8ios_base7failureE`. The version of `c++filt` I am using (2.25.0) does nothing with this. But it appears that this could be the correct class name. – sasquires Sep 24 '19 at 22:34
  • @sasquires That's not a full mangled name, since a type is never named by a linker symbol. But for example, a function `void f(std::ios_base::failure);` would have mangled name `_ZN1fENSt8ios_base7failureE`, and indeed c++filt transforms that to `f(std::ios_base::failure)`. – aschepler Sep 24 '19 at 23:23
  • 2
    @aschepler: `c++filt` has a `-t` option to handle this (turned off by default because of false hits). – Davis Herring Sep 25 '19 at 05:48
  • @DavisHerring Thanks, I didn't know that. Yes, with this, it gives the expected answer. – sasquires Sep 26 '19 at 16:44

0 Answers0