UNPKG

traceback

Version:

Easy access to the call stack, written in pure JavaScript

137 lines (116 loc) 5.21 kB
// Traceback // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. var util = require('util') , assert = require('assert') var frame = require('./lib/frame') , v8 = require('./lib/v8') if(Error.prepareStackTrace) throw new Error('Traceback does not support Error.prepareStackTrace being defined already') // The module API module.exports = traceback module.exports.raw = raw // The public API call takes no arguments. function raw() { return raw_traceback(raw) } function traceback() { var stack = raw_traceback(traceback) //console.error('Returning easy stack') return stack.map(function(callsite) { return frame.make(callsite) }) } // The design: // // Unfortunately, only way to access raw stack frames is through one global // function callback, Error.prepareStackTrace. See // node/deps/v8/src/messages.js. So, despite this module having nothing to do // with error handling at all, it must do a lot with the Error system. // // It won't work to simply set Error.prepareStackTrace and leave it there // forever. Other code will want to print normal exceptions, formatted // normally. Many people expect an error .stack attribute to be a string, and // to be formatted in exactly the right way. This module therefore tries to be // as conservative as possible. // // First, when a stack is requested, the idea is to hook in to // prepareStackTrace, use it, then reset things back to normal. As long as the // original Error.prepareStackTrace is in place, then people will get their // expected .stack formatting. The most typical case is that it is // undefined/deleted, and the built-in V8 code mentioned above will run. // // But, this module itself might throw an exception, leaving prepareStackTrace // pointing to the wrong function. That is why `getting_the_stack` is in the // module. Before every traceback call, it is set from null to a real object. // When our prepareStackTrace runs, it checks that the "error" object it got is // identical to getting_the_stack. If they are identical, then, yep, things are // working fine. After executing, getting_the_stack is set back to null. // // If cleanup failed somehow, and then a normal Error is created later, this // code will be called again, inappropriately. It will detect this situation // because the incoming error object will not be getting_the_stack. To recover, // it just restores the correct prepareStackTrace function. But it is still on // the hook to return a formatted stack value. // // If the original prepareStackTrace is an actual function, it can be called to // produce the expected .stack value. Unfortunately, the most common situation // is that it should be *undefined*, and the built-in V8 formatter is desired. // But there is no way to execute that code on the given error object. I // checked. It sucks. So the final workaround is to ship a copy of that // formatter and use it as a last resort. // // In any case, the correct Error.prepareStackTrace value will be restored, so // subsequent exceptions will get the right behavior. var getting_the_stack = null function raw_traceback(begin_func) { assert.ok(begin_func, 'Must supply begin_func') var stack = null , error = null if(Error.original_prepareStackTrace) // Should be quite rare console.error('Traceback error detected: Error.original_prepareStackTrace exists') else Error.original_prepareStackTrace = Error.prepareStackTrace getting_the_stack = {} try { //console.error('Beginning capture') Error.captureStackTrace(getting_the_stack, begin_func) Error.prepareStackTrace = return_raw_stack stack = getting_the_stack.stack // This actually calls the prepareStackTrace function } catch (capture_er) { //console.error('= Capture error =') error = capture_er } finally { getting_the_stack = null Error.prepareStackTrace = Error.original_prepareStackTrace // TODO could this ever fail? delete Error.original_prepareStackTrace } if(error) throw error else if(stack) return stack else throw new Error('Unknown result getting the stack') } function return_raw_stack(er, stack) { if(getting_the_stack && er === getting_the_stack) return stack; // At this point something has gone wrong. Try to recover the existing prep function. if(Error.original_prepareStackTrace) { //console.error('Restoring original prepareStackTrace') Error.prepareStackTrace = Error.original_prepareStackTrace delete Error.original_prepareStackTrace return Error.prepareStackTrace(er, stack) } //console.error('Returning to normal (deleted) Error.prepareStackTrace') delete Error.prepareStackTrace return v8.FormatStackTrace(er, stack) }