summaryrefslogtreecommitdiff
path: root/test/valgrindcheck.sh
blob: 358564369265318efc5523dad3ef1873e7941866 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#! /bin/sh
#
# Wrapper script which runs a command under valgrind control,
# redirects valgrind output into a log file and then
# checks after the run whether valgrind errors or warnings
# were logged.
#
# The environment is set so that GNOME libs and libstdc++ are
# valgrind-friendly (for example, explicit alloc/free instead of
# pooling).
#
# Additional valgrind parameters can be passed in the VALGRIND_ARGS
# environment variable. The log file can be chosen via VALGRIND_LOG,
# with valgrind.<pid of shell>.<current process>.out as default.


LOGPARAM=${VALGRIND_LOG:-valgrind.p$$.c%p.out}
# more than one file might get written
LOGFILES=${VALGRIND_LOG:-valgrind.p$$.*.out}

trap "cat $LOGFILES >&2 2>/dev/null; rm -f $LOGFILES" EXIT

# Finds all valgrind processes created for this run of
# valgrindcheck.sh, detected based on the log files created for
# them. This is necessary to catch processes which might have forked
# off from the $VALGRIND_PID that we know about.  We cannot simply
# kill all valgrind processes, because there might be other, longer
# running processes that need to continue.
valgrindProcesses () {
    if [ $LOGPARAM = "valgrind.p$$.c%p.out" ]; then
        for file in $LOGFILES; do
            pid=`echo $file | perl -p -e 's/.*valgrind\.p\d+\.c(\d+).*/$1/'`
            echo $pid
        done
    fi
}

# Any child valgrind processes still running?
valgrindRunning () {
    for pid in `valgrindProcesses`; do
        if ps $pid >/dev/null; then
            return 0
        fi
    done
    return 1
}

# Kill child valgrind process with given list of signals.
killvalgrind () {
    signals=$1
    for pid in `valgrindProcesses`; do
        if ps --no-headers ww $pid; then
            for signal in $signals; do
                echo "valgrind.sh: killing forked process $pid with signal $signal"
                kill -$signal $pid 2>&1 | grep -v 'No such process'
            done
        fi
    done
}

( set +x; echo "*** starting $1 under valgrind, output to ${VALGRIND_CMD_LOG:-stdout}" )
( set -x; if [ "$VALGRIND_CMD_LOG" ]; then exec >>"$VALGRIND_CMD_LOG" 2>&1; fi; exec env GLIBCXX_FORCE_NEW=1 G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind $VALGRIND_ARGS --gen-suppressions=all --log-file=$LOGPARAM "$@" $OUTPUT 2>&1 ) &
VALGRIND_PID=$!

intvalgrind () {
    kill -INT $VALGRIND_PID
}
termvalgrind () {
    kill -TERM $VALGRIND_PID
}

trap "kill -TERM $VALGRIND_PID" TERM
trap "kill -INT $VALGRIND_PID" INT

wait $VALGRIND_PID
RET=$?
echo "valgrindcheck ($$): '$@' ($VALGRIND_PID): returned $RET" >&2

# give other valgrind instances some time to settle down, then kill them
sleep 1
killvalgrind INT TERM
# let valgrind chew on leak checking for up to 30 seconds before killing it
# for good
i=0
while valgrindRunning && [ $i -lt 30 ]; do
    sleep 1
    i=`expr $i + 1`
done
killvalgrind KILL
# Filter out leaks in forked processes if VALGRIND_LEAK_CHECK_ONLY_FIRST is set,
# detect if unfiltered errors were reported by valgrind. Unfiltered errors
# are detected because valgrind will produce a suppression for us, which can
# found easily by looking for "insert_a_suppression_name_here".
#
# Here is some example output:
#
# ==13044== Memcheck, a memory error detector
# ==13044== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
# ==13044== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
# ==13044== Command: /tmp/test-fork
# ==13044== Parent PID: 13043
# ==13044== 
# ==13044== Conditional jump or move depends on uninitialised value(s)
# ==13044==    at 0x400555: main (test-fork.c:9)
# ==13044== 
# {
#    <insert_a_suppression_name_here>
#    Memcheck:Cond
#    fun:main
# }
# ==13044== 
# ==13044== HEAP SUMMARY:
# ==13044==     in use at exit: 0 bytes in 0 blocks
# ==13044==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
# ==13044== 
# ==13044== All heap blocks were freed -- no leaks are possible
# ==13044== 
# ==13044== For counts of detected and suppressed errors, rerun with: -v
# ==13044== Use --track-origins=yes to see where uninitialised values come from
# ==13044== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
# ==13047== 
# ==13047== HEAP SUMMARY:
# ==13047==     in use at exit: 100 bytes in 1 blocks
# ==13047==   total heap usage: 1 allocs, 0 frees, 100 bytes allocated
# ==13047== 
# ==13047== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
# ==13047==    at 0x4C244E8: malloc (vg_replace_malloc.c:236)
# ==13047==    by 0x40056E: main (test-fork.c:15)
# ==13047== 
# {
#    <insert_a_suppression_name_here>
#    Memcheck:Leak
#    fun:malloc
#    fun:main
# }
# ==13047== LEAK SUMMARY:
# ==13047==    definitely lost: 100 bytes in 1 blocks
# ==13047==    indirectly lost: 0 bytes in 0 blocks
# ==13047==      possibly lost: 0 bytes in 0 blocks
# ==13047==    still reachable: 0 bytes in 0 blocks
# ==13047==         suppressed: 0 bytes in 0 blocks
# ==13047== 
# ==13047== For counts of detected and suppressed errors, rerun with: -v
# ==13047== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 4 from 4)
#
# This output is meant to be printed completely when VALGRIND_LEAK_CHECK_ONLY_FIRST
# is not set. Otherwise the "definitely lost" error including its suppression
# shall be filtered out.
#
# 100 is returned by Perl either way because of the Cond error in the main process.
#

SUBRET=0
for i in $LOGFILES; do
perl \
    -e '$onlyfirst = $ENV{VALGRIND_LEAK_CHECK_ONLY_FIRST};' \
    -e '@leakskip = split(/,/, $ENV{VALGRIND_LEAK_CHECK_SKIP});' \
    -e '%skippids = {};' \
    -e '$ret = 0;' \
    -e '$pid = 0;' \
    -e '$skipping = 0;' \
    -e 'while (<>) {' \
    -e '   if (/^==(\d*)== Command: (.*)/) {' \
    -e '      $newpid = $1; $command = $2;' \
    -e '      foreach $pattern (@leakskip) {' \
    -e '         if ($command =~ /$pattern/) {' \
    -e '             $skippids{$newpid} = 1;' \
    -e '             last;' \
    -e '         }' \
    -e '      }' \
    -e '   } elsif (/^==(\d*)==/) {'\
    -e '      $newpid = $1;' \
    -e '   } else {' \
    -e '      $newpid = 0;' \
    -e '   }' \
    -e '   if ($newpid && ($skipping == 1 && $pid == $newpid || $skipping == 2 && !$skippids{$newpid})) {' \
    -e '      $skipping = 0;' \
    -e '   }' \
    -e '   if (!$skipping) {' \
    -e '      $pid = $newpid unless $pid;' \
    -e '      $ispotential = /(possibly|indirectly) lost in loss record/;' \
    -e '      if ($newpid && $skippids{$newpid} && $ispotential) {' \
    -e '         $skipping = 2;' \
    -e '      } elsif ($newpid && $onlyfirst && $pid != $newpid && $ispotential) {' \
    -e '         $skipping = 1;' \
    -e '      } else {' \
    -e '         print;' \
    -e '         if (/insert_a_suppression_name_here/) {' \
    -e '            $ret = 100;' \
    -e '         }' \
    -e '      }' \
    -e '   }' \
    -e '}' \
    -e 'exit $ret;' \
    $i
    SUBSUBRET=$?
    if [ $SUBRET -eq 0 ]; then
       SUBRET=$SUBSUBRET
    fi
done

# bad valgrind log result always overrides normal completion status:
# that way the valgrind errors also show up in the nightly test summary
# in the case where some other test already failed
if [ $SUBRET -ne 0 ]; then
    RET=$SUBRET
    echo valgrindcheck: "$@": log analysis overrides return code $RET with $SUBRET >&2
fi
rm $LOGFILES

echo valgrindcheck: "$@": final result $RET >&2
exit $RET