Skip to content

Commit

Permalink
Reapply "[clang analysis][thread-safety] Handle return-by-reference..… (
Browse files Browse the repository at this point in the history
#68394)

…. (#67776)"

Now that `scudo` issues have been fixed (#68273).

This reverts commit 462bdd5.
  • Loading branch information
legrosbuffle authored Oct 6, 2023
1 parent d205624 commit cd184c8
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 28 deletions.
8 changes: 7 additions & 1 deletion clang/include/clang/Analysis/Analyses/ThreadSafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ enum ProtectedOperationKind {
POK_PassByRef,

/// Passing a pt-guarded variable by reference.
POK_PtPassByRef
POK_PtPassByRef,

/// Returning a guarded variable by reference.
POK_ReturnByRef,

/// Returning a pt-guarded variable by reference.
POK_PtReturnByRef,
};

/// This enum distinguishes between different kinds of lock actions. For
Expand Down
10 changes: 9 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3864,7 +3864,7 @@ def warn_fun_requires_negative_cap : Warning<
"calling function %0 requires negative capability '%1'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;

// Thread safety warnings on pass by reference
// Thread safety warnings on pass/return by reference
def warn_guarded_pass_by_reference : Warning<
"passing variable %1 by reference requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
Expand All @@ -3873,6 +3873,14 @@ def warn_pt_guarded_pass_by_reference : Warning<
"passing the value that %1 points to by reference requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyReference>, DefaultIgnore;
def warn_guarded_return_by_reference : Warning<
"returning variable %1 by reference requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyReference>, DefaultIgnore;
def warn_pt_guarded_return_by_reference : Warning<
"returning the value that %1 points to by reference requires holding %0 "
"%select{'%2'|'%2' exclusively}3">,
InGroup<ThreadSafetyReference>, DefaultIgnore;

// Imprecise thread safety warnings
def warn_variable_requires_lock : Warning<
Expand Down
80 changes: 54 additions & 26 deletions clang/lib/Analysis/ThreadSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ class ThreadSafetyAnalyzer {
threadSafety::SExprBuilder SxBuilder;

ThreadSafetyHandler &Handler;
const CXXMethodDecl *CurrentMethod = nullptr;
const FunctionDecl *CurrentFunction;
LocalVariableMap LocalVarMap;
FactManager FactMan;
std::vector<CFGBlockInfo> BlockInfo;
Expand Down Expand Up @@ -1243,10 +1243,10 @@ bool ThreadSafetyAnalyzer::inCurrentScope(const CapabilityExpr &CapE) {

// Members are in scope from methods of the same class.
if (const auto *P = dyn_cast<til::Project>(SExp)) {
if (!CurrentMethod)
if (!isa_and_nonnull<CXXMethodDecl>(CurrentFunction))
return false;
const ValueDecl *VD = P->clangDecl();
return VD->getDeclContext() == CurrentMethod->getDeclContext();
return VD->getDeclContext() == CurrentFunction->getDeclContext();
}

return false;
Expand Down Expand Up @@ -1541,6 +1541,8 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {

ThreadSafetyAnalyzer *Analyzer;
FactSet FSet;
// The fact set for the function on exit.
const FactSet &FunctionExitFSet;
/// Maps constructed objects to `this` placeholder prior to initialization.
llvm::SmallDenseMap<const Expr *, til::LiteralPtr *> ConstructedObjects;
LocalVariableMap::Context LVarCtx;
Expand All @@ -1566,9 +1568,11 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
bool SkipFirstParam = false);

public:
BuildLockset(ThreadSafetyAnalyzer *Anlzr, CFGBlockInfo &Info)
BuildLockset(ThreadSafetyAnalyzer *Anlzr, CFGBlockInfo &Info,
const FactSet &FunctionExitFSet)
: ConstStmtVisitor<BuildLockset>(), Analyzer(Anlzr), FSet(Info.EntrySet),
LVarCtx(Info.EntryContext), CtxIndex(Info.EntryIndex) {}
FunctionExitFSet(FunctionExitFSet), LVarCtx(Info.EntryContext),
CtxIndex(Info.EntryIndex) {}

void VisitUnaryOperator(const UnaryOperator *UO);
void VisitBinaryOperator(const BinaryOperator *BO);
Expand All @@ -1577,6 +1581,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
void VisitCXXConstructExpr(const CXXConstructExpr *Exp);
void VisitDeclStmt(const DeclStmt *S);
void VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *Exp);
void VisitReturnStmt(const ReturnStmt *S);
};

} // namespace
Expand Down Expand Up @@ -1758,6 +1763,8 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
// Pass by reference warnings are under a different flag.
ProtectedOperationKind PtPOK = POK_VarDereference;
if (POK == POK_PassByRef) PtPOK = POK_PtPassByRef;
if (POK == POK_ReturnByRef)
PtPOK = POK_PtReturnByRef;

const ValueDecl *D = getValueDecl(Exp);
if (!D || !D->hasAttrs())
Expand Down Expand Up @@ -2142,6 +2149,25 @@ void BuildLockset::VisitMaterializeTemporaryExpr(
}
}

void BuildLockset::VisitReturnStmt(const ReturnStmt *S) {
if (Analyzer->CurrentFunction == nullptr)
return;
const Expr *RetVal = S->getRetValue();
if (!RetVal)
return;

// If returning by reference, check that the function requires the appropriate
// capabilities.
const QualType ReturnType =
Analyzer->CurrentFunction->getReturnType().getCanonicalType();
if (ReturnType->isLValueReferenceType()) {
Analyzer->checkAccess(
FunctionExitFSet, RetVal,
ReturnType->getPointeeType().isConstQualified() ? AK_Read : AK_Written,
POK_ReturnByRef);
}
}

/// Given two facts merging on a join point, possibly warn and decide whether to
/// keep or replace.
///
Expand Down Expand Up @@ -2251,8 +2277,7 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {

CFG *CFGraph = walker.getGraph();
const NamedDecl *D = walker.getDecl();
const auto *CurrentFunction = dyn_cast<FunctionDecl>(D);
CurrentMethod = dyn_cast<CXXMethodDecl>(D);
CurrentFunction = dyn_cast<FunctionDecl>(D);

if (D->hasAttr<NoThreadSafetyAnalysisAttr>())
return;
Expand All @@ -2278,7 +2303,7 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
PostOrderCFGView::CFGBlockSet VisitedBlocks(CFGraph);

CFGBlockInfo &Initial = BlockInfo[CFGraph->getEntry().getBlockID()];
CFGBlockInfo &Final = BlockInfo[CFGraph->getExit().getBlockID()];
CFGBlockInfo &Final = BlockInfo[CFGraph->getExit().getBlockID()];

// Mark entry block as reachable
Initial.Reachable = true;
Expand Down Expand Up @@ -2348,6 +2373,25 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
}
}

// Compute the expected exit set.
// By default, we expect all locks held on entry to be held on exit.
FactSet ExpectedFunctionExitSet = Initial.EntrySet;

// Adjust the expected exit set by adding or removing locks, as declared
// by *-LOCK_FUNCTION and UNLOCK_FUNCTION. The intersect below will then
// issue the appropriate warning.
// FIXME: the location here is not quite right.
for (const auto &Lock : ExclusiveLocksAcquired)
ExpectedFunctionExitSet.addLock(
FactMan, std::make_unique<LockableFactEntry>(Lock, LK_Exclusive,
D->getLocation()));
for (const auto &Lock : SharedLocksAcquired)
ExpectedFunctionExitSet.addLock(
FactMan,
std::make_unique<LockableFactEntry>(Lock, LK_Shared, D->getLocation()));
for (const auto &Lock : LocksReleased)
ExpectedFunctionExitSet.removeLock(FactMan, Lock);

for (const auto *CurrBlock : *SortedGraph) {
unsigned CurrBlockID = CurrBlock->getBlockID();
CFGBlockInfo *CurrBlockInfo = &BlockInfo[CurrBlockID];
Expand Down Expand Up @@ -2407,7 +2451,7 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
if (!CurrBlockInfo->Reachable)
continue;

BuildLockset LocksetBuilder(this, *CurrBlockInfo);
BuildLockset LocksetBuilder(this, *CurrBlockInfo, ExpectedFunctionExitSet);

// Visit all the statements in the basic block.
for (const auto &BI : *CurrBlock) {
Expand Down Expand Up @@ -2483,24 +2527,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
if (!Final.Reachable)
return;

// By default, we expect all locks held on entry to be held on exit.
FactSet ExpectedExitSet = Initial.EntrySet;

// Adjust the expected exit set by adding or removing locks, as declared
// by *-LOCK_FUNCTION and UNLOCK_FUNCTION. The intersect below will then
// issue the appropriate warning.
// FIXME: the location here is not quite right.
for (const auto &Lock : ExclusiveLocksAcquired)
ExpectedExitSet.addLock(FactMan, std::make_unique<LockableFactEntry>(
Lock, LK_Exclusive, D->getLocation()));
for (const auto &Lock : SharedLocksAcquired)
ExpectedExitSet.addLock(FactMan, std::make_unique<LockableFactEntry>(
Lock, LK_Shared, D->getLocation()));
for (const auto &Lock : LocksReleased)
ExpectedExitSet.removeLock(FactMan, Lock);

// FIXME: Should we call this function for all blocks which exit the function?
intersectAndWarn(ExpectedExitSet, Final.ExitSet, Final.ExitLoc,
intersectAndWarn(ExpectedFunctionExitSet, Final.ExitSet, Final.ExitLoc,
LEK_LockedAtEndOfFunction, LEK_NotLockedAtEndOfFunction);

Handler.leaveFunction(CurrentFunction);
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1983,6 +1983,12 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
case POK_PtPassByRef:
DiagID = diag::warn_pt_guarded_pass_by_reference;
break;
case POK_ReturnByRef:
DiagID = diag::warn_guarded_return_by_reference;
break;
case POK_PtReturnByRef:
DiagID = diag::warn_pt_guarded_return_by_reference;
break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
<< D
Expand Down Expand Up @@ -2013,6 +2019,12 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
case POK_PtPassByRef:
DiagID = diag::warn_pt_guarded_pass_by_reference;
break;
case POK_ReturnByRef:
DiagID = diag::warn_guarded_return_by_reference;
break;
case POK_PtReturnByRef:
DiagID = diag::warn_pt_guarded_return_by_reference;
break;
}
PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << Kind
<< D
Expand Down
79 changes: 79 additions & 0 deletions clang/test/SemaCXX/warn-thread-safety-analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5580,6 +5580,85 @@ class Bar {
}
};

class Return {
Mutex mu;
Foo foo GUARDED_BY(mu);
Foo* foo_ptr PT_GUARDED_BY(mu);

Foo returns_value_locked() {
MutexLock lock(&mu);
return foo;
}

Foo returns_value_locks_required() EXCLUSIVE_LOCKS_REQUIRED(mu) {
return foo;
}

Foo returns_value_releases_lock_after_return() UNLOCK_FUNCTION(mu) {
MutexLock lock(&mu, true);
return foo;
}

Foo returns_value_aquires_lock() EXCLUSIVE_LOCK_FUNCTION(mu) {
mu.Lock();
return foo;
}

Foo returns_value_not_locked() {
return foo; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
}

Foo returns_value_releases_lock_before_return() UNLOCK_FUNCTION(mu) {
mu.Unlock();
return foo; // expected-warning {{reading variable 'foo' requires holding mutex 'mu'}}
}

Foo &returns_ref_not_locked() {
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu'}}
}

Foo &returns_ref_locked() {
MutexLock lock(&mu);
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu'}}
}

Foo &returns_ref_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu' exclusively}}
}

Foo &returns_ref_exclusive_locks_required() EXCLUSIVE_LOCKS_REQUIRED(mu) {
return foo;
}

Foo &returns_ref_releases_lock_after_return() UNLOCK_FUNCTION(mu) {
MutexLock lock(&mu, true);
return foo; // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu' exclusively}}
}

Foo& returns_ref_releases_lock_before_return() UNLOCK_FUNCTION(mu) {
mu.Unlock();
return foo; // // expected-warning {{returning variable 'foo' by reference requires holding mutex 'mu' exclusively}}
}

Foo &returns_ref_aquires_lock() EXCLUSIVE_LOCK_FUNCTION(mu) {
mu.Lock();
return foo;
}

const Foo &returns_constref_shared_locks_required() SHARED_LOCKS_REQUIRED(mu) {
return foo;
}

Foo *returns_ptr() {
return &foo; // FIXME -- Do we want to warn on this ?
}

Foo &returns_ref2() {
return *foo_ptr; // expected-warning {{returning the value that 'foo_ptr' points to by reference requires holding mutex 'mu' exclusively}}
}

};


} // end namespace PassByRefTest

Expand Down

1 comment on commit cd184c8

@cmtice
Copy link
Contributor

@cmtice cmtice commented on cd184c8 Oct 7, 2023

Choose a reason for hiding this comment

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

This commit is breaking several of our tests. Reverting the commit until this can be fixed (gave internal reproducers directly to the author of this commit).

Please sign in to comment.