h8 0.2.5 → 0.3.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/.gitignore +1 -0
- data/benchmark/README.md +19 -0
- data/benchmark/km.cpp +141 -0
- data/benchmark/knightsmove.coffee +8 -8
- data/benchmark/{knigthsmove.rb → knightsmove.rb} +7 -8
- data/benchmark/tools.rb +15 -3
- data/ext/h8/h8.cpp +8 -2
- data/ext/h8/h8.h +30 -16
- data/ext/h8/js_catcher.cpp +2 -3
- data/ext/h8/js_gate.h +6 -0
- data/ext/h8/main.cpp +17 -9
- data/ext/h8/object_wrap.h +1 -1
- data/ext/h8/ruby_gate.cpp +19 -3
- data/lib/h8/context.rb +3 -2
- data/lib/h8/version.rb +1 -1
- data/spec/ruby_gate_spec.rb +26 -8
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38c8f3b25b694cb718a7be51eb9ad7cdb3796d7a
|
4
|
+
data.tar.gz: 548f468f6a4dec9f63bd5de2fe6eff950a51b5b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63ab563ae77b67b5b2a89e545e350736a6fc34a0ff22b7a74d796ce102c8bf98486c71201712e42275b0dbe1901f7473367cc772cb504da1fe0b99cb793dbaa6
|
7
|
+
data.tar.gz: d6a50a1bb8f933027f920fcca11be972922d6744ea95174409e99431a3255d1af34519d2d751b2cc51ecfadd0804cc6e153e1635c15b9eaa32ad4f56ed3f69c0
|
data/.gitignore
CHANGED
data/benchmark/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
The most interesting benchmark is the classic knightsmove task. For 5 repitions 7x7 board on my i7
|
2
|
+
it gives:
|
3
|
+
|
4
|
+
hybrid8/benchmark$ ruby knightsmove.rb
|
5
|
+
coffee : 3.970693
|
6
|
+
ruby : 13.152436 scaled: 65.76218
|
7
|
+
total : 13.152677
|
8
|
+
|
9
|
+
In other words, coffee and ruby rub in parallel (I have many cores) and coffee is roughly
|
10
|
+
*17 times faster* than ruby.
|
11
|
+
|
12
|
+
Moreover, if you run optimized C++ version, you'll have:
|
13
|
+
|
14
|
+
hybrid8/benchmark$ g++ -O3 --std=c++11 km.cpp && ./a.out
|
15
|
+
C++: 7.00417
|
16
|
+
|
17
|
+
which is, in turn, *1.76 times slower than coffeescript!*
|
18
|
+
|
19
|
+
The results are very inspiring, as for me.
|
data/benchmark/km.cpp
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
#include <iostream>
|
2
|
+
#include <iomanip>
|
3
|
+
#include <vector>
|
4
|
+
|
5
|
+
using namespace std;
|
6
|
+
|
7
|
+
struct Point {
|
8
|
+
int row;
|
9
|
+
int col;
|
10
|
+
};
|
11
|
+
|
12
|
+
ostream& operator << (ostream& os, const Point& p) {
|
13
|
+
os << "[" << p.row << "," << p.col << "]";
|
14
|
+
return os;
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
Smallest and fastest 2d array implementation. No checks. Nothing ;)
|
19
|
+
*/
|
20
|
+
template<class T>
|
21
|
+
class Array2 {
|
22
|
+
public:
|
23
|
+
Array2(unsigned rows,unsigned cols)
|
24
|
+
: rows_(rows), cols_(cols), data_(new T[rows*cols])
|
25
|
+
{
|
26
|
+
}
|
27
|
+
|
28
|
+
T& at(unsigned row, unsigned col) {
|
29
|
+
return data_[row*cols_ + col];
|
30
|
+
}
|
31
|
+
|
32
|
+
template<class P>
|
33
|
+
T& at(const P& p) {
|
34
|
+
return at(p.row, p.col);
|
35
|
+
}
|
36
|
+
|
37
|
+
const T& at(unsigned row, unsigned col) const {
|
38
|
+
return data_[row*cols_ + col];
|
39
|
+
}
|
40
|
+
|
41
|
+
void fill(const T& value) {
|
42
|
+
for( unsigned i=0; i<rows_*cols_; i++)
|
43
|
+
data_[i] = value;
|
44
|
+
}
|
45
|
+
|
46
|
+
unsigned rows() const { return rows_; }
|
47
|
+
unsigned cols() const { return cols_; }
|
48
|
+
|
49
|
+
~Array2() {
|
50
|
+
delete data_;
|
51
|
+
}
|
52
|
+
|
53
|
+
private:
|
54
|
+
Array2(const Array2&& copy) {}
|
55
|
+
|
56
|
+
unsigned rows_, cols_;
|
57
|
+
T* data_;
|
58
|
+
};
|
59
|
+
|
60
|
+
template <class T>
|
61
|
+
ostream& operator <<(ostream& os,const Array2<T>& arr) {
|
62
|
+
for( unsigned i=0; i<arr.rows(); i++ ) {
|
63
|
+
for( unsigned j=0; j<arr.cols(); j++ ) {
|
64
|
+
os << setw(3) << arr.at(i,j);
|
65
|
+
}
|
66
|
+
os << endl;
|
67
|
+
}
|
68
|
+
return os;
|
69
|
+
}
|
70
|
+
|
71
|
+
static Point move_shifts[] = { {-2, +1}, {-1, +2}, {+1, +2}, {+2, +1}, {+2, -1}, {+1, -2}, {-1, -2}, {-2, -1} };
|
72
|
+
static unsigned move_count = 8;
|
73
|
+
|
74
|
+
class KM {
|
75
|
+
public:
|
76
|
+
KM(unsigned rank)
|
77
|
+
: rank_(rank), board_(rank, rank)
|
78
|
+
{
|
79
|
+
}
|
80
|
+
|
81
|
+
bool solve() {
|
82
|
+
board_.fill(0);
|
83
|
+
depth_ = 0;
|
84
|
+
goal_ = rank_*rank_;
|
85
|
+
return step( {0, 0} );
|
86
|
+
}
|
87
|
+
|
88
|
+
const Array2<unsigned>& board() const { return board_; }
|
89
|
+
private:
|
90
|
+
|
91
|
+
bool step(const Point& p) {
|
92
|
+
board_.at(p) = ++depth_;
|
93
|
+
if( depth_ >= goal_ )
|
94
|
+
return true;
|
95
|
+
for( Point m: moves(p) ) {
|
96
|
+
if( step(m) )
|
97
|
+
return true;
|
98
|
+
}
|
99
|
+
board_.at(p) = 0;
|
100
|
+
depth_--;
|
101
|
+
return false;
|
102
|
+
}
|
103
|
+
|
104
|
+
vector<Point> moves(const Point& pos) {
|
105
|
+
vector<Point> mm;
|
106
|
+
for( unsigned i=0; i<move_count; i++ ) {
|
107
|
+
// Optimizing the bottleneck:
|
108
|
+
const Point& m = move_shifts[i];
|
109
|
+
int r = pos.row + m.row;
|
110
|
+
if( r >= 0 && r < rank_ ) {
|
111
|
+
int c = pos.col + m.col;
|
112
|
+
if( c >= 0 && c < rank_ && board_.at(r,c) == 0 ) {
|
113
|
+
mm.push_back({r,c});
|
114
|
+
}
|
115
|
+
}
|
116
|
+
}
|
117
|
+
return mm;
|
118
|
+
}
|
119
|
+
|
120
|
+
unsigned rank_, depth_, goal_;
|
121
|
+
Array2<unsigned> board_;
|
122
|
+
};
|
123
|
+
|
124
|
+
template<class S>
|
125
|
+
void timing(const S& name,int repetitions, const std::function<void(void)>& block) {
|
126
|
+
clock_t start = clock();
|
127
|
+
while( repetitions-- > 0) {
|
128
|
+
block();
|
129
|
+
}
|
130
|
+
cout << name << ": " << ((clock() - start)/((double)CLOCKS_PER_SEC)) << endl;
|
131
|
+
}
|
132
|
+
|
133
|
+
int main(int argc, char** argv) {
|
134
|
+
cout << "starting\n";
|
135
|
+
timing( "C++", 5, [] {
|
136
|
+
KM km(7);
|
137
|
+
km.solve();
|
138
|
+
// cout << km.board() << endl;
|
139
|
+
});
|
140
|
+
}
|
141
|
+
|
@@ -4,11 +4,11 @@ pad = (n, len) ->
|
|
4
4
|
res = ' '+res while res.length < len
|
5
5
|
res
|
6
6
|
|
7
|
-
shifts = [[-2, +1], [-1, +2], [+1, +2], [+2, +1], [+2, -1], [-2, -1]]
|
7
|
+
shifts = [[-2, +1], [-1, +2], [+1, +2], [+2, +1], [+2, -1], [+1, -2], [-1, -2], [-2, -1]]
|
8
8
|
|
9
9
|
class Solver
|
10
10
|
|
11
|
-
constructor: (@n
|
11
|
+
constructor: (@n) ->
|
12
12
|
@nn = @n * @n
|
13
13
|
@n2 = @n + @n
|
14
14
|
@desk = []
|
@@ -19,7 +19,7 @@ class Solver
|
|
19
19
|
|
20
20
|
solve: (r, c) ->
|
21
21
|
@desk[r][c] = ++@depth
|
22
|
-
return true if @depth
|
22
|
+
return true if @depth >= @nn
|
23
23
|
for [r1, c1] in @moves(r, c)
|
24
24
|
return true if @solve(r1, c1)
|
25
25
|
@desk[r][c] = 0
|
@@ -49,12 +49,12 @@ timing = (name, cb) ->
|
|
49
49
|
console.log("#{name}: #{(new Date().getTime() - start)/1000}")
|
50
50
|
res
|
51
51
|
|
52
|
-
result = timing 'KN h8', ->
|
53
|
-
new Solver(7, 3).toString()
|
54
|
-
console.log result
|
52
|
+
#result = timing 'KN h8', ->
|
53
|
+
# new Solver(7, 3).toString()
|
54
|
+
#console.log result
|
55
55
|
|
56
56
|
|
57
57
|
|
58
|
-
|
59
|
-
|
58
|
+
return (n, left) ->
|
59
|
+
new Solver(n, left).toString()
|
60
60
|
|
@@ -2,10 +2,9 @@ require './tools'
|
|
2
2
|
|
3
3
|
class Solver
|
4
4
|
|
5
|
-
def initialize rank
|
5
|
+
def initialize rank
|
6
6
|
@n, @nn = rank, rank*rank
|
7
7
|
@n2 = @n+@n
|
8
|
-
@leave_free = leave_free
|
9
8
|
@desk = []
|
10
9
|
@n.times { @desk << [0] * @n }
|
11
10
|
@depth = 0
|
@@ -14,7 +13,7 @@ class Solver
|
|
14
13
|
|
15
14
|
def solve r, c
|
16
15
|
@desk[r][c] = (@depth+=1)
|
17
|
-
return true if @depth
|
16
|
+
return true if @depth >= @nn
|
18
17
|
moves(r, c) { |r1, c1|
|
19
18
|
return true if solve(r1, c1)
|
20
19
|
}
|
@@ -23,7 +22,7 @@ class Solver
|
|
23
22
|
false
|
24
23
|
end
|
25
24
|
|
26
|
-
@@shifts = [[-2, +1], [-1, +2], [+1, +2], [+2, +1], [+2, -1], [-2, -1]]
|
25
|
+
@@shifts = [[-2, +1], [-1, +2], [+1, +2], [+2, +1], [+2, -1], [+1, -2], [-1, -2], [-2, -1]]
|
27
26
|
|
28
27
|
def moves r, c
|
29
28
|
@@shifts.each { |sr, sc|
|
@@ -52,13 +51,13 @@ end
|
|
52
51
|
|
53
52
|
cs = js_context.eval coffee(:knightsmove)
|
54
53
|
|
55
|
-
N
|
54
|
+
N = 7
|
56
55
|
|
57
56
|
res1 = res2 = 0
|
58
57
|
timing('total') {
|
59
58
|
tt = []
|
60
|
-
tt << Thread.start { timing('ruby') { res1 = Solver.new(N
|
61
|
-
tt << Thread.start { timing('coffee') { res2 = cs.call(N
|
59
|
+
tt << Thread.start { timing('ruby', 1, 5) { res1 = Solver.new(N).to_s } }
|
60
|
+
tt << Thread.start { timing('coffee', 5) { res2 = cs.call(N) } }
|
62
61
|
tt.each &:join
|
63
62
|
}
|
64
63
|
|
@@ -68,4 +67,4 @@ if res1 != res2
|
|
68
67
|
puts "Coffee:\n#{res2}"
|
69
68
|
end
|
70
69
|
|
71
|
-
puts res1
|
70
|
+
# puts res1
|
data/benchmark/tools.rb
CHANGED
@@ -1,17 +1,29 @@
|
|
1
1
|
require 'h8'
|
2
2
|
|
3
|
-
def timing name
|
3
|
+
def timing name, repetitions = 1, scale = 1
|
4
4
|
s = Time.now
|
5
|
-
yield
|
6
|
-
|
5
|
+
repetitions.times { yield }
|
6
|
+
t = Time.now - s
|
7
|
+
if scale != 1
|
8
|
+
puts "#{name}\t: #{t} scaled: #{t*scale}"
|
9
|
+
else
|
10
|
+
puts "#{name}\t: #{t}"
|
11
|
+
end
|
7
12
|
rescue
|
8
13
|
puts "*** #{$!}"
|
9
14
|
raise
|
10
15
|
end
|
11
16
|
|
17
|
+
class Console
|
18
|
+
def log *args
|
19
|
+
puts args.join(' ')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
12
23
|
def js_context
|
13
24
|
cxt = H8::Context.new
|
14
25
|
cxt[:print] = -> (*args) { puts args.join(' ') }
|
26
|
+
cxt[:console] = Console
|
15
27
|
cxt
|
16
28
|
end
|
17
29
|
|
data/ext/h8/h8.cpp
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
#include <ruby/thread.h>
|
8
8
|
#include "ruby_gate.h"
|
9
9
|
|
10
|
-
void h8::JsError::raise() {
|
10
|
+
void h8::JsError::raise() const {
|
11
11
|
if (has_js_exception) {
|
12
12
|
VALUE ruby_exception;
|
13
13
|
{
|
@@ -39,7 +39,11 @@ void h8::JsError::raise() {
|
|
39
39
|
}
|
40
40
|
}
|
41
41
|
|
42
|
-
|
42
|
+
const char* h8::JsError::what() const noexcept {
|
43
|
+
return reason;
|
44
|
+
}
|
45
|
+
|
46
|
+
void h8::JsTimeoutError::raise() const {
|
43
47
|
rb_raise(js_timeout_exception, "timeout expired");
|
44
48
|
}
|
45
49
|
|
@@ -52,6 +56,8 @@ Local<Value> h8::H8::gateObject(VALUE ruby_value) {
|
|
52
56
|
} else
|
53
57
|
return gate->value();
|
54
58
|
}
|
59
|
+
if( ruby_value == Rundefined )
|
60
|
+
return v8::Undefined(isolate);
|
55
61
|
// Generic Ruby object
|
56
62
|
RubyGate *gate = new RubyGate(this, ruby_value);
|
57
63
|
return gate->handle(isolate);
|
data/ext/h8/h8.h
CHANGED
@@ -20,6 +20,8 @@ extern VALUE Rundefined;
|
|
20
20
|
extern ID id_is_a;
|
21
21
|
extern ID id_safe_call;
|
22
22
|
|
23
|
+
VALUE protect_ruby(const std::function<VALUE()> &block);
|
24
|
+
|
23
25
|
namespace h8 {
|
24
26
|
|
25
27
|
/// Allocate ruby H8::Context class (wrapper for h8::H8), ruby utility function
|
@@ -50,7 +52,7 @@ public:
|
|
50
52
|
/**
|
51
53
|
* Call it with a proper exception class and be careful - after this call no code will be executed!
|
52
54
|
*/
|
53
|
-
virtual void raise();
|
55
|
+
virtual void raise() const;
|
54
56
|
|
55
57
|
Local<Message> message() const;
|
56
58
|
|
@@ -58,7 +60,9 @@ public:
|
|
58
60
|
|
59
61
|
Local<Value> stacktrace() const;
|
60
62
|
|
61
|
-
virtual
|
63
|
+
virtual const char* what() const noexcept;
|
64
|
+
|
65
|
+
virtual ~JsError() {
|
62
66
|
_message.Reset();
|
63
67
|
_exception.Reset();
|
64
68
|
}
|
@@ -75,7 +79,7 @@ public:
|
|
75
79
|
JsTimeoutError(H8* h8) :
|
76
80
|
JsError(h8, NULL) {
|
77
81
|
}
|
78
|
-
virtual void raise();
|
82
|
+
virtual void raise() const;
|
79
83
|
};
|
80
84
|
|
81
85
|
class H8 {
|
@@ -91,12 +95,9 @@ public:
|
|
91
95
|
|
92
96
|
public:
|
93
97
|
Scope(H8* cxt) :
|
94
|
-
locker(cxt->getIsolate()),
|
95
|
-
|
96
|
-
|
97
|
-
context_scope(cxt->getContext()),
|
98
|
-
rcontext(cxt)
|
99
|
-
{
|
98
|
+
locker(cxt->getIsolate()), handle_scope(cxt->getIsolate()), isolate_scope(
|
99
|
+
cxt->getIsolate()), context_scope(cxt->getContext()), rcontext(
|
100
|
+
cxt) {
|
100
101
|
}
|
101
102
|
};
|
102
103
|
|
@@ -125,9 +126,11 @@ public:
|
|
125
126
|
* to this value, JsTimeoutError will be thrown if exceeded
|
126
127
|
* \return the value returned by the script.
|
127
128
|
*/
|
128
|
-
Handle<Value> eval(const char* script_utf, unsigned max_ms = 0,
|
129
|
+
Handle<Value> eval(const char* script_utf, unsigned max_ms = 0,
|
130
|
+
const char* script_name = NULL);
|
129
131
|
|
130
|
-
VALUE eval_to_ruby(const char* script_utf, int timeout = 0,
|
132
|
+
VALUE eval_to_ruby(const char* script_utf, int timeout = 0,
|
133
|
+
const char* script_name = NULL) {
|
131
134
|
// TODO: throw ruby exception on error
|
132
135
|
return to_ruby(eval(script_utf, timeout, script_name));
|
133
136
|
}
|
@@ -161,7 +164,8 @@ public:
|
|
161
164
|
|
162
165
|
void gc() {
|
163
166
|
// puts("H8 GC");
|
164
|
-
while(!isolate->IdleNotification(500)) {
|
167
|
+
while (!isolate->IdleNotification(500)) {
|
168
|
+
}
|
165
169
|
}
|
166
170
|
|
167
171
|
Local<Value> to_js(VALUE ruby_value) {
|
@@ -176,6 +180,10 @@ public:
|
|
176
180
|
return v8::Undefined(isolate);
|
177
181
|
case T_NIL:
|
178
182
|
return v8::Null(isolate);
|
183
|
+
case T_TRUE:
|
184
|
+
return v8::True(isolate);
|
185
|
+
case T_FALSE:
|
186
|
+
return v8::False(isolate);
|
179
187
|
case T_ARRAY:
|
180
188
|
case T_HASH:
|
181
189
|
case T_DATA:
|
@@ -202,15 +210,21 @@ public:
|
|
202
210
|
|
203
211
|
void ruby_mark_gc() const;
|
204
212
|
|
205
|
-
bool isGvlReleased() const noexcept {
|
213
|
+
bool isGvlReleased() const noexcept {
|
214
|
+
return gvl_released;
|
215
|
+
}
|
206
216
|
|
207
|
-
void setGvlReleased(bool state) noexcept {
|
217
|
+
void setGvlReleased(bool state) noexcept {
|
218
|
+
gvl_released = state;
|
219
|
+
}
|
208
220
|
|
209
221
|
void setInterrupted() {
|
210
222
|
rb_interrupted = true;
|
211
223
|
}
|
212
224
|
|
213
|
-
bool isInterrupted() const {
|
225
|
+
bool isInterrupted() const {
|
226
|
+
return rb_interrupted;
|
227
|
+
}
|
214
228
|
|
215
229
|
virtual ~H8();
|
216
230
|
|
@@ -244,7 +258,7 @@ inline h8::JsError::JsError(H8* h8, Local<Message> message,
|
|
244
258
|
Local<Value> exception) :
|
245
259
|
h8(h8), _message(h8->getIsolate(), message), _exception(
|
246
260
|
h8->getIsolate(), exception), has_js_exception(true), reason(
|
247
|
-
|
261
|
+
NULL) {
|
248
262
|
}
|
249
263
|
|
250
264
|
inline Local<Message> h8::JsError::message() const {
|
data/ext/h8/js_catcher.cpp
CHANGED
@@ -15,11 +15,10 @@ JsCatcher::JsCatcher(H8* h8) : h8(h8), v8::TryCatch(h8->getIsolate()) {}
|
|
15
15
|
void JsCatcher::throwIfCaught() {
|
16
16
|
if( HasCaught() ) {
|
17
17
|
if( h8->isInterrupted() ) {
|
18
|
-
puts("INTERRUPTED!");
|
19
18
|
throw JsError(h8, "interrupted");
|
20
19
|
}
|
21
|
-
if( !CanContinue() && HasTerminated() ) {
|
22
|
-
|
20
|
+
// if( !CanContinue() && HasTerminated() ) {
|
21
|
+
if( HasTerminated() ) {
|
23
22
|
throw JsTimeoutError(h8);
|
24
23
|
}
|
25
24
|
throw JsError(h8, Message(), Exception());
|
data/ext/h8/js_gate.h
CHANGED
@@ -263,6 +263,12 @@ VALUE h8::JsGate::to_ruby(H8* h8, const Handle<T>& value) {
|
|
263
263
|
if( v->IsUndefined()) {
|
264
264
|
return Rundefined;
|
265
265
|
}
|
266
|
+
if( v->IsTrue() ) {
|
267
|
+
return Qtrue;
|
268
|
+
}
|
269
|
+
if( v->IsFalse() ) {
|
270
|
+
return Qfalse;
|
271
|
+
}
|
266
272
|
if( v->IsNull() ) {
|
267
273
|
return Qnil;
|
268
274
|
}
|
data/ext/h8/main.cpp
CHANGED
@@ -21,7 +21,12 @@ VALUE protect_ruby(const std::function<VALUE()> &block) {
|
|
21
21
|
try {
|
22
22
|
return block();
|
23
23
|
} catch (JsError& e) {
|
24
|
-
|
24
|
+
try {
|
25
|
+
e.raise();
|
26
|
+
}
|
27
|
+
catch(...) {
|
28
|
+
rb_raise(js_exception, "error while converting JS exception (pls report a bug)");
|
29
|
+
}
|
25
30
|
} catch (...) {
|
26
31
|
rb_raise(rb_eStandardError, "unknown error in JS");
|
27
32
|
}
|
@@ -126,13 +131,14 @@ inline H8* rc(VALUE self) {
|
|
126
131
|
return prcxt;
|
127
132
|
}
|
128
133
|
|
129
|
-
static VALUE context_eval(VALUE self, VALUE script,VALUE timeout,
|
134
|
+
static VALUE context_eval(VALUE self, VALUE script, VALUE timeout,
|
135
|
+
VALUE script_name_ruby) {
|
130
136
|
return protect_ruby([&] {
|
131
|
-
H8* cxt = rc(self)
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
137
|
+
H8* cxt = rc(self); // v8::Locker l(cxt->getIsolate());
|
138
|
+
H8::Scope s(cxt);
|
139
|
+
const char* script_name = script_name_ruby != Qnil ? StringValueCStr(script_name_ruby) : NULL;
|
140
|
+
return cxt->eval_to_ruby(StringValueCStr(script), FIX2INT(timeout), script_name);
|
141
|
+
});
|
136
142
|
}
|
137
143
|
|
138
144
|
static VALUE context_set_var(VALUE self, VALUE name, VALUE value) {
|
@@ -185,7 +191,8 @@ void Init_h8(void) {
|
|
185
191
|
rb_define_method(context_class, "_eval", (ruby_method) context_eval, 3);
|
186
192
|
rb_define_method(context_class, "_set_var", (ruby_method) context_set_var,
|
187
193
|
2);
|
188
|
-
rb_define_method(context_class, "javascript_gc",
|
194
|
+
rb_define_method(context_class, "javascript_gc",
|
195
|
+
(ruby_method) context_force_gc, 0);
|
189
196
|
|
190
197
|
value_class = rb_define_class_under(h8, "Value", rb_cObject);
|
191
198
|
rb_define_alloc_func(value_class, rvalue_alloc);
|
@@ -214,7 +221,8 @@ void Init_h8(void) {
|
|
214
221
|
|
215
222
|
h8_exception = rb_define_class_under(h8, "Error", rb_eStandardError);
|
216
223
|
js_exception = rb_define_class_under(h8, "JsError", h8_exception);
|
217
|
-
js_timeout_exception = rb_define_class_under(h8, "TimeoutError",
|
224
|
+
js_timeout_exception = rb_define_class_under(h8, "TimeoutError",
|
225
|
+
js_exception);
|
218
226
|
|
219
227
|
VALUE u_class = rb_define_class_under(h8, "UndefinedClass", rb_cObject);
|
220
228
|
Rundefined = rb_funcall(u_class, rb_intern("instance"), 0);
|
data/ext/h8/object_wrap.h
CHANGED
@@ -111,7 +111,7 @@ protected:
|
|
111
111
|
private:
|
112
112
|
static void WeakCallback(
|
113
113
|
const v8::WeakCallbackData<v8::Object, ObjectWrap>& data) {
|
114
|
-
puts("WEAK CALLBACK!!");
|
114
|
+
// puts("WEAK CALLBACK!!");
|
115
115
|
v8::Isolate* isolate = data.GetIsolate();
|
116
116
|
v8::HandleScope scope(isolate);
|
117
117
|
ObjectWrap* wrap = data.GetParameter();
|
data/ext/h8/ruby_gate.cpp
CHANGED
@@ -106,14 +106,30 @@ void h8::RubyGate::rescued_call(VALUE rb_args, VALUE (*call)(VALUE),
|
|
106
106
|
const std::function<void(VALUE)> &block) {
|
107
107
|
VALUE res;
|
108
108
|
{
|
109
|
-
Unlocker u(context->getIsolate());
|
110
109
|
last_ruby_error = Qnil;
|
111
110
|
VALUE me = Data_Wrap_Struct(ruby_gate_class, 0, 0, this);
|
111
|
+
Unlocker u(context->getIsolate());
|
112
112
|
res = rb_rescue((ruby_method) (call), rb_args,
|
113
113
|
(ruby_method) (rescue_callback), me);
|
114
114
|
}
|
115
|
-
|
116
|
-
|
115
|
+
|
116
|
+
if (last_ruby_error == Qnil) {
|
117
|
+
// This could be removed later, as normally here shouldn't be any
|
118
|
+
// exceptions...
|
119
|
+
try {
|
120
|
+
block(res);
|
121
|
+
}
|
122
|
+
catch(JsError& e) {
|
123
|
+
Local<v8::Object> error = v8::Exception::Error(
|
124
|
+
context->js(e.what())).As<v8::Object>();
|
125
|
+
context->getIsolate()->ThrowException(error);
|
126
|
+
}
|
127
|
+
catch(...) {
|
128
|
+
Local<v8::Object> error = v8::Exception::Error(
|
129
|
+
context->js("unknown exception (inner bug)")).As<v8::Object>();
|
130
|
+
context->getIsolate()->ThrowException(error);
|
131
|
+
}
|
132
|
+
}
|
117
133
|
else
|
118
134
|
throw_js();
|
119
135
|
}
|
data/lib/h8/context.rb
CHANGED
@@ -59,7 +59,7 @@ module H8
|
|
59
59
|
end
|
60
60
|
|
61
61
|
# Secure gate for JS to securely access ruby class properties (methods with no args)
|
62
|
-
# and methods. This class implements security policy
|
62
|
+
# and methods. This class implements security policy. Overriding this method could
|
63
63
|
# breach security and provide full access to ruby object trees.
|
64
64
|
#
|
65
65
|
# It has very complex logic so the security model update should be done somehow
|
@@ -68,7 +68,8 @@ module H8
|
|
68
68
|
method = method.to_sym
|
69
69
|
begin
|
70
70
|
m = instance.public_method(method)
|
71
|
-
|
71
|
+
owner = m.owner
|
72
|
+
if owner != Object.class && owner != Kernel && owner != BasicObject.class
|
72
73
|
return m.call(*args) if method[0] == '[' || method[-1] == '='
|
73
74
|
if m.arity != 0
|
74
75
|
return -> (*args) { m.call *args }
|
data/lib/h8/version.rb
CHANGED
data/spec/ruby_gate_spec.rb
CHANGED
@@ -60,13 +60,25 @@ describe 'ruby gate' do
|
|
60
60
|
GC.start
|
61
61
|
end
|
62
62
|
|
63
|
-
it 'should convert nil' do
|
63
|
+
it 'should convert nil, true, false and undefined' do
|
64
64
|
cxt = H8::Context.new
|
65
|
+
value = false
|
65
66
|
cxt[:fn] = -> {
|
66
|
-
nil
|
67
|
+
[nil, true, false, value, H8::Undefined]
|
67
68
|
}
|
68
|
-
cxt.eval('
|
69
|
-
cxt.eval('
|
69
|
+
cxt.eval('true').should == true
|
70
|
+
cxt.eval('fn();').should == [nil, true, false, false, H8::Undefined]
|
71
|
+
cxt.eval('""+fn()[0]').should == 'null'
|
72
|
+
# cxt.eval("fn().join(',').toString()").should == ',true,false,false,undefined'
|
73
|
+
expect(cxt.eval("(fn()[4] == undefined).toString()")).to eql 'true'
|
74
|
+
expect(cxt.eval("(fn()[0] == undefined).toString()")).to eql 'true'
|
75
|
+
cxt.eval("(fn()[1])").should == true
|
76
|
+
cxt.eval("(fn()[2])").should == false
|
77
|
+
cxt.eval("(fn()[3])").should == false
|
78
|
+
cxt.eval("(fn()[4])").should == H8::Undefined
|
79
|
+
cxt.eval("(fn()[0])").should == nil
|
80
|
+
cxt.eval('true').inspect.should == 'true'
|
81
|
+
cxt.eval('false').inspect.should == 'false'
|
70
82
|
end
|
71
83
|
|
72
84
|
it 'should convert strings to native string' do
|
@@ -173,7 +185,7 @@ describe 'ruby gate' do
|
|
173
185
|
context 'accessing ruby code' do
|
174
186
|
class Base
|
175
187
|
def base
|
176
|
-
|
188
|
+
'base called'
|
177
189
|
end
|
178
190
|
|
179
191
|
attr_accessor :do_throw
|
@@ -227,8 +239,14 @@ describe 'ruby gate' do
|
|
227
239
|
cxt[:foo] = Test.new
|
228
240
|
cxt.eval('foo.ro').should == 'readonly'
|
229
241
|
cxt.eval('foo.rw').should == 'not initialized'
|
230
|
-
cxt.eval('foo.base').should ==
|
242
|
+
cxt.eval('foo.base').should == 'base called'
|
231
243
|
cxt.eval('foo.send').should == H8::Undefined
|
244
|
+
cxt.eval('foo.freeze').should == H8::Undefined
|
245
|
+
cxt.eval('foo.dup').should == H8::Undefined
|
246
|
+
cxt.eval('foo.eval').should == H8::Undefined
|
247
|
+
cxt.eval('foo.extend').should == H8::Undefined
|
248
|
+
cxt.eval('foo.instance_variable_get').should == H8::Undefined
|
249
|
+
cxt.eval('foo.object_id').should == H8::Undefined
|
232
250
|
cxt.eval('foo.prot_method').should == H8::Undefined
|
233
251
|
cxt.eval('foo.priv_method').should == H8::Undefined
|
234
252
|
cxt.eval('foo.test_args').should be_kind_of(Proc)
|
@@ -243,7 +261,7 @@ describe 'ruby gate' do
|
|
243
261
|
cxt.eval('foo.rw').should == 'hello'
|
244
262
|
end
|
245
263
|
|
246
|
-
context '
|
264
|
+
context 'do interceptors' do
|
247
265
|
class Test2 < Base
|
248
266
|
attr :ro
|
249
267
|
attr_accessor :rw
|
@@ -275,7 +293,7 @@ describe 'ruby gate' do
|
|
275
293
|
cxt[:foo] = Test2.new
|
276
294
|
cxt.eval('foo.ro').should == 'readonly'
|
277
295
|
cxt.eval('foo.rw').should == 'not initialized'
|
278
|
-
cxt.eval('foo.base').should ==
|
296
|
+
cxt.eval('foo.base').should == 'base called'
|
279
297
|
cxt.eval('foo.send').should == H8::Undefined
|
280
298
|
cxt.eval('foo.prot_method').should == H8::Undefined
|
281
299
|
cxt.eval('foo.priv_method').should == H8::Undefined
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: h8
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sergeych
|
@@ -98,9 +98,11 @@ files:
|
|
98
98
|
- LICENSE.txt
|
99
99
|
- README.md
|
100
100
|
- Rakefile
|
101
|
+
- benchmark/README.md
|
101
102
|
- benchmark/big.txt
|
103
|
+
- benchmark/km.cpp
|
102
104
|
- benchmark/knightsmove.coffee
|
103
|
-
- benchmark/
|
105
|
+
- benchmark/knightsmove.rb
|
104
106
|
- benchmark/process_text.coffee
|
105
107
|
- benchmark/text_process.rb
|
106
108
|
- benchmark/tools.rb
|