rallhook 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|