#!/bin/bash12# This file is part of t8code.3# t8code is a C library to manage a collection (a forest) of multiple4# connected adaptive space-trees of general element classes in parallel.5#6# Copyright (C) 2025 the developers7#8# t8code is free software; you can redistribute it and/or modify9# it under the terms of the GNU General Public License as published by10# the Free Software Foundation; either version 2 of the License, or11# (at your option) any later version.12#13# t8code is distributed in the hope that it will be useful,14# but WITHOUT ANY WARRANTY; without even the implied warranty of15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16# GNU General Public License for more details.17#18# You should have received a copy of the GNU General Public License19# along with t8code; if not, write to the Free Software Foundation, Inc.,20# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.2122#23# This script runs Valgrind on an input binary paths with specified memory leak detection flags.24# The Valgrind output is parsed. If any errors are found, they are printed and the script exits with a status of 1.25# If errors are found, the Valgrind output is kept in the file valgrind-output.log for further inspection.26# Using "--supp=[FILE]", you can provide a path to a suppression file that is used by Valgrind to suppress certain errors.27# With "--ntasks=[NUMBER]", you can provide the number of processes to use with MPI (default is 1).28#29USAGE="\nUSAGE: This script executes valgrind in parallel on a given input file. Use \n30$0 [FILE] --supp=[SUPPRESSION_FILE] --ntasks=[NUM_TASKS]\n31to run valgrind on FILE. Optionally you can provide a suppression file and the number of parallel processes to use with MPI.\n"3233# Check that an argument is given and that the argument is a file.34if [ ${1-x} = x ]; then35echo "ERROR: Need to provide a file as first argument."36echo -e "$USAGE"37exit 138fi39if [ -f "$1" ]; then40FILE="$1"41else42# Try from folder above.43if [ -f "../$1" ]; then44FILE="../$1"45else46echo "ERROR: Non existing file: $1"47echo -e "$USAGE"48exit 149fi50fi5152# Check if a suppression file is provided. If yes, add the flag to incorporate the Valgrind suppression file.53VALGRIND_FLAGS=""54for arg in "$@"; do55if [[ "$arg" == --supp=* ]]; then56supp_file="${arg#--supp=}"57if [ -f "$supp_file" ]; then58VALGRIND_FLAGS="${VALGRIND_FLAGS} --suppressions=${supp_file}"59else60echo "ERROR: Suppression file '$supp_file' does not exist."61echo -e "$USAGE"62exit 163fi64fi65done6667# Check if a number of processes is provided. If not, set to 1.68num_procs=169for arg in "$@"; do70if [[ "$arg" == --ntasks=* ]]; then71ntasks_val="${arg#--ntasks=}"72if [[ "$ntasks_val" =~ ^[0-9]+$ ]]; then73num_procs="$ntasks_val"74else75echo "ERROR: --ntasks value '$ntasks_val' is not a valid number."76echo -e "$USAGE"77exit 178fi79fi80done8182echo "Valgrind check of ${FILE} using ${num_procs} processes..."8384# Write valgrind output to variable OUTPUT_FILE.85OUTPUT_FILE="valgrind-output-$(basename "$FILE").log"86# Set valgrind flags.87VALGRIND_FLAGS="${VALGRIND_FLAGS} --leak-check=full --track-origins=yes \88--trace-children=yes --show-leak-kinds=definite,indirect,possible \89--errors-for-leak-kinds=definite,indirect,possible"90# There are some more flags that can be reasonable to use, e.g., for debugging reasons if you found an error.91# We used minimal flags for performance reasons.92# Further flags include (but of course are not limited to): --expensive-definedness-checks=yes --track-fds=yes93# For more detailed outputs: -read-var-info=yes --read-inline-info=yes --gen-suppressions=all94# Warning: --show-leak-kinds=all will find a lot of still reachable leaks. This is not necessarily a problem.9596# Run valgrind on given file with flags and write output to OUTPUT_FILE.97mpirun -n $num_procs valgrind $VALGRIND_FLAGS "${FILE}" > /dev/null 2>"${OUTPUT_FILE}"9899# Parse valgrind output.100declare -a VALGRIND_RULES=(101"^==.*== .* bytes in .* blocks are definitely lost in loss record .* of .*$"102"^==.*== .* bytes in .* blocks are indirectly lost in loss record .* of .*$"103"^==.*== .* bytes in .* blocks are possibly lost in loss record .* of .*$"104"^==.*== Invalid .* of size .*$"105"^==.*== Open file descriptor .*: .*$"106"^==.*== Invalid free() / delete / delete\[\] / realloc()$"107"^==.*== Mismatched free() / delete / delete \[\].*$"108"^==.*== Syscall param .* points to uninitialised byte(s).*$"109"^==.*== Source and destination overlap in .*$"110"^==.*== Argument .* of function .* has a fishy (possibly negative) value: .*$"111"^==.*== .*alloc() with size 0$"112"^==.*== Invalid alignment value: .* (should be power of 2)$"113"^==.*== Conditional jump or move depends on uninitialised value(s)"114)115report_id=1116status=0117error=""118119while IFS= read -r line; do120if [[ "${error}" != "" ]]; then121# Error message of valgrind always end with a line ==.*== without any further information.122# Only print if we collected every line of the error message.123if [[ $(echo "${line}" | grep '^==.*== $') ]]; then124echo "::Error found in valgrind report '${FILE}' (${report_id})::"125echo -e "${error}"126echo ""127report_id=$(( $report_id + 1 ))128error=""129status=1130else131# Add to error message that is printed with the last line of the error.132error="${error}\n${line}"133fi134fi135for rule in "${VALGRIND_RULES[@]}"; do136# Check if we found one of the errors defined in VALGRIND_RULES.137if [[ $(echo "${line}" | grep "${rule}") ]]; then138error="${line}"139break140fi141done142if [[ $(echo "${line}" | grep '^==.*== ERROR SUMMARY:') ]]; then143if ! [[ $(echo "${line}" | grep '^==.*== ERROR SUMMARY: 0 ') ]]; then144# Set status to 1 if an error was found that is not included in VALGRIND_RULES.145status=1146fi147echo "${line}"148elif [[ $(echo "${line}" | grep 'valgrind:.*: command not found') ]]; then149echo "${line}"150status=1151fi152done < "${OUTPUT_FILE}"153154# Remove the output file only if the checks were error free.155if [ "$status" -eq 0 ]; then156rm -f "${OUTPUT_FILE}"157fi158exit "${status}"159160161