Skip to content

Commit 9c41108

Browse files
committed
Added strict compiler options and sanitizers
1 parent 5d1cdd2 commit 9c41108

17 files changed

Lines changed: 208 additions & 151 deletions

File tree

.github/workflows/ci.yml

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ jobs:
6969
python -m pip install --upgrade pip
7070
pip install meson ninja meson-python
7171
72-
- name: Install package in development mode
73-
run: pip install -e ".[dev]"
72+
- name: Install package in development mode (strict warnings)
73+
run: pip install -e ".[dev]" --config-settings=setup-args="-Dstrict=true"
7474

7575
- name: Run tests with coverage
7676
run: |
@@ -118,14 +118,51 @@ jobs:
118118
python -m pip install --upgrade pip
119119
pip install meson ninja meson-python
120120
121-
- name: Install package in development mode
122-
run: pip install -e ".[dev]"
121+
- name: Install package in development mode (strict warnings)
122+
run: pip install -e ".[dev]" --config-settings=setup-args="-Dstrict=true"
123123

124124
- name: Run tests
125125
run: |
126126
python -X faulthandler -m pytest tests/test_profiler.py -v --tb=short
127127
timeout-minutes: 5
128128

129+
# =============================================================================
130+
# Memory safety testing with sanitizers (Linux only)
131+
# =============================================================================
132+
sanitizers:
133+
runs-on: ubuntu-latest
134+
steps:
135+
- uses: actions/checkout@v4
136+
137+
- name: Set up Python 3.12
138+
uses: actions/setup-python@v5
139+
with:
140+
python-version: "3.12"
141+
142+
- name: Install system dependencies
143+
run: |
144+
sudo apt-get update
145+
sudo apt-get install -y libasan8 libubsan1
146+
147+
- name: Install build tools
148+
run: |
149+
python -m pip install --upgrade pip
150+
pip install meson ninja meson-python pytest pytest-timeout
151+
152+
- name: Build with ASAN+UBSAN and strict warnings
153+
run: |
154+
meson setup builddir --buildtype=debug -Dsanitize=address,undefined -Dstrict=true
155+
meson compile -C builddir
156+
157+
- name: Run tests with sanitizers
158+
env:
159+
PYTHONPATH: ${{ github.workspace }}/builddir/src/spprof:${{ github.workspace }}/src
160+
ASAN_OPTIONS: detect_leaks=1:abort_on_error=1:symbolize=1:halt_on_error=1
161+
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
162+
run: |
163+
python -m pytest tests/ -v --tb=short -x
164+
timeout-minutes: 10
165+
129166
# =============================================================================
130167
# Benchmarks
131168
# =============================================================================
@@ -144,8 +181,8 @@ jobs:
144181
python -m pip install --upgrade pip
145182
pip install meson ninja meson-python
146183
147-
- name: Install package in development mode
148-
run: pip install -e ".[dev]"
184+
- name: Install package in development mode (strict warnings)
185+
run: pip install -e ".[dev]" --config-settings=setup-args="-Dstrict=true"
149186

150187
- name: Run benchmarks
151188
run: |

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ MANIFEST
3030
*.dll
3131
*.dylib
3232
*.dSYM/
33+
builddir/
34+
builddir-asan/
35+
builddir-pedantic/
3336

3437
# Virtual environments
3538
.venv/

meson.build

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ project(
99
meson_version: '>= 1.2.0',
1010
default_options: [
1111
'c_std=c11',
12-
'warning_level=2',
12+
'warning_level=3',
1313
'buildtype=release',
1414
],
1515
)
@@ -31,15 +31,40 @@ common_c_args = []
3131

3232
if cc.get_id() in ['gcc', 'clang']
3333
common_c_args += [
34-
'-Wno-unused-function',
35-
'-Wno-missing-field-initializers',
36-
'-Wno-unused-parameter',
34+
# Pedantic warnings for high code quality
35+
'-pedantic',
36+
'-Wextra',
37+
'-Wconversion',
38+
'-Wsign-conversion',
39+
'-Wdouble-promotion',
40+
'-Wformat=2',
41+
'-Wshadow',
42+
'-Wundef',
43+
'-Wcast-qual',
44+
'-Wwrite-strings',
45+
'-Wnull-dereference',
46+
# Suppress warnings that are unavoidable in Python C extensions
47+
'-Wno-unused-parameter', # Python API requires 'self' even when unused
48+
'-Wno-missing-field-initializers', # Common with designated initializers
49+
# Code generation
3750
'-fvisibility=hidden',
3851
'-fPIC',
3952
]
53+
# GCC-specific warnings
54+
if cc.get_id() == 'gcc'
55+
common_c_args += [
56+
'-Wlogical-op',
57+
'-Wduplicated-cond',
58+
'-Wduplicated-branches',
59+
]
60+
endif
4061
elif cc.get_id() == 'msvc'
4162
common_c_args += [
63+
'/W4', # High warning level
4264
'/D_CRT_SECURE_NO_WARNINGS',
65+
'/wd4100', # unreferenced formal parameter (like -Wno-unused-parameter)
66+
'/wd4115', # named type definition in parentheses (from Python's pytime.h)
67+
'/wd4702', # unreachable code (false positives from uthash macros)
4368
]
4469
endif
4570

@@ -49,6 +74,21 @@ if get_option('buildtype') == 'debug'
4974
add_project_arguments('-DSPPROF_DEBUG=1', language: 'c')
5075
endif
5176

77+
# Treat warnings as errors (for CI)
78+
if get_option('strict')
79+
common_c_args += cc.get_id() in ['gcc', 'clang'] ? ['-Werror'] : ['/WX']
80+
message('Strict mode: treating warnings as errors')
81+
endif
82+
83+
# Sanitizer configuration
84+
sanitize_opt = get_option('sanitize')
85+
if sanitize_opt != 'none' and cc.get_id() in ['gcc', 'clang']
86+
sanitize_args = ['-fsanitize=' + sanitize_opt, '-fno-omit-frame-pointer', '-g']
87+
common_c_args += sanitize_args
88+
add_project_link_arguments(sanitize_args, language: 'c')
89+
message('Sanitizers enabled: ' + sanitize_opt)
90+
endif
91+
5292
# Version defines
5393
add_project_arguments(
5494
'-DSPPROF_PY_MAJOR=' + py_major,

meson_options.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-License-Identifier: MIT
2+
# meson_options.txt - Build options for spprof
3+
4+
option('sanitize', type: 'combo', choices: ['none', 'address', 'undefined', 'address,undefined'],
5+
value: 'none',
6+
description: 'Enable sanitizers (ASAN/UBSAN)')
7+
8+
option('strict', type: 'boolean', value: false,
9+
description: 'Treat warnings as errors (recommended for CI)')
10+

src/spprof/_ext/code_registry.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ void code_registry_release_ref(uintptr_t code_addr) {
259259
}
260260
}
261261

262-
void code_registry_release_refs_batch(uintptr_t* code_addrs, size_t count) {
262+
void code_registry_release_refs_batch(const uintptr_t* code_addrs, size_t count) {
263263
if (!g_initialized || code_addrs == NULL || count == 0) {
264264
return;
265265
}

src/spprof/_ext/code_registry.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ void code_registry_release_ref(uintptr_t code_addr);
160160
* @param code_addrs Array of raw PyCodeObject* pointers.
161161
* @param count Number of pointers in the array.
162162
*/
163-
void code_registry_release_refs_batch(uintptr_t* code_addrs, size_t count);
163+
void code_registry_release_refs_batch(const uintptr_t* code_addrs, size_t count);
164164

165165
/**
166166
* Validate a code object pointer (REQUIRES GIL).

src/spprof/_ext/error.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,26 @@
5757
extern "C" {
5858
#endif
5959

60+
/*
61+
* =============================================================================
62+
* COMPILER PORTABILITY MACROS
63+
* =============================================================================
64+
*/
65+
66+
/**
67+
* SPPROF_UNUSED - Mark a function or variable as intentionally unused.
68+
*
69+
* Suppresses "unused function" or "unused variable" warnings across compilers.
70+
* Use when a function is conditionally used or kept for future use.
71+
*/
72+
#if defined(_MSC_VER)
73+
#define SPPROF_UNUSED /* MSVC doesn't warn about static unused functions */
74+
#elif defined(__GNUC__) || defined(__clang__)
75+
#define SPPROF_UNUSED __attribute__((unused))
76+
#else
77+
#define SPPROF_UNUSED
78+
#endif
79+
6080
/**
6181
* SpResult - Common result type for operations that can fail in multiple ways
6282
*

src/spprof/_ext/internal/pycore_tstate.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ _spprof_capture_frames_extended(
499499
if (_spprof_ptr_valid(code)) {
500500
frames[count].code_ptr = (uintptr_t)code;
501501
frames[count].instr_ptr = (uintptr_t)_spprof_frame_get_instr_ptr(frame);
502-
frames[count].owner = _spprof_frame_get_owner(frame);
502+
frames[count].owner = (int8_t)_spprof_frame_get_owner(frame);
503503
count++;
504504
}
505505

@@ -525,7 +525,7 @@ _spprof_capture_frames_extended(
525525
if (_spprof_ptr_valid(code)) {
526526
frames[count].code_ptr = (uintptr_t)code;
527527
frames[count].instr_ptr = (uintptr_t)_spprof_frame_get_instr_ptr(frame);
528-
frames[count].owner = _spprof_frame_get_owner(frame);
528+
frames[count].owner = (int8_t)_spprof_frame_get_owner(frame);
529529
count++;
530530
}
531531

src/spprof/_ext/module.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,24 @@ static void spprof_cleanup(void);
6262
* Internal function. Use spprof.start() from Python.
6363
*/
6464
static PyObject* spprof_start(PyObject* self, PyObject* args, PyObject* kwargs) {
65+
/* Note: PyArg_ParseTupleAndKeywords expects char**, not const char**.
66+
* This is a limitation of Python's C API. We suppress the warning locally.
67+
* GCC uses -Wdiscarded-qualifiers, Clang uses -Wincompatible-pointer-types-discards-qualifiers. */
68+
#if defined(__clang__)
69+
#pragma clang diagnostic push
70+
#pragma clang diagnostic ignored "-Wwrite-strings"
71+
#pragma clang diagnostic ignored "-Wincompatible-pointer-types-discards-qualifiers"
72+
#elif defined(__GNUC__)
73+
#pragma GCC diagnostic push
74+
#pragma GCC diagnostic ignored "-Wwrite-strings"
75+
#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
76+
#endif
6577
static char* kwlist[] = {"interval_ns", NULL};
78+
#if defined(__clang__)
79+
#pragma clang diagnostic pop
80+
#elif defined(__GNUC__)
81+
#pragma GCC diagnostic pop
82+
#endif
6683
uint64_t interval_ns = 10000000; /* Default 10ms */
6784

6885
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|K", kwlist, &interval_ns)) {
@@ -618,7 +635,7 @@ static PyObject* spprof_get_code_registry_stats(PyObject* self, PyObject* args)
618635

619636
/* Method table */
620637
static PyMethodDef SpProfMethods[] = {
621-
{"_start", (PyCFunction)spprof_start, METH_VARARGS | METH_KEYWORDS,
638+
{"_start", (PyCFunction)(void(*)(void))spprof_start, METH_VARARGS | METH_KEYWORDS,
622639
"Start profiling (internal). Use spprof.start() instead."},
623640
{"_stop", spprof_stop, METH_NOARGS,
624641
"Stop profiling and return raw samples (internal, legacy API)."},

src/spprof/_ext/platform/darwin_mach.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include "darwin_mach.h"
5858
#include "../ringbuffer.h"
5959
#include "../code_registry.h"
60+
#include "../error.h"
6061

6162
/* Internal API for Python frame capture */
6263
#ifdef SPPROF_USE_INTERNAL_API
@@ -108,7 +109,8 @@ static void mach_debug_log(const char* fmt, ...) {
108109
}
109110
#define MACH_DEBUG(fmt, ...) mach_debug_log(fmt, ##__VA_ARGS__)
110111
#else
111-
#define MACH_DEBUG(fmt, ...) ((void)0)
112+
/* Accept any arguments but discard them - avoids C23 variadic warning */
113+
#define MACH_DEBUG(...) ((void)0)
112114
#endif
113115

114116
/*
@@ -717,6 +719,7 @@ static int write_mixed_sample_to_ringbuffer(
717719
/**
718720
* Write a captured Python stack to the ring buffer (legacy, no native frames).
719721
*/
722+
SPPROF_UNUSED
720723
static int write_python_sample_to_ringbuffer(
721724
uint64_t thread_id,
722725
uint64_t timestamp,
@@ -732,6 +735,7 @@ static int write_python_sample_to_ringbuffer(
732735
/**
733736
* Write a captured native stack to the ring buffer (for native-only mode).
734737
*/
738+
SPPROF_UNUSED
735739
static int write_native_sample_to_ringbuffer(const CapturedStack* stack,
736740
RingBuffer* ringbuffer) {
737741
RawSample sample;
@@ -1000,6 +1004,7 @@ static void sample_all_threads(ThreadSnapshot* snapshot, MachSamplerState* state
10001004
/**
10011005
* Legacy single-thread sample function (not used in current implementation).
10021006
*/
1007+
SPPROF_UNUSED
10031008
static int sample_thread(ThreadEntry* entry, MachSamplerState* state) {
10041009
(void)entry;
10051010
(void)state;
@@ -1129,7 +1134,7 @@ void mach_sampler_cleanup(void) {
11291134

11301135
/* Remove introspection hook */
11311136
if (g_state.registry.hook_installed) {
1132-
pthread_introspection_hook_install(g_state.registry.prev_hook);
1137+
(void)pthread_introspection_hook_install(g_state.registry.prev_hook);
11331138
g_state.registry.hook_installed = 0;
11341139
}
11351140

0 commit comments

Comments
 (0)