+ implementation
This commit is contained in:
parent
0e18026fba
commit
87b4bfa795
10 changed files with 1446 additions and 0 deletions
52
CMakeLists.txt
Normal file
52
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# refcnt/CMakeLists.txt
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
|
||||||
|
project(xo-refcnt VERSION 0.1)
|
||||||
|
enable_language(CXX)
|
||||||
|
|
||||||
|
include(cmake/cxx.cmake)
|
||||||
|
include(cmake/code-coverage.cmake)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# unit test setup
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
|
||||||
|
# activate code coverage for all executables + libraries (when configured with -DCODE_COVERAGE=ON)
|
||||||
|
add_code_coverage()
|
||||||
|
# 1. assuming that /nix/store/ prefixes .hpp files belonging to gcc, catch2 etc.
|
||||||
|
# we're not interested in code coverage for these sources.
|
||||||
|
# 2. exclude the utest/ subdir, we don't need coverage on the unit tests themselves;
|
||||||
|
# rather, want coverage on the code that the unit tests exercise.
|
||||||
|
#
|
||||||
|
add_code_coverage_all_targets(EXCLUDE /nix/store/* utest/*)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# c++ settings
|
||||||
|
|
||||||
|
set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2")
|
||||||
|
|
||||||
|
add_definitions(${PROJECT_CXX_FLAGS})
|
||||||
|
|
||||||
|
if(NOT CMAKE_CXX_STANDARD)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
|
||||||
|
# always write compile_commands.json
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON CACHE INTERNAL "")
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# sources
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# install .hpp
|
||||||
|
|
||||||
|
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/ DESTINATION include)
|
||||||
|
|
||||||
|
# end CMakeLists.txt
|
||||||
678
cmake/code-coverage.cmake
Normal file
678
cmake/code-coverage.cmake
Normal file
|
|
@ -0,0 +1,678 @@
|
||||||
|
#
|
||||||
|
# Copyright (C) 2018-2020 by George Cave - gcave@stablecoder.ca
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
|
# use this file except in compliance with the License. You may obtain a copy of
|
||||||
|
# the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations under
|
||||||
|
# the License.
|
||||||
|
|
||||||
|
# USAGE: To enable any code coverage instrumentation/targets, the single CMake
|
||||||
|
# option of `CODE_COVERAGE` needs to be set to 'ON', either by GUI, ccmake, or
|
||||||
|
# on the command line.
|
||||||
|
#
|
||||||
|
# From this point, there are two primary methods for adding instrumentation to
|
||||||
|
# targets:
|
||||||
|
#
|
||||||
|
# 1 - A blanket instrumentation by calling `add_code_coverage()`, where
|
||||||
|
# all targets in that directory and all subdirectories are automatically
|
||||||
|
# instrumented.
|
||||||
|
#
|
||||||
|
# 2 - Per-target instrumentation by calling
|
||||||
|
# `target_code_coverage(<TARGET_NAME>)`, where the target is given and thus only
|
||||||
|
# that target is instrumented. This applies to both libraries and executables.
|
||||||
|
#
|
||||||
|
# To add coverage targets, such as calling `make ccov` to generate the actual
|
||||||
|
# coverage information for perusal or consumption, call
|
||||||
|
# `target_code_coverage(<TARGET_NAME>)` on an *executable* target.
|
||||||
|
#
|
||||||
|
# Example 1: All targets instrumented
|
||||||
|
#
|
||||||
|
# In this case, the coverage information reported will will be that of the
|
||||||
|
# `theLib` library target and `theExe` executable.
|
||||||
|
#
|
||||||
|
# 1a: Via global command
|
||||||
|
#
|
||||||
|
# ~~~
|
||||||
|
# add_code_coverage() # Adds instrumentation to all targets
|
||||||
|
#
|
||||||
|
# add_library(theLib lib.cpp)
|
||||||
|
#
|
||||||
|
# add_executable(theExe main.cpp)
|
||||||
|
# target_link_libraries(theExe PRIVATE theLib)
|
||||||
|
# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target
|
||||||
|
# # (instrumentation already added via global anyways)
|
||||||
|
# # for generating code coverage reports.
|
||||||
|
# ~~~
|
||||||
|
#
|
||||||
|
# 1b: Via target commands
|
||||||
|
#
|
||||||
|
# ~~~
|
||||||
|
# add_library(theLib lib.cpp)
|
||||||
|
# target_code_coverage(theLib) # As a library target, adds coverage instrumentation but no targets.
|
||||||
|
#
|
||||||
|
# add_executable(theExe main.cpp)
|
||||||
|
# target_link_libraries(theExe PRIVATE theLib)
|
||||||
|
# target_code_coverage(theExe) # As an executable target, adds the 'ccov-theExe' target and instrumentation for generating code coverage reports.
|
||||||
|
# ~~~
|
||||||
|
#
|
||||||
|
# Example 2: Target instrumented, but with regex pattern of files to be excluded
|
||||||
|
# from report
|
||||||
|
#
|
||||||
|
# ~~~
|
||||||
|
# add_executable(theExe main.cpp non_covered.cpp)
|
||||||
|
# target_code_coverage(theExe EXCLUDE non_covered.cpp test/*) # As an executable target, the reports will exclude the non-covered.cpp file, and any files in a test/ folder.
|
||||||
|
# ~~~
|
||||||
|
#
|
||||||
|
# Example 3: Target added to the 'ccov' and 'ccov-all' targets
|
||||||
|
#
|
||||||
|
# ~~~
|
||||||
|
# add_code_coverage_all_targets(EXCLUDE test/*) # Adds the 'ccov-all' target set and sets it to exclude all files in test/ folders.
|
||||||
|
#
|
||||||
|
# add_executable(theExe main.cpp non_covered.cpp)
|
||||||
|
# target_code_coverage(theExe AUTO ALL EXCLUDE non_covered.cpp test/*) # As an executable target, adds to the 'ccov' and ccov-all' targets, and the reports will exclude the non-covered.cpp file, and any files in a test/ folder.
|
||||||
|
# ~~~
|
||||||
|
|
||||||
|
# Options
|
||||||
|
option(
|
||||||
|
CODE_COVERAGE
|
||||||
|
"Builds targets with code coverage instrumentation. (Requires GCC or Clang)"
|
||||||
|
OFF)
|
||||||
|
|
||||||
|
# Programs
|
||||||
|
find_program(LLVM_COV_PATH llvm-cov)
|
||||||
|
find_program(LLVM_PROFDATA_PATH llvm-profdata)
|
||||||
|
find_program(LCOV_PATH lcov)
|
||||||
|
find_program(GENHTML_PATH genhtml)
|
||||||
|
# Hide behind the 'advanced' mode flag for GUI/ccmake
|
||||||
|
mark_as_advanced(FORCE LLVM_COV_PATH LLVM_PROFDATA_PATH LCOV_PATH GENHTML_PATH)
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
set(CMAKE_COVERAGE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/ccov)
|
||||||
|
set_property(GLOBAL PROPERTY JOB_POOLS ccov_serial_pool=1)
|
||||||
|
|
||||||
|
# Common initialization/checks
|
||||||
|
if(CODE_COVERAGE AND NOT CODE_COVERAGE_ADDED)
|
||||||
|
set(CODE_COVERAGE_ADDED ON)
|
||||||
|
|
||||||
|
# Common Targets
|
||||||
|
add_custom_target(
|
||||||
|
ccov-preprocessing
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}
|
||||||
|
DEPENDS ccov-clean)
|
||||||
|
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
|
||||||
|
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
|
||||||
|
# Messages
|
||||||
|
message(STATUS "Building with llvm Code Coverage Tools")
|
||||||
|
|
||||||
|
if(NOT LLVM_COV_PATH)
|
||||||
|
message(FATAL_ERROR "llvm-cov not found! Aborting.")
|
||||||
|
else()
|
||||||
|
# Version number checking for 'EXCLUDE' compatibility
|
||||||
|
execute_process(COMMAND ${LLVM_COV_PATH} --version
|
||||||
|
OUTPUT_VARIABLE LLVM_COV_VERSION_CALL_OUTPUT)
|
||||||
|
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" LLVM_COV_VERSION
|
||||||
|
${LLVM_COV_VERSION_CALL_OUTPUT})
|
||||||
|
|
||||||
|
if(LLVM_COV_VERSION VERSION_LESS "7.0.0")
|
||||||
|
message(
|
||||||
|
WARNING
|
||||||
|
"target_code_coverage()/add_code_coverage_all_targets() 'EXCLUDE' option only available on llvm-cov >= 7.0.0"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
|
||||||
|
add_custom_target(
|
||||||
|
ccov-clean
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E remove -f
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E remove -f
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
|
||||||
|
else()
|
||||||
|
add_custom_target(
|
||||||
|
ccov-clean
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -f
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -f
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Used to get the shared object file list before doing the main all-
|
||||||
|
# processing
|
||||||
|
add_custom_target(
|
||||||
|
ccov-libs
|
||||||
|
COMMAND ;
|
||||||
|
COMMENT "libs ready for coverage report.")
|
||||||
|
|
||||||
|
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
|
||||||
|
"GNU")
|
||||||
|
# Messages
|
||||||
|
message(STATUS "Building with lcov Code Coverage Tools")
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE)
|
||||||
|
string(TOUPPER ${CMAKE_BUILD_TYPE} upper_build_type)
|
||||||
|
if(NOT ${upper_build_type} STREQUAL "DEBUG")
|
||||||
|
message(
|
||||||
|
WARNING
|
||||||
|
"Code coverage results with an optimized (non-Debug) build may be misleading"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(
|
||||||
|
WARNING
|
||||||
|
"Code coverage results with an optimized (non-Debug) build may be misleading"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
if(NOT LCOV_PATH)
|
||||||
|
message(FATAL_ERROR "lcov not found! Aborting...")
|
||||||
|
endif()
|
||||||
|
if(NOT GENHTML_PATH)
|
||||||
|
message(FATAL_ERROR "genhtml not found! Aborting...")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
add_custom_target(ccov-clean COMMAND ${LCOV_PATH} --directory
|
||||||
|
${CMAKE_BINARY_DIR} --zerocounters)
|
||||||
|
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Code coverage requires Clang or GCC. Aborting.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Adds code coverage instrumentation to a library, or instrumentation/targets
|
||||||
|
# for an executable target.
|
||||||
|
# ~~~
|
||||||
|
# EXECUTABLE ADDED TARGETS:
|
||||||
|
# GCOV/LCOV:
|
||||||
|
# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
|
||||||
|
# ccov-${TARGET_NAME} : Generates HTML code coverage report for the associated named target.
|
||||||
|
# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
|
||||||
|
#
|
||||||
|
# LLVM-COV:
|
||||||
|
# ccov : Generates HTML code coverage report for every target added with 'AUTO' parameter.
|
||||||
|
# ccov-report : Generates HTML code coverage report for every target added with 'AUTO' parameter.
|
||||||
|
# ccov-${TARGET_NAME} : Generates HTML code coverage report.
|
||||||
|
# ccov-report-${TARGET_NAME} : Prints to command line summary per-file coverage information.
|
||||||
|
# ccov-export-${TARGET_NAME} : Exports the coverage report to a JSON file.
|
||||||
|
# ccov-show-${TARGET_NAME} : Prints to command line detailed per-line coverage information.
|
||||||
|
# ccov-all : Generates HTML code coverage report, merging every target added with 'ALL' parameter into a single detailed report.
|
||||||
|
# ccov-all-report : Prints summary per-file coverage information for every target added with ALL' parameter to the command line.
|
||||||
|
# ccov-all-export : Exports the coverage report to a JSON file.
|
||||||
|
#
|
||||||
|
# Required:
|
||||||
|
# TARGET_NAME - Name of the target to generate code coverage for.
|
||||||
|
# Optional:
|
||||||
|
# PUBLIC - Sets the visibility for added compile options to targets to PUBLIC instead of the default of PRIVATE.
|
||||||
|
# INTERFACE - Sets the visibility for added compile options to targets to INTERFACE instead of the default of PRIVATE.
|
||||||
|
# PLAIN - Do not set any target visibility (backward compatibility with old cmake projects)
|
||||||
|
# AUTO - Adds the target to the 'ccov' target so that it can be run in a batch with others easily. Effective on executable targets.
|
||||||
|
# ALL - Adds the target to the 'ccov-all' and 'ccov-all-report' targets, which merge several executable targets coverage data to a single report. Effective on executable targets.
|
||||||
|
# EXTERNAL - For GCC's lcov, allows the profiling of 'external' files from the processing directory
|
||||||
|
# COVERAGE_TARGET_NAME - For executables ONLY, changes the outgoing target name so instead of `ccov-${TARGET_NAME}` it becomes `ccov-${COVERAGE_TARGET_NAME}`.
|
||||||
|
# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex! **These do not copy to the 'all' targets.**
|
||||||
|
# OBJECTS <TARGETS> - For executables ONLY, if the provided targets are shared libraries, adds coverage information to the output
|
||||||
|
# ARGS <ARGUMENTS> - For executables ONLY, appends the given arguments to the associated ccov-* executable call
|
||||||
|
# ~~~
|
||||||
|
function(target_code_coverage TARGET_NAME)
|
||||||
|
# Argument parsing
|
||||||
|
set(options AUTO ALL EXTERNAL PUBLIC INTERFACE PLAIN)
|
||||||
|
set(single_value_keywords COVERAGE_TARGET_NAME)
|
||||||
|
set(multi_value_keywords EXCLUDE OBJECTS ARGS)
|
||||||
|
cmake_parse_arguments(
|
||||||
|
target_code_coverage "${options}" "${single_value_keywords}"
|
||||||
|
"${multi_value_keywords}" ${ARGN})
|
||||||
|
|
||||||
|
# Set the visibility of target functions to PUBLIC, INTERFACE or default to
|
||||||
|
# PRIVATE.
|
||||||
|
if(target_code_coverage_PUBLIC)
|
||||||
|
set(TARGET_VISIBILITY PUBLIC)
|
||||||
|
set(TARGET_LINK_VISIBILITY PUBLIC)
|
||||||
|
elseif(target_code_coverage_INTERFACE)
|
||||||
|
set(TARGET_VISIBILITY INTERFACE)
|
||||||
|
set(TARGET_LINK_VISIBILITY INTERFACE)
|
||||||
|
elseif(target_code_coverage_PLAIN)
|
||||||
|
set(TARGET_VISIBILITY PUBLIC)
|
||||||
|
set(TARGET_LINK_VISIBILITY)
|
||||||
|
else()
|
||||||
|
set(TARGET_VISIBILITY PRIVATE)
|
||||||
|
set(TARGET_LINK_VISIBILITY PRIVATE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT target_code_coverage_COVERAGE_TARGET_NAME)
|
||||||
|
# If a specific name was given, use that instead.
|
||||||
|
set(target_code_coverage_COVERAGE_TARGET_NAME ${TARGET_NAME})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CODE_COVERAGE)
|
||||||
|
|
||||||
|
# Add code coverage instrumentation to the target's linker command
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
|
||||||
|
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
|
||||||
|
target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY}
|
||||||
|
-fprofile-instr-generate -fcoverage-mapping)
|
||||||
|
target_link_options(${TARGET_NAME} ${TARGET_VISIBILITY}
|
||||||
|
-fprofile-instr-generate -fcoverage-mapping)
|
||||||
|
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
|
||||||
|
"GNU")
|
||||||
|
target_compile_options(${TARGET_NAME} ${TARGET_VISIBILITY} -fprofile-arcs
|
||||||
|
-ftest-coverage)
|
||||||
|
target_link_libraries(${TARGET_NAME} ${TARGET_LINK_VISIBILITY} gcov)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
get_target_property(target_type ${TARGET_NAME} TYPE)
|
||||||
|
|
||||||
|
# Add shared library to processing for 'all' targets
|
||||||
|
if(target_type STREQUAL "SHARED_LIBRARY" AND target_code_coverage_ALL)
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
|
||||||
|
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
|
||||||
|
add_custom_target(
|
||||||
|
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>" >>
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
|
||||||
|
DEPENDS ccov-preprocessing ${TARGET_NAME})
|
||||||
|
|
||||||
|
if(NOT TARGET ccov-libs)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_dependencies(ccov-libs
|
||||||
|
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# For executables add targets to run and produce output
|
||||||
|
if(target_type STREQUAL "EXECUTABLE")
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
|
||||||
|
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
|
||||||
|
|
||||||
|
# If there are shared objects to also work with, generate the string to
|
||||||
|
# add them here
|
||||||
|
foreach(SO_TARGET ${target_code_coverage_OBJECTS})
|
||||||
|
# Check to see if the target is a shared object
|
||||||
|
if(TARGET ${SO_TARGET})
|
||||||
|
get_target_property(SO_TARGET_TYPE ${SO_TARGET} TYPE)
|
||||||
|
if(${SO_TARGET_TYPE} STREQUAL "SHARED_LIBRARY")
|
||||||
|
set(SO_OBJECTS ${SO_OBJECTS} -object=$<TARGET_FILE:${SO_TARGET}>)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
# Run the executable, generating raw profile data Make the run data
|
||||||
|
# available for further processing. Separated to allow Windows to run
|
||||||
|
# this target serially.
|
||||||
|
add_custom_target(
|
||||||
|
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${CMAKE_COMMAND} -E env
|
||||||
|
LLVM_PROFILE_FILE=${target_code_coverage_COVERAGE_TARGET_NAME}.profraw
|
||||||
|
$<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
|
||||||
|
COMMAND
|
||||||
|
${CMAKE_COMMAND} -E echo "-object=$<TARGET_FILE:${TARGET_NAME}>"
|
||||||
|
${SO_OBJECTS} >> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list
|
||||||
|
COMMAND
|
||||||
|
${CMAKE_COMMAND} -E echo
|
||||||
|
"${CMAKE_CURRENT_BINARY_DIR}/${target_code_coverage_COVERAGE_TARGET_NAME}.profraw"
|
||||||
|
>> ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list
|
||||||
|
JOB_POOL ccov_serial_pool
|
||||||
|
DEPENDS ccov-preprocessing ccov-libs ${TARGET_NAME})
|
||||||
|
|
||||||
|
# Merge the generated profile data so llvm-cov can process it
|
||||||
|
add_custom_target(
|
||||||
|
ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${LLVM_PROFDATA_PATH} merge -sparse
|
||||||
|
${target_code_coverage_COVERAGE_TARGET_NAME}.profraw -o
|
||||||
|
${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
|
||||||
|
DEPENDS ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
|
||||||
|
# Ignore regex only works on LLVM >= 7
|
||||||
|
if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
|
||||||
|
foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
|
||||||
|
set(EXCLUDE_REGEX ${EXCLUDE_REGEX}
|
||||||
|
-ignore-filename-regex='${EXCLUDE_ITEM}')
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Print out details of the coverage information to the command line
|
||||||
|
add_custom_target(
|
||||||
|
ccov-show-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
|
||||||
|
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
|
||||||
|
-show-line-counts-or-regions ${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
|
||||||
|
# Print out a summary of the coverage information to the command line
|
||||||
|
add_custom_target(
|
||||||
|
ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} report $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
|
||||||
|
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
|
||||||
|
${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
|
||||||
|
# Export coverage information so continuous integration tools (e.g.
|
||||||
|
# Jenkins) can consume it
|
||||||
|
add_custom_target(
|
||||||
|
ccov-export-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} export $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
|
||||||
|
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
|
||||||
|
-format="text" ${EXCLUDE_REGEX} >
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.json
|
||||||
|
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
|
||||||
|
# Generates HTML output of the coverage information for perusal
|
||||||
|
add_custom_target(
|
||||||
|
ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} show $<TARGET_FILE:${TARGET_NAME}> ${SO_OBJECTS}
|
||||||
|
-instr-profile=${target_code_coverage_COVERAGE_TARGET_NAME}.profdata
|
||||||
|
-show-line-counts-or-regions
|
||||||
|
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
-format="html" ${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-processing-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
|
||||||
|
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
|
||||||
|
"GNU")
|
||||||
|
set(COVERAGE_INFO
|
||||||
|
"${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}.info"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run the executable, generating coverage information
|
||||||
|
add_custom_target(
|
||||||
|
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
|
||||||
|
DEPENDS ccov-preprocessing ${TARGET_NAME})
|
||||||
|
|
||||||
|
# Generate exclusion string for use
|
||||||
|
foreach(EXCLUDE_ITEM ${target_code_coverage_EXCLUDE})
|
||||||
|
set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
|
||||||
|
'${EXCLUDE_ITEM}')
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(EXCLUDE_REGEX)
|
||||||
|
set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file
|
||||||
|
${COVERAGE_INFO})
|
||||||
|
else()
|
||||||
|
set(EXCLUDE_COMMAND ;)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT ${target_code_coverage_EXTERNAL})
|
||||||
|
set(EXTERNAL_OPTION --no-external)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Capture coverage data
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
|
||||||
|
add_custom_target(
|
||||||
|
ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
|
||||||
|
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
|
||||||
|
COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
|
||||||
|
COMMAND
|
||||||
|
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
|
||||||
|
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
|
||||||
|
${COVERAGE_INFO}
|
||||||
|
COMMAND ${EXCLUDE_COMMAND}
|
||||||
|
DEPENDS ccov-preprocessing ${TARGET_NAME})
|
||||||
|
else()
|
||||||
|
add_custom_target(
|
||||||
|
ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
|
||||||
|
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
|
||||||
|
COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
|
||||||
|
COMMAND
|
||||||
|
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
|
||||||
|
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
|
||||||
|
${COVERAGE_INFO}
|
||||||
|
COMMAND ${EXCLUDE_COMMAND}
|
||||||
|
DEPENDS ccov-preprocessing ${TARGET_NAME})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Generates HTML output of the coverage information for perusal
|
||||||
|
add_custom_target(
|
||||||
|
ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
COMMAND
|
||||||
|
${GENHTML_PATH} -o
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
${COVERAGE_INFO}
|
||||||
|
DEPENDS ccov-capture-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ccov-${target_code_coverage_COVERAGE_TARGET_NAME}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ;
|
||||||
|
COMMENT
|
||||||
|
"Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/${target_code_coverage_COVERAGE_TARGET_NAME}/index.html in your browser to view the coverage report."
|
||||||
|
)
|
||||||
|
|
||||||
|
# AUTO
|
||||||
|
if(target_code_coverage_AUTO)
|
||||||
|
if(NOT TARGET ccov)
|
||||||
|
add_custom_target(ccov)
|
||||||
|
endif()
|
||||||
|
add_dependencies(ccov ccov-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
|
||||||
|
if(NOT CMAKE_C_COMPILER_ID MATCHES "GNU" AND NOT CMAKE_CXX_COMPILER_ID
|
||||||
|
MATCHES "GNU")
|
||||||
|
if(NOT TARGET ccov-report)
|
||||||
|
add_custom_target(ccov-report)
|
||||||
|
endif()
|
||||||
|
add_dependencies(
|
||||||
|
ccov-report
|
||||||
|
ccov-report-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ALL
|
||||||
|
if(target_code_coverage_ALL)
|
||||||
|
if(NOT TARGET ccov-all-processing)
|
||||||
|
message(
|
||||||
|
FATAL_ERROR
|
||||||
|
"Calling target_code_coverage with 'ALL' must be after a call to 'add_code_coverage_all_targets'."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_dependencies(ccov-all-processing
|
||||||
|
ccov-run-${target_code_coverage_COVERAGE_TARGET_NAME})
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Adds code coverage instrumentation to all targets in the current directory and
|
||||||
|
# any subdirectories. To add coverage instrumentation to only specific targets,
|
||||||
|
# use `target_code_coverage`.
|
||||||
|
function(add_code_coverage)
|
||||||
|
if(CODE_COVERAGE)
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
|
||||||
|
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
|
||||||
|
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
|
||||||
|
add_link_options(-fprofile-instr-generate -fcoverage-mapping)
|
||||||
|
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
|
||||||
|
"GNU")
|
||||||
|
add_compile_options(-fprofile-arcs -ftest-coverage)
|
||||||
|
link_libraries(gcov)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Adds the 'ccov-all' type targets that calls all targets added via
|
||||||
|
# `target_code_coverage` with the `ALL` parameter, but merges all the coverage
|
||||||
|
# data from them into a single large report instead of the numerous smaller
|
||||||
|
# reports. Also adds the ccov-all-capture Generates an all-merged.info file, for
|
||||||
|
# use with coverage dashboards (e.g. codecov.io, coveralls).
|
||||||
|
# ~~~
|
||||||
|
# Optional:
|
||||||
|
# EXCLUDE <PATTERNS> - Excludes files of the patterns provided from coverage. Note that GCC/lcov excludes by glob pattern, and clang/LLVM excludes via regex!
|
||||||
|
# ~~~
|
||||||
|
function(add_code_coverage_all_targets)
|
||||||
|
# Argument parsing
|
||||||
|
set(multi_value_keywords EXCLUDE)
|
||||||
|
cmake_parse_arguments(add_code_coverage_all_targets "" ""
|
||||||
|
"${multi_value_keywords}" ${ARGN})
|
||||||
|
|
||||||
|
if(CODE_COVERAGE)
|
||||||
|
if(CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang"
|
||||||
|
OR CMAKE_CXX_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
|
||||||
|
|
||||||
|
# Merge the profile data for all of the run executables
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-processing
|
||||||
|
COMMAND
|
||||||
|
powershell -Command $$FILELIST = Get-Content
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list\; llvm-profdata.exe
|
||||||
|
merge -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
|
||||||
|
-sparse $$FILELIST)
|
||||||
|
else()
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-processing
|
||||||
|
COMMAND
|
||||||
|
${LLVM_PROFDATA_PATH} merge -o
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata -sparse `cat
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/profraw.list`)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Regex exclude only available for LLVM >= 7
|
||||||
|
if(LLVM_COV_VERSION VERSION_GREATER_EQUAL "7.0.0")
|
||||||
|
foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
|
||||||
|
set(EXCLUDE_REGEX ${EXCLUDE_REGEX}
|
||||||
|
-ignore-filename-regex='${EXCLUDE_ITEM}')
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Print summary of the code coverage information to the command line
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-report
|
||||||
|
COMMAND
|
||||||
|
powershell -Command $$FILELIST = Get-Content
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe
|
||||||
|
report $$FILELIST
|
||||||
|
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
|
||||||
|
${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-all-processing)
|
||||||
|
else()
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-report
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} report `cat
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
|
||||||
|
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
|
||||||
|
${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-all-processing)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Export coverage information so continuous integration tools (e.g.
|
||||||
|
# Jenkins) can consume it
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-export
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} export `cat
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
|
||||||
|
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
|
||||||
|
-format="text" ${EXCLUDE_REGEX} >
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/coverage.json
|
||||||
|
DEPENDS ccov-all-processing)
|
||||||
|
|
||||||
|
# Generate HTML output of all added targets for perusal
|
||||||
|
if(WIN32)
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all
|
||||||
|
COMMAND
|
||||||
|
powershell -Command $$FILELIST = Get-Content
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list\; llvm-cov.exe show
|
||||||
|
$$FILELIST
|
||||||
|
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
|
||||||
|
-show-line-counts-or-regions
|
||||||
|
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
|
||||||
|
-format="html" ${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-all-processing)
|
||||||
|
else()
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all
|
||||||
|
COMMAND
|
||||||
|
${LLVM_COV_PATH} show `cat
|
||||||
|
${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/binaries.list`
|
||||||
|
-instr-profile=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.profdata
|
||||||
|
-show-line-counts-or-regions
|
||||||
|
-output-dir=${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
|
||||||
|
-format="html" ${EXCLUDE_REGEX}
|
||||||
|
DEPENDS ccov-all-processing)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
elseif(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES
|
||||||
|
"GNU")
|
||||||
|
set(COVERAGE_INFO "${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged.info")
|
||||||
|
|
||||||
|
# Nothing required for gcov
|
||||||
|
add_custom_target(ccov-all-processing COMMAND ;)
|
||||||
|
|
||||||
|
# Exclusion regex string creation
|
||||||
|
set(EXCLUDE_REGEX)
|
||||||
|
foreach(EXCLUDE_ITEM ${add_code_coverage_all_targets_EXCLUDE})
|
||||||
|
set(EXCLUDE_REGEX ${EXCLUDE_REGEX} --remove ${COVERAGE_INFO}
|
||||||
|
'${EXCLUDE_ITEM}')
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
if(EXCLUDE_REGEX)
|
||||||
|
set(EXCLUDE_COMMAND ${LCOV_PATH} ${EXCLUDE_REGEX} --output-file
|
||||||
|
${COVERAGE_INFO})
|
||||||
|
else()
|
||||||
|
set(EXCLUDE_COMMAND ;)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Capture coverage data
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-capture
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E remove -f ${COVERAGE_INFO}
|
||||||
|
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
|
||||||
|
--output-file ${COVERAGE_INFO}
|
||||||
|
COMMAND ${EXCLUDE_COMMAND}
|
||||||
|
DEPENDS ccov-preprocessing ccov-all-processing)
|
||||||
|
else()
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all-capture
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E rm -f ${COVERAGE_INFO}
|
||||||
|
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --capture
|
||||||
|
--output-file ${COVERAGE_INFO}
|
||||||
|
COMMAND ${EXCLUDE_COMMAND}
|
||||||
|
DEPENDS ccov-preprocessing ccov-all-processing)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Generates HTML output of all targets for perusal
|
||||||
|
add_custom_target(
|
||||||
|
ccov-all
|
||||||
|
COMMAND ${GENHTML_PATH} -o ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged
|
||||||
|
${COVERAGE_INFO} -p ${CMAKE_SOURCE_DIR}
|
||||||
|
DEPENDS ccov-all-capture)
|
||||||
|
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET ccov-all
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ;
|
||||||
|
COMMENT
|
||||||
|
"Open ${CMAKE_COVERAGE_OUTPUT_DIRECTORY}/all-merged/index.html in your browser to view the coverage report."
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
86
cmake/cxx.cmake
Normal file
86
cmake/cxx.cmake
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# use this in subdirs that compile c++ code
|
||||||
|
#
|
||||||
|
macro(xo_include_options target)
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# PROJECT_SOURCE_DIR:
|
||||||
|
# so we can for example write
|
||||||
|
# #include "ordinaltree/foo.hpp"
|
||||||
|
# from anywhere in the project
|
||||||
|
# PROJECT_BINARY_DIR:
|
||||||
|
# since generated version file will be in build directory,
|
||||||
|
# need that build directory to also appear in
|
||||||
|
# compiler's include path
|
||||||
|
#
|
||||||
|
target_include_directories(
|
||||||
|
${target} PUBLIC
|
||||||
|
${PROJECT_SOURCE_DIR}/include # e.g. for #include "indentlog/scope.hpp"
|
||||||
|
${PROJECT_SOURCE_DIR}/include/${target} # e.g. for #include "Refcounted.hpp" in refcnt/src
|
||||||
|
${PROJECT_BINARY_DIR} # e.g. for generated config.hpp file
|
||||||
|
)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# make standard directories for std:: includes explicit
|
||||||
|
# so that
|
||||||
|
# (1) they appear in compile_commands.json.
|
||||||
|
# (2) clangd (run from emacs lsp-mode) can find them
|
||||||
|
#
|
||||||
|
if(CMAKE_EXPORT_COMPILE_COMMANDS)
|
||||||
|
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES})
|
||||||
|
endif()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# variable
|
||||||
|
# XO_ADDRESS_SANITIZE
|
||||||
|
# determines whether to enable address sanitizer for the XO project
|
||||||
|
# (see toplevel CMakeLists.txt)
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
if(XO_ADDRESS_SANITIZE)
|
||||||
|
add_compile_options(-fsanitize=address)
|
||||||
|
add_link_options(-fsanitize=address)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# XO_STANDARD_COMPILE_OPTIONS: use these when XO_ADDRESS_SANITIZE=OFF
|
||||||
|
set(XO_STANDARD_COMPILE_OPTIONS -Werror -Wall -Wextra)
|
||||||
|
|
||||||
|
# XO_ADDRESS_SANITIZE_COMPILE_OPTIONS: use when XO_ADDRESS_SANITIZE=ON
|
||||||
|
#
|
||||||
|
# address sanitizer build complains about _FORTIFY_SOURCE redefines
|
||||||
|
# In file included from <built-in>:460:
|
||||||
|
# <command line>:1:9: error: '_FORTIFY_SOURCE' macro redefined [-Werror,-Wmacro-redefined]
|
||||||
|
# #define _FORTIFY_SOURCE 2
|
||||||
|
#
|
||||||
|
set(XO_ADDRESS_SANITIZE_COMPILE_OPTIONS -Werror -Wall -Wextra -Wno-macro-redefined)
|
||||||
|
|
||||||
|
# XO_COMPILE_OPTIONS: use these with xo_compile_options() macro
|
||||||
|
if(XO_ADDRESS_SANITIZE)
|
||||||
|
set(XO_COMPILE_OPTIONS ${XO_ADDRESS_SANITIZE_COMPILE_OPTIONS})
|
||||||
|
else()
|
||||||
|
set(XO_COMPILE_OPTIONS ${XO_STANDARD_COMPILE_OPTIONS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# generally want all the errors+warnings!
|
||||||
|
# however: address sanitizer generates error on _FORTIFY_SOURCE
|
||||||
|
#
|
||||||
|
macro(xo_compile_options target)
|
||||||
|
target_compile_options(${target} PRIVATE ${XO_COMPILE_OPTIONS})
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# use this for a subdir that builds a library
|
||||||
|
#
|
||||||
|
macro(xo_install_library target)
|
||||||
|
install(TARGETS ${target} DESTINATION lib)
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------
|
||||||
|
# use this when relying on indentlog [[https://github.com/rconybea/indentlog]] headers
|
||||||
|
#
|
||||||
|
macro(xo_indentlog_dependency target)
|
||||||
|
find_package(indentlog REQUIRED)
|
||||||
|
#add_dependencies(${target} indentlog)
|
||||||
|
target_link_libraries(${target} PUBLIC indentlog)
|
||||||
|
#target_include_directories(${target} PUBLIC ${indentlog_DIR}/../../../include)
|
||||||
|
endmacro()
|
||||||
92
include/cxxutil/demangle.hpp
Normal file
92
include/cxxutil/demangle.hpp
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
/* @file demangle.hpp */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <array> // std::array
|
||||||
|
#include <utility> // std::index_sequence
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace reflect {
|
||||||
|
|
||||||
|
template <std::size_t...Idxs>
|
||||||
|
constexpr auto
|
||||||
|
substring_as_array(std::string_view str,
|
||||||
|
std::index_sequence<Idxs...> indexes)
|
||||||
|
{
|
||||||
|
//return std::array<char, indexes.size()+1>{str[Idxs]..., '\n'};
|
||||||
|
return std::array<char, indexes.size()>{str[Idxs]...};
|
||||||
|
} /*substring_as_array*/
|
||||||
|
|
||||||
|
template <typename T> constexpr auto type_name_array() {
|
||||||
|
#if defined(__clang__)
|
||||||
|
constexpr auto prefix = std::string_view{"[T = "};
|
||||||
|
constexpr auto suffix = std::string_view{"]"};
|
||||||
|
constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
|
||||||
|
#elif defined(__GNUC__)
|
||||||
|
constexpr auto prefix = std::string_view{"with T = "};
|
||||||
|
constexpr auto suffix = std::string_view{"]"};
|
||||||
|
constexpr auto function = std::string_view{__PRETTY_FUNCTION__};
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
|
constexpr auto prefix = std::string_view{"type_name_array<"};
|
||||||
|
constexpr auto suffix = std::string_view{">(void)"};
|
||||||
|
constexpr auto function = std::string_view{__FUNCSIG__};
|
||||||
|
#else
|
||||||
|
# error type_name_array: Unsupported compiler
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr auto start = function.find(prefix) + prefix.size();
|
||||||
|
constexpr auto end = function.rfind(suffix);
|
||||||
|
|
||||||
|
//static_assert(start < end);
|
||||||
|
|
||||||
|
constexpr auto name = function.substr(start, (end - start));
|
||||||
|
|
||||||
|
constexpr auto ixseq = std::make_index_sequence<name.size()>{};
|
||||||
|
|
||||||
|
return substring_as_array(name, ixseq);
|
||||||
|
} /*type_name_array*/
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct type_name_holder {
|
||||||
|
static inline constexpr auto value = type_name_array<T>();
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
constexpr auto type_name() -> std::string_view
|
||||||
|
{
|
||||||
|
constexpr auto& value = type_name_holder<T>::value;
|
||||||
|
return std::string_view{value.data(), value.size()};
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef NOT_IN_USE
|
||||||
|
template <std::string_view const&... Strs>
|
||||||
|
struct join
|
||||||
|
{
|
||||||
|
// Join all strings into a single std::array of chars
|
||||||
|
static constexpr auto impl() noexcept
|
||||||
|
{
|
||||||
|
constexpr std::size_t len = (Strs.size() + ... + 0);
|
||||||
|
std::array<char, len + 1> arr{};
|
||||||
|
auto append = [i = 0, &arr](auto const& s) mutable {
|
||||||
|
for (auto c : s) arr[i++] = c;
|
||||||
|
};
|
||||||
|
(append(Strs), ...);
|
||||||
|
arr[len] = 0;
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
// Give the joined string static storage
|
||||||
|
static constexpr auto arr = impl();
|
||||||
|
// View as a std::string_view
|
||||||
|
static constexpr std::string_view value {arr.data(), arr.size() - 1};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper to get the value out
|
||||||
|
template <std::string_view const&... Strs>
|
||||||
|
static constexpr auto join_v = join<Strs...>::value;
|
||||||
|
#endif
|
||||||
|
} /*namespace reflect*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end demangle.hpp */
|
||||||
29
include/refcnt/Displayable.hpp
Normal file
29
include/refcnt/Displayable.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/* @file Displayable.hpp */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "refcnt/Refcounted.hpp"
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace ref {
|
||||||
|
class Displayable : public Refcount {
|
||||||
|
public:
|
||||||
|
/* write some kind of human-readable representation on stream */
|
||||||
|
virtual void display(std::ostream & os) const = 0;
|
||||||
|
std::string display_string() const;
|
||||||
|
}; /*Displayable*/
|
||||||
|
|
||||||
|
/* see also
|
||||||
|
* operator<<(std::ostream &, intrusive_ptr<T> const &)
|
||||||
|
* in [Refcounted.hpp]
|
||||||
|
*/
|
||||||
|
inline std::ostream &
|
||||||
|
operator<<(std::ostream &os, Displayable const & x) {
|
||||||
|
x.display(os);
|
||||||
|
return os;
|
||||||
|
} /*operator<<*/
|
||||||
|
|
||||||
|
} /*namespace ref*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end Displayable.hpp */
|
||||||
294
include/refcnt/Refcounted.hpp
Normal file
294
include/refcnt/Refcounted.hpp
Normal file
|
|
@ -0,0 +1,294 @@
|
||||||
|
/* @file Refcounted.hpp */
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "indentlog/scope.hpp"
|
||||||
|
#include "cxxutil/demangle.hpp"
|
||||||
|
|
||||||
|
//#include <boost/intrusive_ptr.hpp>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace ref {
|
||||||
|
class Refcount;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class Borrow;
|
||||||
|
|
||||||
|
/* originally used boost::instrusive_ptr<>.
|
||||||
|
* ran into a bug. probably mine, but implemented
|
||||||
|
* refcounting inline for debugging
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
class intrusive_ptr {
|
||||||
|
public:
|
||||||
|
using element_type = T;
|
||||||
|
|
||||||
|
public:
|
||||||
|
intrusive_ptr() : ptr_(nullptr) {}
|
||||||
|
intrusive_ptr(T * x) : ptr_(x) {
|
||||||
|
intrusive_ptr_log_ctor(sc_self_type, this, x);
|
||||||
|
intrusive_ptr_add_ref(ptr_);
|
||||||
|
} /*ctor*/
|
||||||
|
|
||||||
|
/* NOTE: need exactly this form for copy-constructor
|
||||||
|
* clang11 will not recognize template form below as
|
||||||
|
* supplying copy ctor, and default version is broken for
|
||||||
|
* instrusive_ptr.
|
||||||
|
*/
|
||||||
|
intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) {
|
||||||
|
intrusive_ptr_log_cctor(sc_self_type, this, x.get());
|
||||||
|
intrusive_ptr_add_ref(ptr_);
|
||||||
|
} /*cctor*/
|
||||||
|
|
||||||
|
/* create from instrusive pointer to some related type S */
|
||||||
|
template<typename S>
|
||||||
|
intrusive_ptr(intrusive_ptr<S> const & x) : ptr_(x.get()) {
|
||||||
|
intrusive_ptr_log_cctor(sc_self_type, this, x.get());
|
||||||
|
intrusive_ptr_add_ref(ptr_);
|
||||||
|
} /*cctor*/
|
||||||
|
|
||||||
|
/* move ctor -- in this case don't need to update refcount */
|
||||||
|
intrusive_ptr(intrusive_ptr && x) : ptr_{std::move(x.ptr_)} {
|
||||||
|
intrusive_ptr_log_mctor(sc_self_type, this, ptr_);
|
||||||
|
/* since we're moving from x, need to make sure x dtor
|
||||||
|
* doesn't decrement refcount
|
||||||
|
*/
|
||||||
|
x.ptr_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* aliasing ctor. see ctor (8) here:
|
||||||
|
* [[https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr]]
|
||||||
|
* and this dicsussion:
|
||||||
|
* [[https://stackoverflow.com/questions/49178231/pybind11-multiple-inheritance-with-custom-holder-type-fails-to-cast-to-base-type/73131206#73131206]]
|
||||||
|
*/
|
||||||
|
template<typename Y>
|
||||||
|
intrusive_ptr(intrusive_ptr<Y> const & /*x*/, element_type * y) : ptr_{y} {
|
||||||
|
if (std::is_same<Y, element_type>()) {
|
||||||
|
intrusive_ptr_log_actor(sc_self_type, this, y);
|
||||||
|
intrusive_ptr_add_ref(ptr_);
|
||||||
|
; /* trivial aliasing, proceed */
|
||||||
|
} else {
|
||||||
|
using xo::xtag;
|
||||||
|
throw std::runtime_error(tostr("attempt to use aliasing ctor with",
|
||||||
|
xtag("Y", reflect::type_name<Y>()),
|
||||||
|
xtag("T", reflect::type_name<T>())));
|
||||||
|
}
|
||||||
|
} /*ctor*/
|
||||||
|
|
||||||
|
~intrusive_ptr() {
|
||||||
|
T * x = this->ptr_;
|
||||||
|
|
||||||
|
intrusive_ptr_log_dtor(sc_self_type, this, x);
|
||||||
|
|
||||||
|
this->ptr_ = nullptr;
|
||||||
|
|
||||||
|
intrusive_ptr_release(x);
|
||||||
|
} /*dtor*/
|
||||||
|
|
||||||
|
static bool compare(intrusive_ptr<T> const & x,
|
||||||
|
intrusive_ptr<T> const & y) {
|
||||||
|
return ptrdiff_t(x.get() - y.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Borrow<T> borrow() const;
|
||||||
|
|
||||||
|
T * get() const { return ptr_; }
|
||||||
|
|
||||||
|
T * operator->() const { return ptr_; }
|
||||||
|
|
||||||
|
operator bool() const { return ptr_ != nullptr; }
|
||||||
|
|
||||||
|
intrusive_ptr<T> & operator=(intrusive_ptr<T> const & rhs) {
|
||||||
|
T * x = rhs.get();
|
||||||
|
|
||||||
|
intrusive_ptr_log_assign(sc_self_type, this, x);
|
||||||
|
|
||||||
|
T * old = this->ptr_;
|
||||||
|
this->ptr_ = x;
|
||||||
|
|
||||||
|
intrusive_ptr_add_ref(x);
|
||||||
|
intrusive_ptr_release(old);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
} /*operator=*/
|
||||||
|
|
||||||
|
intrusive_ptr<T> & operator=(intrusive_ptr<T> && rhs) {
|
||||||
|
intrusive_ptr_log_massign(sc_self_type, this, rhs.get());
|
||||||
|
|
||||||
|
std::swap(this->ptr_, rhs.ptr_);
|
||||||
|
|
||||||
|
/* dtor on rhs will decrement refcount on old value of this->ptr_
|
||||||
|
* don't increment for new value, since refcount just transfers from rhs to *this
|
||||||
|
*/
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
} /*operator=*/
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr std::string_view sc_self_type = xo::reflect::type_name<intrusive_ptr<T>>();
|
||||||
|
|
||||||
|
private:
|
||||||
|
T * ptr_ = nullptr;
|
||||||
|
}; /*intrusive_ptr*/
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline bool operator==(intrusive_ptr<T> const & x, intrusive_ptr<T> const & y) { return intrusive_ptr<T>::compare(x, y) == 0; }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using rp = intrusive_ptr<T>;
|
||||||
|
|
||||||
|
class Refcount {
|
||||||
|
public:
|
||||||
|
Refcount() : reference_counter_(0) {}
|
||||||
|
/* WARNING: virtual dtor here is essential,
|
||||||
|
* since it's what allows us to invoke delete on a Refcount*,
|
||||||
|
* for an object of some derived class type T. Otherwise clang
|
||||||
|
* will use different addresses for Refcount-part and T-part of
|
||||||
|
* such instance, which means pointer given to delete will not be
|
||||||
|
* the same as pointer returned from new
|
||||||
|
*/
|
||||||
|
virtual ~Refcount() = default;
|
||||||
|
|
||||||
|
uint32_t reference_counter() const { return reference_counter_.load(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend uint32_t intrusive_ptr_refcount(Refcount *);
|
||||||
|
friend void intrusive_ptr_add_ref(Refcount *);
|
||||||
|
friend void intrusive_ptr_release(Refcount *);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<uint32_t> reference_counter_;
|
||||||
|
}; /*Refcount*/
|
||||||
|
|
||||||
|
inline uint32_t
|
||||||
|
intrusive_ptr_refcount(Refcount * x) {
|
||||||
|
/* reporting accurately for diagnostics */
|
||||||
|
if (x)
|
||||||
|
return x->reference_counter_.load();
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
} /*intrusive_ptr_refcount*/
|
||||||
|
|
||||||
|
void intrusive_ptr_set_debug(bool x);
|
||||||
|
void intrusive_ptr_log_ctor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
/* here actor short for 'aliasing ctor' */
|
||||||
|
void intrusive_ptr_log_actor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
void intrusive_ptr_log_cctor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
void intrusive_ptr_log_mctor(std::string_view const & self_type,
|
||||||
|
void *this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
void intrusive_ptr_log_dtor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
void intrusive_ptr_log_assign(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
void intrusive_ptr_log_massign(std::string_view const & self_type,
|
||||||
|
void *this_ptr,
|
||||||
|
Refcount * x);
|
||||||
|
void intrusive_ptr_add_ref(Refcount * x);
|
||||||
|
void intrusive_ptr_release(Refcount * x);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline std::ostream &
|
||||||
|
operator<<(std::ostream & os, intrusive_ptr<T> const & x) {
|
||||||
|
if(x.get()) {
|
||||||
|
os << *(x.get());
|
||||||
|
} else {
|
||||||
|
os << "<nullptr " << reflect::type_name<T>() << ">";
|
||||||
|
}
|
||||||
|
return os;
|
||||||
|
} /*operator<<*/
|
||||||
|
|
||||||
|
/* borrow a reference-counted pointer to pass down the stack
|
||||||
|
* 1. borrowed pointer intended to replace:
|
||||||
|
* a. code like
|
||||||
|
* foo(rp<T> x),
|
||||||
|
* passing rp<T> by value requires increment/decrement pair,
|
||||||
|
* which is superfluous given that caller holds reference throughout
|
||||||
|
* b. code like
|
||||||
|
* foo(rp<T> const & x)
|
||||||
|
* passing rp<T> by reference requires double-indirection in called
|
||||||
|
* code
|
||||||
|
* 2. borrowed pointer does not check/maintain reference count.
|
||||||
|
* it should never be stored in a struct; intended strictly
|
||||||
|
* to be passed down stack
|
||||||
|
* 3. just the same, want to be able to copy the borrowed pointer,
|
||||||
|
* to avoid double-indirection
|
||||||
|
* 4. also can promote borrowed pointer to full reference-counted
|
||||||
|
* whenever desired
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
class Borrow {
|
||||||
|
public:
|
||||||
|
template<typename S>
|
||||||
|
Borrow(rp<S> const & x) : ptr_(x.get()) {}
|
||||||
|
|
||||||
|
Borrow(Borrow const & x) = default;
|
||||||
|
|
||||||
|
/* convert from another borrow, if it has compatible pointer type */
|
||||||
|
template<typename S>
|
||||||
|
Borrow(Borrow<S> const & x) : ptr_(x.get()) {}
|
||||||
|
|
||||||
|
/* dynamic cast from a pointer to an object of some convertible type */
|
||||||
|
template<typename S>
|
||||||
|
static Borrow<T> from(Borrow<S> x) {
|
||||||
|
return Borrow(dynamic_cast<T *>(x.get()));
|
||||||
|
} /*from*/
|
||||||
|
|
||||||
|
/* promote from native pointer */
|
||||||
|
static Borrow<T> from_native(T * x) {
|
||||||
|
return Borrow(x);
|
||||||
|
} /*from_native*/
|
||||||
|
|
||||||
|
T * get() const { return ptr_; }
|
||||||
|
|
||||||
|
rp<T> promote() const { return rp<T>(ptr_); }
|
||||||
|
|
||||||
|
T & operator*() const { return *ptr_; }
|
||||||
|
T * operator->() const { return ptr_; }
|
||||||
|
|
||||||
|
operator bool() const { return ptr_ != nullptr; }
|
||||||
|
|
||||||
|
static int32_t compare(Borrow const & x, Borrow const & y) {
|
||||||
|
return ptrdiff_t(x.get() - y.get());
|
||||||
|
} /*compare*/
|
||||||
|
|
||||||
|
static int32_t compare(rp<T> const & x, Borrow const & y) {
|
||||||
|
return ptrdiff_t(x.get() - y.get());
|
||||||
|
} /*compare*/
|
||||||
|
|
||||||
|
private:
|
||||||
|
Borrow(T * x) : ptr_(x) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
T * ptr_ = nullptr;
|
||||||
|
}; /*Borrow*/
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline bool operator==(Borrow<T> x, Borrow<T> y) { return Borrow<T>::compare(x, y) == 0; }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline bool operator==(rp<T> const & x, Borrow<T> y) { return Borrow<T>::compare(x, y) == 0; }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using brw = Borrow<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Borrow<T>
|
||||||
|
intrusive_ptr<T>::borrow() const {
|
||||||
|
return Borrow<T>(*this);
|
||||||
|
} /*borrow*/
|
||||||
|
|
||||||
|
} /*namespace ref*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end Refcounted.hpp */
|
||||||
28
include/refcnt/Unowned.hpp
Normal file
28
include/refcnt/Unowned.hpp
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* @file Unowned.hpp */
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace ref {
|
||||||
|
/* use this is a holder type for pointers that pybind11 should treat
|
||||||
|
* as "not-my-problem". in particular that pybind11 should never delete.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
class unowned_ptr {
|
||||||
|
public:
|
||||||
|
unowned_ptr(T * x) : ptr_{x} {}
|
||||||
|
unowned_ptr(unowned_ptr const & x) = default;
|
||||||
|
~unowned_ptr() = default;
|
||||||
|
|
||||||
|
T * get() const { return ptr_; }
|
||||||
|
T * operator->() const { return ptr_; }
|
||||||
|
|
||||||
|
operator bool() const { return ptr_ != nullptr; }
|
||||||
|
|
||||||
|
unowned_ptr<T> & operator=(unowned_ptr<T> const & rhs) = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
T * ptr_ = nullptr;
|
||||||
|
}; /*unowned_ptr*/
|
||||||
|
} /*namespace ref*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end Unowned.hpp */
|
||||||
14
src/CMakeLists.txt
Normal file
14
src/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
set(SELF_LIBRARY_NAME refcnt)
|
||||||
|
set(SELF_SOURCE_FILES Refcounted.cpp Displayable.cpp)
|
||||||
|
add_library(${SELF_LIBRARY_NAME} SHARED ${SELF_SOURCE_FILES})
|
||||||
|
|
||||||
|
set_target_properties(${SELF_LIBRARY_NAME}
|
||||||
|
PROPERTIES
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
SOVERSION 1)
|
||||||
|
|
||||||
|
xo_indentlog_dependency(${SELF_LIBRARY_NAME})
|
||||||
|
|
||||||
|
xo_include_options(${SELF_LIBRARY_NAME})
|
||||||
|
xo_compile_options(${SELF_LIBRARY_NAME})
|
||||||
|
xo_install_library(${SELF_LIBRARY_NAME})
|
||||||
16
src/Displayable.cpp
Normal file
16
src/Displayable.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/* @file Displayable.cpp */
|
||||||
|
|
||||||
|
#include "refcnt/Displayable.hpp"
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
using xo::tostr;
|
||||||
|
|
||||||
|
namespace ref {
|
||||||
|
std::string
|
||||||
|
Displayable::display_string() const {
|
||||||
|
return tostr(*this);
|
||||||
|
} /*display_string*/
|
||||||
|
} /*namespace ref*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end Displayable.cpp */
|
||||||
157
src/Refcounted.cpp
Normal file
157
src/Refcounted.cpp
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
/* @file Refcounted.cpp */
|
||||||
|
|
||||||
|
#include "Refcounted.hpp"
|
||||||
|
|
||||||
|
namespace xo {
|
||||||
|
namespace ref {
|
||||||
|
namespace {
|
||||||
|
/* verbose logging for intrusive_ptr */
|
||||||
|
static bool s_logging_enabled = false;
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_aux(std::string_view const & self_type,
|
||||||
|
std::string_view const & method_name,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
scope lscope(XO_LITERAL(verbose, self_type, method_name),
|
||||||
|
"enter",
|
||||||
|
xtag("this", this_ptr),
|
||||||
|
xtag("x", x),
|
||||||
|
xtag("n", intrusive_ptr_refcount(x)));
|
||||||
|
} /*intrusive_ptr_log_aux*/
|
||||||
|
} /*namespace*/
|
||||||
|
|
||||||
|
bool
|
||||||
|
intrusive_ptr_set_debug(bool debug_flag) {
|
||||||
|
s_logging_enabled = debug_flag;
|
||||||
|
} /*intrusive_ptr_set_debug*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_ctor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::ctor", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_ctor*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_actor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::actor", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_actor*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_cctor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::cctor", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_cctor*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_mctor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::mctor", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_mctor*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_dtor(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::dtor", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_dtor*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_assign(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::=", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_assign*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_log_massign(std::string_view const & self_type,
|
||||||
|
void * this_ptr,
|
||||||
|
Refcount * x)
|
||||||
|
{
|
||||||
|
if (s_logging_enabled)
|
||||||
|
intrusive_ptr_log_aux(self_type, "::m=", this_ptr, x);
|
||||||
|
} /*intrusive_ptr_log_massign*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_add_ref(Refcount * x)
|
||||||
|
{
|
||||||
|
/* for adding reference -- can use relaxed order,
|
||||||
|
* since any reordering of a set of intrusive_ptr_add_ref()
|
||||||
|
* calls is ok, provided no intervening intrusive_ptr_release()
|
||||||
|
* calls.
|
||||||
|
*/
|
||||||
|
bool success = (x == nullptr);
|
||||||
|
|
||||||
|
while(!success) {
|
||||||
|
uint32_t n = x->reference_counter_.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
success = x->reference_counter_.compare_exchange_strong(n, n+1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
} /*intrusive_ptr_add_ref*/
|
||||||
|
|
||||||
|
void
|
||||||
|
intrusive_ptr_release(Refcount * x)
|
||||||
|
{
|
||||||
|
using xo::scope;
|
||||||
|
using xo::xtag;
|
||||||
|
|
||||||
|
scope log(XO_ENTER0(verbose),
|
||||||
|
"enter",
|
||||||
|
xtag("x", x),
|
||||||
|
xtag("n", x ? x->reference_counter_.load() : 0));
|
||||||
|
|
||||||
|
/* for decrement, need acq_rel ordering */
|
||||||
|
bool success = (x == nullptr);
|
||||||
|
uint32_t n = 0;
|
||||||
|
|
||||||
|
while(!success) {
|
||||||
|
n = x->reference_counter_.load(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if(n == static_cast<uint32_t>(-1)) {
|
||||||
|
log && log("detected double-delete attempt",
|
||||||
|
xtag("x", x),
|
||||||
|
xtag("n", n));
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
success = x->reference_counter_.compare_exchange_strong(n, n-1,
|
||||||
|
std::memory_order_acq_rel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(n == 1) {
|
||||||
|
/* just deleted the last reference, so recover the object */
|
||||||
|
|
||||||
|
log && log("delete object with 0 refs");
|
||||||
|
|
||||||
|
/* for good measure: replace refcount with -1,
|
||||||
|
* in hope of detecting a double-delete attempt
|
||||||
|
*/
|
||||||
|
x->reference_counter_.store(static_cast<uint32_t>(-1));
|
||||||
|
|
||||||
|
delete x;
|
||||||
|
}
|
||||||
|
} /*intrusive_ptr_release*/
|
||||||
|
|
||||||
|
} /*namespace ref*/
|
||||||
|
} /*namespace xo*/
|
||||||
|
|
||||||
|
/* end Refcounted.cpp */
|
||||||
Loading…
Add table
Add a link
Reference in a new issue