File Coverage

lib/Devel/Trepan/DB/LineCache.pm
Criterion Covered Total %
statement 288 471 61.1
branch 72 192 37.5
condition 17 60 28.3
subroutine 52 71 73.2
pod 25 36 69.4
total 454 830 54.7


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             #
3             # Copyright (C) 2011-2014 Rocky Bernstein <rocky@cpan.org>
4             #
5             #
6 14     14   29035 use Digest::SHA;
  14         45689  
  14         787  
7 14     14   233 use Scalar::Util;
  14         39  
  14         626  
8              
9 14     14   3175 use version; $VERSION = '0.3';
  14         11969  
  14         97  
10              
11             package Devel::Trepan::DB::LineCache;
12              
13             # FIXME: Figure out where to put this
14             # *pod
15             #
16             # I<eval_ok($code)> => I<boolean>
17             #
18             # Evaluate I<$code> and return true if there's no error.
19             # *cut
20             sub eval_ok ($)
21             {
22 0     0 0 0 my $code = shift;
23 14     14   1563 no strict; no warnings;
  14     14   36  
  14         408  
  14         76  
  14         34  
  14         1482  
24 0 0       0 $DB::namespace_package = 'package main' unless $DB::namespace_package;
25 0         0 my $wrapped = "$DB::namespace_package; sub { $code }";
26 0         0 my %orig_sub = %DB::sub;
27 0         0 eval $wrapped;
28 0         0 %DB::sub = %orig_sub;
29             # print $@, "\n" if $@;
30 0         0 return !$@;
31             }
32              
33 14     14   92 use rlib '../../..';
  14         33  
  14         190  
34              
35             =head1 NAME
36              
37             Devel::Trepan::DB::LineCache - package to read and cache lines of a Perl program.
38              
39             =head1 SYNOPSIS
40              
41             The LineCache package allows one to get any line from any file,
42             caching lines of the file on first access to the file. Although the
43             file may be any file, the common use is when the file is a Perl
44             script since parsing of the file is done to figure out where the
45             statement boundaries are.
46              
47             The routines here may be is useful when a small random sets of lines
48             are read from a single file, in particular in a debugger to show
49             source lines.
50              
51             use Devel::Trepan::DB::LineCache;
52             $lines = getlines('/tmp/myperl.pl')
53              
54             # The following lines have same effect as the above.
55             unshift @INC, '/tmp';
56             $lines = getlines('myperl.pl');
57             shift @INC;
58              
59             chdir '/tmp';
60             $lines = getlines('myperl.pl')
61              
62             $line = getline('/tmp/myperl.pl', 6)
63             # Note lines[6] == line (if /tmp/myperl.pl has 6 lines)
64              
65             clear_file_cache
66             update_cache # Check for modifications of all cached files.
67              
68             =cut
69              
70             our(@ISA, @EXPORT, $VERSION);
71             require Exporter;
72             @ISA = qw(Exporter);
73             @EXPORT = qw(cache_file clear_file_format_cache
74             clear_cache update_cache
75             file_list getlines
76             filename_is_eval getline map_file is_cached
77             highlight_string cache_file
78             load_file map_script map_file_line remap_file
79             remap_dbline_to_file remap_string_to_tempfile %script_cache
80             trace_line_numbers update_script_cache
81             );
82             $VERSION = "1.0";
83              
84 14     14   6846 use English qw( -no_match_vars );
  14         38  
  14         105  
85 14     14   5425 use vars qw(%file_cache %script_cache);
  14         38  
  14         804  
86              
87 14     14   81 use strict; use warnings;
  14     14   32  
  14         305  
  14         76  
  14         34  
  14         410  
88 14     14   75 no warnings 'once';
  14         35  
  14         467  
89 14     14   80 no warnings 'redefine';
  14         27  
  14         411  
90              
91 14     14   79 use Cwd 'abs_path';
  14         31  
  14         620  
92 14     14   84 use File::Basename;
  14         32  
  14         826  
93 14     14   100 use File::Spec;
  14         40  
  14         362  
94 14     14   7759 use File::stat;
  14         79034  
  14         101  
95              
96             ## struct(stat => '$', lines => '%', path => '$', sha1 => '$');
97              
98             # The file cache. The key is a name as would be given by Perl for
99             # __FILE__. The value is a LineCacheInfo object.
100              
101              
102             # Maps a string filename (a String) to a key in %file_cache (a
103             # String).
104             #
105             # One important use of %file2file_remap is mapping the a full path
106             # of a file into the name stored in %file_cache or given by Perl's
107             # __FILE__. Applications such as those that get input from users,
108             # may want canonicalize a file name before looking it up. This map
109             # gives a way to do that.
110             #
111             # Another related use is when a template system is used. Here we'll
112             # probably want to remap not only the file name but also line
113             # ranges. Will probably use this for that, but I'm not sure.
114              
115             my %file2file_remap;
116             my %file2file_remap_lines;
117             my %script2file;
118             my @tempfiles;
119              
120             =head1 SUBROUTINES
121              
122             I<Note:> in what follows we use I<$file_or_script> to refer to either
123             a filename which generally should be a Perl file, or a psuedo-file
124             name that is created in an I<eval()> string. Often, the filename does
125             not have to be fully qualified. In some cases I<@INC> will be used to
126             find the file.
127              
128             =cut
129              
130             ## FIXME:: Make conditional
131 14     14   8245 use Devel::Trepan::DB::Colors;
  14         103  
  14         24673  
132             my $perl_formatter;
133              
134             sub color_setup {
135 14     14 0 97 $perl_formatter = Devel::Trepan::DB::Colors::setup(@_);
136             }
137             color_setup('light');
138              
139             sub remove_temps()
140             {
141 14     14 0 78 for my $filename (values %script2file) {
142 1 50       143 unlink($filename) if -f $filename;
143             }
144 14         143 for my $filename (@tempfiles) {
145 0 0       0 unlink($filename) if -f $filename;
146             }
147             }
148              
149             END {
150 14     14   4044 $DB::ready = 0;
151 14         77 remove_temps
152             };
153              
154             =head2 clear_file_cache
155              
156             B<clear_file_cache()>
157              
158             B<clear_file_cache(I<$filename>)>
159              
160              
161             Clear the file cache of I<$filename>. If I<$filename>
162             is not given, clear all files in the cache.
163              
164             =cut
165              
166             sub clear_file_cache(;$)
167             {
168 1 50   1 1 1055 if (scalar @_ == 1) {
169 0         0 my $filename = shift;
170 0 0       0 if ($file_cache{$filename}) {
171 0         0 delete $file_cache{$filename};
172             }
173             } else {
174 1         39 %file_cache = ();
175 1         13 %file2file_remap = ();
176 1         5 %file2file_remap_lines = ();
177             }
178             }
179              
180             =head2 clear_file_format_cache
181              
182             B<clear_file_format_cache()>
183              
184             Remove syntax-formatted lines in the cache. Use this when you change
185             the L<Syntax::Highlight::Perl> colors and want to redo how files may
186             have previously been syntax marked.
187              
188             =cut
189              
190             sub clear_file_format_cache()
191             {
192 0     0 1 0 while (my ($fname, $cache_info) = each %file_cache) {
193 0         0 while (my($format, $lines) = each %{$cache_info->{lines_href}}) {
  0         0  
194 0 0       0 next if 'plain' eq $format;
195 0         0 my $ref = $file_cache{$fname};
196 0         0 $ref->{lines_href}->{$format} = undef;
197             }
198             }
199             }
200              
201             =pod
202              
203             =head2 clear_script_cache
204              
205             B<clear_script_cache()>
206              
207             Clear the script cache entirely.
208              
209             =cut
210              
211             sub clear_script_cache() {
212 0     0 1 0 %script_cache = {};
213             }
214              
215             =pod
216              
217             =head2 cached_files
218              
219             B<cached_files()> => I<list of files>
220              
221             Return an array of cached file names
222              
223             =cut
224              
225             sub cached_files() {
226 0     0 1 0 keys %file_cache;
227             }
228              
229             =pod
230              
231             =head2 checkcache
232              
233             B<checkcache()> => I<list-of-filenames>
234              
235             B<checkcache(I<$filename> [, $opts])> => I<list-of-filenames>
236              
237             Discard cache entries that are out of date. If I<$filename>is I<undef>,
238             all entries in the file cache are checked.
239              
240             If we did not previously have I<stat()> information about a file, it
241             will be added. Return a list of invalidated filenames. I<undef> is
242             returned if a filename was given but not found cached.
243              
244             =cut
245              
246             sub checkcache(;$$)
247             {
248 0     0 1 0 my ($filename, $opts) = @_;
249 0 0       0 $opts = {} unless defined $opts;
250              
251 0         0 my $use_perl_d_file = $opts->{use_perl_d_file};
252              
253 0         0 my @filenames;
254 0 0       0 if (defined $filename) {
    0          
255 0         0 @filenames = keys %file_cache;
256             } elsif (exists $file_cache{$filename}) {
257 0         0 @filenames = ($filename);
258             } else {
259 0         0 return undef;
260             }
261              
262 0         0 my @result = ();
263 0         0 for my $filename (@filenames) {
264 0 0       0 next unless exists $file_cache{$filename};
265 0         0 my $path = $file_cache{$filename}{path};
266 0 0       0 if (-f $path) {
267 0         0 my $cache_info = $file_cache{$filename}{stat};
268 0         0 my $stat = File::stat::stat($path);
269 0 0       0 if ($cache_info) {
270 0 0 0     0 if ($stat &&
      0        
271             ($cache_info->{size} != $stat->size or
272             $cache_info->{mtime} != $stat->mtime)) {
273 0         0 push @result, $filename;
274 0         0 update_cache($filename, $opts);
275             }
276             }
277             } else {
278 0         0 push @result, $filename;
279 0         0 update_cache($filename, $opts);
280             }
281             }
282 0         0 return @result;
283             }
284              
285             =pod
286              
287             =head2 cache_script
288              
289             B<cache_script(I<$script> [, I<$opts>]) > => I<script>
290              
291             Cache psuedo eval-string for a pseudo eval-string if it's not already cached.
292              
293             =cut
294              
295             sub cache_script($;$)
296             {
297 0     0 1 0 my ($script, $opts) = @_;
298 0 0       0 $opts = {} unless defined $opts;
299 0 0       0 if (exists $script_cache{$script}) {
300 0         0 return 1;
301             } else {
302 0         0 return update_script_cache($script, $opts);
303             }
304             }
305              
306             =pod
307              
308             =head2 cache
309              
310             B<cache(I<$file_or_script> [, I<$reload_on_change>]) > => I<filename>
311              
312             Cache file name or script object if it's not already cached.
313              
314             Return the expanded filename for it in the cache if a filename,
315             or the script, or I<undef> if we can't find the file.
316              
317             =cut
318              
319             sub cache($;$)
320             {
321 0     0 1 0 my ($file_or_script, $reload_on_change) = @_;
322 0         0 cache_file($file_or_script, $reload_on_change)
323             }
324              
325             =pod
326              
327             =head2 cache_file
328              
329             B<cache(I<$file_or_script> [, I<$reload_on_change>, $opts]) > => I<filename>
330              
331             Cache I<$filename_or_script> if it's not already cached.
332              
333             Return the expanded filename for I<$file_or_script> if it is in the
334             cache or I<undef> if we can't find it.
335              
336             =cut
337              
338             sub cache_file($;$$)
339             {
340 1     1 1 3 my ($filename, $reload_on_change, $opts) = @_;
341 1 50       4 $opts = {} unless defined $opts;
342 1 50       4 if (exists $file_cache{$filename}) {
343 1 50       4 checkcache($filename) if $reload_on_change;
344             } else {
345 0 0       0 $opts->{use_perl_d_file} = 1 unless defined $opts->{use_perl_d_file};
346 0         0 update_cache($filename, $opts);
347             }
348 1 50       3 if (exists $file_cache{$filename}) {
349 1         3 $file_cache{$filename}{path};
350             } else {
351 0         0 return undef;
352             }
353             }
354              
355             =pod
356              
357             =head2 is_cached
358              
359             B<cache(I<$file_or_script>)> => I<boolean>
360              
361             Return I<true> if I<$file_or_script> is cached.
362              
363             =cut
364              
365             sub is_cached($)
366             {
367 0     0 1 0 my $file_or_script = shift;
368 0 0       0 return undef unless defined $file_or_script;
369 0         0 exists $file_cache{map_file($file_or_script)};
370             }
371              
372             sub is_cached_script($)
373             {
374 0     0 0 0 my $filename = shift;
375 0         0 my $name = map_file($filename);
376 0         0 scalar @{"_<$name"};
  0         0  
377             }
378              
379             sub is_empty($)
380             {
381 0     0 0 0 my $filename = shift;
382 0         0 $filename=map_file($filename);
383 0         0 my $ref = $file_cache{$filename};
384 0         0 $ref->{lines_href}{plain};
385             }
386              
387             sub file_list()
388             {
389 0     0 0 0 my @list = (cached_files(), keys(%file2file_remap));
390 0         0 my %seen;
391 0         0 my @uniq = grep { ! $seen{$_} ++ } @list;
  0         0  
392 0         0 sort(@uniq);
393             }
394              
395             =pod
396              
397             =head2 getline
398              
399             B<getline($file_or_script, $line_number [, $opts])> => I<string>
400              
401             Get line I<$line_number> from I<$file_script>. Return I<undef> if
402             there was a problem. If a file named I<$file_or_script> is not found, the
403             function will look for it in the I<@INC> array.
404              
405             =cut
406              
407             sub getline($$;$)
408             {
409 4     4 1 840 my ($file_or_script, $line_number, $opts) = @_;
410 4 100       13 $opts = {} unless defined $opts;
411 4         10 my $reload_on_change = $opts->{reload_on_change};
412 4         11 my $filename = map_file($file_or_script);
413 4         18 ($filename, $line_number) = map_file_line($filename, $line_number);
414 4         16 my $lines = getlines($filename, $opts);
415             # Adjust for 0-origin arrays vs 1 origin line numbers
416 4 50       14 return undef unless $lines;
417 4         8 my $max_index = scalar(@$lines) - 1;
418 4         9 my $index = $line_number - 1;
419 4 50 33     38 if (defined $lines && @$lines && $index >= 0 && $index <= $max_index) {
      33        
      33        
420 4   50     26 my $max_continue = $opts->{maxlines} || 1;
421 4         8 my $line = $lines->[$index];
422 4 50       12 return undef unless defined $line;
423 4 50       10 if ($max_continue > 1) {
424 0         0 my $plain_lines = getlines($filename, {output => 'plain'});
425             # FIXME: should cache results
426 0 0       0 my $sep = ($plain_lines eq $lines) ? '' : "\n";
427 0         0 my $plain_line = $plain_lines->[$index];
428 0   0     0 while (--$max_continue && !eval_ok($plain_line)) {
429 0         0 my $next_line = $lines->[++$index];
430 0 0       0 last unless $next_line;
431 0         0 $line .= ($sep . $next_line);
432 0         0 $plain_line .= $plain_lines->[$index];
433 0 0       0 last if $file_cache{$filename}{trace_nums}{$index+1};
434             }
435             }
436 4 50       20 chomp $line if defined $line;
437 4         17 return $line;
438             } else {
439 0         0 return undef;
440             }
441             }
442              
443             =pod
444              
445             =head2 getlines
446              
447             B<getlines($filename, [$opts])> => I<string>
448              
449             Read lines of I<$filename> and cache the results. However
450             if I<$filename> was previously cached use the results from the
451             cache. Return I<undef> if we can't get lines.
452              
453             B<Examples:>
454              
455             $lines = getline('/tmp/myfile.pl')
456             # Same as above
457             push @INC, '/tmp';
458             $lines = getlines('myfile.pl')
459              
460             =cut
461              
462             sub getlines($;$);
463             sub getlines($;$)
464             {
465 7     7 1 702 my ($filename, $opts) = @_;
466 7 100       25 $opts = {use_perl_d_file => 1} unless defined $opts;
467             my ($reload_on_change, $use_perl_d_file) =
468 7         20 ($opts->{reload_on_change}, $opts->{use_perl_d_file});
469 7 50       18 checkcache($filename) if $reload_on_change;
470 7   100     31 my $format = $opts->{output} || 'plain';
471 7 100       24 if (exists $file_cache{$filename}) {
    50          
472 5         13 my $lines_href = $file_cache{$filename}{lines_href};
473 5         11 my $lines_aref = $lines_href->{$format};
474 5 50       26 return $lines_href->{plain} if $format eq 'plain';
475 0 0 0     0 if ($opts->{output} && !defined $lines_aref) {
476 0         0 my @formatted_lines = ();
477 0         0 $lines_aref = $lines_href->{plain};
478 0         0 for my $line (@$lines_aref) {
479 0         0 push @formatted_lines, highlight_string($line);
480             ## print $formatted_text;
481             }
482 0         0 $lines_href->{$format} = \@formatted_lines;
483 0         0 return \@formatted_lines;
484             } else {
485 0         0 return $lines_aref;
486             }
487             } elsif (exists $script_cache{$filename}) {
488             ### FIXME: combine with above...
489             ### print "+++IS IN SCRIPT CACHE\n";
490 0         0 my $lines_href = $script_cache{$filename}{lines_href};
491 0         0 my $lines_aref = $lines_href->{$format};
492 0 0 0     0 if ($opts->{output} && !defined $lines_aref) {
493 0         0 my @formatted_lines = ();
494 0         0 my $lines_aref = $lines_href->{plain};
495 0         0 for my $line (@$lines_aref) {
496 0         0 push @formatted_lines, highlight_string($line);
497             ## print $formatted_text;
498             }
499 0         0 $lines_href->{$format} = \@formatted_lines;
500 0         0 return \@formatted_lines;
501             } else {
502 0         0 return $lines_aref;
503             }
504             } else {
505 2         5 $opts->{use_perl_d_file} = 1;
506 2         10 update_cache($filename, $opts);
507 2 50       11 if (exists $file_cache{$filename}) {
508 2         17 return getlines($filename, $opts);
509             } else {
510 0         0 return undef;
511             }
512             }
513             }
514              
515             =head2 highlight_string
516              
517             B<highlight_string($string)> => I<marked-up-string>
518              
519             Add syntax-formatting characters via
520             L<Syntax::Highlight::Perl::Improved> to I<marked-up-string> according to table
521             given in L<Devel::Trepan::DB::Colors>.
522              
523             =cut
524              
525             sub highlight_string($)
526             {
527 0     0 1 0 my ($string) = shift;
528 0         0 $string = $perl_formatter->format_string($string);
529 0         0 chomp $string;
530 0         0 $string;
531             }
532              
533             =head2 path
534              
535             B<path($filename)> => I<string>
536              
537             Return full filename path for I<$filename>.
538              
539             =cut
540              
541             sub path($)
542             {
543 0     0 1 0 my $filename = shift;
544 0         0 $filename = map_file($filename);
545 0 0       0 return undef unless exists $file_cache{$filename};
546 0         0 $file_cache{$filename}->path();
547             }
548              
549             =pod
550              
551             =head2 remap_file
552              
553             B<remap_file($from_file, $to_file)> => $to_file
554              
555             Set to make any lookups retriving lines from of I<$from_file> refer to
556             I<$to_file>.
557              
558             B<Example>:
559              
560             Running:
561              
562             use Devel::Trepan::DB::LineCache;
563             remap_file('another_name', __FILE__);
564             print getline('another_name', __LINE__), "\n";
565              
566             gives:
567              
568             print getline('another_name', __LINE__), "\n";
569              
570             =cut
571              
572             sub remap_file($$)
573             {
574 1     1 1 562 my ($from_file, $to_file) = @_;
575 1         3 $file2file_remap{$from_file} = $to_file;
576 1         4 cache_file($to_file);
577             }
578              
579             sub remap_string_to_tempfile($)
580             {
581 0     0 0 0 my $string = shift;
582 0         0 my ($fh, $tempfile) = tempfile('XXXX', SUFFIX=>'.pl',
583             TMPDIR => 1);
584 0         0 push @tempfiles, $tempfile;
585 0         0 $fh->autoflush(1);
586 0         0 print $fh $string;
587 0         0 $fh->close();
588 0         0 return $tempfile;
589             }
590              
591             =pod
592              
593             =head2 remap_dbline_to_file
594              
595             I<remap_dbline_to_file()>
596              
597             When we run C<trepan.pl -e> ... or C<perl -d:Trepan -e ...> we have
598             data in internal an "line" array I<@DB::dbline> but no external
599             file. Here, we will create a temporary file and store the data in
600             that.
601              
602             =cut
603              
604             sub remap_dbline_to_file()
605             {
606 14     14   150 no strict;
  14         65  
  14         8668  
607 0     0 1 0 my @lines = @DB::dbline;
608 0 0       0 shift @lines if $lines[0] eq "use Devel::Trepan;\n";
609 0         0 my $string = join('', @lines);
610 0         0 my $tempfile = remap_string_to_tempfile $string;
611 0         0 remap_file('-e', $tempfile);
612             }
613              
614             sub remap_file_lines($$$$)
615             {
616 0     0 0 0 my ($from_file, $to_file, $range_ref, $start) = @_;
617 0         0 my @range = @$range_ref;
618 0 0       0 $to_file = $from_file unless $to_file;
619 0         0 my $ary_ref = ${$file2file_remap_lines{$to_file}};
  0         0  
620 0 0       0 $ary_ref = [] unless defined $ary_ref;
621             # FIXME: need to check for overwriting ranges: whether
622             # they intersect or one encompasses another.
623 0         0 push @$ary_ref, [$from_file, @range, $start];
624             }
625              
626             =pod
627              
628             =head2 sha1
629              
630             I<sha1($filename)> => I<string>
631              
632             Return SHA1 for I<$filename>.
633              
634             B<Example>:
635              
636             In file C</tmp/foo.pl>:
637              
638             use Devel::Trepan::DB::LineCache;
639             cache_file(__FILE__);
640             printf "SHA1 of %s is:\n%s\n", __FILE__, Devel::Trepan::DB::LineCache::sha1(__FILE__);
641              
642             gives:
643              
644             SHA1 of /tmp/foo.pl is:
645             719b1aa8d559e64bd0de70b325beff79beac32f5
646              
647             =cut
648              
649             sub Devel::Trepan::DB::LineCache::sha1($)
650             {
651 1     1 1 9 my $filename = shift;
652 1         5 $filename = map_file($filename);
653 1 50       7 return undef unless exists $file_cache{$filename};
654 1         4 my $sha1 = $file_cache{$filename}{sha1};
655 1 50       5 return $sha1->hexdigest if exists $file_cache{$filename}{sha1};
656 1         12 $sha1 = Digest::SHA->new('sha1');
657 1         33 my $line_ary = $file_cache{$filename}{lines_href}{plain};
658 1         4 for my $line (@$line_ary) {
659 107 50       236 next unless defined $line;
660 107         268 $sha1->add($line);
661             }
662 1         4 $file_cache{$filename}{sha1} = $sha1;
663 1         7 $sha1->hexdigest;
664             }
665              
666             =pod
667              
668             =head2 size
669              
670             I<size($filename_or_script)> => I<string>
671              
672             Return the number of lines in I<$filename_or_script>.
673              
674             B<Example>:
675              
676             In file C</tmp/foo.pl>:
677              
678             use :Devel::Trepan::DB::LineCache;
679             cache_file(__FILE__);
680             printf "%s has %d lines\n", __FILE__, Devel::Trepan::DB::LineCache::size(__FILE__);
681              
682             gives:
683              
684             /tmp/foo.pl has 3 lines
685              
686             =cut
687              
688             sub size($)
689             {
690 0     0 1 0 my $file_or_script = shift;
691 0         0 $file_or_script = map_file($file_or_script);
692 0         0 cache($file_or_script);
693 0 0       0 return undef unless exists $file_cache{$file_or_script};
694 0         0 my $lines_href = $file_cache{$file_or_script}{lines_href};
695 0 0       0 return undef unless defined $lines_href;
696 0         0 scalar @{$lines_href->{plain}};
  0         0  
697             }
698              
699             =pod
700              
701             =head2 stat
702              
703             B<stat(I<$filename>)> => I<stat-info>
704              
705             Return file I<stat()> info in the cache for I<$filename>.
706              
707             B<Example>:
708              
709             In file C</tmp/foo.pl>:
710              
711             use Devel::Trepan::DB::LineCache;
712             cache_file(__FILE__);
713             printf("stat() info for %s is:
714             dev ino mode nlink uid gid rdev size atime ctime ...
715             %4d %8d %7o %3d %4d %4d %4d %4d %d %d",
716             __FILE__,
717             @{Devel::Trepan::DB::LineCache::stat(__FILE__)});
718              
719             gives:
720              
721             stat() info for /tmp/foo.pl is:
722             dev ino mode nlink uid gid rdev size atime ctime ...
723             2056 5242974 100664 1 1000 1000 0 266 1347890102 1347890101
724              
725             =cut
726              
727             sub Devel::Trepan::DB::LineCache::stat($)
728             {
729 0     0   0 my $filename = shift;
730 0 0       0 return undef unless exists $file_cache{$filename};
731 0         0 $file_cache{$filename}{stat};
732             }
733              
734             =pod
735              
736             =head2 trace_line_numbers
737              
738             B<trace_line_numbers($filename [, $reload_on_change])> => I<list-of-numbers>
739              
740             Return an array of line numbers in (control opcodes) COP in
741             $I<filename>. These line numbers are the places where a breakpoint
742             might be set in a debugger.
743              
744             We get this information from the Perl run-time, so that should have
745             been set up for this to take effect. See L<B::CodeLines> for a way to
746             get this information, basically by running an Perl invocation that has
747             this set up.
748              
749             =cut
750              
751             sub trace_line_numbers($;$)
752             {
753 1     1 1 16 my ($filename, $reload_on_change) = @_;
754 1         7 my $fullname = update_cache($filename, $reload_on_change);
755 1 50       10 return undef unless $fullname;
756 1         6 return sort {$a <=> $b} keys %{$file_cache{$filename}{trace_nums}};
  11         43  
  1         19  
757             }
758              
759             =pod
760              
761             =head2 is_trace_line
762              
763             B<is_trace_line($filename, $line_num [,$reload_on_change])> => I<boolean>
764              
765             Return I<true> if I<$line_num> is a trace line number of I<$filename>.
766              
767             See the comment in L<trace_line_numbers> regarding run-time setup that
768             needs to take place for this to work.
769              
770             =cut
771              
772             sub is_trace_line($$;$)
773             {
774 0     0 1 0 my ($filename, $line_num, $reload_on_change) = @_;
775 0         0 my $fullname = cache($filename, $reload_on_change);
776 0 0       0 return undef unless $fullname;
777 0         0 return !!$file_cache{$filename}{trace_nums}{$line_num};
778             }
779              
780             =pod
781              
782             =head2 map_file
783              
784             B<map_file($filename)> => string
785              
786             A previous invocation of I<remap_file()> could have mapped
787             I<$filename> into something else. If that is the case we return the
788             name that I<$filename> was mapped into. Otherwise we return I<$filename>
789              
790             =cut
791              
792             sub map_file($)
793             {
794 6     6 1 13 my $filename = shift;
795 6 50       16 return undef unless defined($filename);
796 6 100       20 if ($file2file_remap{$filename}) {
    50          
797 1         3 $file2file_remap{$filename};
798             } elsif ($script2file{$filename}) {
799 0         0 $script2file{$filename};
800             } else {
801 5         15 $filename
802             }
803             }
804              
805             =pod
806              
807             =head2 map_script
808              
809             B<map_script($script, $string)> => string
810              
811             Note that a previous invocation of I<remap_file()> could have mapped I<$script>
812             (a pseudo-file name that I<eval()> uses) into something else.
813              
814             Return the temporary file name that I<$script> was mapped to.
815              
816             =cut
817              
818 14     14   12473 use File::Temp qw(tempfile);
  14         223769  
  14         7894  
819             sub map_script($$;$)
820             {
821 1     1 1 6 my ($script, $string, $opts) = @_;
822 1 50       7 if (exists $script2file{$script}) {
823 0         0 return $script2file{$script};
824             }
825              
826 1         8 my ($fh, $tempfile) = tempfile('XXXX', SUFFIX=>'.pl',
827             TMPDIR => 1);
828 1 50       696 return undef unless defined($string);
829 1         10 print $fh $string;
830 1         13 $fh->close();
831 1   50     78 $opts ||= {};
832 1         4 $opts->{use_perl_d_file} = 0;
833 1         6 update_cache($tempfile, $opts);
834 1         4 $script2file{$script} = $tempfile;
835              
836 1         7 return $tempfile;
837             }
838              
839             sub map_file_line($$)
840             {
841 4     4 0 14 my ($file, $line) = @_;
842 4 50       14 if (exists $file2file_remap_lines{$file}) {
843 0         0 my $triplet_ref = $file2file_remap_lines{$file};
844 0         0 for my $triplet (@$triplet_ref) {
845 0         0 my ($from_file, $range_ref, $start) = @$triplet;
846 0         0 my @range = @$range_ref;
847 0 0 0     0 if ( $range[0] >= $line && $range[-1] <= $line) {
848 0   0     0 my $from_file = $from_file || $file;
849 0         0 return [$from_file, $start+$line-$range[0]];
850             }
851             }
852             }
853 4         11 return ($file, $line);
854             }
855              
856             =pod
857              
858             =head2 filename_is_eval
859              
860             B<filename_is_eval($filename)> => I<boolean>
861              
862             Return I<true> if $filename matches one of the pseudo-filename strings
863             that get created for by I<eval()>.
864              
865             =cut
866              
867             sub filename_is_eval($)
868             {
869 10     10 1 1528 my $filename = shift;
870 10 50       32 return 0 unless defined $filename;
871             return !!
872 10   66     115 ($filename =~ /^\(eval \d+\)|-e$/
873             # SelfLoader does this:
874             || $filename =~ /^sub \S+::\S+/
875             );
876             }
877              
878             =pod
879              
880             =head2 update_script_cache
881              
882             B<update_script_cache($script, $opts)> => I<boolean>
883              
884             Update a cache entry for an pseudo eval-string file name. If something
885             is wrong, return I<undef>. Return I<true> if the cache was updated and
886             I<false> if not.
887              
888             =cut
889              
890             sub update_script_cache($$)
891             {
892 1     1 1 190 my ($script, $opts) = @_;
893 1 50       4 return 0 unless filename_is_eval($script);
894 1         3 my $string = $opts->{string};
895 1         3 my $lines_href = {};
896 1 50       3 if (defined($string)) {
897 1         4 my @lines = split(/\n/, $string);
898 1         4 $lines_href->{plain} = \@lines;
899             } else {
900 0 0       0 if ($script eq $DB::filename) {
901             ## SelfLoader evals
902 0 0 0     0 if (!@DB::line && $script =~/^sub (\S+)/) {
903 0         0 my $func = $1;
904 0         0 my $string = $Devel::Trepan::SelfLoader::Cache{$func};
905 0 0       0 return 0 unless $string;
906 0         0 $string =~ s/^\n#line 1.+\n//;
907 0         0 @DB::line = split(/\n/, $string);
908             }
909              
910             # Should be the same as the else case,
911             # but just in case...
912 0         0 $lines_href->{plain} = \@DB::line;
913 0         0 $string = join("\n", @DB::line);
914             } else {
915 14     14   157 no strict;
  14         42  
  14         7336  
916 0         0 $lines_href->{plain} = \@{"_<$script"};
  0         0  
917 0         0 $string = join("\n", @{"_<$script"});
  0         0  
918             }
919 0 0       0 return 0 unless length($string);
920             }
921             $lines_href->{$opts->{output}} = highlight_string($string) if
922 1 50 33     4 $opts->{output} && $opts->{output} ne 'plain';
923              
924 1         3 my $entry = {
925             lines_href => $lines_href,
926             };
927 1         16 $script_cache{$script} = $entry;
928 1         5 return 1;
929             }
930              
931             =head2
932              
933             B<dualvar_lines($file_or_string, $is_file, $mark_trace)> =>
934             # I<list of dual-var strings>
935              
936             # Routine to create dual numeric/string values for
937             # C<$file_or_string>. A list reference is returned. In string context
938             # it is the line with a trailing "\n". In a numeric context it is 0 or
939             # 1 if $mark_trace is set and B::CodeLines determines it is a trace
940             # line.
941             #
942             # Note: Perl implementations seem to put a COP address inside
943             # @DB::db_line when there are trace lines. I am not sure if this is
944             # specified as part of the API. We don't do that here but (and might
945             # even if it is not officially defined in the API.) Instead put value
946             # 1.
947             #
948             =cut
949              
950             # FIXME: $mark_trace may be something of a hack. Without it we can
951             # get into infinite regress in marking %INC modules.
952              
953             sub dualvar_lines($$;$$) {
954 1     1 0 4 my ($file_or_string, $dualvar_lines, $is_file, $mark_trace) = @_;
955 1         3 my @break_line = ();
956 1         4 local $INPUT_RECORD_SEPARATOR = "\n";
957              
958             # Setup for B::CodeLines and for reading file lines
959 1         5 my ($cmd, @text);
960 1         0 my $fh;
961 1         0 my $filename;
962 1 50       7 if ($is_file) {
963 1         2 $filename = $file_or_string;
964 1 50       40 return () unless open($fh, '<', $filename);
965 1         17 @text = readline $fh;
966 1         6 $cmd = "$^X -MO=CodeLines $filename";
967 1         6 close $fh;
968             } else {
969 0         0 @text = split("\n", $file_or_string);
970 0         0 $cmd = "$^X -MO=CodeLines,-exec -e '$file_or_string'";
971             }
972              
973             # Make text data be 1-origin rather than 0-origin.
974 1         4 unshift @text, undef;
975              
976             # Get trace lines from B::CodeLines
977 1 50 33     1743 if ($mark_trace and open($fh, '-|', "$cmd 2>/dev/null")) {
978 1         50867 while (my $line=<$fh>) {
979 6 50       57 next unless $line =~ /^\d+$/;
980 6         2164 $break_line[$line] = $line;
981             }
982             }
983             # Create dual variable array and hash.
984 1         16 my $trace_nums = {};
985 1         16 for (my $i = 1; $i < scalar @text; $i++) {
986 15 100       60 my $num = exists $break_line[$i] ? $mark_trace : 0;
987 15         63 $trace_nums->{$i} = -$i;
988 15         138 $dualvar_lines->[$i] = Scalar::Util::dualvar($num, $text[$i] . "\n");
989             }
990 1         14 $file_cache{$filename}{trace_nums} = $trace_nums;
991 1         76 return $dualvar_lines;
992             }
993              
994             =head2
995              
996             B<load_file(I<$filename>)> => I<list of strings>
997              
998             Somewhat simulates what Perl does in reading a file when debugging is
999             turned on. We return the file contents as a list of strings in
1000             I<_E<gt>$filename>. But also entry is a dual variable. In numeric
1001             context, each entry of the list is I<true> if that line is traceable
1002             or break-pointable (is the address of a COP instruction). In a
1003             non-numeric context, each entry is a string of the line contents
1004             including the trailing C<\n>.
1005              
1006             I<Note:> something similar exists in L<Enbugger> and it is useful when
1007             a debugger is called via Enbugger which turn on debugging late so source
1008             files might not have been read in.
1009              
1010             =cut
1011             sub load_file($;$) {
1012 1     1 0 2113 my ($filename, $eval_string) = @_;
1013              
1014             # The symbols by which we'll know ye.
1015 1         6 my $base_symname = "_<$filename";
1016 1         6 my $symname = "main::$base_symname";
1017              
1018 14     14   126 no strict 'refs';
  14         43  
  14         3952  
1019              
1020             # Note: dualvar_lines updates @$synmame;
1021 1 50       7 if (defined($eval_string)) {
1022 0         0 dualvar_lines($eval_string, \@$symname, 0, 1);
1023             } else {
1024 1         9 dualvar_lines($filename, \@$symname, 1, 1);
1025             }
1026              
1027 1   33     27 $$symname ||= $filename;
1028              
1029 1         14 return;
1030             }
1031              
1032             =head2 readlines
1033              
1034             B<readlines(I<$filename>)> => I<list of strings>
1035              
1036             Return a a list of strings for I<$filename>. If we can't read
1037             I<$filename> retun I<undef>. Each line will have a "\n" at the end.
1038              
1039             =cut
1040              
1041             sub readlines($)
1042             {
1043 10     10 1 26 my $path = shift;
1044 10 50       257 if (-r $path) {
1045 10         27 my $fh;
1046 10         338 open($fh, '<', $path);
1047 10         75 seek $fh, 0, 0;
1048 10         414 my @lines = <$fh>;
1049 10         110 close $fh;
1050 10         143 return @lines;
1051             } else {
1052 0         0 return undef;
1053             }
1054             }
1055              
1056             =head2 update_cache
1057              
1058             B<update_cache($filename, [, $opts]>
1059              
1060             Update a cache entry. If something's wrong, return I<undef>. Return
1061             the expanded file name if the cache was updated and I<false> if not. If
1062             $I<$opts-E<gt>{use_perl_d_file}> is I<true>, use that as the source for the
1063             lines of the file.
1064              
1065             =cut
1066              
1067             sub update_cache($;$)
1068             {
1069 5     5 1 37 my ($filename, $opts) = @_;
1070 5         14 my $read_file = 0;
1071 5 100       20 $opts = {} unless defined $opts;
1072 5         16 my $use_perl_d_file = $opts->{use_perl_d_file};
1073 5 100       21 $use_perl_d_file = 1 unless defined $use_perl_d_file;
1074              
1075 5 50       17 return undef unless $filename;
1076              
1077 5         51 delete $file_cache{$filename};
1078              
1079 5         24 my $is_eval = filename_is_eval($filename);
1080 5         17 my $path = $filename;
1081 5 50       18 unless ($is_eval) {
1082 5 50       238 $path = abs_path($filename) if -f $filename;
1083             }
1084 5         16 my $lines_href;
1085 5         14 my $trace_nums = {};
1086 5         15 my $stat;
1087 5 100       20 if ($use_perl_d_file) {
1088 4         19 my @list = ($filename);
1089 4 50       15 if ($is_eval) {
1090 0         0 cache_script($filename);
1091             ## FIXME: create a temporary file in script2file;
1092             }
1093 4 100       20 push @list, $file2file_remap{$path} if exists $file2file_remap{$path};
1094 4         20 for my $name (@list) {
1095 14     14   118 no strict; # Avoid string as ARRAY ref error message
  14         35  
  14         2301  
1096 5 50       15 if (scalar @{"main::_<$name"}) {
  5         43  
1097 5         35 $stat = File::stat::stat($path);
1098             }
1099 5         1104 my $raw_lines = \@{"main::_<$name"};
  5         29  
1100              
1101             # Perl sometimes doesn't seem to save all file data, such
1102             # as those intended for POD or possibly those after
1103             # __END__. But we want these, so we'll have to read the
1104             # file the old-fashioned way and check lines. Variable
1105             # $incomplete records if there was a mismatch.
1106 5         13 my $incomplete = 0;
1107 5 50       105 if (-r $path) {
1108 5         23 my @lines_check = readlines($path);
1109 5         70 my @lines = @$raw_lines;
1110 5         16 my $totally_empty = 1;
1111 5         51 for (my $i=1; $i<=$#lines; $i++) {
1112 27 100       98 if (defined $raw_lines->[$i]) {
1113 5         13 $totally_empty = 0;
1114 5         14 last;
1115             }
1116             }
1117 5 50       36 if ($totally_empty) {
1118 0         0 load_file($filename);
1119 0         0 $trace_nums = $file_cache{$filename}{trace_nums};
1120             } else {
1121 5         26 for (my $i=1; $i<=$#lines; $i++) {
1122 181 100       358 if (defined $raw_lines->[$i]) {
1123 14     14   108 no warnings;
  14         37  
  14         1199  
1124 159 100       380 $trace_nums->{$i} = (-$raw_lines->[$i]) if
1125             (+$raw_lines->[$i]) != 0;
1126 159 50       519 $incomplete = 1 if $raw_lines->[$i] ne $lines[$i];
1127             } else {
1128 22         57 $raw_lines->[$i] = $lines_check[$i-1]
1129             }
1130             }
1131             }
1132             }
1133 14     14   93 use strict;
  14         40  
  14         604  
1134 5         17 $lines_href = {};
1135 5         25 $lines_href->{plain} = $raw_lines;
1136 5 50 66     31 if ($opts->{output} && $opts->{output} ne 'plain' && defined($raw_lines)) {
  0   33     0  
1137             # Some lines in $raw_lines may be undefined
1138 14     14   76 no strict; no warnings;
  14     14   47  
  14         298  
  14         76  
  14         34  
  14         1169  
1139 0         0 local $WARNING=0;
1140 0         0 my $highlight_lines = highlight_string(join('', @$raw_lines));
1141 0         0 my @highlight_lines = split(/\n/, $highlight_lines);
1142 0         0 $lines_href->{$opts->{output}} = \@highlight_lines;
1143 14     14   150 use strict; use warnings;
  14     14   46  
  14         308  
  14         72  
  14         35  
  14         3334  
1144             }
1145 5         19 $read_file = 1;
1146             }
1147             }
1148              
1149             # File based reading is done here.
1150 5 50       92 if (-f $path ) {
    0          
1151 5 100       30 $stat = File::stat::stat($path) unless defined $stat;
1152             } elsif (!$read_file) {
1153 0 0       0 if (basename($filename) eq $filename) {
1154             # try looking through the search path.
1155 0         0 for my $dirname (@INC) {
1156 0         0 $path = File::Spec->catfile($dirname, $filename);
1157 0 0       0 if ( -f $path) {
1158 0         0 $stat = File::stat::stat($path);
1159 0         0 last;
1160             }
1161             }
1162             }
1163 0 0       0 return 0 unless defined $stat;
1164             }
1165 5 50       254 if ( -r $path ) {
1166 5         23 my @lines = readlines($path);
1167 5         33 $lines_href = {plain => \@lines};
1168 5 50 66     41 if ($opts->{output} && $opts->{output} ne 'plain') {
1169 0         0 my $highlight_lines = highlight_string(join('', @lines));
1170 0         0 my @highlight_lines = split(/\n/, $highlight_lines);
1171 0         0 $lines_href->{$opts->{output}} = \@highlight_lines;
1172             }
1173             }
1174 5         42 my $entry = {
1175             stat => $stat,
1176             lines_href => $lines_href,
1177             path => $path,
1178             incomplete => 0,
1179             trace_nums => $trace_nums,
1180             };
1181 5         18 $file_cache{$filename} = $entry;
1182 14     14   111 no warnings;
  14         45  
  14         1871  
1183 5         20 $file2file_remap{$path} = $filename;
1184 5         20 return $path;
1185             }
1186              
1187             # example usage
1188             unless (caller) {
1189             BEGIN {
1190 14     14   98 use English qw( -no_match_vars );
  14         47  
  14         156  
1191 14     14   5382 $PERLDB |= 0x400;
1192             }; # Turn on saving @{_<$filename};
1193             my $file=__FILE__;
1194             my $fullfile = abs_path($file);
1195 14     14   96 no strict;
  14         36  
  14         632  
1196             print scalar(@{"main::_<$file"}), "\n";
1197 14     14   80 use strict;
  14         38  
  14         9966  
1198              
1199             my $script_name = '(eval 234)';
1200             update_script_cache($script_name, {string => "now\nis\nthe\ntime"});
1201             print join(', ', keys %script_cache), "\n";
1202             my $lines = $script_cache{$script_name}{lines_href}{plain};
1203             print join("\n", @{$lines}), "\n";
1204             $lines = getlines($script_name);
1205             printf "%s has %d lines\n", $script_name, scalar @$lines;
1206             printf("Line 1 of $script_name is:\n%s\n",
1207             getline($script_name, 1));
1208             my $max_line = size($script_name);
1209             printf("%s has %d lines via size\n",
1210             $script_name, scalar @$lines);
1211             do __FILE__;
1212             my @line_nums = trace_line_numbers(__FILE__);
1213              
1214             ### FIXME: add more of this stuff into unit test.
1215             printf("Breakpoints for: %s:\n%s\n",
1216             __FILE__, join(', ', @line_nums[0..30]));
1217             $lines = getlines(__FILE__);
1218             printf "%s has %d lines\n", __FILE__, scalar @$lines;
1219             my $full_file = abs_path(__FILE__);
1220             $lines = getlines(__FILE__);
1221             printf "%s still has %d lines\n", __FILE__, scalar @$lines;
1222             $lines = getlines(__FILE__);
1223             printf "%s also has %d lines\n", $full_file, scalar @$lines;
1224             my $line_number = __LINE__;
1225             my $line = getline(__FILE__, $line_number);
1226             printf "The %d line is:\n%s\n", $line_number, $line ;
1227             remap_file('another_name', __FILE__);
1228             print getline('another_name', __LINE__), "\n";
1229             printf "Files cached: %s\n", join(', ', cached_files);
1230             update_cache(__FILE__);
1231             printf "I said %s has %d lines!\n", __FILE__, size(__FILE__);
1232             printf "SHA1 of %s is:\n%s\n", __FILE__, sha1(__FILE__);
1233              
1234             my $stat = stat(__FILE__);
1235             printf("stat info size: %d, ctime %s, mode %o\n",
1236             $stat->size, $stat->ctime, $stat->mode);
1237              
1238             my $lines_aref = getlines(__FILE__, {output=>'term'});
1239             print join("\n", @$lines_aref[0..5,50..55]), "\n" if defined $lines_aref;
1240             $DB::filename = '(eval 4)';
1241             my $filename = map_script($DB::filename, "\$x=1;\n\$y=2;\n\$z=3;\n");
1242             print "mapped eval is $filename\n";
1243             printf("%s is a trace line? %d\n", __FILE__,
1244             is_trace_line(__FILE__, __LINE__-1));
1245             printf("%s is a trace line? %d\n", __FILE__,
1246             is_trace_line(__FILE__, __LINE__));
1247             eval "printf \"filename_is_eval: %s, %d\n\", __FILE__,
1248             filename_is_eval(__FILE__);";
1249             printf("filename_is_eval: %s, %d\n", __FILE__, filename_is_eval(__FILE__));
1250             printf("filename_is_eval: %s, %d\n", '-e', filename_is_eval('-e'));
1251              
1252             #$DB::filename = 'bogus';
1253             #eval {
1254             # print '+++', is_cached_script(__FILE__), "\n";
1255             #};
1256              
1257             $lines_aref = getlines(__FILE__, {output=>'term'});
1258             # print("trace nums again: ", join(', ',
1259             # trace_line_numbers(__FILE__)),
1260             # "\n");
1261             $line = getline(__FILE__, __LINE__,
1262             {output=>'term',
1263             maxlines => 6});
1264             print '-' x 30, "\n";
1265             print "$line\n";
1266             $line = getline(__FILE__, __LINE__,
1267             {output=>'plain',
1268             maxlines => 5});
1269             print '-' x 30, "\n";
1270             print "$line\n";
1271             print '-' x 30, "\n";
1272              
1273             my $dirname = File::Basename::dirname(__FILE__);
1274             my $colors_file = File::Spec->catfile($dirname, 'Colors.pm');
1275             load_file($colors_file);
1276             @line_nums = trace_line_numbers($colors_file);
1277             print join(', ', @line_nums, "\n");
1278             }
1279              
1280             1;