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.

Files changed (49) hide show
  1. data/Rakefile +3 -2
  2. data/ext/v8/rr.cpp +5 -18
  3. data/ext/v8/rr.h +0 -3
  4. data/ext/v8/upstream/Makefile +1 -1
  5. data/ext/v8/v8_array.cpp +3 -9
  6. data/ext/v8/v8_callbacks.cpp +1 -1
  7. data/ext/v8/v8_cxt.cpp +19 -36
  8. data/ext/v8/v8_cxt.h +0 -7
  9. data/ext/v8/v8_exception.cpp +2 -1
  10. data/ext/v8/v8_external.cpp +18 -11
  11. data/ext/v8/v8_external.h +1 -0
  12. data/ext/v8/v8_func.cpp +2 -5
  13. data/ext/v8/v8_func.h +0 -2
  14. data/ext/v8/v8_msg.cpp +1 -2
  15. data/ext/v8/v8_obj.cpp +3 -13
  16. data/ext/v8/v8_obj.h +0 -1
  17. data/ext/v8/v8_ref.cpp +1 -6
  18. data/ext/v8/v8_ref.h +1 -2
  19. data/ext/v8/v8_script.cpp +0 -2
  20. data/ext/v8/v8_str.cpp +9 -3
  21. data/ext/v8/v8_str.h +0 -2
  22. data/ext/v8/v8_template.cpp +49 -37
  23. data/ext/v8/v8_template.h +0 -4
  24. data/ext/v8/v8_try_catch.cpp +0 -1
  25. data/ext/v8/v8_value.cpp +1 -2
  26. data/lib/v8.rb +2 -1
  27. data/lib/v8/access.rb +90 -1
  28. data/lib/v8/array.rb +1 -1
  29. data/lib/v8/context.rb +8 -23
  30. data/lib/v8/error.rb +111 -0
  31. data/lib/v8/function.rb +6 -5
  32. data/lib/v8/object.rb +1 -1
  33. data/lib/v8/to.rb +26 -30
  34. data/spec/redjs/jsapi_spec.rb +55 -14
  35. data/spec/v8/error_spec.rb +118 -0
  36. data/therubyracer.gemspec +4 -4
  37. metadata +14 -21
  38. data/ext/v8/callbacks.cpp +0 -185
  39. data/ext/v8/callbacks.h +0 -14
  40. data/ext/v8/convert_ruby.cpp +0 -8
  41. data/ext/v8/convert_ruby.h +0 -99
  42. data/ext/v8/convert_string.cpp +0 -10
  43. data/ext/v8/convert_string.h +0 -73
  44. data/ext/v8/convert_v8.cpp +0 -9
  45. data/ext/v8/convert_v8.h +0 -124
  46. data/ext/v8/converters.cpp +0 -84
  47. data/ext/v8/converters.h +0 -21
  48. data/ext/v8/v8.bundle +0 -0
  49. 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.ruby(@native.Call(this, To.v8(args)))
11
- err = JavascriptError.new(try) if try.HasCaught()
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 StandardError => e
32
- V8::C::ThrowException(V8::C::Exception::Error(V8::C::String::New(e.message)))
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
@@ -11,7 +11,7 @@ module V8
11
11
 
12
12
  def [](key)
13
13
  @context.enter do
14
- To.ruby(@native.Get(To.v8(key)))
14
+ To.rb(@native.Get(To.v8(key)))
15
15
  end
16
16
  end
17
17
 
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 ruby(value)
6
+ def rb(value)
6
7
  case value
7
- when V8::C::Function then V8::Function.new(value)
8
- when V8::C::Array then V8::Array.new(value)
9
- when V8::C::Object then V8::Object.new(value)
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, Symbol
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.ruby(arguments[i])
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
- rubyobject = C::ObjectTemplate::New()
52
- rubyobject.SetNamedPropertyHandler(
53
- NamedPropertyGetter,
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
@@ -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 "loading javascript source into the interpreter" do
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(JavascriptError)
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