スタックトレースAPI
V8内部でスローされるすべてのエラーは、作成時にスタックトレースをキャプチャします。このスタックトレースは、標準外のerror.stackプロパティを通じてJavaScriptからアクセスできます。V8には、スタックトレースの収集およびフォーマットを制御するためのさまざまなフックがあり、カスタムエラーでもスタックトレースを収集する機能を提供します。この文書では、V8のJavaScriptスタックトレースAPIについて説明します。
基本的なスタックトレース
デフォルトでは、V8によってスローされるほぼすべてのエラーには、文字列としてフォーマットされた最上位10個のスタックフレームを保持するstackプロパティがあります。以下は完全にフォーマットされたスタックトレースの例です:
ReferenceError: FAIL は定義されていません
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以降デフォルトで有効)は、新しいゼロコスト非同期スタックトレースを有効にし、非同期スタックフレーム、つまりコード内のawait位置を含むErrorインスタンスのstackプロパティを強化します。これらの非同期フレームは、スタック文字列内でasyncとマークされます:
ReferenceError: FAIL は定義されていません
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);
// その他の初期化処理はここに記述します。
}
MyErrorを第二引数として渡すことで、MyErrorへのコンストラクタ呼び出しがスタックトレースに表示されなくなります。
スタックトレースのカスタマイズ
Javaでは例外のスタックトレースがスタック状態の検査を可能にする構造化された値であるのに対し、V8ではstackプロパティはフォーマット済みスタックトレースを含むフラットな文字列として保持されます。これは、他のブラウザとの互換性を保つためのものです。ただし、これはハードコードされているわけではなく、あくまでデフォルトの動作であり、ユーザースクリプトによってオーバーライド可能です。
効率性のため、スタックトレースはキャプチャされたときにフォーマットされるのではなく、初回にstackプロパティがアクセスされたときにオンデマンドでフォーマットされます。スタックトレースは次の呼び出しでフォーマットされます:
Error.prepareStackTrace(error, structuredStackTrace)
そして、この呼び出しが返す値をstackプロパティの値として使用します。他の関数をError.prepareStackTraceに割り当てた場合、その関数がスタックトレースを整形するために使用されます。準備されるスタックトレースに渡されるエラーオブジェクトと、構造化されたスタックの表現が渡されます。ユーザー定義のスタックトレースフォーマッタは、スタックトレースを自由に整形することができ、文字列以外の値を返すことも可能です。prepareStackTraceメソッドの呼び出しが完了した後も、構造化されたスタックトレースオブジェクトへの参照を安全に保持することができ、有効な戻り値として使用できます。カスタムのprepareStackTrace関数は、エラーオブジェクトのstackプロパティがアクセスされる場合のみ呼び出される点に注意してください。
構造化されたスタックトレースは、各スタックフレームを表すCallSiteオブジェクトの配列です。CallSiteオブジェクトには以下のメソッドが定義されています。
getThis:this値を返しますgetTypeName: 文字列としてのthisの型を返します。これはthisのコンストラクタフィールドに格納されている関数名で、利用可能でない場合はオブジェクトの[[Class]]内部プロパティ。getFunction: 現在の関数を返しますgetFunctionName: 現在の関数の名前を返します。通常はnameプロパティです。nameプロパティが利用できない場合は、関数のコンテキストから名前を推定します。getMethodName: 現在の関数を格納しているthisまたはそのプロトタイプのプロパティの名前を返します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を通じて利用可能です。
厳密モード関数に課された制限を維持するため、厳密モード関数を持つフレーム及びその呼び出し元等のすべてのフレームは、受信者及び関数オブジェクトへのアクセスを許可されません。これらのフレームでは、getFunction()とgetThis()はundefinedを返します。
互換性
ここで説明するAPIはV8に特化しており、他のJavaScript実装ではサポートされていません。ほとんどの実装ではerror.stackプロパティを提供しますが、スタックトレースの形式はここで説明されている形式とは異なる可能性があります。このAPIの推奨使用法は以下の通りです:
- フォーマット済みスタックトレースのレイアウトに頼る場合は、コードがV8で実行されていることを知っている場合のみ。
- 実装に関係なくコードが実行されている場合でも
Error.stackTraceLimitとError.prepareStackTraceを設定するのは安全ですが、それが効果を持つのはコードがV8で実行されている場合のみです。
付録:スタックトレースフォーマット
V8が使用するデフォルトのスタックトレース形式は、各スタックフレームに以下の情報を提供する可能性があります:
- 呼び出しがコンストラクター呼び出しかどうか。
this値の型 (Type)。- 呼び出された関数の名前 (
functionName)。 thisまたはそのプロトタイプが保持する関数のプロパティの名前 (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]]プロパティが使用されます。
一つの特例はグローバルオブジェクトで、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のライブラリ内にある場合、位置は以下の通りです:
ネイティブ
そして、位置が利用できない場合、以下の通りです:
不明な場所