Skip to content

Commit fa8ccbe

Browse files
committed
cmake: update code coverage module
The newly MCDC coverage complements the existing gcov implementation. Gcov works at the object code level which may better reflect actual execution. However, Gcov lacks the necessary information to correlate coverage measurement with source code location when compiler optimization level is non-zero (which is the default when building the kernel). In addition, gcov reports are occasionally ambiguous when attempting to compare with source code level developer intent. Two compiler flags (-fprofile-arcs and -ftest-coverage) were removed, because these flags are GCC-specific. 4.1.6. Tension Between Fuzz Testing And 100% MC/DC Testing [4] 1. https://clang.llvm.org/docs/SourceBasedCodeCoverage.html 2. https://llvm.org/docs/LibFuzzer.html#fork-mode 2. https://lwn.net/Articles/989075/ 3. https://maskray.me/blog/2024-01-28-mc-dc-and-compiler-implementations 4. https://www.sqlite.org/testing.html
1 parent c970175 commit fa8ccbe

File tree

8 files changed

+69
-76
lines changed

8 files changed

+69
-76
lines changed

CMakeLists.txt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ set(CMAKE_INCLUDE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_INCLUDE_PATH}
2929
include(utils)
3030
include(SetBuildParallelLevel)
3131
include(SetHardwareArch)
32+
if(ENABLE_COV)
33+
include(CodeCoverage)
34+
endif()
3235

3336
if (ENABLE_LAPI_TESTS)
3437
include(LibFuzzer)
@@ -106,10 +109,6 @@ endif()
106109

107110
SetBuildParallelLevel(CMAKE_BUILD_PARALLEL_LEVEL)
108111

109-
if(ENABLE_COV)
110-
include(CodeCoverage)
111-
endif()
112-
113112
add_subdirectory(extra)
114113
add_subdirectory(libluamut)
115114
add_subdirectory(tests)

cmake/BuildLua.cmake

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,6 @@ macro(build_lua LUA_VERSION)
5959
endif()
6060

6161
if (ENABLE_COV)
62-
string(JOIN " " CODE_COVERAGE_FLAGS
63-
-fcoverage-mapping
64-
-fprofile-arcs
65-
-fprofile-instr-generate
66-
-ftest-coverage
67-
)
6862
AppendFlags(CFLAGS ${CODE_COVERAGE_FLAGS})
6963
AppendFlags(LDFLAGS ${CODE_COVERAGE_FLAGS})
7064
endif()

cmake/BuildLuaJIT.cmake

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,6 @@ macro(build_luajit LJ_VERSION)
8080
endif()
8181

8282
if (ENABLE_COV)
83-
string(JOIN " " CODE_COVERAGE_FLAGS
84-
-fcoverage-mapping
85-
-fprofile-arcs
86-
-fprofile-instr-generate
87-
-ftest-coverage
88-
)
8983
AppendFlags(CFLAGS ${CODE_COVERAGE_FLAGS})
9084
AppendFlags(LDFLAGS ${CODE_COVERAGE_FLAGS})
9185
endif()

cmake/CodeCoverage.cmake

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,63 @@
1-
find_program(GCOVR gcovr)
21
find_program(LLVM_COV llvm-cov)
2+
find_program(LLVM_PROFDATA llvm-profdata)
33

4-
set(COVERAGE_DIR "${PROJECT_BINARY_DIR}/coverage")
5-
set(COVERAGE_HTML_REPORT "${COVERAGE_DIR}/report.html")
6-
set(COVERAGE_XML_REPORT "${COVERAGE_DIR}/report.xml")
4+
if(NOT LLVM_COV OR NOT LLVM_PROFDATA)
5+
message(FATAL_ERROR "Required LLVM coverage tools not found.")
6+
endif()
7+
8+
set(CODE_COVERAGE_DIR "${PROJECT_BINARY_DIR}/coverage")
9+
set(CODE_COVERAGE_HTML_REPORT ${CODE_COVERAGE_DIR}/index.html)
10+
list(APPEND CODE_COVERAGE_FLAGS
11+
-fcoverage-mapping
12+
-fprofile-instr-generate
13+
)
714

8-
set(target_name "coverage-report")
9-
if(NOT GCOVR OR NOT LLVM_COV)
10-
set(MSG "${target_name} is a dummy target")
11-
add_custom_target(${target_name}
12-
COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --red ${MSG}
15+
# Clang Version 18.1.0 was the first release with full, native
16+
# support for MC/DC coverage analysis using the source-based code
17+
# coverage feature.
18+
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
19+
CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "18.1")
20+
list(APPEND CODE_COVERAGE_FLAGS
21+
-fcoverage-mcdc
1322
)
14-
message(WARNING "Either `gcovr' or `llvm-cov` not found, "
15-
"so ${target_name} target is dummy.")
16-
return()
1723
endif()
1824

19-
# See https://gcovr.com/en/stable/manpage.html.
20-
set(GCOVR_OPTIONS
21-
--txt-metric branch
22-
--cobertura ${COVERAGE_XML_REPORT}
23-
--decisions
24-
--gcov-executable "llvm-cov gcov"
25-
--html
26-
--html-details
27-
--html-title "Code Coverage Report"
28-
-j ${CMAKE_BUILD_PARALLEL_LEVEL}
29-
--output ${COVERAGE_HTML_REPORT}
30-
--print-summary
31-
--root ${LUA_SOURCE_DIR}
32-
--sort-key uncovered-percent
25+
file(MAKE_DIRECTORY ${CODE_COVERAGE_DIR})
26+
27+
list(APPEND LLVM_COV_PROFRAW_MASK
28+
${PROJECT_BINARY_DIR}/tests/capi/*.profraw
29+
${PROJECT_BINARY_DIR}/tests/lapi/*.profraw
30+
)
31+
set(LLVM_COV_PROFDATA ${PROJECT_BINARY_DIR}/tests/default.profdata)
32+
33+
list(APPEND LLVM_COV_FLAGS
34+
-instr-profile=${LLVM_COV_PROFDATA}
35+
-output-dir=${CODE_COVERAGE_DIR}
36+
--show-branches=count
37+
--show-expansions
38+
--show-mcdc
39+
--show-mcdc-summary
3340
)
3441

42+
# XXX: This variable is defined in BuildLua.cmake and
43+
# BuildLuaJIT.cmake. However, these modules are included after
44+
# CodeCoverage.cmake, so not available.
45+
set(LUA_EXECUTABLE ${PROJECT_BINARY_DIR}/luajit-v2.1/source/src/luajit)
3546
if(USE_LUA)
36-
set(GCOVR_OPTIONS ${GCOVR_OPTIONS} --object-directory ${LUA_SOURCE_DIR})
37-
endif ()
38-
39-
if(IS_LUAJIT)
40-
# Exclude DynASM files, that contain a low-level VM code for CPUs.
41-
set(GCOVR_OPTIONS ${GCOVR_OPTIONS} --exclude ".*\.dasc")
42-
# Exclude buildvm source code, it's a project's build infrastructure.
43-
set(GCOVR_OPTIONS ${GCOVR_OPTIONS} --exclude ".*/host/")
44-
set(GCOVR_OPTIONS ${GCOVR_OPTIONS} --object-directory ${LUA_SOURCE_DIR}/src)
45-
endif ()
47+
set(LUA_EXECUTABLE ${PROJECT_BINARY_DIR}/lua-master/source/lua)
48+
endif()
4649

47-
file(MAKE_DIRECTORY ${COVERAGE_DIR})
48-
add_custom_target(${target_name})
49-
add_custom_command(TARGET ${target_name}
50-
COMMENT "Building coverage report"
51-
COMMAND ${GCOVR} ${GCOVR_OPTIONS}
50+
add_custom_target(coverage-report
51+
COMMAND ${LLVM_PROFDATA} merge -sparse ${LLVM_COV_PROFRAW_MASK}
52+
-o ${LLVM_COV_PROFDATA}
53+
COMMAND ${LLVM_COV} show --format=html ${LLVM_COV_FLAGS} ${LUA_EXECUTABLE}
5254
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
55+
COMMENT "Generating HTML code coverage report in ${CODE_COVERAGE_DIR}"
5356
)
5457

55-
# The .gcda count data file is generated when a program containing
56-
# object files built with the GCC -fprofile-arcs option is executed.
57-
# https://gcc.gnu.org/onlinedocs/gcc/Gcov-Data-Files.html
58-
set(GCDA_FILES "${LUA_SOURCE_DIR}/*.gcda")
59-
if(IS_LUAJIT)
60-
# Files 'src/host/*.gcda' are not removed, because
61-
# CMake cannot remove recursively by globbing.
62-
# Files 'src/host/*.gcda' are not used for building coverage report.
63-
set(GCDA_FILES "${LUA_SOURCE_DIR}/src/*.gcda")
64-
endif()
6558
add_custom_target(coverage-reset
6659
COMMENT "Reset code coverage counters"
67-
COMMAND ${CMAKE_COMMAND} -E rm -f ${GCDA_FILES}
60+
COMMAND ${CMAKE_COMMAND} -E rm -f ${LLVM_COV_PROFRAW_MASK} ${LLVM_COV_PROFDATA}
6861
)
6962

70-
message(STATUS "Code coverage HTML report: ${COVERAGE_HTML_REPORT}")
71-
message(STATUS "Code coverage XML report: ${COVERAGE_XML_REPORT}")
63+
message(STATUS "Code coverage HTML report: ${CODE_COVERAGE_HTML_REPORT}")

libluamut/CMakeLists.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
set(CFLAGS -Wall -Wextra -Wpedantic -Wno-unused-parameter)
22

33
if (ENABLE_COV)
4-
set(CFLAGS ${CFLAGS} -fprofile-instr-generate -fprofile-arcs
5-
-fcoverage-mapping -ftest-coverage)
6-
set(LDFLAGS ${LDFLAGS} -fprofile-instr-generate -fprofile-arcs
7-
-fcoverage-mapping -ftest-coverage)
4+
set(CFLAGS ${CFLAGS} "${CODE_COVERAGE_FLAGS}")
5+
set(LDFLAGS ${LDFLAGS} "${CODE_COVERAGE_FLAGS}")
86
endif()
97

108
set(LIB_LUA_MUTATE lua_mutate)

tests/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ string(JOIN " " LIBFUZZER_OPTS
1212
-workers=${CMAKE_BUILD_PARALLEL_LEVEL}
1313
)
1414

15+
if(ENABLE_COV)
16+
string(JOIN " " LIBFUZZER_OPTS
17+
-fork=${CMAKE_BUILD_PARALLEL_LEVEL}
18+
-ignore_ooms=1
19+
-ignore_timeouts=1
20+
-ignore_crashes=1
21+
)
22+
endif()
23+
1524
set(CORPUS_BASE_PATH ${PROJECT_SOURCE_DIR}/corpus)
1625
if(IS_LUAJIT)
1726
set(CORPUS_BASE_PATH ${CORPUS_BASE_PATH}/corpus)

tests/capi/CMakeLists.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,17 @@ function(create_test)
9595
add_test(NAME ${test_name}
9696
COMMAND ${SHELL} -c "$<TARGET_FILE:${test_name}> ${LIBFUZZER_OPTS}"
9797
)
98+
list(APPEND TEST_ENV
99+
LLVM_PROFILE_FILE=${test_name}.%1m.%p.profraw
100+
)
98101
if (USE_LUA)
99-
set_tests_properties(${test_name} PROPERTIES
100-
ENVIRONMENT "ASAN_OPTIONS='detect_invalid_pointer_pairs=2'"
102+
list(APPEND TEST_ENV
103+
asan_options='detect_invalid_pointer_pairs=2'
101104
)
102105
endif()
103106
set_tests_properties(${test_name} PROPERTIES
104107
LABELS capi
108+
ENVIRONMENT "${TEST_ENV}"
105109
)
106110

107111
if (IS_LUAJIT)

tests/lapi/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ function(create_test)
5050
COMMAND ${SHELL} -c "${LUA_EXECUTABLE} ${FUZZ_FILENAME} ${LIBFUZZER_OPTS}"
5151
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
5252
)
53+
list(APPEND TEST_ENV
54+
LLVM_PROFILE_FILE=${test_name}.%1m.%p.profraw
55+
)
5356
set_tests_properties(${test_name} PROPERTIES
5457
LABELS "lapi"
5558
ENVIRONMENT "${TEST_ENV}"

0 commit comments

Comments
 (0)