rallhook 0.7.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.
- data/AUTHORS +3 -0
- data/CHANGELOG +82 -0
- data/README +207 -0
- data/Rakefile +49 -0
- data/TODO +8 -0
- data/examples/hook/example1.rb +19 -0
- data/examples/hook/example2.rb +30 -0
- data/examples/hook/example3.rb +24 -0
- data/examples/hook/example4.rb +18 -0
- data/examples/hook/intercept.rb +41 -0
- data/examples/hook/intercept2.rb +49 -0
- data/examples/hook/redirect.rb +55 -0
- data/examples/hook/redirect_inherited.rb +28 -0
- data/examples/hook/shadow.rb +44 -0
- data/examples/instrospection/main.rb +13 -0
- data/examples/instrospection/source1.rb +4 -0
- data/examples/instrospection/source2.rb +4 -0
- data/ext/rallhook_base/distorm.h +401 -0
- data/ext/rallhook_base/extconf.rb +21 -0
- data/ext/rallhook_base/hook.c +165 -0
- data/ext/rallhook_base/hook.h +30 -0
- data/ext/rallhook_base/hook_rb_call.c +88 -0
- data/ext/rallhook_base/hook_rb_call.h +33 -0
- data/ext/rallhook_base/method_node.c +212 -0
- data/ext/rallhook_base/method_node.h +27 -0
- data/ext/rallhook_base/node_defs.h +294 -0
- data/ext/rallhook_base/rallhook.c +396 -0
- data/ext/rallhook_base/rb_call_fake.c +398 -0
- data/ext/rallhook_base/rb_call_fake.h +138 -0
- data/ext/rallhook_base/restrict_def.c +176 -0
- data/ext/rallhook_base/restrict_def.h +37 -0
- data/ext/rallhook_base/ruby_redirect.c +122 -0
- data/ext/rallhook_base/ruby_redirect.h +33 -0
- data/ext/rallhook_base/ruby_symbols.c +43 -0
- data/ext/rallhook_base/ruby_symbols.h +28 -0
- data/ext/rallhook_base/ruby_version.h +21 -0
- data/lib/rallhook/thread_hook.rb +37 -0
- data/lib/rallhook.rb +384 -0
- data/test/basic_proc.rb +45 -0
- data/test/integrity/test_array.rb +42 -0
- data/test/integrity/test_binding.rb +26 -0
- data/test/integrity/test_block.rb +37 -0
- data/test/integrity/test_call.rb +1 -0
- data/test/integrity/test_class_methods.rb +1 -0
- data/test/integrity/test_exception.rb +1 -0
- data/test/integrity/test_super.rb +34 -0
- data/test/introspection/test_call.rb +29 -0
- data/test/introspection/test_class_method.rb +41 -0
- data/test/introspection/test_file.rb +15 -0
- metadata +113 -0
@@ -0,0 +1,396 @@
|
|
1
|
+
/*
|
2
|
+
|
3
|
+
This file is part of the rallhook project, http://github.com/tario/rallhook
|
4
|
+
|
5
|
+
Copyright (c) 2009-2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
|
6
|
+
|
7
|
+
rallhook is free software: you can redistribute it and/or modify
|
8
|
+
it under the terms of the gnu general public license as published by
|
9
|
+
the free software foundation, either version 3 of the license, or
|
10
|
+
(at your option) any later version.
|
11
|
+
|
12
|
+
rallhook is distributed in the hope that it will be useful,
|
13
|
+
but without any warranty; without even the implied warranty of
|
14
|
+
merchantability or fitness for a particular purpose. see the
|
15
|
+
gnu general public license for more details.
|
16
|
+
|
17
|
+
you should have received a copy of the gnu general public license
|
18
|
+
along with rallhook. if not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
*/
|
21
|
+
|
22
|
+
#include <ruby.h>
|
23
|
+
#include "method_node.h"
|
24
|
+
#include "ruby_redirect.h"
|
25
|
+
#include "restrict_def.h"
|
26
|
+
|
27
|
+
VALUE rb_cHook;
|
28
|
+
VALUE rb_mRallHook;
|
29
|
+
VALUE rb_mMethodRedirect;
|
30
|
+
VALUE rb_mMethodReturn;
|
31
|
+
ID id_call_;
|
32
|
+
|
33
|
+
ID id_call;
|
34
|
+
ID id_method_wrapper;
|
35
|
+
ID id_handle_method;
|
36
|
+
ID id_binding;
|
37
|
+
ID id_method_added;
|
38
|
+
ID id_hook_enabled;
|
39
|
+
ID id_hook_enable_left;
|
40
|
+
ID id_hook_proc;
|
41
|
+
|
42
|
+
ID id_return_value_var, id_klass_var, id_recv_var, id_method_var, id_unhook_var;
|
43
|
+
|
44
|
+
void *rb_get_method_body(VALUE klass, ID id, ID *idp);
|
45
|
+
|
46
|
+
typedef struct rb_thread_struct
|
47
|
+
{
|
48
|
+
VALUE self;
|
49
|
+
void *vm; // rb_vm_t_
|
50
|
+
|
51
|
+
/* execution information */
|
52
|
+
VALUE *stack; /* must free, must mark */
|
53
|
+
unsigned long stack_size;
|
54
|
+
void *cfp; // rb_control_frame_t_
|
55
|
+
int safe_level;
|
56
|
+
int raised_flag;
|
57
|
+
VALUE last_status; /* $? */
|
58
|
+
|
59
|
+
/* passing state */
|
60
|
+
int state;
|
61
|
+
|
62
|
+
/* for rb_iterate */
|
63
|
+
void *passed_block; // rb_block_t_
|
64
|
+
|
65
|
+
// ...
|
66
|
+
} rb_thread_t__;
|
67
|
+
|
68
|
+
typedef struct AttachedThreadInfo_ {
|
69
|
+
int hook_enabled;
|
70
|
+
int hook_enable_left;
|
71
|
+
VALUE hook_proc;
|
72
|
+
int handle_method_arity;
|
73
|
+
} AttachedThreadInfo;
|
74
|
+
|
75
|
+
AttachedThreadInfo* tinfo_from_thread(VALUE thread) {
|
76
|
+
VALUE tmp = rb_ivar_get( thread, rb_intern("__tinfo") );
|
77
|
+
|
78
|
+
if (tmp == Qnil) {
|
79
|
+
AttachedThreadInfo* tinfo = malloc(sizeof(AttachedThreadInfo));
|
80
|
+
tinfo->hook_enabled = 0;
|
81
|
+
tinfo->hook_enable_left = 0;
|
82
|
+
tinfo->hook_proc = Qnil;
|
83
|
+
|
84
|
+
rb_ivar_set( thread, rb_intern("__tinfo"), (VALUE)tinfo);
|
85
|
+
|
86
|
+
return tinfo;
|
87
|
+
} else {
|
88
|
+
return (AttachedThreadInfo*)tmp;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
void redirect_left(AttachedThreadInfo* tinfo, int left) {
|
93
|
+
tinfo->hook_enable_left = left;
|
94
|
+
}
|
95
|
+
|
96
|
+
void enable_redirect(AttachedThreadInfo* tinfo) {
|
97
|
+
tinfo->hook_enabled = 1;
|
98
|
+
}
|
99
|
+
|
100
|
+
void disable_redirect(AttachedThreadInfo* tinfo) {
|
101
|
+
tinfo->hook_enabled = 0;
|
102
|
+
}
|
103
|
+
|
104
|
+
int get_hook_enable_left(AttachedThreadInfo* tinfo) {
|
105
|
+
return tinfo->hook_enable_left;
|
106
|
+
}
|
107
|
+
|
108
|
+
int get_hook_enabled(AttachedThreadInfo* tinfo) {
|
109
|
+
return tinfo->hook_enabled;
|
110
|
+
}
|
111
|
+
|
112
|
+
VALUE get_hook_proc() {
|
113
|
+
return tinfo_from_thread( rb_thread_current() )->hook_proc ;
|
114
|
+
}
|
115
|
+
|
116
|
+
/*
|
117
|
+
Disable the hook. Is not usually necesary because of the RAII feature of Hook#hook
|
118
|
+
*/
|
119
|
+
VALUE unhook(VALUE self) {
|
120
|
+
disable_redirect(tinfo_from_thread( rb_thread_current() ) );
|
121
|
+
return Qnil;
|
122
|
+
}
|
123
|
+
|
124
|
+
VALUE ensured_handle_method( VALUE params ) {
|
125
|
+
int argc = (int)((VALUE*)params)[0];
|
126
|
+
VALUE* argv = ((VALUE*)params)+1;
|
127
|
+
return rb_funcall2( get_hook_proc(), id_handle_method, argc, argv);
|
128
|
+
}
|
129
|
+
|
130
|
+
VALUE restore_hook_status(AttachedThreadInfo* tinfo) {
|
131
|
+
enable_redirect(tinfo);
|
132
|
+
return Qnil;
|
133
|
+
}
|
134
|
+
|
135
|
+
void set_handle_method_arity(int value) {
|
136
|
+
tinfo_from_thread(rb_thread_current() )->handle_method_arity = value;
|
137
|
+
}
|
138
|
+
|
139
|
+
void rallhook_redirect_handler ( VALUE* klass, VALUE* recv, ID* mid ) {
|
140
|
+
|
141
|
+
VALUE current_thread = rb_thread_current();
|
142
|
+
AttachedThreadInfo* tinfo = tinfo_from_thread(current_thread);
|
143
|
+
int hook_enable_left = get_hook_enable_left(tinfo);
|
144
|
+
|
145
|
+
if (get_hook_enabled(tinfo) == 0 || hook_enable_left > 0){
|
146
|
+
if(hook_enable_left>0) hook_enable_left --;
|
147
|
+
redirect_left( tinfo, hook_enable_left );
|
148
|
+
return;
|
149
|
+
}
|
150
|
+
|
151
|
+
VALUE argv_[6];
|
152
|
+
if(*mid == id_method_added) {
|
153
|
+
*recv = unshadow(*recv); // recv is THE class
|
154
|
+
*klass = CLASS_OF(*recv);
|
155
|
+
}
|
156
|
+
|
157
|
+
argv_[0] = tinfo->handle_method_arity;
|
158
|
+
if (tinfo->handle_method_arity == 4) {
|
159
|
+
|
160
|
+
VALUE sym;
|
161
|
+
if (rb_id2name(*mid)) {
|
162
|
+
sym = ID2SYM(*mid);
|
163
|
+
} else {
|
164
|
+
sym =Qnil;
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
argv_[1] = unshadow(*klass);
|
169
|
+
argv_[2] = *recv;
|
170
|
+
argv_[3] = sym;
|
171
|
+
argv_[4] = LONG2FIX(*mid);
|
172
|
+
} else {
|
173
|
+
argv_[1] = unshadow(*klass);
|
174
|
+
argv_[2] = *recv;
|
175
|
+
argv_[3] = LONG2FIX(*mid);
|
176
|
+
}
|
177
|
+
|
178
|
+
ID original_id = *mid;
|
179
|
+
|
180
|
+
disable_redirect(tinfo);
|
181
|
+
|
182
|
+
rb_thread_t__* th;
|
183
|
+
Data_Get_Struct( current_thread, rb_thread_t__, th );
|
184
|
+
|
185
|
+
void* blockptr = th->passed_block;
|
186
|
+
VALUE result = rb_ensure(ensured_handle_method,(VALUE)argv_,restore_hook_status, (VALUE)tinfo);
|
187
|
+
|
188
|
+
th->passed_block = blockptr;
|
189
|
+
|
190
|
+
if (original_id != id_binding ) {
|
191
|
+
|
192
|
+
// method named "binding" cannot be redirected
|
193
|
+
if (rb_obj_is_kind_of(result,rb_mMethodRedirect) == Qtrue ) {
|
194
|
+
*klass = rb_ivar_get(result,id_klass_var );
|
195
|
+
*recv = rb_ivar_get(result,id_recv_var );
|
196
|
+
*mid = rb_to_id( rb_ivar_get(result,id_method_var) );
|
197
|
+
|
198
|
+
if (rb_ivar_get(result,id_unhook_var) != Qnil ) {
|
199
|
+
disable_redirect(tinfo);
|
200
|
+
}
|
201
|
+
|
202
|
+
} else {
|
203
|
+
shadow_redirect(klass, recv, mid);
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
// methods over class hook are illegal, may change the state of hook
|
208
|
+
if (*recv == rb_cHook ) {
|
209
|
+
rb_raise(rb_eSecurityError, "Illegal method call: Hook.%s", rb_id2name(*mid) );
|
210
|
+
}
|
211
|
+
|
212
|
+
|
213
|
+
}
|
214
|
+
|
215
|
+
/*
|
216
|
+
Activate the hook, it is desirable to use the RAII call to make the hook block exception safe:
|
217
|
+
|
218
|
+
rallhook.hook method_hadler do
|
219
|
+
print "hello world\n" # calls to print, write or whatever are intercepted by method_handler#method_handle
|
220
|
+
end # in the finish of the block, the hook are disabled
|
221
|
+
|
222
|
+
*/
|
223
|
+
VALUE hook(VALUE self, VALUE hook_proc) {
|
224
|
+
|
225
|
+
tinfo_from_thread( rb_thread_current() )->hook_proc = hook_proc;
|
226
|
+
|
227
|
+
VALUE handle_method_method =rb_obj_method(hook_proc, ID2SYM(id_handle_method) );
|
228
|
+
VALUE handle_method_method_arity = rb_funcall( handle_method_method, rb_intern("arity"), 0 );
|
229
|
+
tinfo_from_thread( rb_thread_current() )->handle_method_arity = FIX2INT( handle_method_method_arity );
|
230
|
+
|
231
|
+
put_redirect_handler( rallhook_redirect_handler );
|
232
|
+
|
233
|
+
enable_redirect(tinfo_from_thread(rb_thread_current()));
|
234
|
+
|
235
|
+
if (rb_block_given_p() ) {
|
236
|
+
return rb_ensure(rb_yield, Qnil, unhook, self);
|
237
|
+
}
|
238
|
+
|
239
|
+
return Qnil;
|
240
|
+
}
|
241
|
+
|
242
|
+
|
243
|
+
/*
|
244
|
+
Disable the hook in the next N calls and reenable them. Useful to avoid infinite recursion and used
|
245
|
+
in RallHook::Helper::MethodWrapper
|
246
|
+
*/
|
247
|
+
VALUE from(VALUE self, VALUE num) {
|
248
|
+
redirect_left(tinfo_from_thread( rb_thread_current() ), FIX2INT(num)+1);
|
249
|
+
return self;
|
250
|
+
}
|
251
|
+
|
252
|
+
|
253
|
+
/*
|
254
|
+
Re-enable the hook if the hook was disabled and was activated previously with Hook#hook
|
255
|
+
If no call to Hook#hook has made, rehook does nothing
|
256
|
+
*/
|
257
|
+
VALUE rehook(VALUE unused) {
|
258
|
+
enable_redirect(tinfo_from_thread( rb_thread_current() ));
|
259
|
+
if (rb_block_given_p() ) {
|
260
|
+
return rb_ensure(rb_yield, Qnil, unhook, Qnil);
|
261
|
+
}
|
262
|
+
return Qnil;
|
263
|
+
}
|
264
|
+
|
265
|
+
|
266
|
+
|
267
|
+
VALUE thread_restore_attributes(AttachedThreadInfo* tinfo) {
|
268
|
+
|
269
|
+
AttachedThreadInfo* current_tinfo = tinfo_from_thread(rb_thread_current());
|
270
|
+
|
271
|
+
current_tinfo->hook_enabled = tinfo->hook_enabled;
|
272
|
+
current_tinfo->hook_enable_left = tinfo->hook_enable_left;
|
273
|
+
current_tinfo->hook_proc = tinfo->hook_proc;
|
274
|
+
current_tinfo->handle_method_arity = tinfo->handle_method_arity;
|
275
|
+
|
276
|
+
return Qnil;
|
277
|
+
}
|
278
|
+
|
279
|
+
VALUE rb_thread_acquire_attributes( VALUE thread ) {
|
280
|
+
|
281
|
+
AttachedThreadInfo* orig = tinfo_from_thread(thread);
|
282
|
+
AttachedThreadInfo* dest = tinfo_from_thread(rb_thread_current() );
|
283
|
+
|
284
|
+
AttachedThreadInfo oldattr;
|
285
|
+
|
286
|
+
oldattr.hook_enabled = dest->hook_enabled;
|
287
|
+
oldattr.hook_enable_left = dest->hook_enable_left;
|
288
|
+
oldattr.hook_proc = dest->hook_proc;
|
289
|
+
oldattr.handle_method_arity = dest->handle_method_arity;
|
290
|
+
|
291
|
+
dest->hook_enabled = orig->hook_enabled;
|
292
|
+
dest->hook_enable_left = orig->hook_enable_left;
|
293
|
+
dest->hook_proc = orig->hook_proc;
|
294
|
+
dest->handle_method_arity = orig->handle_method_arity;
|
295
|
+
|
296
|
+
if (rb_block_given_p() ) {
|
297
|
+
return rb_ensure(rb_yield, Qnil, thread_restore_attributes, (VALUE)&oldattr);
|
298
|
+
}
|
299
|
+
|
300
|
+
return Qnil;
|
301
|
+
}
|
302
|
+
|
303
|
+
|
304
|
+
extern void Init_rallhook_base() {
|
305
|
+
|
306
|
+
const char* initcode = "require 'rubygems'\n"
|
307
|
+
"require 'ruby-cymbol'\n";
|
308
|
+
|
309
|
+
rb_eval_string(initcode);
|
310
|
+
|
311
|
+
/*
|
312
|
+
This module brings together all classes and methods belonging to rallhook with the exception of Node
|
313
|
+
*/
|
314
|
+
rb_mRallHook = rb_define_module("RallHook");
|
315
|
+
/*
|
316
|
+
This class handles the hook, enabling and disable it.
|
317
|
+
|
318
|
+
Example:
|
319
|
+
|
320
|
+
# ... instanciate method_handler ... (see README and examples)
|
321
|
+
|
322
|
+
RallHook::Hook.hook method_hadler do
|
323
|
+
print "hello world\n" # calls to print, write or whatever are intercepted by method_handler#method_handle
|
324
|
+
end # in the finish of the block, the hook are disabled
|
325
|
+
|
326
|
+
*/
|
327
|
+
|
328
|
+
rb_cHook = rb_define_class_under(rb_mRallHook, "Hook", rb_cObject);
|
329
|
+
|
330
|
+
rb_define_singleton_method(rb_cHook, "rehook", rehook, 0 );
|
331
|
+
rb_define_singleton_method(rb_cHook, "hook", hook, 1);
|
332
|
+
rb_define_singleton_method(rb_cHook, "unhook", unhook, 0);
|
333
|
+
rb_define_singleton_method(rb_cHook, "from", from, 1);
|
334
|
+
|
335
|
+
/*
|
336
|
+
Marker module to indicate messages of method redirection
|
337
|
+
|
338
|
+
Example:
|
339
|
+
|
340
|
+
class MyMessage
|
341
|
+
include MethodRedirect # indicates that this message is about method redirection
|
342
|
+
|
343
|
+
def initialize(klass, recv, m)
|
344
|
+
@klass = klass
|
345
|
+
@recv = recv
|
346
|
+
@method = m
|
347
|
+
end
|
348
|
+
end
|
349
|
+
class MethodHandler
|
350
|
+
class X
|
351
|
+
def foo
|
352
|
+
end
|
353
|
+
end
|
354
|
+
def method_handle( ... )
|
355
|
+
MyMessage.new(X, x.new, :foo)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
Don't use this module, use x.redirect(:foo) instead.
|
360
|
+
|
361
|
+
Example:
|
362
|
+
class MethodHandler
|
363
|
+
class X
|
364
|
+
def foo
|
365
|
+
end
|
366
|
+
end
|
367
|
+
def method_handle( ... )
|
368
|
+
x.redirect(:foo)
|
369
|
+
end
|
370
|
+
end
|
371
|
+
*/
|
372
|
+
rb_mMethodRedirect = rb_define_module_under(rb_mRallHook, "MethodRedirect");
|
373
|
+
|
374
|
+
init_node();
|
375
|
+
init_restrict_def();
|
376
|
+
init_redirect();
|
377
|
+
|
378
|
+
id_call_ = rb_intern("call");
|
379
|
+
|
380
|
+
id_call = rb_intern("call");
|
381
|
+
id_method_wrapper = rb_intern("method_wrapper");
|
382
|
+
id_handle_method = rb_intern("handle_method");
|
383
|
+
id_return_value_var = rb_intern("@return_value");
|
384
|
+
id_klass_var = rb_intern("@klass");
|
385
|
+
id_recv_var = rb_intern("@recv");
|
386
|
+
id_method_var = rb_intern("@method");
|
387
|
+
id_unhook_var = rb_intern("@unhook");
|
388
|
+
id_binding = rb_intern("binding");
|
389
|
+
id_method_added = rb_intern("method_added");
|
390
|
+
id_hook_enabled = rb_intern("__hook_enabled");
|
391
|
+
id_hook_enable_left = rb_intern("__hook_enable_left");
|
392
|
+
id_hook_proc = rb_intern("__hook_proc");
|
393
|
+
|
394
|
+
rb_define_method(rb_cThread, "acquire_attributes", rb_thread_acquire_attributes,0);
|
395
|
+
|
396
|
+
}
|