Skip to content

fix: resolve Solidity qualified library calls Lib.fn() (AGE-5)#32

Merged
saldestechnology merged 2 commits into
mainfrom
johan/age-5-solidity-qualified-library-calls-libfn-go-unresolved-when
Jul 11, 2026
Merged

fix: resolve Solidity qualified library calls Lib.fn() (AGE-5)#32
saldestechnology merged 2 commits into
mainfrom
johan/age-5-solidity-qualified-library-calls-libfn-go-unresolved-when

Conversation

@saldestechnology

Copy link
Copy Markdown
Collaborator

Stacked on #29 (AGE-4). Base is the AGE-4 branch so the diff shows only the AGE-5 change; retarget to main once #29 merges.

Summary

Solidity qualified calls LibraryName.fn(...) (e.g. ChessPureLib.isKingInCheck(...)) were captured as edges but frequently left unresolved (target_id = NULL) whenever the bare function name existed in ≥2 files (e.g. a Solidity library and a TypeScript mirror). Since fan_in/fan_out/complexity/reachability count only resolved edges, the callee's metrics silently collapsed to 0 — corrupting hotspots ranking, query callers/impact, and any reachability closure. Higher severity than the sibling Solidity issues because it silently corrupts existing metrics rather than omitting a feature.

Root cause (two drop points)

  1. Qualifier discarded at capture — the MemberAccess arm of extract_calls_from_expr kept only member.name and threw away the ChessPureLib object; the edge stored the bare name with context: None.
  2. Ambiguity bail in the resolverresolve_edge_targets's unique-name step only resolves when the bare target_name matches exactly one symbol; ambiguous names were left NULL. No step consulted the qualifier or qualified_name.

Fix

  • Parser (src/parser/solidity.rs): for Name.member(...) calls, store the fully qualified Name.member in the edge's context (kept target_name = bare name for fallback and get_incoming_edges). Complex/chained receivers stay unqualified. Same treatment for block-style calls.
  • Resolver (src/db/schema.rs): new Step 0 resolves an edge whose context equals a symbol's qualified_name exactly (Solidity library methods already carry qualified_name = "ChessPureLib.isKingInCheck"), when unique. Ordered before the existing substring-LIKE step so the exact match wins and later target_id IS NULL steps skip the row — no regression to other languages' resolution (verified: the LIKE step keeps its target_id IS NULL guard).

The optional same-language tiebreak for unqualified ambiguous names was deferred (would risk cross-language behavior changes; the exact-qualified step resolves the reported cases).

Tests

test_solidity_qualified_call_resolves_across_ambiguous_bare_name: two libraries define isKingInCheck (ambiguous, COUNT=2) and a caller does ChessPureLib.isKingInCheck(...); asserts the edge resolves target_id to the correct library method, not NULL and not the other file's symbol.

  • cargo fmt --all -- --check
  • cargo clippy --all-targets -- -D warnings ✅ (0 warnings)
  • cargo test ✅ (304 lib + all integration suites, 0 failures)

Refs AGE-5. Depends on #29 (AGE-4).

Applying a modifier to a function (e.g. `function f() onlyOwner`) produced
no edge in the code graph, so `ctx query callers <modifier>`, `impact`, and
`v1.edges` could not answer access-control questions. Modifier *definitions*
were already emitted as symbols; only the edge was missing.

Add `extract_modifier_edges`, which walks each function's
`FunctionAttribute::BaseOrModifier` attributes and emits a `calls` edge from
the function to the modifier, resolving `target_id` against the modifier
symbol so it participates in fan_in/impact traversal. Also covers constructor
base-contract invocations. Includes a parser test asserting the resolved
edge.

Refs AGE-4
Qualified calls like `ChessPureLib.isKingInCheck(...)` were captured as edges
but left unresolved (target_id NULL) whenever the bare function name existed
in >=2 files: the parser discarded the `ChessPureLib.` qualifier and the
resolver's unique-name step bails when the bare name is ambiguous. Because
fan_in/fan_out/complexity/reachability count only resolved edges, a callee's
metrics silently collapsed to 0 -- corrupting hotspots ranking, callers/impact,
and any reachability closure on Solidity-primary repos.

Capture the qualifier: for `Name.member(...)` the parser now stores the fully
qualified name (e.g. "ChessPureLib.isKingInCheck") in the edge's `context`,
keeping `target_name` as the bare name for fallback. Add a resolver Step 0 that
matches an edge's `context` exactly against a symbol's `qualified_name`
(Solidity library methods already carry it), placed before the substring LIKE
step so the exact target wins and later NULL-only steps skip the row. This
disambiguates the intended library method even when the bare name collides
across files/languages. Same treatment applied to block-style calls.

Adds an index-level test covering an ambiguous bare name resolved via the
qualifier.

Refs AGE-5
@saldestechnology
saldestechnology force-pushed the johan/age-4-solidity-modifiers-are-not-modeled-in-the-graph-no-edges-no branch from e14986a to ac295b2 Compare July 11, 2026 14:19
@saldestechnology
saldestechnology force-pushed the johan/age-5-solidity-qualified-library-calls-libfn-go-unresolved-when branch from a403010 to c10de59 Compare July 11, 2026 14:19
Base automatically changed from johan/age-4-solidity-modifiers-are-not-modeled-in-the-graph-no-edges-no to main July 11, 2026 15:13
@saldestechnology
saldestechnology merged commit 850277b into main Jul 11, 2026
@saldestechnology
saldestechnology deleted the johan/age-5-solidity-qualified-library-calls-libfn-go-unresolved-when branch July 11, 2026 15:14
saldestechnology added a commit that referenced this pull request Jul 11, 2026
…es + edges, providers) (#33)

The AGE-2/3/4/5 PRs (#29-#32) shipped behavior changes without doc
updates. Bring docs/website in line:

- index/embed run in parallel by default; document --serial and demote
  -j/--parallel to a retained no-op (indexing, getting-started,
  code-intelligence help snapshots + synopsis)
- ctx duplicates now fingerprints Solidity via the solang-parser lexer;
  drop the "skipped" caveats and set skipped_languages: []
  (duplicates, code-intelligence, json-output, score)
- Solidity modifier applications emit calls edges and qualified library
  calls Lib.fn() resolve (language-support)
- round out provider docs: add Ollama to the embed providers table and
  offer --provider <local|openai|ollama> in similar/smart (code-intelligence,
  similar, smart)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant