h8 0.0.2 → 0.0.4

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.
@@ -1,16 +1,108 @@
1
+ #include <thread>
2
+ #include <mutex>
3
+ #include <condition_variable>
4
+ #include <chrono>
5
+
1
6
  #include "h8.h"
7
+ #include "ruby_gate.h"
8
+
9
+ void h8::JsError::raise() {
10
+ if (has_js_exception) {
11
+ VALUE ruby_exception;
12
+ {
13
+ // raising exception does longjump so we should keep all memory
14
+ // allocation done before:
15
+ H8::Scope scope(h8);
16
+
17
+ Local<Object> jsx = exception().As<Object>();
18
+ Local<Value> source = jsx->Get(h8->js("source"));
19
+ RubyGate *rg = RubyGate::unwrap(source.As<Object>());
20
+ if (rg) {
21
+ // Passing thru the Ruby exception
22
+ ruby_exception = rg->rubyObject();
23
+ } else {
24
+ Local<String> s = message()->Get();
25
+ String::Utf8Value res(s->ToString());
26
+ ruby_exception = ruby_exception = rb_exc_new2(js_exception,
27
+ *res ? *res : "test");
28
+ rb_iv_set(ruby_exception, "@message", h8->to_ruby(s));
29
+ // TODO: Pass also all information from Message instance
30
+ rb_iv_set(ruby_exception, "@source", h8->to_ruby(source));
31
+ }
32
+ }
33
+ rb_exc_raise(ruby_exception);
34
+ // }
35
+ } else {
36
+ rb_raise(h8_exception, "%s", reason);
37
+ }
38
+ }
2
39
 
3
- Local<Value> h8::H8::gateObject(VALUE ruby_value) const {
40
+ void h8::JsTimeoutError::raise() {
41
+ rb_raise(js_timeout_exception, "timeout expired");
42
+ }
43
+
44
+ Local<Value> h8::H8::gateObject(VALUE ruby_value) {
4
45
  if ( Qtrue == rb_funcall(ruby_value, id_is_a, 1, value_class)) {
5
46
  JsGate *gate;
6
47
  Data_Get_Struct(ruby_value, JsGate, gate);
7
- if( gate->h8 != this ) {
8
- rb_raise(h8_exception, "H8::Value is bound to other H8::Context");
9
- return Undefined(isolate);
10
- }
11
- else
48
+ if (gate->h8 != this) {
49
+ throw JsError(this, "H8::Value is bound to other H8::Context");
50
+ } else
12
51
  return gate->value();
13
52
  }
14
- rb_raise(h8_exception, "Object gate is not implemented");
15
- return Undefined(isolate);
53
+ // Generic Ruby object
54
+ RubyGate *gate = new RubyGate(this, ruby_value);
55
+ return gate->handle(isolate);
56
+ }
57
+
58
+ void h8::H8::ruby_mark_gc() const {
59
+ for (chain::link *x : resources)
60
+ ((AllocatedResource*) x)->rb_mark_gc();
61
+ }
62
+
63
+ v8::Handle<v8::Value> h8::H8::eval(const char* script_utf,unsigned max_ms) {
64
+ v8::EscapableHandleScope escape(isolate);
65
+ Local<Value> result;
66
+
67
+ Handle<v8::String> script_source = String::NewFromUtf8(isolate, script_utf);
68
+ v8::Handle<v8::Script> script;
69
+ JsCatcher try_catch(this);
70
+ v8::ScriptOrigin origin(String::NewFromUtf8(isolate, "eval"));
71
+
72
+ script = v8::Script::Compile(script_source, &origin);
73
+
74
+ if (script.IsEmpty()) {
75
+ try_catch.throwIfCaught();
76
+ result = Undefined(isolate);
77
+ } else {
78
+ result = Undefined(isolate);
79
+ if( max_ms > 0 ) {
80
+ std::mutex m;
81
+ std::condition_variable cv;
82
+ std::thread thr( [&] {
83
+ std::unique_lock<std::mutex> lock(m);
84
+ if( std::cv_status::timeout == cv.wait_for(lock, std::chrono::milliseconds(max_ms) ) ) {
85
+ isolate->TerminateExecution();
86
+ }
87
+ });
88
+ script->Run();
89
+ cv.notify_all();
90
+ thr.join();
91
+ }
92
+ else {
93
+ result = script->Run();
94
+ }
95
+ try_catch.throwIfCaught();
96
+ }
97
+ return escape.Escape(result);
98
+ }
99
+
100
+ h8::H8::~H8() {
101
+ while (!resources.is_empty()) {
102
+ // this should also remove it from the list:
103
+ resources.peek_first<AllocatedResource>()->free();
104
+ }
105
+
106
+ persistent_context.Reset();
107
+ isolate->Dispose();
16
108
  }
@@ -4,24 +4,76 @@
4
4
  #include <include/v8.h>
5
5
  #include <ruby.h>
6
6
  #include <iostream>
7
+ #include <exception>
8
+ #include "allocated_resource.h"
9
+ #include "JsCatcher.h"
7
10
 
8
11
  using namespace v8;
9
12
  using namespace std;
10
13
 
11
- extern VALUE h8_exception;
12
- extern VALUE h8_class;
14
+ extern VALUE h8_exception, js_exception, js_timeout_exception;
15
+ extern VALUE context_class;
13
16
  extern VALUE value_class;
17
+ extern VALUE ruby_gate_class;
18
+ extern VALUE Rundefined;
14
19
 
15
20
  extern ID id_is_a;
16
-
17
- //#include <ruby/thread.h>
21
+ extern ID id_safe_call;
18
22
 
19
23
  namespace h8 {
20
24
 
25
+ /// Allocate ruby H8::Context class (wrapper for h8::H8), ruby utility function
26
+ /// shold be declared as friend
27
+ VALUE context_alloc(VALUE klass);
28
+
29
+ class RubyGate;
30
+ class H8;
31
+
21
32
  template<class T> inline void t(const T& x) {
22
33
  cout << x << endl << flush;
23
34
  }
24
35
 
36
+ /**
37
+ * The exception that is raised toward ruby code, e.g. when JS code throws uncaught exception,
38
+ * interpreter fails to cope with syntax, parameters are used in a wrong way, etc. Instead of calling
39
+ * rb_raise() which will longjump() over all your C++ code, throw instance of JsError.
40
+ */
41
+ class JsError: public std::exception {
42
+ public:
43
+ JsError(H8* h8, const char* str_reason) :
44
+ h8(h8), has_js_exception(false) {
45
+ reason = str_reason;
46
+ }
47
+
48
+ JsError(H8* h8, v8::Local<v8::Message> message, v8::Local<v8::Value> exception);
49
+
50
+ /**
51
+ * Call it with a proper exception class and be careful - after this call no code will be executed!
52
+ */
53
+ virtual void raise();
54
+
55
+ Local<Message> message() const;
56
+
57
+ Local<Value> exception() const;
58
+
59
+ virtual ~JsError() noexcept {
60
+ _message.Reset();
61
+ _exception.Reset();
62
+ }
63
+ protected:
64
+ const char* reason;
65
+ bool has_js_exception;
66
+ H8 *h8;
67
+ v8::Persistent<v8::Message, v8::CopyablePersistentTraits<v8::Message>> _message;
68
+ v8::Persistent<v8::Value, v8::CopyablePersistentTraits<v8::Value>> _exception;
69
+ };
70
+
71
+ class JsTimeoutError : public JsError {
72
+ public:
73
+ JsTimeoutError(H8* h8) : JsError(h8, NULL) {}
74
+ virtual void raise();
75
+ };
76
+
25
77
  class H8 {
26
78
  public:
27
79
 
@@ -29,6 +81,7 @@ public:
29
81
  v8::Isolate::Scope isolate_scope;
30
82
  v8::Context::Scope context_scope;
31
83
  H8* rcontext;
84
+
32
85
  public:
33
86
  Scope(H8* cxt) :
34
87
  HandleScope(cxt->getIsolate()), isolate_scope(
@@ -39,7 +92,8 @@ public:
39
92
 
40
93
  static void init();
41
94
 
42
- H8() {
95
+ H8() :
96
+ self(Qnil) {
43
97
  isolate = Isolate::New();
44
98
  Isolate::Scope isolate_scope(isolate);
45
99
  HandleScope handle_scope(isolate);
@@ -51,33 +105,19 @@ public:
51
105
  persistent_context.Reset(isolate, context);
52
106
  }
53
107
 
54
- Handle<Value> eval(const char* script_utf) {
55
- v8::EscapableHandleScope escape(isolate);
56
- Local<Value> result;
57
-
58
- Handle<v8::String> script_source = String::NewFromUtf8(isolate,
59
- script_utf);
60
- v8::Handle<v8::Script> script;
61
- v8::TryCatch try_catch;
62
- v8::ScriptOrigin origin(String::NewFromUtf8(isolate, "eval"));
63
-
64
- script = v8::Script::Compile(script_source, &origin);
65
-
66
- if (script.IsEmpty()) {
67
- report_exception(try_catch);
68
- result = Undefined(isolate);
69
- } else {
70
- result = script->Run();
71
- if (try_catch.HasCaught()) {
72
- report_exception(try_catch);
73
- }
74
- }
75
- return escape.Escape(result);
76
- }
77
-
78
- VALUE eval_to_ruby(const char* script_utf) {
108
+ /**
109
+ * Evaluate javascript.
110
+ *
111
+ * \param script_utf the null-terminated script string in utf8 endcoding
112
+ * \param max_ms if set, then script maximum execution time will be limited
113
+ * to this value, JsTimeoutError will be thrown if exceeded
114
+ * \return the value returned by the script.
115
+ */
116
+ Handle<Value> eval(const char* script_utf,unsigned max_ms=0);
117
+
118
+ VALUE eval_to_ruby(const char* script_utf,int timeout=0) {
79
119
  // TODO: throw ruby exception on error
80
- return to_ruby(eval(script_utf));
120
+ return to_ruby(eval(script_utf,timeout));
81
121
  }
82
122
 
83
123
  Handle<Context> getContext() {
@@ -107,7 +147,7 @@ public:
107
147
  getContext()->Global()->Set(js(name), to_js(value));
108
148
  }
109
149
 
110
- Local<Value> to_js(VALUE ruby_value) const {
150
+ Local<Value> to_js(VALUE ruby_value) {
111
151
  switch (TYPE(ruby_value)) {
112
152
  case T_STRING:
113
153
  return js(ruby_value);
@@ -115,35 +155,46 @@ public:
115
155
  return v8::Int32::New(isolate, FIX2INT(ruby_value));
116
156
  case T_FLOAT:
117
157
  return v8::Number::New(isolate, NUM2DBL(ruby_value));
158
+ case T_UNDEF:
159
+ return v8::Undefined(isolate);
160
+ case T_NIL:
161
+ return v8::Null(isolate);
118
162
  case T_DATA:
119
163
  case T_OBJECT:
120
164
  return gateObject(ruby_value);
121
165
  default:
122
- rb_raise(h8_exception, "can't gate to js: unknown type");
166
+ VALUE msg = rb_str_new2("can't gate to js (unknown): ");
167
+ rb_str_append(msg, rb_any_to_s(ruby_value));
168
+ throw JsError(this, StringValueCStr(msg));
123
169
  }
124
170
  return Undefined(isolate);
125
171
  }
126
172
 
127
- Local<Value> gateObject(VALUE object) const;
173
+ Local<Value> gateObject(VALUE object);
128
174
 
129
- virtual ~H8() {
130
- persistent_context.Reset();
175
+ VALUE ruby_context() const {
176
+ return self;
131
177
  }
132
178
 
179
+ void add_resource(AllocatedResource *resource) {
180
+ resources.push(resource);
181
+ }
182
+
183
+ void ruby_mark_gc() const;
184
+
185
+ virtual ~H8();
186
+
133
187
  private:
134
- friend VALUE context_alloc(VALUE klass);
135
- friend void rvalue_mark(void* ptr);
188
+ friend VALUE h8::context_alloc(VALUE klass);
136
189
 
137
190
  Isolate *isolate;
138
191
  VALUE self;
139
192
 
140
- void report_exception(v8::TryCatch& tc) {
141
- rb_raise(h8_exception, "Failed to compile/execute script");
142
- }
143
-
144
193
  Persistent<Context> persistent_context;
145
194
 
146
195
  bool is_error = false;
196
+
197
+ chain resources;
147
198
  };
148
199
  // Context
149
200
  }
@@ -156,4 +207,20 @@ inline VALUE h8::H8::to_ruby(Handle<Value> value) {
156
207
  return JsGate::to_ruby(this, value);
157
208
  }
158
209
 
210
+ inline h8::JsError::JsError(H8* h8, v8::Local<v8::Message> message,
211
+ v8::Local<v8::Value> exception) :
212
+ h8(h8), _message(h8->getIsolate(), message), _exception(h8->getIsolate(),
213
+ exception), has_js_exception(true), reason(NULL) {
214
+ }
215
+
216
+ inline Local<Message> h8::JsError::message() const {
217
+ return Local<Message>::New(h8->getIsolate(), _message);
218
+ }
219
+
220
+ inline Local<Value> h8::JsError::exception() const {
221
+ return Local<Value>::New(h8->getIsolate(), _exception);
222
+ }
223
+
224
+
225
+
159
226
  #endif
@@ -0,0 +1,18 @@
1
+ #include "h8.h"
2
+ #include "JsCatcher.h"
3
+
4
+ using namespace h8;
5
+
6
+ VALUE JsGate::apply(Local<Value> self, VALUE args) const {
7
+ H8::Scope scope(h8);
8
+ long count = RARRAY_LEN(args);
9
+ Local<Value> *js_args = new Local<Value> [count];
10
+ for (int i = 0; i < count; i++) {
11
+ js_args[i] = h8->to_js(rb_ary_entry(args, i));
12
+ }
13
+ h8::JsCatcher catcher(h8);
14
+ Local<Value> result = object()->CallAsFunction(self, count, js_args);
15
+ catcher.throwIfCaught();
16
+ delete [] js_args;
17
+ return h8->to_ruby(result);
18
+ }
@@ -2,6 +2,7 @@
2
2
  #define __js_gate_h
3
3
 
4
4
  #include "h8.h"
5
+ #include "allocated_resource.h"
5
6
 
6
7
  using namespace v8;
7
8
 
@@ -10,7 +11,7 @@ namespace h8 {
10
11
  /**
11
12
  * Interface to anything that could be converted to a Javascipt object. Provides common helpers.
12
13
  */
13
- class JsValue {
14
+ class JsValue: public AllocatedResource {
14
15
  public:
15
16
  virtual Local<Value> value() const = 0;
16
17
 
@@ -19,9 +20,10 @@ public:
19
20
  }
20
21
 
21
22
  virtual Isolate* isolate() = 0;
23
+ virtual ~JsValue() {
24
+ }
22
25
  };
23
26
 
24
-
25
27
  /**
26
28
  * Gates JS object to ruby environment. Holds persistent reference to the source js object until
27
29
  * ruby object is recycled (then frees it). Note that this ruby object is not meant to be kept alive
@@ -29,7 +31,7 @@ public:
29
31
  *
30
32
  * Methods of this class do not need the H8::Scope, they create one internally.
31
33
  */
32
- class JsGate : public JsValue {
34
+ class JsGate: public JsValue {
33
35
  public:
34
36
  /**
35
37
  * Used in the ruby allocator. Do not call unless you know what you do.
@@ -41,14 +43,8 @@ public:
41
43
  * Return Ruby object that gates specified Handled javascript object. Ruby object
42
44
  * locks permanently value until get recycled.
43
45
  */
44
- template <class T>
45
- static VALUE to_ruby(H8* h8, const Handle<T>& value) {
46
- JsGate *gate;
47
- VALUE ruby_gate = rb_class_new_instance(0, NULL, value_class);
48
- Data_Get_Struct(ruby_gate, JsGate, gate);
49
- gate->set(h8, value);
50
- return ruby_gate;
51
- }
46
+ template<class T>
47
+ static VALUE to_ruby(H8* h8, const Handle<T>& value);
52
48
 
53
49
  /**
54
50
  * Reset gate to the specified handle.
@@ -57,6 +53,7 @@ public:
57
53
  void set(H8 *h8, const Handle<T>& val) {
58
54
  this->h8 = h8;
59
55
  persistent_value.Reset(h8->getIsolate(), val);
56
+ h8->add_resource(this);
60
57
  }
61
58
 
62
59
  /**
@@ -73,7 +70,7 @@ public:
73
70
  */
74
71
  VALUE to_i() {
75
72
  H8::Scope scope(h8);
76
- return INT2FIX(value()->IntegerValue());
73
+ return INT2FIX(value()->IntegerValue());
77
74
  }
78
75
 
79
76
  /**
@@ -81,7 +78,7 @@ public:
81
78
  */
82
79
  VALUE to_f() {
83
80
  H8::Scope scope(h8);
84
- return DBL2NUM(value()->NumberValue());
81
+ return DBL2NUM(value()->NumberValue());
85
82
  }
86
83
 
87
84
  /**
@@ -89,7 +86,7 @@ public:
89
86
  */
90
87
  VALUE is_int() {
91
88
  H8::Scope scope(h8);
92
- return value()->IsInt32() ? Qtrue : Qfalse;
89
+ return value()->IsInt32() ? Qtrue : Qfalse;
93
90
  }
94
91
 
95
92
  /**
@@ -97,7 +94,7 @@ public:
97
94
  */
98
95
  VALUE is_float() {
99
96
  H8::Scope scope(h8);
100
- return value()->IsNumber() ? Qtrue : Qfalse;
97
+ return value()->IsNumber() ? Qtrue : Qfalse;
101
98
  }
102
99
 
103
100
  /**
@@ -105,7 +102,7 @@ public:
105
102
  */
106
103
  VALUE is_array() {
107
104
  H8::Scope scope(h8);
108
- return value()->IsArray() ? Qtrue : Qfalse;
105
+ return value()->IsArray() ? Qtrue : Qfalse;
109
106
  }
110
107
 
111
108
  /**
@@ -113,7 +110,7 @@ public:
113
110
  */
114
111
  VALUE is_object() {
115
112
  H8::Scope scope(h8);
116
- return value()->IsObject() ? Qtrue : Qfalse;
113
+ return value()->IsObject() ? Qtrue : Qfalse;
117
114
  }
118
115
 
119
116
  /**
@@ -122,7 +119,8 @@ public:
122
119
  */
123
120
  VALUE get_attribute(VALUE name) {
124
121
  H8::Scope scope(h8);
125
- Local<Value> v8_name = v8::String::NewFromUtf8(isolate(), StringValueCStr(name));
122
+ Local<Value> v8_name = v8::String::NewFromUtf8(isolate(),
123
+ StringValueCStr(name));
126
124
  return h8->to_ruby(object()->Get(v8_name));
127
125
  }
128
126
 
@@ -136,7 +134,7 @@ public:
136
134
  */
137
135
  VALUE is_string() {
138
136
  H8::Scope scope(h8);
139
- return value()->IsString() ? Qtrue : Qfalse;
137
+ return value()->IsString() ? Qtrue : Qfalse;
140
138
  }
141
139
 
142
140
  VALUE is_function() {
@@ -149,6 +147,9 @@ public:
149
147
  return value()->IsUndefined() ? Qtrue : Qfalse;
150
148
  }
151
149
 
150
+ /**
151
+ * Call this as function over global context
152
+ */
152
153
  VALUE call(VALUE args) const {
153
154
  v8::HandleScope scope(h8->getIsolate());
154
155
  return apply(h8->getContext()->Global(), args);
@@ -159,16 +160,19 @@ public:
159
160
  return apply(h8->gateObject(self), args);
160
161
  }
161
162
 
162
- VALUE apply(Local<Value> self, VALUE args) const {
163
- H8::Scope scope(h8);
164
- long count = RARRAY_LEN(args);
165
- Local<Value> *js_args = new Local<Value>[count];
166
- for (int i = 0; i < count; i++) {
167
- js_args[i] = h8->to_js(rb_ary_entry(args, i));
168
- }
169
- Local<Value> result = object()->CallAsFunction(self, count, js_args);
170
- delete[] js_args;
171
- return h8->to_ruby(result);
163
+ VALUE ruby_context() const {
164
+ return h8->ruby_context();
165
+ }
166
+
167
+ /**
168
+ * Call this object as function applying it to the object self
169
+ * (which will be this during javascript call)
170
+ */
171
+ VALUE apply(Local<Value> self, VALUE args) const;
172
+
173
+ virtual void free() {
174
+ persistent_value.Reset();
175
+ AllocatedResource::free();
172
176
  }
173
177
 
174
178
  virtual Local<Value> value() const {
@@ -179,7 +183,7 @@ public:
179
183
  return h8->getIsolate();
180
184
  }
181
185
 
182
- ~JsGate() {
186
+ virtual ~JsGate() {
183
187
  persistent_value.Reset();
184
188
  }
185
189
 
@@ -187,10 +191,45 @@ private:
187
191
  friend void rvalue_mark(void* ptr);
188
192
  friend class H8;
189
193
 
190
- H8 *h8=0;
194
+ H8 *h8 = 0;
191
195
  Persistent<Value> persistent_value;
192
196
  };
193
197
 
194
198
  }
195
199
 
200
+ #include "ruby_gate.h"
201
+
202
+ template<class T>
203
+ VALUE h8::JsGate::to_ruby(H8* h8, const Handle<T>& value) {
204
+ // Convert primitives
205
+ Local<Value> v = value;
206
+ if (v->IsString()) {
207
+ H8::Scope scope(h8);
208
+ String::Utf8Value res(v);
209
+ return *res ? rb_str_new2(*res) : Qnil;
210
+ }
211
+ if (v->IsInt32()) {
212
+ return INT2FIX(v->Int32Value());
213
+ }
214
+ if (v->IsNumber()) {
215
+ return DBL2NUM(v->NumberValue());
216
+ }
217
+ if( v->IsUndefined()) {
218
+ return Rundefined;
219
+ }
220
+ if( v->IsNull() ) {
221
+ return Qnil;
222
+ }
223
+ RubyGate *rg = RubyGate::unwrap(v.As<v8::Object>());
224
+ if( rg ) {
225
+ return rg->rubyObject();
226
+ }
227
+ JsGate *gate;
228
+ VALUE ruby_gate = rb_class_new_instance(0, NULL, value_class);
229
+ Data_Get_Struct(ruby_gate, JsGate, gate);
230
+ gate->set(h8, value);
231
+ return ruby_gate;
232
+ }
233
+
234
+
196
235
  #endif