Confirmed working in the current query_graph evaluator and locked in:
- #240 labels() function with AS alias
- #237 DISTINCT applied before ORDER BY + LIMIT
- #252 toInteger()
- #305 count(*) with AS alias
Closes#240. Closes#237. Closes#252. Closes#305.
.mjs (ES modules) and .cjs (CommonJS) were absent from the extension
table, so those files were never assigned a language — they weren't
indexed (no graph nodes) and search_code returned 0 for any pattern in
them. Add .mjs/.cjs → JavaScript and .mts/.cts → TypeScript.
Verified end-to-end: a function in a .mjs file is now extracted and found
by search_code. Closes#197.
#220: C++ CALLS edges originated from the Module node instead of the
enclosing Function, breaking function-level trace_path. The cross-LSP
resolver now sets caller_qn = enclosing_func_qn; the test asserts a call
(doWork→helper) is attributed to doWork, not the module.
Closes#220.
search_graph file_pattern always returned 0 for a bare name like
"offer-server": cbm_glob_to_like produced the literal "offer-server",
so `file_path LIKE 'offer-server'` only matched a path equal to it, never
"src/offer-server/x.js". When the pattern contains no glob wildcards
(* or ?), wrap the LIKE as %pattern% so it matches anywhere in the path.
Explicit globs keep their semantics.
Closes#200.
#382 — Java Method nodes now carry decorators (@GetMapping) and a non-empty
signature; verified by extract_java_method_annotations_issue382.
#213 — a 256 KB / 4000-function TS file now yields Function nodes instead of
a childless File node (fixed by the tree-sitter read-buffer padding);
verified by extract_large_ts_has_functions_issue213.
Closes#382. Closes#213.
#228: a class/static method invoked on a cross-file imported class name
(ActionRecordX.build_from_text(...)) produced no CALLS edge, so the
method looked like dead code (in/out degree 0). The v0.7.0 py_lsp
cross-file resolver handles this; the test feeds the exact pattern through
cbm_run_py_lsp_cross and asserts the call resolves.
Closes#228.
The #336 (Icinga2/Linux) and #385 (KiCad/Windows) reproductions are done —
both index cleanly on v0.7.0 on their reported platforms. Removing the
throwaway workflow.
The reused _build.yml builds all platforms; a cancelled macos-15-intel
runner skipped the icinga2/kicad index jobs. Build inline in each job so
the Linux (#336) and Windows (#385) repros don't depend on unrelated
platforms. Still temporary — delete after the run.
Issues #344/#340 reported a scale-dependent SIGSEGV in the TS cross-file
LSP pass once the collected def array exceeded ~1189 entries. The v0.7.0
cross-LSP rewrite registers defs through the arena (no fixed-size def
array), so the threshold no longer exists. This test drives
cbm_run_ts_lsp_cross with a 4000-entry def array under -fsanitize=address
and resolves cleanly, locking in the fix.
Closes#344. Closes#340.
Temporary workflow_dispatch harness: builds current-main binaries and
indexes Icinga2 on Linux (fast) and KiCad on Windows (moderate) to check
whether the reported crashes still reproduce on v0.7.0. To be removed
after the run.
Issue #355 reported a segfault extracting a macro-heavy xxhash C header
(dhw/xx_hash.h, not available to us). Add a guard that runs the vendored
xxhash.h (~7.5k lines, same macro-dense family) through C extraction under
ASan. It passes on current main — the C-LSP crash hardening (#322/#323/
#360) appears to cover this class. Kept as a regression guard; #355 stays
open pending the reporter's confirmation/file.
Passing an unrecognised project name triggered a buffer overflow while
building the available-projects error list: collect_db_project_names did
snprintf(out+offset, out_sz-offset, ...) with no bound, so once the
listed names exceeded the 4 KB projects buffer, (out_sz - offset)
underflowed (unsigned) to a huge size and snprintf wrote past the buffer.
Fixed in 21c73b5 (PR #352) with an offset>=out_sz bounds check + clamp.
This test fills a temp CBM_CACHE_DIR with 40 long-named .db files
(~5 KB of names) and issues a bad-project search_graph call; the test
build runs under -fsanitize=address so the old underflow would abort here.
Closes#235.
A C++ function template with a defaulted type param and an `auto`
parameter, called with matching arg count, previously segfaulted in the
definitions pass (unbound `U` flowed through type substitution as NULL).
Fixed by the cbm_type_substitute NULL-preservation guard; this locks in
a reproduction so it can't regress.
Closes#312.
The full-repo manifest walk added for workspace-import resolution hung
the Windows CI test-runner (pipeline_structure_nodes): pkgmap_walk_dir
uses plain stat() on Windows (no lstat/S_ISLNK), so directory junctions
in the walked tree can be followed into cycles, never terminating.
Make cbm_pkgmap_scan_repo a no-op on Windows and compile pkgmap_walk_dir
only on POSIX. Windows falls back to the files[]-based pkgmap (the
pre-batch baseline behavior). Workspace-import resolution from
discovery-ignored manifests (package.json) is therefore Linux/macOS-only
until the walk is made junction-safe on Windows (follow-up).
The CBM_WORKERS env tests called bare setenv/unsetenv, which are
POSIX-only and undeclared on the MSYS2 Windows test build. Switch to the
cbm_setenv/cbm_unsetenv compat wrappers (a no-op #define to setenv on
POSIX) and include foundation/compat.h. Fixes the test-windows build.
- pass_pkgmap: lstat/S_ISLNK are POSIX-only and undeclared on the MSYS2
Windows build. Guard them under #ifndef _WIN32 (plain stat on Windows,
where symlinks aren't a concern here). Fixes the Windows build break
introduced by the workspace-manifest walker.
- Run clang-format-20 over str_util.c, store.c, mcp.c, pipeline.c,
pass_route_nodes.c, pass_pkgmap.c so the recently-landed pkgmap / route /
security passes satisfy the lint gate.
- cbm_arena_alloc returns NULL on a NULL arena (both arena.c copies)
instead of dereferencing it — defense-in-depth against the NULL-arena
type-allocation path that could crash the LSP type layer.
- discover: add "vendored" to the always-skip directory list.
Distilled from #374, taking the parts not already on main. The PR's
ts_lsp tuple-arena allocation, the C/C++ template formal-count clamp,
and the type_rep unbound-param preservation it also carried all landed
independently in v0.7.0 / via #322 / #360, so only these two
defensive improvements remain. Relates to #390.
Two structural fixes for TS monorepos (pnpm/yarn workspaces) and
SvelteKit projects, measured to take a 885-file SvelteKit+Turborepo repo
from 319 edges to 31802 (100x) with no fixture regressions.
- pkgmap: package.json / composer.json live in IGNORED_JSON_FILES, so the
manifest-driven pkgmap builders never saw them and every
'@my/pkg' workspace import was silently dropped. Add a symlink-safe,
skip-dir-aware filesystem walker (cbm_pkgmap_scan_repo /
cbm_pkgmap_build_from_repo) that harvests manifests directly from the
repo regardless of the discovery filter. Sequential and parallel paths
both feed it now.
- definitions: extract every file's defs (creating Module nodes) BEFORE
resolving imports, so a workspace import in the first file can resolve
against the complete in-memory graph (two-phase, cache-backed).
- routes: synthesise Route nodes + HANDLES edges from SvelteKit's
filesystem layout (+server / +page.server / +layout.server), which has
no app.get(...) call site for pass_calls to pick up.
Distilled from #369 onto current main. The PR's CBM_DISABLE_LSP_CROSS
escape hatch was rebased onto the v0.7.0 fused cross-LSP architecture:
it now gates run_cross_lsp (NULL all_defs makes the fused resolver no-op
cross-file resolution) instead of wrapping the removed standalone
pass_lsp_cross call. Relates to #271 / #56.
Replace ANSI Windows APIs (FindFirstFileA, CreateFileA, etc.) with
wide-char (W) counterparts. Add UTF-8/UTF-16 conversion helpers.
Fix stat() calls in discover pipeline to use _wstat64.
Fixes#357
Add WAL checkpoint on store close and after WAL-mode enable at startup.
This ensures graceful shutdown leaves a clean WAL, and crash recovery
merges stale WAL on next open. Both use PASSIVE mode (non-blocking,
no ftruncate). Best-effort — silently skip if concurrent reader holds
a lock.
Fixes#277
Co-authored-by: HC Cheng <hz.chang@pm.me>
Inside a container, sysconf(_SC_NPROCESSORS_ONLN) and sysinfo() report
the host's CPU count and RAM, not the cgroup's effective quota — so
cbm_default_worker_count over-provisions workers and the mmap budget can
exceed the cgroup memory cap, driving OOMKills (#363).
detect_system_linux now reads the cgroup limits:
- cbm_detect_cgroup_cpus: cgroup v2 cpu.max, then v1 cpu.cfs_quota_us /
cpu.cfs_period_us → ceil(quota/period); -1 when unlimited.
- cbm_detect_cgroup_mem: cgroup v2 memory.max, then v1
memory.limit_in_bytes; 0 when unlimited or the v1 near-ULLONG_MAX
sentinel.
- Effective value is min(cgroup, host), guarding against mis-mounted
cgroups that report more than the host.
Helpers read via a bounded read_small_file (fopen "re", capped fread)
and are exposed through system_info_internal.h for unit tests that drive
them against a fake cgroup tree. The test teardown uses opendir/unlink/
rmdir (no shell spawn).
Distilled from #365 onto current main (unioned the test additions with
the CBM_WORKERS tests from #364, and replaced the test-cleanup
system("rm -rf") with a shell-free recursive remove). Closes#363
together with #364.
Adds a CBM_WORKERS env knob (clamped to [1, 256], invalid values warned
and ignored) that explicitly sets the parallel-indexing worker count.
In containers, sysconf(_SC_NPROCESSORS_ONLN) reports host CPUs rather
than the cgroup's effective quota, so a 1-vCPU pod on a 16-core node
otherwise spawns ~16 workers — the dominant OOMKill driver. When unset,
behaviour is unchanged.
From #364.
Root cause: unbound template parameters flowed through cbm_type_substitute
as NULL, and call-expression return handling then dereferenced the NULL
substituted type. Reproduced on MangoHud's src/hud_elements.cpp.
- type_rep: cbm_type_substitute now returns the original param `t` (not
NULL) when a type arg is unbound, in both the TYPE_PARAM and named-param
paths — unbound params stay intact instead of poisoning the type.
- c_lsp: guard `if (!ret) return cbm_type_unknown();` before unwrapping
the substituted return type; guard the substitute result before use; add
template_function call-node resolution (qualified/scoped/plain names).
- extract_defs: factor template-inner lookup into find_cpp_template_inner_node,
which recurses into nested template_declarations and also handles
field_declaration; reuse it from unwrap_template_inner.
- tests: clsp_nocrash_template_function_multi_param_nested_call (the nested
reproducer) and typerep_substitute_unbound_param_preserved.
Distilled from #360 onto current main. The formal-parameter-count clamp
that overlapped this PR already landed via #322, so only the additive
crash fixes are taken here. Contributes to the C++ stability cluster (#390;
relates to #215/#312).
- str_util: add cbm_validate_project_name() — strict allowlist
([A-Za-z0-9._-], no leading dot, no '..', no path separators) so a
project name can never escape the cache directory.
- store/mcp/http_server: validate project names (fail-closed) before
interpolating them into .db file paths (path-traversal hardening;
defense-in-depth for the wrong-path class in #331).
- store: fix use-after-free in the LIKE pool — like_pool_add() frees its
argument when the pool is full, so capture pool_was_full before the add
and skip the subsequent bind that would touch the freed pointer (two
call sites). Also fix the find_edges_by_url_path LIKE pattern (stray
double-percent) and add NULL guards to cbm_store_checkpoint /
cbm_store_node_degree / cbm_store_restore_from.
- mcp: emit "result": null when a JSON-RPC response would otherwise
carry neither result nor error (2.0 compliance); bounds-check the
project-name accumulator against truncation.
- http_server: JSON-escape directory and parent names in the browse
endpoint to prevent stored-XSS/JSON injection via crafted names.
- cypher: expr_free now recurses on EXPR_FREE_STACK overflow instead of
silently dropping subtrees (memory leak).
Distilled from #352 onto current main (store.c like-pool hunk re-resolved
against the v0.7.0 search refactor; restored cypher.c EOF newline).
The 8 channel-extraction DFS walkers (Python consts, Python, Go, Java,
C#, Ruby, Elixir, Rust) used fixed-size `TSNode stack[CHAN_STACK_CAP]`
arrays whose push loops guarded with `&& top < CHAN_STACK_CAP`. On
deeply-nested ASTs that guard silently dropped children, and an
unguarded overflow could corrupt adjacent stack memory — surfacing as a
segfault in a later pass (lsp_cross / semantic_edges) on large repos.
Convert all 8 to the arena-backed growable TSNodeStack
(ts_nstack_init/push/pop), matching the pattern already used in
extract_calls.c / extract_imports.c (PR #217). No silent drops, no
overflow.
Distilled from #339 (trailing-newline cleanup only; conversion is the
author's).
The git-history popen command used `cd '%s' && git log ... 2>/dev/null`,
which fails on Windows cmd.exe: single quotes are not recognized and
/dev/null is not a valid path, producing "The filename, directory name,
or volume label syntax is incorrect" at the git-history step of a full
index. Switch to `git -C "%s" log ... 2>NUL` (POSIX: 2>/dev/null),
which is valid on both cmd.exe and POSIX shells. Double-quoting the path
is safe because cbm_validate_shell_arg already rejects ", $, backtick,
backslash and the other active metacharacters before the command is built.
Distilled from #325 onto current main (preserves the :%%ct commit-time
field added since the PR branch). Closes#324.
Adds a per-file expression-eval step budget (10k) to the C/C++ LSP resolver so pathological recovery-mode ASTs degrade to unknown instead of hanging indexing (the Bitcoin-derived C++ repro). Verified locally: build clean, all 3,617 tests pass including the full clsp_ suite.
Fixes the OOB read in c_resolve_pending_template_calls when a call site has more arguments than the parsed function signature (issue #215). Verified locally: build clean, all 3,617 tests pass including the new clsp_nocrash_template_extra_call_args regression.
Route Claude Code config paths (skills, .mcp.json, .claude.json,
settings.json, hook scripts) and agent detection through
CLAUDE_CONFIG_DIR-aware helpers, falling back to ~/.claude. Hook command
strings written to settings.json keep the legacy tilde form when the env
var is unset, so existing configs stay portable across HOME values. Prints
a one-line migration nudge when CLAUDE_CONFIG_DIR is set and a legacy
~/.claude tree still exists.
Adds cli_detect_agents_finds_claude_via_env and isolates CLAUDE_CONFIG_DIR
in the existing detection tests so the runner env can't leak in.
Distilled from #321 onto current main (adapts to the v0.7.0 non-blocking
augmenter hook signature, which the original branch predated). Closes#320.
- New top-level "Hybrid LSP" section above "Language Support" with a
short explanation of why tree-sitter alone is insufficient, what the
clean-room LSP layer does, a per-language coverage table (Python,
TS/JS/JSX/TSX, PHP, C#, Go, C/C++) marking what's new in v0.7.0 and
what was sharpened, and the two-layer architecture.
- Hero paragraph + indexing-pipeline bullet now list the full v0.7.0
Hybrid-LSP language set and link to the new section.
- Tests badge bumped 2812 → 3615 (from the ubuntu-latest CI log of the
published v0.7.0 release run).
- New "Hybrid LSP — 6 languages" badge linking to the new section.
- VirusTotal click-through badge + per-binary scans table refreshed to
the v0.7.0 binaries (linux-amd64/arm64, darwin-amd64/arm64,
windows-amd64) using the hashes from the published release body.
The release.yml workflow publishes both wrappers as-is from the repo;
they were still pinned to 0.6.1, which made the npm publish step try to
republish v0.6.1 instead of v0.7.0.
The LSP-cross loop in parallel_resolve called cbm_slab_reclaim() without
first destroying the thread-local tree-sitter parser, violating the
documented slab contract. The parser's lexer holds slab-allocated state
(notably lexer.included_ranges, a 24-byte allocation that lands in the
≤64B slab bucket); reclaiming the slab leaves that pointer dangling.
Workers don't trip on it because they don't reparse afterward, but the
main thread participates in cbm_parallel_for and any later cbm_extract_file
on the main thread — including the sequential pass_definitions in the
incremental pipeline — hits a heap-use-after-free in ts_lexer_goto on
macOS-ASan.
Two-part fix:
- pass_parallel.c: destroy the thread parser before cbm_slab_reclaim() in
the per-file LSP-cross loop, matching the worker extract loop.
- pass_definitions.c: defensively drop the thread parser at pass entry so
the incremental sequential path cannot inherit stale state from any
prior run.
GCC's preprocessor doesn't short-circuit 'defined(__has_feature) && __has_feature(...)' — it tries to parse the call syntax of the undefined token and errors with 'missing binary operator before token "("'. Modern GCC and Clang both define __SANITIZE_ADDRESS__ under -fsanitize=address, so use that single check.
Free the result before the time-budget assert (no leak on miss) and scale the budget ×10 when ASan/UBSan is active, so the perf benches run cleanly in the release CI (skip_perf=false) instead of only being gated by CBM_SKIP_PERF in dry-run.
Same fix as read_file in pass_definitions: the calls/usages/semantic/k8s/lsp_cross passes each read a source file into a malloc'd buffer and re-parse it, so they hit the same tree-sitter lexer over-read past EOF under ASan. Over-allocate by 16 and zero the tail in every reader.
read_file allocated size+1, but tree-sitter's lexer peeks a few bytes past the final UTF-8 char (ts_lexer_goto), reading past the buffer end. Under ASan this is a heap-buffer-overflow (intermittently flagged in the incremental suite on macOS); in production it is a benign over-read of adjacent heap. Over-allocate by 16 bytes and zero the tail so the lookahead stays in-bounds.
cbm_mkdtemp rewrites the template in place to the translated %TEMP% path on Windows, which overflowed the 23-byte 'char tmpl[]' literal (ASan stack-buffer-overflow). Use a 256-byte buffer as the cbm_mkdtemp contract requires (matching the other test callers).
Four tests hardcoded POSIX /tmp/ paths that don't exist on Windows: test_path_alias used raw mkdtemp("/tmp/...") and the store pragma/checkpoint tests built /tmp/*.db directly. Switch to cbm_mkdtemp (translates /tmp/ -> %TEMP% on Windows) and cbm_tmpdir() (matching test_store_bulk). build/smoke/soak-windows already pass; this makes the Windows unit-test job green too.