sqlalchemy's package inits (sqlalchemy, sqlalchemy.orm, sqlalchemy.sql) and
orm/collections.py end with `__go(locals())`. Any module-level locals() handed
to a call failed the whole file as a dynamic namespace effect, so every import
there was unaudited and every export UNSUPPORTED: 14,464 real CALLS edges of
sqlalchemy abstained as python_stabilized_export_binding, and their children
(`from sqlalchemy import event`) as python_export_member_dispatch_unproven.
Three rules, each true of Python:
- `f(locals())` / `f(vars())` at module level writes nothing when f is the
module's one undecorated def, bound before the call, whose body never spells
the parameter receiving the namespace, nor locals, vars, eval, exec or a
frame's locals. The body's own effects are walked as anywhere else.
- eval/exec of one plain string literal that is a single identifier only reads
it (pydantic/v1/utils.py `eval('__IPYTHON__')`).
- `alias = globals()` in a function writes nothing when every other spelling
of the alias there is a membership test, an item read or a read-only method,
and the function has no other way to reach it (sqlalchemy exc.py and
ext/automap.py). A module-level alias stays an effect: it is a module
attribute another file can write through.
Referee against SCIP, python, 10 repos: recall 70.5% -> 82.0%, precision
99.3% -> 99.2%. sqlalchemy 42.9% -> 67.6%, pydantic 85.4% -> 90.6%; click,
flask, poetry, django, celery, httpx, scrapy, starlette unchanged. Every new
sqlalchemy SCIP disagreement was listed and read: builtin/typing name
collisions, same-line chain mappings, and narrowed-type selections.
The pipeline gap python-reexport.go_reexport is now required: __go never
spells lcls. Its parameter-write decoy stays forbidden.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A Go import targets the node of its package's directory, and the strict
import path accepted only Folder nodes. The repository root has none: the
Project node is that directory, and its QN is the module of every root
file. caddy keeps go.mod and `package caddy` at the root, imported as
"github.com/caddyserver/caddy/v2", so that import never resolved and every
caddy.X call, and everything typed through one, stayed unknown under the
generic shared_binding_type_fixed_point reason. No cap was involved.
go_package_node accepts the Project node for the root directory in the
module-path strategy, the duplicate-module fallback and the ownership
check, which reads the package directory ("" for the root) from it. The
ownership rule itself is unchanged: a module without a replace directive
or go.work entry still takes the path from the module cache.
caddy against SCIP: recall 83.45% -> 95.89%, real lost 794 -> 200, never
found 229 -> 54, new false 0 -> 0. traefik, minio, cli, helm, prometheus
and etcd unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Its sibling callback writes through the same parameter, so the shared type cannot take const.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A forward-declared C++ class in view binds to the one definition of that qualified name, by the one-definition rule; with several definitions, to the one whose file includes the declaration. C tags have no linkage and stay abstained. A forward in view outranks a short-name match in another namespace, which removes false edges such as lldb's Stream to llvm::yaml::Stream. Color::RED, gfx::Blend::ADD and typedef'd enum values bind.
Repairs: an #include in an enum body beside a #define no longer breaks the rest of the file; a brace opened in one #ifndef block and closed in another, as DeclSpec.h does, no longer costs the file its namespace; #include inside a namespace is read. Whole llvm-project: CALLS 647,135 to 654,085, USAGE 970,146 to 1,015,783, rc 0, 603 s, no quarantine. Changed relations sampled from source: no wrong edge.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Layout only, on the six files whose new lines clang-format would wrap differently. The suites ran on the tree before this commit.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The k8s change of f8bbdc07c, reverted in f344f72a7, left the same-copies quadratic where it was: the pass took 1,192 -> 5,010 -> 17,024 ms at 2,000 -> 4,000 -> 8,000 same-named copies with it and 1,217 -> 4,791 ms without it, and its growth test stayed at ratio 4.00. The references were already cheap (refs=8000 edges=8000); the cost is in another step of the pass. The test goes with the change.
internal/cbm/lsp/lsp_all.c was an empty file a touch created, picked up by 750b68951.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Each name of a file walked every import, imports times names. The imports that can change a name's answer are those whose library, or something it exports, declares the name, and those whose library the build's configuration picks; every other import adds nothing wherever it stands. They are found from the libraries declaring the name, up through who exports them, and asked in written order, so each reason stays the one the first deciding import gives. A prefix and an outside import with no filter are looked up by name.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Each reference gathered and sorted every same-named object of its bundles. A USAGE reference draws its edge only when exactly one object answers it, so it now stops at the second answer, which is the same rule written without the gather. Any other reference with a copy in its own file means that copy, so it reads only its own file's objects, indexed by file and name. Same-named copies of one directory are linear now.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A chain of one name held every file's private declaration of it, and each lookup read them all: private let logger in every file of a module cost files squared. The table now keeps, for a chain holding a file-local declaration, the others in order and each file's first own one, and the seven loops that skip invisible privates read that view: the same declarations in the same order, the others never read.
A name no declaration of the project has, and no closed protocol's member, can only find whether a type's chain leaves the project, which is the type's answer and not the name's. It is walked once per file per type. This keeps extension A: P bases linear now that the chain walk has no bound.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Bases and mixins of Dart, PHP, Swift, Scala, Ruby and Perl classes, a PHP variable's writes, a Python function's returns, the files a shell process sources, the C# using-directive walk and the headers a C translation unit reaches were fixed arrays: past them a declaration was dropped or its class went open. They grow on the walk's arena now. A shell command asks the files that declare its name instead of every file of the process.
The binding walks, the source adapters and the proof passes each pass over a file a bounded number of times, and their node and visit counts voided a large generated file whole. Only their nesting guards stay. A Rust trait's method names are no longer cut at 63.
Tests: a class of 70 traits, a variable of 12 writes, a class of 40 bases, a process of 300 sourced files, the seen set's growth and depth guard, and the extension-named imports of item 4, which bind nothing. The chain test of item 1 now measures the walk alone, and the barrel's class is no substring of another needle.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
CBM_SOURCE_CHAIN_MAX was a fixed array of 128 names: past it a class chain or a Dart barrel abstained because a counter ran out. The seen set now starts inline and moves to a table on the walk's arena, with constant lookups. Recursion was bounded by that array too, so each recursing walker now keeps a depth, with a guard of 1024 levels for the stack.
A Dart barrel read its export list for every name of every file, which only the old bound kept near linear. A name's walk now starts from the libraries declaring it and goes up through who exports them, without recursion, so a barrel's width costs nothing.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
GCC's production build refused the null %s, with -Werror. Also the Lua and Shell adapters' cppcheck items: an always-false check and three scopes that can narrow.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Elixir and Erlang made a composite value a CALL_REFERENCE; a dozen walks that grew with the square of the input now read an index once, with the graph unchanged on 12 fingerprints across nine repositories.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Each call's spawn check read the file's import rows to find the one its callee's head names, in every language with a spawn table, on both drivers. A Tcl script of 32,000 `source` lines asked that 32,000 times and spent 1.1 s on it, and a call to a name a table lists read the rows once more for its module.
A file's calls now ask a table built on the first question: a local name's first row with a path, and for each module a table names, whether a row imports it. It lasts one file on one thread and answers only for the rows it was made from. Anything else, a name too long for the buffer, or memory running out, reads the rows as before.
complexity_spawn_match_is_linear_in_a_files_imports covers a repeated callee, distinct callees and a module check. complexity_spawn_file_index_answers_as_the_rows_do compares the two routes across languages and callees, with rows lacking a path, repeated names and rows of another file. Numbers and identity: the lane report.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The last dot ends an import's local name, so every `source ./a.sh` row is named sh, and every `part 'a.dart'` dart. All of a file's rows then conflict, and each one cleared all of the name's IMPORTS entries, which the row before it had just cleared. One shell file of 16,000 `source` lines spent 3.8 s in its import map, and one Dart library of 16,000 parts 3.7 s.
Each name now clears them once. Nothing else can add such an entry after the first row: edges are all read before the rows, and a name no edge mapped has none. The map holds what it held.
complexity_import_map_same_local_name_is_linear asserts the map's contents at both sizes. Numbers and identity: the lane report.
The extension as a local name is recorded there, not changed.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The oracle treats an edge nobody listed as wrong: the struct values and the function in the shorthand case are real, and the second body's call is the file's second occurrence.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
They ship the test runner itself as the fake release, and it is now 525,426,472 bytes against a limit of 524,288,000, so the update reported a binary missing from the archive. The limit is right for a release and stays. These two tests lift it for themselves through a seam, and a new test pins that an archive over the limit is refused, which nothing did.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The direct writer's tables point at CREATE text the stage owns, and finalize runs whether the stage worked or not. On failure the stage was freed first, so finalize built sqlite_master from freed memory: heap-use-after-free in importance and pipeline_semantic_manifest_repro, whose rejected publish reaches it. The caller already frees the stage on every path, so it now gets the stage back either way.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A name that is not a type's own member asks each of its bases, so a type with many conforming extensions cost bases x names: 16,000 `extension A: P` took 157 s in resolve. Past the chain's bound of 128, every further base answers open, so the loop goes on only to reach the answer it already has.
It now stops there, once the bound has passed and the answer is already open. Nothing past that point can change the result: a base can no longer be found or be ambiguous, and an open answer returns the same whatever `declared` or `result` hold. The `extension_only` scan also stops at its first declared record.
complexity_swift_type_bases_are_near_linear: 3.84 on the old walk, 2.00 now.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Every file that imports a barrel read the barrel's whole export list again, though one name's walk stops at the chain's bound of 128 libraries. With the barrel listing the project, that is files x exports: 16,000 libraries took 39.7 s in resolve.
Exports are now read when a walk first reaches them, and a walk stops once the bound is passed, since no further library can then be visited or change its answer. So `dart_export_graph_not_walked` is still what a name past the bound gets.
Left as it was: an export that a `show` or `hide` rules out costs a filter check per name and does not count toward the bound.
complexity_dart_barrel_exports_are_near_linear: 3.71 on the old read, 1.94 now.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
NASM and ObjectScript routines are flat: each statement looks back over its earlier siblings for the label above it. A long stretch under one label was quadratic in steps, and each step was itself a walk from the first child.
The walk reaches siblings in order, so a search now stops at the sibling the last search under this parent started from, and what that one found holds for everything before it. One memo per ancestor level, for NASM's climb. Which label a statement gets is unchanged.
complexity_flat_preceding_label_is_linear: NASM 4.00 on the old walk, 2.00 now; ObjectScript 2.00 now.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
For every route call, go_find_binding walked back over the statements before it to find what the receiver was bound to, and each step back was itself a walk from the block's first child. A function of 16,000 `r.GET("/x", h)` lines spent 91 s in extraction, and 16,000 `g := r.Group("/g")` pairs 207 s.
A block's bindings are now read once, the first time a route call in it asks: every name each statement binds, by name and then in source order, in `scratch`, so it ends with the file. A question is a binary search for the last binding before the statement that asks. Which statement answers is unchanged: rebinding, shadowing, `var` lists, tuples, a name with no value and function boundaries all behave as before, and running out of memory still gives no prefix rather than a partial one.
complexity_go_route_bindings_are_near_linear, old walk -> now: see the lane report for each row. Identity: 12 fingerprints identical on gitea (1,054 `m.X("/...")` registrations in two files) and on a generated tree of 834 routes that composes each of those forms. gitea registers on one receiver and has no `g := m.Group(...)` binding in those files, so the generated tree is what covers the group forms.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A source adapter asks which definition of a name belongs to each declaration, and the answer walked every definition of that name in the file. A name can repeat all through a file, as a script's `x=1`, a chunk's `local ok` or `init` in generated code do, so the join was quadratic in the repeats. Every adapter goes through these three calls.
A name's definitions are now sorted by first line, with a link from each to the nearest earlier one still open where it starts. The answer is a binary search, then that path, which is as long as the nesting there. The tie-break is unchanged: the tightest span, and of equal spans the latest in the file's own order. `cbm_source_def_qn_of_name` keeps its answer per name.
complexity_source_same_named_definitions_are_near_linear covers Lua functions and locals, Shell, Vim, Tcl, Swift, Dart and Haskell. See the lane report for each row's ratio on the old walk. Graph output is unchanged: 12 fingerprints identical on neovim, redis, koel, Alamofire, bloc, plug, cowboy, gitea and a generated tree of nested same-named definitions.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
ts_node_prev_sibling and ts_node_prev_named_sibling walk from the parent's first child to the node, so asking once per child of a wide parent is quadratic in sibling steps alone, and a walk back is worse. Sites that do it per definition or per statement include the decorators before a method, a Dart function_body's signature, a C `TEST(x) {}` body and a Dart selector's callee.
Both now route through the index, like ts_node_parent and ts_node_child, so every file that includes syntax_index.h gets them, including a call added later. The answer comes from the list the index already holds. A zero-width node keeps tree-sitter's own answer: its place among byte offsets is what ts_node__prev_sibling decides, and the list cannot.
complexity_syntax_index_prev_siblings_answer_as_tree_sitter_does compares both calls with tree-sitter on every node of eight sources, parse errors and MISSING tokens included. complexity_prev_sibling_walk_is_linear: plain 4.00, indexed 2.00.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The extras path in resolve_calls called any callable a site names a CALL_REFERENCE, with no composite check. So `flag && &a/0`, `if flag, do: &a/0, else: &b/0` and `case Flag of true -> fun a/0; false -> fun b/0 end` each gave CALL_REFERENCE edges, which call_reference_language_complex_contract forbids: the value is one of several. This is the Perl bug in the other carrier.
The adapter now says so, as a carrier's may_be_call_reference does: an extra site carries `composite`, and such a site is USAGE. It holds under `&&`, `||`, `and`, `or`, `andalso`, `orelse`, and among the branches of `if`, `unless`, `case`, `cond`, `with`, `try`, `receive` and `maybe`, parentheses and blocks aside. A direct argument, a bound name, a return, a list or tuple entry, a message sent and a closure's body are one value again and stay CALL_REFERENCE, including inside a branch.
Ruby and Dart are not affected, and the contract suite now pins them. Ruby's `method(:a)` binds nothing, so it is no edge. A Dart tear-off is USAGE in every form, which also means a direct one never gets CALL_REFERENCE: a recall gap, not a false edge.
RED with the two assignments reverted, on the oracle with 2c3967d88: elixir-composite wrong=17, erlang-composite wrong=15, and both new contract rows fail on `reference=1/1`.
plug, cowboy and bloc: all 12 fingerprints identical before and after, so none of their 8, 19 and 23 CALL_REFERENCE edges was composite. No recall is traded on them.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
`struct X;` directly under `#ifdef` has the directive as its parent, and only the translation unit above it says the tag is being declared. The directive's own name climbs to the root first and settles that path, so without `!bodyless_tag &&` the tag's climb stops at the directive and leaves a usage row for a declaration. The guard came in during a merge, by reasoning, with no test.
Removing the guard fails this at `lone_under_ifdef`: 1 row, expected 0.
The decoys count raw rows: an active branch is read twice, raw and preprocessed, so `#ifndef OTHER` gives two rows in all.
Not covered, because it is not the guard's doing: `struct X;` under `#else` stays a usage row with or without the guard. Its `alternative` field trips is_value_field first.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
swift_closure_signature returned a pointer to a local array for () -> Void, and the signature was stored and read long after, so what a closure's parameters meant came from a dead frame. That signature is everywhere in Swift. cppcheck found it; the test goes red under detect_stack_use_after_return=1, which the normal test run turns off.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The exemption for edges with their own producer compared json_extract(...) with =. For an edge without that key the comparison is NULL, NOT (NULL) is NULL, and the row left the unattested-edge audit: every CALLS edge to a Function lacking strategy, every USAGE into a Table, View or Model lacking via. IS keeps them in. resolver_quality_oracle_rejects_mutated_occurrences_and_projection caught it with an edge that has no occurrence row.
A Kotlin parameter's written type is USES_TYPE of the class, which the graph now states; the fixture still held it as a USAGE known gap.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Both drain workers ended without cbm_destroy_thread_parser, so the 24 KiB field-name cache of each worker thread was lost on every index, which a long-running server pays for. LeakSanitizer on Linux shows it across 29 suite runs; macOS has no LSan.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The exemption for edges with their own producer compared json_extract(...) with =. For an edge without that key the comparison is NULL, NOT (NULL) is NULL, and the row left the unattested-edge audit: every CALLS edge to a Function lacking strategy, every USAGE into a Table, View or Model lacking via. IS keeps them in. resolver_quality_oracle_rejects_mutated_occurrences_and_projection caught it with an edge that has no occurrence row.
A Kotlin parameter's written type is USES_TYPE of the class, which the graph now states; the fixture still held it as a USAGE known gap.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
enum E : int; and enum class E; inside a block reached c_bind_local_tag_and_enumerators with no body, which then read the enumerators of a null node. A worker crash has its file quarantined and the index still reports success, so one such file was dropped silently; a second exhausted the retries and failed the whole index, as clang's own tests did on llvm-project.
The lone-tag rule keeps its meaning: the name still hides the outer enum from there on.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Lua CALLS on neovim go from 4 to 19,685 against 20,307 at baseline, whose count holds wrong targets: lexical scope and value flow through aliases, function M.f, return M, require via package.path forms or a unique lua, src or lib root, and __index with @class parents. Whole-file gates on load, _G, goto and ... are gone, because none can rebind a local; _ENV, setfenv and debug.setlocal still abstain. New adapters give Vimscript, Shell, Tcl and Make proven edges: neovim vim CALLS 0 to 3,365, redis Tcl CALLS 0 to 2,815, with a function table per process following source.
A Vue or Svelte script is a module whatever its markup says: koel Vue CALLS 2 to 1,484, IMPORTS 0 to 1,650. Members of an imported object literal, and const { open } = useThing() where every return names it, resolve. An adapter may now declare parse errors local: 74% of Tcl files have one. About 490 edges read from source, one wrong and fixed.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Graph fingerprints stayed identical on 27 of 27 repositories across 15 languages on the integrated tree, while elasticsearch went from 367 s to 207 s, TypeScript 248 s to 70 s, kilocode 112 s to 34 s and django 43 s to 15 s. Elasticsearch's charged peak falls from 29.8 GB, 7.3 GB over budget, to 16.6 GB within it. A 990 KB Kotlin file of unclosed nested objects goes from over 50 minutes to 4.3 s, a bug main had too.
Resolved so both sides' behaviour survives: the projection deletes every controlled type in one walk while leaving edges its exemption claims. The Dart callee rule reads through the walk's linear sibling lookup. The binding climb's memo stands aside for a bodyless C tag, whose answer can lie above a settled ancestor, as in struct X; directly under an #ifdef. The source adapter keeps the repairing parse, so it reads the tree extraction read.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A narrowing covers its whole function, so the search for the one covering a receiver only gets long across functions narrowing the same name. The first fixture kept every narrowing in one function and stayed at 1.61 on the old scan, so it could not fail. Across functions the old scan reaches 3.65.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Alamofire's voided files go from 20 of 101 to 0, Vapor's from 49 of 277 to 6, through a same-length Swift parse view. Baseline edges kept on Alamofire: CALLS 1,049 to 3,027 of 4,698, USAGE 188 to 2,804 of 9,020; of the 978 CALLS still lost with nothing like them left, 28 of 32 read were fake. Swift private types are keyed per file, which also fixes a false edge between two files declaring one private name. Dart class headers, constructor defaults, super and exports resolve. PHP adds a project trait beside an outside one, and function return types.
Swift property QNs now carry their class, as Kotlin's do. Swift init, deinit, subscript and enum cases gain nodes, as do Dart mixin, abstract and accessor members. Swift IMPORTS is now a supported relation.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Each test measures a counter at n and 2n and asserts a ratio near 2, so no clock decides it. Each was run against the old scan and failed at a ratio of about 3 to 4. Equivalence tests pin the indexes to the answers of what they replaced: the syntax index to tree-sitter, name rows and the registry tail to a scan, the parse memo to a fresh parse.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A 990 KB Kotlin file of 90,000 unclosed `object A {` lines held an index run for over 50 minutes, and main has the same cost. Declarations left open sit side by side under one error node, so each question about a child walks that node from its start. Extraction has an index for this, but the passes that parse again did not: Kotlin's, C's and C++'s cross passes and Rust's source facts now open one, and the TypeScript, Kotlin and C proofs, compiled apart, now ask through it. Dart's next-sibling test uses the walk's cursor. That file now indexes in about 4 seconds.
The same lines closed are real depth. Each identifier's usage classifiers climbed to the root, and each definition's decorator lookup started from it. A climb that reaches the root undecided now settles its path for everything below it, so the next one stops where it meets that path: a level's answer depends only on the path through it, and a node with any extent lies in one child of its parent. Decorators ask the index for the previous sibling. What is left at great depth is the qualified names themselves, whose stored length grows with depth.
Each path went from about 4x to about 2x work when the input doubled. Answers are unchanged: all 12 graph fingerprints match on every repo measured.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The TypeScript proof scanned a file's imports for each callee and annotation, and every narrowing of a path for each receiver on it. TypeScript's own checker.ts names over a thousand imports in 54,000 lines. Imports now use the file's rows, and a narrowing lookup is one binary search: entries sorted by path and start carry the furthest end so far, and one covers a position exactly when that end passes it.
A Rust module's uses were walked for each local item found, each imported name and each trait a method call asked about. Long lists now answer from rows by local name and by last segment, in list order.
Each of these went from about 4x to about 2x work when the input doubled. Answers are unchanged.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The projection that decides whether results spill was fitted before the strict resolver, at 160 bytes per reference. Resolve now grows by up to 3,250 bytes per reference, so elasticsearch projected 2,534 MB against 26,479 MB real, kept its results in memory, and reached 29,755 MB charged against a budget of 22,488 MB. No phase after extraction can spill.
The refit is 3,400 bytes per reference, fitted to Java, the most expensive language measured. Elasticsearch now spills by itself and peaks at 16,833 MB charged, with no slowdown and all 12 graph fingerprints the same as in memory. Spilling changes when results park, not what is resolved, so this is no work cap. It over-reads other languages, which only costs disk reads: kubernetes projects 18,479 MB for 8,384 MB real, and still keeps results in memory at the full budget.
Nothing updates the constant. A later change that costs more per reference has to refit it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The shared resolver built each file's binding index up to four times, and the preparation passes reparsed every file on one thread. One cached index now serves them, and a parse batch parses ahead on all workers. Python's root facts and the builtins' file list stop walking per file (both 4.00x to about 2x), and the struct field fold joins by class instead of scanning a file's definitions per struct (4.00x to 2.00x). lsp_cross_prepare on django goes from 7.4 s to under 1 s.
C# reference-assembly files keep their deferred pass, which parses them one at a time as before.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The language passes scanned a file's imports for each use and the registry for each definition while it filled, so their work grew with the square of a large file. Rows chained by name now answer those questions in the order a scan found them, and a registry can index what was added since its last index in a scratch arena, with the same first-registered answers. Each path below went from about 4x to about 2x work when a file doubled: imports in TypeScript, JavaScript, Java, Kotlin, PHP, Rust and Go, Python's imports and statements, locals in Rust and the Java, Kotlin and C# walkers, Rust module children and impls, TypeScript methods per type, Java registration and overload patches, Rust derives, Go generics and C# signature patches.
Answers are unchanged: rows hold the same order, and duplicate names still resolve to the first registration where a scan did.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Writing evidence row by row through SQL was the largest single cost of an index: 12.9 s of writes on django, 35.6 s on kubernetes. Rows now go straight to b-tree pages and digests run in parallel, the proof pool keeps a memo of the texts of a session, and hot keys no longer go through snprintf, which takes a process-wide locale lock on macOS. Writes take under 0.4 s on django and under 0.9 s on kubernetes. Databases shrink: kubernetes 4,364 to 4,162 MB, django 932 to 869 MB. Nothing readers use is removed; the same rows are stored.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
ts_node_parent and ts_node_child(n, i) walk from the root or the first child, so any pass over a flat file was quadratic in its top-level items: child steps grew 3.99x when flat.py doubled, 3.98x for flat.ts, 3.94x for flat.php. Passes now read a per-tree index built on first use, which answers exactly what tree-sitter answers, and every step count above grows 2.00x. The index is scoped over the whole of extraction. A worker keeps its parse of a file for the other passes over that file, so a file is no longer parsed once per pass. C#'s file-scoped namespace is looked for once per file instead of once per class, and the doc memo keeps a wide root cached.
No graph content changes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A stranded static function lost its static flag and read as externally linked, so a call could bind across files: fixed with a decoy. Tag uses such as struct cfs_rq *cfs_rq now bind, kernel subtree USAGE 44,251 to 113,857, along with cast macros as receivers, C++ unions as classes, and two-headed function repair. Macro hand-off walks keep a seen set on macro and position: on doubling depth the work counter goes 35 to 47 where it was 155 to 8,225 without one.
Withdrawn as not provable: a rule binding a macro in one configuration branch and a function in another, because the call exists in only one, costing kernel subtree CALLS about 1,041; and a raw define beside a missing header, which that header may undefine, costing neovim about 5,500 edges that look true. Canonical lint passes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Projection deleted every controlled edge the resolver had not recorded, including route registrations, Helm includes, Kubernetes references, SQL lineage and Ansible notifies. A narrow exemption now needs the producer's own via marker and the target's label together; a forged-looking CALLS still goes, by decoy test. Over 47 repositories CALLS into Route go from 4,315 at baseline and 0 before this to 1,146, without client calls and string methods: django 1,206 to 0. Helm includes return to 3,365 and Kubernetes references to 3,936.
One empty .tf or terragrunt.hcl no longer aborts the whole index. One templated Ansible handler no longer voids every notify in the project, nor one unfollowable .env value every $ image in a compose project. Go method-value and ASP.NET method-group handlers no longer bind by name.
For review: a receiver named router or app, with a literal path and a handler, counts as evidence for a Route, which is name-based and an exception to CALLS meaning a declaration, as is a Helm define. A dbt model target in SQL is kept without package namespace proof. The test audits skip the projection table's rows.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
source_damage_scan runs on a resolve worker when a source adapter reads a file with parse damage, and it fills the thread-local bitset cache of cbm_kind_in_set. extract_worker frees that cache at thread exit; resolve_worker_impl did not, so each such worker leaked it. LeakSanitizer on Linux reported 152 bytes in 3 objects from ks_build in resolver_source_langs, once the much larger OCaml cursor leak was gone. The first run's report was cut before it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
rust-std-receiver reported wrong=2 projection_errors=1 because its two constructor rows had no target identity, written when the graph had none for a constructor. The go-rust lane's rule now emits CALLS to the struct, and it is true: a tuple struct's name is its constructor function in the value namespace. `EvidenceTier(1)` and `EvidenceTier(2)` bind the one `pub struct EvidenceTier(pub u8)` in that module, and resolver_rust_tuple_struct_constructor_is_a_call pins the named, unit, value and shadowed cases that must not.
The rows now name the Struct node and are required. Naming it brings real_caller's return type into the audit, so `-> EvidenceTier` is attested as USAGE and USES_TYPE, as the matrix does for class mentions.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
callback-parity failed on recovered_gaps=1. The log's QUALITY_MISS for javascript-script-shadow-control was only the last line before the summary: that row is an unchanged known gap, and the row that recovered is callback-perl-coderef. `perlReferenceAccept(\&perlReferenceTarget)` is a direct coderef argument to a sub of the file, and the Perl adapter now proves it, so the CALL_REFERENCE is true.
With the row required, callback-parity reads genuine=10 found=7 missing=3 recovered_gaps=0 on both routes. The sequential floor assertion also covers this pair now, so the exclusion list goes from six to five.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>