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.
- data/LICENSE +23 -0
- data/NEWS +2 -0
- data/README.md +9 -0
- data/Rakefile +162 -0
- data/ext/extconf.rb +12 -0
- data/ext/iseq_extra.c +404 -0
- data/ext/iseq_extra.h +8 -0
- data/ext/iseq_mini.h +41 -0
- data/ext/node.h +483 -0
- data/ext/proc_extra.c +108 -0
- data/ext/proc_extra.h +3 -0
- data/ext/thread_extra.c +84 -0
- data/ext/thread_extra.h +5 -0
- data/ext/thread_frame.c +1022 -0
- data/ext/thread_frame.h +4 -0
- data/ext/thread_pthread.h +24 -0
- data/include/method_mini.h +90 -0
- data/include/node.h +483 -0
- data/include/ruby19_externs.h +36 -0
- data/include/thread_pthread.h +24 -0
- data/include/vm_core_mini.h +357 -0
- data/lib/iseq_extra.rb +89 -0
- data/lib/thread_frame.rb +3 -0
- data/test/unit/cfunc-use.rb +11 -0
- data/test/unit/test-argc.rb +45 -0
- data/test/unit/test-binding.rb +44 -0
- data/test/unit/test-invalid.rb +40 -0
- data/test/unit/test-iseq-brkpt.rb +61 -0
- data/test/unit/test-iseq.rb +121 -0
- data/test/unit/test-lib-iseq-extra.rb +57 -0
- data/test/unit/test-prev.rb +54 -0
- data/test/unit/test-proc.rb +23 -0
- data/test/unit/test-return-stop.rb +64 -0
- data/test/unit/test-settracefunc.rb +315 -0
- data/test/unit/test-source.rb +104 -0
- data/test/unit/test-sp-size.rb +45 -0
- data/test/unit/test-thread-trace-masks.rb +90 -0
- data/test/unit/test-thread.rb +168 -0
- data/test/unit/test-trace.rb +55 -0
- data/threadframe.rd +163 -0
- metadata +110 -0
data/lib/thread_frame.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../ext/thread_frame'
|
3
|
+
|
4
|
+
class TestARGC < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@original_compile_option = RubyVM::InstructionSequence.compile_option
|
8
|
+
RubyVM::InstructionSequence.compile_option = {
|
9
|
+
:trace_instruction => false,
|
10
|
+
:specialized_instruction => false
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
set_trace_func(nil)
|
16
|
+
RubyVM::InstructionSequence.compile_option = @original_compile_option
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_C_argc
|
20
|
+
cmd='File.basename("/tmp/foo.rb");File.basename("/tmp/foo.rb",".rb")'
|
21
|
+
iseq = RubyVM::InstructionSequence.compile(cmd)
|
22
|
+
events = []
|
23
|
+
all_events = []
|
24
|
+
eval <<-EOF.gsub(/^.*?: /, "")
|
25
|
+
1: set_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
|
26
|
+
2: tf = RubyVM::ThreadFrame.current.prev(1)
|
27
|
+
3: all_events << [tf.argc, tf.arity, tf.type, mid]
|
28
|
+
4: if :basename == mid
|
29
|
+
5: events << [tf.argc, tf.arity, tf.type, mid]
|
30
|
+
6: end
|
31
|
+
7: })
|
32
|
+
8: iseq.eval
|
33
|
+
9: set_trace_func(nil)
|
34
|
+
EOF
|
35
|
+
# p all_events
|
36
|
+
assert_equal([[1, -1, "CFUNC", :basename], # 1-arg c-call
|
37
|
+
[1, -1, "CFUNC", :basename], # 1-arg c-return
|
38
|
+
[2, -1, "CFUNC", :basename], # 2-arg c-call
|
39
|
+
[2, -1, "CFUNC", :basename] # 2-arg c-return
|
40
|
+
], events)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# We want to double-check we didn't mess up any pointers somewhere.
|
45
|
+
at_exit { GC.start }
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../ext/thread_frame'
|
3
|
+
|
4
|
+
def outside(a)
|
5
|
+
return eval('a', RubyVM::ThreadFrame.new(Thread::current).binding)
|
6
|
+
end
|
7
|
+
|
8
|
+
$a = 10
|
9
|
+
|
10
|
+
class TestBinding < Test::Unit::TestCase
|
11
|
+
def test_basic
|
12
|
+
a = 1
|
13
|
+
c = 0
|
14
|
+
assert_equal(5, outside(5))
|
15
|
+
tf = RubyVM::ThreadFrame.new(Thread::current)
|
16
|
+
b = tf.binding
|
17
|
+
assert_equal(1, eval('a', b))
|
18
|
+
assert_equal(10, eval('$a', b))
|
19
|
+
assert_equal(self, tf.self)
|
20
|
+
1.times do |i;a|
|
21
|
+
tf2 = Thread::current.threadframe()
|
22
|
+
b2 = tf2.binding
|
23
|
+
a = 2
|
24
|
+
assert_equal(2, eval('a', b2))
|
25
|
+
assert_equal(0, eval('c', b2))
|
26
|
+
|
27
|
+
# Times is C inline so prev we can't get a binding for it
|
28
|
+
# But we can for use the instruction sequence before that.
|
29
|
+
assert_equal(1, eval('a', tf2.prev(2).binding))
|
30
|
+
end
|
31
|
+
def inner(a)
|
32
|
+
tf3 = Thread::current.threadframe()
|
33
|
+
b3 = tf3.binding
|
34
|
+
if a == 4
|
35
|
+
assert_equal(4, eval('a', b3))
|
36
|
+
inner(a-1)
|
37
|
+
else
|
38
|
+
assert_equal(3, eval('a', b3))
|
39
|
+
assert_equal(4, eval('a', tf3.prev.binding))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
inner(4)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../ext/thread_frame'
|
3
|
+
|
4
|
+
class TestProc < Test::Unit::TestCase
|
5
|
+
def test_basic
|
6
|
+
@tf = RubyVM::ThreadFrame::current
|
7
|
+
assert_equal(false, @tf.invalid?,
|
8
|
+
'Frame should be valid right after ThreadFrame::current')
|
9
|
+
def notgood(test_tf=nil)
|
10
|
+
# FIXME
|
11
|
+
# if test_tf
|
12
|
+
# assert_equal(test_tf != @tf, test_tf.invalid?)
|
13
|
+
# end
|
14
|
+
return RubyVM::ThreadFrame::current
|
15
|
+
end
|
16
|
+
|
17
|
+
def inner_fn(tf)
|
18
|
+
tf.invalid?
|
19
|
+
end
|
20
|
+
|
21
|
+
invalid_tf = notgood
|
22
|
+
# FIXME:
|
23
|
+
# assert_equal(true, invalid_tf.invalid?,
|
24
|
+
# 'current thread frame should not be returned from a fn')
|
25
|
+
# begin
|
26
|
+
# b = invalid_tf.binding
|
27
|
+
# assert false, 'Should have raised an ThreadFrameError'
|
28
|
+
# rescue ThreadFrameError
|
29
|
+
# assert true
|
30
|
+
# end
|
31
|
+
# Add a new local variable
|
32
|
+
x = 5
|
33
|
+
assert_equal(false, @tf.invalid?,
|
34
|
+
'Frame should still be valid after adding more locals')
|
35
|
+
assert_equal(false, inner_fn(@tf),
|
36
|
+
'outer thread frame should ok inside a called fn')
|
37
|
+
notgood(invalid_tf)
|
38
|
+
notgood(@tf)
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../ext/thread_frame'
|
3
|
+
|
4
|
+
class TestISeqBrkpt < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_iseq_brkpt
|
7
|
+
iseq = RubyVM::ThreadFrame.current.iseq
|
8
|
+
assert iseq
|
9
|
+
assert_equal(nil, iseq.brkpts)
|
10
|
+
assert_equal(true, iseq.brkpt_alloc)
|
11
|
+
assert_equal([], iseq.brkpts)
|
12
|
+
assert_equal(false, iseq.brkpt_alloc)
|
13
|
+
|
14
|
+
offlines = iseq.offsetlines
|
15
|
+
offsets = offlines.values
|
16
|
+
[offsets.min[0], offsets.max[0]].each do |offset|
|
17
|
+
assert_equal(true, iseq.brkpt_set(offset))
|
18
|
+
assert_equal(true, iseq.brkpt_set(offset))
|
19
|
+
assert_equal(true, iseq.brkpt_get(offset),
|
20
|
+
"Offset %d should be set" % offset)
|
21
|
+
assert_equal(true, iseq.brkpt_unset(offset),
|
22
|
+
"Offset %d should be unset" % offset)
|
23
|
+
assert_equal(false, iseq.brkpt_get(offset),
|
24
|
+
"Offset %d should be unset now" % offset)
|
25
|
+
assert_equal(true, iseq.brkpt_unset(offset),
|
26
|
+
"Offset %d should be unset again" % offset)
|
27
|
+
iseq.brkpt_set(offset) # For test below
|
28
|
+
end
|
29
|
+
assert_equal(2, iseq.brkpts.size)
|
30
|
+
|
31
|
+
max_offset = offsets.max[0]
|
32
|
+
|
33
|
+
assert_raises TypeError do iseq.brkpt_get(iseq.iseq_size) end
|
34
|
+
|
35
|
+
assert_equal(true, iseq.brkpt_dealloc)
|
36
|
+
assert_equal(false, iseq.brkpt_dealloc)
|
37
|
+
assert_equal(true, iseq.brkpt_unset(max_offset),
|
38
|
+
"Offset %d should be unset even when deallocated" % max_offset)
|
39
|
+
|
40
|
+
assert_raises TypeError do iseq.brkpt_set('a') end
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_iseq_brkpt_set
|
44
|
+
add_trace_func(Proc.new { |event, file, lineno, mid, binding, klass|
|
45
|
+
if 'brkpt' == event
|
46
|
+
$saw_brkpt = true
|
47
|
+
end
|
48
|
+
})
|
49
|
+
|
50
|
+
$saw_brkpt = false
|
51
|
+
tf = RubyVM::ThreadFrame.current
|
52
|
+
tf.iseq.offsetlines.keys.each do |offset|
|
53
|
+
tf.iseq.brkpt_set(offset)
|
54
|
+
end
|
55
|
+
assert_equal(true, $saw_brkpt)
|
56
|
+
clear_trace_func
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# We want to double-check we didn't mess up any pointers somewhere.
|
61
|
+
at_exit { GC.start }
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../ext/thread_frame'
|
3
|
+
|
4
|
+
class TestISeq < Test::Unit::TestCase
|
5
|
+
|
6
|
+
class C
|
7
|
+
def initialize(test_obj, optional=true)
|
8
|
+
iseq = RubyVM::ThreadFrame::current.iseq
|
9
|
+
test_obj.assert_equal('test_obj', iseq.local_name(0))
|
10
|
+
test_obj.assert_equal(1, iseq.arity)
|
11
|
+
test_obj.assert_equal(-1, iseq.arg_block)
|
12
|
+
test_obj.assert_equal(1, iseq.argc)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_fields
|
17
|
+
start_lineno = __LINE__ - 1;
|
18
|
+
iseq = RubyVM::ThreadFrame::current.iseq
|
19
|
+
assert iseq
|
20
|
+
assert_equal('test_fields', iseq.name)
|
21
|
+
## FIXME: Why does this fail?
|
22
|
+
## assert_equal(start_lineno, iseq.lineno, 'iseq.lineno')
|
23
|
+
assert_equal(0, iseq.arity)
|
24
|
+
assert_equal(-1, iseq.arg_block)
|
25
|
+
assert_equal(0, iseq.argc)
|
26
|
+
assert_equal(0, iseq.arg_opts)
|
27
|
+
assert_equal(4, iseq.local_table_size)
|
28
|
+
x = lambda do |x,y|
|
29
|
+
iseq = RubyVM::ThreadFrame::current.iseq
|
30
|
+
assert iseq
|
31
|
+
assert_equal(2, iseq.arity)
|
32
|
+
assert_equal(-1, iseq.arg_block)
|
33
|
+
assert_equal(2, iseq.argc)
|
34
|
+
assert_equal(0, iseq.arg_opts)
|
35
|
+
assert_equal(3, iseq.local_table_size)
|
36
|
+
['x', 'y'].each_with_index do |expect, i|
|
37
|
+
assert_equal(expect, iseq.local_name(i))
|
38
|
+
end
|
39
|
+
|
40
|
+
assert_equal('x', iseq.local_name(-1))
|
41
|
+
assert_raise IndexError do
|
42
|
+
x = iseq.local_name(10)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
x.call(1,2)
|
46
|
+
|
47
|
+
x = Proc.new do |a|
|
48
|
+
iseq = RubyVM::ThreadFrame::current.iseq
|
49
|
+
assert iseq
|
50
|
+
assert_equal(1, iseq.arity)
|
51
|
+
assert_equal(-1, iseq.arg_block)
|
52
|
+
assert_equal(1, iseq.argc)
|
53
|
+
assert_equal(0, iseq.arg_opts)
|
54
|
+
assert_equal(1, iseq.local_table_size)
|
55
|
+
['a'].each_with_index do |expect, i|
|
56
|
+
assert_equal(expect, iseq.local_name(i))
|
57
|
+
end
|
58
|
+
assert_raises IndexError do
|
59
|
+
x = iseq.local_name(100)
|
60
|
+
end
|
61
|
+
assert_raises TypeError do
|
62
|
+
p iseq.local_name('a')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
x.call(1,2)
|
66
|
+
C.new(self, 5)
|
67
|
+
end_lineno = __LINE__ + 3
|
68
|
+
assert_equal((start_lineno..end_lineno),
|
69
|
+
method(:test_fields).iseq.line_range, 'line range')
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_iseq_equal
|
73
|
+
tf = RubyVM::ThreadFrame.current
|
74
|
+
tf2 = RubyVM::ThreadFrame.current
|
75
|
+
while !tf.iseq do
|
76
|
+
tf = tf.prev
|
77
|
+
tf2 = tf2.prev
|
78
|
+
end
|
79
|
+
assert_equal(false, tf.iseq.equal?(nil))
|
80
|
+
assert_equal(true, tf.iseq.equal?(tf.iseq))
|
81
|
+
assert_equal(true, tf.iseq.equal?(tf2.iseq))
|
82
|
+
tf2 = tf2.prev
|
83
|
+
while !tf2.iseq do tf2 = tf2.prev end
|
84
|
+
assert_equal(false, tf.iseq.equal?(tf2.iseq))
|
85
|
+
assert_raises TypeError do
|
86
|
+
tf.iseq.equal?(tf)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# FIXME: killcache interface will probably change. Try make less sensitive
|
91
|
+
# to compile sequence
|
92
|
+
def test_iseq_killcache
|
93
|
+
iseq = RubyVM::ThreadFrame.current.iseq
|
94
|
+
count = iseq.killcache
|
95
|
+
if 0 != count
|
96
|
+
assert_equal(0, iseq.killcache,
|
97
|
+
'Doing killcache a second time should do nothing')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_offsetlines
|
102
|
+
start = __LINE__ - 1
|
103
|
+
tf = RubyVM::ThreadFrame::current
|
104
|
+
iseq = tf.iseq
|
105
|
+
offlines = iseq.offsetlines
|
106
|
+
pc = tf.pc_offset
|
107
|
+
assert_equal(__LINE__, offlines[pc][0]+1)
|
108
|
+
offlines.values.each do |value|
|
109
|
+
assert(value[0] >= start, "#{value[0]} should be not less than starting line #{start}")
|
110
|
+
# Rough count of # of lines is less than 20
|
111
|
+
assert(value[0] < start + 20, "#{value[0]} should be less than starting line #{start}")
|
112
|
+
end
|
113
|
+
offlines.keys.each do |offset|
|
114
|
+
assert_equal offlines[offset][0], iseq.offset2lines(offset)[0]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
# We want to double-check we didn't mess up any pointers somewhere.
|
121
|
+
at_exit { GC.start }
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../lib/iseq_extra'
|
3
|
+
|
4
|
+
class TestLibISeqExtra < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_basic
|
7
|
+
tf = RubyVM::ThreadFrame.current
|
8
|
+
iseq = tf.iseq
|
9
|
+
# See that we get the same line numbers
|
10
|
+
assert_equal(iseq.offsetlines.values.flatten.uniq.sort,
|
11
|
+
iseq.lineoffsets.keys.sort)
|
12
|
+
# See that we get the same offsets
|
13
|
+
assert_equal(iseq.lineoffsets.values.flatten.uniq.sort,
|
14
|
+
iseq.offsetlines.keys.sort)
|
15
|
+
|
16
|
+
assert_equal(iseq.lineoffsets[__LINE__].sort,
|
17
|
+
iseq.line2offsets(__LINE__-1).sort)
|
18
|
+
|
19
|
+
assert_equal([], iseq.line2offsets(__LINE__+100))
|
20
|
+
top_iseq = tf.prev(-1).iseq
|
21
|
+
assert_equal('method', RubyVM::InstructionSequence::TYPE2STR[top_iseq.type])
|
22
|
+
|
23
|
+
iseq2 = tf.iseq
|
24
|
+
# Different object ids...
|
25
|
+
assert_not_equal(iseq.object_id, iseq2.object_id)
|
26
|
+
# but same SHA1s..
|
27
|
+
assert_equal(iseq.sha1, iseq2.sha1)
|
28
|
+
# and equal instruction sequences
|
29
|
+
assert_equal(true, iseq.equal?(iseq2))
|
30
|
+
|
31
|
+
# Now try two different iseqs
|
32
|
+
tf2 = tf.prev
|
33
|
+
tf2 = tf2.prev until !tf2.prev || tf2.prev.iseq
|
34
|
+
if tf2
|
35
|
+
assert_not_equal(iseq.sha1, tf2.iseq.sha1)
|
36
|
+
assert_equal(false, iseq.equal?(tf2.iseq))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_format_args
|
41
|
+
# These prototypes are from IRB
|
42
|
+
def evaluate(context, statements, file = __FILE__, line = __LINE__); end
|
43
|
+
def def_extend_command(cmd_name, load_file, *aliases); end
|
44
|
+
|
45
|
+
assert_equal('context, statements; file, line',
|
46
|
+
method(:evaluate).iseq.format_args)
|
47
|
+
assert_equal('cmd_name, load_file; aliases',
|
48
|
+
method(:def_extend_command).iseq.format_args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_sha1
|
52
|
+
sha1 = proc{ 5 }.iseq.sha1
|
53
|
+
assert_equal(40, sha1.size)
|
54
|
+
assert_equal(0, sha1 =~ /^[0-9a-f]+$/)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require_relative '../../ext/thread_frame'
|
3
|
+
|
4
|
+
class TestThread < Test::Unit::TestCase
|
5
|
+
def test_stack_size_with_prev
|
6
|
+
tf = RubyVM::ThreadFrame.new(Thread::current)
|
7
|
+
|
8
|
+
# valid prev counts are -stack_size .. stack_size-1
|
9
|
+
n = tf.stack_size
|
10
|
+
assert_equal(nil, tf.prev(n),
|
11
|
+
'Should have accessed past the top of the stack')
|
12
|
+
top_frame = tf.prev(n-1)
|
13
|
+
assert(top_frame,
|
14
|
+
'Should have gotten the top frame')
|
15
|
+
assert_equal(tf, tf.prev(-n),
|
16
|
+
'tf.prev(tf.stack_size) == tf')
|
17
|
+
assert(top_frame)
|
18
|
+
assert_equal('TOP', top_frame.type,
|
19
|
+
'The type of the top frame should be "TOP"')
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_prev
|
23
|
+
|
24
|
+
assert RubyVM::ThreadFrame::prev(Thread::current, 0)
|
25
|
+
assert(RubyVM::ThreadFrame::prev(Thread::current, 2),
|
26
|
+
'There should be at least two prior frames')
|
27
|
+
|
28
|
+
top_frame = RubyVM::ThreadFrame::prev(Thread::current, -1)
|
29
|
+
assert(top_frame, 'Should give back the top frame')
|
30
|
+
assert_equal('TOP', top_frame.type,
|
31
|
+
'The type of the top frame should be "TOP"')
|
32
|
+
|
33
|
+
assert_equal(nil, RubyVM::ThreadFrame::prev(Thread::current, 1000))
|
34
|
+
|
35
|
+
tf = RubyVM::ThreadFrame::current.prev
|
36
|
+
|
37
|
+
assert tf.prev(2)
|
38
|
+
assert_equal(tf, tf.prev(0),
|
39
|
+
'tf.prev(0) is defined as be tf')
|
40
|
+
assert tf.prev(-1)
|
41
|
+
assert_equal(nil, tf.prev(1000))
|
42
|
+
|
43
|
+
assert_raises TypeError do
|
44
|
+
tf.prev('a')
|
45
|
+
end
|
46
|
+
assert_raises TypeError do
|
47
|
+
RubyVM::ThreadFrame::prev([1])
|
48
|
+
end
|
49
|
+
assert_raises TypeError do
|
50
|
+
RubyVM::ThreadFrame::prev(RubyVM::ThreadFrame::current, [1])
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|