Skip to content

Builtin-name list is checked by every language, so Go/Rust/Ruby/PHP/C/C++ lose real call edges — but it also hides wrong ones (follows #2296) #2313

Open

Description

@PathGao

When graphify links a call to its definition, it skips any callee whose name is in _LANGUAGE_BUILTIN_GLOBALS. That list mixes builtins from Python, JS, Swift and others, and every language checks it. So a Go method named set gets no incoming calls, because set is a Python builtin.

The obvious fix is to give each language its own list. Doing only that would make things worse: on a real Go repo, most of what the list blocks today are wrong edges that should stay blocked. Details below.

Example

From 3x-ui. One file, one struct, two methods:

// internal/web/job/node_traffic_sync_job.go
type atomicBool struct { mu sync.Mutex; v bool }

func (a *atomicBool) set()          { ... }   // L63
func (a *atomicBool) takeAndReset() { ... }   // L71

j.structural.set()            // L147
j.structural.takeAndReset()   // L234
j.structural.set()            // L379
(*atomicBool).set            incoming calls = 0   ← called twice
(*atomicBool).takeAndReset   incoming calls = 1   ← correct

The two methods are on the same struct, in the same file, and are called the same way. The only difference is that set is on the list.

Where the check runs

graphify/extractors/go.py:395       (Go builtins are checked first since #2296)
graphify/extractors/rust.py:372
graphify/extractors/elixir.py:203
graphify/extractors/engine.py:4158  Ruby, C#, Java, Kotlin, PHP, Python, JS/TS, Swift, C, C++
graphify/extract.py:5305            shared cross-file pass, all languages

At each extraction site the check blocks both the in-file match and the hand-off to cross-file resolution. At extract.py:5305 it runs before import evidence is looked at, so a name that is explicitly imported is still dropped.

How widespread

I made a two-file repo for each language: file a defines a name and file b calls it from another file. I tested 29 common names, plus a made-up control name zqxwidget.

language control resolves names blocked (of 29)
Python yes 0
JS, TS, Rust, Ruby, C, C++ yes 29
Go yes 27
PHP yes 28
Elixir no, even the control fails not measured

Changing one letter makes each name resolve (len → lenx, Error → Errorz), so the list is the cause. Python is not affected because it resolves names earlier through symbol_resolution, even though Python builtins are the biggest part of the list.

Why splitting the list alone is wrong

On a 466-file Go repo, turning the list off adds 45 edges:

target edges definitions in repo real?
Error 31 6 no
Int 5 1 (stub, no source file) no
Map 3 1 (stub, no source file) no
String 3 1 (but fmt.Stringer is everywhere) doubtful
set 3 2 2 yes (the example above)

Six types in that repo define Error(). Without type information graphify can't tell which one err.Error() calls. If those 31 calls were linked by name, one node would collect calls that belong to six types. That is the problem #2296 removed. Today Error is blocked only because JS has a global Error. The reason is wrong but the result is right. Split the list per language and those 31 wrong edges come back in every Go repo.

The TS/JS side of the same repo has 512 blocked edges. Almost all of them are names with several definitions (set 260 edges across 6 definitions, map 158 across 3, filter 39 across 2). Every single-definition case I checked (next, range, iter) was inside a minified vendor bundle.

Suggested order

  1. When linking a bare name across files, refuse if the repo has more than one definition with that name. This applies whether or not the name is on any list. disambiguate_ambiguous_candidates does something close for the ambiguous case, and this would be the default.
  2. Then split the list per language. Error stays blocked for the right reason and set comes back.
  3. Where possible, decide by the shape of the call instead of by name lists. fix(go): filter Go predeclared functions from call-target resolution #2296 checks whether the callee is a bare identifier, which is safe at any stage.

Not checked

  • Real-repo numbers exist only for Go and TS/JS. Rust, Ruby, PHP, C, C++, C# and Java were confirmed only on the synthetic repos.
  • Elixir was not measured, because the control name doesn't resolve there either.
  • In-file matches were spot-checked only for Go and Rust.
  • I only checked Go for the opposite case: a language whose builtins are on no list and end up getting wrongly linked.

Measured on upstream/v8 at ecfcd16 plus the #2296 head.

Activity

  1. safishamsi commented on Jul 30, 2026

    @safishamsi
    Member

    The Go-specific symptom is addressed in v0.9.31 (#2296): predeclared-function filtering is now scoped to Go bare-identifier callees + a Go raw_calls gate, so it no longer touches the shared behavior. The broader design point you raise here — that _LANGUAGE_BUILTIN_GLOBALS is applied cross-language at extraction time and doubles as an accidental multi-definition god-node guard — remains open. Your proposed order (multi-definition god-node guard first, then per-language split, then syntactic-shape evidence over name lists) is the right plan; leaving this open to track it. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.31

  2. iakshkhurana commented on Oct 1, 2026

    @iakshkhurana

    Hi @safishamsi, I'd like to work on this, following the order you approved:

    1. Multi-definition god-node guard first. Make the "accidental" protection explicit so removing names from the shared list doesn't create god nodes.
    2. Per-language split. Scope builtin names to their own language so Python/Swift/JS names stop suppressing real Go/Rust/Ruby/PHP/C/C++ edges.
    3. Syntactic-shape evidence over name lists, where it's practical.
      I'd send step 1 as its own PR with tests, then step 2 as a follow-up, so each one is easy to review. OK to go ahead?
  3. changed the title [-]_LANGUAGE_BUILTIN_GLOBALS is applied at extraction time in every language: Python/Swift/JS builtin names suppress genuine Go/Rust/Ruby/PHP/C/C++ edges — but it is load-bearing as an accidental god-node guard (follows #2296)[/-] [+]Builtin-name list is checked by every language, so Go/Rust/Ruby/PHP/C/C++ lose real call edges — but it also hides wrong ones (follows #2296)[/+] on Oct 3, 2026
  4. PathGao commented on Oct 3, 2026

    @PathGao
    ContributorAuthor

    @iakshkhurana I rewrote the issue body in plainer language, so it should be easier to follow now. The numbers and the suggested order are unchanged.

  5. iakshkhurana commented on Oct 3, 2026

    @iakshkhurana

    @iakshkhurana I rewrote the issue body in plainer language, so it should be easier to follow now. The numbers and the suggested order are unchanged.

    Can you assign me please?

  6. PathGao commented on Oct 3, 2026

    @PathGao
    ContributorAuthor

    @iakshkhurana I rewrote the issue body in plainer language, so it should be easier to follow now. The numbers and the suggested order are unchanged.

    Can you assign me please?

    I am not a part of team so I cant assgin anybody

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions