Skip to content

Conversation

@ivanmurashko
Copy link
Member

Extends LifetimeCheck to detect use-after-move using CIR's pmap/pset infrastructure. Handles primitive types (int, float, etc.) moved via rvalue reference parameters and lambda captures, plus smart pointers (std::unique_ptr, std::shared_ptr) with their well-defined null state semantics.

Commits

1. Basic detection via rvalue reference parameters (1b6691172cdf)

  • Marks values as moved-from when passed to T&& parameters
  • Example: consume(std::move(x)); int y = x; // warns
  • Handles reinitialization via assignment

2. Lambda init-capture support (3769a2a37cfe)

  • Detects [b = std::move(a)] by accessing Clang AST from CIR
  • Distinguishes move/value/reference captures

3. Test coverage (8 new tests) (94870c91a068)

  • Move-after-move, parameter moves, loops, lambda captures after move
  • Switch fallthrough, declaration moves, multiple uses
  • Total: 22 tests covering all scenarios

4. Edge case fixes (b69b7e7f29f4)

  • Only warn once per variable (per-value tracking)
  • Detect move-after-move (warn on second move)
  • Detect int b(std::move(a)) via rvalue initialization

5. Smart pointer support (eca591755916)

  • Detects std::unique_ptr and std::shared_ptr by qualified name
  • Marks smart pointers as null (not invalid) after move - reflects well-defined state
  • Distinguishes safe operations (get(), reset(), bool) from unsafe (*, ->)
  • Adds checkMoveCtor() to handle move constructor semantics
  • Adds markOwnerAsMovedFrom() helper to unify move handling logic
  • New test file: lifetime-check-smart-pointer-after-move.cpp with 13 tests

Implementation

Uses InvalidStyle::MovedFrom in pmap to track state. AST access via CIR's AstAttr for lambda analysis and smart pointer type detection.

Smart pointers use State::getNullPtr() instead of State::getInvalid() to reflect their well-defined null state after move.

Limitations

Smart pointer reinitialization via reset(new T) is not yet tracked as a state change (future work).

Testing

  • Primitives and lambdas: 22 tests in clang/test/CIR/Transforms/lifetime-check-use-after-move.cpp
  • Smart pointers: 13 tests in clang/test/CIR/Transforms/lifetime-check-smart-pointer-after-move.cpp

…eters

Extends the CIR LifetimeCheck pass to detect use-after-move for all types
including primitives (int, double, etc.) when passed to functions via
rvalue reference parameters.

Example now detected:
  void consume(int&& a);
  int x = 10;
  consume(std::move(x));
  int y = x;  // Warning: use of moved-from value 'x'

Key changes:
- Add checkMoveViaCall() to detect rvalue reference parameters
- Track moved-from state for Value types in existing pmap/pset
- Handle reinitialization via assignment clearing moved-from state
- Extend diagnostics to distinguish moved-from values from pointers

The implementation leverages existing InvalidStyle::MovedFrom and
pmap/pset infrastructure, requiring only ~120 new lines of code.

Test coverage includes primitives, reinitialization, and negative cases
(lvalue references, by-value passing).

Note: Lambda capture support will be added in a follow-up commit.
Extends use-after-move detection to lambda init-captures initialized
with std::move. Since move captures and value captures generate
identical CIR (both are load+store), this implementation accesses the
Clang AST to determine if the capture initializer uses std::move.

Example now detected:
  int x = 10;
  auto lambda = [y = std::move(x)]() { };
  int z = x;  // Warning: use of moved-from value 'x'

Key changes:
- Extend checkLambdaCaptureStore() to access lambda AST
- Add checkLambdaInitCaptureMove() helper to detect std::move
- Detect init-captures via VarDecl::isInitCapture()
- Identify std::move in initializer via CallExpr analysis
- Mark source variable as moved-from when capture uses std::move
- Match captures by field name to avoid false positives

The implementation uses AST inspection because CIR lowering does not
preserve the distinction between move and copy captures.

Test coverage includes init-captures with/without move, reference
captures, value captures, and multiple capture scenarios.
Add 8 new test cases to provide parity with clang-tidy's
bugprone-use-after-move checker tests:
- Move-after-move detection
- Function parameter moves
- Loops with conditional moves
- Lambda captures after move (explicit and implicit)
- Switch statement fallthrough
- Move in declaration
- Multiple uses after move (only first warned)

No implementation changes needed - existing code already handles
these scenarios correctly.
Fix three issues discovered by expanded test coverage:

1. Only warn once per moved-from variable (not once per use location)
2. Detect move-after-move (using an already-moved value)
3. Detect moves in variable declarations: int b(std::move(a))

These fixes ensure use-after-move detection works correctly for
all primitive types across different usage patterns.
…ection

Smart pointers (std::unique_ptr, std::shared_ptr) have well-defined null
state after move, unlike other owner types which become invalid. This patch:

- Adds isSmartPointerType() to detect std::unique_ptr/std::shared_ptr
- Marks smart pointers as null (not invalid) after move operations
- Distinguishes safe operations (get, reset, bool) from unsafe (*, ->)
- Adds checkMoveCtor() to handle move constructor semantics
- Adds markOwnerAsMovedFrom() helper to unify move handling

New test file with 13 tests covering move constructor, move assignment,
safe/unsafe operations, and function parameter moves.
@bcardosolopes
Copy link
Member

@ivanmurashko this is a great addition of functionality - I'll go for a deeper review soon. In the meantime, seems like a few tests are failing!

@ivanmurashko
Copy link
Member Author

@ivanmurashko this is a great addition of functionality - I'll go for a deeper review soon. In the meantime, seems like a few tests are failing!

Cool, I will look at the failed tests

Temporaries (e.g., ref.tmp0) being moved into variables via move
constructors or rvalue references don't need to be tracked as moved-from
since they are about to be destroyed anyway. Tracking them leads to false
positives where the moved-from state pollutes the target variable's pset.

This fix adds an isTemporary() check that skips marking temporaries as
moved-from in:
- Move constructors (checkMoveCtor)
- Rvalue reference parameters (checkMoveViaCall)

Coroutine tasks are explicitly excluded from this optimization via
isTaskType() since they need lifetime tracking even as temporaries.

Additionally, fixes a critical issue where coroutine task tracking was
skipped due to early return in checkStore(). Moved task checking before
the Value type reinitialization check to ensure tasks are properly tracked.

Fixes 2 test regressions:
- lifetime-this.cpp (smart pointer false positive)
- lifetime-check-coro-task.cpp (coroutine task tracking)
@ivanmurashko
Copy link
Member Author

@ivanmurashko this is a great addition of functionality - I'll go for a deeper review soon. In the meantime, seems like a few tests are failing!

@bcardosolopes I fixed the failed tests at 9cc5da2

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

Overall this is great, thank you!
Added comments, questions and some requests!

…dback

Address code review feedback for use-after-move feature:

- Add AST interface methods (getNumParams, isParamRValueReference,
  isSmartPointerOwner) to avoid direct AST access in LifetimeCheck
- Refactor isSmartPointerType() to use interface method with caching
- Extract helper methods (isSmartPointerSafeMethod, checkMovedFromValue,
  checkArgForRValueRef) to improve code organization
- Rename isTemporary() to shouldSkipMoveFromTemporary() with TODO about
  ref.tmp naming reliability
- Rename checkMoveViaCall() to checkMoveInCallArgs() for clarity
- Unify diagnostic suppression: remove value-based warnedMovedFromValues,
  use location-based emittedDiagnostics consistently for all diagnostics
- Remove smart pointer special case in markOwnerAsMovedFrom(): treat all
  owner types uniformly as invalid after move, handle safe operations
  separately in isSmartPointerSafeMethod()
- Document CIR_CXXSpecialMemberAttr system: add comments explaining how
  move constructors and assignments are marked via CIR attributes
- Update tests to match new behavior (warn at every use location, unified
  invalidation notes)
…dback

Address remaining reviewer comments from PR llvm#2077:

- Fix diagnostic clearing: Add emittedDiagnostics.clear() in checkFunc()
  to prevent diagnostic suppression across function boundaries
- Remove lambda init-capture move detection (deferred to separate PR)
- Remove reinitialization support (deferred to separate PR)
- Rename shouldSkipMoveFromTemporary to movesFromTemporary for clarity
- Adjust test cases to match simplified implementation

Tests:
- lifetime-check-use-after-move.cpp
- lifetime-check-smart-pointer-after-move.cpp
…sTaskType

Move AST access to flow through the AST interface with proper null-checking:
- isLambdaType: Check astAttr before calling isLambda()
- isTaskType: Check astAttr before calling hasPromiseType()

This follows the same pattern as other methods (e.g., isSmartPointerType)
and ensures consistent AST interface usage throughout the pass.
@ivanmurashko
Copy link
Member Author

@bcardosolopes, just addressed the comments and refactored the code. Could you look at the result?

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

Thanks for all the fixes and for your patience. This is looking super good, one more round of review!

- Add documentation for isSmartPointerOwner() explaining narrow scope
- Rename movesFromTemporary() to isSkippableTemporary() for clarity
- Add documentation for isSmartPointerSafeMethod()
- Add documentation for checkMoveInCallArgs()
- Change TODO to FIXME for temporary detection
@github-actions
Copy link

github-actions bot commented Jan 17, 2026

✅ With the latest revision this PR passed the C/C++ code formatter.

@ivanmurashko
Copy link
Member Author

Thanks for all the fixes and for your patience. This is looking super good, one more round of review!

@bcardosolopes, thank you for the thorough review! I've addressed all the feedback you provided in the commit d9e5628.

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

Another round. Looks like many comments haven't been addressed, but looks good for the smart pointer comments.

- Add C++ standard references for smart pointer safe/unsafe methods
- Add helper methods: isValueType, hasInvalidState, isValueTypeMovedFrom,
  and markPointerOrValueTypeAsMovedFrom to eliminate code duplication
- Add documentation for checkMovedFromValue method
- Refactor duplicate moved-from checks to use semantic helper methods
- Apply early-exit control flow pattern consistently
- Add explanatory comments for params/args difference and LoadOp handling
- Update coroutine task temporary comment to mention frame capture
Copy link
Member Author

@ivanmurashko ivanmurashko left a comment

Choose a reason for hiding this comment

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

@bcardosolopes sorry, I missed a lot of comments last time. I hope I addressed all of them this turn

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

Awesome, LGTM pending a remaining comment

@bcardosolopes bcardosolopes merged commit f9a229f into llvm:main Jan 29, 2026
6 checks passed
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.

2 participants