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 +4 -4
- data/README.md +54 -13
- data/ext/h8/h8.cpp +4 -4
- data/ext/h8/h8.h +18 -12
- data/lib/h8/context.rb +2 -2
- data/lib/h8/value.rb +12 -8
- data/lib/h8/version.rb +1 -1
- data/lib/h8.rb +15 -1
- data/spec/ruby_gate_spec.rb +61 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 157a38dc54ce46cae280e12cd780c074d1b40c4c
|
4
|
+
data.tar.gz: 769f4a9417ae920f2b293b8bff43d10b8dbe2925
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
4
|
-
|
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
|
-
|
8
|
-
|
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
|
-
|
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
|
35
|
-
|
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<
|
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 : "
|
28
|
+
*res ? *res : "unknown javascript exception");
|
28
29
|
rb_iv_set(ruby_exception, "@message", h8->to_ruby(s));
|
29
|
-
|
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,
|
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
|
73
|
+
class JsTimeoutError: public JsError {
|
72
74
|
public:
|
73
|
-
JsTimeoutError(H8* h8) :
|
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,
|
214
|
-
|
215
|
-
h8(h8),
|
216
|
-
|
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
|
-
|
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
|
-
|
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
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
|
-
|
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)
|
data/spec/ruby_gate_spec.rb
CHANGED
@@ -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
|
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-
|
11
|
+
date: 2014-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|