pvscheck.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. #!/bin/sh
  2. # Assume that "local" is available.
  3. # shellcheck disable=SC2039
  4. set -e
  5. # Note: -u causes problems with posh, it barks at “undefined” $@ when no
  6. # arguments provided.
  7. test -z "$POSH_VERSION" && set -u
  8. get_jobs_num() {
  9. if [ -n "${TRAVIS:-}" ] ; then
  10. # HACK: /proc/cpuinfo on Travis CI is misleading, so hardcode 1.
  11. echo 1
  12. else
  13. echo $(( $(grep -c "^processor" /proc/cpuinfo) + 1 ))
  14. fi
  15. }
  16. help() {
  17. echo 'Usage:'
  18. echo ' pvscheck.sh [--pvs URL] [--deps] [--environment-cc]'
  19. echo ' [target-directory [branch]]'
  20. echo ' pvscheck.sh [--pvs URL] [--recheck] [--environment-cc] [--update]'
  21. echo ' [target-directory]'
  22. echo ' pvscheck.sh [--pvs URL] --only-analyse [target-directory]'
  23. echo ' pvscheck.sh [--pvs URL] --pvs-install {target-directory}'
  24. echo ' pvscheck.sh --patch [--only-build]'
  25. echo
  26. echo ' --pvs: Fetch pvs-studio from URL.'
  27. echo
  28. echo ' --pvs detect: Auto-detect latest version (by scraping viva64.com).'
  29. echo
  30. echo ' --deps: (for regular run) Use top-level Makefile and build deps.'
  31. echo ' Without this it assumes all dependencies are already'
  32. echo ' installed.'
  33. echo
  34. echo ' --environment-cc: (for regular run and --recheck) Do not export'
  35. echo ' CC=clang. Build is still run with CFLAGS=-O0.'
  36. echo
  37. echo ' --only-build: (for --patch) Only patch files in ./build directory.'
  38. echo
  39. echo ' --pvs-install: Only install PVS-studio to the specified location.'
  40. echo
  41. echo ' --patch: patch sources in the current directory.'
  42. echo ' Does not patch already patched files.'
  43. echo ' Does not run analysis.'
  44. echo
  45. echo ' --recheck: run analysis on a prepared target directory.'
  46. echo
  47. echo ' --update: when rechecking first do a pull.'
  48. echo
  49. echo ' --only-analyse: run analysis on a prepared target directory '
  50. echo ' without building Neovim.'
  51. echo
  52. echo ' target-directory: Directory where build should occur.'
  53. echo ' Default: ../neovim-pvs'
  54. echo
  55. echo ' branch: Branch to check.'
  56. echo ' Default: master.'
  57. }
  58. getopts_error() {
  59. local msg="$1" ; shift
  60. local do_help=
  61. if test "$msg" = "--help" ; then
  62. msg="$1" ; shift
  63. do_help=1
  64. fi
  65. printf '%s\n' "$msg" >&2
  66. if test -n "$do_help" ; then
  67. printf '\n' >&2
  68. help >&2
  69. fi
  70. echo 'return 1'
  71. return 1
  72. }
  73. # Usage `eval "$(getopts_long long_defs -- positionals_defs -- "$@")"`
  74. #
  75. # long_defs: list of pairs of arguments like `longopt action`.
  76. # positionals_defs: list of arguments like `action`.
  77. #
  78. # `action` is a space-separated commands:
  79. #
  80. # store_const [const] [varname] [default]
  81. # Store constant [const] (default 1) (note: eval’ed) if argument is present
  82. # (long options only). Assumes long option accepts no arguments.
  83. # store [varname] [default]
  84. # Store value. Assumes long option needs an argument.
  85. # run {func} [varname] [default]
  86. # Run function {func} and store its output to the [varname]. Assumes no
  87. # arguments accepted (long options only).
  88. # modify {func} [varname] [default]
  89. # Like run, but assumes a single argument, passed to function {func} as $1.
  90. #
  91. # All actions stores empty value if neither [varname] nor [default] are
  92. # present. [default] is evaled by top-level `eval`, so be careful. Also note
  93. # that no arguments may contain spaces, including [default] and [const].
  94. getopts_long() {
  95. local positional=
  96. local opt_bases=""
  97. while test $# -gt 0 ; do
  98. local arg="$1" ; shift
  99. local opt_base=
  100. local act=
  101. local opt_name=
  102. if test -z "$positional" ; then
  103. if test "$arg" = "--" ; then
  104. positional=0
  105. continue
  106. fi
  107. act="$1" ; shift
  108. opt_name="$(echo "$arg" | tr '-' '_')"
  109. opt_base="longopt_$opt_name"
  110. else
  111. if test "$arg" = "--" ; then
  112. break
  113. fi
  114. : $(( positional+=1 ))
  115. act="$arg"
  116. opt_name="arg_$positional"
  117. opt_base="positional_$positional"
  118. fi
  119. opt_bases="$opt_bases $opt_base"
  120. eval "local varname_$opt_base=$opt_name"
  121. local i=0
  122. for act_subarg in $act ; do
  123. eval "local act_$(( i+=1 ))_$opt_base=\"\$act_subarg\""
  124. done
  125. done
  126. # Process options
  127. local positional=0
  128. local force_positional=
  129. while test $# -gt 0 ; do
  130. local argument="$1" ; shift
  131. local opt_base=
  132. local has_equal=
  133. local equal_arg=
  134. local is_positional=
  135. if test "$argument" = "--" ; then
  136. force_positional=1
  137. continue
  138. elif test -z "$force_positional" && test "${argument#--}" != "$argument"
  139. then
  140. local opt_name="${argument#--}"
  141. local opt_name_striparg="${opt_name%%=*}"
  142. if test "$opt_name" = "$opt_name_striparg" ; then
  143. has_equal=0
  144. else
  145. has_equal=1
  146. equal_arg="${argument#*=}"
  147. opt_name="$opt_name_striparg"
  148. fi
  149. # Use trailing x to prevent stripping newlines
  150. opt_name="$(printf '%sx' "$opt_name" | tr '-' '_')"
  151. opt_name="${opt_name%x}"
  152. if test -n "$(printf '%sx' "$opt_name" | tr -d 'a-z_')" ; then
  153. getopts_error "Option contains invalid characters: $opt_name"
  154. fi
  155. opt_base="longopt_$opt_name"
  156. else
  157. : $(( positional+=1 ))
  158. opt_base="positional_$positional"
  159. is_positional=1
  160. fi
  161. if test -n "$opt_base" ; then
  162. eval "local occurred_$opt_base=1"
  163. eval "local act_1=\"\${act_1_$opt_base:-}\""
  164. eval "local varname=\"\${varname_$opt_base:-}\""
  165. local need_val=
  166. local func=
  167. case "$act_1" in
  168. (store_const)
  169. eval "local const=\"\${act_2_${opt_base}:-1}\""
  170. eval "local varname=\"\${act_3_${opt_base}:-$varname}\""
  171. printf 'local %s=%s\n' "$varname" "$const"
  172. ;;
  173. (store)
  174. eval "varname=\"\${act_2_${opt_base}:-$varname}\""
  175. need_val=1
  176. ;;
  177. (run)
  178. eval "func=\"\${act_2_${opt_base}}\""
  179. eval "varname=\"\${act_3_${opt_base}:-$varname}\""
  180. printf 'local %s="$(%s)"\n' "$varname" "$func"
  181. ;;
  182. (modify)
  183. eval "func=\"\${act_2_${opt_base}}\""
  184. eval "varname=\"\${act_3_${opt_base}:-$varname}\""
  185. need_val=1
  186. ;;
  187. ("")
  188. getopts_error --help "Wrong argument: $argument"
  189. ;;
  190. esac
  191. if test -n "$need_val" ; then
  192. local val=
  193. if test -z "$is_positional" ; then
  194. if test $has_equal = 1 ; then
  195. val="$equal_arg"
  196. else
  197. if test $# -eq 0 ; then
  198. getopts_error "Missing argument for $opt_name"
  199. fi
  200. val="$1" ; shift
  201. fi
  202. else
  203. val="$argument"
  204. fi
  205. local escaped_val="'$(printf "%s" "$val" | sed "s/'/'\\\\''/g")'"
  206. case "$act_1" in
  207. (store)
  208. printf 'local %s=%s\n' "$varname" "$escaped_val"
  209. ;;
  210. (modify)
  211. printf 'local %s="$(%s %s)"\n' "$varname" "$func" "$escaped_val"
  212. ;;
  213. esac
  214. fi
  215. fi
  216. done
  217. # Print default values when no values were provided
  218. local opt_base=
  219. for opt_base in $opt_bases ; do
  220. eval "local occurred=\"\${occurred_$opt_base:-}\""
  221. if test -n "$occurred" ; then
  222. continue
  223. fi
  224. eval "local act_1=\"\$act_1_$opt_base\""
  225. eval "local varname=\"\$varname_$opt_base\""
  226. case "$act_1" in
  227. (store)
  228. eval "local varname=\"\${act_2_${opt_base}:-$varname}\""
  229. eval "local default=\"\${act_3_${opt_base}:-}\""
  230. printf 'local %s=%s\n' "$varname" "$default"
  231. ;;
  232. (store_const|run|modify)
  233. eval "local varname=\"\${act_3_${opt_base}:-$varname}\""
  234. eval "local default=\"\${act_4_${opt_base}:-}\""
  235. printf 'local %s=%s\n' "$varname" "$default"
  236. ;;
  237. esac
  238. done
  239. }
  240. get_pvs_comment() {
  241. local tgt="$1" ; shift
  242. cat > "$tgt/pvs-comment" << EOF
  243. // This is an open source non-commercial project. Dear PVS-Studio, please check
  244. // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
  245. EOF
  246. }
  247. install_pvs() {(
  248. local tgt="$1" ; shift
  249. local pvs_url="$1" ; shift
  250. cd "$tgt"
  251. mkdir pvs-studio
  252. cd pvs-studio
  253. curl -L -o pvs-studio.tar.gz "$pvs_url"
  254. tar xzf pvs-studio.tar.gz
  255. rm pvs-studio.tar.gz
  256. local pvsdir="$(find . -maxdepth 1 -mindepth 1)"
  257. find "$pvsdir" -maxdepth 1 -mindepth 1 -exec mv '{}' . \;
  258. rmdir "$pvsdir"
  259. )}
  260. create_compile_commands() {(
  261. local tgt="$1" ; shift
  262. local deps="$1" ; shift
  263. local environment_cc="$1" ; shift
  264. if test -z "$environment_cc" ; then
  265. export CC=clang
  266. fi
  267. export CFLAGS=' -O0 '
  268. if test -z "$deps" ; then
  269. mkdir -p "$tgt/build"
  270. (
  271. cd "$tgt/build"
  272. cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="$PWD/root"
  273. make -j"$(get_jobs_num)"
  274. )
  275. else
  276. (
  277. cd "$tgt"
  278. make -j"$(get_jobs_num)" CMAKE_EXTRA_FLAGS=" -DCMAKE_INSTALL_PREFIX=$PWD/root -DCMAKE_BUILD_TYPE=Debug "
  279. )
  280. fi
  281. find "$tgt/build/src/nvim/auto" -name '*.test-include.c' -delete
  282. )}
  283. # Warning: realdir below only cares about directories unlike realpath.
  284. #
  285. # realpath is not available in Ubuntu trusty yet.
  286. realdir() {(
  287. local dir="$1"
  288. local add=""
  289. while ! cd "$dir" 2>/dev/null ; do
  290. add="${dir##*/}/$add"
  291. local new_dir="${dir%/*}"
  292. if test "$new_dir" = "$dir" ; then
  293. return 1
  294. fi
  295. dir="$new_dir"
  296. done
  297. printf '%s\n' "$PWD/$add"
  298. )}
  299. patch_sources() {(
  300. local tgt="$1" ; shift
  301. local only_bulid="${1}" ; shift
  302. get_pvs_comment "$tgt"
  303. local sh_script='
  304. pvs_comment="$(cat pvs-comment ; echo -n EOS)"
  305. filehead="$(head -c $(( ${#pvs_comment} - 3 )) "$1" ; echo -n EOS)"
  306. if test "x$filehead" != "x$pvs_comment" ; then
  307. cat pvs-comment "$1" > "$1.tmp"
  308. mv "$1.tmp" "$1"
  309. fi
  310. '
  311. cd "$tgt"
  312. if test "$only_build" != "--only-build" ; then
  313. find \
  314. src/nvim test/functional/fixtures test/unit/fixtures \
  315. -name '*.c' \
  316. -exec /bin/sh -c "$sh_script" - '{}' \;
  317. fi
  318. find \
  319. build/src/nvim/auto build/config \
  320. -name '*.c' -not -name '*.test-include.c' \
  321. -exec /bin/sh -c "$sh_script" - '{}' \;
  322. rm pvs-comment
  323. )}
  324. run_analysis() {(
  325. local tgt="$1" ; shift
  326. cd "$tgt"
  327. # pvs-studio-analyzer exits with a non-zero exit code when there are detected
  328. # errors, so ignore its return
  329. pvs-studio-analyzer \
  330. analyze \
  331. --threads "$(get_jobs_num)" \
  332. --output-file PVS-studio.log \
  333. --verbose \
  334. --file build/compile_commands.json \
  335. --sourcetree-root . || true
  336. rm -rf PVS-studio.{xml,err,tsk,html.d}
  337. local plog_args="PVS-studio.log --srcRoot . --excludedCodes V011"
  338. plog-converter $plog_args --renderTypes xml --output PVS-studio.xml
  339. plog-converter $plog_args --renderTypes errorfile --output PVS-studio.err
  340. plog-converter $plog_args --renderTypes tasklist --output PVS-studio.tsk
  341. plog-converter $plog_args --renderTypes fullhtml --output PVS-studio.html.d
  342. )}
  343. detect_url() {
  344. local url="${1:-detect}"
  345. if test "$url" = detect ; then
  346. curl --silent -L 'https://www.viva64.com/en/pvs-studio-download-linux/' \
  347. | grep -o 'https\{0,1\}://[^"<>]\{1,\}/pvs-studio[^/"<>]*\.tgz' \
  348. || echo FAILED
  349. else
  350. printf '%s' "$url"
  351. fi
  352. }
  353. do_check() {
  354. local tgt="$1" ; shift
  355. local branch="$1" ; shift
  356. local pvs_url="$1" ; shift
  357. local deps="$1" ; shift
  358. local environment_cc="$1" ; shift
  359. if test -z "$pvs_url" || test "$pvs_url" = FAILED ; then
  360. pvs_url="$(detect_url detect)"
  361. if test -z "$pvs_url" || test "$pvs_url" = FAILED ; then
  362. echo "failed to auto-detect PVS URL"
  363. exit 1
  364. fi
  365. echo "Auto-detected PVS URL: ${pvs_url}"
  366. fi
  367. git clone --branch="$branch" . "$tgt"
  368. install_pvs "$tgt" "$pvs_url"
  369. do_recheck "$tgt" "$deps" "$environment_cc" ""
  370. }
  371. do_recheck() {
  372. local tgt="$1" ; shift
  373. local deps="$1" ; shift
  374. local environment_cc="$1" ; shift
  375. local update="$1" ; shift
  376. if test -n "$update" ; then
  377. (
  378. cd "$tgt"
  379. local branch="$(git rev-parse --abbrev-ref HEAD)"
  380. git checkout --detach
  381. git fetch -f origin "${branch}:${branch}"
  382. git checkout -f "$branch"
  383. )
  384. fi
  385. create_compile_commands "$tgt" "$deps" "$environment_cc"
  386. do_analysis "$tgt"
  387. }
  388. do_analysis() {
  389. local tgt="$1" ; shift
  390. if test -d "$tgt/pvs-studio" ; then
  391. local saved_pwd="$PWD"
  392. cd "$tgt/pvs-studio"
  393. export PATH="$PWD/bin${PATH+:}${PATH}"
  394. cd "$saved_pwd"
  395. fi
  396. run_analysis "$tgt"
  397. }
  398. main() {
  399. eval "$(
  400. getopts_long \
  401. help store_const \
  402. pvs 'modify detect_url pvs_url' \
  403. patch store_const \
  404. only-build 'store_const --only-build' \
  405. recheck store_const \
  406. only-analyse store_const \
  407. pvs-install store_const \
  408. deps store_const \
  409. environment-cc store_const \
  410. update store_const \
  411. -- \
  412. 'modify realdir tgt "$PWD/../neovim-pvs"' \
  413. 'store branch master' \
  414. -- "$@"
  415. )"
  416. if test -n "$help" ; then
  417. help
  418. return 0
  419. fi
  420. # set -x
  421. if test -n "$patch" ; then
  422. patch_sources "$tgt" "$only_build"
  423. elif test -n "$pvs_install" ; then
  424. install_pvs "$tgt" "$pvs_url"
  425. elif test -n "$recheck" ; then
  426. do_recheck "$tgt" "$deps" "$environment_cc" "$update"
  427. elif test -n "$only_analyse" ; then
  428. do_analysis "$tgt"
  429. else
  430. do_check "$tgt" "$branch" "$pvs_url" "$deps" "$environment_cc"
  431. fi
  432. }
  433. main "$@"