fuzzr 0.9.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fuzzr might be problematic. Click here for more details.

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