Skip to content

Add build_assert_not_inlined lint#17

Open
mqqz wants to merge 7 commits intoRust-for-Linux:trunkfrom
mqqz:build-assert-not-inlined
Open

Add build_assert_not_inlined lint#17
mqqz wants to merge 7 commits intoRust-for-Linux:trunkfrom
mqqz:build-assert-not-inlined

Conversation

@mqqz
Copy link
Copy Markdown

@mqqz mqqz commented Apr 3, 2026

This PR was split out from #13

Adds a build_assert_not_inlined Lint:

warns when a build_assert! condition depends on non-static values and the surrounding function, or a caller that propagates that dependency, is not marked #[inline(always)].

This is intended for build_assert! uses whose error path must optimize away
e.g. cases where:

  • a local helper return value feeds a build_assert!
  • a caller forwards non-static values into another function whose build_assert! still depends on them
  • a trait/default method carries a runtime-dependent

const-only uses do not trigger the lint.

Implementation Notes

This PR adds a dedicated build_assert_not_inlined analysis/lint in src/build_assert_not_inlined.rs.

The lint:

  • recognizes build_assert! / build_error!
  • recovers direct source-level build_assert! sites for diagnostics
  • computes MIR/instance-based semantic summaries for cross-crate propagation
  • persists those summaries by reusing the existing libsql persistent-query sidecar infrastructure
  • emits diagnostics only for proven runtime-dependent cases

The implementation is not entirely complete for indirect calls; unsupported or unresolved indirect calls are treated as "unknown" rather than lint-worthy by default. This intentionally favors proven positives (too many false positives otherwise), but may miss some cases when indirect call targets cannot be resolved.

To avoid whole-crate MIR cost (too slow when I tried on the kernel), local analysis is seeded from relevant build_assert! uses (in HIR) and propagated only through the necessary local slice.

Other Changes / Refactors

Tests

Two separate test suites were added: normal UI compile-fail tests in tests/ui/build_assert_not_inlined.rs and another test suite to verify "cross-crate" behaviour.

tests/ui/build_assert_not_inlined.rs

UI test coverage includes:

  • direct runtime-dependent assertions
  • parameter + const-generic mixtures
  • local helper return flow
  • boolean predicate helpers
  • caller propagation
  • wrapper macros
  • local rebinding
  • runtime-dependent match
  • trait/default methods
  • partially constant callers
  • recursion
  • negative indirect-call cases

tests/build_assert_cross_crate.rs

cross-crate test coverage includes:

  • verifies upstream summary emission
  • verifies downstream summary loading
  • checks positive cross-crate propagation
  • checks const-only and unknown-indirect negative cases

I also tested it on the Linux kernel tree (Rust-for-Linux/linux@3a2486c), it did not trigger at all but when I manually removed #[inline(always)], it was able to detect it.

Docs

Add documentation in doc/build_assert_not_inlined.md and the corresponding README entry in the “Implemented Lints” section.

Differences from the Older Approach in PR #13:

  • cross-crate support is now a first-class part of the design
  • mono-graph/HIR propagation was scrapped (doesn't work cross-crate).
  • persisted instance summaries for cross-crate propagation
  • the current implementation is MIR/instance-first, restricted to functions reachable from build_assert! calls, and conservative on unknown indirect calls

Limitations

No support for cases like:

if cond {
    build_error!()
}
if !cond { diverge!() }
build_assert!(cond);

yet.

Open Questions

There are a few things I'm not entirely sure on and would appreciate some feedback or further discussion.

  • whether the HIR-seeded canditates is the right performance/correctness tradeoff

The current implementation uses a cheap local HIR prepass to identify functions that are plausibly relevant to build_assert!, and only runs the heavier MIR/instance analysis on that subset. This avoids the whole-crate MIR cost that was intractable on the kernel codebase.

Is this the right approach (does it miss anything?) or whether a different pruning/identification strategy is preferable?

  • whether the "unknown" vs proven-dependency split matches the intended lint semantics

Unsupported or unresolved indirect calls are treated as "unknown" rather than lint-worthy by default. In practice, this means the lint only fires for proven build_assert! dependencies which sharply reduces false positives, but it also means some real cases may be missed when indirect call targets cannot be resolved.

Should we prioritise high-confidence warnings or more conservative over-reporting, or is there an even better approach to deal with indirect calls?

  • whether the Instance-keyed cross-crate summary shape is the right long-term abstraction

Cross-crate summaries are keyed by PseudoCanonicalInput<Instance<'tcx>> rather than plain DefId, because the relevant behavior often depends on the resolved downstream instance, not just the item definition. This gives us the precision needed, especially for generics, but it also makes the abstraction more MIR/monomorphization-oriented.

Is Instance is the right long-term semantic unit, or whether a more abstract item-level summary model would be preferable later?


Apologies, hope this was not too long.

Closes #7

mqqz added 7 commits April 3, 2026 22:15
Recognize `{static,const,build}_assert!` as special items, preferring
explicit `#[klint::diagnostic_item]` annotations and keeping a
conservative kernel-specific fallback for older trees.

The fallback resolves known macro paths structurally, extending the
existing diagnostic-item discovery pattern so later lints can identify
assert macros semantically.

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Introduce the public surface for `build_assert_not_inlined`:

- declare the lint
- register the late lint pass
- add derived diagnostics and the `inline(always)` suggestion helper
- define the basic origin enum used by emitted notes

This commit only adds the diagnostic shell and registration. The pass
is present but does not perform semantic analysis yet.

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Add the cross-crate storage and lookup plumbing for
`build_assert_not_inlined`.

This introduces:
- the semantic summary types used to describe whether a function's
  `build_assert!` behavior depends on runtime inputs
- the `instance_build_assert_summary` persistent query, keyed by
  function instance
- metadata table registration for storing those summaries alongside
  other `klint` query data
- explicit metadata finalization/export hooks so stored summaries are
  written out at the end of analysis

In other words, this commit adds the mechanism for recording and
loading build_assert-related function summaries across crates.

The query is still a stub in this commit. No analysis is performed
yet; that comes in the following commit.

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Implement `build_assert_not_inlined` semantic analysis and lint
emission.

The analysis is MIR/instance-based and supports cross-crate summaries.
It computes requirement summaries, propagates proven build_assert
dependencies through relevant local functions, recovers local
diagnostic origins, and emits the lint for functions that should be
marked `#[inline(always)]`.

This commit turns the previously added lint shell and summary plumbing
into a working end-to-end lint.

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Add local UI coverage for `build_assert_not_inlined`.

The tests cover direct build_assert use, helper propagation, recursive
propagation, trait/default-method shapes, and negative cases such as
unknown indirect calls that should not lint.

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Add a cross-crate regression test for `build_assert_not_inlined`.

The test verifies that:
- summaries are emitted by an upstream crate
- downstream analysis loads those summaries
- proven runtime-dependent cross-crate callers are linted
- constant-only and unknown-indirect cases stay quiet

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Add docs for the new lint including the reasoning behind it, examples
of when it triggers, and the expected `#[inline(always)]` remediation.

Also add corresponding entry in README "Implemented Lints" section.

Signed-off-by: Mohamad Alsadhan <mo@sdhn.cc>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Warn on build_assert calls without #[inline(always)]

1 participant