C++ 例外サポート

デフォルトでは、Emscripten では例外キャッチが無効になっています。たとえば、次のプログラムをコンパイルすると

#include <stdio.h>

int main() {
  try {
    puts("throw...");
    throw 1;
    puts("(never reached)");
  } catch(...) {
    puts("catch!");
  }
  return 0;
}

最初の throw はプログラムを中断し、出力に次のようなものが表示されます。

throw...
Aborted(Assertion failed: Exception thrown, but exception catching is not enabled. Compile with -sNO_DISABLE_EXCEPTION_CATCHING or -sEXCEPTION_CATCHING_ALLOWED=[..] to catch.)

オプトインする場合は、次の 2 つのオプションがあります。

Emscripten (JavaScript ベース) 例外サポート

まず、Emscripten の JavaScript ベースのサポートを介して例外を有効にできます。有効にするには、コンパイル時とリンク時の両方で -fexceptions を渡します。

このフラグを使用して上記の例をリビルドすると、出力は次のように変更されます。

throw...
catch!

このオプションは比較的オーバーヘッドが高いことに注意してください。ただし、WebAssembly をサポートするすべての JavaScript エンジンで機能します。例外が有効になっている許可された関数のリストを指定することで、オーバーヘッドを削減できます。 EXCEPTION_CATCHING_ALLOWED 設定を参照してください。

WebAssembly 例外処理ベースのサポート

または、ネイティブ WebAssembly 例外処理提案にオプトインすることもできます。有効にするには、コンパイル時とリンク時の両方で -fwasm-exceptions を渡します。

このフラグを使用して例をリビルドすると、上記の -fexceptions と同じ出力が得られます。

throw...
catch!

このオプションは、WebAssembly で例外をスローおよびキャッチするための組み込み命令をもたらす新しい機能を利用します。その結果、JavaScript ベースの実装と比較して、コードサイズとパフォーマンスのオーバーヘッドを削減できます。このオプションは現在、いくつかの主要な Web ブラウザでサポートされていますが、すべての WebAssembly エンジンでまだサポートされていない可能性があります

例外のデバッグ

スタックトレース

ネイティブ Wasm 例外の場合、ASSERTIONS が有効になっていると、キャッチされない例外はデバッグ用のスタックトレースを出力します。 ASSERTIONS は、-O0 でデフォルトで有効になり、最適化されたビルド (-O1 以上) で無効になります。最適化されたビルドで -sASSERTIONSemcc コマンドラインに渡すことで有効にできます。スタックトレースに Wasm 関数名を表示するには、–profiling-funcs (または -g または -gsource-map) も必要です。

JavaScript では、WebAssembly.Exception.prototype.stack プロパティを使用してスタックトレースを調べることもできます。例:

try {
  ... // some code that calls WebAssembly
} catch (e) {
  // Do something with e.stack
  console.log(e.stack);
}

Wasm コード内のスタックトレースは、JavaScript ベースの例外ではサポートされていません。

JavaScript からの C++ 例外の処理

C++ 例外が std::exception から継承しており、したがって what メソッドを持っている場合、JavaScript から C++ 例外の型とメッセージをキャッチして調べることができます。

getExceptionMessage は、2 つの文字列のリスト [type, message] を返します。 message は、例外が std::exception のサブクラスの場合に what メソッドを呼び出した結果です。それ以外の場合は、空の文字列になります。

var sp = stackSave();
try {
  ... // some code that calls WebAssembly
} catch (e) {
  stackRestore(sp);
  console.log(getExceptionMessage(e).toString());
} finally {
  ...
}

スローされた値が整数 3 の場合、これは int, と出力します。これは、メッセージ部分が空であるためです。スローされた値が std::exception のサブクラスである MyException のインスタンスであり、その what メッセージが My exception thrown の場合、このコードは MyException,My exception thrown と出力します。

この関数を使用するには、オプションに -sEXPORT_EXCEPTION_HANDLING_HELPERS を渡す必要があります。このオプションを使用するには、Emscripten EH または Wasm EH のいずれかを有効にする必要があります。

例外がスローされる前に Wasm 関数内でスタック割り当てによりスタックポインタが移動された場合は、stackSave()stackRestore() を使用してスタックポインタを復元し、スタックメモリがリークしないようにすることができます。

Wasm 例外をキャッチして再スローしない場合は、JS で decrementExceptionRefcount メソッドを使用して例外に関連付けられたストレージを解放する必要があります。これは、Wasm の例外キャッチコードにそれを解放する機会がないためです。ただし、現在、Wasm EH と Emscripten (JS ベース) EH の実装の問題により、Emscripten EH の場合は、追加で incrementExceptionRefcount を呼び出す必要があります。詳細とコード例については、https://github.com/emscripten-core/emscripten/issues/17115 を参照してください。

例外と setjmp-longjmp の同時使用

例外と setjmp-longjmp の同時使用を参照してください。