Skip to content

std::current_exception is NULL in termination handler #23720

Closed as not planned
Closed as not planned
@stevenwdv

Description

@stevenwdv

std::current_exception is nullptr when called from a termination handler.

Steps to reproduce

  • Create main.cpp with the following contents:
#include <exception>
#include <iostream>
#include <stdexcept>
#include <utility>

static void printCurrentException() {
	auto exPtr = std::current_exception();
	std::cerr << "exception_ptr = ";
	if (exPtr)
		try {
			std::rethrow_exception(std::move(exPtr));
		} catch (const std::runtime_error &ex) {
			std::cerr << "runtime_error: " << ex.what() << '\n';
		} catch (...) {
			std::cerr << "non-runtime_error\n";
		}
	else std::cerr << "NULL\n";
}

void noThrow() noexcept {
	try {
		throw std::runtime_error("error1");
	} catch (...) {
		printCurrentException();
	}
	std::set_terminate([] {
		printCurrentException();
		std::abort();
	});
	throw std::runtime_error("error2");
}

int main() {
	noThrow();
}
  • Now compile with exceptions (optionally with debug symbols):
em++ -fexceptions main.cpp -o hello.js
  • Now run the file (e.g. with Node.js, or use -o hello.html and open with a browser), and observe output:
$ node hello.js

exception_ptr = runtime_error: error1
exception_ptr = NULL
Aborted(native code called abort())
  • Note that std::current_exception() was properly set in the catch block, but the termination handler got a nullptr, while it should've been a pointer wrapping std::runtime_error("error2").

The same happens with -fwasm-exceptions and -fwasm-exceptions -sNO_WASM_LEGACY_EXCEPTIONS (run with --experimental-wasm-exnref as first parameter to node).

Details

The standard says the following

constexpr exception_ptr current_exception() noexcept;
  1. Returns: An exception_ptr object that refers to the currently handled exception or a copy of the currently handled exception, or a null exception_ptr object if no exception is being handled.
    [...]

and:

  1. If no matching handler is found, the function std​::​terminate is invoked; whether or not the stack is unwound before this invocation of std​::​terminate is implementation-defined ([except.terminate]).
  2. A handler is considered active when initialization is complete for the parameter (if any) of the catch clause.
    [Note 4: The stack will have been unwound at that point.
    — end note]
    Also, an implicit handler is considered active when the function std​::​terminate is entered due to a throw.
    A handler is no longer considered active when the catch clause exits.
  3. The exception with the most recently activated handler that is still active is called the currently handled exception.

So, as I understand it, the "implicit handler" should be active at this point. Besides, it works fine when compiled for x86 using Clang or GCC.

Version of emscripten/emsdk

emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 4.0.3 (a9651ff57165f5710bb09a5fe52590fd6ddb72df)
clang version 21.0.0git (https:/github.com/llvm/llvm-project 6dc41a639334b913e762f65410fcd14a722b137f)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/swdv/emsdk/upstream/bin

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions