スタックトレース API
V8 でスローされるすべての内部エラーは、作成時にスタックトレースをキャプチャします。このスタックトレースは、非標準の error.stack プロパティを通じて JavaScript からアクセスできます。V8 には、スタックトレースの収集方法とフォーマット方法を制御したり、カスタムエラーもスタックトレースを収集できるようにするためのさまざまなフックも用意されています。このドキュメントでは、V8 の JavaScript スタックトレース API について概説します。
基本的なスタックトレース #
デフォルトでは、V8 によってスローされるほとんどすべてのエラーには、文字列としてフォーマットされた上位 10 個のスタックフレームを保持する stack プロパティがあります。完全にフォーマットされたスタックトレースの例を以下に示します。
ReferenceError: FAIL is not defined
at Constraint.execute (deltablue.js:525:2)
at Constraint.recalculate (deltablue.js:424:21)
at Planner.addPropagate (deltablue.js:701:6)
at Constraint.satisfy (deltablue.js:184:15)
at Planner.incrementalAdd (deltablue.js:591:21)
at Constraint.addConstraint (deltablue.js:162:10)
at Constraint.BinaryConstraint (deltablue.js:346:7)
at Constraint.EqualityConstraint (deltablue.js:515:38)
at chainTest (deltablue.js:807:6)
at deltaBlue (deltablue.js:879:2)
スタックトレースはエラーの作成時に収集され、エラーがスローされた場所や回数に関係なく同じです。通常は有用でありながら、顕著な負のパフォーマンスへの影響がない程度に十分なため、10 個のフレームを収集します。収集するスタックフレームの数を制御するには、変数を設定します。
Error.stackTraceLimit0 に設定すると、スタックトレースの収集が無効になります。任意の有限整数値を、収集するフレームの最大数として使用できます。Infinity に設定すると、すべてのフレームが収集されます。この変数は現在のコンテキストにのみ影響します。異なる値が必要なコンテキストごとに明示的に設定する必要があります。(V8 の用語で「コンテキスト」と呼ばれるものは、Google Chrome のページまたは <iframe> に対応することに注意してください)。すべてのコンテキストに影響する異なるデフォルト値を設定するには、次の V8 コマンドラインフラグを使用します。
--stack-trace-limit <value>Google Chrome を実行するときにこのフラグを V8 に渡すには、以下を使用します。
--js-flags='--stack-trace-limit <value>'非同期スタックトレース #
--async-stack-traces フラグ(V8 v7.3 以降デフォルトで有効)は、新しい ゼロコストの非同期スタックトレース を有効にします。これは、Error インスタンスの stack プロパティを非同期スタックフレーム、つまりコード内の await の場所で強化します。これらの非同期フレームは、stack 文字列内で async とマークされます。
ReferenceError: FAIL is not defined
at bar (<anonymous>)
at async foo (<anonymous>)
この記事の執筆時点では、この機能は await の場所、Promise.all()、および Promise.any() に限定されています。これらの場合、エンジンは追加のオーバーヘッドなしに必要な情報を再構築できるためです(そのため、ゼロコストです)。
カスタム例外のスタックトレース収集 #
組み込みエラーに使用されるスタックトレースメカニズムは、ユーザースクリプトでも使用できる一般的なスタックトレース収集 API を使用して実装されています。関数
Error.captureStackTrace(error, constructorOpt)は、指定された error オブジェクトに、captureStackTrace が呼び出された時点のスタックトレースを生成する stack プロパティを追加します。Error.captureStackTrace を介して収集されたスタックトレースは、すぐに収集、フォーマットされ、指定された error オブジェクトに添付されます。
オプションの constructorOpt パラメータを使用すると、関数値を渡すことができます。スタックトレースを収集するとき、この関数への最上位の呼び出しより上のすべてのフレーム(その呼び出しを含む)は、スタックトレースから除外されます。これは、ユーザーにとって役に立たない実装の詳細を隠すのに役立ちます。スタックトレースをキャプチャするカスタムエラーを定義する通常の方法は次のとおりです。
function MyError() {
Error.captureStackTrace(this, MyError);
// Any other initialization goes here.
}2 番目の引数として MyError を渡すと、MyError へのコンストラクタ呼び出しがスタックトレースに表示されなくなります。
スタックトレースのカスタマイズ #
例外のスタックトレースがスタック状態の検査を可能にする構造化値である Java とは異なり、V8 の stack プロパティは、フォーマットされたスタックトレースを含むフラットな文字列のみを保持します。これは、他のブラウザとの互換性以外の理由はありません。ただし、これはハードコードされておらず、デフォルトの動作にのみ該当し、ユーザースクリプトによってオーバーライドできます。
効率のために、スタックトレースはキャプチャされたときではなく、オンデマンドで、stack プロパティに初めてアクセスしたときにフォーマットされます。スタックトレースは、以下を呼び出すことによってフォーマットされます。
Error.prepareStackTrace(error, structuredStackTrace)そして、この呼び出しが返すものを stack プロパティの値として使用します。異なる関数値を Error.prepareStackTrace に割り当てると、その関数がスタックトレースのフォーマットに使用されます。スタックトレースを準備しているエラーオブジェクトと、スタックの構造化表現が渡されます。ユーザースタックトレースフォーマッタは、スタックトレースを自由にフォーマットし、文字列以外の値を返すこともできます。prepareStackTrace の呼び出しが完了した後、構造化スタックトレースオブジェクトへの参照を保持しても安全であるため、有効な戻り値でもあります。カスタム prepareStackTrace 関数は、`Error` オブジェクトの stack プロパティにアクセスしたときにのみ呼び出されることに注意してください。
構造化スタックトレースは CallSite オブジェクトの配列であり、それぞれがスタックフレームを表します。CallSite オブジェクトは、次のメソッドを定義します。
getThis:thisの値を返します。getTypeName:thisの型を文字列として返します。これは、thisのコンストラクタフィールドに格納されている関数の名前(使用可能な場合)、そうでない場合はオブジェクトの[[Class]]内部プロパティです。getFunction:現在の関数を返します。getFunctionName:現在の関数の名前(通常はnameプロパティ)を返します。nameプロパティが使用できない場合は、関数のコンテキストから名前を推測しようとします。getMethodName:現在の関数を保持するthisまたはそのプロトタイプの 1 つのプロパティの名前を返します。getFileName:この関数がスクリプトで定義されている場合、スクリプトの名前を返します。getLineNumber:この関数がスクリプトで定義されている場合、現在の行番号を返します。getColumnNumber:この関数がスクリプトで定義されている場合、現在の列番号を返します。getEvalOrigin:この関数がevalの呼び出しを使用して作成された場合、evalが呼び出された場所を表す文字列を返します。isToplevel:これはトップレベルの呼び出しですか?つまり、これはグローバルオブジェクトですか?isEval:この呼び出しは、evalの呼び出しによって定義されたコードで行われますか?isNative:この呼び出しはネイティブ V8 コードにありますか?isConstructor:これはコンストラクタ呼び出しですか?isAsync: これは非同期呼び出しですか(つまり、`await`、`Promise.all()`、または `Promise.any()` ですか)?isPromiseAll: これは `Promise.all()` への非同期呼び出しですか?getPromiseIndex: 非同期スタックトレースの `Promise.all()` または `Promise.any()` で追跡された Promise 要素のインデックスを返します。`CallSite` が非同期 `Promise.all()` または `Promise.any()` 呼び出しでない場合は `null` を返します。
デフォルトのスタックトレースは CallSite API を使用して作成されるため、そこで利用可能な情報はすべてこの API を介しても利用できます。
strict モード関数に課せられた制限を維持するために、strict モード関数を持つフレームとその下のすべてのフレーム(その呼び出し元など)は、レシーバーオブジェクトと関数オブジェクトにアクセスできません。これらのフレームの場合、`getFunction()` と `getThis()` は `undefined` を返します。
互換性 #
ここで説明する API は V8 固有のものであり、他の JavaScript 実装ではサポートされていません。ほとんどの実装は error.stack プロパティを提供しますが、スタックトレースの形式はここで説明するものとは異なる可能性があります。この API の推奨される使用方法は次のとおりです。
- コードが v8 で実行されていることがわかっている場合にのみ、フォーマットされたスタックトレースのレイアウトに依存してください。
- コードを実行している実装に関係なく、
Error.stackTraceLimitとError.prepareStackTraceを設定しても安全ですが、コードが V8 で実行されている場合にのみ効果があることに注意してください。
付録:スタックトレースの形式 #
V8 で使用されるデフォルトのスタックトレース形式は、各スタックフレームについて次の情報を提供できます。
- 呼び出しがコンストラクト呼び出しであるかどうか。
this値の型(Type)。- 呼び出された関数の名前(
functionName)。 - 関数を保持する this またはそのプロトタイプの 1 つのプロパティの名前(
methodName)。 - ソース内の現在の場所(
location)
これらのいずれかが利用できない場合があり、この情報の利用可能性に応じて、スタックフレームに異なる形式が使用されます。上記のすべての情報が利用可能な場合、フォーマットされたスタックフレームは次のようになります。
at Type.functionName [as methodName] (location)
または、コンストラクト呼び出しの場合は次のようになります。
at new functionName (location)
または、非同期呼び出しの場合は次のようになります。
at async functionName (location)
functionName と methodName のいずれか一方のみが使用可能な場合、または両方が使用可能であるが同じである場合、形式は次のようになります。
at Type.name (location)
どちらも使用できない場合は、<anonymous> が名前として使用されます。
Type 値は、this のコンストラクタフィールドに格納されている関数の名前です。V8 では、すべてのコンストラクタ呼び出しでこのプロパティがコンストラクタ関数に設定されるため、オブジェクトの作成後にこのフィールドがアクティブに変更されていない限り、作成元の関数の名前が保持されます。利用できない場合は、オブジェクトの [[Class]] プロパティが使用されます。
特別なケースの 1 つは、Type が表示されないグローバルオブジェクトです。その場合、スタックフレームは次のようにフォーマットされます。
at functionName [as methodName] (location)
場所自体にはいくつかの形式があります。最も一般的なのは、現在の関数を定義したスクリプト内のファイル名、行番号、列番号です。
fileName:lineNumber:columnNumber
現在の関数が eval を使用して作成された場合、形式は次のようになります。
eval at position
…ここで、position は eval への呼び出しが発生した完全な位置です。これは、たとえば、eval へのネストされた呼び出しがある場合、位置がネストされる可能性があることを意味します。
eval at Foo.a (eval at Bar.z (myscript.js:10:3))
スタックフレームが V8 のライブラリ内にある場合、場所は次のようになります。
native
…そして、利用できない場合は、次のようになります。
unknown location