-
Notifications
You must be signed in to change notification settings - Fork 192
[CIR][LifetimeCheck] Add use-after-move detection #2077
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[CIR][LifetimeCheck] Add use-after-move detection #2077
Conversation
…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.
|
@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)
@bcardosolopes I fixed the failed tests at 9cc5da2 |
bcardosolopes
left a comment
There was a problem hiding this 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.
|
@bcardosolopes, just addressed the comments and refactored the code. Could you look at the result? |
bcardosolopes
left a comment
There was a problem hiding this 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
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
@bcardosolopes, thank you for the thorough review! I've addressed all the feedback you provided in the commit d9e5628. |
bcardosolopes
left a comment
There was a problem hiding this 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
ivanmurashko
left a comment
There was a problem hiding this 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
bcardosolopes
left a comment
There was a problem hiding this 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
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)
T&¶metersconsume(std::move(x)); int y = x; // warns2. Lambda init-capture support (3769a2a37cfe)
[b = std::move(a)]by accessing Clang AST from CIR3. Test coverage (8 new tests) (94870c91a068)
4. Edge case fixes (b69b7e7f29f4)
int b(std::move(a))via rvalue initialization5. Smart pointer support (eca591755916)
std::unique_ptrandstd::shared_ptrby qualified nameget(),reset(),bool) from unsafe (*,->)checkMoveCtor()to handle move constructor semanticsmarkOwnerAsMovedFrom()helper to unify move handling logiclifetime-check-smart-pointer-after-move.cppwith 13 testsImplementation
Uses
InvalidStyle::MovedFromin pmap to track state. AST access via CIR's AstAttr for lambda analysis and smart pointer type detection.Smart pointers use
State::getNullPtr()instead ofState::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
clang/test/CIR/Transforms/lifetime-check-use-after-move.cppclang/test/CIR/Transforms/lifetime-check-smart-pointer-after-move.cpp