heapinfo 0.0.5 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 38cb3f9332f88d77ee939beeec7ef2e1563e92fb
4
- data.tar.gz: 897f2130e7167fcae28c678df332b4aa0c8be682
3
+ metadata.gz: bb558249510b9c160a5f0f699d702a17b1189624
4
+ data.tar.gz: dacc4780724a2254d8137a23d2f920734db9034d
5
5
  SHA512:
6
- metadata.gz: 3fcb02ec66361586e3f5c00466f266dd1c388b0b27651c3ce030f88ab7d084b080cf7296bd71bbc32dc96a1a41e65f4318d73456c2d6126f6b37a9659e72bcb6
7
- data.tar.gz: 7884b477325650a2baf197a303387f580d4cb6de6100691b9e985842b23b8ea863afce4f07b1385a65cf5ce189aff3b70979b00681d39b6863fdde337375d62f
6
+ metadata.gz: 8fce3e42275b5895a2bec83dbe31d216d1d5a7506905e4d0e527f449c4418f9af00d6f9133d8b1074bb293e7a33ac0557833539562d68dd7a7cfc22d04404885
7
+ data.tar.gz: 1d036055d53f75e3d9639af6274682b29f5d8f6b275d4644d2619fa29ce4bbed559871ea9caf4b72c66fe81a82dcf4bd0496de239fd88b807df8b50757790ef5
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![Build Status](https://travis-ci.org/david942j/heapinfo.svg?branch=master)](https://travis-ci.org/david942j/heapinfo)
2
+ ![](http://ruby-gem-downloads-badge.herokuapp.com/heapinfo?type=total)
2
3
  [![Code Climate](https://codeclimate.com/github/david942j/heapinfo/badges/gpa.svg)](https://codeclimate.com/github/david942j/heapinfo)
3
4
  [![Issue Count](https://codeclimate.com/github/david942j/heapinfo/badges/issue_count.svg)](https://codeclimate.com/github/david942j/heapinfo)
4
5
  [![Test Coverage](https://codeclimate.com/github/david942j/heapinfo/badges/coverage.svg)](https://codeclimate.com/github/david942j/heapinfo/coverage)
data/lib/heapinfo.rb CHANGED
@@ -11,12 +11,13 @@ require 'fileutils'
11
11
  module HeapInfo
12
12
  # Directory for writing some tmp files when working,
13
13
  # make sure /tmp is writable
14
- TMP_DIR = '/tmp/.heapinfo'
15
- FileUtils.mkdir_p TMP_DIR
14
+ TMP_DIR = '/tmp/.heapinfo'.freeze
15
+ FileUtils.mkdir_p(TMP_DIR)
16
16
 
17
- # Entry point for using HeapInfo.
18
- # Show segments info of the process after loaded
19
- # @param [String, Fixnum] prog The program name of victim. If a number is given, seem as pid (useful when multi-processes exist)
17
+ # Entry point for using {HeapInfo}.
18
+ # Show segments info of the process after loaded.
19
+ # @param [String, Fixnum] prog
20
+ # The program name of victim. If a number is given, seem as pid (useful when multi-processes exist).
20
21
  # @param [Hash] options Give library's file name.
21
22
  # @option options [String, Regexp] :libc file name of glibc, default is <tt>/libc[^\w]/</tt>
22
23
  # @option options [String, Regexp] :ld file name of dynamic linker/loader, default is <tt>/\/ld-.+\.so/</tt>
@@ -34,7 +35,7 @@ module HeapInfo
34
35
  # # => "/lib/x86_64-linux-gnu/libc-2.19.so"
35
36
  # p h.ld.name
36
37
  # # => "/lib/x86_64-linux-gnu/ld-2.19.so"
37
- #
38
+ #
38
39
  # @example
39
40
  # h = heapinfo(27605, libc: 'libc.so.6', ld: 'ld-linux-x86-64.so.2')
40
41
  # # pid 27605 is run by custom loader
@@ -53,9 +54,8 @@ end
53
54
  # @return [HeapInfo::Process]
54
55
  # @param [Mixed] args see #HeapInfo::heapinfo for more information
55
56
  def heapinfo(*args)
56
- ::HeapInfo::heapinfo(*args)
57
+ ::HeapInfo.heapinfo(*args)
57
58
  end
58
-
59
59
 
60
60
  require 'heapinfo/helper'
61
61
  require 'heapinfo/nil'
@@ -11,34 +11,36 @@ module HeapInfo
11
11
  attr_reader :unsorted_bin
12
12
  # @return [Array<HeapInfo::Smallbin>] Smallbins in an array.
13
13
  attr_reader :smallbin
14
- # @return [Integer] The <tt>system_mem</tt>
14
+ # @return [Integer] The +system_mem+ in arena.
15
15
  attr_reader :system_mem
16
16
  # attr_reader :largebin, :last_remainder
17
17
 
18
- # Instantiate a <tt>HeapInfo::Arena</tt> object.
18
+ # Instantiate a {HeapInfo::Arena} object.
19
19
  #
20
20
  # @param [Integer] base Base address of arena.
21
21
  # @param [Integer] size_t Either 8 or 4
22
22
  # @param [Proc] dumper For dump more data
23
23
  def initialize(base, size_t, dumper)
24
- @base, @size_t, @dumper = base, size_t, dumper
24
+ @base = base
25
+ @size_t = size_t
26
+ @dumper = dumper
25
27
  reload!
26
28
  end
27
29
 
28
30
  # Refresh all attributes.
29
- # Retrive data using <tt>@dumper</tt>, load bins, top chunk etc.
31
+ # Retrive data using +@dumper+, load bins, top chunk etc.
30
32
  # @return [HeapInfo::Arena] self
31
33
  def reload!
32
34
  top_ptr_offset = @base + 8 + size_t * 10
33
35
  top_ptr = Helper.unpack(size_t, @dumper.call(top_ptr_offset, size_t))
34
36
  @fastbin = []
35
- return self if top_ptr == 0 # arena not init yet
37
+ return self if top_ptr.zero? # arena not init yet
36
38
  @top_chunk = Chunk.new size_t, top_ptr, @dumper
37
39
  @last_remainder = Chunk.new size_t, top_ptr_offset + 8, @dumper
38
40
  # this offset diff after 2.23
39
- @system_mem = 2.times.map do |off|
41
+ @system_mem = Array.new(2) do |off|
40
42
  Helper.unpack(size_t, @dumper.call(top_ptr_offset + 258 * size_t + 16 + off * size_t, size_t))
41
- end.find { |val| val >= 0x21000 and (val & 0xfff) == 0 }
43
+ end.find { |val| val >= 0x21000 && (val & 0xfff).zero? }
42
44
  @fastbin = Array.new(7) do |idx|
43
45
  f = Fastbin.new(size_t, @base + 8 - size_t * 2 + size_t * idx, @dumper, head: true)
44
46
  f.index = idx
@@ -67,7 +69,8 @@ module HeapInfo
67
69
  res
68
70
  end
69
71
 
70
- private
72
+ private
73
+
71
74
  attr_reader :size_t
72
75
  end
73
76
 
@@ -77,10 +80,10 @@ module HeapInfo
77
80
  attr_reader :fd
78
81
  # @return [Integer] index
79
82
  attr_accessor :index
80
-
81
- # Instantiate a <tt>HeapInfo::Fastbin</tt> object.
83
+
84
+ # Instantiate a {HeapInfo::Fastbin} object.
82
85
  #
83
- # @param [Mixed] args See <tt>HeapInfo::Chunk</tt> for more information.
86
+ # @param [Mixed] args See {HeapInfo::Chunk} for more information.
84
87
  def initialize(*args)
85
88
  super
86
89
  @fd = Helper.unpack(size_t, @data[0, @size_t])
@@ -95,7 +98,9 @@ module HeapInfo
95
98
  # For pretty inspect.
96
99
  # @return [String] Title with color codes.
97
100
  def title
98
- "%s%s: " % [Helper.color(Helper.class_name(self), sev: :bin), index.nil? ? nil : "[#{Helper.color("%#x" % idx_to_size)}]"]
101
+ class_name = Helper.color(Helper.class_name(self), sev: :bin)
102
+ size_str = index.nil? ? nil : "[#{Helper.color(format('%#x', idx_to_size))}]"
103
+ "#{class_name}#{size_str}: "
99
104
  end
100
105
 
101
106
  # Pretty inspect.
@@ -104,15 +109,15 @@ module HeapInfo
104
109
  title + list.map do |ptr|
105
110
  next "(#{ptr})\n" if ptr.is_a? Symbol
106
111
  next " => (nil)\n" if ptr.nil?
107
- " => %s" % Helper.color("%#x" % ptr)
112
+ format(' => %s', Helper.color(format('%#x', ptr)))
108
113
  end.join
109
114
  end
110
115
 
111
- # @return [Array<Integer, Symbol, NilClass>] single link list of <tt>fd</tt> chain.
116
+ # @return [Array<Integer, Symbol, NilClass>] single link list of +fd+ chain.
112
117
  # Last element will be:
113
- # - <tt>:loop</tt> if loop detectded
114
- # - <tt>:invalid</tt> invalid address detected
115
- # - <tt>nil</tt> end with zero address (normal case)
118
+ # - +:loop+ if loop detectded
119
+ # - +:invalid+ invalid address detected
120
+ # - +nil+ end with zero address (normal case)
116
121
  def list
117
122
  dup = {}
118
123
  ptr = @fd
@@ -127,19 +132,20 @@ module HeapInfo
127
132
  ret << nil
128
133
  end
129
134
 
130
- # @param [Integer] ptr Get the <tt>fd</tt> value of chunk at <tt>ptr</tt>.
131
- # @return [Integer] The <tt>fd</tt>.
135
+ # @param [Integer] ptr Get the +fd+ value of chunk at +ptr+.
136
+ # @return [Integer] The +fd+.
132
137
  def fd_of(ptr)
133
138
  addr_of(ptr, 2)
134
139
  end
135
140
 
136
- # @param [Integer] ptr Get the <tt>bk</tt> value of chunk at <tt>ptr</tt>.
137
- # @return [Integer] The <tt>bk</tt>.
141
+ # @param [Integer] ptr Get the +bk+ value of chunk at +ptr+.
142
+ # @return [Integer] The +bk+.
138
143
  def bk_of(ptr)
139
144
  addr_of(ptr, 3)
140
145
  end
141
146
 
142
- private
147
+ private
148
+
143
149
  def addr_of(ptr, offset)
144
150
  t = dump(ptr + size_t * offset, size_t)
145
151
  return nil if t.nil?
@@ -152,63 +158,60 @@ module HeapInfo
152
158
  # @return [Integer]
153
159
  attr_reader :bk
154
160
 
155
- # Instantiate a <tt>HeapInfo::UnsortedBin</tt> object.
161
+ # Instantiate a {HeapInfo::UnsortedBin} object.
156
162
  #
157
- # @param [Mixed] args See <tt>HeapInfo::Chunk</tt> for more information.
163
+ # @param [Mixed] args See {HeapInfo::Chunk} for more information.
158
164
  def initialize(*args)
159
165
  super
160
166
  @bk = Helper.unpack(size_t, @data[@size_t, @size_t])
161
167
  end
162
168
 
163
- # @option [Integer] size At most expand size. For <tt>size = 2</tt>, the expand list would be <tt>bk, bk, bin, fd, fd</tt>.
169
+ # @param [Integer] size
170
+ # At most expand size. For +size = 2+, the expand list would be +bk, bk, bin, fd, fd+.
164
171
  # @return [String] unsorted bin layouts wrapper with color codes.
165
172
  def inspect(size: 2)
166
173
  list = link_list(size)
167
- return '' if list.size <= 1 and Helper.class_name(self) != 'UnsortedBin' # bad..
174
+ return '' if list.size <= 1 && Helper.class_name(self) != 'UnsortedBin' # bad..
168
175
  title + pretty_list(list) + "\n"
169
176
  end
170
177
 
171
178
  # Wrapper the double-linked list with color codes.
172
- # @param [Array<Integer>] list The list from <tt>#link_list</tt>.
179
+ # @param [Array<Integer>] list The list from +#link_list+.
173
180
  # @return [String] Wrapper with color codes.
174
181
  def pretty_list(list)
175
182
  center = nil
176
183
  list.map.with_index do |c, idx|
177
- next center = Helper.color("[self]", sev: :bin) if c == @base
184
+ next center = Helper.color('[self]', sev: :bin) if c == @base
185
+ color_c = Helper.color(format('%#x', c))
178
186
  fwd = fd_of(c)
179
- next "%s(invalid)" % Helper.color("%#x" % c) if fwd.nil? # invalid c
187
+ next "#{color_c}(invalid)" if fwd.nil? # invalid c
180
188
  bck = bk_of(c)
181
189
  if center.nil? # bk side
182
- next Helper.color("%s%s" % [
183
- Helper.color("%#x" % c),
184
- fwd == list[idx+1] ? nil : "(%#x)" % fwd,
185
- ])
186
- else #fd side
187
- next Helper.color("%s%s" % [
188
- bck == list[idx-1] ? nil : "(%#x)" % bck,
189
- Helper.color("%#x" % c),
190
- ])
190
+ Helper.color(format('%s%s', color_c, fwd == list[idx + 1] ? nil : format('(%#x)', fwd)))
191
+ else # fd side
192
+ Helper.color(format('%s%s', bck == list[idx - 1] ? nil : format('(%#x)', bck), color_c))
191
193
  end
192
- end.join(" === ")
194
+ end.join(' === ')
193
195
  end
194
196
 
195
197
  # Return the double link list with bin in the center.
196
198
  #
197
- # The list will like <tt>[..., bk of bk, bk of bin, bin, fd of bin, fd of fd, ...]</tt>.
198
- # @param [Integer] expand_size At most expand size. For <tt>size = 2</tt>, the expand list would be <tt>bk, bk, bin, fd, fd</tt>.
199
+ # The list will like +[..., bk of bk, bk of bin, bin, fd of bin, fd of fd, ...]+.
200
+ # @param [Integer] expand_size
201
+ # At most expand size. For +size = 2+, the expand list would be +bk, bk, bin, fd, fd+.
199
202
  # @return [Array<Integer>] The linked list.
200
203
  def link_list(expand_size)
201
204
  list = [@base]
202
205
  # fd
203
- work = Proc.new do |ptr, nxt, append|
206
+ work = proc do |ptr, nxt, append|
204
207
  sz = 0
205
208
  dup = {}
206
- while ptr != @base and sz < expand_size
209
+ while ptr != @base && sz < expand_size
207
210
  append.call ptr
208
211
  break if ptr.nil? # invalid pointer
209
212
  break if dup[ptr] # looped
210
213
  dup[ptr] = true
211
- ptr = self.send(nxt, ptr)
214
+ ptr = send(nxt, ptr)
212
215
  sz += 1
213
216
  end
214
217
  end
@@ -220,7 +223,6 @@ module HeapInfo
220
223
 
221
224
  # Class for record smallbin type chunk.
222
225
  class Smallbin < UnsortedBin
223
-
224
226
  # Mapping index of smallbin to chunk size.
225
227
  # @return [Integer] size
226
228
  def idx_to_size
@@ -1,9 +1,8 @@
1
1
  require 'digest'
2
2
  module HeapInfo
3
-
4
3
  # Self implment file-base cache manager.
5
4
  #
6
- # Values are recorded in files based on <tt>Marshal</tt>
5
+ # Values are recorded in files based on <tt>Marshal</tt>.
7
6
  module Cache
8
7
  # Directory for caching files.
9
8
  # e.g. HeapInfo will record main_arena_offset for glibc(s)
@@ -24,8 +23,8 @@ module HeapInfo
24
23
  # @return [Boolean] true
25
24
  def self.write(key, value)
26
25
  filepath = realpath key
27
- FileUtils.mkdir_p(File.dirname filepath)
28
- IO.binwrite(filepath, Marshal::dump(value))
26
+ FileUtils.mkdir_p(File.dirname(filepath))
27
+ IO.binwrite(filepath, Marshal.dump(value))
29
28
  true
30
29
  end
31
30
 
@@ -35,8 +34,8 @@ module HeapInfo
35
34
  # @return [Object, NilClass] value that recorded, return <tt>nil</tt> when cache miss.
36
35
  def self.read(key)
37
36
  filepath = realpath key
38
- return unless File.file? filepath
39
- Marshal::load IO.binread filepath
37
+ return unless File.file?(filepath)
38
+ Marshal.load(IO.binread(filepath))
40
39
  rescue
41
40
  nil # handle if file content invalid
42
41
  end
@@ -44,24 +43,24 @@ module HeapInfo
44
43
  # @param [String] key
45
44
  # @return [String] Prepend with <tt>CACHE_DIR</tt>
46
45
  def self.realpath(key)
47
- raise ArgumentError.new('Invalid key(file path)') if key =~ /[^\w\/]/
46
+ raise ArgumentError, 'Invalid key(file path)' if key =~ %r{[^\w/]}
48
47
  File.join(CACHE_DIR, key)
49
48
  end
50
49
 
51
- # @return [Object] Not important.
52
- def self.load
50
+ # @return [void]
51
+ def self.init
53
52
  FileUtils.mkdir_p(CACHE_DIR)
54
53
  rescue
55
54
  # To prevent ~/ is not writable.
56
- self.send :remove_const, :CACHE_DIR
57
- self.const_set :CACHE_DIR, File.join(HeapInfo::TMP_DIR, '.cache/heapinfo')
55
+ send(:remove_const, :CACHE_DIR)
56
+ const_set(:CACHE_DIR, File.join(HeapInfo::TMP_DIR, '.cache/heapinfo'))
58
57
  end
59
58
 
60
59
  # Clear the cache directory.
61
- # @return [Object] Not important.
60
+ # @return [void]
62
61
  def self.clear_all
63
62
  FileUtils.rm_rf CACHE_DIR
64
63
  end
65
- self.load
64
+ init
66
65
  end
67
66
  end
@@ -10,24 +10,24 @@ module HeapInfo
10
10
  # @return [Integer] Base address of this chunk
11
11
  attr_reader :base
12
12
 
13
- # Instantiate a <tt>HeapInfo::Chunk</tt> object
13
+ # Instantiate a {HeapInfo::Chunk} object
14
14
  #
15
15
  # @param [Integer] size_t 4 or 8
16
16
  # @param [Integer] base Start address of this chunk
17
17
  # @param [Proc] dumper For dump more information of this chunk
18
- # @param [Boolean] head For specific if is fake chunk in <tt>arena</tt>. If <tt>head</tt> is <tt>true</tt>, will not load <tt>size</tt> and <tt>prev_size</tt> (since it's meaningless)
18
+ # @param [Boolean] head
19
+ # For specific if is fake chunk in +arena+.
20
+ # If +head+ is +true+, will not load +size+ and +prev_size+ (since it's meaningless)
19
21
  # @example
20
- # HeapInfo::Chunk.new 8, 0x602000, ->(addr, len) { [0,0x21, 0xda4a].pack("Q*")[addr-0x602000, len] }
22
+ # HeapInfo::Chunk.new 8, 0x602000, ->(addr, len) { [0,0x21, 0xda4a].pack('Q*')[addr-0x602000, len] }
21
23
  # # create a chunk with chunk size 0x21
22
24
  def initialize(size_t, base, dumper, head: false)
23
- raise ArgumentError.new('size_t can be either 4 or 8') unless [4, 8].include? size_t
24
- self.class.send(:define_method, :dump){|*args| dumper.call(*args)}
25
+ raise ArgumentError, 'size_t can be either 4 or 8' unless [4, 8].include?(size_t)
26
+ self.class.send(:define_method, :dump) { |*args| dumper.call(*args) }
25
27
  @size_t = size_t
26
28
  @base = base
27
29
  sz = dump(@base, size_t * 2)
28
- if head # no need to read size if is bin
29
- return @data = dump(@base + size_t * 2, size_t * 4)
30
- end
30
+ return @data = dump(@base + size_t * 2, size_t * 4) if head # no need to read size if is bin
31
31
  @prev_size = Helper.unpack(size_t, sz[0, size_t])
32
32
  @size = Helper.unpack(size_t, sz[size_t..-1])
33
33
  r_size = [size - size_t * 2, size_t * 4].min # don't read too much data
@@ -35,14 +35,14 @@ module HeapInfo
35
35
  @data = dump(@base + size_t * 2, r_size)
36
36
  end
37
37
 
38
- # Hook <tt>#to_s</tt> for pretty printing
38
+ # Hook +#to_s+ for pretty printing
39
39
  # @return [String]
40
40
  def to_s
41
- ret = Helper.color("#<%s:%#x>\n" % [self.class.to_s, @base], sev: :klass) +
42
- "flags = [#{flags.map{|f|Helper.color(":#{f}", sev: :sym)}.join(',')}]\n" +
43
- "size = #{Helper.color "%#x" % size} (#{bintype})\n"
44
- ret += "prev_size = #{Helper.color "%#x" % @prev_size}\n" unless flags.include? :prev_inuse
45
- ret += "data = #{Helper.color @data.inspect}#{'...' if @data.length < size - size_t * 2}\n"
41
+ ret = Helper.color(format("#<%s:%#x>\n", self.class.to_s, @base), sev: :klass)
42
+ ret += "flags = [#{flags.map { |f| Helper.color(":#{f}", sev: :sym) }.join(',')}]\n"
43
+ ret += "size = #{Helper.color(format('%#x', size))} (#{bintype})\n"
44
+ ret += "prev_size = #{Helper.color(format('%#x', @prev_size))}\n" unless flags.include? :prev_inuse
45
+ ret += "data = #{Helper.color(@data.inspect)}#{'...' if @data.length < size - size_t * 2}\n"
46
46
  ret
47
47
  end
48
48
 
@@ -55,29 +55,29 @@ module HeapInfo
55
55
  def flags
56
56
  mask = @size - size
57
57
  flag = []
58
- flag << :non_main_arena unless mask & 4 == 0
59
- flag << :mmapped unless mask & 2 == 0
60
- flag << :prev_inuse unless mask & 1 == 0
58
+ flag << :non_main_arena unless (mask & 4).zero?
59
+ flag << :mmapped unless (mask & 2).zero?
60
+ flag << :prev_inuse unless (mask & 1).zero?
61
61
  flag
62
62
  end
63
63
 
64
64
  # Ask if chunk not belongs to main arena.
65
65
  #
66
- # @return [Boolean] <tt>true|false</tt> if chunk not belongs to main arena
66
+ # @return [Boolean] +true|false+ if chunk not belongs to main arena
67
67
  def non_main_arena?
68
68
  flags.include? :non_main_arena
69
69
  end
70
70
 
71
71
  # Ask if chunk is mmapped.
72
72
  #
73
- # @return [Boolean] <tt>true|false</tt> if chunk is mmapped
73
+ # @return [Boolean] +true|false+ if chunk is mmapped
74
74
  def mmapped?
75
75
  flags.include? :mmapped
76
76
  end
77
77
 
78
78
  # Ask if chunk has set the prev-inuse bit.
79
79
  #
80
- # @return [Boolean] <tt>true|false</tt> if the <tt>prev_inuse</tt> bit has been set
80
+ # @return [Boolean] +true|false+ if the +prev_inuse+ bit has been set
81
81
  def prev_inuse?
82
82
  flags.include? :prev_inuse
83
83
  end
@@ -89,7 +89,7 @@ module HeapInfo
89
89
  end
90
90
 
91
91
  # Bin type of this chunk
92
- # @return [Symbol] Bin type is simply determined according to <tt>#size</tt>
92
+ # @return [Symbol] Bin type is simply determined according to +#size+
93
93
  # @example
94
94
  # [c.size, c.size_t]
95
95
  # # => [80, 8]
@@ -110,8 +110,8 @@ module HeapInfo
110
110
  return :unknown if sz < @size_t * 4
111
111
  return :fast if sz <= @size_t * 16
112
112
  return :small if sz <= @size_t * 0x7e
113
- return :large if sz <= @size_t * 0x3ffe # is this correct?
114
- return :mmap
113
+ return :large if sz <= @size_t * 0x3ffe # is this correct?
114
+ :mmap
115
115
  end
116
116
  end
117
117
  end
@@ -1,17 +1,28 @@
1
1
  module HeapInfo
2
2
  # Self-defined array for collecting chunk(s)
3
3
  class Chunks
4
- # Instantiate a <tt>HeapInfo::Chunks</tt> object
4
+ include Enumerable
5
+ # Instantiate a <tt>HeapInfo::Chunks</tt> object.
5
6
  def initialize
6
7
  @chunks = []
7
8
  end
8
9
 
9
- def method_missing(method_sym, *arguments, &block) # :nodoc:
10
- return super unless @chunks.respond_to? method_sym
11
- @chunks.send(method_sym, *arguments, &block)
10
+ # @return [void]
11
+ def <<(val)
12
+ @chunks << val
12
13
  end
13
14
 
14
- # Hook <tt>#to_s</tt> for pretty printing.
15
+ # @return [Integer]
16
+ def size
17
+ @chunks.size
18
+ end
19
+
20
+ # @return [void]
21
+ def each(&block)
22
+ @chunks.each(&block)
23
+ end
24
+
25
+ # Hook +#to_s+ for pretty printing.
15
26
  # @return [String]
16
27
  def to_s
17
28
  @chunks.map(&:to_s).join("\n")