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
11 changes: 8 additions & 3 deletions Makefile.cbm
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,15 @@ TSAN_SANITIZE = -fsanitize=thread -fno-omit-frame-pointer
# every sanitized-budget branch compiled to its NATIVE value on this leg —
# exactly the failure the comment above SANITIZED_DEFINE describes for
# trap-UBSan, repeated on a leg that was never wired up.
#
# The same define now goes on EVERY instrumented flag set of our own code, C and
# C++ alike (CXXFLAGS_TSAN here, GRAMMAR_CFLAGS_TEST/TSAN below). Vendored flag
# sets are left alone: mimalloc, sqlite3, tre, zstd, lz4 and tree-sitter read no
# macro of ours.
CFLAGS_TSAN = $(CFLAGS_COMMON) $(EDITOR_TEST_DEFINES) $(KOTLIN_DEDUP_TEST_DEFINE) \
$(CALL_REFERENCE_LOOKUP_TEST_DEFINE) $(INCREMENTAL_TEST_DEFINE) \
-DCBM_SANITIZED_BUILD=1 -g -O1 $(TSAN_SANITIZE)
CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -g -O1 \
CXXFLAGS_TSAN = $(CXXFLAGS_COMMON) -DCBM_SANITIZED_BUILD=1 -g -O1 \
$(TSAN_SANITIZE)

# Windows needs ws2_32 (Winsock), psapi (GetProcessMemoryInfo), shell32
Expand Down Expand Up @@ -707,9 +712,9 @@ BUILD_DIR = build/c
# Grammar + tree-sitter runtime: compiled without -Werror (upstream code has warnings)
GRAMMAR_CFLAGS = -std=c11 -D_DEFAULT_SOURCE -O2 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC)
GRAMMAR_CFLAGS_TEST = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \
$(SANITIZE)
$(SANITIZED_DEFINE) $(SANITIZE)
GRAMMAR_CFLAGS_TSAN = -std=c11 -D_DEFAULT_SOURCE -g -O1 -w -I$(CBM_DIR) -I$(TS_INCLUDE) -I$(TS_SRC) \
$(TSAN_SANITIZE)
-DCBM_SANITIZED_BUILD=1 $(TSAN_SANITIZE)

# Object files for grammars + ts_runtime + lsp_all + preprocessor
GRAMMAR_OBJS_TEST = $(patsubst $(CBM_DIR)/%.c,$(BUILD_DIR)/%.o,$(GRAMMAR_SRCS))
Expand Down
3 changes: 2 additions & 1 deletion src/foundation/compat_thread.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "foundation/compat_thread.h"

#include "foundation/platform.h"
#include "foundation/sanitized.h" /* CBM_SANITIZED — diagnostic stack floor */

#include <mimalloc.h> /* mi_thread_done at thread exit */

Expand All @@ -31,7 +32,7 @@
* exactly the threads that overflow. Diagnostic builds only: the shipping
* binary keeps its fixed, predictable stack sizes. */
static size_t cbm_thread_stack_floor(size_t requested) {
#if defined(CBM_SANITIZED_BUILD) && CBM_SANITIZED_BUILD
#if CBM_SANITIZED
const char *env = getenv("CBM_THREAD_STACK_MB");
if (env && env[0]) {
char *end = NULL;
Expand Down
60 changes: 60 additions & 0 deletions src/foundation/sanitized.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* sanitized.h — One spelling of "is this binary instrumented?".
*
* Timing budgets, retry windows and stack floors all have to widen under a
* sanitizer, so several places need to ask this question. Before this header
* they each asked it differently — four sites, four spellings, one of which
* (the C# LSP bench) only recognised ASan and therefore ran a NATIVE 200ms
* budget on a ThreadSanitizer binary. Ask it here instead.
*
* The two sources are deliberate and neither replaces the other:
*
* CBM_SANITIZED_BUILD — set by the build system (Makefile.cbm), and the ONLY
* source that can answer for UBSan and trap-UBSan: undefined-behaviour
* instrumentation leaves no macro and no __has_feature bit behind, so no
* probe can see it. This is why the build system stays the source of truth.
*
* Compiler probes — the backstop for the three sanitizers that DO announce
* themselves, for when a new build lane forgets to pass the define. That is
* not hypothetical: CFLAGS_TSAN never included SANITIZED_DEFINE (it keys off
* $(SANITIZE), which TSan does not use), so every sanitized budget compiled
* to its native value on the one leg they were written for, and
* `subprocess_run_spawn_failure` failed on the very PR meant to fix it.
*
* Both clang and GCC spellings are listed because they disagree: clang reports
* thread and memory instrumentation through __has_feature only, GCC through
* __SANITIZE_*__ only, and __SANITIZE_THREAD__ alone — which is what the old
* per-site conditions used — is invisible on the clang lane that broke.
*
* Deliberately no #error when a probe fires without the define. Promoting
* leaves the binary CORRECT while the build lane is fixed, whereas an error
* would break it, and would also break an out-of-tree
* `make CFLAGS_EXTRA=-fsanitize=address` that never went near Makefile.cbm.
*/
#ifndef CBM_SANITIZED_H
#define CBM_SANITIZED_H

/* Define __has_feature away where it does not exist. `defined(__has_feature) &&
* __has_feature(...)` is NOT a substitute: && does not spare a preprocessor
* that lacks the builtin from parsing the call, and there `__has_feature(x)`
* becomes `0 (0)` — a syntax error rather than a 0. Nesting the arm under
* `#elif defined(__has_feature)` compiles everywhere but breaks cppcheck, which
* walks every configuration and rejects the file outright with "failed to
* evaluate #if condition, undefined function-like macro invocation". This is
* the idiom clang documents, and the one tests/test_mem.c already uses. */
#ifndef __has_feature
#define __has_feature(x) 0
#endif

#if defined(CBM_SANITIZED_BUILD) && CBM_SANITIZED_BUILD
#define CBM_SANITIZED 1
#elif __has_feature(address_sanitizer) || __has_feature(thread_sanitizer) || \
__has_feature(memory_sanitizer)
#define CBM_SANITIZED 1
#elif defined(__SANITIZE_ADDRESS__) || defined(__SANITIZE_THREAD__)
#define CBM_SANITIZED 1
#else
#define CBM_SANITIZED 0
#endif

#endif /* CBM_SANITIZED_H */
6 changes: 3 additions & 3 deletions src/foundation/subprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
#include "compat.h" /* cbm_nanosleep */
#include "compat_fs.h"
#include "log.h"
#include "platform.h" /* cbm_now_ms */
#include "platform.h" /* cbm_now_ms */
#include "sanitized.h" /* CBM_SANITIZED — spawn-retry budget */

#include <stdio.h>
#include <stdatomic.h>
Expand Down Expand Up @@ -902,8 +903,7 @@ static cbm_proc_poll_t cbm_subprocess_poll_win(cbm_subprocess_t *process, cbm_pr
* hang for seconds. So the extra patience is scoped to the builds that need it,
* the same way the daemon announce backstop is (test_daemon_frontend.c). Three
* more doublings take the sanitized ceiling to roughly 5s. */
#if defined(CBM_SANITIZED_BUILD) || defined(__SANITIZE_ADDRESS__) || \
defined(__SANITIZE_MEMORY__) || defined(__SANITIZE_THREAD__)
#if CBM_SANITIZED
enum { CBM_SPAWN_RETRY = 2, CBM_SPAWN_RETRY_ATTEMPTS = 9 };
#else
enum { CBM_SPAWN_RETRY = 2, CBM_SPAWN_RETRY_ATTEMPTS = 6 };
Expand Down
11 changes: 7 additions & 4 deletions tests/test_cs_lsp_bench.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
#include "test_framework.h"
#include "cbm.h"
#include "foundation/sanitized.h"
#include "lsp/cs_lsp.h"
#include <stdlib.h>
#include <time.h>
Expand Down Expand Up @@ -235,10 +236,12 @@ TEST(cslsp_bench_resolution_ratio) {
ASSERT_GTE(resolved * 100, calls * 45);
}

/* Time budget. ASan+UBSan instrumentation slows the parse ~5-10×, so
* scale the budget when a sanitizer is active. Native: 200 ms for a
* ~260-line fixture; sanitized: 2000 ms. */
#if defined(CBM_SANITIZED_BUILD) || defined(__SANITIZE_ADDRESS__)
/* Time budget. Instrumentation slows the parse ~5-10×, so scale the budget
* when a sanitizer is active. Native: 200 ms for a ~260-line fixture;
* sanitized: 2000 ms. The condition used to name ASan specifically, which
* left the TSan and MSan lanes measuring an instrumented parse against the
* native 200 ms. */
#if CBM_SANITIZED
ASSERT(ms < 2000.0);
#else
ASSERT(ms < 200.0);
Expand Down
4 changes: 2 additions & 2 deletions tests/test_daemon_frontend.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "foundation/compat.h"
#include "foundation/compat_thread.h"
#include "foundation/platform.h"
#include "foundation/sanitized.h"

#include <stdint.h>
#include <stdatomic.h>
Expand Down Expand Up @@ -842,8 +843,7 @@ static bool frontend_backpressure_run_isolated(bool maintenance) {
* MSan stayed green. Widening the backstop for sanitized builds costs
* nothing when the daemon is healthy: a passing run returns as soon as the
* byte arrives, whatever the ceiling is. */
#if defined(CBM_SANITIZED_BUILD) || defined(__SANITIZE_ADDRESS__) || \
defined(__SANITIZE_MEMORY__) || defined(__SANITIZE_THREAD__)
#if CBM_SANITIZED
const uint64_t announce_backstop_ms = 180000U;
#else
const uint64_t announce_backstop_ms = 30000U;
Expand Down
Loading