Skip to content

Commit 4a105c5

Browse files
committed
feat: add pointer compare/subtract option + smart san support detection
1 parent f9ba484 commit 4a105c5

8 files changed

+173
-118
lines changed

README.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,8 @@ if(FEATURE_TESTS)
100100
set(ENABLE_CPPCHECK "ENABLE_CPPCHECK")
101101
set(ENABLE_COVERAGE "ENABLE_COVERAGE")
102102
103-
check_sanitizers_support(ENABLE_SANITIZER_ADDRESS
104-
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
105-
ENABLE_SANITIZER_LEAK
106-
ENABLE_SANITIZER_THREAD
107-
ENABLE_SANITIZER_MEMORY)
103+
set(ENABLE_SANITIZER_ADDRESS "ENABLE_SANITIZER_ADDRESS")
104+
set(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR")
108105
endif()
109106
110107
# Enable doxgen for the docs
@@ -129,6 +126,8 @@ project_options(
129126
${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
130127
# ${ENABLE_SANITIZER_THREAD}
131128
# ${ENABLE_SANITIZER_MEMORY}
129+
# ENABLE_SANITIZER_POINTER_COMPARE
130+
# ENABLE_SANITIZER_POINTER_SUBTRACT
132131
# ENABLE_CONTROL_FLOW_PROTECTION
133132
# ENABLE_STACK_PROTECTION
134133
# ENABLE_OVERFLOW_PROTECTION

docs/src/project_options_example.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,8 @@ if(FEATURE_TESTS)
5454
set(ENABLE_CPPCHECK "ENABLE_CPPCHECK")
5555
set(ENABLE_COVERAGE "ENABLE_COVERAGE")
5656
57-
check_sanitizers_support(ENABLE_SANITIZER_ADDRESS
58-
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
59-
ENABLE_SANITIZER_LEAK
60-
ENABLE_SANITIZER_THREAD
61-
ENABLE_SANITIZER_MEMORY)
57+
set(ENABLE_SANITIZER_ADDRESS "ENABLE_SANITIZER_ADDRESS")
58+
set(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR")
6259
endif()
6360
6461
# Enable doxgen for the docs
@@ -83,6 +80,8 @@ project_options(
8380
${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
8481
# ${ENABLE_SANITIZER_THREAD}
8582
# ${ENABLE_SANITIZER_MEMORY}
83+
# ENABLE_SANITIZER_POINTER_COMPARE
84+
# ENABLE_SANITIZER_POINTER_SUBTRACT
8685
# ENABLE_CONTROL_FLOW_PROTECTION
8786
# ENABLE_STACK_PROTECTION
8887
# ENABLE_OVERFLOW_PROTECTION

src/DynamicProjectOptions.cmake

+7-2
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,13 @@ macro(dynamic_project_options)
104104
endif()
105105

106106
check_sanitizers_support(
107-
ENABLE_SANITIZER_ADDRESS ENABLE_SANITIZER_UNDEFINED_BEHAVIOR ENABLE_SANITIZER_LEAK
108-
ENABLE_SANITIZER_THREAD ENABLE_SANITIZER_MEMORY
107+
ENABLE_SANITIZER_ADDRESS
108+
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
109+
ENABLE_SANITIZER_LEAK
110+
ENABLE_SANITIZER_THREAD
111+
ENABLE_SANITIZER_MEMORY
112+
ENABLE_SANITIZER_POINTER_COMPARE
113+
ENABLE_SANITIZER_POINTER_SUBTRACT
109114
)
110115

111116
if(ENABLE_SANITIZER_ADDRESS)

src/Index.cmake

+4
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ macro(project_options)
151151
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
152152
ENABLE_SANITIZER_THREAD
153153
ENABLE_SANITIZER_MEMORY
154+
ENABLE_SANITIZER_POINTER_COMPARE
155+
ENABLE_SANITIZER_POINTER_SUBTRACT
154156
ENABLE_CONTROL_FLOW_PROTECTION
155157
ENABLE_STACK_PROTECTION
156158
ENABLE_OVERFLOW_PROTECTION
@@ -266,6 +268,8 @@ macro(project_options)
266268
${ProjectOptions_ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
267269
${ProjectOptions_ENABLE_SANITIZER_THREAD}
268270
${ProjectOptions_ENABLE_SANITIZER_MEMORY}
271+
${ProjectOptions_ENABLE_SANITIZER_POINTER_COMPARE}
272+
${ProjectOptions_ENABLE_SANITIZER_POINTER_SUBTRACT}
269273
)
270274

271275
enable_hardening(

src/Sanitizers.cmake

+143-93
Original file line numberDiff line numberDiff line change
@@ -11,89 +11,112 @@ function(
1111
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
1212
ENABLE_SANITIZER_THREAD
1313
ENABLE_SANITIZER_MEMORY
14+
ENABLE_SANITIZER_POINTER_COMPARE
15+
ENABLE_SANITIZER_POINTER_SUBTRACT
1416
)
1517

16-
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
17-
set(SANITIZERS "")
18-
19-
if(${ENABLE_SANITIZER_ADDRESS})
20-
list(APPEND SANITIZERS "address")
21-
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 8)
22-
list(APPEND SANITIZERS "pointer-compare" "pointer-subtract")
23-
message(
24-
STATUS
25-
"To enable invalid pointer pairs detection, add detect_invalid_pointer_pairs=2 to the environment variable ASAN_OPTIONS."
26-
)
18+
# check if the sanitizers are supported
19+
check_sanitizers_support(
20+
SUPPORTS_SANITIZER_ADDRESS
21+
SUPPORTS_SANITIZER_UNDEFINED_BEHAVIOR
22+
SUPPORTS_SANITIZER_LEAK
23+
SUPPORTS_SANITIZER_THREAD
24+
SUPPORTS_SANITIZER_MEMORY
25+
SUPPORTS_SANITIZER_POINTER_COMPARE
26+
SUPPORTS_SANITIZER_POINTER_SUBTRACT
27+
)
28+
29+
# for each sanitizer, check if it is supported and enabled
30+
set(SANITIZERS "")
31+
foreach(
32+
SANITIZER IN
33+
ITEMS "address"
34+
"leak"
35+
"undefined"
36+
"thread"
37+
"memory"
38+
"pointer-compare"
39+
"pointer-subtract"
40+
)
41+
if(${ENABLE_SANITIZER_${SANITIZER}})
42+
if(${SUPPORTS_SANITIZER_${SANITIZER}})
43+
list(APPEND SANITIZERS ${SANITIZER})
44+
else()
45+
# do not enable the sanitizer if it is not supported
46+
message(STATUS "${SANITIZER} sanitizer is not supported. Not enabling it.")
2747
endif()
2848
endif()
49+
endforeach()
2950

30-
if(${ENABLE_SANITIZER_LEAK})
31-
list(APPEND SANITIZERS "leak")
32-
endif()
51+
# Info on special cases
3352

34-
if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR})
35-
list(APPEND SANITIZERS "undefined")
36-
endif()
37-
38-
if(${ENABLE_SANITIZER_THREAD})
39-
if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
40-
message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled")
41-
else()
42-
list(APPEND SANITIZERS "thread")
43-
endif()
53+
# Address sanitizer requires Leak sanitizer to be disabled
54+
if(${ENABLE_SANITIZER_THREAD} AND "${SUPPORTS_SANITIZER_THREAD}" STREQUAL "ENABLE_SANITIZER_THREAD")
55+
if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
56+
message(
57+
WARNING
58+
"Thread sanitizer does not work with Address or Leak sanitizer enabled. Disabling the thread sanitizer."
59+
)
60+
# remove thread sanitizer from the list
61+
list(REMOVE_ITEM SANITIZERS "thread")
4462
endif()
63+
endif()
4564

46-
if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
65+
# Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives
66+
if(${ENABLE_SANITIZER_MEMORY} AND "${SUPPORTS_SANITIZER_MEMORY}" STREQUAL "ENABLE_SANITIZER_MEMORY"
67+
AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang"
68+
)
69+
message(
70+
STATUS
71+
"Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives"
72+
)
73+
if("address" IN_LIST SANITIZERS OR "thread" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
4774
message(
4875
WARNING
49-
"Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives"
76+
"Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled. Disabling the memory sanitizer."
5077
)
51-
if("address" IN_LIST SANITIZERS OR "thread" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS)
52-
message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled")
53-
else()
54-
list(APPEND SANITIZERS "memory")
55-
endif()
56-
endif()
57-
elseif(MSVC)
58-
if(${ENABLE_SANITIZER_ADDRESS})
59-
list(APPEND SANITIZERS "address")
78+
# remove memory sanitizer from the list
79+
list(REMOVE_ITEM SANITIZERS "memory")
6080
endif()
61-
if(${ENABLE_SANITIZER_LEAK}
62-
OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}
63-
OR ${ENABLE_SANITIZER_THREAD}
64-
OR ${ENABLE_SANITIZER_MEMORY}
81+
endif()
82+
83+
if((${ENABLE_SANITIZER_POINTER_COMPARE} AND "${SUPPORTS_SANITIZER_POINTER_COMPARE}" STREQUAL
84+
"ENABLE_SANITIZER_POINTER_COMPARE")
85+
OR (${ENABLE_SANITIZER_POINTER_SUBTRACT} AND "${SUPPORTS_SANITIZER_POINTER_SUBTRACT}" STREQUAL
86+
"ENABLE_SANITIZER_POINTER_SUBTRACT")
87+
)
88+
message(
89+
STATUS
90+
"To enable invalid pointer pairs detection, add detect_invalid_pointer_pairs=2 to the environment variable ASAN_OPTIONS."
6591
)
66-
message(WARNING "MSVC only supports address sanitizer")
67-
endif()
6892
endif()
6993

94+
# Join the sanitizers
7095
list(JOIN SANITIZERS "," LIST_OF_SANITIZERS)
7196

72-
if(LIST_OF_SANITIZERS)
73-
if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "")
74-
if(NOT MSVC)
75-
target_compile_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
76-
target_link_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
77-
else()
78-
string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir)
79-
if("${index_of_vs_install_dir}" STREQUAL "-1")
80-
message(
81-
SEND_ERROR
82-
"Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project."
83-
)
84-
endif()
85-
if(POLICY CMP0141)
86-
if("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" OR "${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}"
87-
STREQUAL "EditAndContinue"
88-
)
89-
set_target_properties(${_project_name} PROPERTIES MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase)
90-
endif()
91-
else()
92-
target_compile_options(${_project_name} INTERFACE /Zi)
97+
if(LIST_OF_SANITIZERS AND NOT "${LIST_OF_SANITIZERS}" STREQUAL "")
98+
if(NOT MSVC)
99+
target_compile_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
100+
target_link_options(${_project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS})
101+
else()
102+
string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir)
103+
if("${index_of_vs_install_dir}" STREQUAL "-1")
104+
message(
105+
SEND_ERROR
106+
"Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project."
107+
)
108+
endif()
109+
if(POLICY CMP0141)
110+
if("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" OR "${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}"
111+
STREQUAL "EditAndContinue"
112+
)
113+
set_target_properties(${_project_name} PROPERTIES MSVC_DEBUG_INFORMATION_FORMAT ProgramDatabase)
93114
endif()
94-
target_compile_options(${_project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /INCREMENTAL:NO)
95-
target_link_options(${_project_name} INTERFACE /INCREMENTAL:NO)
115+
else()
116+
target_compile_options(${_project_name} INTERFACE /Zi)
96117
endif()
118+
target_compile_options(${_project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /INCREMENTAL:NO)
119+
target_link_options(${_project_name} INTERFACE /INCREMENTAL:NO)
97120
endif()
98121
endif()
99122

@@ -104,7 +127,7 @@ endfunction()
104127
``check_sanitizers_support``
105128
===============
106129
107-
Detect sanitizers support for compiler.
130+
Detect sanitizers support for compiler. You don't need to call this function directly anymore.
108131
109132
Note that some sanitizers cannot be enabled together, and this function doesn't check that. You should decide which sanitizers to enable based on your needs.
110133
@@ -115,6 +138,8 @@ Output variables:
115138
- ``ENABLE_SANITIZER_LEAK``: Leak sanitizer is supported
116139
- ``ENABLE_SANITIZER_THREAD``: Thread sanitizer is supported
117140
- ``ENABLE_SANITIZER_MEMORY``: Memory sanitizer is supported
141+
- ``ENABLE_SANITIZER_POINTER_COMPARE``: Pointer compare sanitizer is supported
142+
- ``ENABLE_SANITIZER_POINTER_SUBTRACT``: Pointer subtract sanitizer is supported
118143
119144
120145
.. code:: cmake
@@ -123,7 +148,9 @@ Output variables:
123148
ENABLE_SANITIZER_UNDEFINED_BEHAVIOR
124149
ENABLE_SANITIZER_LEAK
125150
ENABLE_SANITIZER_THREAD
126-
ENABLE_SANITIZER_MEMORY)
151+
ENABLE_SANITIZER_MEMORY
152+
ENABLE_SANITIZER_POINTER_COMPARE
153+
ENABLE_SANITIZER_POINTER_SUBTRACT)
127154
128155
# then pass the sanitizers (e.g. ${ENABLE_SANITIZER_ADDRESS}) to project_options(... ${ENABLE_SANITIZER_ADDRESS} ...)
129156
@@ -135,9 +162,13 @@ function(
135162
ENABLE_SANITIZER_LEAK
136163
ENABLE_SANITIZER_THREAD
137164
ENABLE_SANITIZER_MEMORY
165+
ENABLE_SANITIZER_POINTER_COMPARE
166+
ENABLE_SANITIZER_POINTER_SUBTRACT
138167
)
139-
set(SANITIZERS "")
140-
if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
168+
set(SUPPORTED_SANITIZERS "")
169+
if(NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" AND (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
170+
OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang")
171+
)
141172
set(HAS_SANITIZER_SUPPORT ON)
142173

143174
# Disable gcc sanitizer on some macos according to https://github.com/orgs/Homebrew/discussions/3384#discussioncomment-6264292
@@ -154,39 +185,58 @@ function(
154185
endif()
155186

156187
if(HAS_SANITIZER_SUPPORT)
157-
list(APPEND SANITIZERS "address")
158-
list(APPEND SANITIZERS "undefined")
159-
list(APPEND SANITIZERS "leak")
160-
list(APPEND SANITIZERS "thread")
161-
list(APPEND SANITIZERS "memory")
188+
set(SUPPORTED_SANITIZERS "")
189+
foreach(
190+
SANITIZER IN
191+
ITEMS "address"
192+
"undefined"
193+
"leak"
194+
"thread"
195+
"memory"
196+
"pointer-compare"
197+
"pointer-subtract"
198+
)
199+
if((SANITIZER STREQUAL "pointer-compare" OR SANITIZER STREQUAL "pointer-subtract")
200+
AND (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_VERSION VERSION_LESS 8)
201+
)
202+
# pointer-compare and pointer-subtract are supported only by GCC 8 and later
203+
continue()
204+
endif()
205+
206+
list(APPEND SUPPORTED_SANITIZERS ${SANITIZER})
207+
endforeach()
162208
endif()
163209
elseif(MSVC)
164210
# or it is MSVC and has run vcvarsall
165211
string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir)
166212
if(NOT "${index_of_vs_install_dir}" STREQUAL "-1")
167-
list(APPEND SANITIZERS "address")
213+
list(APPEND SUPPORTED_SANITIZERS "address")
168214
endif()
169215
endif()
170216

171-
list(JOIN SANITIZERS "," LIST_OF_SANITIZERS)
217+
if(NOT SUPPORTED_SANITIZERS OR "${SUPPORTED_SANITIZERS}" STREQUAL "")
218+
message(STATUS "No sanitizer is supported for the current platform/compiler")
219+
return()
220+
endif()
172221

173-
if(LIST_OF_SANITIZERS)
174-
if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "")
175-
if("address" IN_LIST SANITIZERS)
176-
set(${ENABLE_SANITIZER_ADDRESS} "ENABLE_SANITIZER_ADDRESS" PARENT_SCOPE)
177-
endif()
178-
if("undefined" IN_LIST SANITIZERS)
179-
set(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} "ENABLE_SANITIZER_UNDEFINED_BEHAVIOR" PARENT_SCOPE)
180-
endif()
181-
if("leak" IN_LIST SANITIZERS)
182-
set(${ENABLE_SANITIZER_LEAK} "ENABLE_SANITIZER_LEAK" PARENT_SCOPE)
183-
endif()
184-
if("thread" IN_LIST SANITIZERS)
185-
set(${ENABLE_SANITIZER_THREAD} "ENABLE_SANITIZER_THREAD" PARENT_SCOPE)
186-
endif()
187-
if("memory" IN_LIST SANITIZERS)
188-
set(${ENABLE_SANITIZER_MEMORY} "ENABLE_SANITIZER_MEMORY" PARENT_SCOPE)
189-
endif()
222+
# Set the output variables
223+
foreach(
224+
SANITIZER IN
225+
ITEMS "address"
226+
"undefined"
227+
"leak"
228+
"thread"
229+
"memory"
230+
"pointer-compare"
231+
"pointer-subtract"
232+
)
233+
set(SANITIZER_UPPERCASE "${SANITIZER}")
234+
string(TOUPPER ${SANITIZER} SANITIZER_UPPERCASE)
235+
236+
if(${SANITIZER} IN_LIST SUPPORTED_SANITIZERS)
237+
set(${ENABLE_SANITIZER_${SANITIZER_UPPERCASE}} "ENABLE_SANITIZER_${SANITIZER_UPPERCASE}" PARENT_SCOPE)
238+
else()
239+
set(${ENABLE_SANITIZER_${SANITIZER_UPPERCASE}} "" PARENT_SCOPE)
190240
endif()
191-
endif()
241+
endforeach()
192242
endfunction()

src/StaticAnalyzers.cmake

+2-3
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,8 @@ macro(enable_cppcheck CPPCHECK_OPTIONS)
5454
elseif(CMAKE_C_STANDARD MATCHES [[99|11]])
5555
set(CMAKE_C_CPPCHECK ${CMAKE_C_CPPCHECK} --std=c${CMAKE_C_STANDARD})
5656
else()
57-
message(
58-
${WARNING_MESSAGE}
59-
"cppcheck doesn't support specified C standard ${CMAKE_C_STANDARD}. Using the cppcheck default C standard version."
57+
message(${WARNING_MESSAGE}
58+
"cppcheck doesn't support C ${CMAKE_C_STANDARD} standard. Using the cppcheck default"
6059
)
6160
endif()
6261
endif()

0 commit comments

Comments
 (0)