shared_valgrind_functions.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #!/bin/sh
  2. set -o noclobber -o nounset
  3. ### Design
  4. #
  5. # This file contains functions related to checking with valgrind. The POSIX sh
  6. # language doesn't allow us to specify a "public API", but if we could, it
  7. # would be:
  8. # - valgrind_init():
  9. # Clear previous valgrind output, and prepare for running valgrind tests
  10. # (if applicable).
  11. # - valgrind_setup_cmd():
  12. # Set up the valgrind command if $USE_VALGRIND is greater than or equal to
  13. # ${valgrind_min}.
  14. # - valgrind_check_basenames(exitfile):
  15. # Check for any memory leaks recorded in valgrind logfiles associated with a
  16. # test exitfile. Return the filename if there's a leak; otherwise return an
  17. # empty string.
  18. # - valgrind_incomplete():
  19. # Check if any valgrind log files are incomplete.
  20. # A non-zero value unlikely to be used as an exit code by the programs being
  21. # tested.
  22. valgrind_exit_code=108
  23. ## valgrind_prepare_directory ():
  24. # Clean up a previous valgrind directory, and prepare for new valgrind tests
  25. # (if applicable).
  26. valgrind_prepare_directory() {
  27. # If we don't want to generate new suppressions files, move them.
  28. if [ "${USE_VALGRIND_NO_REGEN}" -gt 0 ]; then
  29. valgrind_suppressions="${out_valgrind}/suppressions"
  30. fds="${out_valgrind}/fds.log"
  31. # Bail if the file doesn't exist.
  32. if [ ! -e "${valgrind_suppressions}" ]; then
  33. echo "No valgrind suppressions file" 1>&2
  34. exit 1
  35. fi
  36. # Move the files away.
  37. supp_tmp="$(mktemp /tmp/valgrind-suppressions.XXXXXX)"
  38. fds_tmp="$(mktemp /tmp/valgrind-fds.XXXXXX)"
  39. mv "${valgrind_suppressions}" "${supp_tmp}"
  40. mv "${fds}" "${fds_tmp}"
  41. fi
  42. # Always delete any previous valgrind directory.
  43. if [ -d "${out_valgrind}" ]; then
  44. rm -rf "${out_valgrind}"
  45. fi
  46. # Bail if we don't want valgrind at all.
  47. if [ "${USE_VALGRIND}" -eq 0 ]; then
  48. return
  49. fi
  50. mkdir "${out_valgrind}"
  51. # If we don't want to generate a new suppressions file, restore it.
  52. if [ "${USE_VALGRIND_NO_REGEN}" -gt 0 ]; then
  53. # Move the files back.
  54. mv "${supp_tmp}" "${valgrind_suppressions}"
  55. mv "${fds_tmp}" "${fds}"
  56. fi
  57. # We don't want to back up this directory.
  58. [ "$(uname)" = "FreeBSD" ] && chflags nodump "${out_valgrind}"
  59. }
  60. ## valgrind_check_optional ():
  61. # Return a $USE_VALGRIND variable defined; if it was previously defined and
  62. # was greater than 0, then check that valgrind is available in the $PATH.
  63. valgrind_check_optional() {
  64. if [ "${USE_VALGRIND}" -gt 0 ]; then
  65. # Look for valgrind in $PATH.
  66. if ! command -v valgrind >/dev/null 2>&1; then
  67. printf "valgrind not found\n" 1>&2
  68. exit 1
  69. fi
  70. # Check the version.
  71. version=$(valgrind --version | cut -d "-" -f 2)
  72. major=$(echo "${version}" | cut -d "." -f 1)
  73. minor=$(echo "${version}" | cut -d "." -f 2)
  74. if [ "${major}" -lt "3" ]; then
  75. printf "valgrind must be at least version 3.13\n" 1>&2
  76. exit 1;
  77. fi
  78. if [ "${major}" -eq "3" ] && [ "${minor}" -lt "13" ]; then
  79. printf "valgrind must be at least version 3.13\n" 1>&2
  80. exit 1;
  81. fi
  82. fi
  83. }
  84. ## valgrind_process_suppression_file(filename):
  85. # Generalize suppressions from a valgrind suppression file by omitting the
  86. # "fun:pl_*" and "fun:main" lines and anything below them.
  87. valgrind_process_suppression_file() {
  88. filename=$1
  89. # How many segments do we have?
  90. num_segments="$(grep -c "^{" "${filename}")"
  91. # Bail if there's nothing to do.
  92. if [ "${num_segments}" -eq "0" ]; then
  93. return
  94. fi
  95. # Sanity check.
  96. if [ "${num_segments}" -gt 100 ]; then
  97. printf "More than 100 valgrind suppressions?!\n" 1>&2
  98. exit 1
  99. fi
  100. # Split into segments.
  101. csplit -f "${filename}" "${filename}" "/{/" \
  102. "{$((num_segments - 1))}" > /dev/null
  103. # Skip "${filename}00" because that doesn't contain a suppression.
  104. i=1
  105. while [ "${i}" -le "${num_segments}" ]; do
  106. segfilename="$(printf "%s%02i" "${filename}" "${i}")"
  107. # Find last relevant line.
  108. lastline="$(grep -n "}" "${segfilename}" | cut -f1 -d:)"
  109. # Cut off anything below the 1st "fun:pl_" (inclusive).
  110. funcline="$(grep -n "fun:pl_" "${segfilename}" | \
  111. cut -f1 -d: | \
  112. head -n1)"
  113. if [ -n "${funcline}" ]; then
  114. if [ "${lastline}" -gt "${funcline}" ]; then
  115. lastline="${funcline}"
  116. fi
  117. fi
  118. # Cut off anything below "fun:main" (including that line).
  119. # (Due to linking and/or optimizations, some memory leaks
  120. # occur without "fun:pl_" appearing in the valgrind
  121. # suppression.)
  122. funcline="$(grep -n "fun:main" "${segfilename}" | cut -f1 -d:)"
  123. if [ -n "${funcline}" ]; then
  124. if [ "${lastline}" -gt "${funcline}" ]; then
  125. lastline="${funcline}"
  126. fi
  127. fi
  128. # Only keep the beginning of each suppression.
  129. lastline="$((lastline - 1))"
  130. head -n "${lastline}" "${segfilename}" >> \
  131. "${valgrind_suppressions}"
  132. printf "}\n" >> "${valgrind_suppressions}"
  133. # Advance to the next suppression.
  134. i=$((i + 1))
  135. done
  136. }
  137. ## valgrind_ensure_suppression (potential_memleaks_binary):
  138. # Run the ${potential_memleaks_binary} through valgrind, keeping
  139. # track of any apparent memory leak in order to suppress reporting
  140. # those leaks when testing other binaries. Record how many file descriptors
  141. # are open at exit in ${valgrind_fds}.
  142. valgrind_ensure_suppression() {
  143. potential_memleaks_binary=$1
  144. # Quit if we're not using valgrind.
  145. if [ ! "${USE_VALGRIND}" -gt 0 ]; then
  146. return
  147. fi;
  148. fds_log="${out_valgrind}/fds.log"
  149. if [ "${USE_VALGRIND_NO_REGEN}" -gt 0 ]; then
  150. printf "Using old valgrind suppressions\n" 1>&2
  151. valgrind_fds=$(grep "FILE DESCRIPTORS" "${fds_log}" | \
  152. awk '{print $4}')
  153. return
  154. fi
  155. printf "Generating valgrind suppressions... " 1>&2
  156. valgrind_suppressions="${out_valgrind}/suppressions"
  157. valgrind_suppressions_log="${out_valgrind}/suppressions.pre"
  158. # Start off with an empty suppression file
  159. touch "${valgrind_suppressions}"
  160. # Get list of tests and the number of open descriptors at a normal exit
  161. valgrind_suppressions_tests="${out_valgrind}/suppressions-names.txt"
  162. valgrind --track-fds=yes --log-file="${fds_log}" \
  163. "${potential_memleaks_binary}" > "${valgrind_suppressions_tests}"
  164. valgrind_fds=$(grep "FILE DESCRIPTORS" "${fds_log}" | awk '{print $4}')
  165. # Generate suppressions for each test
  166. while read -r testname; do
  167. this_valgrind_supp="${valgrind_suppressions_log}-${testname}"
  168. # Run valgrind on the binary, sending it a "\n" so that
  169. # a test which uses STDIN will not wait for user input.
  170. printf "\n" | (valgrind \
  171. --leak-check=full --show-leak-kinds=all \
  172. --gen-suppressions=all \
  173. --suppressions="${valgrind_suppressions}" \
  174. --log-file="${this_valgrind_supp}" \
  175. "${potential_memleaks_binary}" \
  176. "${testname}") \
  177. > /dev/null
  178. # Append name to suppressions file
  179. printf "# %s\n" "${testname}" >> "${valgrind_suppressions}"
  180. # Strip out useless parts from the log file, and allow the
  181. # suppressions to apply to other binaries.
  182. valgrind_process_suppression_file "${this_valgrind_supp}"
  183. done < "${valgrind_suppressions_tests}"
  184. # Clean up
  185. rm -f "${valgrind_suppressions_log}"
  186. printf "done.\n" 1>&2
  187. }
  188. ## valgrind_setup_cmd ():
  189. # Set up the valgrind command if $USE_VALGRIND is greater than or equal to
  190. # ${valgrind_min}.
  191. valgrind_setup_cmd() {
  192. # Bail if we don't want to use valgrind for this check.
  193. if [ "${USE_VALGRIND}" -lt "${c_valgrind_min}" ]; then
  194. return
  195. fi
  196. val_logfilename="${s_val_basename}-${c_count_str}-%p.log"
  197. c_valgrind_cmd="valgrind \
  198. --log-file=${val_logfilename} \
  199. --track-fds=yes \
  200. --leak-check=full --show-leak-kinds=all \
  201. --errors-for-leak-kinds=all \
  202. --suppressions=${valgrind_suppressions}"
  203. echo "${c_valgrind_cmd}"
  204. }
  205. ## valgrind_incomplete:
  206. # Return 0 if at least one valgrind log file is not complete.
  207. valgrind_incomplete() {
  208. # The exit code of `grep -L` is undesirable: if at least one file
  209. # contains the pattern, it returns 0. To detect if at least one file
  210. # does *not* contain the pattern, we need to check grep's output,
  211. # rather than the exit code.
  212. _valgrind_incomplete_logfiles=$(grep -L "ERROR SUMMARY" \
  213. "${out_valgrind}"/*.log)
  214. test -n "${_valgrind_incomplete_logfiles}"
  215. }
  216. ## valgrind_get_basename (exitfile):
  217. # Return the filename without ".log" of the valgrind logfile corresponding to
  218. # ${exitfile}.
  219. valgrind_get_basename() {
  220. exitfile=$1
  221. basename=$(basename "${exitfile}" ".exit")
  222. echo "${out_valgrind}/${basename}"
  223. }
  224. ## valgrind_check_logfile(logfile)
  225. # Check for any (unsuppressed) memory leaks recorded in a valgrind logfile.
  226. # Echo the filename if there's a leak; otherwise, echo nothing.
  227. valgrind_check_logfile() {
  228. logfile=$1
  229. # Bytes in use at exit.
  230. in_use=$(grep "in use at exit:" "${logfile}" | awk '{print $6}')
  231. # Sanity check.
  232. if [ "$(echo "${in_use}" | wc -w)" -ne "1" ]; then
  233. echo "Programmer error: invalid number valgrind outputs" 1>&2
  234. exit 1
  235. fi
  236. # Check for any leaks. Use string comparison, because valgrind formats
  237. # the number with commas, and sh can't convert strings like "1,000"
  238. # into an integer.
  239. if [ "${in_use}" != "0" ] ; then
  240. # Check if all of the leaked bytes are suppressed. The extra
  241. # whitespace in " suppressed" is necessary to distinguish
  242. # between two instances of "suppressed" in the log file. Use
  243. # string comparison due to the format of the number.
  244. suppressed=$(grep " suppressed:" "${logfile}" | \
  245. awk '{print $3}')
  246. if [ "${in_use}" != "${suppressed}" ]; then
  247. # There is an unsuppressed leak.
  248. echo "${logfile}"
  249. return
  250. fi
  251. fi
  252. # Check for the wrong number of open fds. On a normal desktop
  253. # computer, we expect 4: std{in,out,err}, plus the valgrind logfile.
  254. # If this is running inside a virtualized OS or container or shared
  255. # CI setup (such as Travis-CI), there might be other open
  256. # descriptors. The important thing is that the number of fds should
  257. # match the simple test case (executing potential_memleaks without
  258. # running any actual tests).
  259. fds_in_use=$(grep "FILE DESCRIPTORS" "${logfile}" | awk '{print $4}')
  260. if [ "${fds_in_use}" != "${valgrind_fds}" ] ; then
  261. # There is an unsuppressed leak.
  262. echo "${logfile}"
  263. return
  264. fi
  265. # Check the error summary.
  266. num_errors=$(grep "ERROR SUMMARY: " "${logfile}" | awk '{print $4}')
  267. if [ "${num_errors}" -gt 0 ]; then
  268. # There was some other error(s) -- invalid read or write,
  269. # conditional jump based on uninitialized value(s), invalid
  270. # free, etc.
  271. echo "${logfile}"
  272. return
  273. fi
  274. }
  275. ## valgrind_check_basenames (exitfile):
  276. # Check for any memory leaks recorded in valgrind logfiles associated with a
  277. # test exitfile. Return the filename if there's a leak; otherwise return an
  278. # empty string.
  279. valgrind_check_basenames() {
  280. exitfile="$1"
  281. val_basename=$(valgrind_get_basename "${exitfile}")
  282. # Get list of files to check. (Yes, the star goes outside the quotes.)
  283. logfiles=$(ls "${val_basename}"* 2>/dev/null)
  284. num_logfiles=$(echo "${logfiles}" | wc -w)
  285. # Bail if we don't have any valgrind logfiles to check.
  286. # Use numeric comparison, because wc leaves a tab in the output.
  287. if [ "${num_logfiles}" -eq "0" ] ; then
  288. return
  289. fi
  290. # Check a single file.
  291. if [ "${num_logfiles}" -eq "1" ]; then
  292. valgrind_check_logfile "${logfiles}"
  293. return
  294. fi
  295. # If there's two files, there's a fork() -- likely within
  296. # daemonize() -- so only pay attention to the child.
  297. if [ "${num_logfiles}" -eq "2" ]; then
  298. # Find both pids.
  299. val_pids=""
  300. for logfile in ${logfiles} ; do
  301. val_pid=$(head -n 1 "${logfile}" | cut -d "=" -f 3)
  302. val_pids="${val_pids} ${val_pid}"
  303. done
  304. # Find the logfile which has a parent in the list of pids.
  305. for logfile in ${logfiles} ; do
  306. val_parent_pid=$(grep "Parent PID:" "${logfile}" | \
  307. awk '{ print $4 }')
  308. if [ "${val_pids#*"${val_parent_pid}"}" != \
  309. "${val_pids}" ]; then
  310. valgrind_check_logfile "${logfile}"
  311. return "$?"
  312. fi
  313. done
  314. fi
  315. # Programmer error; hard bail.
  316. echo "Programmer error: wrong number of valgrind logfiles!" 1>&2
  317. exit 1
  318. }
  319. ## valgrind_init():
  320. # Clear previous valgrind output, and prepare for running valgrind tests
  321. # (if applicable).
  322. valgrind_init() {
  323. # If we want valgrind, check that the version is high enough.
  324. valgrind_check_optional
  325. # Remove any previous directory, and create a new one.
  326. valgrind_prepare_directory
  327. # Generate valgrind suppression file if it is required. Must be
  328. # done after preparing the directory.
  329. valgrind_ensure_suppression \
  330. "${bindir}/tests/valgrind/potential-memleaks"
  331. }