Permalink
Browse files

support for bilingual backtraces

  • Loading branch information...
1 parent 744baef commit 03e360dc7904975b0de85f944320fd69c525886d @cowboyd committed Aug 13, 2012
Showing with 83 additions and 29 deletions.
  1. +48 −12 lib/v8/error.rb
  2. +34 −15 lib/v8/stack.rb
  3. +1 −2 spec/v8/error_spec.rb
View
@@ -4,7 +4,20 @@ module V8
V8::C::V8::SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kOverview)
class Error < StandardError
include Enumerable
- attr_reader :value, :cause, :javascript_backtrace
+
+ # @!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 [V8::StackTrace] javascript_backtrace
+ # @return the complete JavaScript stack at the point this error was thrown
+ attr_reader :javascript_backtrace
+
+ alias_method :standard_error_backtrace, :backtrace
def initialize(message, value, javascript_backtrace, cause = nil)
super(message)
@@ -23,6 +36,16 @@ def causes
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}} unless modifiers.include?(:framework)
+ end
+ end
+
def root_cause
causes.last
end
@@ -35,15 +58,20 @@ def in_ruby?
!in_javascript?
end
- def multilingual_backtrace
- causes.reduce(:backtrace => [], :ruby => -1, :javascript => -1) { |accumulator, cause|
- ruby_frames = cause.backtrace[0..accumulator[:ruby]]
- accumulator[:backtrace].unshift *ruby_frames
- accumulator[:ruby] -= ruby_frames.length
- if cause.respond_to?(:javascript_backtrace)
- javascript_frames = cause.javascript_frames[0, accumulator[:javascript]].map(&:to_s)
- accumulator[:backtrace].unshift *javascript_frames
- accumulator[:javascript] -= javascript_frames.length
+ 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
@@ -77,19 +105,27 @@ def self.Error(trycatch)
exception = trycatch.Exception()
value = exception.to_ruby
cause = nil
+ javascript_backtrace = V8::StackTrace.new(trycatch.Message().GetStackTrace())
message = if !exception.kind_of?(V8::C::Value)
exception.to_s
elsif exception.IsNativeError()
if cause = exception.GetHiddenValue("rr::Cause")
cause = cause.Value()
end
- exception.Get("message").to_ruby
+ # SyntaxErrors do not have a JavaScript stack (even if they occur during js execution).
+ # To caputre where the error occured, we need to put it in the message
+ 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
- javascript_backtrace = V8::StackTrace.new(trycatch.Message().GetStackTrace())
V8::Error.new(message, value, javascript_backtrace, cause)
end
const_set :JSError, Error
View
@@ -5,17 +5,22 @@ class StackTrace
include Enumerable
def initialize(native)
+ @context = V8::Context.current
@native = native
end
def length
- @native ? @native.GetFrameCount() : 0
+ @context.enter do
+ @native ? @native.GetFrameCount() : 0
+ end
end
def each
return unless @native
- for i in 0..length - 1
- yield V8::StackFrame.new(@native.GetFrame(i))
+ @context.enter do
+ for i in 0..length - 1
+ yield V8::StackFrame.new(@native.GetFrame(i), @context)
+ end
end
end
@@ -26,40 +31,54 @@ def to_s
class StackFrame
- def initialize(native)
- @context = V8::Context.current
+ def initialize(native, context)
+ @context = context
@native = native
end
def script_name
- @context.to_ruby(@native.GetScriptName())
+ @context.enter do
+ @context.to_ruby(@native.GetScriptName())
+ end
end
def function_name
- @context.to_ruby(@native.GetFunctionName())
+ @context.enter do
+ @context.to_ruby(@native.GetFunctionName())
+ end
end
def line_number
- @native.GetLineNumber()
+ @context.enter do
+ @native.GetLineNumber()
+ end
end
def column
- @native.GetColumn()
+ @context.enter do
+ @native.GetColumn()
+ end
end
def eval?
- @native.IsEval()
+ @context.enter do
+ @native.IsEval()
+ end
end
def constructor?
- @native.IsConstructor()
+ @context.enter do
+ @native.IsConstructor()
+ end
end
def to_s
- "at " + if !function_name.empty?
- "#{function_name} (#{script_name}:#{line_number}:#{column})"
- else
- "#{script_name}:#{line_number}:#{column}"
+ @context.enter do
+ "at " + if !function_name.empty?
+ "#{function_name} (#{script_name}:#{line_number}:#{column})"
+ else
+ "#{script_name}:#{line_number}:#{column}"
+ end
end
end
end
View
@@ -62,7 +62,6 @@
end
describe "backtrace" do
- before {pending}
it "is mixed with ruby and javascript" do
throw! do |e|
e.backtrace.first.should == "at three.js:1:7"
@@ -104,7 +103,7 @@
"how do I find out that line 2 has the syntax error?";
INVALID
end.should raise_error(V8::JSError) {|error|
- error.backtrace.first.should == 'at source.js:2:60'
+ error.message.should eql 'Unexpected token : at source.js:2:61'
}
end

0 comments on commit 03e360d

Please sign in to comment.