h8 0.0.5 → 0.1.0

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