h8 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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