Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/scripts/check_valgrind.sh
900 views
1
#!/bin/bash
2
3
# This file is part of t8code.
4
# t8code is a C library to manage a collection (a forest) of multiple
5
# connected adaptive space-trees of general element classes in parallel.
6
#
7
# Copyright (C) 2025 the developers
8
#
9
# t8code is free software; you can redistribute it and/or modify
10
# it under the terms of the GNU General Public License as published by
11
# the Free Software Foundation; either version 2 of the License, or
12
# (at your option) any later version.
13
#
14
# t8code is distributed in the hope that it will be useful,
15
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
# GNU General Public License for more details.
18
#
19
# You should have received a copy of the GNU General Public License
20
# along with t8code; if not, write to the Free Software Foundation, Inc.,
21
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22
23
#
24
# This script runs Valgrind on an input binary paths with specified memory leak detection flags.
25
# The Valgrind output is parsed. If any errors are found, they are printed and the script exits with a status of 1.
26
# If errors are found, the Valgrind output is kept in the file valgrind-output.log for further inspection.
27
# Using "--supp=[FILE]", you can provide a path to a suppression file that is used by Valgrind to suppress certain errors.
28
# With "--ntasks=[NUMBER]", you can provide the number of processes to use with MPI (default is 1).
29
#
30
USAGE="\nUSAGE: This script executes valgrind in parallel on a given input file. Use \n
31
$0 [FILE] --supp=[SUPPRESSION_FILE] --ntasks=[NUM_TASKS]\n
32
to run valgrind on FILE. Optionally you can provide a suppression file and the number of parallel processes to use with MPI.\n"
33
34
# Check that an argument is given and that the argument is a file.
35
if [ ${1-x} = x ]; then
36
echo "ERROR: Need to provide a file as first argument."
37
echo -e "$USAGE"
38
exit 1
39
fi
40
if [ -f "$1" ]; then
41
FILE="$1"
42
else
43
# Try from folder above.
44
if [ -f "../$1" ]; then
45
FILE="../$1"
46
else
47
echo "ERROR: Non existing file: $1"
48
echo -e "$USAGE"
49
exit 1
50
fi
51
fi
52
53
# Check if a suppression file is provided. If yes, add the flag to incorporate the Valgrind suppression file.
54
VALGRIND_FLAGS=""
55
for arg in "$@"; do
56
if [[ "$arg" == --supp=* ]]; then
57
supp_file="${arg#--supp=}"
58
if [ -f "$supp_file" ]; then
59
VALGRIND_FLAGS="${VALGRIND_FLAGS} --suppressions=${supp_file}"
60
else
61
echo "ERROR: Suppression file '$supp_file' does not exist."
62
echo -e "$USAGE"
63
exit 1
64
fi
65
fi
66
done
67
68
# Check if a number of processes is provided. If not, set to 1.
69
num_procs=1
70
for arg in "$@"; do
71
if [[ "$arg" == --ntasks=* ]]; then
72
ntasks_val="${arg#--ntasks=}"
73
if [[ "$ntasks_val" =~ ^[0-9]+$ ]]; then
74
num_procs="$ntasks_val"
75
else
76
echo "ERROR: --ntasks value '$ntasks_val' is not a valid number."
77
echo -e "$USAGE"
78
exit 1
79
fi
80
fi
81
done
82
83
echo "Valgrind check of ${FILE} using ${num_procs} processes..."
84
85
# Write valgrind output to variable OUTPUT_FILE.
86
OUTPUT_FILE="valgrind-output-$(basename "$FILE").log"
87
# Set valgrind flags.
88
VALGRIND_FLAGS="${VALGRIND_FLAGS} --leak-check=full --track-origins=yes \
89
--trace-children=yes --show-leak-kinds=definite,indirect,possible \
90
--errors-for-leak-kinds=definite,indirect,possible"
91
# There are some more flags that can be reasonable to use, e.g., for debugging reasons if you found an error.
92
# We used minimal flags for performance reasons.
93
# Further flags include (but of course are not limited to): --expensive-definedness-checks=yes --track-fds=yes
94
# For more detailed outputs: -read-var-info=yes --read-inline-info=yes --gen-suppressions=all
95
# Warning: --show-leak-kinds=all will find a lot of still reachable leaks. This is not necessarily a problem.
96
97
# Run valgrind on given file with flags and write output to OUTPUT_FILE.
98
mpirun -n $num_procs valgrind $VALGRIND_FLAGS "${FILE}" > /dev/null 2>"${OUTPUT_FILE}"
99
100
# Parse valgrind output.
101
declare -a VALGRIND_RULES=(
102
"^==.*== .* bytes in .* blocks are definitely lost in loss record .* of .*$"
103
"^==.*== .* bytes in .* blocks are indirectly lost in loss record .* of .*$"
104
"^==.*== .* bytes in .* blocks are possibly lost in loss record .* of .*$"
105
"^==.*== Invalid .* of size .*$"
106
"^==.*== Open file descriptor .*: .*$"
107
"^==.*== Invalid free() / delete / delete\[\] / realloc()$"
108
"^==.*== Mismatched free() / delete / delete \[\].*$"
109
"^==.*== Syscall param .* points to uninitialised byte(s).*$"
110
"^==.*== Source and destination overlap in .*$"
111
"^==.*== Argument .* of function .* has a fishy (possibly negative) value: .*$"
112
"^==.*== .*alloc() with size 0$"
113
"^==.*== Invalid alignment value: .* (should be power of 2)$"
114
"^==.*== Conditional jump or move depends on uninitialised value(s)"
115
)
116
report_id=1
117
status=0
118
error=""
119
120
while IFS= read -r line; do
121
if [[ "${error}" != "" ]]; then
122
# Error message of valgrind always end with a line ==.*== without any further information.
123
# Only print if we collected every line of the error message.
124
if [[ $(echo "${line}" | grep '^==.*== $') ]]; then
125
echo "::Error found in valgrind report '${FILE}' (${report_id})::"
126
echo -e "${error}"
127
echo ""
128
report_id=$(( $report_id + 1 ))
129
error=""
130
status=1
131
else
132
# Add to error message that is printed with the last line of the error.
133
error="${error}\n${line}"
134
fi
135
fi
136
for rule in "${VALGRIND_RULES[@]}"; do
137
# Check if we found one of the errors defined in VALGRIND_RULES.
138
if [[ $(echo "${line}" | grep "${rule}") ]]; then
139
error="${line}"
140
break
141
fi
142
done
143
if [[ $(echo "${line}" | grep '^==.*== ERROR SUMMARY:') ]]; then
144
if ! [[ $(echo "${line}" | grep '^==.*== ERROR SUMMARY: 0 ') ]]; then
145
# Set status to 1 if an error was found that is not included in VALGRIND_RULES.
146
status=1
147
fi
148
echo "${line}"
149
elif [[ $(echo "${line}" | grep 'valgrind:.*: command not found') ]]; then
150
echo "${line}"
151
status=1
152
fi
153
done < "${OUTPUT_FILE}"
154
155
# Remove the output file only if the checks were error free.
156
if [ "$status" -eq 0 ]; then
157
rm -f "${OUTPUT_FILE}"
158
fi
159
exit "${status}"
160
161