Skip to content
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

emscripten lacks standards-conforming C++ exceptions #2531

Closed
b-spencer opened this issue Jul 17, 2014 · 8 comments
Closed

emscripten lacks standards-conforming C++ exceptions #2531

b-spencer opened this issue Jul 17, 2014 · 8 comments
Labels

Comments

@b-spencer
Copy link

It seems the specific issue of catching std::exception has been fixed (in the incoming branch), but in general, C++ exceptions still aren't working.

Below is a small program that is based on one set of real, essential C++ exceptions from a real program. It exercises exception polymorphism, including in cases with virtual and multiple inheritence. Here are a few examples of the expected (g++) and actual (emscripten) output:

$ g++ -Wall -o test test.cc && ./test exception
Throwing ::exception
Caught ::exception: what=my_what
$ em++ -Wall -s DISABLE_EXCEPTION_CATCHING=0 -o test.js test.cc -O2  && node test.js exception
Throwing ::exception
Caught C string:

$ g++ -Wall -o test test.cc && ./test some_error
Throwing ::some_error
Caught ::some_error: arg=my_arg
$ em++ -Wall -s DISABLE_EXCEPTION_CATCHING=0 -o test.js test.cc -O2  && node test.js some_error
Throwing ::some_error
Caught ::some_error: arg=my_arg

$ g++ -Wall -o test test.cc && ./test specific_error
Throwing ::specific_error
Caught ::specific_error: what=my_what arg=my_arg
$ em++ -Wall -s DISABLE_EXCEPTION_CATCHING=0 -o test.js test.cc -O2  && node test.js specific_error
Throwing ::specific_error
Caught C string:

$ g++ -Wall -o test test.cc -DNO_CATCH_EXCEPTION && ./test exception
Throwing ::exception
Caught std::runtime_error: my_what
$ em++ -Wall -s DISABLE_EXCEPTION_CATCHING=0 -o test.js test.cc -O2 -DNO_CATCH_EXCEPTION && node test.js exception
Throwing ::exception
Caught C string:

I can't really exercise most of the polymorphism cases because everything is falling into the "first catch" still.

By no means is this program an exhaustive test of all C++ exceptions, but it does demonstrate some of what is missing.

toolchain and emscripten (on the incoming branches):

$ em++ -v
emcc (Emscripten GCC-like replacement + linker emulating GNU ld ) 1.21.3
clang version 3.3 (https://github.com/kripken/emscripten-fastcomp-clang a75664929022e41fc31aec5fa8252bda7e199807) (https://github.com/kripken/emscripten-fastcomp 339be449e4285685a2b414cba93d2b82327df4c6)
Target: x86_64-unknown-linux-gnu
Thread model: posix
INFO     root: (Emscripten: Running sanity checks)

test.cc

#include <iostream>
#include <stdexcept>

using std::cout;
using std::endl;

struct exception : public virtual std::runtime_error 
{
  // To allow this to be thrown directly in the tests below.
  explicit exception(const std::string &what)
    : std::runtime_error(what)
  {}

protected:
    exception()
      // This won't be called because of virtual inheritance.
      : std::runtime_error("::exception")
  {}
};

// Helper interface and implementation for some exceptions.
struct some_error
{
  explicit some_error(const std::string &arg)
    : m_arg(arg)
  {}

  ~some_error() throw()
  {}

  const std::string m_arg;
};

// More derived exception.
struct specific_error : public virtual some_error,
                        public virtual exception
{
  specific_error(const std::string &what,
                 const std::string &arg)
    : some_error(arg),
      std::runtime_error(what)
  {}
};


int main(const int argc, const char * const * const argv)
{
  // What to do?
  const std::string arg(argc > 1 ? argv[1] : "exception");

  try {
    if(arg == "exception") {
      cout << "Throwing ::exception" << endl;
      throw ::exception("my_what");
    }
    if(arg == "some_error") {
      cout << "Throwing ::some_error" << endl;
      throw ::some_error("my_arg");
    }
    if(arg == "specific_error") {
      cout << "Throwing ::specific_error" << endl;
      throw ::specific_error("my_what", "my_arg");
    }

  } catch(const char * ex) {
    cout << "Caught C string: " << ex << endl;

#if !defined(NO_CATCH_SPECIFIC_ERROR)    
  } catch(const ::specific_error &ex) {
    cout
      << "Caught ::specific_error: what=" << ex.what()
      << " arg=" << ex.m_arg << endl;
#endif

#if !defined(NO_CATCH_SOME_ERROR)    
  } catch(const ::some_error &ex) {
    cout << "Caught ::some_error: arg=" << ex.m_arg << endl;
#endif

#if !defined(NO_CATCH_EXCEPTION)
  } catch(const ::exception &ex) {
    cout << "Caught ::exception: what=" << ex.what() << endl;
#endif

  } catch(const std::runtime_error &ex) {
    cout << "Caught std::runtime_error: " << ex.what() << endl;

  } catch(const std::exception &ex) {
    cout << "Caught std::exception: " << ex.what() << endl;

  } catch(const std::string &ex) {
    cout << "Caught std::string: " << ex << endl;

  } catch(...) {
    cout << "Caught something else" << endl;
  }
}
@nickdesaulniers
Copy link

I suspect our personality routine might be having issues with polymorphic exceptions.

Other possibly helpful documents:
http://sourcery.mentor.com/archives/cxx-abi-dev/msg01924.html
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2179.html

@nickdesaulniers
Copy link

Ok, I plan on compiling libc++abi from source with debug symbols and linking against that. Then stepping through the above example while stepping through emscripten's build and seeing where they diverge. Suspect code. Also, cc @waywardmonkeys in case anything stands out.

@nickdesaulniers
Copy link

Not sure we handle virtual inheritance here.

@kmaccara2
Copy link

An exception that has virtual inheritance gets corrupted. This is demonstrated in the following test program; mangled_exception.cc:

#include <cerrno>
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>

//============================================================================
// :: Helpers

namespace
{
  // A straight up derivation from std::runtime_error.
  struct exception1: public std::runtime_error
  {
    // Construct with an errno value.  The what() string will contain the
    // std::strerror() string associated with the given error value.
    explicit exception1(const int error)
      : std::runtime_error(std::string("exception1: ")
                           + std::strerror(error)),
        m_error(error)
    {}

    // Access to our stored error value.
    int error() const
    { return m_error; }

  private:
    // The errno value.
    const int m_error;
  };

  // A virtual derivation from std::runtime_error.
  struct exception2: public virtual std::runtime_error
  {
    // Construct with an errno value.  The what() string will contain the
    // std::strerror() string associated with the given error value.
    explicit exception2(const int error)
      : std::runtime_error(std::string("exception2: ")
                           + std::strerror(error)),
        m_error(error)
    {}

    // Access to our stored error value.
    int error() const
    { return m_error; }

  private:
    // The errno value.
    const int m_error;
  };

  void printException2(const exception2& ex)
  {
    std::cout
      << "exception2 with errno=" << ex.error() << " and what="
      << ex.what() << std::endl;
  }
}

//============================================================================
// :: Entry Point

int main()
{
  try {
    throw exception1(EIO);
  }
  catch (const exception1& ex) {
    std::cout
      << "Caught exception: " << ex.what() << " with error="
      << ex.error() << std::endl;
  }

  try {
    const exception2 test(EIO);
    printException2(test);
    throw exception2(EIO);
  }
  catch (const exception2& ex) {
    std::cout << "Caught exception: " << ex.error() << std::endl;
    printException2(ex);
  }
}

To build use the following command:

em++ -s DISABLE_EXCEPTION_CATCHING=0 -Wall -Werror -O2 -o mangled_exception.js mangled_exception.cc

When the generated JavaScript is run through node, the following output is observed:

$ node mangled_exception.js
Caught exception: exception1: I/O error with error=5
exception2 with errno=5 and what=exception2: I/O error
Caught exception: 2
4
4

/home/kmaccara/junk/mangled_exception.js:54
t"&&e.stack)Module.printErr("exception thrown: "+[e,e.stack]);throw e}}finally
                                                                    ^
abort() at Error
    at jsStackTrace (/home/kmaccara/junk/mangled_exception.js:1:21842)
    at stackTrace (/home/kmaccara/junk/mangled_exception.js:1:22025)
    at abort (/home/kmaccara/junk/mangled_exception.js:54:32407)
    at Array.Xo (/home/kmaccara/junk/mangled_exception.js:33:107312)
    at Array.Jc [as 120] (/home/kmaccara/junk/mangled_exception.js:25:5749)
    at Object.wo [as dynCall_vi] (/home/kmaccara/junk/mangled_exception.js:33:104964)
    at invoke_vi (/home/kmaccara/junk/mangled_exception.js:1:154684)
    at Object.Fc [as _main] (/home/kmaccara/junk/mangled_exception.js:25:2777)
    at Object.callMain (/home/kmaccara/junk/mangled_exception.js:54:30841)
    at doRun (/home/kmaccara/junk/mangled_exception.js:54:31673)
If this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.

Currently using a recent version of the incoming branches of emscripten:

 em++ --version
emcc (Emscripten GCC-like replacement) 1.21.6 (commit 1f07e38ce18c37d4bfa80ea34e49ae16a4da66c9)
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ em++ -v 
emcc (Emscripten GCC-like replacement + linker emulating GNU ld ) 1.21.6
clang version 3.3 (https://github.com/kripken/emscripten-fastcomp-clang 8575c9da410b5187ba7e082a7ee7fed6cb6aa0f4) (https://github.com/kripken/emscripten-fastcomp 5fc2896cd91ed988685c0c93b1f0bd183e632d94)
Target: x86_64-unknown-linux-gnu
Thread model: posix
INFO:root:(Emscripten: Running sanity checks)

@juj juj added the fastcomp label Jul 25, 2014
@juj
Copy link
Collaborator

juj commented Jul 25, 2014

This looks like a duplicate of #1546.

@kripken
Copy link
Member

kripken commented Jul 25, 2014

Yes, different testcases in them though, so let's keep both open so we don't forget one.

@kripken
Copy link
Member

kripken commented Jul 28, 2014

I believe I have managed to hook our exceptions catching decision code to use libcxxabi's can_catch, which should be what we want. However, it is failing on some tests the old code worked on, not sure yet what is going on.

kripken added a commit that referenced this issue Jul 28, 2014
…ust the pointer as an out param; fixes #2531; 1.21.9
@kripken
Copy link
Member

kripken commented Jul 28, 2014

Those last commits make use use libcxxabi's can_catch, and let it adjust the thrown pointer so that virtual exceptions work. This fixes the test case here.

@kripken kripken closed this as completed Jul 28, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants