Skip to content

Commit 62f6c2d

Browse files
committed
coverage support
1 parent 0a672b7 commit 62f6c2d

File tree

10 files changed

+342
-32
lines changed

10 files changed

+342
-32
lines changed

CMakeLists.txt

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,26 @@ set(CMAKE_CXX_STANDARD 26)
66

77
project(rsltest CXX)
88
add_library(rsltest SHARED)
9+
add_library(rsltest_main SHARED)
10+
add_library(rsltest_cov)
911

1012
set_target_properties(rsltest PROPERTIES OUTPUT_NAME rsltest)
1113
target_compile_options(rsltest PUBLIC
1214
"-freflection-latest"
1315
# "-ftime-trace"
1416
# "-fconstexpr-steps=10000000" # required to scan the global namespace
1517
)
18+
19+
target_compile_options(rsltest_cov PUBLIC
20+
"-freflection-latest"
21+
)
22+
1623
target_include_directories(rsltest PUBLIC
1724
$<INSTALL_INTERFACE:include>
1825
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>)
1926

2027
find_package(libassert REQUIRED)
2128
target_link_libraries(rsltest PUBLIC libassert::assert)
22-
23-
add_library(rsltest_main SHARED)
24-
2529
target_link_libraries(rsltest_main PUBLIC rsltest)
2630

2731
find_package(rsl-util REQUIRED)
@@ -35,16 +39,12 @@ target_link_libraries(rsltest_main PUBLIC rsl::xml)
3539

3640
add_subdirectory(src)
3741

38-
install(TARGETS rsltest_main)
39-
install(TARGETS rsltest)
40-
install(DIRECTORY include/ DESTINATION include)
41-
4242
option(BUILD_TESTING "Enable tests" ON)
4343
option(ENABLE_COVERAGE "Enable coverage instrumentation" OFF)
4444

4545
if (BUILD_TESTING)
4646
message(STATUS "Building unit tests")
47-
47+
4848
add_executable(rsltest_test)
4949
add_subdirectory(test)
5050
# target_compile_definitions(rsltest_test PRIVATE RSL_TEST_NAMESPACE=testing)
@@ -53,18 +53,17 @@ if (BUILD_TESTING)
5353

5454
# enable_testing()
5555
# add_test(NAME rsltest_test COMMAND rsltest_example)
56-
57-
if(ENABLE_COVERAGE)
58-
message(STATUS "Instrumenting for coverage")
59-
60-
target_compile_options(rsltest PRIVATE -g -O0 --coverage)
61-
target_link_libraries(rsltest PRIVATE --coverage)
62-
endif()
6356
endif()
64-
57+
6558
if (BUILD_EXAMPLES)
6659
add_executable(example_test)
6760
add_subdirectory(example)
6861
target_link_libraries(example_test PRIVATE rsltest)
6962
target_link_libraries(example_test PRIVATE rsltest_main)
70-
endif()
63+
target_link_libraries(example_test PRIVATE rsltest_cov)
64+
endif()
65+
66+
install(TARGETS rsltest_cov)
67+
install(TARGETS rsltest_main)
68+
install(TARGETS rsltest)
69+
install(DIRECTORY include/ DESTINATION include)

example/CMakeLists.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,3 @@ target_sources(example_test PRIVATE
55
fixtures.cpp
66
conditional.cpp
77
)
8-
# add_executable(params_debug params_debug.cpp)
9-
# target_compile_options(example_test PUBLIC -fprofile-instr-generate -fcoverage-mapping)
10-
# target_link_options(example_test PUBLIC -fprofile-instr-generate -fcoverage-mapping)
11-
# target_link_libraries(params_debug PUBLIC rsltest)

example/always_passes.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22
#include <iostream>
33

44
namespace demo {
5-
auto zoinks() {
5+
auto zoinks(bool zoinks) {
66
bool x = true;
7-
ASSERT(x == false);
7+
// ASSERT(x == false);
8+
if (zoinks) {
9+
for (int i = 0; i < 4; ++i) {
10+
x += std::puts("foo");
11+
}
12+
} else {
13+
x = false;
14+
}
15+
return x;
816
}
917

1018
[[= rsl::test]] void always_passes() {
1119
std::cout << "foo\n";
1220
std::cerr << "bar\n";
13-
zoinks();
21+
zoinks(true);
22+
zoinks(false);
1423
}
1524
} // namespace demo

src/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ target_sources(rsltest PUBLIC
33
test.cpp
44
)
55

6-
add_subdirectory(main)
6+
add_subdirectory(main)
7+
add_subdirectory(coverage)

src/coverage/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
target_sources(rsltest_cov PUBLIC
2+
hooks.cpp
3+
runner.cpp
4+
)
5+
6+
set(COVERAGE_FLAGS
7+
-fsanitize-coverage=pc-table,trace-pc-guard
8+
)
9+
10+
target_compile_options(rsltest_cov INTERFACE ${COVERAGE_FLAGS} "-O0" "-g")
11+
target_link_options(rsltest_cov INTERFACE ${COVERAGE_FLAGS})

src/coverage/coverage.hpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#pragma once
2+
#include <cstdint>
3+
#include <cstddef>
4+
5+
namespace rsl::coverage {
6+
struct CoverageReport {
7+
std::uintptr_t pc;
8+
std::uint64_t hits;
9+
};
10+
} // namespace rsl::coverage
11+
12+
extern "C" __attribute__((weak))
13+
void _rsl_test_run_with_coverage(
14+
void (*fnc)(void const*),
15+
void const* test,
16+
rsl::coverage::CoverageReport** output,
17+
std::size_t* output_size);

src/coverage/hooks.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#include <cassert>
2+
#include <cstddef>
3+
#include <cstdio>
4+
#include <cstdint>
5+
#include <vector>
6+
7+
#include "hooks.hpp"
8+
9+
namespace rsl::coverage {
10+
std::uint64_t* counters = nullptr;
11+
PCTableEntry const* pc_table = nullptr;
12+
std::size_t guard_count = 0;
13+
14+
std::vector<std::uintptr_t>& pc_tracker() {
15+
static std::vector<std::uintptr_t> data;
16+
return data;
17+
}
18+
19+
} // namespace rsl::coverage
20+
21+
extern "C" {
22+
uint64_t __sancov_should_track = 0;
23+
24+
void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop) {
25+
using rsl::coverage::counters;
26+
using rsl::coverage::guard_count;
27+
28+
if (counters != nullptr) {
29+
// reset
30+
fprintf(stderr, "guard reinitialized!");
31+
delete[] rsl::coverage::counters;
32+
}
33+
guard_count = stop - start;
34+
counters = new std::uint64_t[guard_count];
35+
for (size_t i = 0; i < guard_count; i++) {
36+
start[i] = i + 1;
37+
}
38+
}
39+
40+
void __sanitizer_cov_pcs_init(std::uintptr_t const* pcs_beg, std::uintptr_t const* pcs_end) {
41+
using rsl::coverage::guard_count;
42+
using rsl::coverage::pc_table;
43+
guard_count = (pcs_end - pcs_beg) / 2;
44+
assert((pcs_end - pcs_beg) / 2 == guard_count);
45+
46+
pc_table = reinterpret_cast<rsl::coverage::PCTableEntry const*>(pcs_beg);
47+
}
48+
49+
void __sanitizer_cov_trace_pc_guard(uint32_t* guard) {
50+
using rsl::coverage::counters;
51+
if (__sancov_should_track == 0 || counters == nullptr || guard == nullptr || *guard == 0U) {
52+
return;
53+
}
54+
auto idx = (*guard) - 1;
55+
assert(idx < rsl::coverage::guard_count);
56+
counters[idx]++;
57+
}
58+
}

src/coverage/hooks.hpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#pragma once
2+
#include <cstdint>
3+
#include <cstddef>
4+
#include <vector>
5+
6+
namespace rsl::coverage {
7+
8+
struct PCTableEntry {
9+
std::uintptr_t pc;
10+
std::uintptr_t flags;
11+
[[nodiscard]] bool is_function_entry() const { return flags != 0; }
12+
};
13+
14+
//? this isn't wrapped in a unique_ptr for more direct access
15+
//? otherwise the hook might become recursive
16+
extern std::uint64_t* counters;
17+
extern PCTableEntry const* pc_table;
18+
extern std::size_t guard_count;
19+
std::vector<std::uintptr_t>& pc_tracker();
20+
} // namespace rsl::coverage
21+
22+
extern "C" {
23+
//? utilities from compiler-rt
24+
int __sanitizer_get_module_and_offset_for_pc(void* pc,
25+
char* module_name,
26+
std::uintptr_t module_name_len,
27+
void** pc_offset);
28+
29+
void __sanitizer_symbolize_pc(std::uintptr_t pc,
30+
char const* fmt,
31+
char* out_buf,
32+
std::uintptr_t out_buf_size);
33+
34+
void __sanitizer_symbolize_global(std::uintptr_t data_addr,
35+
char const* fmt,
36+
char* out_buf,
37+
std::uintptr_t out_buf_size);
38+
39+
//? -mllvm -sanitizer-coverage-gated-trace-callbacks
40+
extern uint64_t __sancov_should_track;
41+
42+
//? -fsanitize-coverage=stack-depth
43+
// extern thread_local uintptr_t __sancov_lowest_stack;
44+
// void __sanitizer_cov_stack_depth();
45+
46+
//? -fsanitize-coverage=trace-pc-guard
47+
void __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* end);
48+
void __sanitizer_cov_trace_pc_guard(uint32_t* guard);
49+
50+
//? -fsanitize-coverage=inline-8bit-counters
51+
// void __sanitizer_cov_8bit_counters_init(uint8_t* start, uint8_t* end);
52+
53+
//? -fsanitize-coverage=inline-bool-flag
54+
// void __sanitizer_cov_bool_flag_init(bool* start, bool* end);
55+
56+
//? -fsanitize-coverage=pc-table
57+
//? requires one of inline-8bit-counters, inline-bool-flag, trace-pc-guard
58+
void __sanitizer_cov_pcs_init(uintptr_t const* pcs_beg, uintptr_t const* pcs_end);
59+
60+
//? -fsanitize-coverage=trace-pc
61+
void __sanitizer_cov_trace_pc(void* callee); // also available in GCC?
62+
63+
//? -fsanitize-coverage=indirect-calls
64+
// void __sanitizer_cov_trace_pc_indir(uintptr_t callee);
65+
66+
//? -fsanitize-coverage=trace-cmp
67+
//? requires one of trace-pc, inline-8bit-counters, inline-bool-flag
68+
// void __sanitizer_cov_trace_cmp8(uint64_t Arg1, uint64_t Arg2);
69+
// void __sanitizer_cov_trace_const_cmp8(uint64_t Arg1, uint64_t Arg2);
70+
// void __sanitizer_cov_trace_cmp4(uint32_t Arg1, uint32_t Arg2);
71+
// void __sanitizer_cov_trace_const_cmp4(uint32_t Arg1, uint32_t Arg2);
72+
// void __sanitizer_cov_trace_cmp2(uint16_t Arg1, uint16_t Arg2);
73+
// void __sanitizer_cov_trace_const_cmp2(uint16_t Arg1, uint16_t Arg2);
74+
// void __sanitizer_cov_trace_cmp1(uint8_t Arg1, uint8_t Arg2);
75+
// void __sanitizer_cov_trace_const_cmp1(uint8_t Arg1, uint8_t Arg2);
76+
// void __sanitizer_cov_trace_switch(uint64_t Val, uint64_t* Cases);
77+
78+
//? -fsanitize-coverage=trace-div
79+
//? requires one of trace-pc, inline-8bit-counters, inline-bool-flag
80+
// void __sanitizer_cov_trace_div4(uint32_t Val);
81+
// void __sanitizer_cov_trace_div8(uint64_t Val);
82+
83+
//? -fsanitize-coverage=trace-gep
84+
//? requires one of trace-pc, inline-8bit-counters, inline-bool-flag
85+
// void __sanitizer_cov_trace_gep(uintptr_t Idx);
86+
87+
//? -fsanitize-coverage=trace-loads
88+
//? requires one of trace-pc, inline-8bit-counters, inline-bool-flag
89+
// void __sanitizer_cov_load1(uint8_t* addr);
90+
// void __sanitizer_cov_load2(uint16_t* addr);
91+
// void __sanitizer_cov_load4(uint32_t* addr);
92+
// void __sanitizer_cov_load8(uint64_t* addr);
93+
// void __sanitizer_cov_load16(__int128* addr);
94+
95+
//? -fsanitize-coverage=trace-stores
96+
//? requires one of trace-pc, inline-8bit-counters, inline-bool-flag
97+
// void __sanitizer_cov_store1(uint8_t* addr);
98+
// void __sanitizer_cov_store2(uint16_t* addr);
99+
// void __sanitizer_cov_store4(uint32_t* addr);
100+
// void __sanitizer_cov_store8(uint64_t* addr);
101+
// void __sanitizer_cov_store16(__int128* addr);
102+
} // extern "C"

src/coverage/runner.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#include <functional>
2+
#include <print>
3+
4+
#include "hooks.hpp"
5+
#include "coverage.hpp"
6+
7+
namespace {
8+
void reset_counters() {
9+
using rsl::coverage::counters;
10+
using rsl::coverage::guard_count;
11+
12+
if (guard_count == 0 || counters == nullptr) {
13+
return;
14+
}
15+
16+
for (auto idx = 0; idx < guard_count; ++idx) {
17+
counters[idx] = 0;
18+
}
19+
}
20+
21+
} // namespace
22+
23+
extern "C" __attribute__((no_sanitize("coverage"))) void _rsl_test_run_with_coverage(
24+
void (*fnc)(void const*),
25+
void const* test,
26+
rsl::coverage::CoverageReport** output,
27+
std::size_t* output_size) {
28+
//! this function is not thread safe
29+
//? to avoid atomics it is assumed that we're in single threaded context here
30+
31+
//? data races on counters are acceptable
32+
//? => coverage counters are only approximate
33+
using namespace rsl::coverage;
34+
35+
std::println("running with coverage");
36+
reset_counters();
37+
__sancov_should_track = 1;
38+
39+
auto finalize = [&] {
40+
41+
};
42+
43+
// try {
44+
fnc(test);
45+
// } catch (...) {
46+
// finalize();
47+
// throw;
48+
// }
49+
// finalize();
50+
__sancov_should_track = 0;
51+
std::unordered_map<std::uintptr_t, std::uint64_t> reached;
52+
for (std::size_t idx = 0; idx < guard_count; ++idx) {
53+
if (counters[idx] != 0) {
54+
reached[pc_table[idx].pc] = counters[idx];
55+
}
56+
}
57+
std::vector<uintptr_t> snapshot = auto{pc_tracker()};
58+
for (auto pc : snapshot) {
59+
reached[pc]++;
60+
}
61+
62+
// set output
63+
*output = (CoverageReport*)malloc(sizeof(CoverageReport) * reached.size());
64+
65+
std::size_t idx = 0;
66+
for (auto const&[pc, count] : reached) {
67+
// char PcDescr[1024];
68+
// __sanitizer_symbolize_pc(pc + 4, "%s:%l:%c", PcDescr, sizeof(PcDescr));
69+
// char PcDescr2[1024];
70+
// __sanitizer_symbolize_global(pc + 4, "%s:%l:0", PcDescr2, sizeof(PcDescr2));
71+
// std::println("{} - {}", PcDescr, PcDescr);
72+
(*output)[idx] = {pc, count};
73+
++idx;
74+
}
75+
76+
*output_size = reached.size();
77+
}

0 commit comments

Comments
 (0)