File Coverage

lib/B/DeparseTree/Fragment.pm
Criterion Covered Total %
statement 15 213 7.0
branch 0 94 0.0
condition 0 30 0.0
subroutine 5 19 26.3
pod 0 14 0.0
total 20 370 5.4


line stmt bran cond sub pod time code
1             package B::DeparseTree::Fragment;
2              
3 1     1   5787 use strict; use warnings;
  1     1   2  
  1         21  
  1         3  
  1         2  
  1         20  
4 1     1   4 use Data::Printer;
  1         2  
  1         7  
5 1     1   55 use vars qw(@ISA @EXPORT);
  1         2  
  1         1714  
6             @ISA = ('Exporter');
7             @EXPORT = qw(deparse_offset
8             dump
9             extract_node_info
10             get_addr_info
11             get_parent_addr_info get_parent_op
12             get_prev_addr_info
13             trim_line_pair
14             underline_parent
15             );
16              
17             sub deparse_offset
18             {
19 0     0 0   my ($funcname, $address) = @_;
20              
21 0           my $deparse = B::DeparseTree->new();
22 0 0         if ($funcname eq "DB::DB") {
23 0           $deparse->main2info;
24             } else {
25 0           $deparse->coderef2info(\&$funcname);
26             }
27 0           get_addr_info($deparse, $address);
28             }
29              
30             sub get_addr($$)
31             {
32 0     0 0   my ($deparse, $addr) = @_;
33 0 0         return undef unless $addr;
34 0           return $deparse->{optree}{$addr};
35             }
36              
37             sub get_addr_info($$)
38             {
39 0     0 0   my ($deparse, $addr) = @_;
40 0           my $op_info = get_addr($deparse, $addr);
41 0           return $op_info;
42             }
43              
44             sub get_parent_op($)
45             {
46 0     0 0   my ($op_info) = @_;
47 0 0         return undef unless $op_info;
48 0           my $deparse = $op_info->{deparse};
49              
50             # FIXME:
51 0           return $deparse->{ops}{$op_info->{addr}}{parent};
52             }
53              
54             sub get_parent_addr_info($)
55             {
56 0     0 0   my ($op_info) = @_;
57 0           my $deparse = $op_info->{deparse};
58             # FIXME
59             # my $parent_op = get_parent_op($op_info);
60 0           my $parent_addr = $op_info->{parent};
61 0 0         return undef unless $parent_addr;
62 0           return $deparse->{optree}{$parent_addr};
63             }
64              
65             sub get_prev_info($);
66             sub get_prev_info($)
67             {
68 0     0 0   my ($op_info) = @_;
69 0 0         return undef unless $op_info;
70             return $op_info->{prev_expr}
71 0           }
72              
73             sub get_prev_addr_info($);
74             sub get_prev_addr_info($)
75             {
76 0     0 0   my ($op_info) = @_;
77 0 0         return undef unless $op_info;
78 0 0         if (!exists $op_info->{prev_expr}) {
79 0           my $parent_info = get_parent_addr_info($op_info);
80 0 0         if ($parent_info) {
81 0           return get_prev_addr_info($parent_info);
82             } else {
83 0           return undef;
84             }
85             }
86             return $op_info->{prev_expr}
87 0           }
88              
89             sub underline_parent($$$) {
90 0     0 0   my ($child_text, $parent_text, $char) = @_;
91 0           my $start_pos = index($parent_text, $child_text);
92 0           return (' ' x $start_pos) . ($char x length($child_text));
93              
94             }
95             # Return either 2 or 3 strings in an array reference.
96             # There are various cases to consider.
97             # 1. Child and parent texts are no more than a single line:
98             # return and the underline, two entries. For example:
99             # my ($a, $b) = (5, 6);
100             # -----
101             # 2. The parent spans more than a line but the child is
102             # on that line. Return an array of the first line of the parent
103             # with elision and the child underline, two entries. Example
104             # if the child is $a in:
105             # if ($a) {
106             # $b
107             # }
108             # return:
109             # if ($a) {...
110             # --
111             # -----
112             # 3. The parent spans more than a line and the child is
113             # not that line. Return an array of the first line of the parent
114             # with elision, then the line containing the child and the child underline,
115             # three entries. Example:
116             # if the child is $b in:
117             # if ($a) {
118             # $b;
119             # $c;
120             # }
121             # return:
122             # if ($a) {...
123             # $b;
124             # --
125              
126             # 4. The parent spans more than a line and the child is
127             # not that line and also spans more than a single line.
128             # Do the same as 3. but add eplises to the underline.
129             # Example:
130             # if the child is "\$b;\n \$c" in:
131             # if ($a) {
132             # $b;
133             # $c;
134             # }
135             # return:
136             # if ($a) {...
137             # $b;
138             # ---...
139             # 5. Like 4, but the child is on the first line. A cross between
140             # 3 and 4. No elipses for the first line is needed, just one on the
141             # underline
142             #
143             sub trim_line_pair($$$$) {
144 0     0 0   my ($parent_text, $child_text, $parent_underline, $start_pos) = @_;
145             # If the parent text is longer than a line, use just the line.
146             # The underline indicator adds an elipsis to show it is elided.
147 0           my @parent_lines = split(/\n/, $parent_text);
148 0           my $i = 0;
149 0 0         if (scalar(@parent_lines) > 1) {
150 0           for ($i=0; $start_pos > length($parent_lines[$i]); $i++) {
151 0           my $l = length($parent_lines[$i]);
152 0           $start_pos -= ($l+1);
153 0           $parent_underline = substr($parent_underline, $l+1);
154             }
155             }
156 0           my @result = ();
157 0 0         if ($i > 0) {
158 0           push @result, $parent_lines[0] . '...';
159             }
160 0           my $stripped_parent = $parent_lines[$i];
161 0           my @child_lines = split(/\n/, $child_text);
162 0 0         if (scalar(@child_lines) > 1) {
163 0           $parent_underline = substr($parent_underline, 0, length($child_lines[0])+1) . '...';
164             }
165              
166 0           push @result, $stripped_parent, $parent_underline;
167 0           return \@result;
168             }
169              
170             # FIXME: this is a mess
171              
172             # return an ARRAY ref to strings that can be joined with "\n" to
173             # give a position in the text. Here are some examples
174             # after joining
175             #
176             # my ($x, $y) = (1, 2)
177             # ------
178             # my ($x, $y) = (1, 2)
179             # ~~~~~
180             # my ($x, $y) = (1, 2)
181             # -
182             # (1, 2)
183             #
184             # if ($x) { ...
185             # my ($x, $y = (1, 2)
186             # -------------------
187             #
188             # if ($x) { ...
189             # my ($x, $y = (1, 2)...
190             # -------------------
191              
192             # When we can, we text in the context of the surrounding
193             # text which is obtained by going up levels of the tree
194             # form the instruction. We might have to go a few levels
195             # up the tree before we find a text that spans more than
196             # a single line. In the fourth case where we don't
197             # have an underline but simply have "(1, 2)" that means
198             # were unable to get the parent text.
199              
200             # We hope that in the normal case, using the place holders in the
201             # format specifiers, we can know for sure where a child fits in as
202             # that child's node is stored in the parent as some "texts"
203             # entry. However this isn't always possible right now. So in the
204             # second example where "~" was used instead of "-", to
205             # indicate that the result was obtained the result by string matching
206             # rather than by exact node matching inside the parent.
207             # We can also use the "|" instead for instructions that really
208             # don't have an equivalent concept in the source code, so we've
209             # artificially tagged a location that is reasonable. "pushmark"
210             # and "padrange" instructions would be in this category.
211             #
212             # In the last two examples, we show how we do elision. The ...
213             # in the parent text means that we have only given the first line
214             # of the parent text along with the line that the child fits in.
215             # if there is an elision in the child text it means that that
216             # spans more than one line.
217              
218             sub extract_node_info($)
219             {
220 0     0 0   my ($info) = @_;
221              
222 0           my $child_text = $info->{text};
223 0           my $parent_text = undef;
224 0           my $candidate_pair = undef;
225 0           my $marked_position = undef;
226              
227             # Some opcodes like pushmark , padrange, and null,
228             # don't have an well-defined correspondence to a string in the
229             # source code, so we have made a somewhat arbitrary association fitting
230             # into the parent string. Examples of such artificial associations are
231             # the function name part of call, or an open brace of a scope.
232             # You can tell these nodes because they have a "position" field.
233 0 0         if (exists $info->{position}) {
234 0           my $found_pos = $info->{position};
235 0           $marked_position = $found_pos;
236 0           $parent_text = $child_text;
237 0           $child_text = substr($parent_text,
238             $found_pos->[0], $found_pos->[1]);
239 0           my $parent_underline = ' ' x $found_pos->[0];
240 0           $parent_underline .= '|' x $found_pos->[1];
241 0           $candidate_pair = trim_line_pair($parent_text, $child_text,
242             $parent_underline,
243             $found_pos->[0]);
244              
245             }
246              
247 0 0         my $parent = $info->{parent} ? $info->{parent} : undef;
248 0 0         unless ($parent) {
249 0 0         return $candidate_pair ? $candidate_pair : [$child_text];
250             }
251              
252 0           my $child_addr = $info->{addr};
253 0           my $deparsed = $info->{deparse};
254 0           my $parent_info = $deparsed->{optree}{$parent};
255              
256 0 0         unless ($parent_info) {
257 0 0         return $candidate_pair ? $candidate_pair : [$child_text];
258             }
259              
260 0 0         my $separator = exists $parent_info->{sep} ? $parent_info->{sep} : '';
261 0 0         my @texts = exists $parent_info->{texts} ? @{$parent_info->{texts}} : ($parent_info->{text});
  0            
262 0           my $parent_line = '';
263 0           my $text_len = $#texts;
264 0           my $result = '';
265              
266 0 0 0       if (!exists $parent_info->{fmt}
      0        
267             and scalar(@texts) == 1
268 0           and eval{$texts[0]->isa("B::DeparseTree::Node")}) {
269 0           $parent_info = $texts[0];
270             }
271 0 0 0       if (exists $parent_info->{fmt} || exists $parent_info->{position}) {
272             # If the child text is the same as the parent's, go up the parent
273             # chain until we find something different.
274 0   0       while ($parent_info->{text} eq $child_text
      0        
275             && $parent_info->{parent}
276             && $deparsed->{optree}{$parent_info->{parent}}
277             ) {
278 0           $parent_info = $deparsed->{optree}{$parent_info->{parent}};
279             }
280 0           my $fmt = $parent_info->{fmt};
281 0           my $indexes = $parent_info->{indexes};
282 0           my $args = $parent_info->{texts};
283 0           my ($str, $found_pos) = $deparsed->template_engine($fmt, $indexes, $args,
284             $child_addr);
285              
286             # Keep gathering parent text until we have at least one full line.
287 0   0       while (index($str, "\n") == -1 && $parent_info->{parent}) {
288 0           $child_addr = $parent_info->{addr};
289 0           $parent_info = $deparsed->{optree}{$parent_info->{parent}};
290 0           $fmt = $parent_info->{fmt};
291 0           $indexes = $parent_info->{indexes};
292 0           $args = $parent_info->{texts};
293 0           my ($next_str, $next_found_pos) = $deparsed->template_engine($fmt,
294             $indexes, $args,
295             $child_addr);
296 0 0         last unless $next_found_pos;
297 0           my $nl_pos = index($next_str, "\n");
298 0   0       while ($nl_pos >= 0 and $nl_pos < $next_found_pos->[0]) {
299 0           $next_str = substr($next_str, $nl_pos+1);
300 0           $next_found_pos->[0] -= ($nl_pos+1);
301 0           $nl_pos = index($next_str, "\n");
302             }
303 0           $str = $next_str;
304 0 0         if ($found_pos) {
305 0           $found_pos->[0] += $next_found_pos->[0];
306             } else {
307 0           $found_pos = $next_found_pos;
308             }
309             }
310              
311 0 0         if (defined($found_pos)) {
312 0           my $parent_underline;
313 0 0         if ($marked_position) {
314 0           $parent_underline = ' ' x ($found_pos->[0] + $marked_position->[0]);
315 0           $parent_underline .= '|' x $marked_position->[1];
316             } else {
317 0           $parent_underline = ' ' x $found_pos->[0];
318 0           $parent_underline .= '-' x $found_pos->[1];
319             }
320 0           return trim_line_pair($str, $child_text, $parent_underline, $found_pos->[0]);
321             }
322 0           $result = $str;
323             } else {
324 0           for (my $i=0; $i <= $text_len; $i++) {
325 0           my $text = $texts[$i];
326 0 0         $result .= $separator if $result;
327              
328 0 0         if (ref($text)) {
329 0 0 0       if (ref($text) eq 'ARRAY' and (scalar(@$text) == 2)) {
    0          
330 0 0         if ($text->[1] == $child_addr) {
331 0           $child_text = $text->[0];
332 0           my $parent_underline = ' ' x length($result);
333 0           $result .= $text->[0];
334 0           $parent_underline .= '-' x length($text->[0]);
335 0 0         if ($i < $text_len) {
336 0           $result .= $separator;
337 0           my @remain_texts = @texts[$i+1..$#texts];
338 0           my $tail = $deparsed->combine2str($separator, \@remain_texts);
339 0           $result .= $tail;
340             }
341 0           return trim_line_pair($result, $child_text, $parent_underline, 0);
342             } else {
343 0           $result .= $text->[0];
344             }
345             } elsif ($text->{addr} == $child_addr) {
346 0           my $parent_underline = ' ' x length($result);
347 0           $result .= $text->{text};
348 0           $parent_underline .= '-' x length($text->{text});
349 0 0         if ($i < $text_len) {
350 0           $result .= $separator;
351 0           my @remain_texts = @texts[$i+1..$#texts];
352 0           my $tail = $deparsed->combine2str($separator, \@remain_texts);
353 0           $result .= $tail;
354             }
355 0           return trim_line_pair($result, $child_text, $parent_underline, 0);
356             } else {
357 0           $result .= $text->{text};
358             }
359             } else {
360 0           $result .= $text;
361             }
362             }
363             }
364             # Can't find by node address info, so just try to find the string
365             # inside of the parent.
366 0           $parent_text = $parent_info->{text};
367 0           my $start_index = index($parent_text, $child_text);
368 0 0         if ($start_index >= 0) {
369 0 0         if (index($parent_text, $child_text, $start_index+1) < 0) {
370             # It is in there *uniquely*!
371 0           my $parent_underline = underline_parent($child_text, $parent_text, '~');
372 0           return trim_line_pair($parent_text, $child_text, $parent_underline, $start_index);
373             }
374             }
375             }
376              
377             # Dump out full information of a node in relation to its
378             # parent
379             sub dump($) {
380 0     0 0   my ($deparse_tree) = @_;
381 0           my @addrs = sort keys %{$deparse_tree->{optree}};
  0            
382 0           for (my $i=0; $i < $#addrs; $i++) {
383 0           print $i, '-' x 50, "\n";
384 0           my $info = get_addr_info($deparse_tree, $addrs[$i]);
385 0 0         if ($info) {
386 0           printf "0x%0x\n", $addrs[$i];
387 0           p $info ;
388             }
389 0 0         if ($info->{parent}) {
390 0           my $parent = get_parent_addr_info($info);
391 0 0         if ($parent) {
392 0           p $parent ;
393 0           my $texts = extract_node_info($info);
394 0 0         if ($texts) {
395 0           print join("\n", @$texts), "\n";
396             }
397             }
398             }
399 0           print $i, '-' x 50, "\n";
400             }
401             }
402              
403             # Dump out essention information of a node in relation to its
404             # parent
405             sub dump_relations($) {
406 0     0 0   my ($deparse_tree) = @_;
407 0           my @addrs = sort keys %{$deparse_tree->{optree}};
  0            
408 0           for (my $i=0; $i < $#addrs; $i++) {
409 0           my $info = get_addr_info($deparse_tree, $addrs[$i]);
410 0 0 0       next unless $info && $info->{parent};
411 0           my $parent = get_parent_addr_info($info);
412 0 0         next unless $parent;
413 0           print $i, '-' x 50, "\n";
414 0           print "Child info:\n";
415 0           printf "\taddr: 0x%0x, parent: 0x%0x\n", $addrs[$i], $parent->{addr};
416 0 0         printf "\top: %s\n", $info->{op}->can('name') ? $info->{op}->name : $info->{op} ;
417 0           printf "\ttext: %s\n\n", $info->{text};
418             # p $parent ;
419 0           my $texts = extract_node_info($info);
420 0 0         if ($texts) {
421 0           print join("\n", @$texts), "\n";
422             }
423 0           print $i, '-' x 50, "\n";
424             }
425             }
426              
427             sub dump_tree($$);
428              
429             # Dump out the entire texts in tree format
430             sub dump_tree($$) {
431 0     0 0   my ($deparse_tree, $info) = @_;
432 0 0 0       if (ref($info) and (ref($info->{texts}) eq 'ARRAY')) {
433 0           foreach my $child_info (@{$info->{texts}}) {
  0            
434 0 0         if (ref($child_info)) {
435 0 0         if (ref($child_info) eq 'ARRAY') {
    0          
436 0           p $child_info;
437             } elsif (ref($child_info) eq 'B::DeparseTree::Node') {
438 0           dump_tree($deparse_tree, $child_info)
439             } else {
440 0           printf "Unknown child_info type %s\n", ref($child_info);
441 0           p $child_info;
442             }
443             }
444             }
445 0           print '-' x 50, "\n";
446             }
447 0           p $info ;
448 0           print '=' x 50, "\n";
449             }
450              
451             unless (caller) {
452             sub bug() {
453 0     0 0   my ($a, $b) = @_;
454 0 0         ($a, $b) = ($b, $a) if ($a > $b);
455             # -((1, 2) x 2);
456             # no strict;
457             # for ( $i=0; $i;) {};
458             # my ($a, $b, $c);
459             # CORE::exec($foo $bar);
460             # exec $foo $bar;
461             # exec $foo $bar;
462             }
463              
464             my $child_text = '$foo $bar';
465             my $result = 'exec $foo $bar';
466             my $parent_underline = " ---------";
467             my $start_pos = 0;
468             my $lines = trim_line_pair($result, $child_text, $parent_underline,
469             $start_pos);
470             print join("\n", @$lines), "\n";
471              
472             my $deparse = B::DeparseTree->new();
473 1     1   6 use B;
  1         2  
  1         100  
474             $deparse->pessimise(B::main_root, B::main_start);
475             # my @addrs = sort keys %{$deparse->{ops}}, "\n";
476             # use Data::Printer;
477             # p @addrs;
478              
479             # my @info_addrs = sort keys %{$deparse->{optree}}, "\n";
480             # print '-' x 40, "\n";
481             # p @info_addrs;
482              
483             # $deparse->init();
484             # my $svref = B::svref_2object(\&bug);
485             # my $x = $deparse->deparse_sub($svref, $addrs[9]);
486             # my $x = $deparse->deparse_sub($svref);
487             # dump_tree($deparse, $x);
488              
489             # # my @info_addrs = sort keys %{$deparse->{optree}}, "\n";
490             # # print '-' x 40, "\n";
491             # # p @info_addrs;
492              
493             # #my $info = get_addr_info($deparse, $addrs[10]);
494             # # p $info;
495             # exit 0;
496              
497             $deparse->coderef2info(\&bug);
498             # $deparse->coderef2info(\&get_addr_info);
499             my @addrs = sort keys %{$deparse->{optree}}, "\n";
500             B::DeparseTree::Fragment::dump($deparse);
501              
502             # my ($parent_text, $pu);
503             # $parent_text = "now is the time";
504             # $child_text = 'is';
505             # $start_pos = index($parent_text, $child_text);
506             # $pu = underline_parent($child_text, $parent_text, '-');
507             # print join("\n", @{trim_line_pair($parent_text, $child_text,
508             # $pu, $start_pos)}), "\n";
509             # $parent_text = "if (\$a) {\n\$b\n}";
510             # $child_text = '$b';
511             # $start_pos = index($parent_text, $child_text);
512             # $pu = underline_parent($child_text, $parent_text, '-');
513             # print join("\n", @{trim_line_pair($parent_text, $child_text,
514             # $pu, $start_pos)}), "\n";
515              
516             # $parent_text = "if (\$a) {\n \$b;\n \$c}";
517             # $child_text = '$b';
518             # $start_pos = index($parent_text, $child_text);
519             # $pu = underline_parent($child_text, $parent_text, '-');
520             # print join("\n", @{trim_line_pair($parent_text, $child_text,
521             # $pu, $start_pos)}), "\n";
522             # $parent_text = "if (\$a) {\n \$b;\n \$c}";
523             # $child_text = "\$b;\n \$c";
524             # $start_pos = index($parent_text, $child_text);
525             # $pu = underline_parent($child_text, $parent_text, '-');
526             # print join("\n", @{trim_line_pair($parent_text, $child_text,
527             # $pu, $start_pos)}), "\n";
528             }
529              
530             1;