From c9b43ff46c2e8c255cef34c32cce942db8456518 Mon Sep 17 00:00:00 2001
From: Charles Lowell <cowboyd@frontside.io>
Date: Wed, 12 Aug 2015 20:18:55 +0300
Subject: [PATCH] Throw JavaScript exceptions from Ruby

---
 ext/v8/exception.h       | 50 ++++++++++++++++++++++++++++++++++++++++
 ext/v8/init.cc           |  2 +-
 ext/v8/isolate.cc        |  8 ++++++-
 ext/v8/isolate.h         |  1 +
 ext/v8/rr.h              |  1 +
 spec/c/try_catch_spec.rb | 10 +++++++-
 6 files changed, 69 insertions(+), 3 deletions(-)
 create mode 100644 ext/v8/exception.h

diff --git a/ext/v8/exception.h b/ext/v8/exception.h
new file mode 100644
index 0000000..37bc602
--- /dev/null
+++ b/ext/v8/exception.h
@@ -0,0 +1,50 @@
+// -*- mode: c++ -*-
+#ifndef RR_EXCEPTION_H
+#define RR_EXCEPTION_H
+#include "rr.h"
+
+namespace rr {
+  class Exception {
+  public:
+    static inline void Init() {
+      ClassBuilder("Exception").
+        defineSingletonMethod("RangeError", &RangeError).
+        defineSingletonMethod("ReferenceError", &ReferenceError).
+        defineSingletonMethod("SyntaxError", &SyntaxError).
+        defineSingletonMethod("TypeError", &TypeError).
+        defineSingletonMethod("Error", &Error);
+    }
+
+    static VALUE RangeError(VALUE self, VALUE rb_message) {
+      String message(rb_message);
+      Locker lock(message);
+      return Value(message, v8::Exception::RangeError(message));
+    }
+
+    static VALUE ReferenceError(VALUE self, VALUE rb_message) {
+      String message(rb_message);
+      Locker lock(message);
+      return Value(message, v8::Exception::ReferenceError(message));
+    }
+
+    static VALUE SyntaxError(VALUE self, VALUE rb_message) {
+      String message(rb_message);
+      Locker lock(message);
+      return Value(message, v8::Exception::SyntaxError(message));
+    }
+
+    static VALUE TypeError(VALUE self, VALUE rb_message) {
+      String message(rb_message);
+      Locker lock(message);
+      return Value(message, v8::Exception::TypeError(message));
+    }
+
+    static VALUE Error(VALUE self, VALUE rb_message) {
+      String message(rb_message);
+      Locker lock(message);
+      return Value(message, v8::Exception::Error(message));
+    }
+  };
+}
+
+#endif /* RR_EXCEPTION_H */
diff --git a/ext/v8/init.cc b/ext/v8/init.cc
index 41c97d9..0a0a9a2 100644
--- a/ext/v8/init.cc
+++ b/ext/v8/init.cc
@@ -46,12 +46,12 @@ extern "C" {
     StackFrame::Init();
     StackTrace::Init();
     Message::Init();
+    Exception::Init();
     TryCatch::Init();
 
     // Invocation::Init();
     // Constants::Init();
     // Template::Init();
-    // Exception::Init();
     // ResourceConstraints::Init();
     // HeapStatistics::Init();
   }
diff --git a/ext/v8/isolate.cc b/ext/v8/isolate.cc
index 2a63e21..97808d5 100644
--- a/ext/v8/isolate.cc
+++ b/ext/v8/isolate.cc
@@ -9,7 +9,7 @@ namespace rr {
     rb_eval_string("require 'v8/retained_objects'");
     ClassBuilder("Isolate").
       defineSingletonMethod("New", &New).
-
+      defineMethod("ThrowException", &ThrowException).
       defineMethod("SetCaptureStackTraceForUncaughtExceptions", &SetCaptureStackTraceForUncaughtExceptions).
       defineMethod("IdleNotificationDeadline", &IdleNotificationDeadline).
 
@@ -33,6 +33,12 @@ namespace rr {
     return Isolate(isolate);
   }
 
+  VALUE Isolate::ThrowException(VALUE self, VALUE error) {
+    Isolate isolate(self);
+    Locker lock(isolate);
+    return Value(isolate, isolate->ThrowException(Value(error)));
+  }
+
   VALUE Isolate::SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options) {
     Isolate isolate(self);
     Locker lock(isolate);
diff --git a/ext/v8/isolate.h b/ext/v8/isolate.h
index 0837bdb..9899a86 100644
--- a/ext/v8/isolate.h
+++ b/ext/v8/isolate.h
@@ -31,6 +31,7 @@ namespace rr {
 
     static VALUE New(VALUE self);
     static VALUE SetCaptureStackTraceForUncaughtExceptions(VALUE self, VALUE capture, VALUE stack_limit, VALUE options);
+    static VALUE ThrowException(VALUE self, VALUE error);
 
     inline Isolate(IsolateData* data_) : data(data_) {}
     inline Isolate(v8::Isolate* isolate) :
diff --git a/ext/v8/rr.h b/ext/v8/rr.h
index 0d1d8c6..37a5e4d 100644
--- a/ext/v8/rr.h
+++ b/ext/v8/rr.h
@@ -72,6 +72,7 @@ inline VALUE not_implemented(const char* message) {
 #include "stack-frame.h"
 #include "stack-trace.h"
 #include "message.h"
+#include "exception.h"
 #include "try-catch.h"
 
 #endif
diff --git a/spec/c/try_catch_spec.rb b/spec/c/try_catch_spec.rb
index cab4cca..d33ccf0 100644
--- a/spec/c/try_catch_spec.rb
+++ b/spec/c/try_catch_spec.rb
@@ -60,11 +60,19 @@ describe V8::C::TryCatch do
     end
   end
 
+  it "sees JavaScript exceptions thrown from Ruby" do
+    V8::C::TryCatch(@isolate) do |trycatch|
+      message = V8::C::String::NewFromUtf8(@isolate, "boom!")
+      @isolate.ThrowException(V8::C::Exception::Error(message))
+      expect(trycatch.HasCaught).to be_truthy
+    end
+  end
+
   it "won't die on a ruby exception" do
     expect {
       V8::C::TryCatch(@isolate) do |trycatch|
         fail "boom!"
       end
-    }.to raise_error
+    }.to raise_error RuntimeError, "boom!"
   end
 end