File Coverage

lib/Devel/Trepan/DB/LineCache.pm
Criterion Covered Total %
statement 288 474 60.7
branch 73 196 37.2
condition 17 63 26.9
subroutine 52 71 73.2
pod 25 36 69.4
total 455 840 54.1


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   238635 use Digest::SHA;
  14         42059  
  14         689  
7 14     14   93 use Scalar::Util;
  14         28  
  14         451  
8              
9 14     14   2541 use version; $VERSION = '1.0.0';
  14         36508  
  14         92  
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   1356 no strict; no warnings;
  14     14   37  
  14         328  
  14         68  
  14         40  
  14         1500  
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   100 use rlib '../../..';
  14         37  
  14         112  
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.0";
83              
84 14     14   6672 use English qw( -no_match_vars );
  14         35  
  14         90  
85 14     14   4665 use vars qw(%file_cache %script_cache);
  14         29  
  14         634  
86              
87 14     14   74 use strict; use warnings;
  14     14   23  
  14         301  
  14         68  
  14         22  
  14         407  
88 14     14   68 no warnings 'once';
  14         44  
  14         492  
89 14     14   73 no warnings 'redefine';
  14         35  
  14         366  
90              
91 14     14   91 use Cwd 'abs_path';
  14         83  
  14         627  
92 14     14   99 use File::Basename;
  14         29  
  14         892  
93 14     14   98 use File::Spec;
  14         41  
  14         401  
94 14     14   7266 use File::stat;
  14         95310  
  14         59  
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   17169 use Devel::Trepan::DB::Colors;
  14         45  
  14         39485  
132             my $perl_formatter;
133              
134             sub color_setup {
135 14     14 0 108 $perl_formatter = Devel::Trepan::DB::Colors::setup(@_);
136             }
137             color_setup('lightbg');
138              
139             sub remove_temps()
140             {
141 14     14 0 92 for my $filename (values %script2file) {
142 1 50       77 unlink($filename) if -f $filename;
143             }
144 14         375 for my $filename (@tempfiles) {
145 0 0       0 unlink($filename) if -f $filename;
146             }
147             }
148              
149             END {
150 14     14   4970 $DB::ready = 0;
151 14         76 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 1460 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         54 %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 2 my ($filename, $reload_on_change, $opts) = @_;
341 1 50       4 $opts = {} unless defined $opts;
342 1 50       3 if (exists $file_cache{$filename}) {
343 1 50       3 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       2 if (exists $file_cache{$filename}) {
349 1         4 $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 1042 my ($file_or_script, $line_number, $opts) = @_;
410 4 100       14 $opts = {} unless defined $opts;
411 4         11 my $reload_on_change = $opts->{reload_on_change};
412 4         12 my $filename = map_file($file_or_script);
413 4         16 ($filename, $line_number) = map_file_line($filename, $line_number);
414 4         12 my $lines = getlines($filename, $opts);
415             # Adjust for 0-origin arrays vs 1 origin line numbers
416 4 50       11 return undef unless $lines;
417 4         11 my $max_index = scalar(@$lines) - 1;
418 4         9 my $index = $line_number - 1;
419 4 50 33     40 if (defined $lines && @$lines && $index >= 0 && $index <= $max_index) {
      33        
      33        
420 4   50     20 my $max_continue = $opts->{maxlines} || 1;
421 4         8 my $line = $lines->[$index];
422 4 50       10 return undef unless defined $line;
423 4 50       9 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       15 chomp $line if defined $line;
437 4         14 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 1012 my ($filename, $opts) = @_;
466 7 100       19 $opts = {use_perl_d_file => 1} unless defined $opts;
467             my ($reload_on_change, $use_perl_d_file) =
468 7         18 ($opts->{reload_on_change}, $opts->{use_perl_d_file});
469 7 50       16 checkcache($filename) if $reload_on_change;
470 7   100     36 my $format = $opts->{output} || 'plain';
471 7 100       21 if (exists $file_cache{$filename}) {
    50          
472 5         9 my $lines_href = $file_cache{$filename}{lines_href};
473 5         7 my $lines_aref = $lines_href->{$format};
474 5 50       23 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         8 $opts->{use_perl_d_file} = 1;
506 2         9 update_cache($filename, $opts);
507 2 50       7 if (exists $file_cache{$filename}) {
508 2         18 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 624 my ($from_file, $to_file) = @_;
575 1         3 $file2file_remap{$from_file} = $to_file;
576 1         5 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   194 no strict;
  14         32  
  14         10791  
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 7 my $filename = shift;
652 1         2 $filename = map_file($filename);
653 1 50       5 return undef unless exists $file_cache{$filename};
654 1         3 my $sha1 = $file_cache{$filename}{sha1};
655 1 50       3 return $sha1->hexdigest if exists $file_cache{$filename}{sha1};
656 1         8 $sha1 = Digest::SHA->new('sha1');
657 1         22 my $line_ary = $file_cache{$filename}{lines_href}{plain};
658 1         3 for my $line (@$line_ary) {
659 107 50       136 next unless defined $line;
660 107         172 $sha1->add($line);
661             }
662 1         2 $file_cache{$filename}{sha1} = $sha1;
663 1         6 $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 19 my ($filename, $reload_on_change) = @_;
754 1         4 my $fullname = update_cache($filename, $reload_on_change);
755 1 50       5 return undef unless $fullname;
756 1         2 return sort {$a <=> $b} keys %{$file_cache{$filename}{trace_nums}};
  11         35  
  1         22  
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 9 my $filename = shift;
795 6 50       14 return undef unless defined($filename);
796 6 100       17 if ($file2file_remap{$filename}) {
    50          
797 1         2 $file2file_remap{$filename};
798             } elsif ($script2file{$filename}) {
799 0         0 $script2file{$filename};
800             } else {
801 5 50       85 return $filename if -r $filename;
802 0         0 my $testpath = glob $filename;
803 0 0 0     0 return $testpath if defined $testpath && -r $testpath;
804              
805 0         0 return $filename;
806             }
807             }
808              
809             =pod
810              
811             =head2 map_script
812              
813             B<map_script($script, $string)> => string
814              
815             Note that a previous invocation of I<remap_file()> could have mapped I<$script>
816             (a pseudo-file name that I<eval()> uses) into something else.
817              
818             Return the temporary file name that I<$script> was mapped to.
819              
820             =cut
821              
822 14     14   11205 use File::Temp qw(tempfile);
  14         228659  
  14         7707  
823             sub map_script($$;$)
824             {
825 1     1 1 4 my ($script, $string, $opts) = @_;
826 1 50       5 if (exists $script2file{$script}) {
827 0         0 return $script2file{$script};
828             }
829              
830 1         7 my ($fh, $tempfile) = tempfile('XXXX', SUFFIX=>'.pl',
831             TMPDIR => 1);
832 1 50       522 return undef unless defined($string);
833 1         12 print $fh $string;
834 1         15 $fh->close();
835 1   50     51 $opts ||= {};
836 1         3 $opts->{use_perl_d_file} = 0;
837 1         5 update_cache($tempfile, $opts);
838 1         3 $script2file{$script} = $tempfile;
839              
840 1         5 return $tempfile;
841             }
842              
843             sub map_file_line($$)
844             {
845 4     4 0 9 my ($file, $line) = @_;
846 4 50       11 if (exists $file2file_remap_lines{$file}) {
847 0         0 my $triplet_ref = $file2file_remap_lines{$file};
848 0         0 for my $triplet (@$triplet_ref) {
849 0         0 my ($from_file, $range_ref, $start) = @$triplet;
850 0         0 my @range = @$range_ref;
851 0 0 0     0 if ( $range[0] >= $line && $range[-1] <= $line) {
852 0   0     0 my $from_file = $from_file || $file;
853 0         0 return [$from_file, $start+$line-$range[0]];
854             }
855             }
856             }
857 4         10 return ($file, $line);
858             }
859              
860             =pod
861              
862             =head2 filename_is_eval
863              
864             B<filename_is_eval($filename)> => I<boolean>
865              
866             Return I<true> if $filename matches one of the pseudo-filename strings
867             that get created for by I<eval()>.
868              
869             =cut
870              
871             sub filename_is_eval($)
872             {
873 10     10 1 2247 my $filename = shift;
874 10 50       26 return 0 unless defined $filename;
875             return !!
876 10   66     143 ($filename =~ /^\(eval \d+\)|-e$/
877             # SelfLoader does this:
878             || $filename =~ /^sub \S+::\S+/
879             );
880             }
881              
882             =pod
883              
884             =head2 update_script_cache
885              
886             B<update_script_cache($script, $opts)> => I<boolean>
887              
888             Update a cache entry for an pseudo eval-string file name. If something
889             is wrong, return I<undef>. Return I<true> if the cache was updated and
890             I<false> if not.
891              
892             =cut
893              
894             sub update_script_cache($$)
895             {
896 1     1 1 1103 my ($script, $opts) = @_;
897 1 50       4 return 0 unless filename_is_eval($script);
898 1         3 my $string = $opts->{string};
899 1         3 my $lines_href = {};
900 1 50       4 if (defined($string)) {
901 1         4 my @lines = split(/\n/, $string);
902 1         4 $lines_href->{plain} = \@lines;
903             } else {
904 0 0       0 if ($script eq $DB::filename) {
905             ## SelfLoader evals
906 0 0 0     0 if (!@DB::line && $script =~/^sub (\S+)/) {
907 0         0 my $func = $1;
908 0         0 my $string = $Devel::Trepan::SelfLoader::Cache{$func};
909 0 0       0 return 0 unless $string;
910 0         0 $string =~ s/^\n#line 1.+\n//;
911 0         0 @DB::line = split(/\n/, $string);
912             }
913              
914             # Should be the same as the else case,
915             # but just in case...
916 0         0 $lines_href->{plain} = \@DB::line;
917 0         0 $string = join("\n", @DB::line);
918             } else {
919 14     14   119 no strict;
  14         32  
  14         7332  
920 0         0 $lines_href->{plain} = \@{"_<$script"};
  0         0  
921 0         0 $string = join("\n", @{"_<$script"});
  0         0  
922             }
923 0 0       0 return 0 unless length($string);
924             }
925             $lines_href->{$opts->{output}} = highlight_string($string) if
926 1 50 33     7 $opts->{output} && $opts->{output} ne 'plain';
927              
928 1         3 my $entry = {
929             lines_href => $lines_href,
930             };
931 1         2 $script_cache{$script} = $entry;
932 1         3 return 1;
933             }
934              
935             =head2
936              
937             B<dualvar_lines($file_or_string, $is_file, $mark_trace)> =>
938             # I<list of dual-var strings>
939              
940             # Routine to create dual numeric/string values for
941             # C<$file_or_string>. A list reference is returned. In string context
942             # it is the line with a trailing "\n". In a numeric context it is 0 or
943             # 1 if $mark_trace is set and B::CodeLines determines it is a trace
944             # line.
945             #
946             # Note: Perl implementations seem to put a COP address inside
947             # @DB::db_line when there are trace lines. I am not sure if this is
948             # specified as part of the API. We don't do that here but (and might
949             # even if it is not officially defined in the API.) Instead put value
950             # 1.
951             #
952             =cut
953              
954             # FIXME: $mark_trace may be something of a hack. Without it we can
955             # get into infinite regress in marking %INC modules.
956              
957             sub dualvar_lines($$;$$) {
958 1     1 0 3 my ($file_or_string, $dualvar_lines, $is_file, $mark_trace) = @_;
959 1         2 my @break_line = ();
960 1         4 local $INPUT_RECORD_SEPARATOR = "\n";
961              
962             # Setup for B::CodeLines and for reading file lines
963 1         7 my ($cmd, @text);
964 1         0 my $fh;
965 1         0 my $filename;
966 1 50       4 if ($is_file) {
967 1         2 $filename = $file_or_string;
968 1 50       37 return () unless open($fh, '<', $filename);
969 1         20 @text = readline $fh;
970 1         6 $cmd = "$^X -MO=CodeLines $filename";
971 1         8 close $fh;
972             } else {
973 0         0 @text = split("\n", $file_or_string);
974 0         0 $cmd = "$^X -MO=CodeLines,-exec -e '$file_or_string'";
975             }
976              
977             # Make text data be 1-origin rather than 0-origin.
978 1         4 unshift @text, undef;
979              
980             # Get trace lines from B::CodeLines
981 1 50 33     16590 if ($mark_trace and open($fh, '-|', "$cmd 2>/dev/null")) {
982 1         224677 while (my $line=<$fh>) {
983 6 50       83 next unless $line =~ /^\d+$/;
984 6         56 $break_line[$line] = $line;
985             }
986             }
987             # Create dual variable array and hash.
988 1         13 my $trace_nums = {};
989 1         6 for (my $i = 1; $i < scalar @text; $i++) {
990 15 100       35 my $num = exists $break_line[$i] ? $mark_trace : 0;
991 15         60 $trace_nums->{$i} = -$i;
992 15         85 $dualvar_lines->[$i] = Scalar::Util::dualvar($num, $text[$i] . "\n");
993             }
994 1         23 $file_cache{$filename}{trace_nums} = $trace_nums;
995 1         96 return $dualvar_lines;
996             }
997              
998             =head2
999              
1000             B<load_file(I<$filename>)> => I<list of strings>
1001              
1002             Somewhat simulates what Perl does in reading a file when debugging is
1003             turned on. We return the file contents as a list of strings in
1004             I<_E<gt>$filename>. But also entry is a dual variable. In numeric
1005             context, each entry of the list is I<true> if that line is traceable
1006             or break-pointable (is the address of a COP instruction). In a
1007             non-numeric context, each entry is a string of the line contents
1008             including the trailing C<\n>.
1009              
1010             I<Note:> something similar exists in L<Enbugger> and it is useful when
1011             a debugger is called via Enbugger which turn on debugging late so source
1012             files might not have been read in.
1013              
1014             =cut
1015             sub load_file($;$) {
1016 1     1 0 2327 my ($filename, $eval_string) = @_;
1017              
1018             # The symbols by which we'll know ye.
1019 1         4 my $base_symname = "_<$filename";
1020 1         3 my $symname = "main::$base_symname";
1021              
1022 14     14   112 no strict 'refs';
  14         43  
  14         4365  
1023              
1024             # Note: dualvar_lines updates @$synmame;
1025 1 50       3 if (defined($eval_string)) {
1026 0         0 dualvar_lines($eval_string, \@$symname, 0, 1);
1027             } else {
1028 1         8 dualvar_lines($filename, \@$symname, 1, 1);
1029             }
1030              
1031 1   33     27 $$symname ||= $filename;
1032              
1033 1         24 return;
1034             }
1035              
1036             =head2 readlines
1037              
1038             B<readlines(I<$filename>)> => I<list of strings>
1039              
1040             Return a a list of strings for I<$filename>. If we can't read
1041             I<$filename> retun I<undef>. Each line will have a "\n" at the end.
1042              
1043             =cut
1044              
1045             sub readlines($)
1046             {
1047 10     10 1 20 my $path = shift;
1048 10 50       115 if (-r $path) {
1049 10         16 my $fh;
1050 10         301 open($fh, '<', $path);
1051 10         74 seek $fh, 0, 0;
1052 10         337 my @lines = <$fh>;
1053 10         119 close $fh;
1054 10         109 return @lines;
1055             } else {
1056 0         0 return undef;
1057             }
1058             }
1059              
1060             =head2 update_cache
1061              
1062             B<update_cache($filename, [, $opts]>
1063              
1064             Update a cache entry. If something's wrong, return I<undef>. Return
1065             the expanded file name if the cache was updated and I<false> if not. If
1066             $I<$opts-E<gt>{use_perl_d_file}> is I<true>, use that as the source for the
1067             lines of the file.
1068              
1069             =cut
1070              
1071             sub update_cache($;$)
1072             {
1073 5     5 1 46 my ($filename, $opts) = @_;
1074 5         22 my $read_file = 0;
1075 5 100       17 $opts = {} unless defined $opts;
1076 5         16 my $use_perl_d_file = $opts->{use_perl_d_file};
1077 5 100       19 $use_perl_d_file = 1 unless defined $use_perl_d_file;
1078              
1079 5 50       15 return undef unless $filename;
1080              
1081 5         53 delete $file_cache{$filename};
1082              
1083 5         24 my $is_eval = filename_is_eval($filename);
1084 5         17 my $path = $filename;
1085 5 50       30 unless ($is_eval) {
1086 5 50       195 $path = abs_path($filename) if -f $filename;
1087             }
1088 5         12 my $lines_href;
1089 5         39 my $trace_nums = {};
1090 5         31 my $stat;
1091 5 100       15 if ($use_perl_d_file) {
1092 4         10 my @list = ($filename);
1093 4 50       25 if ($is_eval) {
1094 0         0 cache_script($filename);
1095             ## FIXME: create a temporary file in script2file;
1096             }
1097 4 100       16 push @list, $file2file_remap{$path} if exists $file2file_remap{$path};
1098 4         19 for my $name (@list) {
1099 14     14   98 no strict; # Avoid string as ARRAY ref error message
  14         31  
  14         2401  
1100 5 50       9 if (scalar @{"main::_<$name"}) {
  5         29  
1101 5         40 $stat = File::stat::stat($path);
1102             }
1103 5         994 my $raw_lines = \@{"main::_<$name"};
  5         19  
1104              
1105             # Perl sometimes doesn't seem to save all file data, such
1106             # as those intended for POD or possibly those after
1107             # __END__. But we want these, so we'll have to read the
1108             # file the old-fashioned way and check lines. Variable
1109             # $incomplete records if there was a mismatch.
1110 5         10 my $incomplete = 0;
1111 5 50       89 if (-r $path) {
1112 5         20 my @lines_check = readlines($path);
1113 5         63 my @lines = @$raw_lines;
1114 5         19 my $totally_empty = 1;
1115 5         19 for (my $i=1; $i<=$#lines; $i++) {
1116 27 100       55 if (defined $raw_lines->[$i]) {
1117 5         9 $totally_empty = 0;
1118 5         10 last;
1119             }
1120             }
1121 5 50       21 if ($totally_empty) {
1122 0         0 load_file($filename);
1123 0         0 $trace_nums = $file_cache{$filename}{trace_nums};
1124             } else {
1125 5         17 for (my $i=1; $i<=$#lines; $i++) {
1126 181 100       255 if (defined $raw_lines->[$i]) {
1127 14     14   104 no warnings;
  14         41  
  14         1188  
1128 159 100       226 $trace_nums->{$i} = (-$raw_lines->[$i]) if
1129             (+$raw_lines->[$i]) != 0;
1130 159 50       300 $incomplete = 1 if $raw_lines->[$i] ne $lines[$i];
1131             } else {
1132 22         40 $raw_lines->[$i] = $lines_check[$i-1]
1133             }
1134             }
1135             }
1136             }
1137 14     14   98 use strict;
  14         34  
  14         640  
1138 5         11 $lines_href = {};
1139 5         21 $lines_href->{plain} = $raw_lines;
1140 5 50 66     24 if ($opts->{output} && $opts->{output} ne 'plain' && defined($raw_lines)) {
  0   33     0  
1141             # Some lines in $raw_lines may be undefined
1142 14     14   81 no strict; no warnings;
  14     14   33  
  14         336  
  14         68  
  14         36  
  14         1167  
1143 0         0 local $WARNING=0;
1144 0         0 my $highlight_lines = highlight_string(join('', @$raw_lines));
1145 0         0 my @highlight_lines = split(/\n/, $highlight_lines);
1146 0         0 $lines_href->{$opts->{output}} = \@highlight_lines;
1147 14     14   116 use strict; use warnings;
  14     14   41  
  14         580  
  14         74  
  14         43  
  14         3405  
1148             }
1149 5         14 $read_file = 1;
1150             }
1151             }
1152              
1153             # File based reading is done here.
1154 5 50       126 if (-f $path ) {
    0          
1155 5 100       24 $stat = File::stat::stat($path) unless defined $stat;
1156             } elsif (!$read_file) {
1157 0 0       0 if (basename($filename) eq $filename) {
1158             # try looking through the search path.
1159 0         0 for my $dirname (@INC) {
1160 0         0 $path = File::Spec->catfile($dirname, $filename);
1161 0 0       0 if ( -f $path) {
1162 0         0 $stat = File::stat::stat($path);
1163 0         0 last;
1164             }
1165             }
1166             }
1167 0 0       0 return 0 unless defined $stat;
1168             }
1169 5 50       260 if ( -r $path ) {
1170 5         28 my @lines = readlines($path);
1171 5         23 $lines_href = {plain => \@lines};
1172 5 50 66     29 if ($opts->{output} && $opts->{output} ne 'plain') {
1173 0         0 my $highlight_lines = highlight_string(join('', @lines));
1174 0         0 my @highlight_lines = split(/\n/, $highlight_lines);
1175 0         0 $lines_href->{$opts->{output}} = \@highlight_lines;
1176             }
1177             }
1178 5         47 my $entry = {
1179             stat => $stat,
1180             lines_href => $lines_href,
1181             path => $path,
1182             incomplete => 0,
1183             trace_nums => $trace_nums,
1184             };
1185 5         14 $file_cache{$filename} = $entry;
1186 14     14   106 no warnings;
  14         32  
  14         1722  
1187 5         18 $file2file_remap{$path} = $filename;
1188 5         14 return $path;
1189             }
1190              
1191             # example usage
1192             unless (caller) {
1193             BEGIN {
1194 14     14   94 use English qw( -no_match_vars );
  14         27  
  14         164  
1195 14     14   5031 $PERLDB |= 0x400;
1196             }; # Turn on saving @{_<$filename};
1197             my $file=__FILE__;
1198             my $fullfile = abs_path($file);
1199 14     14   92 no strict;
  14         25  
  14         682  
1200             print scalar(@{"main::_<$file"}), "\n";
1201 14     14   311 use strict;
  14         29  
  14         9887  
1202              
1203             my $script_name = '(eval 234)';
1204             update_script_cache($script_name, {string => "now\nis\nthe\ntime"});
1205             print join(', ', keys %script_cache), "\n";
1206             my $lines = $script_cache{$script_name}{lines_href}{plain};
1207             print join("\n", @{$lines}), "\n";
1208             $lines = getlines($script_name);
1209             printf "%s has %d lines\n", $script_name, scalar @$lines;
1210             printf("Line 1 of $script_name is:\n%s\n",
1211             getline($script_name, 1));
1212             my $max_line = size($script_name);
1213             printf("%s has %d lines via size\n",
1214             $script_name, scalar @$lines);
1215             do __FILE__;
1216             my @line_nums = trace_line_numbers(__FILE__);
1217              
1218             ### FIXME: add more of this stuff into unit test.
1219             printf("Breakpoints for: %s:\n%s\n",
1220             __FILE__, join(', ', @line_nums[0..30]));
1221             $lines = getlines(__FILE__);
1222             printf "%s has %d lines\n", __FILE__, scalar @$lines;
1223             my $full_file = abs_path(__FILE__);
1224             $lines = getlines(__FILE__);
1225             printf "%s still has %d lines\n", __FILE__, scalar @$lines;
1226             $lines = getlines(__FILE__);
1227             printf "%s also has %d lines\n", $full_file, scalar @$lines;
1228             my $line_number = __LINE__;
1229             my $line = getline(__FILE__, $line_number);
1230             printf "The %d line is:\n%s\n", $line_number, $line ;
1231             remap_file('another_name', __FILE__);
1232             print getline('another_name', __LINE__), "\n";
1233             printf "Files cached: %s\n", join(', ', cached_files);
1234             update_cache(__FILE__);
1235             printf "I said %s has %d lines!\n", __FILE__, size(__FILE__);
1236             printf "SHA1 of %s is:\n%s\n", __FILE__, sha1(__FILE__);
1237              
1238             my $stat = stat(__FILE__);
1239             printf("stat info size: %d, ctime %s, mode %o\n",
1240             $stat->size, $stat->ctime, $stat->mode);
1241              
1242             my $lines_aref = getlines(__FILE__, {output=>'term'});
1243             print join("\n", @$lines_aref[0..5,50..55]), "\n" if defined $lines_aref;
1244             $DB::filename = '(eval 4)';
1245             my $filename = map_script($DB::filename, "\$x=1;\n\$y=2;\n\$z=3;\n");
1246             print "mapped eval is $filename\n";
1247             printf("%s is a trace line? %d\n", __FILE__,
1248             is_trace_line(__FILE__, __LINE__-1));
1249             printf("%s is a trace line? %d\n", __FILE__,
1250             is_trace_line(__FILE__, __LINE__));
1251             eval "printf \"filename_is_eval: %s, %d\n\", __FILE__,
1252             filename_is_eval(__FILE__);";
1253             printf("filename_is_eval: %s, %d\n", __FILE__, filename_is_eval(__FILE__));
1254             printf("filename_is_eval: %s, %d\n", '-e', filename_is_eval('-e'));
1255              
1256             #$DB::filename = 'bogus';
1257             #eval {
1258             # print '+++', is_cached_script(__FILE__), "\n";
1259             #};
1260              
1261             $lines_aref = getlines(__FILE__, {output=>'term'});
1262             # print("trace nums again: ", join(', ',
1263             # trace_line_numbers(__FILE__)),
1264             # "\n");
1265             $line = getline(__FILE__, __LINE__,
1266             {output=>'term',
1267             maxlines => 6});
1268             print '-' x 30, "\n";
1269             print "$line\n";
1270             $line = getline(__FILE__, __LINE__,
1271             {output=>'plain',
1272             maxlines => 5});
1273             print '-' x 30, "\n";
1274             print "$line\n";
1275             print '-' x 30, "\n";
1276              
1277             my $dirname = File::Basename::dirname(__FILE__);
1278             my $colors_file = File::Spec->catfile($dirname, 'Colors.pm');
1279             load_file($colors_file);
1280             @line_nums = trace_line_numbers($colors_file);
1281             print join(', ', @line_nums, "\n");
1282             }
1283              
1284             1;