スタックトレース 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.stackTraceLimit

0 に設定すると、スタックトレースの収集が無効になります。任意の有限整数値を、収集するフレームの最大数として使用できます。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 オブジェクトは、次のメソッドを定義します。

デフォルトのスタックトレースは CallSite API を使用して作成されるため、そこで利用可能な情報はすべてこの API を介しても利用できます。

strict モード関数に課せられた制限を維持するために、strict モード関数を持つフレームとその下のすべてのフレーム(その呼び出し元など)は、レシーバーオブジェクトと関数オブジェクトにアクセスできません。これらのフレームの場合、`getFunction()` と `getThis()` は `undefined` を返します。

互換性 #

ここで説明する API は V8 固有のものであり、他の JavaScript 実装ではサポートされていません。ほとんどの実装は error.stack プロパティを提供しますが、スタックトレースの形式はここで説明するものとは異なる可能性があります。この API の推奨される使用方法は次のとおりです。

付録:スタックトレースの形式 #

V8 で使用されるデフォルトのスタックトレース形式は、各スタックフレームについて次の情報を提供できます。

これらのいずれかが利用できない場合があり、この情報の利用可能性に応じて、スタックフレームに異なる形式が使用されます。上記のすべての情報が利用可能な場合、フォーマットされたスタックフレームは次のようになります。

at Type.functionName [as methodName] (location)

または、コンストラクト呼び出しの場合は次のようになります。

at new functionName (location)

または、非同期呼び出しの場合は次のようになります。

at async functionName (location)

functionNamemethodName のいずれか一方のみが使用可能な場合、または両方が使用可能であるが同じである場合、形式は次のようになります。

at Type.name (location)

どちらも使用できない場合は、<anonymous> が名前として使用されます。

Type 値は、this のコンストラクタフィールドに格納されている関数の名前です。V8 では、すべてのコンストラクタ呼び出しでこのプロパティがコンストラクタ関数に設定されるため、オブジェクトの作成後にこのフィールドがアクティブに変更されていない限り、作成元の関数の名前が保持されます。利用できない場合は、オブジェクトの [[Class]] プロパティが使用されます。

特別なケースの 1 つは、Type が表示されないグローバルオブジェクトです。その場合、スタックフレームは次のようにフォーマットされます。

at functionName [as methodName] (location)

場所自体にはいくつかの形式があります。最も一般的なのは、現在の関数を定義したスクリプト内のファイル名、行番号、列番号です。

fileName:lineNumber:columnNumber

現在の関数が eval を使用して作成された場合、形式は次のようになります。

eval at position

…ここで、positioneval への呼び出しが発生した完全な位置です。これは、たとえば、eval へのネストされた呼び出しがある場合、位置がネストされる可能性があることを意味します。

eval at Foo.a (eval at Bar.z (myscript.js:10:3))

スタックフレームが V8 のライブラリ内にある場合、場所は次のようになります。

native

…そして、利用できない場合は、次のようになります。

unknown location