evalhook 0.2.0 → 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.
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