Skip to content
This repository was archived by the owner on Feb 21, 2026. It is now read-only.

Commit 2427024

Browse files
committed
[CIR][LifetimeCheck] Integrate temporary check into checkPointerDeref
Refactor use-after-move detection per feedback from PR #2077: - Make checkPointerDeref return bool indicating error status - Integrate isSkippableTemporary check into checkPointerDeref - Add temporary checks to marking functions (markOwnerAsMovedFrom, markPointerOrValueTypeAsMovedFrom) - Simplify call sites in checkArgForRValueRef - Fix emittedDiagnostics guard placement to preserve moved-from tracking - Preserve original behavior: pointer/value types only check Invalid state This reduces duplicated guard patterns and consolidates temporary handling in checkPointerDeref. Includes regression test for multi-arg rvalue-ref scenarios.
1 parent 3c2d87b commit 2427024

2 files changed

Lines changed: 45 additions & 29 deletions

File tree

clang/lib/CIR/Dialect/Transforms/LifetimeCheck.cpp

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ struct LifetimeCheckPass : public LifetimeCheckBase<LifetimeCheckPass> {
7070
CallParam,
7171
IndirectCallParam,
7272
};
73-
void checkPointerDeref(mlir::Value addr, mlir::Location loc,
73+
bool checkPointerDeref(mlir::Value addr, mlir::Location loc,
7474
DerefStyle derefStyle = DerefStyle::Direct);
7575
void checkCoroTaskStore(StoreOp storeOp);
7676
void checkLambdaCaptureStore(StoreOp storeOp);
@@ -1515,8 +1515,12 @@ void LifetimeCheckPass::emitInvalidHistory(mlir::InFlightDiagnostic &D,
15151515
}
15161516
}
15171517

1518-
void LifetimeCheckPass::checkPointerDeref(mlir::Value addr, mlir::Location loc,
1518+
bool LifetimeCheckPass::checkPointerDeref(mlir::Value addr, mlir::Location loc,
15191519
DerefStyle derefStyle) {
1520+
// Skip temporaries early - they're about to be destroyed anyway
1521+
if (isSkippableTemporary(addr))
1522+
return false; // Not an error, just a temporary
1523+
15201524
bool hasInvalid = getPmap()[addr].count(State::getInvalid());
15211525
bool hasNullptr = getPmap()[addr].count(State::getNullPtr());
15221526

@@ -1527,10 +1531,6 @@ void LifetimeCheckPass::checkPointerDeref(mlir::Value addr, mlir::Location loc,
15271531
emitRemark(loc) << "pset => " << Out.str();
15281532
};
15291533

1530-
// Do not emit the same warning twice or more.
1531-
if (emittedDiagnostics.count(loc))
1532-
return;
1533-
15341534
bool psetRemarkEmitted = false;
15351535
if (opts.emitRemarkPsetAlways()) {
15361536
emitPsetRemark();
@@ -1539,15 +1539,20 @@ void LifetimeCheckPass::checkPointerDeref(mlir::Value addr, mlir::Location loc,
15391539

15401540
// 2.4.2 - On every dereference of a Pointer p, enforce that p is valid.
15411541
if (!hasInvalid && !hasNullptr)
1542-
return;
1542+
return false; // No error
15431543

15441544
// TODO: create verbosity/accuracy levels, for now use deref styles directly
15451545
// to decide when not to emit a warning.
15461546

15471547
// For indirect calls, do not relly on blunt nullptr passing, require some
15481548
// invalidation to have happened in a path.
15491549
if (derefStyle == DerefStyle::IndirectCallParam && !hasInvalid)
1550-
return;
1550+
return false; // No error
1551+
1552+
// Do not emit the same warning twice or more (check AFTER we know there's an
1553+
// error)
1554+
if (emittedDiagnostics.count(loc))
1555+
return true; // Already reported
15511556

15521557
// Ok, filtered out questionable warnings, take the bad path leading to this
15531558
// deference point and diagnose it.
@@ -1588,6 +1593,8 @@ void LifetimeCheckPass::checkPointerDeref(mlir::Value addr, mlir::Location loc,
15881593

15891594
if (!psetRemarkEmitted && opts.emitRemarkPsetInvalid())
15901595
emitPsetRemark();
1596+
1597+
return true; // Error was reported
15911598
}
15921599

15931600
static FuncOp getCalleeFromSymbol(ModuleOp mod, llvm::StringRef name) {
@@ -1727,35 +1734,28 @@ void LifetimeCheckPass::checkArgForRValueRef(
17271734

17281735
// Owner types
17291736
if (owners.count(addr)) {
1730-
if (getPmap()[addr].count(State::getInvalid()) ||
1731-
getPmap()[addr].count(State::getNullPtr())) {
1732-
checkPointerDeref(addr, callOp.getLoc());
1733-
return;
1734-
}
1735-
if (!isSkippableTemporary(addr))
1736-
markOwnerAsMovedFrom(addr, callOp.getLoc());
1737+
if (checkPointerDeref(addr, callOp.getLoc()))
1738+
return; // Already reported error
1739+
markOwnerAsMovedFrom(addr, callOp.getLoc());
17371740
return;
17381741
}
17391742

1740-
// Pointer types
1743+
// Pointer types - only check Invalid (not NullPtr)
17411744
if (ptrs.count(addr)) {
17421745
if (getPmap()[addr].count(State::getInvalid())) {
17431746
checkPointerDeref(addr, callOp.getLoc());
17441747
return;
17451748
}
1746-
if (!isSkippableTemporary(addr))
1747-
markPointerOrValueTypeAsMovedFrom(addr, callOp.getLoc());
1749+
markPointerOrValueTypeAsMovedFrom(addr, callOp.getLoc());
17481750
return;
17491751
}
17501752

1751-
// Value types (primitives)
1753+
// Value types - only check Invalid (not NullPtr)
17521754
if (getPmap()[addr].count(State::getInvalid())) {
17531755
checkPointerDeref(addr, callOp.getLoc());
17541756
return;
17551757
}
1756-
1757-
if (!isSkippableTemporary(addr))
1758-
markPointerOrValueTypeAsMovedFrom(addr, callOp.getLoc());
1758+
markPointerOrValueTypeAsMovedFrom(addr, callOp.getLoc());
17591759
}
17601760

17611761
void LifetimeCheckPass::checkCopyAssignment(CallOp callOp,
@@ -1916,6 +1916,10 @@ void LifetimeCheckPass::checkNonConstUseOfOwner(mlir::Value ownerAddr,
19161916

19171917
void LifetimeCheckPass::markOwnerAsMovedFrom(mlir::Value addr,
19181918
mlir::Location loc) {
1919+
// Don't mark temporaries as moved-from - they're about to be destroyed
1920+
if (isSkippableTemporary(addr))
1921+
return;
1922+
19191923
// All owner types are marked invalid after move. Safe operations on specific
19201924
// owner types (e.g., smart pointer get/reset) are handled separately in
19211925
// isSmartPointerSafeMethod.
@@ -1936,6 +1940,10 @@ bool LifetimeCheckPass::isValueTypeMovedFrom(mlir::Value addr) {
19361940

19371941
void LifetimeCheckPass::markPointerOrValueTypeAsMovedFrom(mlir::Value addr,
19381942
mlir::Location loc) {
1943+
// Don't mark temporaries as moved-from
1944+
if (isSkippableTemporary(addr))
1945+
return;
1946+
19391947
markPsetInvalid(addr, InvalidStyle::MovedFrom, loc);
19401948
}
19411949

@@ -1984,18 +1992,14 @@ void LifetimeCheckPass::checkMoveCtor(CallOp callOp, cir::CXXCtorAttr ctor) {
19841992
// Check if this is an Owner type move constructor
19851993
auto addr = getThisParamOwnerCategory(callOp);
19861994
if (addr && owners.count(src)) {
1987-
// Don't mark temporaries as moved-from - they're about to be destroyed
1988-
if (!isSkippableTemporary(src))
1989-
markOwnerAsMovedFrom(src, callOp.getLoc());
1995+
markOwnerAsMovedFrom(src, callOp.getLoc());
19901996
return;
19911997
}
19921998

19931999
// Check if this is a Pointer type move constructor
19942000
addr = getThisParamPointerCategory(callOp);
19952001
if (addr && ptrs.count(src)) {
1996-
// Don't mark temporaries as moved-from
1997-
if (!isSkippableTemporary(src))
1998-
markPointerOrValueTypeAsMovedFrom(src, callOp.getLoc());
2002+
markPointerOrValueTypeAsMovedFrom(src, callOp.getLoc());
19992003
return;
20002004
}
20012005
}
@@ -2183,7 +2187,7 @@ void LifetimeCheckPass::checkCall(CallOp callOp) {
21832187
// invalid access to Ptr if any of its methods are used.
21842188
auto addr = getThisParamPointerCategory(callOp);
21852189
if (addr)
2186-
return checkPointerDeref(addr, callOp.getLoc());
2190+
checkPointerDeref(addr, callOp.getLoc());
21872191
}
21882192

21892193
void LifetimeCheckPass::checkOperation(Operation *op) {

clang/test/CIR/Transforms/lifetime-check-smart-pointer-after-move.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ void test_shared_ptr_arrow_after_move() {
166166

167167
// Test 8: Move via function parameter (unique_ptr)
168168
void consume_unique_ptr(std::unique_ptr<int>&& ptr) {}
169+
void consume_two_unique_ptrs(std::unique_ptr<int>&& ptr1, std::unique_ptr<int>&& ptr2) {}
169170

170171
void test_unique_ptr_move_via_param() {
171172
std::unique_ptr<int> p(new int(42));
@@ -225,3 +226,14 @@ void test_release_after_move() {
225226

226227
int* raw = p.release(); // OK - release() is safe
227228
}
229+
230+
// Test 14: Multiple owner arguments with rvalue references
231+
// Regression test for emittedDiagnostics guard bug
232+
void test_multi_arg_owner_move() {
233+
std::unique_ptr<int> x(new int(1));
234+
std::unique_ptr<int> y(new int(2));
235+
consume_unique_ptr(std::move(x)); // expected-note {{moved here via std::move or rvalue reference}}
236+
consume_two_unique_ptrs(std::move(x), std::move(y)); // expected-warning {{use of invalid pointer 'x'}}
237+
// expected-note@-1 {{moved here via std::move or rvalue reference}}
238+
int use_y = *y; // expected-warning {{use of invalid pointer 'y'}}
239+
}

0 commit comments

Comments
 (0)