shared_valgrind_functions.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  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(str):
  12. # Set up the valgrind command if ${USE_VALGRIND} is greater than or equal to
  13. # ${valgrind_min}. If ${str} is not blank, include it in the log filename.
  14. # - valgrind_check(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. #
  21. # We adopt the convention of "private" function names beginning with an _.
  22. #
  23. ### Variables
  24. #
  25. # Wherever possible, this suite uses local variables and
  26. # explicitly-passed arguments, with the following exceptions:
  27. # - valgrind_suppressions: filename of valgrind suppressions.
  28. # - valgrind_fds_log: filename of the log of open file descriptors.
  29. ## _val_prepdir ():
  30. # Clean up a previous valgrind directory, and prepare for new valgrind tests
  31. # (if applicable).
  32. _val_prepdir() {
  33. # If we don't want to generate new suppressions files, move them.
  34. if [ "${USE_VALGRIND_NO_REGEN}" -gt 0 ]; then
  35. # Bail if the file doesn't exist.
  36. if [ ! -e "${valgrind_suppressions}" ]; then
  37. echo "No valgrind suppressions file" 1>&2
  38. exit 1
  39. fi
  40. # Move the files away.
  41. _val_prepdir_supp_tmp="$(mktemp /tmp/valgrind-suppressions.XXXXXX)"
  42. _val_prepdir_fds_tmp="$(mktemp /tmp/valgrind-fds.XXXXXX)"
  43. mv "${valgrind_suppressions}" "${_val_prepdir_supp_tmp}"
  44. mv "${valgrind_fds_log}" "${_val_prepdir_fds_tmp}"
  45. fi
  46. # Always delete any previous valgrind directory.
  47. if [ -d "${out_valgrind}" ]; then
  48. rm -rf "${out_valgrind}"
  49. fi
  50. # Bail if we don't want valgrind at all.
  51. if [ "${USE_VALGRIND}" -eq 0 ]; then
  52. return
  53. fi
  54. mkdir "${out_valgrind}"
  55. # If we don't want to generate a new suppressions file, restore it.
  56. if [ "${USE_VALGRIND_NO_REGEN}" -gt 0 ]; then
  57. # Move the files back.
  58. mv "${_val_prepdir_supp_tmp}" "${valgrind_suppressions}"
  59. mv "${_val_prepdir_fds_tmp}" "${valgrind_fds_log}"
  60. fi
  61. # We don't want to back up this directory.
  62. [ "$(uname)" = "FreeBSD" ] && chflags nodump "${out_valgrind}"
  63. }
  64. ## _val_checkver ():
  65. # If ${USE_VALGRIND} is greater than 0, check that valgrind is available in
  66. # the ${PATH} and is at least version 3.13.
  67. _val_checkver() {
  68. # Quit if we're not using valgrind.
  69. if [ ! "${USE_VALGRIND}" -gt 0 ]; then
  70. return
  71. fi;
  72. # Look for valgrind in $PATH.
  73. if ! command -v valgrind >/dev/null 2>&1; then
  74. printf "valgrind not found\n" 1>&2
  75. exit 1
  76. fi
  77. # Check the version.
  78. _val_checkver_version=$(valgrind --version | cut -d "-" -f 2)
  79. _val_checkver_major=$(echo "${_val_checkver_version}" | cut -d "." -f 1)
  80. _val_checkver_minor=$(echo "${_val_checkver_version}" | cut -d "." -f 2)
  81. if [ "${_val_checkver_major}" -lt "3" ]; then
  82. printf "valgrind must be at least version 3.13\n" 1>&2
  83. exit 1;
  84. fi
  85. if [ "${_val_checkver_major}" -eq "3" ] && \
  86. [ "${_val_checkver_minor}" -lt "13" ]; then
  87. printf "valgrind must be at least version 3.13\n" 1>&2
  88. exit 1;
  89. fi
  90. }
  91. ## _val_seg(filename):
  92. # Generalize an already-segmented portion of a valgrind suppressions file;
  93. # write the result to ${valgrind_suppressions}.
  94. _val_seg() {
  95. _val_seg_filename=$1
  96. # Find last relevant line.
  97. _val_seg_lastline="$(grep -n "}" "${_val_seg_filename}" | cut -f1 -d:)"
  98. # Cut off anything below the 1st "fun:pl_" (inclusive).
  99. _val_seg_funcline="$(grep -n "fun:pl_" "${_val_seg_filename}" | \
  100. cut -f1 -d: | \
  101. head -n1)"
  102. if [ -n "${_val_seg_funcline}" ]; then
  103. if [ "${_val_seg_lastline}" -gt "${_val_seg_funcline}" ]; then
  104. _val_seg_lastline="${_val_seg_funcline}"
  105. fi
  106. fi
  107. # Cut off anything below "fun:main" (including that line). (Due to
  108. # linking and/or optimizations, some memory leaks occur without
  109. # "fun:pl_" appearing in the valgrind suppression.)
  110. _val_seg_funcline="$(grep -n "fun:main" "${_val_seg_filename}" | \
  111. cut -f1 -d:)"
  112. if [ -n "${_val_seg_funcline}" ]; then
  113. if [ "${_val_seg_lastline}" -gt "${_val_seg_funcline}" ]; then
  114. _val_seg_lastline="${_val_seg_funcline}"
  115. fi
  116. fi
  117. # Only keep the beginning of each suppression.
  118. _val_seg_lastline="$((_val_seg_lastline - 1))"
  119. head -n "${_val_seg_lastline}" "${_val_seg_filename}" >> \
  120. "${valgrind_suppressions}"
  121. printf "}\n" >> "${valgrind_suppressions}"
  122. }
  123. ## _val_generalize(filename):
  124. # Generalize suppressions from a valgrind suppression file by omitting the
  125. # "fun:pl_*" and "fun:main" lines and anything below them.
  126. _val_generalize() {
  127. _val_generalize_filename=$1
  128. # How many segments do we have?
  129. _val_generalize_num_segments="$(grep -c "^{" "${_val_generalize_filename}")"
  130. # Bail if there's nothing to do.
  131. if [ "${_val_generalize_num_segments}" -eq "0" ]; then
  132. return
  133. fi
  134. # Sanity check.
  135. if [ "${_val_generalize_num_segments}" -gt 100 ]; then
  136. printf "More than 100 valgrind suppressions?!\n" 1>&2
  137. exit 1
  138. fi
  139. # Split into segments.
  140. csplit -f "${_val_generalize_filename}" "${_val_generalize_filename}" \
  141. "/{/" "{$((_val_generalize_num_segments - 1))}" > /dev/null
  142. # Skip "${filename}00" because that doesn't contain a suppression.
  143. _val_generalize_i=1
  144. while [ "${_val_generalize_i}" -le "${_val_generalize_num_segments}" ]; do
  145. # Process segment
  146. _val_seg "$(printf "%s%02d" \
  147. "${_val_generalize_filename}" "${_val_generalize_i}")"
  148. # Advance to the next suppression.
  149. _val_generalize_i=$((_val_generalize_i + 1))
  150. done
  151. }
  152. ## _val_ensure (potential_memleaks_binary):
  153. # Run the ${potential_memleaks_binary} through valgrind, keeping
  154. # track of any apparent memory leak in order to suppress reporting
  155. # those leaks when testing other binaries. Record a log file which shows the
  156. # open file descriptors in ${valgrind_fds_log}.
  157. _val_ensure() {
  158. _val_ensure_potential_memleaks_binary=$1
  159. # Quit if we're not using valgrind.
  160. if [ ! "${USE_VALGRIND}" -gt 0 ]; then
  161. return
  162. fi;
  163. if [ "${USE_VALGRIND_NO_REGEN}" -gt 0 ]; then
  164. printf "Using old valgrind suppressions\n" 1>&2
  165. return
  166. fi
  167. printf "Generating valgrind suppressions... " 1>&2
  168. _val_ensure_log="${out_valgrind}/suppressions.pre"
  169. # Start off with an empty suppression file
  170. touch "${valgrind_suppressions}"
  171. # Get list of tests and the number of open descriptors at a normal exit
  172. _val_ensure_names="${out_valgrind}/suppressions-names.txt"
  173. valgrind --track-fds=yes --log-file="${valgrind_fds_log}" \
  174. "${_val_ensure_potential_memleaks_binary}" \
  175. > "${_val_ensure_names}"
  176. # Generate suppressions for each test
  177. while read -r _val_ensure_testname; do
  178. _val_ensure_thisl="${_val_ensure_log}-${_val_ensure_testname}"
  179. # Run valgrind on the binary, sending it a "\n" so that
  180. # a test which uses STDIN will not wait for user input.
  181. printf "\n" | (valgrind \
  182. --leak-check=full --show-leak-kinds=all \
  183. --gen-suppressions=all \
  184. --trace-children=yes \
  185. --suppressions="${valgrind_suppressions}" \
  186. --log-file="${_val_ensure_thisl}" \
  187. "${_val_ensure_potential_memleaks_binary}" \
  188. "${_val_ensure_testname}") \
  189. > /dev/null
  190. # Append name to suppressions file
  191. printf "# %s\n" "${_val_ensure_testname}" \
  192. >> "${valgrind_suppressions}"
  193. # Strip out useless parts from the log file, and allow the
  194. # suppressions to apply to other binaries.
  195. _val_generalize "${_val_ensure_thisl}"
  196. done < "${_val_ensure_names}"
  197. # Clean up
  198. rm -f "${_val_ensure_log}"
  199. printf "done.\n" 1>&2
  200. }
  201. ## valgrind_setup (str):
  202. # Set up the valgrind command if ${USE_VALGRIND} is greater than or equal to
  203. # ${valgrind_min}. If ${str} is not blank, include it in the log filename.
  204. valgrind_setup() {
  205. _valgrind_setup_str=${1:-}
  206. # Bail if we don't want to use valgrind for this check.
  207. if [ "${USE_VALGRIND}" -lt "${c_valgrind_min}" ]; then
  208. return
  209. fi
  210. # Set up the log filename.
  211. if [ -n "${_valgrind_setup_str}" ]; then
  212. _valgrind_setup_logfilename="${s_val_basename}-${c_count_str}-${_valgrind_setup_str}-%p.log"
  213. else
  214. _valgrind_setup_logfilename="${s_val_basename}-${c_count_str}-%p.log"
  215. fi
  216. # Set up valgrind command.
  217. _valgrind_setup_cmd="valgrind \
  218. --log-file=${_valgrind_setup_logfilename} \
  219. --track-fds=yes \
  220. --trace-children=yes \
  221. --leak-check=full --show-leak-kinds=all \
  222. --errors-for-leak-kinds=all \
  223. --suppressions=${valgrind_suppressions}"
  224. echo "${_valgrind_setup_cmd}"
  225. }
  226. ## valgrind_incomplete:
  227. # Return 0 if at least one valgrind log file is not complete.
  228. valgrind_incomplete() {
  229. # The exit code of `grep -L` is undesirable: if at least one file
  230. # contains the pattern, it returns 0. To detect if at least one file
  231. # does *not* contain the pattern, we need to check grep's output,
  232. # rather than the exit code.
  233. _valgrind_incomplete_logfiles=$(grep -L "ERROR SUMMARY" \
  234. "${out_valgrind}"/*.log)
  235. test -n "${_valgrind_incomplete_logfiles}"
  236. }
  237. ## _val_getbase (exitfile):
  238. # Return the filename without ".log" of the valgrind logfile corresponding to
  239. # ${exitfile}.
  240. _val_getbase() {
  241. _val_getbase_exitfile=$1
  242. _val_getbase_basename=$(basename "${_val_getbase_exitfile}" ".exit")
  243. echo "${out_valgrind}/${_val_getbase_basename}"
  244. }
  245. ## _val_checkl(logfile)
  246. # Check for any (unsuppressed) memory leaks recorded in a valgrind logfile.
  247. # Echo the filename if there's a leak; otherwise, echo nothing.
  248. _val_checkl() {
  249. _val_checkl_logfile=$1
  250. # Bytes in use at exit.
  251. _val_checkl_in_use=$(grep "in use at exit:" "${_val_checkl_logfile}" | awk '{print $6}')
  252. # Sanity check.
  253. if [ "$(echo "${_val_checkl_in_use}" | wc -w)" -ne "1" ]; then
  254. echo "Programmer error: invalid number valgrind outputs" 1>&2
  255. exit 1
  256. fi
  257. # Check for any leaks. Use string comparison, because valgrind formats
  258. # the number with commas, and sh can't convert strings like "1,000"
  259. # into an integer.
  260. if [ "${_val_checkl_in_use}" != "0" ] ; then
  261. # Check if all of the leaked bytes are suppressed. The extra
  262. # whitespace in " suppressed" is necessary to distinguish
  263. # between two instances of "suppressed" in the log file. Use
  264. # string comparison due to the format of the number.
  265. _val_checkl_suppressed=$(grep " suppressed:" "${_val_checkl_logfile}" | \
  266. awk '{print $3}')
  267. if [ "${_val_checkl_in_use}" != "${_val_checkl_suppressed}" ]; then
  268. # There is an unsuppressed leak.
  269. echo "${_val_checkl_logfile}"
  270. return
  271. fi
  272. fi
  273. # Check for the wrong number of open fds. On a normal desktop
  274. # computer, we expect 4: std{in,out,err}, plus the valgrind logfile.
  275. # If this is running inside a virtualized OS or container or shared
  276. # CI setup (such as Travis-CI), there might be other open
  277. # descriptors. The important thing is that the number of fds should
  278. # match the simple test case (executing potential_memleaks without
  279. # running any actual tests).
  280. _val_checkl_fds_in_use=$(grep "FILE DESCRIPTORS" "${_val_checkl_logfile}" | awk '{print $4}')
  281. _val_checkl_valgrind_fds=$(grep "FILE DESCRIPTORS" "${valgrind_fds_log}" | \
  282. awk '{print $4}')
  283. if [ "${_val_checkl_fds_in_use}" != "${_val_checkl_valgrind_fds}" ] ; then
  284. # There is an unsuppressed leak.
  285. echo "${_val_checkl_logfile}"
  286. return
  287. fi
  288. # Check the error summary.
  289. _val_checkl_num_errors=$(grep "ERROR SUMMARY: " "${_val_checkl_logfile}" | awk '{print $4}')
  290. if [ "${_val_checkl_num_errors}" -gt 0 ]; then
  291. # There was some other error(s) -- invalid read or write,
  292. # conditional jump based on uninitialized value(s), invalid
  293. # free, etc.
  294. echo "${_val_checkl_logfile}"
  295. return
  296. fi
  297. }
  298. ## _get_pids (logfiles):
  299. # Extract a list of pids in the format %08d from ${logfiles}.
  300. _get_pids() {
  301. _get_pids_logfiles=$1
  302. _get_pids_pids=""
  303. for _get_pids_logfile in ${_valgrind_check_logfiles} ; do
  304. # Get the pid.
  305. _get_pids_pid=$(printf "%s" "${_get_pids_logfile%%.log}" | \
  306. rev | cut -d "-" -f 1 | rev)
  307. # Zero-pad it and add it to the new list.
  308. _get_pids_pids=$(printf "%s %08d" \
  309. "${_get_pids_pids}" "${_get_pids_pid}")
  310. done
  311. echo "${_get_pids_pids}"
  312. }
  313. ## _is_parent (logfile, pids):
  314. # If the parent pid of ${logfile} is in ${pids}, return 0; otherwise, return 1.
  315. _is_parent () {
  316. _is_parent_logfile=$1
  317. _is_parent_pids=$2
  318. # Get the parent pid from the valgrind logfile
  319. ppid=$(grep "Parent PID:" "${_is_parent_logfile}" | \
  320. awk '{ print $4 }')
  321. ppid=$(printf "%08d" "${ppid}")
  322. # If the parent is in the list of pids, this isn't the parent process.
  323. if [ "${_is_parent_pids#*"${ppid}"}" != "${_is_parent_pids}" ] ; then
  324. return 1
  325. fi
  326. # Yes, this is the parent process.
  327. return 0
  328. }
  329. ## valgrind_check (exitfile):
  330. # Check for any memory leaks recorded in valgrind logfiles associated with a
  331. # test exitfile. Return the filename if there's a leak; otherwise return an
  332. # empty string.
  333. valgrind_check() {
  334. _valgrind_check_exitfile="$1"
  335. _valgrind_check_basename=$(_val_getbase "$1")
  336. # Get list of files to check. (Yes, the star goes outside the quotes.)
  337. _valgrind_check_logfiles=$(ls "${_valgrind_check_basename}"* 2>/dev/null)
  338. _valgrind_check_num=$(echo "${_valgrind_check_logfiles}" | wc -w)
  339. # Bail if we don't have any valgrind logfiles to check.
  340. # Use numeric comparison, because wc leaves a tab in the output.
  341. if [ "${_valgrind_check_num}" -eq "0" ] ; then
  342. return
  343. fi
  344. # Check a single file.
  345. if [ "${_valgrind_check_num}" -eq "1" ]; then
  346. _val_checkl "${_valgrind_check_logfiles}"
  347. return
  348. fi
  349. # Get a normalized list of pids.
  350. _valgrind_check_pids=$(_get_pids "${_valgrind_check_logfiles}")
  351. # If the valgrind logfiles contain "-valgrind-parent-", then we only
  352. # want to check the parent. The parent is the logfile whose "parent
  353. # pid" is not in the list of pids. (If one logfile contains
  354. # "-valgrind-parent-" then all of them should have it, so we can
  355. # simply check if that string occurs in the list of logfiles.)
  356. if [ "${_valgrind_check_logfiles#*-valgrind-parent-}" != \
  357. "${_valgrind_check_logfiles}" ]; then
  358. _valgrind_check_parent=1
  359. else
  360. _valgrind_check_parent=0
  361. fi
  362. # Check the logfiles depending on whether it's the parent or not,
  363. # and whether we want to check the parent or children.
  364. for _valgrind_check_logfile in ${_valgrind_check_logfiles} ; do
  365. if _is_parent "${_valgrind_check_logfile}" \
  366. "${_valgrind_check_pids}" ; then
  367. # This is the parent.
  368. if [ "${_valgrind_check_parent}" -eq 1 ] ; then
  369. _val_checkl "${_valgrind_check_logfile}"
  370. # Bail if there's a problem.
  371. if [ "$?" -ne 0 ]; then
  372. return
  373. fi
  374. fi
  375. else
  376. # This is a child.
  377. if [ "${_valgrind_check_parent}" -eq 0 ] ; then
  378. _val_checkl "${_valgrind_check_logfile}"
  379. # Bail if there's a problem.
  380. if [ "$?" -ne 0 ]; then
  381. return
  382. fi
  383. fi
  384. fi
  385. done
  386. }
  387. ## valgrind_init():
  388. # Clear previous valgrind output, and prepare for running valgrind tests
  389. # (if applicable).
  390. valgrind_init() {
  391. # Set up global variables.
  392. valgrind_suppressions="${out_valgrind}/suppressions"
  393. valgrind_fds_log="${out_valgrind}/fds.log"
  394. # If we want valgrind, check that the version is high enough.
  395. _val_checkver
  396. # Remove any previous directory, and create a new one.
  397. _val_prepdir
  398. # Generate valgrind suppression file if it is required. Must be
  399. # done after preparing the directory.
  400. _val_ensure "${bindir}/tests/valgrind/potential-memleaks"
  401. }