h8 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b76a42b3323f67318a62c30a351b47af58ed43c
4
- data.tar.gz: ee264fbcde18819ba8056828f6cda504f4f36e56
3
+ metadata.gz: 157a38dc54ce46cae280e12cd780c074d1b40c4c
4
+ data.tar.gz: 769f4a9417ae920f2b293b8bff43d10b8dbe2925
5
5
  SHA512:
6
- metadata.gz: 77365dcf82c9ff0e7832cb915080b18aacc280e9a9e91cddb917da72168afdbf53be7445082721c539ca71a424e6fb013958ae118317a3274c0b1629c3341842
7
- data.tar.gz: 397d66435fbd04f1ec3b72f5cce6b1d876e316533c4063c8a9288bab96ee97c68022313181075283aa614e7365f02084387793e9325e6402650adcc22fc7dde0
6
+ metadata.gz: 2cae98765a9949524355278bde88517b11aeaeac1ce539f912a4e71ad3515795861898e3e87acc7431cee1c947a0298dbb1117308f383cbacf226ff279447982
7
+ data.tar.gz: 013268e403ee3be27c74656aef90287c3ea04cab5199fca72480c7c7289a4f115429da0ba11f46846b79be8a9ea98410d2bab8534ce6a235e68492e7beae000d
data/README.md CHANGED
@@ -1,11 +1,21 @@
1
1
  # Hybrid8, aka H8
2
2
 
3
- _Warning_ this gem functionality is almost complete but it lacks of testing and some features
4
- (see below). This is a working (or better say, test passing) alpha. Beta suitable versions
5
- will start from 0.1.*.
3
+ _Warning_ this gem is a public beta at the moment - beta testers are welcome!It means, it is not
4
+ yet production stable - we haven't yet tried.
6
5
 
7
- Therubyracer gem alternative to effectively work with ruby 2.1+ in multithreaded environment in an
8
- effective and GC-safe way. Should be out of the box replacement for most scenarios.
6
+ This gem was intended to replace therubyracer for many reasons:
7
+
8
+ * therubyracer has critical bugs that are not fixed for a long time, under load it produces
9
+ numerous frequent crashes.
10
+
11
+ * therubyracer still uses antique version of V8, H8 uses the latest 3.31 branch
12
+
13
+ * H8 is designed to provide very tight and effective integration of two allocation systems and
14
+ object models, passing the same objects between different systems wrapping and unwrapping them
15
+ rather than copying and changing
16
+
17
+ * We hope that by the cost non significant changes we will provide faster execution. And might v8
18
+ modern branch will help us with it ;)
9
19
 
10
20
  Special features:
11
21
 
@@ -26,19 +36,50 @@ uncaught javascript exceptions raise ruby error in ruby code.
26
36
 
27
37
  ## Main difference from therubyracer/features not ready
28
38
 
29
- *This version is not (yet?) thread safe*. For the sake of effectiveness, do not access same
30
- H8::Context and its returned values from concurrent threads. Use Mutexes!
39
+ - This version is not (yet?) thread safe*. For the sake of effectiveness, do not access same
40
+ H8::Context and its returned values from concurrent threads. Use Mutexes if need.
41
+
42
+ The pity thing is, if we will Lock() the context on each call to it, the performance degradation
43
+ will be notable and no matter whether you need threading with it. So, if you really need it, you
44
+ wrap it with Mutex or whatever you want, without slowing down all the rest of us.
45
+
46
+ - Script is executed in the calling ruby thread without unblocking it (other ruby threads can not
47
+ perform on this core while javascript code runs).
48
+
49
+ The same performance reason. If we release gvl on script start and reacquire it every time the ruby
50
+ object, callback, class, whatever is referenced from JS, the execution will considerably degrade. As
51
+ this gem is intended to let javascript/coffescript tightly integrate with ruby objects we have
52
+ decided to not to slow down everything. If it is a problem, lets discuss it.
31
53
 
32
54
  - correct and accurate object tracking in both JS and Ruby VMs, GC aware.
33
55
 
34
- - passed thru objects and exceptions in js -> ruby -> js -> ruby chains are usually kept unchanged,
35
- e.g. wrapped in one language then unwrapped when passed to the original language
56
+ - labmda/proc passed as var to the context **does not receives first (this) argument
57
+ automatically!**
58
+
59
+ E.g. rubyracer code
60
+
61
+ cxt[:fn] = -> (this, a, b) { a + b }
62
+
63
+ Becomes
64
+
65
+ cxt[:fn] = -> (a, b) { a + b }
66
+
67
+ it looks prettier, doesn't it? And, if you really need `this` in the callable, just mention it in
68
+ the call:
69
+
70
+ cxt[:fn] = -> (this, a, b) { a + b + this.offset }
71
+ cxt.eval 'fn(this, 10, 20)'
72
+
73
+ This, again, is done for execution speed. Always wrapping `this` to pass it to ruby is a costly
74
+ procedure which is always performed in rubyracer - no matter is it really needed. In H8 you can
75
+ spend your resources only when it worth extra processing. From my experience, it is a rare situation
76
+ when such a lambda needs javascript's this - but, no problem, pass it this, or whatever else
77
+ you need ;)
78
+
79
+ - there is no 'Context object initialization' - it does not work well in rubyracer so it is not
80
+ likely used widely. We can add it later, though.
36
81
 
37
- - Not Yet: source information in uncaught exception in js
38
82
 
39
- - Script is executed in the calling ruby thread without unblocking it (for the sake of
40
- effectiveness). If we would release GIL and reacquire it, it would take more time. And there is no
41
- multithreading support yet (this one might be added soon).
42
83
 
43
84
  ## Installation
44
85
 
data/ext/h8/h8.cpp CHANGED
@@ -21,13 +21,13 @@ void h8::JsError::raise() {
21
21
  // Passing thru the Ruby exception
22
22
  ruby_exception = rg->rubyObject();
23
23
  } else {
24
- Local<String> s = message()->Get();
24
+ Local<Message> m = message();
25
+ Local<String> s = m->Get();
25
26
  String::Utf8Value res(s->ToString());
26
27
  ruby_exception = ruby_exception = rb_exc_new2(js_exception,
27
- *res ? *res : "test");
28
+ *res ? *res : "unknown javascript exception");
28
29
  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));
30
+ rb_iv_set(ruby_exception, "@javascript_error", h8->to_ruby(jsx));
31
31
  }
32
32
  }
33
33
  rb_exc_raise(ruby_exception);
data/ext/h8/h8.h CHANGED
@@ -45,7 +45,7 @@ public:
45
45
  reason = str_reason;
46
46
  }
47
47
 
48
- JsError(H8* h8, v8::Local<v8::Message> message, v8::Local<v8::Value> exception);
48
+ JsError(H8* h8, Local<Message> message, Local<Value> exception);
49
49
 
50
50
  /**
51
51
  * Call it with a proper exception class and be careful - after this call no code will be executed!
@@ -56,6 +56,8 @@ public:
56
56
 
57
57
  Local<Value> exception() const;
58
58
 
59
+ Local<Value> stacktrace() const;
60
+
59
61
  virtual ~JsError() noexcept {
60
62
  _message.Reset();
61
63
  _exception.Reset();
@@ -68,9 +70,11 @@ protected:
68
70
  v8::Persistent<v8::Value, v8::CopyablePersistentTraits<v8::Value>> _exception;
69
71
  };
70
72
 
71
- class JsTimeoutError : public JsError {
73
+ class JsTimeoutError: public JsError {
72
74
  public:
73
- JsTimeoutError(H8* h8) : JsError(h8, NULL) {}
75
+ JsTimeoutError(H8* h8) :
76
+ JsError(h8, NULL) {
77
+ }
74
78
  virtual void raise();
75
79
  };
76
80
 
@@ -97,6 +101,7 @@ public:
97
101
  isolate = Isolate::New();
98
102
  Isolate::Scope isolate_scope(isolate);
99
103
  HandleScope handle_scope(isolate);
104
+ isolate->SetCaptureStackTraceForUncaughtExceptions(true);
100
105
 
101
106
  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(
102
107
  isolate);
@@ -113,11 +118,11 @@ public:
113
118
  * to this value, JsTimeoutError will be thrown if exceeded
114
119
  * \return the value returned by the script.
115
120
  */
116
- Handle<Value> eval(const char* script_utf,unsigned max_ms=0);
121
+ Handle<Value> eval(const char* script_utf, unsigned max_ms = 0);
117
122
 
118
- VALUE eval_to_ruby(const char* script_utf,int timeout=0) {
123
+ VALUE eval_to_ruby(const char* script_utf, int timeout = 0) {
119
124
  // TODO: throw ruby exception on error
120
- return to_ruby(eval(script_utf,timeout));
125
+ return to_ruby(eval(script_utf, timeout));
121
126
  }
122
127
 
123
128
  Handle<Context> getContext() {
@@ -210,10 +215,13 @@ inline VALUE h8::H8::to_ruby(Handle<Value> value) {
210
215
  return JsGate::to_ruby(this, value);
211
216
  }
212
217
 
213
- inline h8::JsError::JsError(H8* h8, v8::Local<v8::Message> message,
214
- v8::Local<v8::Value> exception) :
215
- h8(h8), _message(h8->getIsolate(), message), _exception(h8->getIsolate(),
216
- exception), has_js_exception(true), reason(NULL) {
218
+ inline h8::JsError::JsError(H8* h8, Local<Message> message,
219
+ Local<Value> exception) :
220
+ h8(h8),
221
+ _message(h8->getIsolate(), message),
222
+ _exception(h8->getIsolate(), exception),
223
+ has_js_exception(true),
224
+ reason(NULL) {
217
225
  }
218
226
 
219
227
  inline Local<Message> h8::JsError::message() const {
@@ -224,6 +232,4 @@ inline Local<Value> h8::JsError::exception() const {
224
232
  return Local<Value>::New(h8->getIsolate(), _exception);
225
233
  }
226
234
 
227
-
228
-
229
235
  #endif
data/lib/h8/context.rb CHANGED
@@ -37,6 +37,7 @@ module H8
37
37
  # @raise [H8::TimeoutError] if the timeout was set and expired
38
38
  def eval script, max_time: 0, timeout: 0
39
39
  timeout = max_time * 1000 if max_time > 0
40
+ yield(self) if block_given?
40
41
  _eval script, timeout.to_i
41
42
  end
42
43
 
@@ -83,7 +84,7 @@ module H8
83
84
  H8::Undefined
84
85
  end
85
86
 
86
- private
87
+ protected
87
88
 
88
89
  # Set var that could be either a callable, class instance, simple value or a Class class
89
90
  # in which case constructor function will be created
@@ -106,6 +107,5 @@ module H8
106
107
  def _do_cretate_ruby_class(klass, arguments)
107
108
  klass.new *arguments.to_ruby.values
108
109
  end
109
-
110
110
  end
111
111
  end
data/lib/h8/value.rb CHANGED
@@ -19,7 +19,7 @@ module H8
19
19
  include Comparable
20
20
 
21
21
  def inspect
22
- "<H8::Value #{to_ruby}>"
22
+ "<H8::Value #{to_ruby rescue '(too deep)'}>"
23
23
  end
24
24
 
25
25
  # Get js object attribute by either name or index (should be Fixnum instance). It always
@@ -105,8 +105,8 @@ module H8
105
105
  end
106
106
 
107
107
  # Try to convert javascript object to a ruby hash
108
- def to_h
109
- each.reduce({}) { |all, kv| all[kv[0]] = kv[1].to_ruby; all }
108
+ def to_h depth=0
109
+ each.reduce({}) { |all, kv| all[kv[0]] = kv[1].to_ruby depth; all }
110
110
  end
111
111
 
112
112
  # Iterate over javascript object keys
@@ -125,8 +125,12 @@ module H8
125
125
  end
126
126
 
127
127
  # Tries to convert wrapped JS object to ruby primitive (Fixed, String, Float, Array, Hash).
128
- # Note that this conversion looses information about source javascript class (if any)
129
- def to_ruby
128
+ # Note that this conversion looses information about source javascript class (if any).
129
+ #
130
+ # @raise H8::Error if the data structure is too deep (e.g. cyclic)
131
+ def to_ruby depth=0
132
+ depth += 1
133
+ raise H8::Error, "object tree too deep" if depth > 100
130
134
  case
131
135
  when integer?
132
136
  to_i
@@ -135,11 +139,11 @@ module H8
135
139
  when float?
136
140
  to_f
137
141
  when array?
138
- _get_attr('length').to_i.times.map { |i| _get_index(i).to_ruby }
142
+ _get_attr('length').to_i.times.map { |i| _get_index(i).to_ruby depth }
139
143
  when function?
140
144
  to_proc
141
145
  when object?
142
- to_h
146
+ to_h depth
143
147
  else
144
148
  raise Error, "Dont know how to convert #{self.class}"
145
149
  end
@@ -179,7 +183,7 @@ end
179
183
  class Object
180
184
  # It is already a ruby object. Gate objects should override
181
185
  # as need
182
- def to_ruby
186
+ def to_ruby depth=0
183
187
  self
184
188
  end
185
189
  end
data/lib/h8/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module H8
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/h8.rb CHANGED
@@ -12,12 +12,26 @@ module H8
12
12
  # The general error caused by the script execution, e.g. uncaught javascript exceptinos and like.
13
13
  # Check #message to see the cause.
14
14
  class JsError < Error
15
+ # Error message
15
16
  attr :message
16
- attr :source
17
+
18
+ # Javascript Error object. May be nil
19
+ attr :javascript_error
17
20
 
18
21
  def to_s
19
22
  message
20
23
  end
24
+
25
+ # Error name
26
+ def name
27
+ @javascript_error.name ? @javascript_error.name : message
28
+ end
29
+
30
+ # String that represents stack trace if any as multiline string (\n separated)
31
+ def javascript_backtrace
32
+ @javascript_error ? @javascript_error.stack : message
33
+ end
34
+
21
35
  end
22
36
 
23
37
  # Script execution is timed out (see H8::Context#eval timeout parameter)
@@ -16,6 +16,36 @@ describe 'ruby gate' do
16
16
  GC.start
17
17
  end
18
18
 
19
+ # it 'should gate callables in therubyrace mode' do
20
+ # cxt = H8::CompatibleContext.new
21
+ # cxt[:fn] = -> (this, a, b) {
22
+ # p this.to_s
23
+ # this.offset + a + b
24
+ # }
25
+ #
26
+ # res = cxt.eval <<-End
27
+ # function Test() {
28
+ # this.offset = 110;
29
+ # this.method = function(a,b) {
30
+ # return fn(a,b);
31
+ # }
32
+ # }
33
+ # new Test().method(11, 22);
34
+ # End
35
+ # res.to_i.should == 143
36
+ # end
37
+
38
+ it 'should allow edit context on yield' do
39
+ cxt = H8::Context.new
40
+ cxt[:fn] = -> (a, b) {
41
+ a + b
42
+ }
43
+ res = cxt.eval("fn(11, 22);") { |cxt|
44
+ cxt[:fn] = -> (a,b) { a - b }
45
+ }
46
+ res.to_i.should == -11
47
+ end
48
+
19
49
  it 'should gate callables with varargs' do
20
50
  cxt = H8::Context.new
21
51
  cxt[:fn] = -> (*args) {
@@ -95,7 +125,29 @@ describe 'ruby gate' do
95
125
  }
96
126
  end
97
127
 
98
- it 'should have information about javascript exceptions'
128
+ it 'should have information about javascript exceptions' do
129
+ # javascript_backtrace - list of strings?
130
+ begin
131
+ H8::Context.eval <<-End
132
+ // This is ok
133
+ var ok = true;
134
+ function bad() {
135
+ throw Error("test");
136
+ }
137
+ function good() {
138
+ bad();
139
+ }
140
+ // This is also ok
141
+ good();
142
+ End
143
+ fail 'did not raise error'
144
+ rescue H8::JsError => e
145
+ x = e.javascript_error
146
+ e.name.should == 'Error'
147
+ e.message.should =~ /test/
148
+ e.javascript_backtrace.should =~ /at bad \(eval\:4\:17\)/
149
+ end
150
+ end
99
151
 
100
152
  context 'accessing ruby code' do
101
153
  class Base
@@ -285,4 +337,12 @@ describe 'ruby gate' do
285
337
  cxt.eval('rc.init_args').should == ['hello', 'world']
286
338
  end
287
339
  end
340
+
341
+ it 'should survive recursive constructions' do
342
+ a = H8::Context.eval 'a=[1,2]; a.push(a); a'
343
+ expect(-> { a.to_ruby }).to raise_error(H8::Error)
344
+ a = H8::Context.eval "a={on:2}; a['a']=a; a"
345
+ expect(-> { a.to_ruby }).to raise_error(H8::Error)
346
+ a.inspect.should =~ /too deep/
347
+ end
288
348
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: h8
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - sergeych
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-27 00:00:00.000000000 Z
11
+ date: 2014-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler