berry icon indicating copy to clipboard operation
berry copied to clipboard

Don't use setjmp/longjmp for exceptions

Open skiars opened this issue 2 years ago • 7 comments

In C++, setjmp/longjmp are not compatible with C++ exceptions. Therefore, I want to change the berry exception to error code mechanism. I think it should be possible to use the int return value of the native-function to be compatible with the existing code as much as possible.

Simply put, we may need to provide some macros:

#define be_call(vm, argc) for (int errcode = be_call_(vm, argc); errcode;) return errcode;
#define be_raise(vm, except, msg) do { be_raise_(vm, except, msg); return BE_EXCEPT_RAISED; } while (0)

In short, when an exception occurs, the function will always return along the call stack. Due to the use of return semantics, this will be compatible with C++ exceptions.

skiars avatar Dec 13 '21 02:12 skiars

Will this work without C++ exception support? Tasmota uses C++ but disables C++ exceptions completely (for code size reasons)

s-hadinger avatar Dec 13 '21 08:12 s-hadinger

Will this work without C++ exception support? Tasmota uses C++ but disables C++ exceptions completely (for code size reasons)

Yes, we generally disable C++ exceptions in the MCU. Unfortunately, it is impossible to disable C++ exceptions anywhere ☹️.

setjmp/longjmp will destroy the C++ stack unwinding mechanism. Let me give an example:

struct Test {
    char *data;
    Test() { data = new char[10](); }
    ~Test() { delete[] data; }
};

int berry_func(bvm *vm) {
    Test obj;
    // do somthing ...
    // if be_raise is called, longjmp will be called to pop the stack
    // directly instead of Temp::~Temp(). So that obj cannot be destructed.
}

skiars avatar Dec 13 '21 08:12 skiars

I understand but I'm still nervous on this change. When running C code called by Berry, it is very handy to call be_raise() deep in the C code when something goes wrong and longjmp does its magic. With the new scheme, I need to change the entire call chain to get back the error code from call to call.

Would there be any way to still enable longjmp when Berry calls C code for which I'm certain that there are no C++ exceptions nor cleaning code needed?

s-hadinger avatar Dec 15 '21 08:12 s-hadinger

This is the main problem I'm worrying about now.

skiars avatar Dec 20 '21 11:12 skiars

Would there be any way to still enable longjmp when Berry calls C code for which I'm certain that there are no C++ exceptions nor cleaning code needed?

There's nothing that prevents you calling setjmp/longjmp from a C only chain. The only requirement is making sure there's no C++ with exceptions involved in the chain, or if they are, that the C++ code does not leak exceptions up. So the easiest solution is to add 2 macros:

#define EnterCPPCode   try {
#define LeaveCPPCode  } catch (const std::exception & e) { be_raise(vm, CPP_CODE_EXCEPT, e.what()); } catch (...) { be_raise(vm, CPP_CODE_EXCEPT, "unknown C++ exception"); }

As long as you don't mix the C++ and C code in the same call chain, it'll work.

X-Ryl669 avatar Jan 10 '22 08:01 X-Ryl669

Thanks, I love this approach. It should be obviously added in the documentation.

As mentioned, C++ exceptions are often disabled in embedded systems, so we're safe here. The danger would be in non-embedded systems.

s-hadinger avatar Jan 10 '22 09:01 s-hadinger

Well, avoiding C++ exceptions in Berry should be a very viable approach. And I found that people don't use C++ exceptions very much.

skiars avatar Jan 10 '22 12:01 skiars