0

I'm working on a stream filter that can decode a custom file format. My goal is to use boost::iostreams::filtering_istream to read the file and process it with my boost::iostreams::multichar_input_filter subclass, so that I can load values using the << operator.

I also want the process to be terminated when my filter cannot decode the stream and throws an exception, which happens when I compile the code using gcc 5.4 on Windows Subsystem for Linux, but the exception gets swallowed before it reaches my code if I compile with VS2017.

I'm using Boost 1.68.0 on both Windows and WSL; I've built and installed it using b2 on both platforms, without any custom arguments or config. I've also tried 1.58.0 on WSL, which comes from the package manager.

The project uses CMake, and I haven't customised anything in CMakeSettings.json or in launch.vs.json.

I've created this simplified code that shows how I use the filter chain, the exception class, and how I try to catch a processing error:

#include <iostream>
#include <boost/iostreams/concepts.hpp>    // multichar_input_filter
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/throw_exception.hpp>

using namespace std;
namespace io = boost::iostreams;

class TestFilterException : public BOOST_IOSTREAMS_FAILURE {
  public:
    explicit TestFilterException(const char* message) : BOOST_IOSTREAMS_FAILURE(message) {
    }
};

class TestFilter : public io::multichar_input_filter {
  public:
    explicit TestFilter() {
    }

    template <typename Source> streamsize read(Source& src, char* output_buffer, streamsize requested_char_count) {
        BOOST_THROW_EXCEPTION(TestFilterException("Something went wrong"));
    }

    template <typename Source> void close(Source&) {
    }
};

int main(const int argc, const char *argv[]) {
    char buffer[64] = {'x'};
    io::array_source source = io::array_source(buffer);

    io::filtering_istream in;
    in.push(TestFilter());
    in.push(source);

    char c;
    try {
        in >> c;
        cout << c;
    } catch (boost::exception& e) {
        cout << "Expected exception";
        return 1;
    }
    return 0;
}

I would expect this code to write the 'Expected exception' message to the output and exit with return code 1 on all platforms. However, when I compile it with Visual Studio, it outputs some garbage and returns code 0.

awagner
  • 58
  • 4
  • Check the value of BOOST_EXCEPTION_DISABLE in `boost/throw_exception.hpp` - maybe it is disabled in Visual Studio. See https://www.boost.org/doc/libs/1_50_0/libs/exception/doc/configuration_macros.html – Rudolfs Bundulis Jan 18 '19 at 12:38
  • [this code does not print exception using gcc either](https://wandbox.org/permlink/HpAb18NpeMXgPnsb). Have you tried debugging it to figure out how exception is thrown? – user7860670 Jan 18 '19 at 12:48
  • @RudolfsBundulis: Strangely, I cannot open `boost/throw_exception.hpp` when I ctrl+click on the include line (the build runs fine), so I cannot check the current values of the macros there, but as I'm using msvc 14.1, it should not default to `BOOST_EXCEPTION_DISABLE`. – awagner Jan 18 '19 at 13:07
  • @VTT Strange, if I switch to gcc 5.4 with Boost 1.68, it behaves differently: https://wandbox.org/permlink/5SOtAvgOLzsFagVV – awagner Jan 18 '19 at 13:11
  • @RudolfsBundulis using `#ifdef BOOST_EXCEPTION_DISABLE` in my code shows that it's undefined, and the debugger cannot break on the line inside that block. – awagner Jan 18 '19 at 13:15
  • @VTT As soon as I step out from the line throwing the exception, I end up in msvcp140d.dll. It seems like `_CATCH_IO_END` doesn't rethrow the exception in `std::basic_istream >::_Ipfx(bool _Noskip)`. – awagner Jan 18 '19 at 13:29
  • Well actually you do have a missing return statement in that function, maybe that causes UB in Visual Studio, what if you add it? – Rudolfs Bundulis Jan 18 '19 at 13:29
  • @RudolfsBundulis I've added `return 123;` after the `BOOST_THROW_EXCEPTION` line, the debugger doesn't break there; so the execution of `read()` is terminated as it's expected. (In the real application I calculate a proper return value of course.) – awagner Jan 18 '19 at 13:41

1 Answers1

1

I think it is a bug in the older gcc. Newer gcc and VS correctly catch exception thrown and set bad bit flag instead of propagating exception through stream methods. Garbage is printed because c is left uninitialized after a failed read attempt. You can make stream throw a bad bit exception by setting exception flags in the stream:

try
{
    io::filtering_istream in;
    in.exceptions(::std::ios_base::badbit | ::std::ios_base::failbit | ::std::ios_base::eofbit);
    in.push(TestFilter());
    in.push(source);
    char c;
    in >> c;
    cout << c;
} catch (boost::exception& e) {
    cout << "not expected boost exception";
    return 1;
}
catch(::std::exception const & e)
{
    cout << "Expected std exception";
    return 2;
}

also see iostream Exceptions documentation

user7860670
  • 35,849
  • 4
  • 58
  • 84
  • Thank you, adding `in.exceptions(ios_base::badbit);` to my code resolved the issue. I can now `catch (const boost::exception& e)`, and see the original error message with `e.what()`. I've read the linked documentation page earlier but didn't understand how and when should be the exception mask used, or if it was required at all for my usecase. – awagner Jan 18 '19 at 14:10