evalhook 0.3.1 → 0.4.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,15 @@
1
+ 0.4.0 Removed evalmimic dependency
2
+
3
+ Implemented hooking of super as methods calls
4
+
5
+ Added hooking for colon2 namespace access (handle_colon2)
6
+
7
+ Added hooking for global variable read acess (handle_gvar)
8
+
9
+ Added hooking for constant read access (handle_const)
10
+
11
+ Refactor of AST processing code (using SexpProcessor)
12
+
1
13
  0.3.1 Fixed bug when use reflection on call handler (thanks jembezmamy for the feedback!)
2
14
 
3
15
  0.3.0 Refactor: removed C extension for hooking and use partialruby instead
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.3.1'
9
+ s.version = '0.4.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 "partialruby", ">= 0.1.0"
15
+ s.add_dependency "partialruby", ">= 0.2.0"
16
16
  s.add_dependency "ruby_parser", ">= 2.0.6"
17
17
  s.has_rdoc = true
18
18
  s.extra_rdoc_files = [ 'README' ]
19
19
  s.rdoc_options << '--main' << 'README'
20
- s.files = Dir.glob("{examples,lib,spec}/**/*.rb") + Dir.glob("ext/**/*.c") + Dir.glob("ext/**/*.h") + Dir.glob("ext/**/extconf.rb") +
20
+ s.files = Dir.glob("{examples,lib,spec}/**/*.rb") +
21
21
  [ 'LICENSE', 'AUTHORS', 'CHANGELOG', 'README', 'Rakefile', 'TODO' ]
22
22
  end
23
23
 
@@ -33,7 +33,7 @@ end
33
33
  desc 'Generate RDoc'
34
34
  Rake::RDocTask.new :rdoc do |rd|
35
35
  rd.rdoc_dir = 'doc'
36
- rd.rdoc_files.add 'lib', 'ext', 'README'
36
+ rd.rdoc_files.add 'lib', 'README'
37
37
  rd.main = 'README'
38
38
  end
39
39
 
@@ -73,6 +73,21 @@ module RedirectHelper
73
73
  end
74
74
  end
75
75
 
76
+ class Value
77
+ attr_reader :value
78
+ def initialize(value)
79
+ @value = value
80
+ end
81
+ end
82
+
83
+ def global_value(value)
84
+ Value.new(value)
85
+ end
86
+
87
+ def const_value(value)
88
+ Value.new(value)
89
+ end
90
+
76
91
  def redirect_method(*args)
77
92
  Redirect.new(*args)
78
93
  end
@@ -0,0 +1,279 @@
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
+ require "sexp_processor"
24
+
25
+ module EvalHook
26
+
27
+
28
+ class TreeProcessor < SexpProcessor
29
+ def initialize(hook_handler)
30
+ super()
31
+ @hook_handler = hook_handler
32
+ @def_scope = Array.new
33
+ self.require_empty = false
34
+ end
35
+
36
+ def const_path_emul(code)
37
+ if code.instance_of? Array
38
+ if (code.size == 1)
39
+ s(:const, code[-1].to_sym)
40
+ else
41
+ s(:colon2, const_path_emul(code[0..-2]), code[-1].to_sym)
42
+ end
43
+ else
44
+ const_path_emul code.split("::")
45
+ end
46
+ end
47
+
48
+ def hook_handler_reference
49
+ # create a global_variable name
50
+ unless @hh_ref_global_name
51
+ global_variable_name = "$hook_handler_" + rand(10000000000).to_s
52
+ eval("#{global_variable_name} = @hook_handler")
53
+
54
+ @hh_ref_global_name = global_variable_name.to_sym
55
+ end
56
+
57
+ s(:gvar, @hh_ref_global_name)
58
+ end
59
+
60
+ def process_gasgn(tree)
61
+
62
+ args1 = s(:arglist, s(:lit, tree[1]))
63
+ args2 = s(:arglist, process(tree[2]))
64
+
65
+ firstcall = s(:call, hook_handler_reference, :hooked_gasgn, args1)
66
+
67
+ s(:call, firstcall, :set_value, args2)
68
+
69
+ end
70
+
71
+
72
+ def process_call(tree)
73
+
74
+ original_receiver = tree[1]
75
+
76
+ receiver = process(original_receiver|| s(:self))
77
+
78
+ args1 = s(:arglist, s(:lit, tree[2]), s(:call, nil, :binding, s(:arglist)))
79
+ args2 = s(:arglist, hook_handler_reference)
80
+
81
+ firstcall = nil
82
+ secondcall = nil
83
+
84
+ if original_receiver
85
+ firstcall = s(:call, receiver, :local_hooked_method, args1)
86
+ secondcall = s(:call, firstcall, :set_hook_handler, args2)
87
+ else
88
+ firstcall = s(:call, receiver, :hooked_method, args1)
89
+ secondcall = s(:call, firstcall, :set_hook_handler, args2)
90
+ end
91
+
92
+ s(:call, secondcall, :call, process(tree[3]))
93
+ end
94
+
95
+ def process_cdecl(tree)
96
+
97
+ const_tree = tree[1]
98
+ value_tree = tree[2]
99
+
100
+ base_class_tree = nil
101
+ const_id = nil
102
+
103
+ unless const_tree.instance_of? Symbol
104
+ if const_tree[0] == :colon2
105
+ base_class_tree = const_tree[1]
106
+ const_id = const_tree[2]
107
+ elsif const_tree[0] == :colon3
108
+ base_class_tree = s(:lit, Object)
109
+ const_id = const_tree[1]
110
+ end
111
+ else
112
+ base_class_tree = s(:lit, Object)
113
+ const_id = const_tree
114
+ end
115
+
116
+ args1 = s(:arglist, base_class_tree)
117
+ args2 = s(:arglist, s(:lit, const_id))
118
+ args3 = s(:arglist, process(value_tree))
119
+
120
+ firstcall = s(:call, hook_handler_reference, :hooked_cdecl, args1 )
121
+ secondcall = s(:call, firstcall, :set_id, args2)
122
+
123
+ s(:call, secondcall, :set_value, args3)
124
+ end
125
+
126
+ def process_dxstr(tree)
127
+ dstr_tree = tree.dup
128
+ dstr_tree[0] = :dstr
129
+
130
+ args = s(:arglist, process(dstr_tree) )
131
+ s(:call, hook_handler_reference, :hooked_xstr, args)
132
+ end
133
+
134
+ def process_xstr(tree)
135
+ args = s(:arglist, s(:lit, tree[1]) )
136
+ s(:call, hook_handler_reference, :hooked_xstr, args)
137
+ end
138
+
139
+ def process_module(tree)
140
+ module_name_tree = tree[1]
141
+ if module_name_tree.instance_of? Sexp
142
+ if module_name_tree.first == :colon3
143
+ module_name_tree = process_colon3(module_name_tree)
144
+ end
145
+ end
146
+
147
+ class_scope(nil) {
148
+ s(:module, module_name_tree, process(tree[2]))
149
+ }
150
+ end
151
+
152
+ def class_scope(class_name)
153
+ @def_scope.push(class_name)
154
+ ret = yield
155
+ @def_scope.pop
156
+ ret
157
+ end
158
+
159
+ def process_class(tree)
160
+ class_name_tree = tree[1]
161
+ if class_name_tree.instance_of? Sexp
162
+ if class_name_tree.first == :colon3
163
+ class_name_tree = process_colon3(class_name_tree)
164
+ end
165
+ end
166
+
167
+ if tree[2]
168
+ class_scope(tree[1]) do
169
+ s(:class, class_name_tree, process(tree[2]), process(tree[3]))
170
+ end
171
+ else
172
+ class_scope(tree[1]) do
173
+ s(:class, class_name_tree, nil, process(tree[3]))
174
+ end
175
+ end
176
+ end
177
+
178
+ def process_colon2(tree)
179
+ name = tree[2].to_s
180
+ args = s(:arglist, process(tree[1]), s(:str, name))
181
+ s(:call, hook_handler_reference, :hooked_colon2, args)
182
+ end
183
+
184
+ def process_colon3(tree)
185
+ if @hook_handler.base_namespace
186
+ s(:colon2, const_path_emul(@hook_handler.base_namespace.to_s), tree[1])
187
+ else
188
+ s(:colon3, tree[1])
189
+ end
190
+ end
191
+
192
+ def process_gvar(tree)
193
+ name = tree[1]
194
+
195
+ args = s(:arglist, s(:lit, name))
196
+ s(:call, hook_handler_reference, :hooked_gvar, args)
197
+ end
198
+
199
+ def process_const(tree)
200
+ name = tree[1].to_s
201
+ args = s(:arglist, s(:str, name))
202
+ s(:call, hook_handler_reference, :hooked_const, args)
203
+ end
204
+
205
+ def process_defn(tree)
206
+ @last_args = tree[2]
207
+ @last_method_name = tree[1]
208
+
209
+ s(tree[0],tree[1],tree[2],process(tree[3]))
210
+ end
211
+
212
+ def superclass_call_tree
213
+ current_class_call = nil
214
+
215
+ def_class = @def_scope.last
216
+ if def_class.instance_of? Symbol
217
+ current_class_call = s(:const, def_class)
218
+ s(:call, current_class_call, :superclass, s(:arglist))
219
+ elsif def_class.instance_of? Sexp
220
+ current_class_call = def_class
221
+ s(:call, current_class_call, :superclass, s(:arglist))
222
+ elsif def_class.instance_of? String
223
+ s(:call, s(:self), :class, s(:arglist))
224
+ else
225
+ current_class_call = s(:call, nil, :raise, s(:arglist, s(:const, :SecurityError)))
226
+ s(:call, current_class_call, :superclass, s(:arglist))
227
+ end
228
+ end
229
+
230
+ def process_super(tree)
231
+
232
+ receiver = s(:self)
233
+
234
+ args1 = s(:arglist, s(:lit, @last_method_name), s(:call, nil, :binding, s(:arglist)))
235
+ args2 = s(:arglist, hook_handler_reference)
236
+
237
+ firstcall = s(:call, receiver, :local_hooked_method, args1)
238
+ secondcall = s(:call, firstcall, :set_hook_handler, args2)
239
+ thirdcall = s(:call,secondcall,:set_class,s(:arglist, superclass_call_tree))
240
+
241
+ # pass the args passed to super
242
+ s(:call, thirdcall, :call, s(:arglist, *tree[1..-1]))
243
+
244
+ end
245
+
246
+ def process_defs(tree)
247
+ block_tree = class_scope( "" ) {
248
+ process(tree[4])
249
+ }
250
+ s(:defs, process(tree[1]), tree[2], tree[3], block_tree)
251
+ end
252
+
253
+ def process_zsuper(tree)
254
+
255
+ receiver = s(:self)
256
+
257
+ args1 = s(:arglist, s(:lit, @last_method_name), s(:call, nil, :binding, s(:arglist)))
258
+ args2 = s(:arglist, hook_handler_reference)
259
+
260
+ firstcall = s(:call, receiver, :local_hooked_method, args1)
261
+ secondcall = s(:call, firstcall, :set_hook_handler, args2)
262
+ thirdcall = s(:call,secondcall,:set_class,s(:arglist, superclass_call_tree))
263
+
264
+ # pass the args of the current defn
265
+
266
+ args = s(:arglist)
267
+ @last_args[1..-1].each do |arg_sym|
268
+ if arg_sym.to_s[0] == "*"
269
+ args << s(:splat, s(:lvar, arg_sym.to_s[1..-1].to_sym))
270
+ else
271
+ args << s(:lvar, arg_sym)
272
+ end
273
+ end
274
+
275
+ s(:call, thirdcall, :call, args)
276
+ end
277
+
278
+ end
279
+ end
data/lib/evalhook.rb CHANGED
@@ -22,7 +22,8 @@ require "partialruby"
22
22
  require "evalhook/redirect_helper"
23
23
  require "evalhook/multi_hook_handler"
24
24
  require "evalhook/hook_handler"
25
- require "evalhook/hook_context"
25
+ require "evalhook/tree_processor"
26
+
26
27
  begin
27
28
  require "evalmimic"
28
29
  $evalmimic_defined = true
@@ -182,6 +183,36 @@ module EvalHook
182
183
  end
183
184
  end
184
185
 
186
+ # used internally
187
+ def hooked_gvar(global_id)
188
+ ret = handle_gvar(global_id)
189
+ if ret
190
+ ret.value
191
+ else
192
+ eval(global_id.to_s)
193
+ end
194
+ end
195
+
196
+ # used internally
197
+ def hooked_const(name)
198
+ ret = handle_const(name)
199
+ if ret
200
+ ret.value
201
+ else
202
+ Object.const_get(name)
203
+ end
204
+ end
205
+
206
+ # used internally
207
+ def hooked_colon2(context,name)
208
+ ret = handle_colon2(context, name)
209
+ if ret
210
+ ret.value
211
+ else
212
+ context.const_get(name)
213
+ end
214
+ end
215
+
185
216
  # used internally
186
217
  def hooked_cdecl(context)
187
218
  HookCdecl.new(context,self)
@@ -217,6 +248,20 @@ module EvalHook
217
248
  nil
218
249
  end
219
250
 
251
+ # Overwrite to handle const read access
252
+ def handle_const(name)
253
+ nil
254
+ end
255
+
256
+ def handle_colon2(context,name)
257
+ nil
258
+ end
259
+
260
+ # Overwrite to handle global variable read access
261
+ def handle_gvar(global_id)
262
+ nil
263
+ end
264
+
220
265
  # used internally
221
266
  def hooked_super(*args)
222
267
  hm = caller_obj(2).hooked_method(caller_method(2))
@@ -243,7 +288,10 @@ module EvalHook
243
288
 
244
289
  tree = RubyParser.new.parse code
245
290
 
246
- context = EvalHook::HookContext.new(self)
291
+ context = PartialRuby::PureRubyContext.new
292
+
293
+ tree = EvalHook::TreeProcessor.new(self).process(tree)
294
+
247
295
  emulationcode = context.emul tree
248
296
 
249
297
  eval emulationcode, b_, name, line
@@ -23,7 +23,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
23
23
  hook_handler = EvalHook::HookHandler.new
24
24
 
25
25
  expected = eval(expr)
26
- hook_handler.evalhook(expr).should be == expected
26
+ hook_handler.evalhook(expr, binding).should be == expected
27
27
 
28
28
  end
29
29
  end
@@ -32,14 +32,14 @@ describe EvalHook::HookHandler, "hook handler defaults" do
32
32
  hook_handler = EvalHook::HookHandler.new
33
33
 
34
34
  $global_variable_test = 5
35
- hook_handler.evalhook("$global_variable_test").should be == $global_variable_test
35
+ hook_handler.evalhook("$global_variable_test", binding).should be == $global_variable_test
36
36
  end
37
37
 
38
38
  it "should allow reference to constants" do
39
39
  hook_handler = EvalHook::HookHandler.new
40
40
 
41
41
  CONSTANTTEST = 5
42
- hook_handler.evalhook("CONSTANTTEST").should be == CONSTANTTEST
42
+ hook_handler.evalhook("CONSTANTTEST", binding).should be == CONSTANTTEST
43
43
  end
44
44
 
45
45
  it "should allow reference to local variables" do
@@ -71,7 +71,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
71
71
 
72
72
  it "should allow method calls" do
73
73
  hook_handler = EvalHook::HookHandler.new
74
- hook_handler.evalhook("X.new.foo").should be X.new.foo
74
+ hook_handler.evalhook("X.new.foo", binding).should be X.new.foo
75
75
  end
76
76
 
77
77
  it "should capture method calls" do
@@ -80,14 +80,14 @@ describe EvalHook::HookHandler, "hook handler defaults" do
80
80
  hook_handler.should_receive(:handle_method).with(X.class,X,:new)
81
81
  hook_handler.should_receive(:handle_method).with(X,anything(),:foo)
82
82
 
83
- hook_handler.evalhook("X.new.foo")
83
+ hook_handler.evalhook("X.new.foo", binding)
84
84
  end
85
85
 
86
86
  it "should capture constant assignment" do
87
87
  hook_handler = EvalHook::HookHandler.new
88
88
 
89
89
  hook_handler.should_receive(:handle_cdecl).with(Object,:TEST_CONSTANT,4)
90
- hook_handler.evalhook("TEST_CONSTANT = 4")
90
+ hook_handler.evalhook("TEST_CONSTANT = 4", binding)
91
91
 
92
92
  end
93
93
 
@@ -95,7 +95,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
95
95
  hook_handler = EvalHook::HookHandler.new
96
96
 
97
97
  hook_handler.should_receive(:handle_gasgn).with(:$test_global_variable,4)
98
- hook_handler.evalhook("$test_global_variable = 4")
98
+ hook_handler.evalhook("$test_global_variable = 4", binding)
99
99
 
100
100
  end
101
101
 
@@ -103,7 +103,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
103
103
  hook_handler = EvalHook::HookHandler.new
104
104
 
105
105
  hook_handler.should_receive(:handle_xstr).with("echo test")
106
- hook_handler.evalhook("`echo test`")
106
+ hook_handler.evalhook("`echo test`", binding)
107
107
 
108
108
  end
109
109
 
@@ -111,7 +111,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
111
111
  hook_handler = EvalHook::HookHandler.new
112
112
 
113
113
  hook_handler.should_receive(:handle_xstr).with("echo test")
114
- hook_handler.evalhook("`echo \#{}test`")
114
+ hook_handler.evalhook("`echo \#{}test`", binding)
115
115
 
116
116
  end
117
117
 
@@ -119,7 +119,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
119
119
  hook_handler = EvalHook::HookHandler.new
120
120
 
121
121
  hook_handler.should_receive(:handle_xstr).with("echo test")
122
- hook_handler.evalhook("%x[echo test]")
122
+ hook_handler.evalhook("%x[echo test]", binding)
123
123
  end
124
124
 
125
125
 
@@ -139,14 +139,14 @@ describe EvalHook::HookHandler, "hook handler defaults" do
139
139
  hook_handler = EvalHook::HookHandler.new
140
140
 
141
141
  hook_handler.base_namespace = :A
142
- hook_handler.evalhook("::B").should be == A::B
142
+ hook_handler.evalhook("::B", binding).should be == A::B
143
143
  end
144
144
 
145
145
  it "should allow define base_namespace (const)" do
146
146
  hook_handler = EvalHook::HookHandler.new
147
147
 
148
148
  hook_handler.base_namespace = A
149
- hook_handler.evalhook("::B").should be == A::B
149
+ hook_handler.evalhook("::B", binding).should be == A::B
150
150
  end
151
151
 
152
152
  class C1
@@ -170,7 +170,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
170
170
  def foo
171
171
  'A1::C1#foo at evalhook'
172
172
  end
173
- end")
173
+ end" , binding)
174
174
 
175
175
  C1.new.foo.should be == "C1#foo" # C1#foo class remains unchanged
176
176
  A1::C1.new.foo.should be == "A1::C1#foo at evalhook" # A1::C1#foo changes
@@ -196,7 +196,7 @@ describe EvalHook::HookHandler, "hook handler defaults" do
196
196
  end
197
197
 
198
198
  end
199
- ")
199
+ ", binding)
200
200
 
201
201
  C2.new.foo.should be == "::C2#foo at evalhook"
202
202
  end
@@ -216,15 +216,14 @@ describe EvalHook::HookHandler, "hook handler defaults" do
216
216
  def foo
217
217
  'A1::A2::C1#foo at evalhook'
218
218
  end
219
- end")
219
+ end", binding)
220
220
 
221
221
  C1.new.foo.should be == "C1#foo" # C1#foo class remains unchanged
222
222
  A1::A2::C1.new.foo.should be == "A1::A2::C1#foo at evalhook" # A1::C1#foo changes
223
223
  end
224
224
 
225
- it "should use current binding when not specified" do
226
- a = 9
227
- EvalHook::HookHandler.new.evalhook("a").should be == 9
225
+ it "should allow declaring classes with ::" do
226
+ EvalHook::HookHandler.new.evalhook("class Fixnum::TestClass12345; end", binding)
228
227
  end
229
228
 
230
229
  module TestInheritedClassMethodError
@@ -243,5 +242,187 @@ describe EvalHook::HookHandler, "hook handler defaults" do
243
242
  hh.evalhook('TestInheritedClassMethodError::B.foo', binding)
244
243
  end
245
244
 
245
+ it "should allow declaring modules with ::" do
246
+ EvalHook::HookHandler.new.evalhook("module Fixnum::TestModule12345; end", binding)
247
+ end
248
+
249
+ module TestModule12347
250
+ end
251
+
252
+ it "should allow assignment of constants nested on modules" do
253
+ EvalHook::HookHandler.new.evalhook("TestModule12347::A = 9", binding)
254
+ end
255
+
256
+ class XTEST44
257
+ def foo(a)
258
+ a+1
259
+ end
260
+ end
261
+ it "should pass arguments on super" do
262
+ EvalHook::HookHandler.new.evalhook('
263
+ class YTEST44 < XTEST44
264
+ def foo(a)
265
+ super
266
+ end
267
+ end
268
+ YTEST44.new.foo(9)
269
+ ', binding).should be == 10
270
+ end
271
+
272
+ it "should pass arguments on super (explicitly)" do
273
+ EvalHook::HookHandler.new.evalhook('
274
+ class YTEST45 < XTEST44
275
+ def foo(a)
276
+ super(a)
277
+ end
278
+ end
279
+ YTEST45.new.foo(9)
280
+ ', binding).should be == 10
281
+ end
282
+
283
+ it "should pass arguments on super with three levels" do
284
+ EvalHook::HookHandler.new.evalhook('
285
+ class YTEST46 < XTEST44
286
+ def foo(a)
287
+ super(a)
288
+ end
289
+ end
290
+
291
+ class ZTEST46 < YTEST46
292
+ def foo(a)
293
+ super(a)
294
+ end
295
+ end
296
+ ZTEST46.new.foo(9)
297
+ ', binding).should be == 10
298
+
299
+ end
300
+
301
+ it "should raise SecurityError when use super outside a class" do
302
+
303
+ lambda {
304
+
305
+ EvalHook::HookHandler.new.evalhook('
306
+ module MODULETEST48
307
+ def foo(a)
308
+ super(a)
309
+ end
310
+ end
311
+
312
+ class XTEST48 < XTEST44
313
+ include MODULETEST48
314
+ end
315
+ XTEST48.new.foo(9)
316
+ ', binding)
317
+
318
+ }.should raise_error(SecurityError)
319
+
320
+ end
321
+
322
+ it "should raise SecurityError when use super inside a module nested on class" do
323
+
324
+ lambda {
325
+
326
+ EvalHook::HookHandler.new.evalhook('
327
+
328
+ class CLASSTEST49
329
+ module MODULETEST49
330
+ def foo(a)
331
+ super(a)
332
+ end
333
+ end
334
+ end
335
+
336
+ class YTEST49 < XTEST44
337
+ include CLASSTEST49::MODULETEST49
338
+ end
339
+ YTEST49.new.foo(9)
340
+ ', binding)
341
+
342
+ }.should raise_error(SecurityError)
343
+
344
+ end
345
+
346
+ it "should pass arguments on super with no arguments throught three levels" do
347
+ EvalHook::HookHandler.new.evalhook('
348
+ class YTEST50 < XTEST44
349
+ def foo(a)
350
+ super
351
+ end
352
+ end
353
+
354
+ class ZTEST50 < YTEST50
355
+ def foo(a)
356
+ super
357
+ end
358
+ end
359
+ ZTEST50.new.foo(9)
360
+ ', binding).should be == 10
361
+
362
+ end
363
+
364
+ it "should raise SecurityError when use super with no arguments outside a class" do
365
+
366
+ lambda {
367
+
368
+ EvalHook::HookHandler.new.evalhook('
369
+ module MODULETEST51
370
+ def foo(a)
371
+ super
372
+ end
373
+ end
374
+
375
+ class XTEST51 < XTEST44
376
+ include MODULETEST51
377
+ end
378
+ XTEST51.new.foo(9)
379
+ ', binding)
380
+
381
+ }.should raise_error(SecurityError)
382
+
383
+ end
384
+
385
+ it "should raise SecurityError when use super with no arguments inside a module nested on class" do
386
+
387
+ lambda {
388
+
389
+ EvalHook::HookHandler.new.evalhook('
390
+
391
+ class CLASSTEST52
392
+ module MODULETEST52
393
+ def foo(a)
394
+ super
395
+ end
396
+ end
397
+ end
398
+
399
+ class YTEST52 < XTEST44
400
+ include CLASSTEST52::MODULETEST52
401
+ end
402
+ YTEST52.new.foo(9)
403
+ ', binding)
404
+
405
+ }.should raise_error(SecurityError)
406
+
407
+ end
408
+
409
+ it "should execute super call from singleton methods" do
410
+ EvalHook::HookHandler.new.evalhook('
411
+
412
+ class CLASSTEST53
413
+ def bar(a)
414
+ a+1
415
+ end
416
+ end
417
+
418
+ obj = CLASSTEST53.new
419
+
420
+ def obj.bar(a)
421
+ super(a+1)
422
+ end
423
+ obj.bar(10)
424
+ ', binding).should be == 12
425
+ end
426
+
246
427
  end
247
428
 
@@ -43,4 +43,140 @@ describe EvalHook::HookHandler, "hook handler hooks" do
43
43
  TEST_CONSTANT.should be == 7
44
44
  end
45
45
 
46
+ it "should intercept constant access" do
47
+ hh = EvalHook::HookHandler.new
48
+ def hh.handle_const(name)
49
+ const_value(77)
50
+ end
51
+
52
+ TEST_CONSTANT_12345 = 8
53
+ hh.evalhook("TEST_CONSTANT_12345").should be == 77
54
+ end
55
+
56
+ it "should allow change constant values using hooking" do
57
+ hh = EvalHook::HookHandler.new
58
+ def hh.handle_colon2(context, name)
59
+ const_value(77)
60
+ end
61
+
62
+ TEST_CONSTANT_12346 = 8
63
+ hh.evalhook("Object::TEST_CONSTANT_12346").should be == 77
64
+ end
65
+
66
+ it "should allow change global variable values using hooking" do
67
+ hh = EvalHook::HookHandler.new
68
+ def hh.handle_gvar(global_id)
69
+ global_value(88)
70
+ end
71
+
72
+ $test_global_12345 = 9
73
+ hh.evalhook("$test_global_12345").should be == 88
74
+ end
75
+
76
+ it "should intercept constant access" do
77
+ hh = EvalHook::HookHandler.new
78
+
79
+ hh.should_receive(:handle_const).with("A")
80
+
81
+ hh.evalhook("A")
82
+ end
83
+
84
+ module TestModule321
85
+ end
86
+
87
+ it "should intercept nested constant access" do
88
+ hh = EvalHook::HookHandler.new
89
+
90
+ hh.should_receive(:handle_const).once.with("TestModule321")
91
+ hh.should_receive(:handle_colon2).once.with(TestModule321,"B")
92
+
93
+ hh.evalhook("TestModule321::B")
94
+ end
95
+
96
+ it "should intercept global_variable access" do
97
+ hh = EvalHook::HookHandler.new
98
+
99
+ hh.should_receive(:handle_gvar).with(:$global_variable_test)
100
+
101
+ hh.evalhook("$global_variable_test")
102
+ end
103
+
104
+
105
+ class X
106
+ def foo
107
+ end
108
+ end
109
+
110
+ class Y < X
111
+ end
112
+
113
+ it "should intercept super as a method call" do
114
+ h = EvalHook::HookHandler.new
115
+
116
+ # reference to X for inheritance
117
+ h.should_receive(:handle_const).with("X")
118
+
119
+ # reference to Y
120
+ h.should_receive(:handle_const).with("Y")
121
+
122
+ # call to new
123
+ h.should_receive(:handle_method).with(Y.class,Y,:new)
124
+
125
+ # first Y#foo call
126
+ h.should_receive(:handle_method).with(Y,anything(),:foo)
127
+
128
+ # super call
129
+ h.should_receive(:handle_method).with(X,anything(),:foo)
130
+
131
+ h.evalhook('
132
+
133
+ class Y < X
134
+ def foo
135
+ super
136
+ end
137
+ end
138
+ Y.new.foo
139
+
140
+ ', binding)
141
+ end
142
+
143
+ class X2
144
+ def foo(a)
145
+ a+1
146
+ end
147
+ end
148
+
149
+ class Y2 < X2
150
+ end
151
+
152
+ it "should intercept super with explicit arguments as a method call" do
153
+ h = EvalHook::HookHandler.new
154
+
155
+ # reference to X for inheritance
156
+ h.should_receive(:handle_const).with("X2")
157
+
158
+ # reference to Y
159
+ h.should_receive(:handle_const).with("Y2")
160
+
161
+ # call to new
162
+ h.should_receive(:handle_method).with(Y2.class,Y2,:new)
163
+
164
+ # first Y#foo call
165
+ h.should_receive(:handle_method).with(Y2,anything(),:foo)
166
+
167
+ # super call
168
+ h.should_receive(:handle_method).with(X2,anything(),:foo)
169
+
170
+ h.evalhook('
171
+
172
+ class Y2 < X2
173
+ def foo(a)
174
+ super(a)
175
+ end
176
+ end
177
+ Y2.new.foo(9)
178
+
179
+ ', binding).should be == 10
180
+ end
181
+
46
182
  end
@@ -7,6 +7,10 @@ describe EvalHook::HookHandler, "hook handler visitor" do
7
7
  def foo
8
8
 
9
9
  end
10
+
11
+ def bar(*x)
12
+
13
+ end
10
14
  end
11
15
 
12
16
  def self.visitor_it(code, name)
@@ -100,4 +104,57 @@ describe EvalHook::HookHandler, "hook handler visitor" do
100
104
 
101
105
  end
102
106
 
107
+ it "should capture inside parameters" do
108
+ x = X.new
109
+ hh = EvalHook::HookHandler.new
110
+
111
+ hh.should_receive(:handle_method).with(X,x,:foo)
112
+ hh.should_receive(:handle_method).with(X,x,:bar)
113
+
114
+ c = nil
115
+ hh.evalhook("
116
+ x.bar(x.foo)
117
+ ", binding
118
+ )
119
+ end
120
+
121
+ it "should capture in global assignment" do
122
+ x = X.new
123
+ hh = EvalHook::HookHandler.new
124
+
125
+ hh.should_receive(:handle_method).with(X,x,:foo)
126
+
127
+ c = nil
128
+ hh.evalhook("
129
+ $a = x.foo
130
+ ", binding
131
+ )
132
+ end
133
+
134
+ it "should capture in constant assignment" do
135
+ x = X.new
136
+ hh = EvalHook::HookHandler.new
137
+
138
+ hh.should_receive(:handle_method).with(X,x,:foo)
139
+
140
+ c = nil
141
+ hh.evalhook("
142
+ TESTCONSTANTA = x.foo
143
+ ", binding
144
+ )
145
+ end
146
+
147
+ it "should capture in dxstr system calls" do
148
+ x = X.new
149
+ hh = EvalHook::HookHandler.new
150
+
151
+ hh.should_receive(:handle_method).with(X,x,:foo)
152
+
153
+ c = nil
154
+ hh.evalhook("
155
+ `\#{x.foo}`
156
+ ", binding
157
+ )
158
+ end
159
+
103
160
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evalhook
3
3
  version: !ruby/object:Gem::Version
4
- hash: 17
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 1
10
- version: 0.3.1
8
+ - 4
9
+ - 0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dario Seminara
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-10 00:00:00 -03:00
18
+ date: 2011-04-28 00:00:00 -03:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -26,12 +26,12 @@ dependencies:
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- hash: 27
29
+ hash: 23
30
30
  segments:
31
31
  - 0
32
- - 1
32
+ - 2
33
33
  - 0
34
- version: 0.1.0
34
+ version: 0.2.0
35
35
  type: :runtime
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
@@ -65,9 +65,9 @@ files:
65
65
  - examples/example4.rb
66
66
  - examples/example2.rb
67
67
  - lib/evalhook/multi_hook_handler.rb
68
+ - lib/evalhook/tree_processor.rb
68
69
  - lib/evalhook/hook_handler.rb
69
70
  - lib/evalhook/redirect_helper.rb
70
- - lib/evalhook/hook_context.rb
71
71
  - lib/evalhook.rb
72
72
  - spec/hook_handler/hook_handler_arguments_spec.rb
73
73
  - spec/hook_handler/hook_handler_hook_spec.rb
@@ -1,144 +0,0 @@
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