From 1e00c18022a3ac65b6c64177ce4659a8147d1197 Mon Sep 17 00:00:00 2001 From: Sergey Bronnikov Date: Tue, 3 Feb 2026 11:36:03 +0300 Subject: [PATCH] lua: support code coverage Follows up tarantool/tarantool#11250 Follows up #14656 Closes #14859 --- infra/base-images/base-runner/coverage | 109 +++++++++++++++++++++--- projects/lua-example/compile_lua_fuzzer | 1 + projects/lua/build.sh | 10 +-- projects/lua/compile_lua_fuzzer | 1 + projects/tarantool/build.sh | 5 +- projects/tarantool/compile_lua_fuzzer | 1 + 6 files changed, 106 insertions(+), 21 deletions(-) diff --git a/infra/base-images/base-runner/coverage b/infra/base-images/base-runner/coverage index c3300fb8a6da..0bafcdbf2122 100755 --- a/infra/base-images/base-runner/coverage +++ b/infra/base-images/base-runner/coverage @@ -389,6 +389,69 @@ function run_javascript_fuzz_target { nyc_report_converter.py $nyc_json_summary_file $summary_file } +function is_lua_wrapper { + local target="$1" + [[ -f "$OUT/$target" ]] || return 1 + head -1 "$OUT/$target" 2>/dev/null | grep -q "^#!.*bash" || return 1 + grep -q "^# LUA_RUNTIME_BINARY:" "$OUT/$target" 2>/dev/null +} + +function get_lua_runtime_binary { + local target="$1" + grep "^# LUA_RUNTIME_BINARY:" "$OUT/$target" | head -1 | sed 's/^# LUA_RUNTIME_BINARY: *//' +} + +function run_lua_fuzz_target { + local target=$1 + local lua_runtime=$2 + + local profraw_file="$DUMPS_DIR/$target.%1m.profraw" + local profraw_file_mask="$DUMPS_DIR/$target.*.profraw" + local profdata_file="$DUMPS_DIR/$target.profdata" + local corpus_real="$CORPUS_DIR/${target}" + + local corpus_dummy="$OUT/dummy_corpus_dir_for_${target}" + rm -rf $corpus_dummy && mkdir -p $corpus_dummy + + local args="-merge=1 -timeout=100 $corpus_dummy $corpus_real" + + export LLVM_PROFILE_FILE=$profraw_file + timeout $TIMEOUT $OUT/$target $args &> $LOGS_DIR/$target.log + cov_retcode=$? + + target_error_log="$LOGS_DIR/${target}_error.log" + grep -E "^==[0-9]+== ERROR: libFuzzer:" "$LOGS_DIR/$target.log" > "$target_error_log" + grep_retcode=$? + + if (( $cov_retcode != 0 || $grep_retcode == 0 )); then + echo "Error occured while running $target:" + echo "Cov returncode: $cov_retcode, grep returncode: $grep_retcode" + cat $LOGS_DIR/$target.log + fi + + rm -rf $corpus_dummy + if (( $(du -c $profraw_file_mask | tail -n 1 | cut -f 1) == 0 )); then + return 0 + fi + + # Use the actual Lua runtime binary, not the shell wrapper. + profraw_update.py $OUT/$lua_runtime -i $profraw_file_mask + llvm-profdata merge -j=1 -sparse $profraw_file_mask -o $profdata_file + + rm $profraw_file_mask + + shared_libraries=$(coverage_helper shared_libs -build-dir=$OUT -object=$lua_runtime) + + llvm-cov export -summary-only -instr-profile=$profdata_file -object=$lua_runtime \ + $shared_libraries $LLVM_COV_COMMON_ARGS > $FUZZER_STATS_DIR/$target.json + + if (( $cov_retcode != 0 || $grep_retcode == 0 )); then + mv "$target_error_log" "$FUZZER_STATS_DIR/${target}_error.log"; + fi + + llvm-cov show -instr-profile=$profdata_file -object=$lua_runtime -line-coverage-gt=0 $shared_libraries $BRANCH_COV_ARGS $LLVM_COV_COMMON_ARGS > ${TEXTCOV_REPORT_DIR}/$target.covreport +} + function generate_html { local profdata=$1 local shared_libraries=$2 @@ -467,18 +530,26 @@ for fuzz_target in $FUZZ_TARGETS; do echo ${fuzz_target} >> $COVERAGE_TARGET_FILE # Run the coverage collection. - run_fuzz_target $fuzz_target & - - # Rewrite object if its a FUZZTEST target - if [[ $fuzz_target == *"@"* ]]; then - # Extract fuzztest binary name from fuzztest wrapper script. - fuzz_target=(${fuzz_target//@/ }[0]) - fi - if [[ -z $objects ]]; then - # The first object needs to be passed without -object= flag. - objects="$fuzz_target" + # Detect Lua/luzer wrapper targets and handle them separately, + # using the actual Lua runtime binary for llvm-cov analysis. + if is_lua_wrapper $fuzz_target; then + lua_runtime=$(get_lua_runtime_binary $fuzz_target) + run_lua_fuzz_target $fuzz_target $lua_runtime & + echo "$lua_runtime" >> "$DUMPS_DIR/lua_runtime_binaries.txt" else - objects="$objects -object=$fuzz_target" + run_fuzz_target $fuzz_target & + + # Rewrite object if its a FUZZTEST target + if [[ $fuzz_target == *"@"* ]]; then + # Extract fuzztest binary name from fuzztest wrapper script. + fuzz_target=(${fuzz_target//@/ }[0]) + fi + if [[ -z $objects ]]; then + # The first object needs to be passed without -object= flag. + objects="$fuzz_target" + else + objects="$objects -object=$fuzz_target" + fi fi fi @@ -622,6 +693,17 @@ else rm -f $PROFILE_FILE llvm-profdata merge -sparse $DUMPS_DIR/*.profdata -o $PROFILE_FILE + # Add Lua runtime binaries to the objects list for llvm-cov. + if [[ -f "$DUMPS_DIR/lua_runtime_binaries.txt" ]]; then + for runtime in $(sort -u "$DUMPS_DIR/lua_runtime_binaries.txt"); do + if [[ -z $objects ]]; then + objects="$runtime" + else + objects="$objects -object=$runtime" + fi + done + fi + # TODO(mmoroz): add script from Chromium for rendering directory view reports. # The first path in $objects does not have -object= prefix (llvm-cov format). shared_libraries=$(coverage_helper shared_libs -build-dir=$OUT -object=$objects) @@ -645,6 +727,11 @@ else continue fi + # For Lua/luzer wrapper targets, use the runtime binary as the + # llvm-cov object instead of the shell wrapper script. + if is_lua_wrapper $fuzz_target; then + fuzz_target=$(get_lua_runtime_binary $fuzz_target) + fi generate_html $profdata_path "$shared_libraries" "$fuzz_target" "$report_dir" done diff --git a/projects/lua-example/compile_lua_fuzzer b/projects/lua-example/compile_lua_fuzzer index 59fc44d0941e..9ed1d9bbf9c3 100755 --- a/projects/lua-example/compile_lua_fuzzer +++ b/projects/lua-example/compile_lua_fuzzer @@ -28,6 +28,7 @@ echo "#!/bin/bash # LLVMFuzzerTestOneInput so that the wrapper script is recognized # as a fuzz target for 'check_build'. +# LUA_RUNTIME_BINARY: $lua_runtime project_dir=\$(dirname \$(realpath \"\$0\")) luarocks=\$(type -p luarocks) diff --git a/projects/lua/build.sh b/projects/lua/build.sh index 6d6fb82785f6..9dadc91b95b6 100755 --- a/projects/lua/build.sh +++ b/projects/lua/build.sh @@ -83,10 +83,11 @@ FUZZER_ARGS="" # # 1. https://github.com/ligurio/lunapark/issues/180. LAPI_TESTING="ON" -if [[ "$FUZZING_ENGINE" != "libfuzzer" ]] || - [[ "$SANITIZER" == "coverage" ]]; then +if [[ "$FUZZING_ENGINE" != "libfuzzer" ]]; then FUZZER_ARGS="-DDISABLE_LIBFUZZER_STATIC_LINKAGE=ON" LAPI_TESTING="OFF" +elif [[ "$SANITIZER" == "coverage" ]]; then + FUZZER_ARGS="-DDISABLE_LIBFUZZER_STATIC_LINKAGE=ON" fi cmake_args=( @@ -142,10 +143,7 @@ done # Finish execution if libFuzzer is not used, because luzer # is libFuzzer-based. -# Code coverage is not supported, -# see https://github.com/google/oss-fuzz/issues/14859. -if [[ "$FUZZING_ENGINE" != libfuzzer ]] || - [[ "$SANITIZER" == "coverage" ]]; then +if [[ "$FUZZING_ENGINE" != libfuzzer ]]; then echo "Lua API testing is not supported." exit fi diff --git a/projects/lua/compile_lua_fuzzer b/projects/lua/compile_lua_fuzzer index 3e4940940e47..5ed1e4436832 100755 --- a/projects/lua/compile_lua_fuzzer +++ b/projects/lua/compile_lua_fuzzer @@ -28,6 +28,7 @@ echo "#!/bin/bash # LLVMFuzzerTestOneInput so that the wrapper script is recognized # as a fuzz target for 'check_build'. +# LUA_RUNTIME_BINARY: $lua_runtime project_dir=\$(dirname \$(realpath \"\$0\")) luarocks=\$(type -p luarocks) diff --git a/projects/tarantool/build.sh b/projects/tarantool/build.sh index fc02e2a7a045..3282ce22c921 100755 --- a/projects/tarantool/build.sh +++ b/projects/tarantool/build.sh @@ -117,10 +117,7 @@ done # Finish execution if libFuzzer is not used, because luzer # is libFuzzer-based. -# Code coverage is not supported, -# see https://github.com/google/oss-fuzz/issues/14859. -if [[ "$FUZZING_ENGINE" != libfuzzer ]] || - [[ "$SANITIZER" == "coverage" ]]; then +if [[ "$FUZZING_ENGINE" != libfuzzer ]]; then exit fi diff --git a/projects/tarantool/compile_lua_fuzzer b/projects/tarantool/compile_lua_fuzzer index b3713c7a4724..4004966f5f1f 100755 --- a/projects/tarantool/compile_lua_fuzzer +++ b/projects/tarantool/compile_lua_fuzzer @@ -28,6 +28,7 @@ echo "#!/bin/bash # LLVMFuzzerTestOneInput so that the wrapper script is recognized # as a fuzz target for 'check_build'. +# LUA_RUNTIME_BINARY: $lua_runtime project_dir=\$(dirname \$(realpath \"\$0\")) luarocks=\$(type -p luarocks)