#!/usr/bin/perl use warnings; use strict; use IO::Handle; # -------------------------------------------------------------------- customize here # top source directory my $topdir = "$ENV{ARBHOME}"; my $toplen = length($topdir); # list containing paths of all source files (generated by arb_valgrind) my $sourcelist = "$topdir/SOURCE_TOOLS/valgrind2grep.lst"; # prefix to write before hidden caller-lines # (-> emacs will not jump to them automatically, you have to remove the prefix first) my $unmark_callers = "(hide) "; # prefix to write before filtered lines my $unmark_filtered = "(filt) "; # prefix to write before other non-error lines my $unmark_rest = "(note) "; sub check_ignore_internal($\$$) { my ($text,$ignore_r,$reason) = @_; if (not defined $$ignore_r) { # defines ignored leaks/errors occurring in internal libraries (wontfixes) # To ignore them add a search expression here. # Please add current date as well, to make it easier to find outdated expressions. # print "check_ignore_internal: reason='$reason'\n"; if ($reason =~ 'loss record') { # memory leaks if ($reason =~ 'still reachable' or $reason =~ 'possibly lost') { # reachable memory leaks # things broken in ARB motif (wontfix; remove these exclusions after gtk merge) if ($text =~ /\b(gbmGetMemImpl)\b/) { $$ignore_r = $&; return; } # 04/12/2013 -- not all blocks of internal mem.management were freed (set MEMORY_TEST 1 in gb_memory.h to find leakers) ; } elsif ($reason =~ 'definitely lost') { # unreachable memory leaks if ($text =~ /\b(awt_create_selection_list_on_(trees|alignments))\b/) { $$ignore_r = $&; return; } # 03/12/2013 ; } } elsif ($reason =~ 'file descriptor') { # open file descriptors if ($text =~ /\b(aw_initstatus)\b/) { $$ignore_r = $&; return; } # 03/12/2013 ; } # else { # illegal memory access # } } } sub is_boring($) { my ($text) = @_; if ($text =~ /provoke_core_dump/) { return 1; } return 0; } my $debug = 0; # --------------------------------------------------------------- customize till here # get args: my $args = scalar(@ARGV); if ($args<2 or $args>3) { die "Usage: valgrind2grep [--suppress-common]\n"; } my $callers = $ARGV[0]; my $filter = $ARGV[1]; my $suppress_common = 0; if ($args==3) { my $a = $ARGV[2]; if ($a eq '--suppress-common') { $suppress_common=1; } else { die "Unknown argument '$a'"; } } # use unbuffered I/O (otherwise pipe waits for valgrind to terminate???) my $in = new IO::Handle; $in->fdopen(fileno(STDIN),"r") || die "can't open STDIN"; my $out = new IO::Handle; $out->fdopen(fileno(STDOUT),"w") || die "can't open STDOUT"; # read list of source files: open(SOURCELIST,"<$sourcelist") || die "can't open $sourcelist"; my %fileIndex = (); sub addFileIndex($$) { my ($key,$val) = @_; if (not exists $fileIndex{$key}) { my @array = (); $fileIndex{$key} = \@array; } my $array_r = $fileIndex{$key}; push @$array_r, $val; } foreach () { chomp; addFileIndex($_,$_); if (/\/([^\/]+)\/([^\/]+)$/) { my $last_dir = $1; my $fname = $2; addFileIndex($fname,$_); addFileIndex($last_dir.'/'.$fname,$_); } elsif (/\/([^\/]+)$/) { my $fname = $1; addFileIndex($fname,$_); } else { die "invalid entry in $sourcelist ('$_')" } } close(SOURCELIST); sub parentDir($) { my ($dirOrFile) = @_; if ($dirOrFile =~ /\/[^\/]+$/o) { return $`; } return undef; } sub makeTargetAbsolute($$) { my ($abslink,$reltarget) = @_; my $absdir = parentDir($abslink); if (defined $absdir) { while ($reltarget =~ /^\.\.\//o) { $reltarget = $'; my $absparent = parentDir($absdir); if (defined $absparent) { $absdir = $absparent; } else { die "Can't detect parent dir of '$absdir'"; } } my $result = $absdir.'/'.$reltarget; return $result; } else { die "Can't detect parent dir of '$abslink'"; } } # make entries unique foreach (keys %fileIndex) { my $array_r = $fileIndex{$_}; my %unique = map { $_ => 1; } @$array_r; my $changed = 1; while ($changed==1) { $changed = 0; my @del = (); my @add = (); foreach (keys %unique) { my $target = undef; eval { $target = readlink($_); }; if ($@) { # a link with invalid target? push @del, $_; $out->print("Remove invalid link '$_' (Reason: $!)\n"); } elsif (defined $target) { # a link with valid target $target = makeTargetAbsolute($_,$target); push @del, $_; push @add, $target; # $out->print("Replace link '$_'\n by target '$target'\n"); # $out->print("Target '$target' exists:".(-e $target ? 'yes' : 'no')."\n"); } # else not a link } if (scalar(@del)) { foreach (@del) { delete $unique{$_}; } $changed=1; } if (scalar(@add)) { foreach (@add) { $unique{$_} = 1; } $changed=1; } } @$array_r = keys %unique; } $out->print("Settings: Showing $callers caller(s).\n"); $out->print(" Filtering with '$filter'.\n"); sub avoid_location($) { # invalidate everything emacs could missinterpret as error-location (i.e. '(file:lineno)') ($_) = @_; s/([(].*)(:)(.*[)])/$1_$2_$3/ig; $_; } my $reg_topdir = qr/^$topdir\//o; sub shorten_location($) { my ($locline) = @_; if ($locline =~ /^([^:]+):([0-9]+):/o) { my ($loc,$line,$msg) = ($1,$2,$'); if ($loc =~ $reg_topdir) { $loc = $'; } $locline = $loc.':'.$line.':'.$msg; } $locline; } my $entered=0; sub entering() { if ($entered==0) { $out->print('vake[2]: Entering directory `'.$topdir."\'\n"); $entered = 1; } } sub leaving() { if ($entered==1) { $out->print('vake[2]: Leaving directory `'.$topdir."\'\n"); $entered = 0; } } sub hideMessages($\@) { my ($hidePrefix,$outstack_r) = @_; $hidePrefix = "($hidePrefix) "; @$outstack_r = map { $hidePrefix.$_; } @$outstack_r; } # variables: my $i; my $called_from = "called from"; my $reason = 'no reason yet'; my $non_caller_reason = 'no reason yet'; my $caller_count = 0; # counts callers my $filtered = 0; # filter current error my $ignore = undef; my $last_ignore = ''; my $ignore_curr_line = 0; # the filter loop: my @outstack = (); while (not $in->eof) { # read one line: $_ = $in->getline; # convert error messages to grep format: if (/^([=\-0-9]+[ ]+)(.*)$/) { my $prefix = $1; my $content = $2; if ($content =~ /^([ab][ty].*)([(][^()]+[)])$/) { # looks like an valgrind error $content = $1; my $location = $2; if ($location =~ /[(](.*):(.*)[)]/) { # seems to have a valid '(file:line)' location at eol my ($file,$line) = ($1,$2); if ($filtered == 1) { $_ = $unmark_filtered.' '.&avoid_location($_); } else { my $array_r = $fileIndex{$file}; if (defined $array_r) { my @lines = (); if (scalar(@$array_r)>1) { push @lines, $unmark_rest."Multiple occurrences of '$file' - not sure which location is the correct one\n"; } if ($reason eq $called_from) { # its a caller $caller_count++; } else { $caller_count = 0; } foreach my $replace (@$array_r) { if (not -f $replace) { $_ = "$sourcelist:1: might be outdated ($replace does not exist)\n"; } else { $_ = "$replace:$line: $reason ($content)\n"; if ($caller_count > $callers) { $_ = $unmark_callers.$_; } # hide this caller } push @lines, $_; } $reason = $called_from; $_ = join '', @lines; } else { # location in unavailable file (i.e. in library) $_ = $unmark_rest.$prefix.$reason." $content (in unavailable file $file line $line)\n"; } if ($reason ne $called_from) { $non_caller_reason = $reason; } if ($suppress_common==1) { check_ignore_internal($_, $ignore, $non_caller_reason); } } } else { # valgrind error w/o location $non_caller_reason = $reason; $_=$unmark_rest.' '.$_; } } else { # no location found if ($content =~ /^TRANSLATE: / or $content =~ /^Reading syms from/ or $content =~ /unhandled CFI instruction/ or # happens since gcc 5.4.2 $content =~ /object doesn.t have a/) { $ignore_curr_line = 1; } elsif ($content =~ /^[ ]*$/) { if (defined $ignore) { hideMessages('ign2', @outstack); } foreach my $line (@outstack) { $out->print($line); } @outstack = (); $out->flush; $ignore = undef; $_ = '( ) '.$_; } else { $reason = $content; $_='( ) '.$_; # should that reason be filtered ? if ($reason =~ /alloc\'d/) { # an allocator message (applies to last message) -> so never filter $reason = "ORIGIN: $reason"; # keep $ignore like before } else { if ($reason =~ /$filter/i) { $filtered = 0; } else { $filtered = 1; } if ($filtered == 1) { $ignore = undef; } } if ($filtered==0) { $non_caller_reason = $reason; } } } } my $boring = 0; if ($ignore_curr_line==0) { $boring = $ignore_curr_line = is_boring($_); } # print out line if ($ignore_curr_line==0) { if (not defined $ignore) { entering(); push @outstack, shorten_location($_); $last_ignore = ''; } else { # defined ignore # print "last_ignore='$last_ignore' ignore='$ignore'\n"; if ($ignore ne $last_ignore) { hideMessages('ign3', @outstack); foreach my $line (@outstack) { $out->print($line); } @outstack = (); s/^\(note\)[ ]*//; $out->print("(igno) '$ignore' ".$_); $out->print("(skip) further messages suppressed\n"); $out->flush; $last_ignore = $ignore; } else { if ($debug==1) { $out->print("(comm) ".$_); $out->flush; } } } } else { if ($boring) { $out->print("(BORE) ".$_); $out->flush(); } elsif ($debug==1) { $out->print("(SUPP) ".$_); $out->flush(); } $ignore_curr_line = 0; } } if (defined $ignore) { hideMessages('ign4', @outstack); } foreach my $line (@outstack) { $out->print($line); } @outstack = (); leaving(); $out->flush; $in->close; $out->close;