therubyracer 0.11.0beta7 → 0.11.0beta8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of therubyracer might be problematic. Click here for more details.

data/ext/v8/backref.cc CHANGED
@@ -7,7 +7,7 @@ namespace rr {
7
7
  ID Backref::object;
8
8
 
9
9
  void Backref::Init() {
10
- Storage = rb_eval_string("Ref::WeakReference");
10
+ Storage = rb_eval_string("V8::Weak::Ref");
11
11
  rb_gc_register_address(&Storage);
12
12
  _new = rb_intern("new");
13
13
  object = rb_intern("object");
data/ext/v8/stack.cc CHANGED
@@ -22,6 +22,7 @@ namespace rr {
22
22
  defineMethod("GetColumn", &Frame::GetColumn).
23
23
  defineMethod("GetScriptName", &Frame::GetScriptName).
24
24
  defineMethod("GetScriptNameOrSourceURL", &Frame::GetScriptNameOrSourceURL).
25
+ defineMethod("GetFunctionName", &Frame::GetFunctionName).
25
26
  defineMethod("IsEval", &Frame::IsEval).
26
27
  defineMethod("IsConstructor", &Frame::IsConstructor).
27
28
  store(&Frame::Class);
data/lib/v8.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require "v8/version"
2
2
 
3
- require 'ref'
3
+ require 'v8/weak'
4
4
  require 'v8/init'
5
- require 'v8/util/weakcell'
6
5
  require 'v8/error'
6
+ require 'v8/stack'
7
7
  require 'v8/conversion/fundamental'
8
8
  require 'v8/conversion/indentity'
9
9
  require 'v8/conversion/reference'
@@ -1,6 +1,6 @@
1
1
  class V8::Conversion
2
2
  module Code
3
- include V8::Util::Weakcell
3
+ include V8::Weak::Cell
4
4
 
5
5
  def to_v8
6
6
  fn = to_template.GetFunction()
@@ -21,11 +21,11 @@ class V8::Conversion
21
21
  end
22
22
 
23
23
  def v8_idmap
24
- @v8_idmap ||= Ref::WeakValueMap.new
24
+ @v8_idmap ||= V8::Weak::WeakValueMap.new
25
25
  end
26
26
 
27
27
  def rb_idmap
28
- @ruby_idmap ||= Ref::WeakValueMap.new
28
+ @ruby_idmap ||= V8::Weak::WeakValueMap.new
29
29
  end
30
30
  end
31
31
  end
@@ -9,7 +9,7 @@ class V8::Conversion
9
9
 
10
10
  class MethodCache
11
11
  def initialize
12
- @map = Ref::WeakValueMap.new
12
+ @map = V8::Weak::WeakValueMap.new
13
13
  end
14
14
 
15
15
  def [](method)
data/lib/v8/error.rb CHANGED
@@ -1,18 +1,89 @@
1
1
  module V8
2
+ # capture 99 stack frames on exception with normal details.
3
+ # You can adjust these values for performance or turn of stack capture entirely
4
+ V8::C::V8::SetCaptureStackTraceForUncaughtExceptions(true, 99, V8::C::StackTrace::kOverview)
2
5
  class Error < StandardError
6
+ include Enumerable
7
+
8
+ # @!attribute [r] value
9
+ # @return [Object] the JavaScript value passed to the `throw` statement
3
10
  attr_reader :value
4
- def initialize(message, value)
11
+
12
+ # @!attribute [r] cause
13
+ # @return [Exception] the underlying error (if any) that triggered this error to be raised
14
+ attr_reader :cause
15
+
16
+ # @!attribute [V8::StackTrace] javascript_backtrace
17
+ # @return the complete JavaScript stack at the point this error was thrown
18
+ attr_reader :javascript_backtrace
19
+
20
+ # keep an alias to the StandardError#backtrace method so that we can capture
21
+ # just ruby backtrace frames
22
+ alias_method :standard_error_backtrace, :backtrace
23
+
24
+ def initialize(message, value, javascript_backtrace, cause = nil)
5
25
  super(message)
6
26
  @value = value
27
+ @cause = cause
28
+ @javascript_backtrace = javascript_backtrace
29
+ end
30
+
31
+ def causes
32
+ [].tap do |causes|
33
+ current = self
34
+ until current.nil? do
35
+ causes.push current
36
+ current = current.respond_to?(:cause) ? current.cause : nil
37
+ end
38
+ end
39
+ end
40
+
41
+ def backtrace(*modifiers)
42
+ return unless super()
43
+ trace_framework = modifiers.include?(:framework)
44
+ trace_ruby = modifiers.length == 0 || modifiers.include?(:ruby)
45
+ trace_javascript = modifiers.length == 0 || modifiers.include?(:javascript)
46
+ bilingual_backtrace(trace_ruby, trace_javascript).tap do |trace|
47
+ trace.reject! {|frame| frame =~ %r{(lib/v8/.*\.rb|ext/v8/.*\.cc)}} unless modifiers.include?(:framework)
48
+ end
49
+ end
50
+
51
+ def root_cause
52
+ causes.last
53
+ end
54
+
55
+ def in_javascript?
56
+ causes.last.is_a? self.class
57
+ end
58
+
59
+ def in_ruby?
60
+ !in_javascript?
61
+ end
62
+
63
+ def bilingual_backtrace(trace_ruby = true, trace_javascript = true)
64
+ backtrace = causes.reduce(:backtrace => [], :ruby => -1, :javascript => -1) { |accumulator, cause|
65
+ accumulator.tap do
66
+ if trace_ruby
67
+ backtrace_selector = cause.respond_to?(:standard_error_backtrace) ? :standard_error_backtrace : :backtrace
68
+ ruby_frames = cause.send(backtrace_selector)[0..accumulator[:ruby]]
69
+ accumulator[:backtrace].unshift *ruby_frames
70
+ accumulator[:ruby] -= ruby_frames.length
71
+ end
72
+ if trace_javascript && cause.respond_to?(:javascript_backtrace)
73
+ javascript_frames = cause.javascript_backtrace.to_a[0..accumulator[:javascript]].map(&:to_s)
74
+ accumulator[:backtrace].unshift *javascript_frames
75
+ accumulator[:javascript] -= javascript_frames.length
76
+ end
77
+ end
78
+ }[:backtrace]
7
79
  end
8
80
 
9
81
  module Try
10
82
  def try
11
- context = V8::Context.current
12
83
  V8::C::TryCatch() do |trycatch|
13
84
  result = yield
14
85
  if trycatch.HasCaught()
15
- V8::Error(trycatch.Exception())
86
+ raise V8::Error(trycatch)
16
87
  else
17
88
  result
18
89
  end
@@ -23,37 +94,41 @@ module V8
23
94
  module Protect
24
95
  def protect
25
96
  yield
26
- rescue Football => e
27
- e.kickoff!
28
97
  rescue Exception => e
29
- e.extend Football
30
- e.kickoff!
31
- end
32
- end
33
-
34
- module Football
35
- def kickoff!
36
- error = V8::C::Exception::Error(message)
37
- error.SetHiddenValue("rr::Football", V8::C::External::New(self))
98
+ error = V8::C::Exception::Error(e.message)
99
+ error.SetHiddenValue("rr::Cause", V8::C::External::New(e))
38
100
  V8::C::ThrowException(error)
39
101
  end
40
102
  end
41
103
 
42
104
  end
43
105
 
44
- def self.Error(exception)
106
+ def self.Error(trycatch)
107
+ exception = trycatch.Exception()
45
108
  value = exception.to_ruby
46
- if !exception.kind_of?(V8::C::Value)
47
- raise V8::Error.new(exception.to_s, value)
109
+ cause = nil
110
+ javascript_backtrace = V8::StackTrace.new(trycatch.Message().GetStackTrace())
111
+ message = if !exception.kind_of?(V8::C::Value)
112
+ exception.to_s
48
113
  elsif exception.IsNativeError()
49
- if football = exception.GetHiddenValue("rr::Football")
50
- raise football.Value()
114
+ if cause = exception.GetHiddenValue("rr::Cause")
115
+ cause = cause.Value()
116
+ end
117
+ # SyntaxErrors do not have a JavaScript stack (even if they occur during js execution).
118
+ # To caputre where the error occured, we need to put it in the message
119
+ if value['constructor'] == V8::Context.current['SyntaxError']
120
+ info = trycatch.Message()
121
+ resource_name = info.GetScriptResourceName().to_ruby
122
+ "#{value['message']} at #{resource_name}:#{info.GetLineNumber()}:#{info.GetStartColumn() + 1}"
51
123
  else
52
- raise V8::Error.new(exception.Get("message").to_ruby, value)
124
+ exception.Get("message").to_ruby
53
125
  end
126
+ elsif exception.IsObject()
127
+ value['message'] || value.to_s
54
128
  else
55
- raise V8::Error.new(exception.ToString().to_ruby, value)
129
+ value.to_s
56
130
  end
131
+ V8::Error.new(message, value, javascript_backtrace, cause)
57
132
  end
58
133
  const_set :JSError, Error
59
134
  end
data/lib/v8/stack.rb ADDED
@@ -0,0 +1,85 @@
1
+
2
+ module V8
3
+
4
+ class StackTrace
5
+ include Enumerable
6
+
7
+ def initialize(native)
8
+ @context = V8::Context.current
9
+ @native = native
10
+ end
11
+
12
+ def length
13
+ @context.enter do
14
+ @native ? @native.GetFrameCount() : 0
15
+ end
16
+ end
17
+
18
+ def each
19
+ return unless @native
20
+ @context.enter do
21
+ for i in 0..length - 1
22
+ yield V8::StackFrame.new(@native.GetFrame(i), @context)
23
+ end
24
+ end
25
+ end
26
+
27
+ def to_s
28
+ @native ? map(&:to_s).join("\n") : ""
29
+ end
30
+ end
31
+
32
+ class StackFrame
33
+
34
+ def initialize(native, context)
35
+ @context = context
36
+ @native = native
37
+ end
38
+
39
+ def script_name
40
+ @context.enter do
41
+ @context.to_ruby(@native.GetScriptName())
42
+ end
43
+ end
44
+
45
+ def function_name
46
+ @context.enter do
47
+ @context.to_ruby(@native.GetFunctionName())
48
+ end
49
+ end
50
+
51
+ def line_number
52
+ @context.enter do
53
+ @native.GetLineNumber()
54
+ end
55
+ end
56
+
57
+ def column
58
+ @context.enter do
59
+ @native.GetColumn()
60
+ end
61
+ end
62
+
63
+ def eval?
64
+ @context.enter do
65
+ @native.IsEval()
66
+ end
67
+ end
68
+
69
+ def constructor?
70
+ @context.enter do
71
+ @native.IsConstructor()
72
+ end
73
+ end
74
+
75
+ def to_s
76
+ @context.enter do
77
+ "at " + if !function_name.empty?
78
+ "#{function_name} (#{script_name}:#{line_number}:#{column})"
79
+ else
80
+ "#{script_name}:#{line_number}:#{column}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
data/lib/v8/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module V8
2
- VERSION = "0.11.0beta7"
2
+ VERSION = "0.11.0beta8"
3
3
  end
data/lib/v8/weak.rb ADDED
@@ -0,0 +1,70 @@
1
+ module V8
2
+ module Weak
3
+ # weak refs are broken on MRI 1.9 and merely slow on 1.8
4
+ # so we mitigate this by using the 'ref' gem. However, this
5
+ # only mitigates the problem. Under heavy load, you will still
6
+ # get different or terminated objects being returned. bad stuff
7
+ #
8
+ # for other platforms we just use the stdlib 'weakref'
9
+ # implementation
10
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
11
+ require 'ref'
12
+ Ref = ::Ref::WeakReference
13
+ WeakValueMap = ::Ref::WeakValueMap
14
+ else
15
+ require 'weakref'
16
+ class Ref
17
+ def initialize(object)
18
+ @ref = ::WeakRef.new(object)
19
+ end
20
+ def object
21
+ @ref.__getobj__
22
+ rescue ::WeakRef::RefError
23
+ nil
24
+ end
25
+ end
26
+
27
+ class WeakValueMap
28
+ def initialize
29
+ @values = {}
30
+ end
31
+
32
+ def [](key)
33
+ if ref = @values[key]
34
+ ref.object
35
+ end
36
+ end
37
+
38
+ def []=(key, value)
39
+ @values[key] = V8::Weak::Ref.new(value)
40
+ end
41
+ end
42
+ end
43
+
44
+ module Cell
45
+ def weakcell(name, &block)
46
+ unless storage = instance_variable_get("@#{name}")
47
+ storage = instance_variable_set("@#{name}", Storage.new)
48
+ end
49
+ storage.access(&block)
50
+ end
51
+ class Storage
52
+ def access(&block)
53
+ if @ref
54
+ @ref.object || populate(block)
55
+ else
56
+ populate(block)
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def populate(block)
63
+ occupant = block.call()
64
+ @ref = V8::Weak::Ref.new(occupant)
65
+ return occupant
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,21 +1,165 @@
1
1
  require 'spec_helper'
2
2
 
3
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
4
+
3
5
  describe V8::Error do
4
- it "uses the same ruby exception through multiple language boundaries" do
5
- V8::Context.new do |cxt|
6
- error = StandardError.new('potato')
7
- cxt['one'] = lambda do
8
- cxt.eval('two()', 'one.js')
6
+
7
+ before do
8
+ @cxt = V8::Context.new
9
+ @cxt['one'] = lambda do
10
+ @cxt.eval('two()', 'one.js')
11
+ end
12
+ @cxt['two'] = lambda do
13
+ @cxt.eval('three()', 'two.js')
14
+ end
15
+ end
16
+
17
+ it "captures a message without over nesting when the error is an error" do
18
+ throw! do |e|
19
+ e.message.should == "BOOM!"
20
+ end
21
+ end
22
+
23
+ it "captures the js message without over nesting when the error is a normal object" do
24
+ throw!('{foo: "bar"}') do |e|
25
+ e.message.should == "[object Object]"
26
+ end
27
+ throw!('{message: "bar"}') do |e|
28
+ e.message.should == "bar"
29
+ end
30
+ end
31
+
32
+ it "captures a thrown value as the message" do
33
+ throw!('"BOOM!"') do |e|
34
+ e.message.should == "BOOM!"
35
+ end
36
+ throw!('6') do |e|
37
+ e.message.should == '6'
38
+ end
39
+ end
40
+
41
+ it "has a reference to the root javascript cause" do
42
+ throw!('"I am a String"') do |e|
43
+ e.should_not be_in_ruby
44
+ e.should be_in_javascript
45
+ e.value['message'].should == "I am a String"
46
+ end
47
+ end
48
+
49
+ it "has a reference to the root ruby cause if one exists" do
50
+ StandardError.new("BOOM!").tap do |bomb|
51
+ @cxt['boom'] = lambda do
52
+ raise bomb
9
53
  end
10
- cxt['two'] = lambda do
11
- cxt.eval('three()', 'two.js')
54
+ lambda {
55
+ @cxt.eval('boom()', 'boom.js')
56
+ }.should(raise_error do |raised|
57
+ raised.should be_in_ruby
58
+ raised.should_not be_in_javascript
59
+ raised.root_cause.should be(bomb)
60
+ end)
61
+ end
62
+ end
63
+
64
+ describe "backtrace" do
65
+ it "is mixed with ruby and javascript" do
66
+ throw! do |e|
67
+ e.backtrace.first.should == "at three.js:1:7"
68
+ e.backtrace[1].should =~ /error_spec.rb/
69
+ e.backtrace[2].should == "at two.js:1:1"
70
+ e.backtrace[3].should =~ /error_spec.rb/
71
+ e.backtrace[4].should == "at one.js:1:1"
12
72
  end
13
- cxt['three'] = lambda do
14
- raise error
73
+ end
74
+
75
+ it "can be set to show only ruby frames" do
76
+ throw! do |e|
77
+ e.backtrace(:ruby).each do |frame|
78
+ frame.should =~ /(\.rb|):\d+/
79
+ end
80
+ end
81
+ end
82
+
83
+ it "can be set to show only javascript frames" do
84
+ throw! do |e|
85
+ e.backtrace(:javascript).each do |frame|
86
+ frame.should =~ /\.js:\d:\d/
87
+ end
88
+ end
89
+ end
90
+
91
+ it "includes a mystery marker when the original frame is unavailable because what got thrown wasn't an error" do
92
+ throw!("6") do |e|
93
+ e.backtrace.first.should == 'at three.js:1:1'
94
+ end
95
+ end
96
+
97
+ it "has a source name and line number when there is a javascript SyntaxError" do
98
+ lambda do
99
+ @cxt.eval(<<-INVALID, 'source.js')
100
+ "this line is okay";
101
+ "this line has a syntax error because it ends with a colon":
102
+ "this line is also okay";
103
+ "how do I find out that line 2 has the syntax error?";
104
+ INVALID
105
+ end.should raise_error(V8::JSError) {|error|
106
+ error.message.should eql 'Unexpected token : at source.js:2:61'
107
+ }
108
+ end
109
+
110
+ it "can start with ruby at the bottom" do
111
+ @cxt['boom'] = lambda do
112
+ raise StandardError, "Bif!"
15
113
  end
16
114
  lambda {
17
- cxt.eval('one()')
18
- }.should raise_error {|e| e.should be error}
115
+ @cxt.eval('boom()', "boom.js")
116
+ }.should(raise_error {|e|
117
+ e.backtrace.first.should =~ /error_spec\.rb/
118
+ e.backtrace[1].should =~ /boom.js/
119
+ })
120
+ end
121
+ end
122
+
123
+
124
+ def throw!(js = "new Error('BOOM!')", &block)
125
+ @cxt['three'] = lambda do
126
+ @cxt.eval("throw #{js}", 'three.js')
19
127
  end
128
+ lambda do
129
+ @cxt['one'].call()
130
+ end.should(raise_error(V8::JSError, &block))
20
131
  end
21
132
  end
133
+
134
+
135
+ # describe V8::Error do
136
+ # describe "A ruby exception thrown inside JavaScript" do
137
+ # before do
138
+ # @error = StandardError.new('potato')
139
+ # begin
140
+ # V8::Context.new do |cxt|
141
+ # cxt['one'] = lambda do
142
+ # cxt.eval('two()', 'one.js')
143
+ # end
144
+ # cxt['two'] = lambda do
145
+ # cxt.eval('three()', 'two.js')
146
+ # end
147
+ # cxt['three'] = lambda do
148
+ # raise @error
149
+ # end
150
+ # cxt.eval('one()')
151
+ # end
152
+ # rescue StandardError => e
153
+ # @thrown = e
154
+ # end
155
+ # end
156
+ # it "is raised up through the call stack" do
157
+ # @thrown.should be(@error)
158
+ # end
159
+ #
160
+ # it "shows both the javascript and the ruby callframes" do
161
+ # puts @error.backtrace.join('<br/>')
162
+ # end
163
+ #
164
+ # end
165
+ # end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: therubyracer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0beta7
4
+ version: 0.11.0beta8
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-09 00:00:00.000000000 Z
12
+ date: 2012-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ref
@@ -100,8 +100,9 @@ files:
100
100
  - lib/v8/error.rb
101
101
  - lib/v8/function.rb
102
102
  - lib/v8/object.rb
103
- - lib/v8/util/weakcell.rb
103
+ - lib/v8/stack.rb
104
104
  - lib/v8/version.rb
105
+ - lib/v8/weak.rb
105
106
  - spec/c/array_spec.rb
106
107
  - spec/c/constants_spec.rb
107
108
  - spec/c/exception_spec.rb
@@ -140,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
141
  version: '0'
141
142
  segments:
142
143
  - 0
143
- hash: -2479943800836070551
144
+ hash: -2118637317038769164
144
145
  required_rubygems_version: !ruby/object:Gem::Requirement
145
146
  none: false
146
147
  requirements:
@@ -1,29 +0,0 @@
1
- module V8
2
- module Util
3
- module Weakcell
4
- def weakcell(name, &block)
5
- unless storage = instance_variable_get("@#{name}")
6
- storage = instance_variable_set("@#{name}", Storage.new)
7
- end
8
- storage.access(&block)
9
- end
10
- class Storage
11
- def access(&block)
12
- if @ref
13
- @ref.object || populate(block)
14
- else
15
- populate(block)
16
- end
17
- end
18
-
19
- private
20
-
21
- def populate(block)
22
- occupant = block.call()
23
- @ref = Ref::WeakReference.new(occupant)
24
- return occupant
25
- end
26
- end
27
- end
28
- end
29
- end