rb-threadframe 0.32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|