mirror of
https://github.com/rubyjs/therubyracer
synced 2023-03-27 23:21:42 -04:00
implement exception handling.
This commit is contained in:
parent
9725e7b86d
commit
be963bbc95
19 changed files with 256 additions and 76 deletions
38
ext/v8/exception.cc
Normal file
38
ext/v8/exception.cc
Normal file
|
@ -0,0 +1,38 @@
|
|||
#include "rr.h"
|
||||
|
||||
namespace rr {
|
||||
void Exception::Init() {
|
||||
ModuleBuilder("V8::C").
|
||||
defineSingletonMethod("ThrowException", &ThrowException);
|
||||
ClassBuilder("Exception").
|
||||
defineSingletonMethod("RangeError", &RangeError).
|
||||
defineSingletonMethod("ReferenceError", &ReferenceError).
|
||||
defineSingletonMethod("SyntaxError", &SyntaxError).
|
||||
defineSingletonMethod("TypeError", &TypeError).
|
||||
defineSingletonMethod("Error", &Error);
|
||||
}
|
||||
|
||||
VALUE Exception::ThrowException(VALUE self, VALUE exception) {
|
||||
return Value(v8::ThrowException(Value(exception)));
|
||||
}
|
||||
|
||||
VALUE Exception::RangeError(VALUE self, VALUE message) {
|
||||
return Value(v8::Exception::RangeError(String(message)));
|
||||
}
|
||||
|
||||
VALUE Exception::ReferenceError(VALUE self, VALUE message) {
|
||||
return Value(v8::Exception::ReferenceError(String(message)));
|
||||
}
|
||||
|
||||
VALUE Exception::SyntaxError(VALUE self, VALUE message) {
|
||||
return Value(v8::Exception::SyntaxError(String(message)));
|
||||
}
|
||||
|
||||
VALUE Exception::TypeError(VALUE self, VALUE message) {
|
||||
return Value(v8::Exception::TypeError(String(message)));
|
||||
}
|
||||
|
||||
VALUE Exception::Error(VALUE self, VALUE message) {
|
||||
return Value(v8::Exception::Error(String(message)));
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ extern "C" {
|
|||
Stack::Init();
|
||||
Message::Init();
|
||||
TryCatch::Init();
|
||||
Exception::Init();
|
||||
Locker::Init();
|
||||
ResourceConstraints::Init();
|
||||
HeapStatistics::Init();
|
||||
|
|
|
@ -132,6 +132,9 @@ VALUE Object::SetAccessor(int argc, VALUE* argv, VALUE self) {
|
|||
}
|
||||
|
||||
Object::operator VALUE() {
|
||||
if (handle.IsEmpty()) {
|
||||
return Qnil;
|
||||
}
|
||||
VALUE value;
|
||||
Backref* backref;
|
||||
v8::Local<v8::String> key(v8::String::NewSymbol("rr::Backref"));
|
||||
|
|
12
ext/v8/rr.h
12
ext/v8/rr.h
|
@ -359,6 +359,7 @@ public:
|
|||
|
||||
inline String(VALUE value) : Ref<v8::String>(value) {}
|
||||
inline String(v8::Handle<v8::String> string) : Ref<v8::String>(string) {}
|
||||
virtual operator v8::Handle<v8::String>() const;
|
||||
};
|
||||
|
||||
class PropertyAttribute: public Enum<v8::PropertyAttribute> {
|
||||
|
@ -763,6 +764,17 @@ public:
|
|||
inline ResourceConstraints(VALUE value) : Pointer<v8::ResourceConstraints>(value) {}
|
||||
};
|
||||
|
||||
class Exception {
|
||||
public:
|
||||
static void Init();
|
||||
static VALUE ThrowException(VALUE self, VALUE exception);
|
||||
static VALUE RangeError(VALUE self, VALUE message);
|
||||
static VALUE ReferenceError(VALUE self, VALUE message);
|
||||
static VALUE SyntaxError(VALUE self, VALUE message);
|
||||
static VALUE TypeError(VALUE self, VALUE message);
|
||||
static VALUE Error(VALUE self, VALUE message);
|
||||
};
|
||||
|
||||
class Constants {
|
||||
public:
|
||||
static void Init();
|
||||
|
|
|
@ -32,4 +32,16 @@ VALUE String::Concat(VALUE self, VALUE left, VALUE right) {
|
|||
return String(v8::String::Concat(String(left), String(right)));
|
||||
}
|
||||
|
||||
String::operator v8::Handle<v8::String>() const {
|
||||
switch (TYPE(value)) {
|
||||
case T_STRING:
|
||||
return v8::String::New(RSTRING_PTR(value), (int)RSTRING_LEN(value));
|
||||
case T_DATA:
|
||||
return Ref<v8::String>::operator v8::Handle<v8::String>();
|
||||
default:
|
||||
VALUE string = rb_funcall(value, rb_intern("to_s"), 0);
|
||||
return v8::String::New(RSTRING_PTR(string), (int)RSTRING_LEN(string));
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace rr
|
|
@ -2,6 +2,9 @@ require "v8/version"
|
|||
|
||||
require 'v8/init'
|
||||
require 'v8/util/weakcell'
|
||||
require 'v8/error'
|
||||
require 'v8/error/protect'
|
||||
require 'v8/error/try'
|
||||
require 'v8/conversion/fundamental'
|
||||
require 'v8/conversion/indentity'
|
||||
require 'v8/conversion/primitive'
|
||||
|
@ -21,4 +24,4 @@ require 'v8/access'
|
|||
require 'v8/context'
|
||||
require 'v8/object'
|
||||
require 'v8/array'
|
||||
require 'v8/function'
|
||||
require 'v8/function'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
require 'stringio'
|
||||
module V8
|
||||
class Context
|
||||
include V8::Error::Try
|
||||
attr_reader :native, :conversion, :access
|
||||
|
||||
def initialize(options = {})
|
||||
|
@ -91,22 +92,10 @@ module V8
|
|||
source = source.read
|
||||
end
|
||||
enter do
|
||||
V8::C::TryCatch() do |trycatch|
|
||||
source = V8::C::String::New(source.to_s)
|
||||
filename = V8::C::String::New(filename.to_s)
|
||||
script = V8::C::Script::New(source, filename)
|
||||
result = script.Run()
|
||||
if trycatch.HasCaught()
|
||||
exception = trycatch.Exception()
|
||||
if exception.IsNativeError()
|
||||
raise to_ruby exception.Get("message")
|
||||
else
|
||||
raise to_ruby exception.ToString()
|
||||
end
|
||||
else
|
||||
to_ruby result
|
||||
end
|
||||
end
|
||||
source = V8::C::String::New(source.to_s)
|
||||
filename = V8::C::String::New(filename.to_s)
|
||||
script = try { V8::C::Script::New(source, filename) }
|
||||
to_ruby try {script.Run()}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
class V8::Conversion
|
||||
module Class
|
||||
include V8::Util::Weakcell
|
||||
|
||||
def to_v8
|
||||
v8_constructor.GetFunction()
|
||||
weakcell(:v8_constructor) do
|
||||
V8::C::FunctionTemplate::New()
|
||||
end.GetFunction()
|
||||
end
|
||||
|
||||
def v8_constructor
|
||||
unless @v8_constructor && @v8_constructor.weakref_alive?
|
||||
template = V8::C::FunctionTemplate::New()
|
||||
# template.SetCallHandler(InvocationHandler.new, V8::C::External::New(self))
|
||||
@v8_constructor = WeakRef.new(template)
|
||||
end
|
||||
return @v8_constructor.__getobj__
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -23,60 +23,64 @@ class V8::Conversion
|
|||
end
|
||||
|
||||
class Get
|
||||
extend V8::Error::Protect
|
||||
def self.call(property, info)
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
name = property.Utf8Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
protect do
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
name = property.Utf8Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
end
|
||||
context.to_v8 access.get(object, name, &dontintercept)
|
||||
end
|
||||
context.to_v8 access.get(object, name, &dontintercept)
|
||||
rescue Exception => e
|
||||
warn "uncaught exception: #{e.class}: #{e.message} while accessing object property: #{e.backtrace.join('\n')}"
|
||||
end
|
||||
end
|
||||
|
||||
class Set
|
||||
extend V8::Error::Protect
|
||||
def self.call(property, value, info)
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
name = property.Utf8Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
protect do
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
name = property.Utf8Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
end
|
||||
access.set(object, name, context.to_ruby(value), &dontintercept)
|
||||
end
|
||||
access.set(object, name, context.to_ruby(value), &dontintercept)
|
||||
rescue Exception => e
|
||||
warn "uncaught exception: #{e.class}: #{e.message} while accessing object property: #{e.backtrace.join('\n')}"
|
||||
end
|
||||
end
|
||||
|
||||
class IGet
|
||||
extend V8::Error::Protect
|
||||
def self.call(index, info)
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
protect do
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
end
|
||||
context.to_v8 access.iget(object, index, &dontintercept)
|
||||
end
|
||||
context.to_v8 access.iget(object, index, &dontintercept)
|
||||
rescue Exception => e
|
||||
warn "uncaught exception: #{e.class}: #{e.message} while accessing object property: #{e.backtrace.join('\n')}"
|
||||
end
|
||||
end
|
||||
|
||||
class ISet
|
||||
extend V8::Error::Protect
|
||||
def self.call(index, value, info)
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
protect do
|
||||
context = V8::Context.current
|
||||
access = context.access
|
||||
object = info.Data().Value()
|
||||
dontintercept = proc do
|
||||
return V8::C::Value::Empty
|
||||
end
|
||||
access.iset(object, index, context.to_ruby(value), &dontintercept)
|
||||
end
|
||||
access.iset(object, index, context.to_ruby(value), &dontintercept)
|
||||
rescue Exception => e
|
||||
warn "uncaught exception: #{e.class}: #{e.message} while accessing object property: #{e.backtrace.join('\n')}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,20 +17,20 @@ class V8::Conversion
|
|||
end
|
||||
|
||||
class InvocationHandler
|
||||
include V8::Error::Protect
|
||||
def call(arguments)
|
||||
context = V8::Context.current
|
||||
proc = arguments.Data().Value()
|
||||
length_of_given_args = arguments.Length()
|
||||
args = ::Array.new(proc.arity < 0 ? length_of_given_args : proc.arity)
|
||||
0.upto(args.length - 1) do |i|
|
||||
if i < length_of_given_args
|
||||
args[i] = context.to_ruby arguments[i]
|
||||
protect do
|
||||
context = V8::Context.current
|
||||
proc = arguments.Data().Value()
|
||||
length_of_given_args = arguments.Length()
|
||||
args = ::Array.new(proc.arity < 0 ? length_of_given_args : proc.arity)
|
||||
0.upto(args.length - 1) do |i|
|
||||
if i < length_of_given_args
|
||||
args[i] = context.to_ruby arguments[i]
|
||||
end
|
||||
end
|
||||
context.to_v8 proc.call(*args)
|
||||
end
|
||||
context.to_v8 proc.call(*args)
|
||||
rescue Exception => e
|
||||
warn "unhandled exception in ruby #{e.class}: #{e.message}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
24
lib/v8/error.rb
Normal file
24
lib/v8/error.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module V8
|
||||
class Error < StandardError
|
||||
attr_reader :value
|
||||
def initialize(message, value)
|
||||
super(message)
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
|
||||
def self.Error(exception)
|
||||
value = exception.to_ruby
|
||||
if !exception.kind_of?(V8::C::Value)
|
||||
raise V8::Error.new(exception.to_s, value)
|
||||
elsif exception.IsNativeError()
|
||||
if football = exception.GetHiddenValue("rr::Football")
|
||||
raise football.Value()
|
||||
else
|
||||
raise V8::Error.new(exception.Get("message").to_ruby, value)
|
||||
end
|
||||
else
|
||||
raise V8::Error.new(exception.ToString().to_ruby, value)
|
||||
end
|
||||
end
|
||||
end
|
20
lib/v8/error/protect.rb
Normal file
20
lib/v8/error/protect.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
class V8::Error
|
||||
module Protect
|
||||
def protect
|
||||
yield
|
||||
rescue Football => e
|
||||
e.kickoff!
|
||||
rescue Exception => e
|
||||
e.extend Football
|
||||
e.kickoff!
|
||||
end
|
||||
end
|
||||
|
||||
module Football
|
||||
def kickoff!
|
||||
error = V8::C::Exception::Error(message)
|
||||
error.SetHiddenValue("rr::Football", V8::C::External::New(self))
|
||||
V8::C::ThrowException(error)
|
||||
end
|
||||
end
|
||||
end
|
15
lib/v8/error/try.rb
Normal file
15
lib/v8/error/try.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
class V8::Error
|
||||
module Try
|
||||
def try
|
||||
context = V8::Context.current
|
||||
V8::C::TryCatch() do |trycatch|
|
||||
result = yield
|
||||
if trycatch.HasCaught()
|
||||
V8::Error(trycatch.Exception())
|
||||
else
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,5 @@
|
|||
class V8::Function < V8::Object
|
||||
include V8::Error::Try
|
||||
def initialize(native = nil)
|
||||
super do
|
||||
native || V8::C::FunctionTemplate::New().GetFunction()
|
||||
|
@ -7,7 +8,7 @@ class V8::Function < V8::Object
|
|||
|
||||
def methodcall(this, *args)
|
||||
@context.enter do
|
||||
@context.to_ruby native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a})
|
||||
@context.to_ruby try {native.Call(@context.to_v8(this), args.map {|a| @context.to_v8 a})}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -19,7 +20,7 @@ class V8::Function < V8::Object
|
|||
|
||||
def new(*args)
|
||||
@context.enter do
|
||||
@context.to_ruby native.NewInstance(args.map {|a| @context.to_v8 a})
|
||||
@context.to_ruby try {native.NewInstance(args.map {|a| @context.to_v8 a})}
|
||||
end
|
||||
end
|
||||
end
|
26
spec/c/exception_spec.rb
Normal file
26
spec/c/exception_spec.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe V8::C::Exception do
|
||||
it "can be thrown from Ruby" do
|
||||
t = V8::C::FunctionTemplate::New(method(:explode))
|
||||
@cxt.Global().Set("explode", t.GetFunction())
|
||||
script = V8::C::Script::New(<<-JS, '<eval>')
|
||||
(function() {
|
||||
try {
|
||||
explode()
|
||||
} catch (e) {
|
||||
return e.message
|
||||
}
|
||||
})()
|
||||
JS
|
||||
result = script.Run()
|
||||
result.should_not be_nil
|
||||
result.should be_kind_of(V8::C::String)
|
||||
result.Utf8Value().should eql 'did not pay syntax'
|
||||
end
|
||||
|
||||
def explode(arguments)
|
||||
error = V8::C::Exception::SyntaxError('did not pay syntax')
|
||||
V8::C::ThrowException(error)
|
||||
end
|
||||
end
|
|
@ -27,6 +27,15 @@ describe V8::C::Function do
|
|||
object.Get(V8::C::String::New('foo')).Utf8Value().should eql "bar"
|
||||
end
|
||||
|
||||
it "doesn't kill the world if invoking it throws a javascript exception" do
|
||||
V8::C::TryCatch() do
|
||||
fn = run '(function() { throw new Error("boom!")})'
|
||||
fn.Call(@cxt.Global(), [])
|
||||
fn.NewInstance([])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def run(source)
|
||||
source = V8::C::String::New(source.to_s)
|
||||
filename = V8::C::String::New("<eval>")
|
||||
|
|
|
@ -5,4 +5,12 @@ describe V8::C::String do
|
|||
string = V8::C::String::New("\u{100000}")
|
||||
string.Utf8Value().should eql "\u{100000}"
|
||||
end
|
||||
|
||||
it "can naturally translate ruby strings into v8 strings" do
|
||||
V8::C::String::Concat(V8::C::String::New("Hello "), "World").Utf8Value().should eql "Hello World"
|
||||
end
|
||||
|
||||
it "can naturally translate ruby objects into v8 strings" do
|
||||
V8::C::String::Concat(V8::C::String::New("forty two is "), 42).Utf8Value().should eql "forty two is 42"
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@ require 'spec_helper'
|
|||
require 'redjs/load_specs'
|
||||
module RedJS
|
||||
Context = V8::Context
|
||||
#Error = V8::JSError
|
||||
Error = V8::Error
|
||||
end
|
||||
describe V8::Context do
|
||||
pending "not ready for prime-time"
|
||||
|
|
21
spec/v8/error_spec.rb
Normal file
21
spec/v8/error_spec.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe V8::Error do
|
||||
it "uses the same ruby exception through multiple language boundaries" do
|
||||
V8::Context.new do |cxt|
|
||||
error = StandardError.new('potato')
|
||||
cxt['one'] = lambda do
|
||||
cxt.eval('two()', 'one.js')
|
||||
end
|
||||
cxt['two'] = lambda do
|
||||
cxt.eval('three()', 'two.js')
|
||||
end
|
||||
cxt['three'] = lambda do
|
||||
raise error
|
||||
end
|
||||
lambda {
|
||||
cxt.eval('three()')
|
||||
}.should raise_error {|e| e.should be error}
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue