Skip to content

Fix: Incomplete Call Graph Due to Early Return in makeAllCG Branch#105

Open
RacerZ-fighting wants to merge 2 commits intoantgroup:mainfrom
RacerZ-fighting:main
Open

Fix: Incomplete Call Graph Due to Early Return in makeAllCG Branch#105
RacerZ-fighting wants to merge 2 commits intoantgroup:mainfrom
RacerZ-fighting:main

Conversation

@RacerZ-fighting
Copy link

@RacerZ-fighting RacerZ-fighting commented Feb 2, 2026

Problem

With Config.makeAllCG enabled, when a callee function node already exists in the call graph, executeCall may return early. In that case, the function can appear in the call graph, but its body may never be analyzed, so additional call edges that would be discovered from analyzing its body are missing.

This logic is implemented in the executeCall method, where the analyzer checks for existing nodes to optimize performance. For further details on the potential for missing call edges when nodes are reused, see #104.

if (
callgraphnode.opts?.funcDef?.loc?.start?.line &&
callgraphnode.opts?.funcDef?.loc?.end?.line &&
callgraphnode.opts?.funcDef?.loc?.sourcefile === fclos.fdef?.loc?.sourcefile &&
callgraphnode.opts?.funcDef?.loc?.start?.line === fclos.fdef?.loc?.start?.line &&
callgraphnode.opts?.funcDef?.loc?.end?.line === fclos.fdef?.loc?.end?.line
) {
this.checkerManager.checkAtFunctionCallBefore(this, scope, node, state, {
argvalues,
fclos,
pcond: state.pcond,
entry_fclos: this.entry_fclos,
einfo: state.einfo,
state,
analyzer: this,
ainfo: this.ainfo,
})
return SymbolValue({
type: 'FunctionCall',
expression: fclos,
arguments: argvalues,
ast: node,
})

Root Cause

In executeCall, the Config.makeAllCG branch adds a call graph edge but then returns early:

// Before fix
if (Config.makeAllCG && /* node exists */) {
  this.checkerManager.checkAtFunctionCallBefore(...)
  return SymbolValue({...})  //Skips function body analysis
}

Solution

Introduced analyzedFunctionBodies Set to track which function bodies have been analyzed:

  • If already analyzed → add edge only, return SymbolValue (avoid redundant analysis)

  • If not analyzed → mark as analyzed, break to continue with executeSingleCall

if (alreadyAnalyzed) {
  return SymbolValue({...})  // Edge added, skip body
} else {
  this.ainfo.analyzedFunctionBodies.add(funcKey)
  break  // Continue to analyze function body
}

Files Changed

  • src/engine/analyzer/common/analyzer.ts

Note

Medium Risk
Touches core executeCall/call graph emission logic, which can change analysis results and performance; risk is mainly around unintended call graph/analysis completeness changes rather than runtime behavior.

Overview
Fixes incomplete call graphs when Config.makeAllCG is enabled by tracking analyzed function bodies (ainfo.analyzedFunctionBodies) and only skipping re-analysis after a function body has been processed once; call edges are still emitted for subsequent calls.

Improves call graph output for unresolved/external calls by filtering noisy internal symbol paths and emitting simplified <external> callee labels derived from the parent chain, instead of pretty-printing opaque internal identifiers.

Written by Cursor Bugbot for commit d8f3a86. This will update automatically on new commits. Configure here.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @RacerZ-fighting, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical issue in the call graph generation logic where enabling Config.makeAllCG could lead to incomplete call graphs. The core problem stemmed from an early return mechanism that prevented the analysis of function bodies if their nodes already existed. The solution introduces a mechanism to track analyzed function bodies, ensuring that all relevant function bodies are processed to build a comprehensive and accurate call graph, thereby enhancing the precision of static analysis.

Highlights

  • Call Graph Accuracy Improvement: The pull request addresses an issue where the call graph could be incomplete when Config.makeAllCG is enabled. Previously, if a function node already existed, its body analysis might be skipped, leading to missing call edges.
  • Function Body Analysis Tracking: A new analyzedFunctionBodies Set has been introduced to track which function bodies have already been processed. This ensures that even if a function is already in the call graph, its body is analyzed if it hasn't been before, preventing incomplete call graphs.
  • Refactored Recursion Check: The recursion check logic within the processCall method has been refactored for improved readability and maintainability by extracting the recursion count calculation into a separate constant.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses a bug that caused an incomplete call graph when Config.makeAllCG is enabled. The introduction of analyzedFunctionBodies to track analyzed functions prevents premature returns and ensures function bodies are processed. The logic is sound and well-implemented. I've included a couple of suggestions to improve efficiency and code clarity.

Comment on lines +2028 to +2030
if (!this.ainfo.analyzedFunctionBodies) {
this.ainfo.analyzedFunctionBodies = new Set()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This check and initialization of analyzedFunctionBodies occurs on every call to executeCall, which can be a hot path. For better performance, it would be more efficient to initialize this.ainfo.analyzedFunctionBodies = new Set() once at the beginning of the analysis, for instance, within the startAnalyze method. This would eliminate this redundant check.

return currentValue.fdef === fdecl ? previousValue + 1 : previousValue
}, 0) > 0
) {
// avoid infinite loops, the re-entry should only less than 3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment is misleading. It states that re-entry should be less than 3, but the logic (recursionCount > 0) prevents any recursion at all. The comment should be updated to accurately reflect the code's behavior to avoid confusion for future maintainers.

Suggested change
// avoid infinite loops, the re-entry should only less than 3
// avoid infinite loops by disallowing recursion

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

})
} else {
this.ainfo.analyzedFunctionBodies.add(funcKey)
break
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double call to checkAtFunctionCallBefore adds duplicate edges

High Severity

When alreadyAnalyzed is false, checkAtFunctionCallBefore is called at line 2043, then the code breaks and falls through to executeSingleCall, which calls checkAtFunctionCallBefore again. This results in the call graph edge being added twice for every function that hasn't been analyzed yet, causing duplicate edges in the call graph.

Additional Locations (1)

Fix in Cursor Fix in Web

}

// Best-effort callee name resolution.
const fclosName = fclos?.name || fclos?.id || fclos?.sid || ''
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fclosName may be object causing startsWith TypeError

Medium Severity

fclosName is assigned via fclos?.name || fclos?.id || fclos?.sid || '' but these properties can be objects, not strings. The prettyPrint method in the same file explicitly handles non-string fclos.id and fclos.sid. If any is an object, fclosName.startsWith('...') in shouldSkipCall throws a TypeError.

Additional Locations (1)

Fix in Cursor Fix in Web

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