Module: Trepan::Frame

Includes:
Util
Defined in:
app/frame.rb

Overview

Call-Stack frame methods

Constant Summary collapse

DEFAULT_STACK_TRACE_SETTINGS =
{
  :basename     => false,   # Basename only for files?
  :maxstack     => 16,      # How many entries to show? nil means all
  :count        => nil,     # How many entries to show? nil means all
  :current_pos  => 0,       # Where are we in the stack?
  :show_pc      => false,   # Show PC offset?
}

Class Method Summary collapse

Methods included from Util

extract_expression, safe_repr, suppress_warnings, uniq_abbrev

Class Method Details

.all_param_names(iseq, delineate = true) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'app/frame.rb', line 21

def all_param_names(iseq, delineate=true)
  return '' unless iseq
  params = param_names(iseq, 0, iseq.argc-1, '')
  if iseq.arg_opts > 0
    opt_params = param_names(iseq, iseq.argc, 
                             iseq.argc + iseq.arg_opts-2, '')
    opt_params[0] = "optional: #{opt_params[0]}" if delineate
    params += opt_params
  end
  params += param_names(iseq, iseq.arg_rest, iseq.arg_rest, '*') if 
    iseq.arg_rest != -1
  if iseq.arg_post_len > 0
    # Manditory arguments after optional ones - new in Ruby 1.9
    post_params = param_names(iseq, iseq.arg_post_start, 
                              iseq.post_start + iseq.arg_post_len, '')
    post_params[0] = "post: #{post_params[0]}" if delineate
    params += post_params
  end
  params += param_names(iseq, iseq.arg_block, iseq.arg_block, '&') if 
    iseq.arg_block != -1

  return params
end

.c_params(frame, maxstring = 20) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'app/frame.rb', line 45

def c_params(frame, maxstring=20)
  argc = frame.argc
  # FIXME should figure out why exception is raised.
  begin
    args = 
      if 0 == argc
        ''
      elsif frame 
        1.upto(argc).map do 
        |i| 
        safe_repr(frame.sp(argc-i+3).inspect, 10)
      end.join(', ')
      else
        '??'
      end
    safe_repr(args, maxstring)
  rescue NotImplementedError
    '??'
  end
end

.eval_string(frame) ⇒ Object

Return the eval string. We get this as the parameter to the eval C call. A bit of checking is done to make sure everything is okay:

- we have to be in an EVAL type frame which has an iseq
- we have to have a prev frame which is a CFUNC
- the prev method name has to be 'eval'


72
73
74
75
76
77
78
79
# File 'app/frame.rb', line 72

def eval_string(frame)
  return nil unless 'EVAL' == frame.type && frame.iseq
  prev = frame.prev
  return nil unless prev && 'CFUNC' == prev.type && 'eval' == prev.method
  retval = prev.sp 3 
  retval = $1 if retval =~ /^\(eval "(.+)"\)/
  retval
end

.fileObject



81
82
83
# File 'app/frame.rb', line 81

def file
  iseq.source_container[1]
end

.format_stack_call(frame, opts) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'app/frame.rb', line 85

def format_stack_call(frame, opts)
  # FIXME: prettify 
  s = "#{frame.type}"
  s += if opts[:class]
         " #{opts[:class]}#"
       else
         begin 
           obj = eval('self', frame.binding)
         rescue
           ''
         else
           if obj
             " #{obj.class}#" 
           else
             ''
           end
         end
       end
  meth = frame.method
  if meth and frame.type != 'IFUNC'
    iseq = frame.iseq
    args = if 'CFUNC' == frame.type
             c_params(frame)
           elsif iseq
             all_param_names(iseq).join(', ')
           end
    s += meth
    if %w(CFUNC METHOD).member?(frame.type)
      s += "(#{args})"
    elsif %w(BLOCK LAMBDA TOP EVAL).member?(frame.type)
      s += " |#{args}|" unless args.nil? || args.empty?
    else
      s += "(#{all_param_names(iseq)})" 
    end
  end
  s
rescue ThreadFrameError
  'invalid frame'
end

.format_stack_entry(frame, opts = {}) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/frame.rb', line 125

def format_stack_entry(frame, opts={})
  return 'invalid frame' if frame.invalid?
  s  = format_stack_call(frame, opts)
  s += " in #{frame.source_container[0]} "
  s += 
    if (eval_str = eval_string(frame))
      safe_repr(eval_str.inspect, 15)
    else
      if 'file' == frame.source_container[0] &&
          opts[:basename]
        File.basename(frame.source_container[1])
      else
        frame.source_container[1]
      end
    end
  if frame.source_location
    s += 
      if opts[:maxwidth] && s.size > opts[:maxwidth]
        "\n\t"
      else
        ' '
      end
    if frame.source_location.size == 1
      s += "at line #{frame.source_location[0]}" if 
        frame.source_location[0] != 0
    else
      s += " at lines #{frame.source_location}"
    end
  end
  s += ", pc: #{frame.pc_offset}" if 
    frame.pc_offset > 0 && opts[:show_pc]
  return s
end

.location_equal(frame1, frame2) ⇒ Object

Return true if frame1 and frame2 are at the same place. We use this for example in detecting tail recursion.



161
162
163
164
165
# File 'app/frame.rb', line 161

def location_equal(frame1, frame2)
  frame1 && frame2 && frame1.source_location == frame2.source_location &&
    frame1.pc_offset == frame2.pc_offset && 
    frame1.source_container == frame2.source_container
end

.offset_for_return(event) ⇒ Object

Raises:

  • (RuntimeError)


167
168
169
170
171
172
# File 'app/frame.rb', line 167

def offset_for_return(event)
  raise RuntimeError unless %w(return c-return).member?(event)
  # FIXME: C calls have a RubyVM::Env added to the stack.
  # Where? Why?
  'return' == event ? 1 : 4
end

.param_names(iseq, start, stop, prefix = '') ⇒ Object



174
175
176
177
178
179
180
181
182
# File 'app/frame.rb', line 174

def param_names(iseq, start, stop, prefix='')
  start.upto(stop).map do |i| 
    begin
      prefix + iseq.local_name(i)
    rescue
      nil
    end
  end.compact
end

.print_stack_entry(frame, i, prefix = ' ', opts = {}) ⇒ Object



184
185
186
187
# File 'app/frame.rb', line 184

def print_stack_entry(frame, i, prefix='    ', opts={})
  opts[:class] = nil unless i == 0
  msg "%s%s" % [prefix, format_stack_entry(frame, opts)]
end

Print ‘count’ frame entries



211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'app/frame.rb', line 211

def print_stack_trace(frame, opts={})
  opts    = DEFAULT_STACK_TRACE_SETTINGS.merge(opts)
  halfstack = (opts[:maxstack]+1) / 2
  n       = frame.stack_size
  n       = [n, opts[:count]].min if opts[:count]
  if n > (halfstack * 2)
    print_stack_trace_from_to(0, halfstack-1, frame, opts)
    msg "... %d levels ..." % (n - halfstack*2)
    print_stack_trace_from_to(n - halfstack, n-1, frame, opts)
  else
    print_stack_trace_from_to(0, n-1, frame, opts)
  end
  msg "(More stack frames follow...)" if n < frame.stack_size
end


189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/frame.rb', line 189

def print_stack_trace_from_to(from, to, frame, opts)
  last_frame = nil
  # TODO: handle indirect recursion.
  direct_recursion_count = 0
  from.upto(to) do |i|
    if location_equal(last_frame, frame)
      direct_recursion_count += 1
    else
      if direct_recursion_count > 0
        msg("... above line repeated #{direct_recursion_count} times")
        direct_recursion_count = 0
      end
      prefix = (i == opts[:current_pos]) ? '-->' : '   '
      prefix += ' #%d ' % [i]
      print_stack_entry(frame, i, prefix, opts)
    end
    last_frame = frame
    frame = frame.prev
  end
end

.set_return_value(frame, event, value) ⇒ Object



226
227
228
229
# File 'app/frame.rb', line 226

def set_return_value(frame, event, value)
  offset = offset_for_return(event)
  frame.sp_set(offset, value)
end

.value_returned(frame, event) ⇒ Object



232
233
234
# File 'app/frame.rb', line 232

def value_returned(frame, event)
  frame.sp(offset_for_return(event))
end