Skip to content

Conversation

aheejin
Copy link
Member

@aheejin aheejin commented Feb 22, 2025

This documents the problems reported in #23720.

  1. Due to the lack of two-phase EH, we, both in Emscripten EH and Wasm EH, do not support std::set_terminate. This documents the limitation.
  2. Due to another LLVM implementation reason, __cxa_begin_catch is not called before when EH handling calls std::terminate. This is documented more in detail in EH termination does not call __cxa_begin_catch #23779.

Due to the lack of two-phase EH, we, both in Emscripten EH and Wasm EH,
do not support `std::set_terminate`. This documents the limitation.

Closes emscripten-core#23720.
@aheejin aheejin requested a review from sbc100 February 22, 2025 05:08
Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should we have calls to set_terminate trap (or warn in debug builds)?

@aheejin aheejin changed the title [docs] Document limitation on std::set_terminate [docs] Document limitation on std::set_terminate and __cxa_begin_catch Feb 27, 2025
@aheejin
Copy link
Member Author

aheejin commented Feb 27, 2025

Added documentation on another problem described in #23779.

@aheejin
Copy link
Member Author

aheejin commented Feb 27, 2025

@sbc100

Can/should we have calls to set_terminate trap (or warn in debug builds)?

Turns out set_terminate is not supported only when an exception is not caught, and we don't know whether an exception will be caught beforehand, so I don't think we can.

EDIT: This comment seems misleading; I clarified in #23728 (comment).

@sbc100
Copy link
Collaborator

sbc100 commented Feb 27, 2025

Turns out set_terminate is not supported only when an exception is not caught, and we don't know whether an exception will be caught beforehand, so I don't think we can.

Do you mean that it is support when the program exits normally? i.e. it acts like atexit()?

@aheejin
Copy link
Member Author

aheejin commented Feb 27, 2025

Turns out set_terminate is not supported only when an exception is not caught, and we don't know whether an exception will be caught beforehand, so I don't think we can.

Do you mean that it is support when the program exits normally? i.e. it acts like atexit()?

No it runs only when std::terminate runs due to an exception. I think my previous comment was either incorrect or unclear. So in this program (which I used in #23779):

#include <iostream>
#include <exception>

int main() noexcept {
  std::set_terminate([] {
    auto ptr = std::current_exception();
    if (ptr)
      std::cerr << "exception_ptr is NOT null" << std::endl;
    else
      std::cerr << "exception_ptr is null" << std::endl;
    std::abort();
  });
  throw 3;
}

Because of noexcept, the stack is not unwound, so this is not the case where we can't run the terminate handler when an uncaught exception unwinds all the stack. Rather, the function calls std::terminate within this function when an exception is thrown (because noexcept). In this case, that custom terminate handler, which prints either exception is null / NOT null runs. It only cannot access the current exception, because of the limitation I listed in the second bullet point and wrote in detail in #23779.

@aheejin
Copy link
Member Author

aheejin commented Mar 1, 2025

@sbc100 Do you have any other comments?

stack and crashes the program. This applies to both Emscripten-style and
WebAssembly exceptions. That functionality requires `two-phase exception
handling <https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html>`_, which
neither supports.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to say that set_terminate will not work.. but in the example below you show it working (although without a valid current_exception). These two things seems to be in contradiction with each other.

I guess maybe the noexcept thing on main is important here and that meas that there is maybe some kind of handler implicitly added to main? but that isn't obvious to me at least ..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to say that set_terminate will not work.. but in the example below you show it working (although without a valid current_exception). These two things seems to be in contradiction with each other.

So there's a condition written in the sentence:

... is NOT supported when a thrown exception does not have a matching handler and unwinds all the stack and crashes the program.

When we call std::terminate is not when all stack is unwound due to an exception.

I guess maybe the noexcept thing on main is important here and that means that there is maybe some kind of handler implicitly added to main? but that isn't obvious to me at least ..

If a function is marked noexcept, the code catches an exception within the function and calls std::terminate (or __clang_call_terminate, which calls std::terminate), and it does not throw the exception to the caller. So the stack unwinding does not happen.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think its a little confusing though since the main function you wrote looks like it doesn't catch anything (since it contains no catches).

Understanding the noexcept injects a invisible catch-all handler is required in order to understand what is going on there. That seems pretty non-obvious to me.

Maybe add comment right above main saying somthing like "The use of noexcept here means that the throw 3 will turn into a termination condition".

Copy link
Member Author

@aheejin aheejin Mar 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added "The use of noexcept here means that the throw 3 will turn into a termination condition" as you suggested in the second bullet point, and also added a little more clarifying words in the first bullet point.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also added an example program to the first bullet point to make the situation clearer.

See :ref:`using-exceptions-and-setjmp-longjmp-together`.


Limitations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since both of these limitation are regarding std::set_terminate should we call this section Limitations regarding std::terminate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

std::cerr << "exception_ptr is null" << std::endl;
std::abort();
});
throw 3;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would writing std::terminate() here have the same effect?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case there's no exception so __cxa_begin_catch is (correctly) not called and the program (correctly) prints exception_ptr is null, so it does not show the bug (or limitation) here.

@aheejin aheejin merged commit 58f77a5 into emscripten-core:main Mar 6, 2025
29 checks passed
@aheejin aheejin deleted the doc_set_terminate branch March 6, 2025 01:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants