1
0
Fork 0
mirror of https://github.com/rubyjs/therubyracer synced 2023-03-27 23:21:42 -04:00

implement exception handling.

This commit is contained in:
Charles Lowell 2012-06-14 22:34:38 -05:00
parent 9725e7b86d
commit be963bbc95
19 changed files with 256 additions and 76 deletions

38
ext/v8/exception.cc Normal file
View 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)));
}
}

View file

@ -30,6 +30,7 @@ extern "C" {
Stack::Init();
Message::Init();
TryCatch::Init();
Exception::Init();
Locker::Init();
ResourceConstraints::Init();
HeapStatistics::Init();

View file

@ -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"));

View file

@ -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();

View file

@ -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

View file

@ -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'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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
View 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
View 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

View file

@ -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
View 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

View file

@ -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>")

View file

@ -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

View file

@ -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
View 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