rb-threadframe 0.32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ require 'test/unit'
2
+
3
+ # require 'thread_frame' # To compare with previous version
4
+ require_relative '../../ext/thread_frame'
5
+
6
+ # Test source_location and source_container.
7
+ class TestSpSize < Test::Unit::TestCase
8
+
9
+ def sizes
10
+ tf = RubyVM::ThreadFrame::current
11
+ ary = []
12
+ 0.upto(2) do |i|
13
+ ary << tf.sp_size
14
+ tf = tf.prev
15
+ end
16
+ # Swap first two items. The item that generally
17
+ # will vary is the a[0].
18
+ ary[0], ary[1] = ary[1], ary[0]
19
+ # p ary
20
+ return ary
21
+ end
22
+
23
+ def f0; return sizes end
24
+ def f1; a=1; return sizes end
25
+ def f1a(a) return sizes end
26
+ def f2(a,b) return sizes end
27
+
28
+ def test_sp_size
29
+ f0_s = f0
30
+ f1_s = f1
31
+ f1a_s = f1a(1)
32
+ f2_s = f2(1,2)
33
+ assert_equal(f0_s[0]+1, f1_s[0])
34
+ assert_equal(f0_s[1..-1], f1_s[1..-1])
35
+ assert_equal(f1_s, f1a_s)
36
+ assert_equal(f1_s[0]+1, f2_s[0])
37
+ assert_equal(f1_s[1..-1], f2_s[1..-1])
38
+
39
+ assert_raises ArgumentError do
40
+ tf = RubyVM::ThreadFrame.current
41
+ tf.sp_set(tf.sp_size, "Should not be able to set this.")
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,90 @@
1
+ # Test of additional tracing flag use to selectively turn on off tracing
2
+ require 'test/unit'
3
+ require_relative '../../ext/thread_frame'
4
+
5
+ class TestTracingMasks < Test::Unit::TestCase
6
+ @@EVENT2MASK = {
7
+ 'line' => 0x01,
8
+ 'call' => 0x08,
9
+ 'return' => 0x10,
10
+ 'c-call' => 0x20,
11
+ 'c-return' => 0x40,
12
+ 'raise' => 0x80,
13
+ }
14
+
15
+ def something_to_test(n)
16
+ def sqr(x)
17
+ x * x
18
+ end
19
+ begin
20
+ n += sqr(5)
21
+ raise TypeError, 'error'
22
+ rescue TypeError => $e
23
+ end
24
+ return [1,2,3].all? {|n| n}
25
+ end
26
+
27
+ def chunk(list, char='-')
28
+ sep = char * 30 + "\n"
29
+ sep + list.map{|e| e.join(' ')}.join("\n") + "\n"
30
+ end
31
+
32
+ def checkit(event_name)
33
+ chunk(@events) if $DEBUG
34
+ assert @events.all?{|e| e[1] == event_name}, chunk(@events)
35
+ assert @events.size > 0, "Expecting at least one #{event_name}"
36
+ @counts[event_name] = @events.size if @keep_count
37
+ end
38
+
39
+ def test_thread_trace_mask
40
+ def trace_hook(event, file, line, id, binding, klass)
41
+ @events << [line, event, id, klass]
42
+ end
43
+
44
+ @keep_count = true
45
+ @counts = {}
46
+ @@EVENT2MASK.each do |event_name, mask|
47
+ @events = []
48
+ Thread.current.set_trace_func(method(:trace_hook).to_proc, mask)
49
+ something_to_test(5)
50
+ Thread.current.set_trace_func(nil)
51
+ checkit(event_name)
52
+ end
53
+ [%w(call return),
54
+ %w(c-call c-return)].each do |c, r|
55
+ assert_equal(@counts[c], @counts[r],
56
+ "Expecting # of #{c}'s to equal # of #{r}'s")
57
+ end
58
+
59
+ @keep_count = false
60
+
61
+ [%w(line call),
62
+ %w(raise c-return return),
63
+ ].each do |event_names|
64
+ @events = []
65
+ mask = event_names.map{|name|
66
+ @@EVENT2MASK[name]
67
+ }.inject do
68
+ |mask, bit|
69
+ mask |= bit
70
+ end
71
+ Thread.current.set_trace_func(method(:trace_hook).to_proc, mask)
72
+ something_to_test(5)
73
+ Thread.current.set_trace_func(nil)
74
+ total = event_names.map{|name| @counts[name]}.inject do
75
+ |sum, n|
76
+ sum += n
77
+ end
78
+ assert_equal(total, @events.size, chunk(@events))
79
+ end
80
+
81
+ # Try without a mask and see that we get the sum of all events
82
+ @events = []
83
+ Thread.current.set_trace_func(method(:trace_hook).to_proc)
84
+ something_to_test(5)
85
+ Thread.current.set_trace_func(nil)
86
+ total = @counts.values.inject {|sum, n| sum += n}
87
+ assert_equal(total, @events.size, chunk(@events))
88
+ end
89
+
90
+ end
@@ -0,0 +1,168 @@
1
+ require 'test/unit'
2
+ require_relative '../../ext/thread_frame'
3
+
4
+ class TestThread < Test::Unit::TestCase
5
+ def test_basic
6
+ assert_equal(RubyVM::ThreadFrame.new(Thread::current).thread,
7
+ RubyVM::ThreadFrame::current.thread)
8
+ assert_equal(RubyVM::ThreadFrame.new(Thread::current).thread,
9
+ Thread::current)
10
+ assert_equal(Thread::current.threadframe.thread, Thread::current)
11
+ end
12
+
13
+ def test_pc_offset
14
+ tf = RubyVM::ThreadFrame::current
15
+ offset_1 = tf.pc_offset
16
+ assert_equal(true, offset_1 > 0,
17
+ "Expecting a positive integer pc offset, got %s" % offset_1)
18
+ offset_2 = tf.pc_offset
19
+ assert_equal(true, offset_2 > 0,
20
+ "Expecting a positive integer pc offset, got %s" % offset_2)
21
+ assert_equal(true, offset_2 > offset_1,
22
+ "Expecting second pc offset %s to be larger than the first %s" %
23
+ [offset_2, offset_1])
24
+ end
25
+
26
+ def test_sp
27
+ tf = RubyVM::ThreadFrame::current.prev
28
+ assert tf.sp(1)
29
+ tf.sp_set(1, 5)
30
+ assert_equal(5, tf.sp(1),
31
+ 'chcking value of recently-set sp(1)')
32
+ end
33
+
34
+ def test_source_container
35
+ cfunc_filebase = 'cfunc-use'
36
+ load File.join(File.dirname(__FILE__), cfunc_filebase + '.rb')
37
+ type, loc = cfunc_loc
38
+ assert_equal(['CFUNC', cfunc_filebase], [type, File.basename(loc, '.rb')],
39
+ 'CFUNCs should get their file location from frame.prev*')
40
+ cont = 'file'
41
+ eval '1.times{cont = RubyVM::ThreadFrame.current.source_container[0]}'
42
+ assert_equal('string', cont,
43
+ 'source container[0] of an eval(...) should be "string"')
44
+ end
45
+
46
+
47
+ def test_source_location
48
+ line = __LINE__
49
+ # There is a bug in Ruby 1.9.2 and before in reporting the source
50
+ # location when the PC is 0. Probably never reported before
51
+ # because the location is reported on a traceback
52
+ # and that probably can't happen at PC 0.
53
+ def bug_when_zero_pc
54
+ @not_first = true
55
+ tf = RubyVM::ThreadFrame::current.prev
56
+ pc_save = tf.pc_offset
57
+ tf.pc_offset = 0
58
+ loc = tf.source_location
59
+ tf.pc_offset = pc_save
60
+ loc
61
+ end
62
+ loc = bug_when_zero_pc unless @not_first
63
+ assert_equal([line - 1], loc)
64
+ end
65
+
66
+
67
+ def test_thread_tracing
68
+ assert_equal(false, Thread.current.tracing)
69
+ Thread.current.tracing = true
70
+ assert_equal(true, Thread.current.tracing)
71
+ Thread.current.tracing = false
72
+ assert_equal(false, Thread.current.tracing)
73
+ end
74
+
75
+ def test_thread_exec_event_tracing
76
+ assert_equal(false, Thread.current.exec_event_tracing)
77
+ Thread.current.exec_event_tracing = true
78
+ assert_equal(true, Thread.current.exec_event_tracing)
79
+ Thread.current.exec_event_tracing = false
80
+ assert_equal(false, Thread.current.exec_event_tracing)
81
+ end
82
+
83
+ def test_fields(notused=nil)
84
+ tf = RubyVM::ThreadFrame::current
85
+ pc1 = tf.pc_offset
86
+ assert(pc1 > 0, 'Should be able to get a valid PC offset')
87
+ # pc_offset is dynamic - it changes constantly
88
+ pc2 = tf.pc_offset
89
+ assert(pc2 > pc1, 'PC offset should have changed (for the greater)')
90
+ assert_equal('test_fields', tf.method)
91
+ assert_equal(self, tf.self)
92
+ assert_equal(0, tf.arity)
93
+ assert_equal(0, tf.argc)
94
+ assert tf.dfp(0)
95
+ assert tf.lfp(0)
96
+
97
+ assert_raises IndexError do
98
+ x = tf.lfp(tf.iseq.local_size+1)
99
+ end
100
+
101
+
102
+ tf_prev = tf.prev
103
+ assert(tf_prev.pc_offset > 0, "Should be valid PC offset for prev")
104
+
105
+ # Is this too specific to test/unit.rb implementation details?
106
+ assert_equal('run', tf_prev.method)
107
+
108
+ # 1.times creates a C frame.
109
+ 1.times do
110
+ tf = RubyVM::ThreadFrame::current
111
+ tup = tf.source_container
112
+ tup[1] = File.basename(tup[1])
113
+ assert_equal(['file', 'test-thread.rb'], tup)
114
+ assert_equal('block in test_fields', tf.method)
115
+ assert_equal('CFUNC', tf.prev.type)
116
+ assert_equal('times', tf.prev.method)
117
+ assert_equal(self, tf.self)
118
+ assert_equal(0, tf.prev.arity, 'C arity should work nowadays' )
119
+ assert_equal(0, tf.prev.argc, 'C args is the same as arity')
120
+ assert_equal('test_fields', tf.prev.prev.method)
121
+ assert_equal(0, tf.arity)
122
+ assert_equal(0, tf.argc)
123
+ end
124
+
125
+ # 1.upto also creates a C frame.
126
+ 1.upto(1) do
127
+ tf = RubyVM::ThreadFrame::current.prev
128
+ assert_equal('CFUNC', tf.type)
129
+ assert_equal(1, tf.arity, 'C arity should work nowadays' )
130
+ assert_equal(1, tf.argc)
131
+ end
132
+
133
+ x = lambda do |x,y|
134
+ frame = RubyVM::ThreadFrame::current
135
+ assert_equal('block in test_fields', frame.method)
136
+ assert_equal('LAMBDA', frame.type)
137
+ assert_equal(x, tf.self)
138
+ assert_equal(2, frame.arity)
139
+ assert_equal(2, frame.argc)
140
+ end
141
+ x.call(x,2)
142
+
143
+ x = Proc.new do |x, y|
144
+ frame = RubyVM::ThreadFrame::current
145
+ assert_equal('block in test_fields', frame.method)
146
+ assert_equal(x, tf.self)
147
+ assert_equal('BLOCK', frame.type)
148
+ end
149
+ x.call(x,2)
150
+
151
+ end
152
+
153
+ def test_threadframe_equal
154
+ tf = RubyVM::ThreadFrame.current
155
+ tf2 = RubyVM::ThreadFrame.current
156
+ assert_equal(true, tf.equal?(tf))
157
+ assert_equal(true, tf.equal?(tf2))
158
+ tf2 = tf2.prev
159
+ assert_equal(false, tf.equal?(tf2))
160
+ assert_raises TypeError do
161
+ tf.equal?(tf.iseq)
162
+ end
163
+ end
164
+ end
165
+
166
+ # We want to double-check we didn't mess up any pointers somewhere along
167
+ # the line.
168
+ at_exit { GC.start }
@@ -0,0 +1,55 @@
1
+ # Test of additional tracing flag use to selectively turn on off tracing
2
+ require 'test/unit'
3
+ require_relative '../../ext/thread_frame'
4
+
5
+ class TestTracing < Test::Unit::TestCase
6
+ def test_basic_query_set_unset
7
+ tf = RubyVM::ThreadFrame::current
8
+ # Test default values
9
+ assert_equal(false, tf.trace_off?)
10
+ assert_equal(false, tf.return_stop?)
11
+
12
+ # Test set true
13
+ tf.trace_off = true
14
+ assert_equal(true, tf.trace_off?)
15
+ tf.return_stop = true
16
+ assert_equal(true, tf.return_stop?)
17
+
18
+ # Test setting off when on
19
+ tf.trace_off = nil
20
+ assert_equal(false, tf.trace_off?)
21
+ tf.return_stop = false
22
+ assert_equal(false, tf.return_stop?)
23
+ end
24
+
25
+ def test_trace_off
26
+ @levels = []
27
+ def trace_hook(event, file, line, id, binding, klass)
28
+ @levels << RubyVM::ThreadFrame::current.stack_size
29
+ end
30
+
31
+ def baz
32
+ 6
33
+ end
34
+ def bar(set_off)
35
+ RubyVM::ThreadFrame::current.trace_off = true if set_off
36
+ baz
37
+ 5
38
+ end
39
+ def foo(set_off)
40
+ bar(set_off)
41
+ end
42
+ # 0x10 is the mask for tracing return events
43
+ Thread.current.set_trace_func(method(:trace_hook).to_proc, 0x10)
44
+ foo(false)
45
+ assert_equal(3, @levels.size)
46
+
47
+ @levels = []
48
+ Thread.current.set_trace_func(method(:trace_hook).to_proc, 0x10)
49
+ foo(true)
50
+ Thread.current.set_trace_func(nil)
51
+ assert_equal(1, @levels.size)
52
+
53
+ end
54
+
55
+ end
data/threadframe.rd ADDED
@@ -0,0 +1,163 @@
1
+ # = NAME
2
+ # +RubyVM+::+ThreadFrame+
3
+ #
4
+ # = SYNOPSES
5
+ #
6
+ # The +RubyVM+::+ThreadFrame+ class gives call-stack frame information
7
+ # (controlled access to +rb_control_frame_t+)
8
+ #
9
+ #
10
+ # It is possible that a The +Thread+::+Frame+ may be proposed for
11
+ # all Ruby 1.9 implementations. +RubyVM+::+ThreadFrame+
12
+ # contains routines in the YARV 1.9 implementation.
13
+ #
14
+ # Should there be a +Thread+::+Frame+ +RubyVM+::+ThreadFrame+ would be
15
+ # a subclass of +Thread+::+Frame+. In code:
16
+ #
17
+ # class Thread
18
+ # class Frame # In all 1.9 implementations
19
+ # # ...
20
+ # end
21
+ # def threadframe
22
+ # RubyVM::ThreadFrame.new(self) # or possibly Thread::Frame.new(self)
23
+ # end
24
+ # end
25
+ #
26
+ # class RubyVM::ThreadFrame < Thread::Frame # In YARV
27
+ # def initialize(thread_object)
28
+ # # implementation-specific code
29
+ # end
30
+ # def iseq
31
+ # # implementation of iseq
32
+ # end
33
+ # # ...
34
+ # end
35
+ #
36
+ # A +RubyVM+::Thread+Frame+ contains information from a frame running +Thread+
37
+ # object, and the information may change or disappear in the course of
38
+ # running that thread. Therefore, it is advisable ensure that the
39
+ # threads of the ThreadFrame objects are blocked.
40
+ #
41
+ #
42
+ # === RubyVM::ThreadFrame::new(thread_object)
43
+ # Creates a thread-frame object for the given thread object.
44
+ #
45
+ # === RubyVM::ThreadFrame::current
46
+ # Shorthand for RubyVM::ThreadFrame.new(Thread::current)
47
+ #
48
+ # === Thread#threadframe
49
+ # tf = Thread::current.threadframe()
50
+ #
51
+ # Creates a thread-frame object for the given thread object.
52
+ # Note:
53
+ # Thread::current.threadframe() == RubyVM::ThreadFrame.new(Thread::current)
54
+ #
55
+ # == RubyVM::ThreadFrame Instance Methods
56
+ #
57
+ # === RubyVM::ThreadFrame#prev
58
+ # tf.prev(n) -> tf or nil
59
+ # tf.prev() -> tf or nil # same as tf.prev(1)
60
+ #
61
+ # Returns the previous control frame. If tail recursion has removed
62
+ # frames as seen in the source, deal with it. ;-) +nil+ can be
63
+ # returned if there is no previous frame or if tf is no longer exists.
64
+ # If a number is passed go back that many frames. The value 0 gives back
65
+ # tf. A negative number of a number greater than the number of frames
66
+ # returns nil.
67
+ #
68
+ # === RubyVM::ThreadFrame#invalid?
69
+ # tf.invalid?() -> boolean
70
+ #
71
+ # Returns true if the frame is no longer valid. On the other hand,
72
+ # since the test we use is weak, returning false might not mean the
73
+ # frame is valid, just that we can't disprove that it is not invalid.
74
+ #
75
+ # It is suggested that frames are used in a way that ensures they will
76
+ # be valid. In particular frames should have local scope and frames to
77
+ # threads other than the running one should be stopped while the frame
78
+ # variable is active.
79
+ #
80
+ #
81
+ # === RubyVM::ThreadFrame#thread
82
+ # tf.thread() -> Thread
83
+ # RubyVM::ThreadFrame.current().thread == Thread.current
84
+ #
85
+ # === RubyVM::ThreadFrame#type
86
+ # tf.type() -> 'C' or 'Ruby'
87
+ #
88
+ # Indicates whether the frame is implemented in C or Ruby.
89
+ #
90
+ # === RubyVM::ThreadFrame#source_container
91
+ # RubyVM::Threadframe#source_container() -> [Type, String]
92
+ #
93
+ # Returns a tuple representing kind of container, e.g. file
94
+ # eval'd string object, and the name of the container. If file,
95
+ # it would be a file name. If an eval'd string it might be the string.
96
+ #
97
+ # === RubyVM::ThreadFrame#source_location
98
+ # RubyVM::ThreadFrame#.source_location() -> Array
99
+ #
100
+ # Returns an array of source location positions that match
101
+ # +tf.instruction_offset+. A source location position is left
102
+ # implementation dependent. It could be line number, a line number
103
+ # and start and end column, or a start line number, start column, end
104
+ # line number, end column.
105
+ #
106
+ # === RubyVM::ThreadFrame#stack_size
107
+ # RubyVM::ThreadFrame#.stack_size -> Fixnum
108
+ #
109
+ # Returns the number of entries
110
+ #
111
+ # === RubyVM::ThreadFrame#binding
112
+ # tf.binding() -> binding
113
+ #
114
+ #
115
+ # If the frame is still valid, a binding that is in effect for the
116
+ # scope of the frame is created. As with all bindings, over the
117
+ # execution of the program, variables may spring into existence and
118
+ # values may change.
119
+
120
+ # == RubyVM::ThreadFrame
121
+ #
122
+ # === RubyVM::new(thread_object)
123
+
124
+ # Like RubyVM::ThreadFrame.new(thread_object), but has additional information
125
+ # available.
126
+ #
127
+ # === RubyVM::ThreadFrame#iseq
128
+ # tf.iseq() -> ISeq
129
+ #
130
+ # Returns an instruction sequence object from the instruction sequence
131
+ # found inside the +ThreadFrame+ object or +nil+ if there is none.
132
+ # But if iseq is +nil+ and tf.type is not C, +binding+, and
133
+ # +instruction_offset+, and +source_location+ are probably meaningless
134
+ # and will be +nil+ as well.
135
+ #
136
+ #
137
+ # === RubyVM::ThreadFrame#instruction_offset
138
+ # tf.instruction_offset -> Fixnum
139
+ # Offset inside ISeq of instruction that the frame is currently on.
140
+ #
141
+ # === RubyVM::ThreadFrame#instruction_offset=
142
+ #
143
+ # tf.instruction_offset=(Fixnum)
144
+ # Sets the threadframe to a new offset. Some restrictions may apply, e.g.
145
+ # the offset will have to refer to a valid offset location and the scope
146
+ # and block level has to be the same.
147
+ #
148
+ # <em>Don't need to implement initially.</em>
149
+ #
150
+ # === RubyVM::ThreadFrame#return_changed?
151
+ # tf.return_changed?() -> boolean
152
+ #
153
+ # Returns true if tf _may_ be part of tail recursion removal so when
154
+ # the code in frame returns it may not be to the immediate caller as
155
+ # seen in the source code. Koichi says this is too difficult to do in
156
+ # YARV.
157
+ #
158
+ # <em>Don't need to implement initially.</em>
159
+ #
160
+ # = THANKS
161
+ #
162
+ # Martin Davis for suggesting
163
+ # RubyVM::ThreadFrame#new == Thread::current.threadframe