shared_valgrind_functions.sh 11 KB

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