Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions cpp/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ bash build.sh
bash build.sh -t=Debug
bash build.sh -t=RelWithDebInfo
bash build.sh -a=ON # Enable AddressSanitizer
bash build.sh -a=ON --sanitizer-shared-runtime=ON \
--sanitizer-checks=address # For the Python bindings (see python/SANITIZER.md)
bash build.sh -a=ON --sanitizer-checks=address # Match the Python binding build (see python/SANITIZER.md)
bash build.sh -c=ON # Enable code coverage

# Disable optional compression libraries
Expand Down Expand Up @@ -71,8 +70,7 @@ All `ON` by default unless noted:
| `ENABLE_LZMA2` | LZMA2 compression (`OFF` by default; bundled XZ requires CMake 3.20+) |
| `ENABLE_THREADS` | Multi-threaded read/write via pthreads |
| `ENABLE_ASAN` | AddressSanitizer (`OFF` by default) |
| `TSFILE_SANITIZER_CHECKS` | `-fsanitize=` list used by `ENABLE_ASAN` (default `address,undefined`) |
| `TSFILE_SANITIZER_SHARED_RUNTIME` | Link the sanitizer runtime dynamically into shared libraries; required when Python bindings preload it (`OFF` by default) |
| `TSFILE_SANITIZER_CHECKS` | `-fsanitize=` list used by `ENABLE_ASAN` (default `address,undefined`); TsFile targets are compiled and linked dynamically against it |
| `ENABLE_SIMD` | SIMD acceleration |

## Source Structure
Expand Down
72 changes: 25 additions & 47 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -161,13 +161,6 @@ option(ENABLE_ASAN "Enable Address Sanitizer" OFF)
set(TSFILE_SANITIZER_CHECKS "address,undefined"
CACHE STRING
"Sanitizer checks passed to -fsanitize= when ENABLE_ASAN is ON (for example 'address' or 'address,undefined')")
# Python bindings preload the sanitizer runtime, so instrumented shared
# libraries must not embed a private copy of it. Keep this OFF for the C++ test
# builds that use -static-libasan, and turn it ON for the coordinated
# C++/Python binding build.
option(TSFILE_SANITIZER_SHARED_RUNTIME
"Link the sanitizer runtime dynamically into shared libraries (for preloaded Python bindings)"
OFF)

if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
Expand Down Expand Up @@ -226,12 +219,9 @@ else ()
# (see TSFILE_ENABLE_NATIVE_ARCH above): the default target for the
# deployment platform is the portable baseline, and on macOS arm64
# it still includes +crc so snappy's fast path stays available.
if (ENABLE_ASAN AND TSFILE_SANITIZER_SHARED_RUNTIME)
if (ENABLE_ASAN)
# LTO loses the stack frames sanitizer reports need, and its
# additional optimization pipeline complicates symbolization.
# Only the binding mode changes optimization: the C++ test mode
# keeps its historical flags so its static runtime stays in
# sync with how libtsfile is linked.
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
elseif (MINGW OR WIN32)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
Expand All @@ -251,8 +241,7 @@ endif ()
message("CMAKE DEBUG: CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS}")

if (ENABLE_ASAN)
message("Address Sanitizer is enabled: checks=${TSFILE_SANITIZER_CHECKS}, "
"shared_runtime=${TSFILE_SANITIZER_SHARED_RUNTIME}")
message("Address Sanitizer is enabled: checks=${TSFILE_SANITIZER_CHECKS}")
if (MSVC)
# MSVC ships AddressSanitizer; it requires Visual Studio 2019 16.9 or
# newer (MSVC_VERSION >= 1928). Only the address sanitizer is available
Expand All @@ -278,33 +267,24 @@ if (ENABLE_ASAN)
endforeach ()
add_link_options(/INCREMENTAL:NO /DEBUG)
elseif (NOT WIN32)
# Compile flags here only survive until add_subdirectory(third_party)
# resets CMAKE_CXX_FLAGS. The binding mode re-applies them after that
# call; the C++ test mode keeps its historical behavior. Link flags set
# here persist.
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS} -fno-omit-frame-pointer")

if (TSFILE_SANITIZER_SHARED_RUNTIME)
# The runtime stays dynamic so one preloaded copy serves Python, the
# extensions and libtsfile. Debug info is required for useful
# reports in every build type.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS}")
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS}")
elseif (NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libasan")
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS} -static-libasan")
if (TSFILE_SANITIZER_CHECKS MATCHES "undefined")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libubsan")
endif ()
else ()
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS}")
endif ()
# Every TsFile target (libtsfile, the tools, the test executable) is
# compiled with instrumentation, and every binary that contains
# instrumented code links the sanitizer runtime dynamically: a macOS
# dylib cannot carry unresolved __asan_* symbols, and embedding a
# private runtime in both libtsfile and the executable is unsupported.
#
# Compile flags set here do not survive the CMAKE_CXX_FLAGS reset before
# add_subdirectory(third_party); they are stored in
# TSFILE_SANITIZER_COMPILE_FLAGS and re-applied for the TsFile targets
# once the bundled third-party code is configured (so that code stays
# uninstrumented). Link flags set here persist.
set(TSFILE_SANITIZER_COMPILE_FLAGS
"-fsanitize=${TSFILE_SANITIZER_CHECKS} -fno-omit-frame-pointer -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TSFILE_SANITIZER_COMPILE_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS
"${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS}")
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS}")
else ()
message(WARNING
"ENABLE_ASAN on Windows is only supported with the MSVC toolchain; "
Expand Down Expand Up @@ -436,13 +416,11 @@ endif ()
add_subdirectory(third_party)

# third_party intentionally rebuilds with a minimal CMAKE_CXX_FLAGS so bundled
# projects do not inherit TsFile's warning flags. The binding mode re-applies
# the sanitizer instrumentation for the TsFile targets that follow. The C++
# test mode keeps its historical static-runtime setup, so re-applying here
# would mix a dynamic runtime in libtsfile with the executable's static one.
if (ENABLE_ASAN AND TSFILE_SANITIZER_SHARED_RUNTIME AND NOT MSVC AND NOT WIN32)
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fsanitize=${TSFILE_SANITIZER_CHECKS} -fno-omit-frame-pointer -g")
# projects do not inherit TsFile's warning flags. Re-apply the sanitizer
# instrumentation for the TsFile targets that follow; without it TsFile sources
# would link the sanitizer runtime but carry no compiler-inserted checks.
if (ENABLE_ASAN AND NOT MSVC AND NOT WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TSFILE_SANITIZER_COMPILE_FLAGS}")
endif ()

# CMake 3.11 cannot call target_link_libraries() on object-library targets, but
Expand Down
9 changes: 0 additions & 9 deletions cpp/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ run_cov_only=0
enable_antlr4=ON
enable_asan=OFF
sanitizer_checks=address,undefined
sanitizer_shared_runtime=OFF

enable_snappy=ON
enable_lz4=ON
Expand Down Expand Up @@ -62,10 +61,6 @@ Options:
--sanitizer-checks=<list>
Checks passed to -fsanitize= when ASan is enabled
(default: address,undefined).
--sanitizer-shared-runtime=<ON|OFF>
Link the sanitizer runtime dynamically into shared
libraries; required when building the Python bindings,
which preload libasan (default: OFF).
-c=<ON|OFF> Enable or disable code coverage.
--build-shared=<ON|OFF>
Build libtsfile as a shared library (default: ON).
Expand Down Expand Up @@ -100,7 +95,6 @@ function print_config()
echo "enable_cov=$enable_cov"
echo "enable_asan=$enable_asan"
echo "sanitizer_checks=$sanitizer_checks"
echo "sanitizer_shared_runtime=$sanitizer_shared_runtime"
echo "enable_antlr4=$enable_antlr4"
echo "enable_snappy=$enable_snappy"
echo "enable_lz4=$enable_lz4"
Expand Down Expand Up @@ -139,8 +133,6 @@ parse_options()
enable_asan=$(get_key_value "$1");;
--sanitizer-checks=*)
sanitizer_checks=$(get_key_value "$1");;
--sanitizer-shared-runtime=*)
sanitizer_shared_runtime=$(get_key_value "$1");;
-c=*)
enable_cov=$(get_key_value "$1");;
-c)
Expand Down Expand Up @@ -241,7 +233,6 @@ cmake ../../ \
-DENABLE_COV=$enable_cov \
-DENABLE_ASAN=$enable_asan \
-DTSFILE_SANITIZER_CHECKS=$sanitizer_checks \
-DTSFILE_SANITIZER_SHARED_RUNTIME=$sanitizer_shared_runtime \
-DDEBUG_SE=$debug_se \
-DENABLE_ANTLR4=$enable_antlr4 \
-DBUILD_TSFILE_ONLY=$build_tsfile_only \
Expand Down
16 changes: 7 additions & 9 deletions cpp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,12 @@
<build.type>Release</build.type>
<enable.asan>OFF</enable.asan>
<!--
Sanitizer details used when enable.asan is ON. The binding build
Sanitizer check set used when enable.asan is ON. The binding build
(-Dtsfile.sanitizer=address) activates the with-sanitizer profile,
which selects the address-only check set and a dynamically linked
runtime that Python can preload.
which selects the address-only check set that setup.py applies to
the Cython extensions as well.
-->
<tsfile.sanitizer.checks>address,undefined</tsfile.sanitizer.checks>
<tsfile.sanitizer.shared.runtime>OFF</tsfile.sanitizer.shared.runtime>
<build.test>ON</build.test>
<tsfile.build.shared>ON</tsfile.build.shared>
<tsfile.dependency.source>BUNDLED</tsfile.dependency.source>
Expand Down Expand Up @@ -119,7 +118,6 @@
<option>-DCMAKE_BUILD_TYPE=${build.type}</option>
<option>-DENABLE_ASAN=${enable.asan}</option>
<option>-DTSFILE_SANITIZER_CHECKS=${tsfile.sanitizer.checks}</option>
<option>-DTSFILE_SANITIZER_SHARED_RUNTIME=${tsfile.sanitizer.shared.runtime}</option>
<option>-DBUILD_TEST=${build.test}</option>
<option>-DTSFILE_BUILD_SHARED=${tsfile.build.shared}</option>
<option>-DTSFILE_DEPENDENCY_SOURCE=${tsfile.dependency.source}</option>
Expand Down Expand Up @@ -305,9 +303,10 @@
<id>with-sanitizer</id>
<!--
Activated by -Dtsfile.sanitizer=<checks> (for example
-Dtsfile.sanitizer=address). Selects ASan, keeps the sanitizer
runtime dynamic, and lets setup.py instrument every Python
extension with the same check set.
-Dtsfile.sanitizer=address). Selects ASan and lets setup.py
instrument every Python extension with the same check set.
libtsfile and the extensions share one dynamically linked
sanitizer runtime, which the interpreter preloads.
-->
<activation>
<property>
Expand All @@ -317,7 +316,6 @@
<properties>
<enable.asan>ON</enable.asan>
<tsfile.sanitizer.checks>${tsfile.sanitizer}</tsfile.sanitizer.checks>
<tsfile.sanitizer.shared.runtime>ON</tsfile.sanitizer.shared.runtime>
</properties>
</profile>
<profile>
Expand Down
14 changes: 14 additions & 0 deletions cpp/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,20 @@ add_test(
-DTEST_ANTLR4_ARCHIVE=${TSFILE_ANTLR4_ARCHIVE_RESOLVED}
-P ${CMAKE_SOURCE_DIR}/cmake/tests/ANTLR4DependencyTest.cmake)

if (ENABLE_ASAN)
# Negative control for the sanitizer build: the program performs a
# deliberate out-of-bounds heap write. The driver script decides the
# outcome from the sanitizer report, because CTest treats the abort ASan
# uses on some platforms as a failure regardless of the exit status that
# PASS_REGULAR_EXPRESSION would otherwise absorb.
add_executable(asan_negative_control asan_negative_control.cc)
add_test(
NAME asan_negative_control
COMMAND ${CMAKE_COMMAND}
"-DASAN_NEGATIVE_CONTROL=$<TARGET_FILE:asan_negative_control>"
-P ${CMAKE_CURRENT_SOURCE_DIR}/asan_negative_control.cmake)
endif ()

file(GLOB_RECURSE TEST_SRCS
"common/*_test.cc"
"encoding/*_test.cc"
Expand Down
38 changes: 38 additions & 0 deletions cpp/test/asan_negative_control.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/

// Built and registered as a CTest case only when ENABLE_ASAN is ON.
//
// The program deliberately writes past the end of a heap allocation. The
// accompanying CTest entry passes only when the sanitizer reports the access,
// so a build that links the runtime without instrumenting TsFile code fails
// this test instead of silently losing its memory-safety checks.

#include <cstdlib>

int main() {
char* buffer = static_cast<char*>(std::malloc(4));
if (buffer == nullptr) {
return 0;
}
volatile char* access = buffer;
access[8] = 1;
std::free(buffer);
return 0;
}
45 changes: 45 additions & 0 deletions cpp/test/asan_negative_control.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#[[
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
]]

# CTest driver for asan_negative_control. The program performs a deliberate
# out-of-bounds heap write, so the test passes only when the sanitizer reports
# it. The exit status is ignored on purpose: ASan aborts on some platforms and
# exits with a code on others, and CTest marks a subprocess abort as a failure
# regardless of PASS_REGULAR_EXPRESSION.

if (NOT DEFINED ASAN_NEGATIVE_CONTROL OR NOT EXISTS "${ASAN_NEGATIVE_CONTROL}")
message(FATAL_ERROR
"ASAN_NEGATIVE_CONTROL does not name an existing executable: "
"${ASAN_NEGATIVE_CONTROL}")
endif ()

execute_process(
COMMAND "${ASAN_NEGATIVE_CONTROL}"
RESULT_VARIABLE _result
OUTPUT_VARIABLE _stdout
ERROR_VARIABLE _stderr)

set(_combined "${_stdout}\n${_stderr}")
string(FIND "${_combined}" "AddressSanitizer" _report_position)
if (_report_position EQUAL -1)
message(FATAL_ERROR
"The sanitizer did not report the deliberate out-of-bounds write "
"(exit status: ${_result}). The build is probably not compiled "
"with instrumentation.\nOutput:\n${_combined}")
endif ()
19 changes: 10 additions & 9 deletions python/SANITIZER.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ across the language boundary.

The CI job runs on `ubuntu-24.04` with the distribution GCC and Python 3.11.

The C++-only `ENABLE_ASAN` jobs keep their historical static-runtime
configuration; this mode is the coordinated C++ + bindings build, where the
sanitizer compile flags are also re-applied after the bundled third-party
projects reset `CMAKE_CXX_FLAGS`.
Every `ENABLE_ASAN` build (C++ only or with bindings) now compiles the TsFile
targets with `-fsanitize` and links the sanitizer runtime dynamically;
`third_party` code stays uninstrumented. The bindings build additionally
instruments the Cython extensions and preloads the single shared runtime into
the interpreter.

## Build

Expand All @@ -64,9 +65,9 @@ projects reset `CMAKE_CXX_FLAGS`.
`-Dtsfile.sanitizer=<checks>` is the single switch. It:

* enables `ENABLE_ASAN` and passes `<checks>` to `-fsanitize=` for the C++
library, adding the same flags to the shared-library link step;
* keeps the sanitizer runtime dynamic (`TSFILE_SANITIZER_SHARED_RUNTIME=ON`) so
one preloaded runtime can serve Python, the extensions and `libtsfile`;
library, adding the same flags to the shared-library and executable links;
* keeps the sanitizer runtime dynamically linked so one preloaded runtime can
serve Python, the extensions and `libtsfile`;
* exports `TSFILE_SANITIZER=<checks>` to `setup.py`, which instruments every
extension (including `python_random_access_read_file.cc`) and verifies the
result;
Expand All @@ -85,8 +86,8 @@ separately, as described below).
### Direct CMake + setup.py

```bash
# 1. C++ library, dynamic sanitizer runtime
bash cpp/build.sh -t=RelWithDebInfo -a=ON --sanitizer-shared-runtime=ON
# 1. C++ library with dynamic sanitizer runtime
bash cpp/build.sh -t=RelWithDebInfo -a=ON

# 2. Point setup.py at that build and instrument the extensions
cd python
Expand Down
Loading