runCoverage.sh 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. #!/bin/bash
  2. set -euo pipefail
  3. # Fuzzer 入口脚本(Linux)。
  4. # 脚本路径:scripts/ci/runCoverage.sh
  5. # 执行模式:FUZZ_MODE=quick|nightly|full
  6. # quick/nightly/full:仅决定默认 workers/jobs(2/2、4/4、6/6)
  7. # 常用参数:
  8. # FUZZ_SKIP_COV=0|1:是否跳过覆盖率
  9. # FUZZ_RUNS=<N>:固定迭代次数;默认 100000
  10. # FUZZ_MAX_TOTAL_TIME=<秒>:显式总时间预算;仅在未设置 FUZZ_RUNS 时生效
  11. # FUZZ_WORKERS / FUZZ_JOBS:并行 worker/job 数
  12. # FUZZ_TIMEOUT / FUZZ_MAX_LEN / FUZZ_VERBOSITY:透传给 libFuzzer
  13. # FUZZ_RSS_LIMIT_MB / FUZZ_MALLOC_LIMIT_MB:内存预算(透传给 libFuzzer)
  14. # FUZZ_CORPUS_DIR / FUZZ_DICT_PATH:corpus 与字典路径
  15. # FUZZ_ENABLE_MERGE=0|1:是否先将输入 corpus merge 到临时目录,再回写原 corpus 目录
  16. # FUZZ_EXTRA_ARGS:附加 libFuzzer 参数(空格分割)
  17. # XMAKE_FORCE_CLEAN=0|1:是否在配置前先清理
  18. scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  19. # shellcheck source=../lib/common.sh
  20. source "${scriptDir}/../lib/common.sh"
  21. # shellcheck source=../lib/semantic_matrix.sh
  22. source "${scriptDir}/../lib/semantic_matrix.sh"
  23. repoRoot="$(ryanjson_repo_root_from_source "${BASH_SOURCE[0]}" 2)"
  24. cd "${repoRoot}"
  25. fuzzMode="${FUZZ_MODE:-quick}"
  26. fuzzSkipCov="${FUZZ_SKIP_COV:-0}"
  27. fuzzTimeout="${FUZZ_TIMEOUT:-4}"
  28. fuzzMaxLen="${FUZZ_MAX_LEN:-8192}"
  29. fuzzVerbosity="${FUZZ_VERBOSITY:-0}"
  30. fuzzCorpusDir="${FUZZ_CORPUS_DIR:-./test/fuzzer/corpus}"
  31. fuzzDictPath="${FUZZ_DICT_PATH:-./test/fuzzer/RyanJsonFuzzer.dict}"
  32. fuzzRuns="${FUZZ_RUNS:-}"
  33. fuzzMaxTotalTime="${FUZZ_MAX_TOTAL_TIME:-}"
  34. fuzzWorkers="${FUZZ_WORKERS:-}"
  35. fuzzJobs="${FUZZ_JOBS:-}"
  36. fuzzRssLimitMb="${FUZZ_RSS_LIMIT_MB:-}"
  37. fuzzMallocLimitMb="${FUZZ_MALLOC_LIMIT_MB:-}"
  38. fuzzEnableMerge="${FUZZ_ENABLE_MERGE:-0}"
  39. xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}"
  40. defaultRuns=100000
  41. ryanjson_require_cmd xmake
  42. case "${fuzzMode}" in
  43. quick)
  44. defaultWorkers=2
  45. defaultJobs=2
  46. ;;
  47. nightly)
  48. defaultWorkers=4
  49. defaultJobs=4
  50. ;;
  51. full)
  52. defaultWorkers=6
  53. defaultJobs=6
  54. ;;
  55. *)
  56. echo "[错误] FUZZ_MODE 仅支持 quick/nightly/full,当前值:${fuzzMode}"
  57. exit 1
  58. ;;
  59. esac
  60. case "${fuzzEnableMerge}" in
  61. 0|1)
  62. ;;
  63. *)
  64. echo "[错误] FUZZ_ENABLE_MERGE 仅支持 0/1,当前值:${fuzzEnableMerge}"
  65. exit 1
  66. ;;
  67. esac
  68. if [[ -z "${fuzzWorkers}" ]]; then
  69. fuzzWorkers="${defaultWorkers}"
  70. fi
  71. if [[ -z "${fuzzJobs}" ]]; then
  72. fuzzJobs="${defaultJobs}"
  73. fi
  74. if [[ -z "${fuzzRuns}" && -z "${fuzzMaxTotalTime}" ]]; then
  75. fuzzRuns="${defaultRuns}"
  76. fi
  77. strictKey="${RYANJSON_STRICT_OBJECT_KEY_CHECK:-false}"
  78. addAtHead="${RYANJSON_DEFAULT_ADD_AT_HEAD:-true}"
  79. scientific="${RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC:-true}"
  80. caseName="$(ryanjson_semantic_case_name "${strictKey}" "${addAtHead}" "${scientific}")"
  81. coverageRoot="test/fuzzer/coverage"
  82. profileRoot="${coverageRoot}/profiles"
  83. ryanjson_prepare_clean_dir "${coverageRoot}"
  84. mkdir -p "${profileRoot}"
  85. profraw="${profileRoot}/coverage.profraw"
  86. profdata="${coverageRoot}/coverage.profdata"
  87. reportTxt="${coverageRoot}/report.txt"
  88. reportHtml="${coverageRoot}/html"
  89. ryanjson_print_banner_begin "Fuzzer 执行配置"
  90. ryanjson_print_banner_kv "模式" "${fuzzMode}"
  91. ryanjson_print_banner_kv "配置" "${caseName}"
  92. ryanjson_print_banner_kv "Corpus" "${fuzzCorpusDir}"
  93. ryanjson_print_banner_kv "字典" "${fuzzDictPath}"
  94. ryanjson_print_banner_kv "merge" "${fuzzEnableMerge}"
  95. if [[ "${fuzzEnableMerge}" == "1" ]]; then
  96. ryanjson_print_banner_kv "merged_corpus" "${fuzzCorpusDir} (in-place)"
  97. fi
  98. ryanjson_print_banner_kv "timeout" "${fuzzTimeout}"
  99. ryanjson_print_banner_kv "max_len" "${fuzzMaxLen}"
  100. ryanjson_print_banner_kv "workers/jobs" "${fuzzWorkers}/${fuzzJobs}"
  101. if [[ -n "${fuzzRssLimitMb}" ]]; then
  102. ryanjson_print_banner_kv "rss_limit_mb" "${fuzzRssLimitMb}"
  103. fi
  104. if [[ -n "${fuzzMallocLimitMb}" ]]; then
  105. ryanjson_print_banner_kv "malloc_limit_mb" "${fuzzMallocLimitMb}"
  106. fi
  107. if [[ -n "${fuzzRuns}" ]]; then
  108. ryanjson_print_banner_kv "runs" "${fuzzRuns}"
  109. else
  110. ryanjson_print_banner_kv "max_total_time" "${fuzzMaxTotalTime}s"
  111. fi
  112. ryanjson_print_banner_end
  113. ryanjson_run_xmake_config "${xmakeForceClean}" "${caseName}"
  114. ryanjson_run_xmake_build "RyanJsonFuzz" "${caseName}"
  115. echo "[信息] xmake 构建完成(target=RyanJsonFuzz)"
  116. if [[ ! -d "${fuzzCorpusDir}" ]]; then
  117. mkdir -p "${fuzzCorpusDir}"
  118. echo "[信息] Corpus 目录不存在,已自动创建:${fuzzCorpusDir}"
  119. fi
  120. actualFuzzCorpusDir="${fuzzCorpusDir}"
  121. if [[ "${fuzzEnableMerge}" == "1" ]]; then
  122. mergeTempDir="${coverageRoot}/merged-corpus.tmp"
  123. ryanjson_prepare_clean_dir "${mergeTempDir}"
  124. echo "[阶段] 正在 merge corpus..."
  125. ./build/linux/x86/release/RyanJsonFuzz -merge=1 "${mergeTempDir}" "${fuzzCorpusDir}"
  126. rm -rf "${fuzzCorpusDir}"
  127. mv "${mergeTempDir}" "${fuzzCorpusDir}"
  128. actualFuzzCorpusDir="${fuzzCorpusDir}"
  129. echo "[信息] merge 完成,后续 fuzz 使用:${actualFuzzCorpusDir}"
  130. fi
  131. declare -a dictArgs=()
  132. if [[ -f "${fuzzDictPath}" ]]; then
  133. dictArgs+=("-dict=${fuzzDictPath}")
  134. else
  135. echo "[警告] 未找到字典文件 ${fuzzDictPath},已跳过 -dict 参数。"
  136. fi
  137. declare -a fuzzArgs=()
  138. fuzzArgs+=("${actualFuzzCorpusDir}")
  139. fuzzArgs+=("${dictArgs[@]}")
  140. fuzzArgs+=("-timeout=${fuzzTimeout}")
  141. fuzzArgs+=("-verbosity=${fuzzVerbosity}")
  142. fuzzArgs+=("-max_len=${fuzzMaxLen}")
  143. fuzzArgs+=("-workers=${fuzzWorkers}")
  144. fuzzArgs+=("-jobs=${fuzzJobs}")
  145. if [[ -n "${fuzzRssLimitMb}" ]]; then
  146. fuzzArgs+=("-rss_limit_mb=${fuzzRssLimitMb}")
  147. fi
  148. if [[ -n "${fuzzMallocLimitMb}" ]]; then
  149. fuzzArgs+=("-malloc_limit_mb=${fuzzMallocLimitMb}")
  150. fi
  151. if [[ -n "${fuzzRuns}" ]]; then
  152. fuzzArgs+=("-runs=${fuzzRuns}")
  153. else
  154. fuzzArgs+=("-max_total_time=${fuzzMaxTotalTime}")
  155. fi
  156. if [[ -n "${FUZZ_EXTRA_ARGS:-}" ]]; then
  157. read -r -a extraArgs <<< "${FUZZ_EXTRA_ARGS}"
  158. fuzzArgs+=("${extraArgs[@]}")
  159. fi
  160. echo "[阶段] 正在运行 fuzz 二进制..."
  161. LLVM_PROFILE_FILE="${profraw}" ./build/linux/x86/release/RyanJsonFuzz "${fuzzArgs[@]}"
  162. if [[ "${fuzzSkipCov}" == "1" ]]; then
  163. echo "[信息] FUZZ_SKIP_COV=1,已跳过覆盖率生成。"
  164. echo "输出目录:${coverageRoot}"
  165. exit 0
  166. fi
  167. if ! command -v llvm-profdata >/dev/null 2>&1; then
  168. echo "[错误] 未找到 llvm-profdata,无法生成覆盖率。"
  169. exit 1
  170. fi
  171. if ! command -v llvm-cov >/dev/null 2>&1; then
  172. echo "[错误] 未找到 llvm-cov,无法生成覆盖率。"
  173. exit 1
  174. fi
  175. llvm-profdata merge -sparse "${profraw}" -o "${profdata}"
  176. echo "---------------- 覆盖率摘要(llvm-cov report) ----------------"
  177. llvm-cov report ./build/linux/x86/release/RyanJsonFuzz \
  178. -instr-profile="${profdata}" \
  179. -show-mcdc-summary \
  180. --use-color \
  181. -sources ./RyanJson
  182. llvm-cov report ./build/linux/x86/release/RyanJsonFuzz \
  183. -instr-profile="${profdata}" \
  184. -show-mcdc-summary \
  185. -sources ./RyanJson > "${reportTxt}"
  186. llvm-cov show ./build/linux/x86/release/RyanJsonFuzz \
  187. -instr-profile="${profdata}" \
  188. -format=html \
  189. -output-dir="${reportHtml}" \
  190. -show-mcdc-summary \
  191. -show-branches=count \
  192. -show-expansions \
  193. -show-regions \
  194. -show-line-counts-or-regions \
  195. -sources ./RyanJson
  196. echo "[完成] Fuzzer 执行结束,覆盖率已生成。"
  197. echo "输出目录:${coverageRoot}"
  198. echo "覆盖率文本报告:${reportTxt}"
  199. echo "覆盖率HTML目录:${reportHtml}"