mirror of
https://github.com/rubyjs/therubyracer
synced 2023-03-27 23:21:42 -04:00
169 lines
5.7 KiB
Ruby
169 lines
5.7 KiB
Ruby
module V8
|
|
# capture 99 stack frames on exception with normal details.
|
|
# You can adjust these values for performance or turn of stack capture entirely
|
|
V8::C::V8::SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kOverview)
|
|
class Error < StandardError
|
|
include Enumerable
|
|
|
|
# @!attribute [r] value
|
|
# @return [Object] the JavaScript value passed to the `throw` statement
|
|
attr_reader :value
|
|
|
|
# @!attribute [r] cause
|
|
# @return [Exception] the underlying error (if any) that triggered this error to be raised
|
|
attr_reader :cause
|
|
|
|
# @!attribute [r] javascript_backtrace
|
|
# @return [V8::StackTrace] the complete JavaScript stack at the point this error was thrown
|
|
attr_reader :javascript_backtrace
|
|
|
|
# keep an alias to the StandardError#backtrace method so that we can capture
|
|
# just ruby backtrace frames
|
|
alias_method :standard_error_backtrace, :backtrace
|
|
|
|
def initialize(message, value, javascript_backtrace, cause = nil)
|
|
super(message)
|
|
@value = value
|
|
@cause = cause
|
|
@javascript_backtrace = javascript_backtrace
|
|
end
|
|
|
|
def causes
|
|
[].tap do |causes|
|
|
current = self
|
|
until current.nil? do
|
|
causes.push current
|
|
current = current.respond_to?(:cause) ? current.cause : nil
|
|
end
|
|
end
|
|
end
|
|
|
|
def backtrace(*modifiers)
|
|
return unless super()
|
|
trace_framework = modifiers.include?(:framework)
|
|
trace_ruby = modifiers.length == 0 || modifiers.include?(:ruby)
|
|
trace_javascript = modifiers.length == 0 || modifiers.include?(:javascript)
|
|
bilingual_backtrace(trace_ruby, trace_javascript).tap do |trace|
|
|
trace.reject! {|frame| frame =~ %r{(lib/v8/.*\.rb|ext/v8/.*\.cc)}} unless modifiers.include?(:framework)
|
|
end
|
|
end
|
|
|
|
def root_cause
|
|
causes.last
|
|
end
|
|
|
|
def in_javascript?
|
|
causes.last.is_a? self.class
|
|
end
|
|
|
|
def in_ruby?
|
|
!in_javascript?
|
|
end
|
|
|
|
def bilingual_backtrace(trace_ruby = true, trace_javascript = true)
|
|
backtrace = causes.reduce(:backtrace => [], :ruby => -1, :javascript => -1) { |accumulator, cause|
|
|
accumulator.tap do
|
|
if trace_ruby
|
|
backtrace_selector = cause.respond_to?(:standard_error_backtrace) ? :standard_error_backtrace : :backtrace
|
|
ruby_frames = cause.send(backtrace_selector)[0..accumulator[:ruby]]
|
|
accumulator[:backtrace].unshift *ruby_frames
|
|
accumulator[:ruby] -= ruby_frames.length
|
|
end
|
|
if trace_javascript && cause.respond_to?(:javascript_backtrace)
|
|
javascript_frames = cause.javascript_backtrace.to_a[0..accumulator[:javascript]].map(&:to_s)
|
|
accumulator[:backtrace].unshift *javascript_frames
|
|
accumulator[:javascript] -= javascript_frames.length
|
|
end
|
|
end
|
|
}[:backtrace]
|
|
end
|
|
|
|
module Try
|
|
def try
|
|
V8::C::TryCatch() do |trycatch|
|
|
result = yield
|
|
if trycatch.HasCaught()
|
|
raise V8::Error(trycatch)
|
|
else
|
|
result
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
module Protect
|
|
def protect
|
|
yield
|
|
rescue Exception => e
|
|
error = V8::C::Exception::Error(e.message)
|
|
error.SetHiddenValue("rr::Cause", V8::C::External::New(e))
|
|
V8::C::ThrowException(error)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
# Convert the result of a triggered JavaScript try/catch block into
|
|
# a V8::Error
|
|
#
|
|
# This is a bit of a yak-shave because JavaScript let's you throw all
|
|
# kinds of things. We do our best to make sure that the message property
|
|
# of the resulting V8::Error is as helpful as possible, and that it
|
|
# contains as much source location information as we can put onto it.
|
|
#
|
|
# For example:
|
|
#
|
|
# throw 4
|
|
# throw 'four'
|
|
# throw {number: 4}
|
|
#
|
|
# are all valid cases, none of which actually reference an exception object
|
|
# with a stack trace and a message. only with something like:
|
|
#
|
|
# throw new Error('fail!')
|
|
#
|
|
# do you get the a proper stacktrace and a message property. However a lot of
|
|
# times JavaScript library authors are lazy and do this:
|
|
#
|
|
# throw {message: 'foo', otherMetadata: 'bar'}
|
|
#
|
|
# It's common enough so we do the courtesy of having the resulting V8::Error
|
|
# have as its message in ruby land the 'message' property of the value object
|
|
#
|
|
# To further complicate things, SyntaxErrors do not have a JavaScript stack
|
|
# (even if they occur during js execution). This can make debugging a nightmare
|
|
# so we copy in the source location of the syntax error into the message of
|
|
# the resulting V8::Error
|
|
#
|
|
# @param [V8::C::TryCatch] native trycatch object that has been triggered
|
|
# @return [V8::Error] the error generated by this try/catch
|
|
def self.Error(trycatch)
|
|
exception = trycatch.Exception()
|
|
|
|
value = exception.to_ruby
|
|
cause = nil
|
|
message = trycatch.Message()
|
|
javascript_backtrace = V8::StackTrace.new(message.GetStackTrace()) if message
|
|
|
|
message = if !exception.kind_of?(V8::C::Value)
|
|
exception.to_s==""?"Script Timed Out":exception.to_s
|
|
elsif exception.IsNativeError()
|
|
if cause = exception.GetHiddenValue("rr::Cause")
|
|
cause = cause.Value()
|
|
end
|
|
if value['constructor'] == V8::Context.current['SyntaxError']
|
|
info = trycatch.Message()
|
|
resource_name = info.GetScriptResourceName().to_ruby
|
|
"#{value['message']} at #{resource_name}:#{info.GetLineNumber()}:#{info.GetStartColumn() + 1}"
|
|
else
|
|
exception.Get("message").to_ruby
|
|
end
|
|
elsif exception.IsObject()
|
|
value['message'] || value.to_s
|
|
else
|
|
value.to_s
|
|
end
|
|
V8::Error.new(message, value, javascript_backtrace, cause)
|
|
end
|
|
const_set :JSError, Error
|
|
end
|