shared_valgrind_functions.sh 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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 \
  222. --show-leak-kinds=all \
  223. --errors-for-leak-kinds=all \
  224. --suppressions=${valgrind_suppressions}"
  225. echo "${_valgrind_setup_cmd}"
  226. }
  227. ## valgrind_incomplete:
  228. # Return 0 if at least one valgrind log file is not complete.
  229. valgrind_incomplete() {
  230. # The exit code of `grep -L` is undesirable: if at least one file
  231. # contains the pattern, it returns 0. To detect if at least one file
  232. # does *not* contain the pattern, we need to check grep's output,
  233. # rather than the exit code.
  234. _valgrind_incomplete_logfiles=$(grep -L "ERROR SUMMARY" \
  235. "${out_valgrind}"/*.log)
  236. test -n "${_valgrind_incomplete_logfiles}"
  237. }
  238. ## _val_getbase (exitfile):
  239. # Return the filename without ".log" of the valgrind logfile corresponding to
  240. # ${exitfile}.
  241. _val_getbase() {
  242. _val_getbase_exitfile=$1
  243. _val_getbase_basename=$(basename "${_val_getbase_exitfile}" ".exit")
  244. echo "${out_valgrind}/${_val_getbase_basename}"
  245. }
  246. ## _val_checkl(logfile)
  247. # Check for any (unsuppressed) memory leaks recorded in a valgrind logfile.
  248. # Echo the filename if there's a leak; otherwise, echo nothing.
  249. _val_checkl() {
  250. _val_checkl_logfile=$1
  251. # Bytes in use at exit.
  252. _val_checkl_in_use=$(grep "in use at exit:" "${_val_checkl_logfile}" | awk '{print $6}')
  253. # Sanity check.
  254. if [ "$(echo "${_val_checkl_in_use}" | wc -w)" -ne "1" ]; then
  255. echo "Programmer error: invalid number valgrind outputs" 1>&2
  256. exit 1
  257. fi
  258. # Check for any leaks. Use string comparison, because valgrind formats
  259. # the number with commas, and sh can't convert strings like "1,000"
  260. # into an integer.
  261. if [ "${_val_checkl_in_use}" != "0" ] ; then
  262. # Check if all of the leaked bytes are suppressed. The extra
  263. # whitespace in " suppressed" is necessary to distinguish
  264. # between two instances of "suppressed" in the log file. Use
  265. # string comparison due to the format of the number.
  266. _val_checkl_suppressed=$(grep " suppressed:" "${_val_checkl_logfile}" | \
  267. awk '{print $3}')
  268. if [ "${_val_checkl_in_use}" != "${_val_checkl_suppressed}" ]; then
  269. # There is an unsuppressed leak.
  270. echo "${_val_checkl_logfile}"
  271. return
  272. fi
  273. fi
  274. # Check for the wrong number of open fds. On a normal desktop
  275. # computer, we expect 4: std{in,out,err}, plus the valgrind logfile.
  276. # If this is running inside a virtualized OS or container or shared
  277. # CI setup (such as Travis-CI), there might be other open
  278. # descriptors. The important thing is that the number of fds should
  279. # match the simple test case (executing potential_memleaks without
  280. # running any actual tests).
  281. _val_checkl_fds_in_use=$(grep "FILE DESCRIPTORS" "${_val_checkl_logfile}" | awk '{print $4}')
  282. _val_checkl_valgrind_fds=$(grep "FILE DESCRIPTORS" "${valgrind_fds_log}" | \
  283. awk '{print $4}')
  284. if [ "${_val_checkl_fds_in_use}" != "${_val_checkl_valgrind_fds}" ] ; then
  285. # There is an unsuppressed leak.
  286. echo "${_val_checkl_logfile}"
  287. return
  288. fi
  289. # Check the error summary. Get the number of expected errors from the
  290. # ${valgrind_fds_log} file. (Ideally this would be 0, but due to
  291. # porting issues, some versions of valgrind on some platforms always
  292. # report a non-zero number of errors.)
  293. _val_checkl_num_errors=$(grep "ERROR SUMMARY: " "${_val_checkl_logfile}" | awk '{print $4}')
  294. _val_checkl_num_errors_basic=$(grep "ERROR SUMMARY: " "${valgrind_fds_log}" | awk '{ print $4}')
  295. if [ "${_val_checkl_num_errors}" != "${_val_checkl_num_errors_basic}" ]; then
  296. # There was some other error(s) -- invalid read or write,
  297. # conditional jump based on uninitialized value(s), invalid
  298. # free, etc.
  299. echo "${_val_checkl_logfile}"
  300. return
  301. fi
  302. }
  303. ## _get_pids (logfiles):
  304. # Extract a list of pids in the format %08d from ${logfiles}.
  305. _get_pids() {
  306. _get_pids_logfiles=$1
  307. _get_pids_pids=""
  308. for _get_pids_logfile in ${_valgrind_check_logfiles} ; do
  309. # Get the pid.
  310. _get_pids_pid=$(printf "%s" "${_get_pids_logfile%%.log}" | \
  311. rev | cut -d "-" -f 1 | rev)
  312. # Zero-pad it and add it to the new list.
  313. _get_pids_pids=$(printf "%s %08d" \
  314. "${_get_pids_pids}" "${_get_pids_pid}")
  315. done
  316. echo "${_get_pids_pids}"
  317. }
  318. ## _is_parent (logfile, pids):
  319. # If the parent pid of ${logfile} is in ${pids}, return 0; otherwise, return 1.
  320. _is_parent () {
  321. _is_parent_logfile=$1
  322. _is_parent_pids=$2
  323. # Get the parent pid from the valgrind logfile
  324. ppid=$(grep "Parent PID:" "${_is_parent_logfile}" | \
  325. awk '{ print $4 }')
  326. ppid=$(printf "%08d" "${ppid}")
  327. # If the parent is in the list of pids, this isn't the parent process.
  328. if [ "${_is_parent_pids#*"${ppid}"}" != "${_is_parent_pids}" ] ; then
  329. return 1
  330. fi
  331. # Yes, this is the parent process.
  332. return 0
  333. }
  334. ## valgrind_check (exitfile):
  335. # Check for any memory leaks recorded in valgrind logfiles associated with a
  336. # test exitfile. Return the filename if there's a leak; otherwise return an
  337. # empty string.
  338. valgrind_check() {
  339. _valgrind_check_exitfile="$1"
  340. _valgrind_check_basename=$(_val_getbase "$1")
  341. # Get list of files to check. (Yes, the star goes outside the quotes.)
  342. _valgrind_check_logfiles=$(ls "${_valgrind_check_basename}"* 2>/dev/null)
  343. _valgrind_check_num=$(echo "${_valgrind_check_logfiles}" | wc -w)
  344. # Bail if we don't have any valgrind logfiles to check.
  345. # Use numeric comparison, because wc leaves a tab in the output.
  346. if [ "${_valgrind_check_num}" -eq "0" ] ; then
  347. return
  348. fi
  349. # Check a single file.
  350. if [ "${_valgrind_check_num}" -eq "1" ]; then
  351. _val_checkl "${_valgrind_check_logfiles}"
  352. return
  353. fi
  354. # Get a normalized list of pids.
  355. _valgrind_check_pids=$(_get_pids "${_valgrind_check_logfiles}")
  356. # If the valgrind logfiles contain "-valgrind-parent-", then we only
  357. # want to check the parent. The parent is the logfile whose "parent
  358. # pid" is not in the list of pids. (If one logfile contains
  359. # "-valgrind-parent-" then all of them should have it, so we can
  360. # simply check if that string occurs in the list of logfiles.)
  361. if [ "${_valgrind_check_logfiles#*-valgrind-parent-}" != \
  362. "${_valgrind_check_logfiles}" ]; then
  363. _valgrind_check_parent=1
  364. else
  365. _valgrind_check_parent=0
  366. fi
  367. # Check the logfiles depending on whether it's the parent or not,
  368. # and whether we want to check the parent or children.
  369. for _valgrind_check_logfile in ${_valgrind_check_logfiles} ; do
  370. if _is_parent "${_valgrind_check_logfile}" \
  371. "${_valgrind_check_pids}" ; then
  372. # This is the parent.
  373. if [ "${_valgrind_check_parent}" -eq 1 ] ; then
  374. _val_checkl "${_valgrind_check_logfile}"
  375. # Bail if there's a problem.
  376. if [ "$?" -ne 0 ]; then
  377. return
  378. fi
  379. fi
  380. else
  381. # This is a child.
  382. if [ "${_valgrind_check_parent}" -eq 0 ] ; then
  383. _val_checkl "${_valgrind_check_logfile}"
  384. # Bail if there's a problem.
  385. if [ "$?" -ne 0 ]; then
  386. return
  387. fi
  388. fi
  389. fi
  390. done
  391. }
  392. ## valgrind_init():
  393. # Clear previous valgrind output, and prepare for running valgrind tests
  394. # (if applicable).
  395. valgrind_init() {
  396. # Set up global variables.
  397. valgrind_suppressions="${out_valgrind}/suppressions"
  398. valgrind_fds_log="${out_valgrind}/fds.log"
  399. # If we want valgrind, check that the version is high enough.
  400. _val_checkver
  401. # Remove any previous directory, and create a new one.
  402. _val_prepdir
  403. # Generate valgrind suppression file if it is required. Must be
  404. # done after preparing the directory.
  405. _val_ensure "${bindir}/tests/valgrind/potential-memleaks"
  406. }