error¶
YanmaJS のエラー処理は、Zig の error union による制御フローと、JavaScript 例外値を保持するpending exceptionの2系統を組み合わせて扱います。
設計方針¶
- スクリプト評価系の API(
eval/runScript/evalModule/callFunction)は Zig のEvalError!Valueを返します。JavaScript 側で例外が投げられ、捕捉されずにトップレベルまで伝播した場合はerror.UncaughtExceptionになります - 例外の中身(JavaScript の
Valueとしての例外オブジェクト)は戻り値のエラーセットとは別に、Context.getPendingException()で取得します - ホスト関数(
HostFn/HostMethod)が Zig のエラーを返した場合は、自動的に JavaScript のErrorオブジェクトに変換されて投げられます
EvalError¶
pub const EvalError = error{
OutOfMemory,
StackOverflow,
ArityMismatch,
CallStackOverflow,
TryStackOverflow,
UncaughtException,
JumpTooLarge,
NotCallable,
RuntimeError,
};
| バリアント | 発生条件 |
|---|---|
OutOfMemory |
アロケータからのメモリ確保に失敗した(initWithMemoryLimit で設定したメモリ上限超過を含む) |
StackOverflow |
値スタック(VM の評価スタック)が上限を超えた |
ArityMismatch |
関数呼び出しの引数数がバイトコード上の期待値と一致しない(内部的な整合性エラー) |
CallStackOverflow |
関数呼び出しのネストが深すぎる(無限再帰など) |
TryStackOverflow |
try/catch/finally のネストが深すぎる |
UncaughtException |
JavaScript コードが投げた例外が捕捉されずトップレベルまで到達した。Context.getPendingException() で例外値を取得できる |
JumpTooLarge |
バイトコードの分岐オフセットが表現範囲を超えた(内部的な整合性エラー、通常は発生しない) |
NotCallable |
予約バリアント。現状はどの経路からも返されない(呼び出し不能な値の呼び出しは TypeError "value is not a function" を pending exception にセットした RuntimeError になる) |
RuntimeError |
上記以外の実行時エラー全般。Context.requestInterrupt() による割り込みもここに含まれる |
eval/compileScript/runScript/evalModule/callFunction はいずれも内部的に anyerror を mapEvalError で EvalError へ写像して返します。mapEvalError にないバリアントは RuntimeError に丸められます。
Pending Exception パターン¶
UncaughtException(または割り込みによる RuntimeError)が返った際、実際の JavaScript 例外オブジェクトはエラー値そのものではなく Context 内部の pending_throw スロットに格納されます。
呼び出すと保留中の例外値を返し、同時にスロットをクリアします(一度取り出すと null になります)。典型的な使い方は次の通りです。
const result = ctx.eval("throw new TypeError('bad')");
if (result) |_| {} else |err| {
if (err == error.UncaughtException) {
const ex = ctx.getPendingException().?;
const msg = try yanmajs.toString(ctx, ex);
defer ctx.allocator.free(msg);
std.debug.print("uncaught: {s}\n", .{msg}); // "TypeError: bad"
}
}
Context.throwValue/throwError/throwTypeError などでホスト側から例外を設定した場合も同じスロットに書き込まれるため、同じ API で取得できます。
throw 系メソッド¶
HostFn/HostMethod の実装内、あるいはホスト API 呼び出しの中で JavaScript 例外を発生させるためのメソッド群です。いずれも Context の pending exception スロットに例外値を設定するだけで、呼び出し自体は即座に返ります(Zig の制御フローを中断しません)。ホスト関数側で例外を投げた後は、通常そのまま return して呼び出し元(VM)に処理を戻します。
Context.throwValue(ctx: *Context, val: Value) void
Context.throwError(ctx: *Context, name: []const u8, message: []const u8) void
Context.throwTypeError(ctx: *Context, message: []const u8) void
Context.throwRangeError(ctx: *Context, message: []const u8) void
Context.throwReferenceError(ctx: *Context, message: []const u8) void
Context.throwSyntaxError(ctx: *Context, message: []const u8) void
| メソッド | 説明 |
|---|---|
throwValue |
任意の Value をそのまま例外値として設定する。エラーオブジェクト以外(文字列や数値など)を投げたい場合に使う |
throwError |
name(コンストラクタ名。"TypeError" や独自の "CustomError" など)と message から Error 系オブジェクトを生成して設定する |
throwTypeError / throwRangeError / throwReferenceError / throwSyntaxError |
それぞれ対応する組み込みエラー型で throwError を呼ぶ便利ラッパー |
例外オブジェクトの生成自体に失敗した場合(メモリ確保失敗など)、throwError 系は静かに何もしません(pending exception は設定されないまま)。
HostFn / HostMethod のエラー変換規則¶
HostFn/HostMethod はいずれも anyerror!Value を返します。VM がこれをディスパッチする際の規則は次の通りです。
- 戻り値が成功(
Valueを返す)であれば、そのままスクリプト側の呼び出し結果になる - 戻り値がエラーの場合、
@errorName(err)(Zig のエラー名文字列。例:"OutOfMemory")をmessageとしてError(name = "Error")オブジェクトを生成し、pending exception に設定した上でundefinedを返す - ホスト関数内で既に
Context.throwTypeErrorなどによって pending exception を設定していても、関数自体が Zig のエラーで抜けた場合は上記のErrorで上書きされる点に注意してください。特定の種類の例外(TypeErrorなど)を投げたい場合は、Zig のエラーを返すのではなくctx.throwXxx(...)を呼んだ上で通常の戻り値(例えばmakeUndefined())を返してください
const validateFn: HostFn = struct {
fn call(c: *Context, args: []const Value) !Value {
if (args.len == 0 or !yanmajs.isNumber(args[0])) {
c.throwTypeError("argument must be a number"); // 具体的なエラー型を明示
return yanmajs.makeUndefined(); // Zig のエラーを返さない
}
return args[0];
}
}.call;
上記のように「ctx.throwXxx を呼んでから正常値として返す」パターンが、狙った種類の JavaScript 例外を投げる正しい方法です。単に return error.SomeZigError; すると "Error: SomeZigError" という汎用エラーになります。