runCoverage.sh 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. #!/bin/bash
  2. set -euo pipefail
  3. # Fuzzer 入口脚本(Linux)。
  4. # 脚本路径:scripts/ci/runCoverage.sh
  5. # 执行模式:FUZZ_MODE=quick|nightly|full
  6. # quick: PR 快检(默认 60s)
  7. # nightly: 夜间巡检(默认 300s)
  8. # full: 全量压测(默认 900s)
  9. # 常用参数:
  10. # FUZZ_SKIP_COV=0|1:是否跳过覆盖率
  11. # FUZZ_RUNS=<N>:固定迭代次数(优先于 max_total_time)
  12. # FUZZ_MAX_TOTAL_TIME=<秒>:总时间预算
  13. # FUZZ_WORKERS / FUZZ_JOBS:并行 worker/job 数
  14. # FUZZ_TIMEOUT / FUZZ_MAX_LEN / FUZZ_VERBOSITY:透传给 libFuzzer
  15. # FUZZ_RSS_LIMIT_MB / FUZZ_MALLOC_LIMIT_MB:内存预算(透传给 libFuzzer)
  16. # FUZZ_CORPUS_DIR / FUZZ_DICT_PATH:corpus 与字典路径
  17. # FUZZ_EXTRA_ARGS:附加 libFuzzer 参数(空格分割)
  18. # XMAKE_FORCE_CLEAN=0|1:是否在配置前先清理
  19. fuzzMode="${FUZZ_MODE:-quick}"
  20. fuzzSkipCov="${FUZZ_SKIP_COV:-0}"
  21. fuzzTimeout="${FUZZ_TIMEOUT:-4}"
  22. fuzzMaxLen="${FUZZ_MAX_LEN:-8192}"
  23. fuzzVerbosity="${FUZZ_VERBOSITY:-0}"
  24. fuzzCorpusDir="${FUZZ_CORPUS_DIR:-./test/fuzzer/corpus}"
  25. fuzzDictPath="${FUZZ_DICT_PATH:-./test/fuzzer/RyanJsonFuzzer.dict}"
  26. fuzzRuns="${FUZZ_RUNS:-}"
  27. fuzzMaxTotalTime="${FUZZ_MAX_TOTAL_TIME:-}"
  28. fuzzWorkers="${FUZZ_WORKERS:-}"
  29. fuzzJobs="${FUZZ_JOBS:-}"
  30. fuzzRssLimitMb="${FUZZ_RSS_LIMIT_MB:-}"
  31. fuzzMallocLimitMb="${FUZZ_MALLOC_LIMIT_MB:-}"
  32. xmakeForceClean="${XMAKE_FORCE_CLEAN:-0}"
  33. # 统一切到仓库根目录,避免从任意 cwd 启动时相对路径失效
  34. scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  35. repoRoot="$(cd "${scriptDir}/../.." && pwd)"
  36. cd "${repoRoot}"
  37. # 按模式设置默认预算(可被环境变量覆盖)
  38. case "${fuzzMode}" in
  39. quick)
  40. defaultMaxTotalTime=60
  41. defaultWorkers=2
  42. defaultJobs=2
  43. ;;
  44. nightly)
  45. defaultMaxTotalTime=300
  46. defaultWorkers=4
  47. defaultJobs=4
  48. ;;
  49. full)
  50. defaultMaxTotalTime=900
  51. defaultWorkers=6
  52. defaultJobs=6
  53. ;;
  54. *)
  55. echo "[错误] FUZZ_MODE 仅支持 quick/nightly/full,当前值:${fuzzMode}"
  56. exit 1
  57. ;;
  58. esac
  59. # 如果用户没显式指定并行参数,则使用模式默认值
  60. if [[ -z "${fuzzWorkers}" ]]; then
  61. fuzzWorkers="${defaultWorkers}"
  62. fi
  63. if [[ -z "${fuzzJobs}" ]]; then
  64. fuzzJobs="${defaultJobs}"
  65. fi
  66. # 如果没设置 FUZZ_RUNS,则走 max_total_time 模式
  67. if [[ -z "${fuzzRuns}" ]]; then
  68. if [[ -z "${fuzzMaxTotalTime}" ]]; then
  69. fuzzMaxTotalTime="${defaultMaxTotalTime}"
  70. fi
  71. fi
  72. strictKey="${RYANJSON_STRICT_OBJECT_KEY_CHECK:-false}"
  73. addAtHead="${RYANJSON_DEFAULT_ADD_AT_HEAD:-true}"
  74. scientific="${RYANJSON_SNPRINTF_SUPPORT_SCIENTIFIC:-true}"
  75. caseName="strict_${strictKey}__head_${addAtHead}__sci_${scientific}"
  76. # 覆盖率目录固定为 coverage/fuzz,每次执行前清理,保证只保留最新结果
  77. coverageRoot="coverage/fuzz"
  78. rm -rf "${coverageRoot}"
  79. profileRoot="${coverageRoot}/profiles"
  80. mkdir -p "${profileRoot}"
  81. profraw="${profileRoot}/coverage.profraw"
  82. profdata="${coverageRoot}/coverage.profdata"
  83. reportTxt="${coverageRoot}/report.txt"
  84. reportHtml="${coverageRoot}/html"
  85. echo "===================================================="
  86. echo "Fuzzer 执行配置"
  87. echo " - 模式: ${fuzzMode}"
  88. echo " - 配置: ${caseName}"
  89. echo " - Corpus: ${fuzzCorpusDir}"
  90. echo " - 字典: ${fuzzDictPath}"
  91. echo " - timeout: ${fuzzTimeout}"
  92. echo " - max_len: ${fuzzMaxLen}"
  93. echo " - workers/jobs: ${fuzzWorkers}/${fuzzJobs}"
  94. if [[ -n "${fuzzRssLimitMb}" ]]; then
  95. echo " - rss_limit_mb: ${fuzzRssLimitMb}"
  96. fi
  97. if [[ -n "${fuzzMallocLimitMb}" ]]; then
  98. echo " - malloc_limit_mb: ${fuzzMallocLimitMb}"
  99. fi
  100. if [[ -n "${fuzzRuns}" ]]; then
  101. echo " - runs: ${fuzzRuns}"
  102. else
  103. echo " - max_total_time: ${fuzzMaxTotalTime}s"
  104. fi
  105. echo "===================================================="
  106. # 重新配置,确保宏变化进入编译命令
  107. if [[ "${xmakeForceClean}" == "1" ]]; then
  108. echo "[阶段] 正在执行 xmake 配置(clean 模式)..."
  109. xmake f -c
  110. else
  111. echo "[阶段] 正在执行 xmake 配置(增量模式)..."
  112. xmake f
  113. fi
  114. echo "[阶段] 正在执行 xmake 构建(target=RyanJsonFuzz)..."
  115. xmake -b RyanJsonFuzz
  116. echo "[信息] xmake 构建完成(target=RyanJsonFuzz)"
  117. # corpus 不存在时自动创建,方便首次运行
  118. if [[ ! -d "${fuzzCorpusDir}" ]]; then
  119. mkdir -p "${fuzzCorpusDir}"
  120. echo "[信息] Corpus 目录不存在,已自动创建:${fuzzCorpusDir}"
  121. fi
  122. # 字典文件可选:存在就启用,不存在就跳过
  123. declare -a dictArgs=()
  124. if [[ -f "${fuzzDictPath}" ]]; then
  125. dictArgs+=("-dict=${fuzzDictPath}")
  126. else
  127. echo "[警告] 未找到字典文件 ${fuzzDictPath},已跳过 -dict 参数。"
  128. fi
  129. # 组装 libFuzzer 参数数组,避免字符串拼接导致转义问题
  130. declare -a fuzzArgs=()
  131. fuzzArgs+=("${fuzzCorpusDir}")
  132. fuzzArgs+=("${dictArgs[@]}")
  133. fuzzArgs+=("-timeout=${fuzzTimeout}")
  134. fuzzArgs+=("-verbosity=${fuzzVerbosity}")
  135. fuzzArgs+=("-max_len=${fuzzMaxLen}")
  136. fuzzArgs+=("-workers=${fuzzWorkers}")
  137. fuzzArgs+=("-jobs=${fuzzJobs}")
  138. if [[ -n "${fuzzRssLimitMb}" ]]; then
  139. fuzzArgs+=("-rss_limit_mb=${fuzzRssLimitMb}")
  140. fi
  141. if [[ -n "${fuzzMallocLimitMb}" ]]; then
  142. fuzzArgs+=("-malloc_limit_mb=${fuzzMallocLimitMb}")
  143. fi
  144. if [[ -n "${fuzzRuns}" ]]; then
  145. fuzzArgs+=("-runs=${fuzzRuns}")
  146. else
  147. fuzzArgs+=("-max_total_time=${fuzzMaxTotalTime}")
  148. fi
  149. # 允许 CI 透传少量临时参数,例如 -rss_limit_mb=4096
  150. if [[ -n "${FUZZ_EXTRA_ARGS:-}" ]]; then
  151. # shellcheck disable=SC2206
  152. extraArgs=( ${FUZZ_EXTRA_ARGS} )
  153. fuzzArgs+=("${extraArgs[@]}")
  154. fi
  155. # 运行 fuzz。使用独立 profile 文件,避免多次执行互相覆盖
  156. echo "[阶段] 正在运行 fuzz 二进制..."
  157. LLVM_PROFILE_FILE="${profraw}" ./build/linux/x86/release/RyanJsonFuzz "${fuzzArgs[@]}"
  158. # 快检模式可跳过覆盖率阶段以缩短总时长
  159. if [[ "${fuzzSkipCov}" == "1" ]]; then
  160. echo "[信息] FUZZ_SKIP_COV=1,已跳过覆盖率生成。"
  161. echo "输出目录:${coverageRoot}"
  162. exit 0
  163. fi
  164. # 覆盖率工具检查:未安装时给出明确错误
  165. if ! command -v llvm-profdata >/dev/null 2>&1; then
  166. echo "[错误] 未找到 llvm-profdata,无法生成覆盖率。"
  167. exit 1
  168. fi
  169. if ! command -v llvm-cov >/dev/null 2>&1; then
  170. echo "[错误] 未找到 llvm-cov,无法生成覆盖率。"
  171. exit 1
  172. fi
  173. # 合并 profile 数据
  174. llvm-profdata merge -sparse "${profraw}" -o "${profdata}"
  175. # 文本汇总覆盖率:
  176. # 先原样打印到终端(尽量保留颜色)
  177. # 再单独写入文件,便于归档
  178. echo "---------------- 覆盖率摘要(llvm-cov report) ----------------"
  179. llvm-cov report ./build/linux/x86/release/RyanJsonFuzz \
  180. -instr-profile="${profdata}" \
  181. -show-mcdc-summary \
  182. --use-color \
  183. -sources ./RyanJson
  184. llvm-cov report ./build/linux/x86/release/RyanJsonFuzz \
  185. -instr-profile="${profdata}" \
  186. -show-mcdc-summary \
  187. -sources ./RyanJson > "${reportTxt}"
  188. # HTML 详细覆盖率(用于本地/制品追踪)
  189. llvm-cov show ./build/linux/x86/release/RyanJsonFuzz \
  190. -instr-profile="${profdata}" \
  191. -format=html \
  192. -output-dir="${reportHtml}" \
  193. -show-mcdc-summary \
  194. -show-branches=count \
  195. -show-expansions \
  196. -show-regions \
  197. -show-line-counts-or-regions \
  198. -sources ./RyanJson
  199. echo "[完成] Fuzzer 执行结束,覆盖率已生成。"
  200. echo "输出目录:${coverageRoot}"
  201. echo "覆盖率文本报告:${reportTxt}"
  202. echo "覆盖率HTML目录:${reportHtml}"