rite-fuzzr 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
data/lib/fuzz/fuzz.rb ADDED
@@ -0,0 +1,399 @@
1
+ # encoding: utf-8
2
+ # -------------------------------------------------------------------
3
+ # fuzz.rb - TAOX11 fuzz checker
4
+ #
5
+ # Author: Martin Corino
6
+ #
7
+ # Copyright (c) Remedy IT Expertise BV
8
+ # -------------------------------------------------------------------
9
+
10
+ require 'optparse'
11
+ require 'tempfile'
12
+ require 'fileutils'
13
+ require 'yaml'
14
+ require 'fuzz/log'
15
+ require 'fuzz/system'
16
+ require 'fuzz/options'
17
+ require 'fuzz/fzzr'
18
+ require 'fuzz/version'
19
+
20
+ module Fuzz
21
+
22
+ def self.root_path
23
+ f = File.expand_path(__FILE__)
24
+ f = File.expand_path(File.readlink(f)) if File.symlink?(f)
25
+ File.dirname(f)
26
+ end
27
+
28
+ FUZZ_ROOT = self.root_path
29
+
30
+ class << self
31
+
32
+ include LogMethods
33
+ include Sys::SysMethods
34
+
35
+ def reporter
36
+ @reporter ||= Fuzz::Reporter.new
37
+ end
38
+
39
+ def set_reporter(rep)
40
+ @reporter = rep
41
+ end
42
+ alias :reporter= :set_reporter
43
+
44
+ def options
45
+ Fuzz::OPTIONS
46
+ end
47
+
48
+ def reset
49
+ options.reset
50
+ end
51
+
52
+ def load_config
53
+ options.load_config
54
+ end
55
+
56
+ def includes
57
+ unless @include_re
58
+ @include_re = []
59
+ # @include_re << "\\.(#{Fuzz::FileObject.extensions.join('|')})$"
60
+ # @include_re << "#{Fuzz::FileObject.filenames.join('|')}$"
61
+ @include_re << "\\.(#{options.config[:exts].join('|')})$"
62
+ @include_re << "#{options.config[:filenames].join('|')}$"
63
+ end
64
+ @include_re
65
+ end
66
+
67
+ #
68
+ # fuzzer registration
69
+ #
70
+
71
+ def fuzzers
72
+ @fuzzers ||= {}
73
+ end
74
+
75
+ def register_fzzr(fzzr)
76
+ raise "Duplicate fuzzer registration: #{fzzr.fuzz_id}" if fuzzers.has_key?(fzzr.fuzz_id)
77
+ fuzzers[fzzr.fuzz_id] = fzzr
78
+ fzzr.setup(options.optparser) if options.optparser && fzzr.respond_to?(:setup) && fzzr_included?(fzzr)
79
+ end
80
+
81
+ def get_fzzr(id)
82
+ fuzzers[id]
83
+ end
84
+
85
+ def fzzr_excluded?(fzzr)
86
+ options.config[:fzzr_excludes].include?(fzzr.fuzz_id.to_sym)
87
+ end
88
+
89
+ def fzzr_included?(fzzr)
90
+ !fzzr_excluded?(fzzr)
91
+ end
92
+
93
+ #
94
+ # load fuzzers
95
+ #
96
+ def load_fuzzers
97
+ # standard fuzzers included in Gem
98
+ unless loaded_fzzr_paths.include?(_p = File.join(FUZZ_ROOT, 'fuzzers'))
99
+ Dir.glob(File.join(_p, '*.rb')).each do |fnm|
100
+ require fnm
101
+ end
102
+ loaded_fzzr_paths << _p
103
+ end
104
+ # configured fuzzers
105
+ options.config[:fzzr_paths].each do |fzzrpath|
106
+ unless loaded_fzzr_paths.include?(_p = File.expand_path(fzzrpath))
107
+ Dir.glob(File.join(_p, '*.rb')).each do |fnm|
108
+ require fnm
109
+ end
110
+ loaded_fzzr_paths << _p
111
+ end
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def loaded_fzzr_paths
118
+ @loaded_fuzzr_paths ||= []
119
+ end
120
+
121
+ end
122
+
123
+ ## Backwards compatibility
124
+ def self.log_verbose(msg)
125
+ log_info(msg) if verbose?
126
+ end
127
+
128
+ #
129
+ # Option methods
130
+ #
131
+
132
+ def self.verbosity
133
+ options.verbose
134
+ end
135
+
136
+ def self.apply_fix?
137
+ options.apply_fix || false
138
+ end
139
+
140
+ def self.follow_symlink?
141
+ options.config[:follow_symlink] || false
142
+ end
143
+
144
+ def self.excludes
145
+ options.config[:excludes] || []
146
+ end
147
+
148
+ def self.excluded?(object)
149
+ excludes.any? { |excl| (object.fullpath =~ /#{excl}/) }
150
+ end
151
+
152
+ #
153
+ # parse commandline arguments
154
+ #
155
+ def self.init_optparser
156
+ script_name = File.basename($0)
157
+ if not script_name =~ /fuzz/
158
+ script_name = "ruby "+$0
159
+ end
160
+
161
+ options.optparser = opts = OptionParser.new
162
+ opts.banner = "Usage: #{script_name} [options] [glob [glob]]\n\n"
163
+ opts.separator "\n--- [General options] ---\n\n"
164
+ opts.on('-t', '--filetype', '=EXT', String,
165
+ 'Defines an alternative filetype to search and scan. Can be specified multiple times.',
166
+ "Default: #{Fuzz::FileObject.extensions.join('|')}") { |v|
167
+ (options.user_config[:exts] ||= []) << v
168
+ }
169
+ opts.on('-f', '--file', '=NAME', String,
170
+ 'Defines an alternative filename to search and scan. Can be specified multiple times.',
171
+ "Default: #{Fuzz::FileObject.filenames.join('|')}") { |v|
172
+ (options.user_config[:filenames] ||= []) << v
173
+ }
174
+ opts.on('-a', '--add-files',
175
+ 'Add custom filenames and/or filetype extensions to default list instead of replacing defaults.',
176
+ 'Default: false') { |v|
177
+ options.user_config[:add_files] = true
178
+ }
179
+ opts.on('-S', '--no-symlinks',
180
+ 'Do not follow symlinks.',
181
+ 'Default: follow symlinks') { |v|
182
+ options.user_config[:follow_symlink] = false
183
+ }
184
+ opts.on('-P', '--fzzr-path', '=PATH',
185
+ 'Adds search path for Fuzzers.',
186
+ "Default: loaded from ~/#{FUZZRC} and/or ./#{FUZZRC}") { |v|
187
+ (options.user_config[:fzzr_paths] ||= []) << v.to_s
188
+ }
189
+ opts.on('-B', '--blacklist', '=FZZRID',
190
+ 'Adds Fuzzer ID to list of fuzzers to exclude from Fuzz check.',
191
+ 'Default: none') { |v|
192
+ (options.user_config[:fzzr_excludes] ||= []) << v.to_sym
193
+ }
194
+ opts.on('-X', '--exclude', '=MASK',
195
+ 'Adds path mask (regular expression) to list to exclude from Fuzz check.',
196
+ 'Default: none') { |v|
197
+ (options.user_config[:excludes] ||= []) << v
198
+ }
199
+ opts.on('-c', '--config', '=FUZZRC',
200
+ 'Load config from FUZZRC file.',
201
+ "Default: ~/#{FUZZRC} and/or ./#{FUZZRC}") { |v|
202
+ options.add_config(v)
203
+ }
204
+ opts.on('--write-config', '=[FUZZRC]',
205
+ 'Write config to file and exit.',
206
+ "Default: ./#{FUZZRC}") { |v|
207
+ options.user_config.save(String === v ? v : FUZZRC)
208
+ exit
209
+ }
210
+ opts.on('--show-config',
211
+ 'Display config settings and exit.') { |v|
212
+ options.load_config
213
+ puts YAML.dump(options.config.__send__ :table)
214
+ exit
215
+ }
216
+
217
+ opts.separator ''
218
+ opts.on('-o', '--output', '=FILE', String,
219
+ 'Specifies filename to write Fuzz messages to.',
220
+ 'Default: stderr') { |v|
221
+ options.output = v
222
+ }
223
+ opts.on('-p', '--apply-fix',
224
+ 'Apply fixes (if any) for Fuzz errors.',
225
+ 'Default: false') { |v|
226
+ options.apply_fix = true
227
+ }
228
+ opts.on('-n', '--no-recurse',
229
+ 'Prevents directory recursion in file selection.',
230
+ 'Default: recurse') { |v|
231
+ options.recurse = false
232
+ }
233
+ opts.on('-v', '--verbose',
234
+ 'Run with increased verbosity level. Repeat to increase more.',
235
+ 'Default: 1') { |v| options.verbose += 1 }
236
+
237
+ opts.separator ''
238
+ opts.on('-L', '--list',
239
+ 'List available Fuzzers and exit.') {
240
+ options.load_config
241
+ load_fuzzers
242
+ puts "TAOX11 fuzz checker #{FUZZ_VERSION_MAJOR}.#{FUZZ_VERSION_MINOR}.#{FUZZ_VERSION_RELEASE}"
243
+ puts FUZZ_COPYRIGHT
244
+ puts('%-30s %s' % %w{Fuzzer Description})
245
+ puts(('-' * 30)+' '+('-' * 48))
246
+ fuzzers.values.each { |fzzr| puts('%-30s %s' % [fzzr.fuzz_id, fzzr.description]) }
247
+ puts
248
+ exit
249
+ }
250
+
251
+ opts.separator ""
252
+ opts.on('-V', '--version',
253
+ 'Show version information and exit.') {
254
+ puts "TAOX11 fuzz checker #{FUZZ_VERSION_MAJOR}.#{FUZZ_VERSION_MINOR}.#{FUZZ_VERSION_RELEASE}"
255
+ puts FUZZ_COPYRIGHT
256
+ exit
257
+ }
258
+ opts.on('-h', '--help',
259
+ 'Show this help message.') {
260
+ options.load_config
261
+ load_fuzzers
262
+ puts opts;
263
+ puts;
264
+ exit
265
+ }
266
+
267
+ opts.separator "\n--- [Fuzzer options] ---\n\n"
268
+ end
269
+
270
+ def self.parse_args(argv)
271
+ options.optparser.parse!(argv)
272
+ end
273
+
274
+ def self.select_fzzrs(object)
275
+ fuzzers.values.collect { |fzzr| (fzzr_included?(fzzr) && fzzr.applies_to?(object)) ? fzzr : nil }.compact
276
+ end
277
+
278
+ def self.update_file_object(fo)
279
+ if File.writable?(fo.fullpath)
280
+ log_verbose(%Q{Updating #{fo}...})
281
+ ftmp = Tempfile.new(fo.name)
282
+ log_verbose(%Q{+ Writing temp file #{ftmp.path}...})
283
+ fo.lines.each { |ln| ftmp.print ln }
284
+ ftmp.close(false) # close but do NOT unlink
285
+ log_verbose(%Q{+ Replacing #{fo} with #{ftmp.path}})
286
+ # create temporary backup
287
+ ftmp2 = Tempfile.new(fo.name)
288
+ ftmp2_name = ftmp2.path.dup
289
+ ftmp2.close(true)
290
+ mv(fo.fullpath, ftmp2_name)
291
+ # replace original
292
+ begin
293
+ mv(ftmp.path, fo.fullpath)
294
+ # preserve file mode
295
+ chmod(File.lstat(ftmp2_name).mode, fo.fullpath)
296
+ rescue
297
+ log_error(%Q{FAILED updating #{fo}: #{$!}})
298
+ # restore backup
299
+ mv(ftmp2_name, fo.fullpath)
300
+ raise
301
+ end
302
+ # remove backup
303
+ File.unlink(ftmp2_name)
304
+ log_verbose(%Q{Finished updating #{fo}.})
305
+ return true
306
+ else
307
+ log_error(%Q{NO_ACCESS - cannot update #{fo}})
308
+ return false
309
+ end
310
+ end
311
+
312
+ def self.handle_object(object)
313
+ log_verbose(%Q{Handling #{object}})
314
+ fzzrs = select_fzzrs(object)
315
+ no_fixes_allowed = false
316
+ rc = fzzrs.inject(true) do |result, fzzr|
317
+ log_verbose(%Q{+ Running fuzzer #{fzzr.fuzz_id}})
318
+ begin
319
+ if fzzr.run(object, options.apply_fix)
320
+ result
321
+ else
322
+ log_verbose(%Q{+ Error from fuzzer #{fzzr.fuzz_id}})
323
+ false
324
+ end
325
+ rescue
326
+ log_error(%Q{EXCEPTION CAUGHT running fuzzer #{fzzr.fuzz_id} on #{object} - #{$!}\n#{$!.backtrace.join("\n")}})
327
+ no_fixes_allowed = true
328
+ break ## immediately stop handling this object, rc will remain false
329
+ end
330
+ end
331
+ unless no_fixes_allowed
332
+ if Fuzz.apply_fix? && object.changed?
333
+ rc = update_file_object(object) && rc
334
+ end
335
+ end
336
+ rc ? true : false
337
+ end
338
+
339
+ def self.iterate_paths(paths)
340
+ paths.inject(true) do |result, path|
341
+ if File.readable?(path) && (!File.symlink?(path) || follow_symlink?)
342
+ if File.directory?(path)
343
+ rc = handle_object(dirobj = Fuzz::DirObject.new(path))
344
+ log_verbose(%Q{Iterating #{path}})
345
+ if options.recurse && !excluded?(dirobj)
346
+ rc = iterate_paths(Dir.glob(File.join(path, '*'))) && rc
347
+ end
348
+ rc
349
+ elsif File.file?(path)
350
+ handle_object(Fuzz::FileObject.new(path))
351
+ else
352
+ true
353
+ end
354
+ else
355
+ log_warning(File.readable?(path) ? %Q{Cannot read #{path}} : %Q{Cannot follow symlink #{path}})
356
+ false
357
+ end && result
358
+ end
359
+ end
360
+
361
+ def self.run_fzzrs(argv)
362
+ options.config[:exts].concat(Fuzz::FileObject.extensions) if options.config[:exts].empty? || options.config[:add_files]
363
+ options.config[:filenames].concat(Fuzz::FileObject.filenames) if options.config[:filenames].empty? || options.config[:add_files]
364
+
365
+ options.config[:exts].uniq!
366
+ options.config[:filenames].uniq!
367
+
368
+ f_close_output = false
369
+ if String === options.output
370
+ options.output = File.open(options.output, 'w')
371
+ f_close_output = true
372
+ end
373
+ begin
374
+ # determin files/paths to test
375
+ paths = argv.collect { |a| Dir.glob(a) }.flatten.uniq
376
+ paths = Dir.glob('*') if paths.empty?
377
+ # scan all determined objects
378
+ return iterate_paths(paths)
379
+ ensure
380
+ options.output.close if f_close_output
381
+ end
382
+ end
383
+
384
+ def self.run
385
+ init_optparser
386
+
387
+ # parse arguments
388
+ parse_args(ARGV)
389
+
390
+ # load config (if any)
391
+ options.load_config
392
+
393
+ # load fuzzers
394
+ load_fuzzers
395
+
396
+ run_fzzrs(ARGV)
397
+ end
398
+
399
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ # -------------------------------------------------------------------
3
+ # check_whitespace.rb - TAOX11 whitespace checker
4
+ #
5
+ # Author: Martin Corino
6
+ #
7
+ # Copyright (c) Remedy IT Expertise BV
8
+ # -------------------------------------------------------------------
9
+
10
+ module Fuzzers
11
+ class WhitespaceChecker
12
+ include Fuzz::Fzzr
13
+ def initialize
14
+ @fuzz_id = :check_whitespace
15
+ @description = 'checks for trailing whitespace, incorrect line endings and tabs'
16
+ end
17
+
18
+ def setup(optparser)
19
+ optparser.on('--wsc:tab-spacing=NUM', Integer,
20
+ 'Fuzzers::WhitespaceChecker - defines tab spacing to use for TAB replacement when --apply-fix is enabled.',
21
+ "Default: #{tab_spacing}") {|v| self.options[:tabspacing] = v }
22
+ end
23
+
24
+ def applies_to?(object)
25
+ Fuzz::FileObject === object && !is_excluded?(object)
26
+ end
27
+
28
+ def run(object, apply_fix)
29
+ _tws = []
30
+ _tabs = []
31
+ object.iterate(fuzz_id) do |lnptr|
32
+ if lnptr.text =~ /(\s\n|[\ \t\f\r\x0B])\Z/
33
+ if apply_fix
34
+ Fuzz.log_verbose(%Q{#{object.path}:#{lnptr.line_nr} - stripping trailing whitespace})
35
+ lnptr.text.rstrip!
36
+ lnptr.text << "\n" if $1.end_with?("\n")
37
+ else
38
+ _tws << lnptr.line_nr
39
+ end
40
+ end
41
+ if lnptr.text =~ /\t/
42
+ if apply_fix
43
+ Fuzz.log_warning(%Q{#{object.path}:#{lnptr.line_nr} - replacing tabs})
44
+ lnptr.text.gsub!(/\t/, ' ' * tab_spacing)
45
+ else
46
+ _tabs << lnptr.line_nr
47
+ end
48
+ end
49
+ end
50
+ Fuzz.log_error(%Q{#{object.path}:[#{_tws.join(',')}] trailing whitespace or incorrect line ending detected}) unless _tws.empty?
51
+ Fuzz.log_error(%Q{#{object.path}:[#{_tabs.join(',')}] tab(s) detected}) unless _tabs.empty?
52
+ return (_tws.empty? && _tabs.empty?)
53
+ end
54
+
55
+ private
56
+ def tab_spacing
57
+ self.options[:tabspacing] || 2
58
+ end
59
+ end
60
+
61
+ Fuzz.register_fzzr(WhitespaceChecker.new)
62
+ end
data/lib/fuzz/fzzr.rb ADDED
@@ -0,0 +1,258 @@
1
+ # encoding: utf-8
2
+ # -------------------------------------------------------------------
3
+ # fuzzr.rb - TAOX11 Fuzzer bases
4
+ #
5
+ # Author: Martin Corino
6
+ #
7
+ # Copyright (c) Remedy IT Expertise BV
8
+ # -------------------------------------------------------------------
9
+
10
+ module Fuzz
11
+ ##
12
+ # Fuzzers are objects having the following readonly attributes:
13
+ # #fuzz_id : id of fuzzer (Symbol)
14
+ # #description: (String)
15
+ # #errormsg : (String)
16
+ #
17
+ # and having the following methods:
18
+ # #applies_to?(object) : checks if the test applies to the object passed
19
+ # (Fuzz::DirObject or Fuzz::FileObject)
20
+ # #run(object,apply_fix): runs the fzzr test on the object passed
21
+ # (Fuzz::DirObject or Fuzz::FileObject)
22
+ # when apply_fix == true Fuzzer is directed to
23
+ # attempt to fix any problems found (future)
24
+ #
25
+ # Fuzz::Fzzr is provided as a convenience Mixin for fuzzers
26
+ #
27
+ # Fuzzers can inspect the options passed to fuzz.rb by referencing Fuzz::OPTIONS
28
+ ##
29
+
30
+ module Fzzr
31
+ attr_reader :fuzz_id, :description, :errormsg
32
+
33
+ def applies_to?(object)
34
+ !is_excluded?(object)
35
+ end
36
+
37
+ def setup(optparser)
38
+ end
39
+
40
+ def run(object, apply_fix)
41
+ true
42
+ end
43
+
44
+ def is_excluded?(object)
45
+ # force excludes to be parsed
46
+ _excludes
47
+ # now examine
48
+ ((!_is_included?(object)) || _excludes.any? { |excl| (object.fullpath =~ /#{excl}/) })
49
+ end
50
+
51
+ def options
52
+ Fuzz::OPTIONS[:config][:fzzr_opts][self.fuzz_id] ||= {}
53
+ end
54
+
55
+ private
56
+
57
+ def _is_included?(object)
58
+ Fuzz::DirObject === object || _includes.empty? || _includes.any? { |incl| (object.fullpath =~ /#{incl}/) }
59
+ end
60
+
61
+ def _includes
62
+ @_includes ||= Fuzz.includes.dup
63
+ end
64
+
65
+ def _excludes
66
+ unless @_excludes
67
+ @_excludes = []
68
+ @_excludes.concat(Fuzz.excludes)
69
+ Fuzz::OPTIONS[:config][:fzzr_paths].each do |fzzrpath|
70
+ fzzr_excl_file = File.join(fzzrpath, "#{self.fuzz_id}.excludes")
71
+ if File.readable?(fzzr_excl_file)
72
+ lns = IO.readlines(fzzr_excl_file).collect { |l| l.strip }
73
+ @_excludes.concat(lns.select {|l| !(l.empty? || l[0] == '!') })
74
+ _includes.concat(lns.select {|l| !(l.empty? || l[0] != '!') }.collect {|l| l[1,l.size].strip })
75
+ else
76
+ false
77
+ end
78
+ end
79
+ end
80
+ @_excludes
81
+ end
82
+ end # Fzzr
83
+
84
+ class DirObject
85
+ attr_reader :path, :fullpath, :name, :ext
86
+ def initialize(path)
87
+ @path = path
88
+ @fullpath = File.expand_path(path)
89
+ @name = File.basename(path)
90
+ @ext = File.extname(path).sub(/^\./,'')
91
+ end
92
+
93
+ def changed?
94
+ false
95
+ end
96
+
97
+ def iterate(fzzr_id, &block)
98
+ # nothing to iterate over
99
+ true
100
+ end
101
+
102
+ def to_s
103
+ "Dir:#{path}"
104
+ end
105
+ end # DirObject
106
+
107
+ class FileObject
108
+ EXTS = [
109
+ 'h', 'hxx', 'hpp', 'c', 'cc', 'cxx', 'cpp', 'H', 'C', 'inl', 'asm',
110
+ 'rb', 'erb', 'pl', 'pm', 'py',
111
+ 'idl', 'pidl',
112
+ 'mwc', 'mpc', 'mpb', 'mpt', 'mpd',
113
+ 'cdp', 'xml', 'conf', 'html',
114
+ 'asc', 'adoc'
115
+ ]
116
+ FILES = [
117
+ 'ChangeLog', 'README'
118
+ ]
119
+
120
+ def self.extensions
121
+ EXTS
122
+ end
123
+
124
+ def self.filenames
125
+ FILES
126
+ end
127
+
128
+ class LinePointer
129
+ attr_reader :err_lines
130
+
131
+ FZZR_ENABLE_RE = /X11_FUZZ\: enable ([^\s]+)/
132
+ FZZR_DISABLE_RE = /X11_FUZZ\: disable ([^\s]+)/
133
+
134
+ def initialize(lines, fzzr_id)
135
+ @lines = lines
136
+ @fzzr_id = fzzr_id.to_s
137
+ @err_lines = []
138
+ reset
139
+ end
140
+ def fzzr_disabled?
141
+ @fzzr_disabled
142
+ end
143
+ def line_nr
144
+ @line_nr+1
145
+ end
146
+ def text_at(offs)
147
+ ln = @line_nr+offs
148
+ if ln>=0 && ln<@lines.size
149
+ return @lines[ln]
150
+ end
151
+ nil
152
+ end
153
+ def set_text_at(offs, txt)
154
+ ln = @line_nr+offs
155
+ if ln>=0 && ln<@lines.size
156
+ return (@lines[ln] = txt)
157
+ end
158
+ nil
159
+ end
160
+ def text
161
+ text_at(0)
162
+ end
163
+ def text=(txt)
164
+ set_text_at(0, txt)
165
+ end
166
+ def move(offs)
167
+ if offs < 0
168
+ _backward(-offs) unless bof?
169
+ else
170
+ _forward(offs) unless eof?
171
+ end
172
+ self.line_nr
173
+ end
174
+ def reset
175
+ @line_nr = 0
176
+ @fzzr_disabled = false
177
+ _check_fzzr_escape
178
+ end
179
+ def to_eof
180
+ _forward(@lines.size - @line_nr)
181
+ end
182
+ def eof?
183
+ @line_nr >= @lines.size
184
+ end
185
+ def bof?
186
+ @line_nr <= 0
187
+ end
188
+ def mark_error(ln = nil)
189
+ @err_lines << (ln || (@line_nr+1))
190
+ end
191
+
192
+ private
193
+
194
+ def _forward(distance)
195
+ distance.times do
196
+ @line_nr += 1
197
+ break if eof?
198
+ _check_fzzr_escape
199
+ end
200
+ end
201
+
202
+ def _backward(distance)
203
+ distance.times do
204
+ break if bof?
205
+ @line_nr -= 1
206
+ _check_fzzr_escape(false)
207
+ end
208
+ end
209
+
210
+ def _check_fzzr_escape(forward = true)
211
+ begin
212
+ if FZZR_ENABLE_RE =~ @lines[@line_nr]
213
+ @fzzr_disabled = !forward if $1 == @fzzr_id
214
+ elsif FZZR_DISABLE_RE =~ @lines[@line_nr]
215
+ @fzzr_disabled = forward if $1 == @fzzr_id
216
+ end
217
+ rescue
218
+ Fuzz.log_error(%Q{ERROR: Exception while checking fzzr escapes in line #{@line_nr+1} - #{$!}\n#{@lines[@line_nr]}})
219
+ raise
220
+ end
221
+ end
222
+ end # LinePointer
223
+
224
+ attr_reader :path, :fullpath, :name, :ext, :lines
225
+
226
+ def initialize(path)
227
+ @path = path
228
+ @fullpath = File.expand_path(path)
229
+ @name = File.basename(path)
230
+ @ext = File.extname(path).sub(/^\./,'')
231
+ @lines = nil
232
+ @pointer = nil
233
+ @changed = false
234
+ end
235
+
236
+ def changed?
237
+ @changed
238
+ end
239
+
240
+ def iterate(fzzr_id, &block)
241
+ @lines ||= IO.readlines(fullpath)
242
+ lines_copy = @lines.collect {|l| l.dup }
243
+ pointer = LinePointer.new(@lines, fzzr_id)
244
+ begin
245
+ block.call(pointer) unless pointer.fzzr_disabled?
246
+ pointer.move(1)
247
+ end while !pointer.eof?
248
+ Fuzz.log_error(%Q{#{self.path}[#{pointer.err_lines.join(',')}] #{Fuzz.get_fzzr(fzzr_id).errormsg}}) unless pointer.err_lines.empty?
249
+ @changed |= (@lines != lines_copy)
250
+ lines_copy = nil
251
+ return pointer.err_lines.empty?
252
+ end
253
+
254
+ def to_s
255
+ "File:#{fullpath}"
256
+ end
257
+ end # FileObject
258
+ end # Fuzz