The cross-file Constant resolution test walks USAGE edges and calls
cbm_store_find_node_by_id() for each source, which populates the caller's
cbm_node_t with heap_strdup'd fields (store.c:195 via scan_node). It freed
the arrays it owned -- consts and edges -- but never released the per-edge
node, leaking one set of fields per iteration.
LeakSanitizer caught it in every sanitizer-enabled lane while the plain
test shards and both Windows legs stayed green, which is why the local
test-runner could not see it:
test-lsan-macos 417 bytes in 6 allocations
test-unix (ubuntu-latest, 2/3) 401 bytes in 6 allocations
test-unix (ubuntu-24.04-arm, 2/3) same signature
test-diag same signature
Use cbm_node_free_fields(), not cbm_store_free_nodes(). The latter frees
the array pointer itself after releasing each element's fields, so calling
it on a stack-allocated node is a bad free -- ASan aborts with
"bad-free test_lang_contract.c" if you try. cbm_node_free_fields() releases
the strdup'd members and leaves the struct alone, which is the idiom the
neighbouring probes already use (test_edge_types_probe.c:245).
The three other find_node_by_id call sites in this file predate this branch
and already handle their own cleanup; only the new Chialisp contract leaked.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-authored-by: darkverbito <8049985+darkverbito@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The Chialisp commit was formatted with the pinned Homebrew clang-format, which
is right for the production sources but wrong for the three test files it also
touched: tests/ is not in LINT_SRCS, so those files have never been under the
format gate and were already format-dirty on main. Running the formatter over
them rewrote code that had nothing to do with Chialisp — 478 whitespace-only
lines in repro_invariant_breadth.c alone, plus 50 in test_extraction.c and 14 in
test_lang_contract.c — which is a review burden and hides the actual change.
Restore those three files to main's bytes and re-apply only the Chialisp
additions, in each file's own house style. The result is token-identical to the
version that was tested, apart from one em-dash written as `--` in a comment.
Re-verified after the rewrite: the six chialisp extraction tests, the cross-file
Constant graph test, the matrix-B row, the breadth case
(`[INV-BREADTH] PASS chialisp calls=1 callable=1 module=0`), and `make lint-ci`.
The production sources keep their formatting: they ARE in LINT_SRCS, and the
only whitespace they carry is the trailing-comment realignment in cbm.h that
adding a longer enumerator forces, plus the re-indent in extract_defs.c from
moving the name-resolution block into an else branch.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Chialisp (.clsp/.clib/.clinc) is the Chia smart-coin language. The only
public grammar, Quexington/tree-sitter-chialisp, cannot parse it: on the five
reference files from chia-blockchain@main it produced 3-34 ERROR nodes each.
Three defects account for all of them, and each desynchronised the rest of the
file rather than losing one node:
* comments were `/;.*\r\n/` -- CRLF-only, while real files are LF;
* `(include foo.clib)` was rejected because of the dot in the filename;
* `(defconstant NAME <expr>)` accepted only a primitive after the name.
So the grammar is written here instead, in tools/tree-sitter-chialisp/, and
files as first-party: its vendored LICENSE is the project's own, byte-identical
to the repository root, carrying no third-party copyright.
It is deliberately GENERIC -- source_file / list / symbol / string / number /
hex / dot / comment, modelling the clvm_tools reader rather than the Chialisp
form vocabulary. `mod`, `defun`, `defconstant` and `include` are ordinary head
symbols; which lists are definitions is decided in extract_defs.c. A dialect
that adds a form (cl-21 -> cl-23 -> cl-26 each added several) therefore needs
no regenerated parser. Acceptance: all five reference files parse with zero
ERROR and zero MISSING nodes; the three defects above are pinned as tests.
Extraction, and the four decisions in it:
* `Constant` is admitted to the cross-file name registry
(cbm_label_is_registry_symbol). Chialisp's condition codes live as
`(defconstant ...)` in a shared .clib and are referenced by name from every
puzzle; labelled but unregistered they are seeded nowhere, and roughly half
of a Chialisp graph would be present as nodes yet never the target of an
edge. All three registry-seeding sites route through that one predicate, so
they cannot diverge. No other language emits `Constant`, so the blast radius
is exactly Chialisp. Pinned by a two-file graph test that goes red when the
label is removed from the predicate.
* `export` / `namespace` are NOT definition heads. `(export foo)` names a
function already defined in the same file, so treating it as a def head
mints a second node for one symbol. They stay in the not-a-call filter.
* A parameter list is a `list` too, but it binds rather than invokes. Without
that gate `(defun check_conditions (HEIGHTLOCK conditions) ...)` mints a
phantom call to HEIGHTLOCK, and real Chialisp is mostly such definitions.
* `(include *standard-cl-26*)` is a dialect directive, not a file dependency;
recording it would invent a dependency on a file that does not exist.
Three helpers that the defs, calls and unified walks all need -- the quote-data
gate, comment-skipping named-child access, and the Chialisp def-head set -- live
in helpers.c rather than being copied per translation unit. They previously
existed as byte-identical duplicates that had already drifted: extract_defs.c
skipped comments when reading a def head while its mirror in extract_unified.c
did not, so a comment between a head and its name desynchronised defs from call
scope. Both now use the same accessor.
Ledger: 161 -> 162 grammars across the seven published surfaces, ABI-14 86 -> 87,
first-party 13 -> 14, CBM_LANG_COUNT 164 -> 165.
Distilled from PR #919 by darkverbito, whose extraction logic -- the CLVM
not-a-call vocabulary, the quoted-data gate, the filename-derived `mod` name and
the include/embed import handling -- is kept and is the substance of this change.
The grammar that PR vendored is not used.
Co-authored-by: darkverbito <8049985+darkverbito@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Escape import aliases and GraphQL operation strings before serializing edge properties, preventing raw control characters and quotes from producing malformed JSON.
Add a reachable single-line GraphQL regression fixture and assert that emitted edge properties remain valid JSON.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: tmonestudio <tmonestudio@users.noreply.github.com>
Three language fixtures assign cbm_project_name_from_path into lp->project each
time they open an indexed store. Teardown frees the last one, so a fixture that
indexes more than once dropped every earlier heap name — 68 allocations across the
run, which the macOS leak lane reports at process exit.
Pre-existing rather than introduced here: none of these files or fqn.c are in this
branch's diff, and the leak lane passed on the previous PR. Adding a suite shifted
what the leak job sees, which is how it surfaced. Fixing it here rather than
recording it, since a gate that only stays green by composition accident is not a
gate.
Freeing before reassigning also makes the ownership obvious at the point it
matters, instead of relying on the reader noticing the teardown three functions
away.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Unblock CI lint so the test job runs. Prefer Homebrew LLVM formatting
on the member IMPORTS retarget. Add contract that
`from ghost_module import nope as alias; alias()` emits no CALLS.
Relabel registry alias pin as adversarial (behavior already on main).
Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
resolve_module often lands on the Module for pkg.mod.symbol paths.
When the member Function/Class exists, IMPORTS must target it so
\`from M import f as g\` + g() can CALLS→f (Yui bridge_execute shape).
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
from M import f as g + g() must emit CALLS→f, not invent M.g.
Use resolve_import_node in sequential import maps, and prefer an
exact registry hit when the IMPORTS target is already the def QN.
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: 涂名恩 <125283161+Joseph-MingEn@users.noreply.github.com>
Run the test process under a private temporary HOME and clear inherited CBM_CACHE_DIR so production index paths and legacy fixtures resolve to the same isolated cache.
Make lang-contract fixtures honor an explicit cache override and remove the sentinel tree at process exit, including after early assertion returns.
Validated on Windows with the full runner: 5828 passed, 22 skipped; a 500-project user cache had zero files added or removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: tmonestudio <tmonestudio@users.noreply.github.com>
from m import f as g; g() produced no CALLS edge at all. Two stacked
defects: (1) py_lsp_bind_imports keyed from-style detection on the
local name matching the module-path tail - an alias never matches, so
g was bound MODULE(m.f) and the call missed; (2) even with the binding
fixed, the pass join compares the bare tail of the resolved QN (f)
against the textual callee (g) and dropped the resolution.
Fix: (1) a dotted module path whose tail differs from the local name
is an aliased binding - bind NAMED(qn) and let phase 6 upgrade it to
the registered function/class (from-import alias) or module (import
a.b as z); (2) when the identifier-callee NAMED branch resolves a name
whose textual form differs from the QN tail, emit strategy
lsp_import_alias with the textual name stashed in reason, and gate
that strategy in the join's reason list - the exact pattern already
used by lsp_dict_dispatch and lsp_func_ptr. The parallel path's hash
index misses the alias key and falls through to the same gated linear
scan.
The contract test pins all four import forms (aliased from-import,
plain from-import, single-segment module alias, dotted module alias)
so the flip from MODULE to NAMED binding cannot regress the module-
alias attribute chains.
Closes#988
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
URLs in .pre-commit-config.yaml, .github/workflows and similar CI
configs are repository/action/registry references for the development
toolchain, never endpoints the indexed service exposes. They matched
is_infra_file (.yaml) and passed the #521 value guards, so each minted
a __route__infra__<url> Route node; the route matcher's root-service
heuristic then attached every handler of an ambiguous '/' route to each
junk URL - 6-7 junk HANDLES targets per handler on plain pallets/flask,
with tie-break-dependent winners feeding HANDLES churn between builds.
Deny by file identity (well-known CI/tooling basenames plus .github/,
.gitlab/, .circleci/ trees) in cbm_pipeline_extract_infra_routes, not
by URL shape: deployment configs (Cloud Scheduler push endpoints,
compose) keep minting their genuine endpoints, guarded by an inverse
test.
Closes#999
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
cbm_store_find_node_by_id heap-allocates the node's string fields;
the #871 helper left them unfreed, tripping LSan on the Linux legs.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
const foo = require('./foo') emitted a Variable definition for the
binding, so calls to foo() resolved to that same-module Variable (an
import alias, not a definition) instead of following the recorded
import to the exported function - which stayed at zero inbound CALLS
and surfaced as a disconnected Variable node. ESM import bindings never
materialize as Variables; the identifier-bound require form now behaves
identically: extract_js_vars skips the Variable def exactly when the
declarator value is a require() call with a string-literal path (the
form extract_imports records with local_name = the identifier), letting
resolution fall through to the import map. Destructured requires keep
their Variable defs since the import row only records the module.
Closes#871
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A wildcard tsconfig path alias (e.g. "@lib/*" -> "./src/lib/*") matches
any import specifier sharing that prefix, including an unrelated scoped
npm package meant to resolve from node_modules. The direct match
already failed cleanly (no node exists at the substituted path), but
cbm_pipeline_resolve_import_node's Strategy 4 - a generic "strip a
trailing path segment and retry" fallback applied to every language,
not just its intended Rust crate::/self::/super:: case - re-resolves
the truncated import string and can land on the tsconfig's OTHER, bare
alias entry that maps straight to a directory. The resulting QN
collides with that directory's real Folder node (cbm_pipeline_fqn_module
and cbm_pipeline_fqn_folder both dot-join path segments with no type
tag), and Strategy 4 accepted whatever node it found there.
Strategy 3 (symbol-name fallback) already guards its matches with
import_targetable_label(), which excludes Folder/Project/etc. Strategy
1b (resolve_sibling_file, used only for markup/build-tool sibling
imports - SCSS/Just/BitBake/Meson/Pony, none of which are directory-
module languages) and Strategy 4 lacked the same guard; both now use
it too.
Strategy 1 (the primary, direct resolution path) intentionally keeps
no such filter: Go and Java are directory-module languages in this
pipeline (cbm_lang_module_is_dir), so a bare package import correctly
and deliberately resolves straight to a Folder node there - adding the
guard to Strategy 1 broke that (verified via the Go/Java integration
tests) before being scoped down to just the fallback strategies where
the collision actually originates.
Closes#767.
Signed-off-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com>
Removes the heaviest vendored grammar (66 MB) along with its language
wiring and test fixtures; the supported-language count moves to 158
across README, site, and manifest. The grammar manifest additionally
records the canonical-source decisions for the five
registry-disagreement grammars and the per-directory license files
restored earlier this week.
A Lerna/Yarn workspaces monorepo where packages/b imports a sibling by package name (@org/a) should resolve via package.json name->dir into a cross-package IMPORTS edge. Reproduction contract; verifies whether the generic workspaces case resolves (cbm_pipeline_resolve_module already looks up bare specifiers in the pkgmap) or exposes a gap.
git is now installed on every CI platform (incl. the Windows msys2 env) and run_git uses portable git -C, so a failed init is a broken-env failure, not a platform skip.
fopen text mode rewrites newlines to CRLF on Windows, making line-ending-sensitive grammars under-extract (below_min/calls_breadth). Open fixtures with wb. Also skip the git-history contract when git init fails (platform limitation) rather than failing.
cbm_mkdtemp on msys2 yields a backslash Windows path; embedding it in the JSON repo_path produced invalid escapes so index_repository failed (count=-1) for every harness contract, plus mixed-separator git -C args. Normalize to forward slashes after mkdtemp — valid in JSON and accepted by Windows file APIs and git.
extract_crashes uses fork/waitpid/<sys/wait.h>, which msys2-clang lacks, so the Windows test build failed with 'sys/wait.h not found'. Guard the include for _WIN32 and run extraction in-process there (fixtures are crash-free; the fork-isolated check still runs on the POSIX CI legs).
GCC at -O1 flags the partially-filled LangFile arrays in contract_all_grammars_in_graph and index_parallel_fixture as maybe-used-uninitialized when passed to lang_index_files (clang does not), failing the -Werror Linux test build. Zero-initialize both; only files[0..nfiles-1] are ever read, so behavior is unchanged.
Index a fixture for every grammar through the full pipeline and assert graph-level invariants: golden node-label histograms, def/node breadth, CALLS and IMPORTS breadth, presence of all 26 pipeline edge types (structural, type-relationship, service/dataflow, and similarity), no-crash, and call resolution. Reproduced quality gaps (under-extraction, import-extraction, and CALLS-resolution) are kept as hard failures for the fix phase rather than skipped. Adds a real-repo scale tier (scale_contract.sh) and ignores local-only private/ scratch.