therubyracer 0.7.1 → 0.7.2.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of therubyracer might be problematic. Click here for more details.
- data/Rakefile +3 -2
- data/ext/v8/rr.cpp +5 -18
- data/ext/v8/rr.h +0 -3
- data/ext/v8/upstream/Makefile +1 -1
- data/ext/v8/v8_array.cpp +3 -9
- data/ext/v8/v8_callbacks.cpp +1 -1
- data/ext/v8/v8_cxt.cpp +19 -36
- data/ext/v8/v8_cxt.h +0 -7
- data/ext/v8/v8_exception.cpp +2 -1
- data/ext/v8/v8_external.cpp +18 -11
- data/ext/v8/v8_external.h +1 -0
- data/ext/v8/v8_func.cpp +2 -5
- data/ext/v8/v8_func.h +0 -2
- data/ext/v8/v8_msg.cpp +1 -2
- data/ext/v8/v8_obj.cpp +3 -13
- data/ext/v8/v8_obj.h +0 -1
- data/ext/v8/v8_ref.cpp +1 -6
- data/ext/v8/v8_ref.h +1 -2
- data/ext/v8/v8_script.cpp +0 -2
- data/ext/v8/v8_str.cpp +9 -3
- data/ext/v8/v8_str.h +0 -2
- data/ext/v8/v8_template.cpp +49 -37
- data/ext/v8/v8_template.h +0 -4
- data/ext/v8/v8_try_catch.cpp +0 -1
- data/ext/v8/v8_value.cpp +1 -2
- data/lib/v8.rb +2 -1
- data/lib/v8/access.rb +90 -1
- data/lib/v8/array.rb +1 -1
- data/lib/v8/context.rb +8 -23
- data/lib/v8/error.rb +111 -0
- data/lib/v8/function.rb +6 -5
- data/lib/v8/object.rb +1 -1
- data/lib/v8/to.rb +26 -30
- data/spec/redjs/jsapi_spec.rb +55 -14
- data/spec/v8/error_spec.rb +118 -0
- data/therubyracer.gemspec +4 -4
- metadata +14 -21
- data/ext/v8/callbacks.cpp +0 -185
- data/ext/v8/callbacks.h +0 -14
- data/ext/v8/convert_ruby.cpp +0 -8
- data/ext/v8/convert_ruby.h +0 -99
- data/ext/v8/convert_string.cpp +0 -10
- data/ext/v8/convert_string.h +0 -73
- data/ext/v8/convert_v8.cpp +0 -9
- data/ext/v8/convert_v8.h +0 -124
- data/ext/v8/converters.cpp +0 -84
- data/ext/v8/converters.h +0 -21
- data/ext/v8/v8.bundle +0 -0
- data/lib/v8/callbacks.rb +0 -88
data/lib/v8/error.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
module V8
|
3
|
+
class JSError < StandardError
|
4
|
+
attr_reader :value, :boundaries
|
5
|
+
|
6
|
+
def initialize(try)
|
7
|
+
begin
|
8
|
+
super(initialize_unsafe(try))
|
9
|
+
rescue Exception => e
|
10
|
+
@boundaries = Boundary.new(:rbframes => e.backtrace)
|
11
|
+
@value = e
|
12
|
+
super("BUG! please report. JSError#initialize failed!: #{e.message}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize_unsafe(try)
|
17
|
+
message = nil
|
18
|
+
ex = To.rb(try.Exception())
|
19
|
+
@boundaries = [Boundary.new :rbframes => caller(3), :jsframes => parse_js_frames(try)]
|
20
|
+
if V8::Object === ex
|
21
|
+
if msg = ex['message']
|
22
|
+
message = msg
|
23
|
+
else
|
24
|
+
message = ex.to_s
|
25
|
+
end
|
26
|
+
if cause = ex.instance_variable_get(:@native).GetHiddenValue("TheRubyRacer::Cause")
|
27
|
+
if !cause.IsEmpty()
|
28
|
+
prev = cause.Value()
|
29
|
+
if prev.kind_of?(JSError)
|
30
|
+
@boundaries.concat prev.boundaries
|
31
|
+
@value = prev.value
|
32
|
+
else
|
33
|
+
@value = prev
|
34
|
+
@boundaries.concat [Boundary.new(:rbframes => prev.backtrace)]
|
35
|
+
end
|
36
|
+
else
|
37
|
+
@value = ex
|
38
|
+
end
|
39
|
+
end
|
40
|
+
else
|
41
|
+
@value = ex
|
42
|
+
message = ex.to_s
|
43
|
+
@boundaries.first.jsframes << 'at [???].js'
|
44
|
+
end
|
45
|
+
return message
|
46
|
+
end
|
47
|
+
|
48
|
+
def in_ruby?
|
49
|
+
@value.kind_of?(Exception)
|
50
|
+
end
|
51
|
+
|
52
|
+
def in_javascript?
|
53
|
+
!in_ruby?
|
54
|
+
end
|
55
|
+
|
56
|
+
def backtrace(*modifiers)
|
57
|
+
trace_framework = modifiers.include?(:framework)
|
58
|
+
trace_ruby = modifiers.length == 0 || modifiers.include?(:ruby)
|
59
|
+
trace_javascript = modifiers.length == 0 || modifiers.include?(:javascript)
|
60
|
+
mixed = []
|
61
|
+
rbcontext = []
|
62
|
+
jscontext = []
|
63
|
+
@boundaries.each do |b|
|
64
|
+
rbframes = b.rbframes.dup
|
65
|
+
rbcontext.reverse_each do |frame|
|
66
|
+
if frame == rbframes.last
|
67
|
+
rbframes.pop
|
68
|
+
else
|
69
|
+
break
|
70
|
+
end
|
71
|
+
end if trace_ruby
|
72
|
+
jsframes = b.jsframes.dup
|
73
|
+
jscontext.reverse_each do |frame|
|
74
|
+
if frame == jsframes.last
|
75
|
+
jsframes.pop
|
76
|
+
else
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end if trace_javascript
|
80
|
+
rbcontext = b.rbframes
|
81
|
+
jscontext = b.jsframes
|
82
|
+
rbframes.reject! {|f| f =~ /lib\/v8\/\w+\.rb/} unless trace_framework
|
83
|
+
mixed.unshift(*rbframes) if trace_ruby
|
84
|
+
mixed.unshift(*jsframes) if trace_javascript
|
85
|
+
end
|
86
|
+
return mixed
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_js_frames(try)
|
90
|
+
raw = To.rb(try.StackTrace())
|
91
|
+
if raw && !raw.empty?
|
92
|
+
raw.split("\n")[1..-1].tap do |frames|
|
93
|
+
frames.each {|frame| frame.strip!.chomp!(",")}
|
94
|
+
end
|
95
|
+
else
|
96
|
+
[]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class Boundary
|
101
|
+
attr_reader :rbframes, :jsframes
|
102
|
+
|
103
|
+
def initialize(frames = {})
|
104
|
+
@rbframes = frames[:rbframes] || []
|
105
|
+
@jsframes = frames[:jsframes] || []
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
#deprecated -- use JSError
|
110
|
+
JavasriptError = JSError
|
111
|
+
end
|
data/lib/v8/function.rb
CHANGED
@@ -7,8 +7,8 @@ module V8
|
|
7
7
|
C::TryCatch.try do |try|
|
8
8
|
@context.enter do
|
9
9
|
this = To.v8(thisObject)
|
10
|
-
return_value = To.
|
11
|
-
err =
|
10
|
+
return_value = To.rb(@native.Call(this, To.v8(args)))
|
11
|
+
err = JSError.new(try) if try.HasCaught()
|
12
12
|
end
|
13
13
|
end
|
14
14
|
raise err if err
|
@@ -28,10 +28,11 @@ module V8
|
|
28
28
|
def self.rubycall(rubycode, *args)
|
29
29
|
begin
|
30
30
|
To.v8(rubycode.call(*args))
|
31
|
-
rescue
|
32
|
-
V8::C::
|
31
|
+
rescue Exception => e
|
32
|
+
error = V8::C::Exception::Error(V8::C::String::New(e.message))
|
33
|
+
error.SetHiddenValue("TheRubyRacer::Cause", C::External::New(e))
|
34
|
+
V8::C::ThrowException(error)
|
33
35
|
end
|
34
36
|
end
|
35
|
-
|
36
37
|
end
|
37
38
|
end
|
data/lib/v8/object.rb
CHANGED
data/lib/v8/to.rb
CHANGED
@@ -1,32 +1,34 @@
|
|
1
|
+
require 'weakref'
|
1
2
|
|
2
3
|
module V8
|
3
4
|
module To
|
4
5
|
class << self
|
5
|
-
def
|
6
|
+
def rb(value)
|
6
7
|
case value
|
7
|
-
when V8::C::Function then V8::Function
|
8
|
-
when V8::C::Array then V8::Array
|
9
|
-
when V8::C::Object then V8::Object
|
8
|
+
when V8::C::Function then peer(value) {V8::Function}
|
9
|
+
when V8::C::Array then peer(value) {V8::Array}
|
10
|
+
when V8::C::Object then peer(value) {V8::Object}
|
10
11
|
when V8::C::String then value.Utf8Value()
|
11
12
|
when V8::C::Date then Time.at(value.NumberValue())
|
13
|
+
when V8::C::Value then nil if value.IsEmpty()
|
12
14
|
else
|
13
15
|
value
|
14
16
|
end
|
15
17
|
end
|
16
18
|
|
17
|
-
alias_method :rb, :ruby
|
18
|
-
|
19
19
|
def v8(value)
|
20
20
|
case value
|
21
21
|
when V8::Object
|
22
22
|
value.instance_eval {@native}
|
23
|
-
when String
|
23
|
+
when String
|
24
24
|
C::String::New(value.to_s)
|
25
|
+
when Symbol
|
26
|
+
C::String::NewSymbol(value.to_s)
|
25
27
|
when Proc,Method
|
26
28
|
template = C::FunctionTemplate::New() do |arguments|
|
27
29
|
rbargs = []
|
28
30
|
for i in 0..arguments.Length() - 1
|
29
|
-
rbargs << To.
|
31
|
+
rbargs << To.rb(arguments[i])
|
30
32
|
end
|
31
33
|
V8::Function.rubycall(value, *rbargs)
|
32
34
|
end
|
@@ -45,35 +47,29 @@ module V8
|
|
45
47
|
end
|
46
48
|
when ::Time
|
47
49
|
C::Date::New(value)
|
50
|
+
when ::Class
|
51
|
+
Constructors[value].GetFunction().tap do |f|
|
52
|
+
f.SetHiddenValue(C::String::NewSymbol("TheRubyRacer::RubyObject"), C::External::New(value))
|
53
|
+
end
|
48
54
|
when nil,Numeric,TrueClass,FalseClass, C::Value
|
49
55
|
value
|
50
56
|
else
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
NamedPropertySetter,
|
55
|
-
nil,
|
56
|
-
nil,
|
57
|
-
NamedPropertyEnumerator
|
58
|
-
)
|
59
|
-
obj = nil
|
60
|
-
unless C::Context::InContext()
|
61
|
-
cxt = C::Context::New()
|
62
|
-
cxt.Enter()
|
63
|
-
begin
|
64
|
-
obj = rubyobject.NewInstance()
|
65
|
-
obj.SetHiddenValue(C::String::New("TheRubyRacer::RubyObject"), C::External::Wrap(value))
|
66
|
-
ensure
|
67
|
-
cxt.Exit()
|
68
|
-
end
|
69
|
-
else
|
70
|
-
obj = rubyobject.NewInstance()
|
71
|
-
obj.SetHiddenValue(C::String::New("TheRubyRacer::RubyObject"), C::External::Wrap(value))
|
72
|
-
end
|
57
|
+
args = C::Array::New(1)
|
58
|
+
args.Set(0, C::External::New(value))
|
59
|
+
obj = Access[value.class].GetFunction().NewInstance(args)
|
73
60
|
return obj
|
74
61
|
end
|
75
62
|
end
|
76
63
|
|
64
|
+
def peer(value)
|
65
|
+
external = value.GetHiddenValue(C::String::NewSymbol("TheRubyRacer::RubyObject"))
|
66
|
+
if external && !external.IsEmpty()
|
67
|
+
external.Value()
|
68
|
+
else
|
69
|
+
yield.new(value)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
77
73
|
def camel_case(str)
|
78
74
|
str.to_s.gsub(/_(\w)/) {$1.upcase}
|
79
75
|
end
|
data/spec/redjs/jsapi_spec.rb
CHANGED
@@ -354,7 +354,7 @@ describe "Ruby Javascript API" do
|
|
354
354
|
cxt.eval("this").should be(scope)
|
355
355
|
end
|
356
356
|
end
|
357
|
-
|
357
|
+
|
358
358
|
it "can directly embed ruby values into javascript" do
|
359
359
|
@cxt["bar"] = 9
|
360
360
|
@cxt['foo'] = "bar"
|
@@ -367,12 +367,62 @@ describe "Ruby Javascript API" do
|
|
367
367
|
@cxt.eval('trU').should be(true)
|
368
368
|
@cxt.eval('falls').should be(false)
|
369
369
|
end
|
370
|
-
|
370
|
+
|
371
371
|
it "has the global object available as a javascript value" do
|
372
372
|
@cxt['foo'] = 'bar'
|
373
373
|
@cxt.scope.should be_kind_of(V8::Object)
|
374
374
|
@cxt.scope['foo'].should == 'bar'
|
375
375
|
end
|
376
|
+
|
377
|
+
it "will treat class objects as constructors by default" do
|
378
|
+
@cxt[:MyClass] = Class.new.tap do |cls|
|
379
|
+
cls.class_eval do
|
380
|
+
attr_reader :one, :two
|
381
|
+
def initialize(one, two)
|
382
|
+
@one, @two = one, two
|
383
|
+
# rputs "one: #{@one}, two: #{@two}"
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
@cxt.eval('new MyClass(1,2).one').should == 1
|
388
|
+
@cxt.eval('new MyClass(1,2).two').should == 2
|
389
|
+
end
|
390
|
+
|
391
|
+
it "unwraps reflected ruby constructor objects into their underlying ruby classes" do
|
392
|
+
@cxt['RubyObject'] = Object
|
393
|
+
@cxt['RubyObject'].should be(Object)
|
394
|
+
end
|
395
|
+
|
396
|
+
it "honors the instanceof operator for ruby instances when compared to their reflected constructors" do
|
397
|
+
@cxt['RubyObject'] = Object
|
398
|
+
@cxt['one'] = Object.new
|
399
|
+
@cxt['two'] = Object.new
|
400
|
+
@cxt.eval('one instanceof RubyObject')
|
401
|
+
@cxt.eval('two instanceof RubyObject')
|
402
|
+
@cxt.eval('RubyObject instanceof Function').should be(true)
|
403
|
+
@cxt.eval('new RubyObject() instanceof RubyObject').should be(true)
|
404
|
+
@cxt.eval('new RubyObject() instanceof Array').should be(false)
|
405
|
+
@cxt.eval('new RubyObject() instanceof Object').should be(true)
|
406
|
+
end
|
407
|
+
|
408
|
+
it "unwraps instances created by a native constructor when passing them back to ruby" do
|
409
|
+
cls = Class.new.tap do |c|
|
410
|
+
c.class_eval do
|
411
|
+
def definitely_a_product_of_this_one_off_class?
|
412
|
+
true
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
@cxt['RubyClass'] = cls
|
417
|
+
@cxt.eval('new RubyClass()').should be_definitely_a_product_of_this_one_off_class
|
418
|
+
end
|
419
|
+
|
420
|
+
it "does not allow you to call a native ruby constructor, unless that constructor has been directly embedded" do
|
421
|
+
@cxt['o'] = Class.new.new
|
422
|
+
lambda {
|
423
|
+
@cxt.eval('new (o.constructor)()')
|
424
|
+
}.should raise_error(JSError)
|
425
|
+
end
|
376
426
|
|
377
427
|
it "extends object to allow for the arbitrary execution of javascript with any object as the scope" do
|
378
428
|
Class.new.class_eval do
|
@@ -388,7 +438,7 @@ describe "Ruby Javascript API" do
|
|
388
438
|
new.eval_js("timesfive(6)").should == 30
|
389
439
|
end
|
390
440
|
end
|
391
|
-
|
441
|
+
|
392
442
|
it "can limit the number of instructions that are executed in the context" do
|
393
443
|
pending "haven't figured out how to constrain resources in V8"
|
394
444
|
lambda {
|
@@ -402,7 +452,7 @@ describe "Ruby Javascript API" do
|
|
402
452
|
end
|
403
453
|
end
|
404
454
|
|
405
|
-
describe "
|
455
|
+
describe "Loading javascript source into the interpreter" do
|
406
456
|
|
407
457
|
it "can take an IO object in the eval method instead of a string" do
|
408
458
|
source = StringIO.new(<<-EOJS)
|
@@ -491,7 +541,7 @@ end
|
|
491
541
|
it "raises javascript exceptions as ruby exceptions" do
|
492
542
|
lambda {
|
493
543
|
Context.eval('foo')
|
494
|
-
}.should raise_error(
|
544
|
+
}.should raise_error(JSError)
|
495
545
|
end
|
496
546
|
|
497
547
|
it "can handle syntax errors" do
|
@@ -499,15 +549,6 @@ end
|
|
499
549
|
Context.eval('does not compiles')
|
500
550
|
}.should raise_error
|
501
551
|
end
|
502
|
-
|
503
|
-
it "should track message state" do
|
504
|
-
begin
|
505
|
-
Context.new.eval("var foo = 'bar';\nsyntax error!", "foo.js")
|
506
|
-
rescue JavascriptError => e
|
507
|
-
e.line_number.should == 2
|
508
|
-
e.source_name.should == "foo.js"
|
509
|
-
end
|
510
|
-
end
|
511
552
|
|
512
553
|
it "translates ruby exceptions into javascript exceptions if they are thrown from code called it javascript" do
|
513
554
|
Context.new do |cxt|
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe V8::JSError do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@cxt = V8::Context.new
|
7
|
+
@cxt['one'] = lambda do
|
8
|
+
@cxt.eval('two()', 'one.js')
|
9
|
+
end
|
10
|
+
@cxt['two'] = lambda do
|
11
|
+
@cxt.eval('three()', 'two.js')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "captures a message without over nesting when the error is an error" do
|
16
|
+
throw! do |e|
|
17
|
+
e.message.should == "BOOM!"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "captures the js message without over nesting when the error is a normal object" do
|
22
|
+
throw!('{foo: "bar"}') do |e|
|
23
|
+
e.message.should == "[object Object]"
|
24
|
+
end
|
25
|
+
throw!('{message: "bar"}') do |e|
|
26
|
+
e.message.should == "bar"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it "captures a thrown value as the message" do
|
31
|
+
throw!('"BOOM!"') do |e|
|
32
|
+
e.message.should == "BOOM!"
|
33
|
+
end
|
34
|
+
throw!('6') do |e|
|
35
|
+
e.message.should == '6'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "has a reference to the root javascript cause" do
|
40
|
+
throw!('"I am a String"') do |e|
|
41
|
+
e.should_not be_in_ruby
|
42
|
+
e.should be_in_javascript
|
43
|
+
e.value.should == "I am a String"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "has a reference to the root ruby cause if one exists" do
|
48
|
+
StandardError.new("BOOM!").tap do |bomb|
|
49
|
+
@cxt['boom'] = lambda do
|
50
|
+
raise bomb
|
51
|
+
end
|
52
|
+
lambda {
|
53
|
+
@cxt.eval('boom()', 'boom.js')
|
54
|
+
}.should(raise_error do |raised|
|
55
|
+
raised.should be_in_ruby
|
56
|
+
raised.should_not be_in_javascript
|
57
|
+
raised.value.should be(bomb)
|
58
|
+
end)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "backtrace" do
|
63
|
+
|
64
|
+
it "is mixed with ruby and javascript" do
|
65
|
+
throw! do |e|
|
66
|
+
e.backtrace.first.should == "at three.js:1:7"
|
67
|
+
e.backtrace[1].should =~ /error_spec.rb/
|
68
|
+
e.backtrace[2].should == "at two.js:1:1"
|
69
|
+
e.backtrace[3].should =~ /error_spec.rb/
|
70
|
+
e.backtrace[4].should == "at one.js:1:1"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "can be set to show only ruby frames" do
|
75
|
+
throw! do |e|
|
76
|
+
e.backtrace(:ruby).each do |frame|
|
77
|
+
frame.should =~ /(\.rb|):\d+/
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it "can be set to show only javascript frames" do
|
83
|
+
throw! do |e|
|
84
|
+
e.backtrace(:javascript).each do |frame|
|
85
|
+
frame.should =~ /\.js:\d:\d/
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "includes a mystery marker when the original frame is unavailable because what got thrown wasn't an error" do
|
91
|
+
throw!("6") do |e|
|
92
|
+
e.backtrace.first.should == 'at [???].js'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
it "can start with ruby at the bottom" do
|
97
|
+
@cxt['boom'] = lambda do
|
98
|
+
raise StandardError, "Bif!"
|
99
|
+
end
|
100
|
+
lambda {
|
101
|
+
@cxt.eval('boom()', "boom.js")
|
102
|
+
}.should(raise_error {|e|
|
103
|
+
e.backtrace.first.should =~ /error_spec\.rb/
|
104
|
+
e.backtrace[1].should =~ /boom.js/
|
105
|
+
})
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def throw!(js = "new Error('BOOM!')", &block)
|
111
|
+
@cxt['three'] = lambda do
|
112
|
+
@cxt.eval("throw #{js}", 'three.js')
|
113
|
+
end
|
114
|
+
lambda do
|
115
|
+
@cxt['one'].call()
|
116
|
+
end.should(raise_error(V8::JSError, &block))
|
117
|
+
end
|
118
|
+
end
|