evalhook 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,5 @@
1
+ 0.3.0 Refactor: removed C extension for hooking and use partialruby instead
2
+
1
3
  0.2.0 Added support to hook xstr nodes (system calls with backsticks and %x)
2
4
 
3
5
  Added MultiHookHandler (for multiple nested hooks)
data/Rakefile CHANGED
@@ -6,18 +6,18 @@ require 'rake/gempackagetask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = 'evalhook'
9
- s.version = '0.2.0'
9
+ s.version = '0.3.0'
10
10
  s.author = 'Dario Seminara'
11
11
  s.email = 'robertodarioseminara@gmail.com'
12
12
  s.platform = Gem::Platform::RUBY
13
13
  s.summary = 'Alternate eval which hook all methods executed in the evaluated code'
14
14
  s.homepage = "http://github.com/tario/evalhook"
15
- s.add_dependency "evalmimic", ">= 0.1.0"
15
+ s.add_dependency "partialruby", ">= 0.1.0"
16
+ s.add_dependency "ruby_parser", ">= 2.0.6"
16
17
  s.has_rdoc = true
17
18
  s.extra_rdoc_files = [ 'README' ]
18
19
  s.rdoc_options << '--main' << 'README'
19
- s.extensions = FileList["ext/**/extconf.rb"].to_a
20
- s.files = Dir.glob("{examples,lib,test}/**/*.rb") + Dir.glob("ext/**/*.c") + Dir.glob("ext/**/*.h") + Dir.glob("ext/**/extconf.rb") +
20
+ s.files = Dir.glob("{examples,lib,spec}/**/*.rb") + Dir.glob("ext/**/*.c") + Dir.glob("ext/**/*.h") + Dir.glob("ext/**/extconf.rb") +
21
21
  [ 'LICENSE', 'AUTHORS', 'CHANGELOG', 'README', 'Rakefile', 'TODO' ]
22
22
  end
23
23
 
@@ -0,0 +1,144 @@
1
+ =begin
2
+
3
+ This file is part of the evalhook project, http://github.com/tario/evalhook
4
+
5
+ Copyright (c) 2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ evalhook 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
+ evalhook 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 evalhook. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ require "ruby_parser"
22
+ require "partialruby"
23
+
24
+ module EvalHook
25
+ class HookContext < PartialRuby::PureRubyContext
26
+
27
+ def initialize(hook_handler)
28
+ @hook_handler = hook_handler
29
+ end
30
+
31
+ def const_path_emul(code)
32
+ if code.instance_of? Array
33
+ if (code.size == 1)
34
+ s(:const, code[-1].to_sym)
35
+ else
36
+ s(:colon2, const_path_emul(code[0..-2]), code[-1].to_sym)
37
+ end
38
+ else
39
+ const_path_emul code.split("::")
40
+ end
41
+ end
42
+
43
+ def ruby_emul_colon3(tree)
44
+ if @hook_handler.base_namespace
45
+ emul s(:colon2, const_path_emul(@hook_handler.base_namespace.to_s), tree[1])
46
+ else
47
+ super tree
48
+ end
49
+ end
50
+
51
+ def ruby_emul_call(tree)
52
+
53
+ method_name = tree[2]
54
+
55
+ if tree.respond_to?(:is_marked?)
56
+ return super(tree)
57
+ end
58
+
59
+ args1 = s(:arglist, s(:lit, method_name), marked(s(:call, nil, :binding, s(:arglist))))
60
+
61
+ args2 = s(:arglist, s(:lit, @hook_handler))
62
+
63
+ receiver = tree[1] || s(:self)
64
+
65
+ firstcall = nil
66
+ secondcall = nil
67
+
68
+ if tree[1]
69
+ firstcall = marked(s(:call, receiver, :local_hooked_method, args1))
70
+ secondcall = marked(s(:call, firstcall, :set_hook_handler, args2))
71
+ else
72
+ firstcall = marked(s(:call, receiver, :hooked_method, args1))
73
+ secondcall = marked(s(:call, firstcall, :set_hook_handler, args2))
74
+ end
75
+
76
+ super marked(s(:call, secondcall, :call, tree[3]))
77
+ end
78
+
79
+ def ruby_emul_dxstr(tree)
80
+
81
+ dstr_tree = tree.dup
82
+ dstr_tree[0] = :dstr
83
+
84
+ args = s(:arglist, dstr_tree )
85
+
86
+ emul marked(s(:call, s(:lit, @hook_handler), :hooked_xstr, args))
87
+ end
88
+
89
+ def ruby_emul_xstr(tree)
90
+ args = s(:arglist, s(:lit, tree[1]) )
91
+
92
+ emul marked(s(:call, s(:lit, @hook_handler), :hooked_xstr, args))
93
+ end
94
+
95
+ def ruby_emul_cdecl(tree)
96
+ const_tree = tree[1]
97
+ value_tree = tree[2]
98
+
99
+ base_class_tree = nil
100
+ const_id = nil
101
+
102
+ unless const_tree.instance_of? Symbol
103
+ if const_tree[0] == :colon2
104
+ base_class_tree = const_tree[1]
105
+ const_id = const_tree[2]
106
+ elsif const_tree[0] == :colon3
107
+ base_class_tree = s(:lit, Object)
108
+ const_id = const_tree[1]
109
+ end
110
+ else
111
+ base_class_tree = s(:lit, Object)
112
+ const_id = const_tree
113
+ end
114
+
115
+ args1 = s(:arglist, base_class_tree)
116
+ args2 = s(:arglist, s(:lit, const_id))
117
+ args3 = s(:arglist, value_tree)
118
+
119
+ firstcall = marked(s(:call, s(:lit, @hook_handler), :hooked_cdecl, args1 ))
120
+ secondcall = marked(s(:call, firstcall, :set_id, args2))
121
+ thirdcall = marked(s(:call, secondcall, :set_value, args3))
122
+
123
+ emul thirdcall
124
+ end
125
+
126
+ def ruby_emul_gasgn(tree)
127
+ args1 = s(:arglist, s(:lit, tree[1]))
128
+ args2 = s(:arglist, tree[2] )
129
+
130
+ firstcall = marked(s(:call, s(:lit, @hook_handler), :hooked_gasgn, args1))
131
+ secondcall = marked(s(:call, firstcall, :set_value, args2))
132
+ emul secondcall
133
+ end
134
+
135
+ private
136
+
137
+ def marked(ast)
138
+ def ast.is_marked?
139
+ true
140
+ end
141
+ ast
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,35 @@
1
+ =begin
2
+
3
+ This file is part of the evalhook project, http://github.com/tario/evalhook
4
+
5
+ Copyright (c) 2010 Roberto Dario Seminara <robertodarioseminara@gmail.com>
6
+
7
+ evalhook 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
+ evalhook 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 evalhook. if not, see <http://www.gnu.org/licenses/>.
19
+
20
+ =end
21
+ require "ruby_parser"
22
+
23
+ module EvalHook
24
+ def self.validate_syntax(code)
25
+ begin
26
+ RubyParser.new.parse(code)
27
+ rescue
28
+ raise SyntaxError
29
+ end
30
+ true
31
+ end
32
+
33
+ class HookHandler
34
+ end
35
+ end
@@ -18,7 +18,7 @@ you should have received a copy of the gnu general public license
18
18
  along with evalhook. if not, see <http://www.gnu.org/licenses/>.
19
19
 
20
20
  =end
21
- require "evalhook"
21
+ require "evalhook/hook_handler"
22
22
 
23
23
  module EvalHook
24
24
 
data/lib/evalhook.rb CHANGED
@@ -18,19 +18,25 @@ you should have received a copy of the gnu general public license
18
18
  along with evalhook. if not, see <http://www.gnu.org/licenses/>.
19
19
 
20
20
  =end
21
- require "evalhook"
22
- require "evalhook_base"
21
+ require "partialruby"
23
22
  require "evalhook/redirect_helper"
24
23
  require "evalhook/multi_hook_handler"
24
+ require "evalhook/hook_handler"
25
+ require "evalhook/hook_context"
26
+ begin
25
27
  require "evalmimic"
28
+ $evalmimic_defined = true
29
+ rescue LoadError
30
+ $evalmimic_defined = false
31
+ end
26
32
 
27
33
 
28
34
  class Object
29
- def local_hooked_method(mname)
30
- EvalHook::HookedMethod.new(self,mname,true)
35
+ def local_hooked_method(mname,_binding)
36
+ EvalHook::HookedMethod.new(self,mname,true,_binding)
31
37
  end
32
- def hooked_method(mname)
33
- EvalHook::HookedMethod.new(self,mname,false)
38
+ def hooked_method(mname,_binding)
39
+ EvalHook::HookedMethod.new(self,mname,false,_binding)
34
40
  end
35
41
  end
36
42
 
@@ -39,10 +45,11 @@ module EvalHook
39
45
  # used internally
40
46
  class HookedMethod
41
47
 
42
- def initialize(recv, m,localcall)
48
+ def initialize(recv, m,localcall,_binding)
43
49
  @recv = recv
44
50
  @m = m
45
51
  @localcall = localcall
52
+ @_binding = _binding
46
53
  end
47
54
 
48
55
  # used internally
@@ -62,8 +69,15 @@ module EvalHook
62
69
  method_handler = @method_handler
63
70
  ret = nil
64
71
 
65
- klass = @klass || @recv.method(@m).owner
66
72
  method_name = @m
73
+ if args.length == 0
74
+ local_vars = @_binding.eval("local_variables").map(&:to_s)
75
+ if local_vars.include? method_name.to_s
76
+ return @_binding.eval(method_name.to_s)
77
+ end
78
+ end
79
+
80
+ klass = @klass || @recv.method(@m).owner
67
81
  recv = @recv
68
82
 
69
83
  if method_handler
@@ -215,35 +229,35 @@ module EvalHook
215
229
  runstr = handle_xstr(str) || str
216
230
  end
217
231
 
218
- define_eval_method :evalhook
232
+ def evalhook_i(code, b_ = nil, name = "(eval)", line = 1)
233
+
234
+ EvalHook.validate_syntax code
235
+
236
+ tree = RubyParser.new.parse code
237
+
238
+ context = EvalHook::HookContext.new(self)
239
+ emulationcode = context.emul tree
240
+
241
+ eval emulationcode, b_, name, line
219
242
 
220
- # used internally
221
- def internal_eval(b_, original_args)
222
- raise ArgumentError if original_args.size == 0
223
- evalhook_i(original_args[0], original_args[1] || b_, original_args[2] || "(eval)", original_args[3] || 0)
224
243
  end
225
244
 
226
- def evalhook_i(code, b_ = nil, name = "(eval)", line = 1)
245
+ if ($evalmimic_defined)
227
246
 
228
- EvalHook.validate_syntax code
247
+ define_eval_method :evalhook
229
248
 
230
- code = "
231
- retvalue = nil
232
- EvalHook.double_run do |run|
233
- ( if (run)
234
- retvalue = begin
235
- #{code}
236
- end
237
- EvalHook::FakeEvalHook
238
- else
239
- EvalHook
240
- end ).hook_block(ObjectSpace._id2ref(#{object_id}))
241
- end
242
- retvalue
243
- "
244
- eval(code, b_, name, line)
249
+ # used internally
250
+ def internal_eval(b_, original_args)
251
+ raise ArgumentError if original_args.size == 0
252
+ evalhook_i(original_args[0], original_args[1] || b_, original_args[2] || "(eval)", original_args[3] || 0)
253
+ end
245
254
 
255
+ else
256
+ def evalhook(*args)
257
+ evalhook_i(*args)
258
+ end
246
259
  end
260
+
247
261
  end
248
262
 
249
263
  module ModuleMethods
@@ -0,0 +1,15 @@
1
+ require "rubygems"
2
+ require "evalhook"
3
+
4
+ describe EvalHook::HookHandler, "hook handler" do
5
+
6
+ it "should accept specified source name" do
7
+ hh = EvalHook::HookHandler.new
8
+
9
+ begin
10
+ hh.evalhook("raise '0'",binding, "sourcename", 1)
11
+ rescue Exception => e
12
+ e.backtrace.join.include? ("sourcename").should be == true
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,230 @@
1
+ require "rubygems"
2
+ require "evalhook"
3
+
4
+ class NilClass
5
+ def strip
6
+ ""
7
+ end
8
+ end
9
+
10
+ describe EvalHook::HookHandler, "hook handler defaults" do
11
+ it "should throw exception when call evalhook with no parameters" do
12
+ hook_handler = EvalHook::HookHandler.new
13
+
14
+ lambda {
15
+ hook_handler.evalhook
16
+ }.should raise_error(ArgumentError)
17
+ end
18
+
19
+ exprlist = ["1+1", "[1,2,3].map{|x| x*2}"]
20
+
21
+ exprlist.each do |expr|
22
+ it "should eval expresion '#{expr}' same as eval" do
23
+ hook_handler = EvalHook::HookHandler.new
24
+
25
+ expected = eval(expr)
26
+ hook_handler.evalhook(expr).should be == expected
27
+
28
+ end
29
+ end
30
+
31
+ it "should allow reference to global variables" do
32
+ hook_handler = EvalHook::HookHandler.new
33
+
34
+ $global_variable_test = 5
35
+ hook_handler.evalhook("$global_variable_test").should be == $global_variable_test
36
+ end
37
+
38
+ it "should allow reference to constants" do
39
+ hook_handler = EvalHook::HookHandler.new
40
+
41
+ CONSTANTTEST = 5
42
+ hook_handler.evalhook("CONSTANTTEST").should be == CONSTANTTEST
43
+ end
44
+
45
+ it "should allow reference to local variables" do
46
+ hook_handler = EvalHook::HookHandler.new
47
+
48
+ a = 5
49
+ hook_handler.evalhook("a", binding).should be == a
50
+ end
51
+
52
+
53
+ class N
54
+ def foo(hook_handler)
55
+ @a = 5
56
+ hook_handler.evalhook("@a", binding)
57
+ end
58
+ end
59
+
60
+
61
+ it "should allow reference to instance variables" do
62
+ hook_handler = EvalHook::HookHandler.new
63
+ N.new.foo(hook_handler).should be == 5
64
+ end
65
+
66
+ class X
67
+ def foo
68
+ 3
69
+ end
70
+ end
71
+
72
+ it "should allow method calls" do
73
+ hook_handler = EvalHook::HookHandler.new
74
+ hook_handler.evalhook("X.new.foo").should be X.new.foo
75
+ end
76
+
77
+ it "should capture method calls" do
78
+ hook_handler = EvalHook::HookHandler.new
79
+
80
+ hook_handler.should_receive(:handle_method).with(X.class,X,:new)
81
+ hook_handler.should_receive(:handle_method).with(X,anything(),:foo)
82
+
83
+ hook_handler.evalhook("X.new.foo")
84
+ end
85
+
86
+ it "should capture constant assignment" do
87
+ hook_handler = EvalHook::HookHandler.new
88
+
89
+ hook_handler.should_receive(:handle_cdecl).with(Object,:TEST_CONSTANT,4)
90
+ hook_handler.evalhook("TEST_CONSTANT = 4")
91
+
92
+ end
93
+
94
+ it "should capture global assignment" do
95
+ hook_handler = EvalHook::HookHandler.new
96
+
97
+ hook_handler.should_receive(:handle_gasgn).with(:$test_global_variable,4)
98
+ hook_handler.evalhook("$test_global_variable = 4")
99
+
100
+ end
101
+
102
+ it "should capture system exec with backsticks" do
103
+ hook_handler = EvalHook::HookHandler.new
104
+
105
+ hook_handler.should_receive(:handle_xstr).with("echo test")
106
+ hook_handler.evalhook("`echo test`")
107
+
108
+ end
109
+
110
+ it "should capture system exec with backsticks and dynamic strings" do
111
+ hook_handler = EvalHook::HookHandler.new
112
+
113
+ hook_handler.should_receive(:handle_xstr).with("echo test")
114
+ hook_handler.evalhook("`echo \#{}test`")
115
+
116
+ end
117
+
118
+ it "should capture system exec with %x" do
119
+ hook_handler = EvalHook::HookHandler.new
120
+
121
+ hook_handler.should_receive(:handle_xstr).with("echo test")
122
+ hook_handler.evalhook("%x[echo test]")
123
+ end
124
+
125
+
126
+ module B
127
+
128
+ end
129
+ module A
130
+ module B
131
+ class C
132
+
133
+ end
134
+
135
+ end
136
+ end
137
+
138
+ it "should allow define base_namespace" do
139
+ hook_handler = EvalHook::HookHandler.new
140
+
141
+ hook_handler.base_namespace = :A
142
+ hook_handler.evalhook("::B").should be == A::B
143
+ end
144
+
145
+ it "should allow define base_namespace (const)" do
146
+ hook_handler = EvalHook::HookHandler.new
147
+
148
+ hook_handler.base_namespace = A
149
+ hook_handler.evalhook("::B").should be == A::B
150
+ end
151
+
152
+ class C1
153
+ def foo
154
+ "C1#foo"
155
+ end
156
+ end
157
+ module A1
158
+ class C1
159
+ def foo
160
+ "A1::C1#foo"
161
+ end
162
+ end
163
+ end
164
+
165
+ it "should allow define base_namespace (class)" do
166
+ hook_handler = EvalHook::HookHandler.new
167
+
168
+ hook_handler.base_namespace = A1
169
+ hook_handler.evalhook("class ::C1
170
+ def foo
171
+ 'A1::C1#foo at evalhook'
172
+ end
173
+ end")
174
+
175
+ C1.new.foo.should be == "C1#foo" # C1#foo class remains unchanged
176
+ A1::C1.new.foo.should be == "A1::C1#foo at evalhook" # A1::C1#foo changes
177
+ end
178
+
179
+
180
+ class C2
181
+ def foo
182
+ "C2#foo"
183
+ end
184
+ end
185
+
186
+ it "should default base_namespace to Object" do
187
+ hook_handler = EvalHook::HookHandler.new
188
+
189
+ hook_handler.evalhook("
190
+
191
+ module Z
192
+ class ::C2
193
+ def foo
194
+ '::C2#foo at evalhook'
195
+ end
196
+ end
197
+
198
+ end
199
+ ")
200
+
201
+ C2.new.foo.should be == "::C2#foo at evalhook"
202
+ end
203
+
204
+ module A1
205
+ module A2
206
+
207
+ end
208
+ end
209
+
210
+
211
+ it "should allow define base_namespace (3 levels)" do
212
+ hook_handler = EvalHook::HookHandler.new
213
+
214
+ hook_handler.base_namespace = A1::A2
215
+ hook_handler.evalhook("class ::C1
216
+ def foo
217
+ 'A1::A2::C1#foo at evalhook'
218
+ end
219
+ end")
220
+
221
+ C1.new.foo.should be == "C1#foo" # C1#foo class remains unchanged
222
+ A1::A2::C1.new.foo.should be == "A1::A2::C1#foo at evalhook" # A1::C1#foo changes
223
+ end
224
+
225
+ it "should use current binding when not specified" do
226
+ a = 9
227
+ EvalHook::HookHandler.new.evalhook("a").should be == 9
228
+ end
229
+ end
230
+
@@ -0,0 +1,46 @@
1
+ require "rubygems"
2
+ require "evalhook"
3
+
4
+ describe EvalHook::HookHandler, "hook handler hooks" do
5
+
6
+ class X2
7
+ def foo
8
+ 9
9
+ end
10
+
11
+ def bar
12
+ 4
13
+ end
14
+ end
15
+
16
+ it "should intercept and change method calls" do
17
+ hh = EvalHook::HookHandler.new
18
+ def hh.handle_method(klass, recv, method_name)
19
+ redirect_method(klass, recv, :bar)
20
+ end
21
+
22
+ x = X2.new
23
+ hh.evalhook("x.foo", binding).should be == 4
24
+ end
25
+
26
+ it "should intercept and change global variable assignments" do
27
+ hh = EvalHook::HookHandler.new
28
+ def hh.handle_gasgn(global_id, value)
29
+ redirect_gasgn(global_id, 7)
30
+ end
31
+
32
+ hh.evalhook("$test_global_variable = 9")
33
+ $test_global_variable.should be == 7
34
+ end
35
+
36
+ it "should intercept and change constant assignments" do
37
+ hh = EvalHook::HookHandler.new
38
+ def hh.handle_cdecl(context, name, value)
39
+ redirect_cdecl(context,name,7)
40
+ end
41
+
42
+ hh.evalhook("TEST_CONSTANT = 9")
43
+ TEST_CONSTANT.should be == 7
44
+ end
45
+
46
+ end
@@ -0,0 +1,67 @@
1
+ require "rubygems"
2
+ require "evalhook"
3
+
4
+ describe EvalHook::MultiHookHandler, "multiple hook handler redirect" do
5
+
6
+ class X
7
+ def self.foo
8
+
9
+ end
10
+ end
11
+
12
+ handles = [
13
+ [:handle_method, "X.foo"],
14
+ [:handle_cdecl, "TEST_CONSTANT = 4"],
15
+ [:handle_gasgn, "$test_global_variable = 4"] ]
16
+
17
+ handles.each do |handle|
18
+ it "should recall #{handle[0]} in correct order" do
19
+ hook_handler = EvalHook::MultiHookHandler.new
20
+
21
+ nested_handler_1 = EvalHook::HookHandler.new
22
+ nested_handler_2 = EvalHook::HookHandler.new
23
+ hook_handler.add nested_handler_1
24
+ hook_handler.add nested_handler_2
25
+
26
+ nested_handler_1.should_receive(handle[0])
27
+ nested_handler_2.should_receive(handle[0])
28
+
29
+ hook_handler.evalhook(handle[1])
30
+ end
31
+ end
32
+
33
+ class X_
34
+ def foo
35
+ nil
36
+ end
37
+
38
+ def bar
39
+ nil
40
+ end
41
+ end
42
+
43
+
44
+ it "should handle redirects on recall of handle_method" do
45
+ hook_handler = EvalHook::MultiHookHandler.new
46
+
47
+ nested_handler_1 = EvalHook::HookHandler.new
48
+ nested_handler_2 = EvalHook::HookHandler.new
49
+ hook_handler.add nested_handler_1
50
+ hook_handler.add nested_handler_2
51
+
52
+ x = X_.new
53
+
54
+ def nested_handler_1.handle_method(klass, recv, m )
55
+ RedirectHelper::Redirect.new(klass, recv, :bar)
56
+ end
57
+
58
+ nested_handler_2.should_receive(:handle_method).
59
+ with(X_, x, :bar).
60
+ and_return(nil)
61
+
62
+ hook_handler.evalhook("x.foo", binding)
63
+
64
+
65
+ end
66
+
67
+ end