rb-threadframe 0.32

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.
@@ -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