Skip to content

[lint] Fix false positive in needless visibility checker#19481

Merged
vineethk merged 2 commits intomainfrom
vk/fix-needless-visible-lint
Apr 20, 2026
Merged

[lint] Fix false positive in needless visibility checker#19481
vineethk merged 2 commits intomainfrom
vk/fix-needless-visible-lint

Conversation

@vineethk
Copy link
Copy Markdown
Contributor

@vineethk vineethk commented Apr 17, 2026

Description

needless_visibility lint emitted false positives for package/friend functions whose only direct callers were inline functions in the same module — missing that post-inlining those functions are reachable from external modules. This bug was reported by @zzjas.

Fixed by adding get_using_functions_with_transitive_inline(), a dual to an existing function get_used_functions_with_transitive_inline().

How Has This Been Tested?

Added a new test that triggered a false positive before, but does not now.

Type of Change

  • Bug fix

Which Components or Systems Does This Change Impact?

  • Move linter

Note

Medium Risk
Adjusts Move model call-graph caching and linter logic to treat inline functions as expanded at call sites; mistakes could change cross-module usage detection and lint output.

Overview
Fixes a needless_visibility false positive by teaching the Move model to compute “using functions after inline expansion”.

This adds a new cached query FunctionEnv::get_using_functions_with_transitive_inline() (and wiring/caches in FunctionData, module_builder, and cache invalidation) and updates the linter’s has_same_module_users_only check to use it so inline-only direct callers don’t hide external reachability.

Adds a regression test (needless_visibility_lint_fp_fix.move/.exp) covering a package function only called via an inline wrapper that is invoked from another module.

Reviewed by Cursor Bugbot for commit e09d9c2. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown
Contributor Author

vineethk commented Apr 17, 2026

@vineethk vineethk changed the title fix false positive in needless visibility checker [lint] Fix false positive in needless visibility checker Apr 17, 2026
@vineethk vineethk marked this pull request as ready for review April 17, 2026 01:03
@vineethk vineethk requested a review from Copilot April 17, 2026 01:03
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes a false positive in the needless_visibility Move linter by accounting for the post-inlining call graph when determining whether a function is only used within its defining module.

Changes:

  • Add FunctionEnv::get_using_functions_with_transitive_inline() to compute “effective callers” after inline expansion.
  • Update needless_visibility’s shared helper to use the new transitive-inline caller computation.
  • Add a regression test ensuring package visibility isn’t flagged when the only direct caller is an inline function that’s reached cross-module.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
third_party/move/tools/move-linter/tests/model_ast_lints/needless_visibility_lint_fp_fix.move New regression test reproducing the prior false positive scenario.
third_party/move/tools/move-linter/tests/model_ast_lints/needless_visibility_lint_fp_fix.exp Expected output asserting no diagnostics are emitted.
third_party/move/tools/move-linter/src/model_ast_lints/unused_common.rs Switch same-module-user check to use callers-after-inlining.
third_party/move/move-model/src/model.rs Add cached computation of “using functions” with transitive inline expansion.
third_party/move/move-model/src/builder/module_builder.rs Initialize the new per-function cache field in constructed FunctionData.
Comments suppressed due to low confidence (1)

third_party/move/tools/move-linter/src/model_ast_lints/unused_common.rs:69

  • has_same_module_users_only now unconditionally calls get_using_functions_with_transitive_inline(), which will panic if call info isn’t available (it uses expect("call info available")). Previously this function returned false when get_using_functions() returned None. Consider preserving the non-panicking behavior by handling the None case (e.g., returning false/empty set) or by making the transitive-inline helper return Option and propagating it here.
    let using_funs = func.get_using_functions_with_transitive_inline();
    let func_qfid = func.get_qualified_id();
    let func_module_id = func.module_env.get_id();

    let mut has_non_self_user = false;
    for user in using_funs.iter() {
        if *user == func_qfid {
            continue;
        }
        has_non_self_user = true;
        if user.module_id != func_module_id {
            return false;
        }
    }
    has_non_self_user

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread third_party/move/move-model/src/model.rs
Copy link
Copy Markdown
Contributor

@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.

👀

Differential Security Review — Move Linter: Fix false positive in needless visibility checker

PR: #19481 | Codebase size: SMALL | Risk classification: LOW (linter tool, not consensus/execution path)

Context

The PR adds FunctionEnv::get_using_functions_with_transitive_inline() — a reverse BFS that walks the caller graph upward through inline functions to find the "effective" non-inline callers after inlining. The needless_visibility checker's has_same_module_users_only is updated to use this new API.

The change is architecturally sound and the BFS logic is correct for the intended semantics. Cache invalidation in FunctionData::new, the module builder, and set_function_def is complete and symmetric with the existing used_functions_with_transitive_inline field.


Finding 1 — LOW: Panic regression vs. prior graceful degradation

Location: model.rs get_using_functions_with_transitive_inlineunused_common.rs has_same_module_users_only

The old has_same_module_users_only explicitly handled a None result from get_using_functions() by returning false:

let Some(using_funs) = func.get_using_functions() else {
    return false;
};

The new code calls get_using_functions_with_transitive_inline(), which internally calls:

for user in fnc.get_using_functions().expect("call info available") {

get_using_functions() returns None whenever any function in the global environment has used_funs: None (it short-circuits on the first ? during the scan). This occurs for placeholder entries created via FunctionData::new() that never receive a definition — for example, functions in binary-only / dependency stubs loaded without source.

Concrete impact: If the linter is ever invoked on a model where not every function has used_funs populated (e.g. a dependency module in a mixed source+binary build), the needless_visibility checker will panic instead of silently returning false as it did before. No current exploit exists, but this is a silent interface contract change — the old API chose Option specifically to signal "call info not available."

Suggestion (not a required fix): Propagate the Option through get_using_functions_with_transitive_inline rather than using expect, or document and assert the precondition that used_funs is always populated by the time the linter runs.

Open in Web View Automation 

Sent by Cursor Automation: Security Review Bot

Comment thread third_party/move/move-model/src/model.rs
Comment thread third_party/move/move-model/src/model.rs
@vineethk vineethk force-pushed the vk/fix-needless-visible-lint branch from b5ef59b to e09d9c2 Compare April 20, 2026 21:23
@vineethk vineethk enabled auto-merge (squash) April 20, 2026 21:24
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions
Copy link
Copy Markdown
Contributor

✅ Forge suite compat success on ca049383dd80675149ef2d0042668964f9f9107a ==> e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6

Compatibility test results for ca049383dd80675149ef2d0042668964f9f9107a ==> e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6 (PR)
1. Check liveness of validators at old version: ca049383dd80675149ef2d0042668964f9f9107a
compatibility::simple-validator-upgrade::liveness-check : committed: 14400.94 txn/s, latency: 2413.70 ms, (p50: 2400 ms, p70: 2700, p90: 3300 ms, p99: 3900 ms), latency samples: 471500
2. Upgrading first Validator to new version: e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6
compatibility::simple-validator-upgrade::single-validator-upgrade : committed: 6299.04 txn/s, latency: 5356.90 ms, (p50: 5900 ms, p70: 6100, p90: 6200 ms, p99: 6400 ms), latency samples: 217920
3. Upgrading rest of first batch to new version: e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6
compatibility::simple-validator-upgrade::half-validator-upgrade : committed: 6284.88 txn/s, latency: 5382.67 ms, (p50: 5900 ms, p70: 6100, p90: 6200 ms, p99: 6300 ms), latency samples: 215520
4. upgrading second batch to new version: e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6
compatibility::simple-validator-upgrade::rest-validator-upgrade : committed: 11006.55 txn/s, latency: 2951.91 ms, (p50: 3100 ms, p70: 3200, p90: 3400 ms, p99: 4000 ms), latency samples: 360500
5. check swarm health
Compatibility test for ca049383dd80675149ef2d0042668964f9f9107a ==> e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6 passed
Test Ok

@github-actions
Copy link
Copy Markdown
Contributor

✅ Forge suite realistic_env_max_load success on e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6

two traffics test: inner traffic : committed: 14080.01 txn/s, latency: 1300.75 ms, (p50: 1200 ms, p70: 1300, p90: 1500 ms, p99: 1800 ms), latency samples: 5258040
two traffics test : committed: 100.01 txn/s, latency: 669.90 ms, (p50: 600 ms, p70: 700, p90: 800 ms, p99: 900 ms), latency samples: 1660
Latency breakdown for phase 0: ["MempoolToBlockCreation: max: 0.569, avg: 0.485", "ConsensusProposalToOrdered: max: 0.117, avg: 0.113", "ConsensusOrderedToCommit: max: 0.229, avg: 0.217", "ConsensusProposalToCommit: max: 0.346, avg: 0.329"]
Max non-epoch-change gap was: 2 rounds at version 44149 (avg 0.00) [limit 4], 2.09s no progress at version 44149 (avg 0.06s) [limit 15].
Max epoch-change gap was: 0 rounds at version 0 (avg 0.00) [limit 4], 0.48s no progress at version 2590036 (avg 0.48s) [limit 16].
Test Ok

@github-actions
Copy link
Copy Markdown
Contributor

✅ Forge suite framework_upgrade success on ca049383dd80675149ef2d0042668964f9f9107a ==> e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6

Compatibility test results for ca049383dd80675149ef2d0042668964f9f9107a ==> e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6 (PR)
Upgrade the nodes to version: e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 2468.07 txn/s, submitted: 2476.39 txn/s, failed submission: 8.32 txn/s, expired: 8.32 txn/s, latency: 1193.92 ms, (p50: 1200 ms, p70: 1200, p90: 1500 ms, p99: 2400 ms), latency samples: 219602
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 2523.92 txn/s, submitted: 2531.68 txn/s, failed submission: 7.77 txn/s, expired: 7.77 txn/s, latency: 1180.66 ms, (p50: 1200 ms, p70: 1200, p90: 1500 ms, p99: 2400 ms), latency samples: 227461
5. check swarm health
Compatibility test for ca049383dd80675149ef2d0042668964f9f9107a ==> e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6 passed
Upgrade the remaining nodes to version: e09d9c29f19be0cdc9fe607cdb47a7adcfe417c6
framework_upgrade::framework-upgrade::full-framework-upgrade : committed: 1888.52 txn/s, submitted: 1894.69 txn/s, failed submission: 6.17 txn/s, expired: 6.17 txn/s, latency: 1669.40 ms, (p50: 1200 ms, p70: 1500, p90: 2400 ms, p99: 11100 ms), latency samples: 165240
Test Ok

@vineethk vineethk merged commit 83f1f55 into main Apr 20, 2026
92 of 103 checks passed
@vineethk vineethk deleted the vk/fix-needless-visible-lint branch April 20, 2026 22:05
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.

4 participants