mirror of
https://github.com/rubyjs/therubyracer
synced 2023-03-27 23:21:42 -04:00
support for a mixed javascript and ruby stack.
This commit is contained in:
parent
48d6e04e17
commit
168394721f
5 changed files with 228 additions and 12 deletions
113
lib/v8/error.rb
113
lib/v8/error.rb
|
@ -1,16 +1,111 @@
|
|||
|
||||
module V8
|
||||
class JSError < StandardError
|
||||
attr_reader :source_name, :source_line, :line_number, :javascript_stacktrace
|
||||
|
||||
attr_reader :value, :boundaries
|
||||
|
||||
def initialize(try)
|
||||
msg = try.Message()
|
||||
@source_name = To.rb(msg.GetScriptResourceName())
|
||||
@source_line = To.rb(msg.GetSourceLine())
|
||||
@line_number = To.rb(msg.GetLineNumber())
|
||||
@javascript_stacktrace = To.rb(try.StackTrace())
|
||||
super("#{To.rb(msg.Get())}: #{@source_name}:#{@line_number}")
|
||||
begin
|
||||
super(initialize_unsafe(try))
|
||||
rescue Exception => e
|
||||
@boundaries = Boundary.new(:rbframes => e.backtrace)
|
||||
@value = e
|
||||
super("BUG! please report. JSError#initialize failed!: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_unsafe(try)
|
||||
message = nil
|
||||
ex = To.rb(try.Exception())
|
||||
@boundaries = [Boundary.new :rbframes => caller(3), :jsframes => parse_js_frames(try)]
|
||||
if V8::Object === ex
|
||||
if msg = ex['message']
|
||||
message = msg
|
||||
else
|
||||
message = ex.to_s
|
||||
end
|
||||
if cause = ex.instance_variable_get(:@native).GetHiddenValue("TheRubyRacer::Cause")
|
||||
if !cause.IsEmpty()
|
||||
prev = cause.Value()
|
||||
if prev.kind_of?(JSError)
|
||||
@boundaries.concat prev.boundaries
|
||||
@value = prev.value
|
||||
else
|
||||
@value = prev
|
||||
@boundaries.concat [Boundary.new(:rbframes => prev.backtrace)]
|
||||
end
|
||||
else
|
||||
@value = ex
|
||||
end
|
||||
end
|
||||
else
|
||||
@value = ex
|
||||
message = ex.to_s
|
||||
@boundaries.first.jsframes << 'at [???].js'
|
||||
end
|
||||
return message
|
||||
end
|
||||
|
||||
def in_ruby?
|
||||
@value.kind_of?(Exception)
|
||||
end
|
||||
|
||||
def in_javascript?
|
||||
!in_ruby?
|
||||
end
|
||||
|
||||
def backtrace(*modifiers)
|
||||
trace_framework = modifiers.include?(:framework)
|
||||
trace_ruby = modifiers.length == 0 || modifiers.include?(:ruby)
|
||||
trace_javascript = modifiers.length == 0 || modifiers.include?(:javascript)
|
||||
mixed = []
|
||||
rbcontext = []
|
||||
jscontext = []
|
||||
@boundaries.each do |b|
|
||||
rbframes = b.rbframes.dup
|
||||
rbcontext.reverse_each do |frame|
|
||||
if frame == rbframes.last
|
||||
rbframes.pop
|
||||
else
|
||||
break
|
||||
end
|
||||
end if trace_ruby
|
||||
jsframes = b.jsframes.dup
|
||||
jscontext.reverse_each do |frame|
|
||||
if frame == jsframes.last
|
||||
jsframes.pop
|
||||
else
|
||||
break
|
||||
end
|
||||
end if trace_javascript
|
||||
rbcontext = b.rbframes
|
||||
jscontext = b.jsframes
|
||||
rbframes.reject! {|f| f =~ /lib\/v8\/\w+\.rb/} unless trace_framework
|
||||
mixed.unshift(*rbframes) if trace_ruby
|
||||
mixed.unshift(*jsframes) if trace_javascript
|
||||
end
|
||||
return mixed
|
||||
end
|
||||
|
||||
def parse_js_frames(try)
|
||||
raw = To.rb(try.StackTrace())
|
||||
if raw && !raw.empty?
|
||||
raw.split("\n")[1..-1].tap do |frames|
|
||||
frames.each {|frame| frame.strip!.chomp!(",")}
|
||||
end
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
class Boundary
|
||||
attr_reader :rbframes, :jsframes
|
||||
|
||||
def initialize(frames = {})
|
||||
@rbframes = frames[:rbframes] || []
|
||||
@jsframes = frames[:jsframes] || []
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
#deprecated -- use JSError
|
||||
JavasriptError = JSError
|
||||
end
|
|
@ -28,8 +28,10 @@ module V8
|
|||
def self.rubycall(rubycode, *args)
|
||||
begin
|
||||
To.v8(rubycode.call(*args))
|
||||
rescue StandardError => e
|
||||
V8::C::ThrowException(V8::C::Exception::Error(V8::C::String::New(e.message)))
|
||||
rescue Exception => e
|
||||
error = V8::C::Exception::Error(V8::C::String::New(e.message))
|
||||
error.SetHiddenValue("TheRubyRacer::Cause", C::External::New(e))
|
||||
V8::C::ThrowException(error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,7 @@ module V8
|
|||
when V8::C::Object then peer(value) {V8::Object}
|
||||
when V8::C::String then value.Utf8Value()
|
||||
when V8::C::Date then Time.at(value.NumberValue())
|
||||
when V8::C::Value then nil if value.IsEmpty()
|
||||
else
|
||||
value
|
||||
end
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 43aaf4c58cfa2c554a9975abd6f2a98faf0f504e
|
||||
Subproject commit 0d6e2b23318b4f49779462671a5c8b1541d7adee
|
118
spec/v8/error_spec.rb
Normal file
118
spec/v8/error_spec.rb
Normal file
|
@ -0,0 +1,118 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
||||
|
||||
describe V8::JSError do
|
||||
|
||||
before(:each) do
|
||||
@cxt = V8::Context.new
|
||||
@cxt['one'] = lambda do
|
||||
@cxt.eval('two()', 'one.js')
|
||||
end
|
||||
@cxt['two'] = lambda do
|
||||
@cxt.eval('three()', 'two.js')
|
||||
end
|
||||
end
|
||||
|
||||
it "captures a message without over nesting when the error is an error" do
|
||||
throw! do |e|
|
||||
e.message.should == "BOOM!"
|
||||
end
|
||||
end
|
||||
|
||||
it "captures the js message without over nesting when the error is a normal object" do
|
||||
throw!('{foo: "bar"}') do |e|
|
||||
e.message.should == "[object Object]"
|
||||
end
|
||||
throw!('{message: "bar"}') do |e|
|
||||
e.message.should == "bar"
|
||||
end
|
||||
end
|
||||
|
||||
it "captures a thrown value as the message" do
|
||||
throw!('"BOOM!"') do |e|
|
||||
e.message.should == "BOOM!"
|
||||
end
|
||||
throw!('6') do |e|
|
||||
e.message.should == '6'
|
||||
end
|
||||
end
|
||||
|
||||
it "has a reference to the root javascript cause" do
|
||||
throw!('"I am a String"') do |e|
|
||||
e.should_not be_in_ruby
|
||||
e.should be_in_javascript
|
||||
e.value.should == "I am a String"
|
||||
end
|
||||
end
|
||||
|
||||
it "has a reference to the root ruby cause if one exists" do
|
||||
StandardError.new("BOOM!").tap do |bomb|
|
||||
@cxt['boom'] = lambda do
|
||||
raise bomb
|
||||
end
|
||||
lambda {
|
||||
@cxt.eval('boom()', 'boom.js')
|
||||
}.should(raise_error do |raised|
|
||||
raised.should be_in_ruby
|
||||
raised.should_not be_in_javascript
|
||||
raised.value.should be(bomb)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "backtrace" do
|
||||
|
||||
it "is mixed with ruby and javascript" do
|
||||
throw! do |e|
|
||||
e.backtrace.first.should == "at three.js:1:7"
|
||||
e.backtrace[1].should =~ /error_spec.rb/
|
||||
e.backtrace[2].should == "at two.js:1:1"
|
||||
e.backtrace[3].should =~ /error_spec.rb/
|
||||
e.backtrace[4].should == "at one.js:1:1"
|
||||
end
|
||||
end
|
||||
|
||||
it "can be set to show only ruby frames" do
|
||||
throw! do |e|
|
||||
e.backtrace(:ruby).each do |frame|
|
||||
frame.should =~ /(\.rb|):\d+/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "can be set to show only javascript frames" do
|
||||
throw! do |e|
|
||||
e.backtrace(:javascript).each do |frame|
|
||||
frame.should =~ /\.js:\d:\d/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "includes a mystery marker when the original frame is unavailable because what got thrown wasn't an error" do
|
||||
throw!("6") do |e|
|
||||
e.backtrace.first.should == 'at [???].js'
|
||||
end
|
||||
end
|
||||
|
||||
it "can start with ruby at the bottom" do
|
||||
@cxt['boom'] = lambda do
|
||||
raise StandardError, "Bif!"
|
||||
end
|
||||
lambda {
|
||||
@cxt.eval('boom()', "boom.js")
|
||||
}.should(raise_error {|e|
|
||||
e.backtrace.first.should =~ /error_spec\.rb/
|
||||
e.backtrace[1].should =~ /boom.js/
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def throw!(js = "new Error('BOOM!')", &block)
|
||||
@cxt['three'] = lambda do
|
||||
@cxt.eval("throw #{js}", 'three.js')
|
||||
end
|
||||
lambda do
|
||||
@cxt['one'].call()
|
||||
end.should(raise_error(V8::JSError, &block))
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue