Skip to content

Commit b6fd3cf

Browse files
committed
Thread Safety Analysis: Very basic capability alias-analysis
Add a simple form of alias analysis for capabilities by substituting local pointer variables with their initializers if they are `const` or never reassigned. For example, the analysis will no longer generate false positives for cases such as: void testNestedAccess(Container *c) { Foo *ptr = &c->foo; ptr->mu.Lock(); c->foo.data = 42; // OK - no false positive ptr->mu.Unlock(); } void testNestedAcquire(Container *c) EXCLUSIVE_LOCK_FUNCTION(&c->foo.mu) { Foo *buf = &c->foo; buf->mu.Lock(); // OK - no false positive warning } This implementation would satisfy the basic needs of addressing the concerns for Linux kernel application [1]. Current limitations: * The analysis does not handle pointers that are reassigned; it conservatively assumes they could point to anything after the reassignment. * Aliases created through complex control flow are not tracked. Link: https://lore.kernel.org/all/CANpmjNPquO=W1JAh1FNQb8pMQjgeZAKCPQUAd7qUg=5pjJ6x=Q@mail.gmail.com/ [1]
1 parent 497d177 commit b6fd3cf

File tree

5 files changed

+310
-22
lines changed

5 files changed

+310
-22
lines changed

clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
#include "clang/Analysis/CFG.h"
3232
#include "clang/Basic/LLVM.h"
3333
#include "llvm/ADT/DenseMap.h"
34+
#include "llvm/ADT/DenseSet.h"
3435
#include "llvm/ADT/PointerIntPair.h"
3536
#include "llvm/ADT/PointerUnion.h"
3637
#include "llvm/ADT/SmallVector.h"
3738
#include "llvm/Support/Casting.h"
39+
#include <optional>
3840
#include <sstream>
3941
#include <string>
4042
#include <utility>
@@ -386,6 +388,11 @@ class SExprBuilder {
386388
SelfVar->setKind(til::Variable::VK_SFun);
387389
}
388390

391+
// Create placeholder for this: we don't know the VarDecl on construction yet.
392+
til::LiteralPtr *createThisPlaceholder() {
393+
return new (Arena) til::LiteralPtr(nullptr);
394+
}
395+
389396
// Translate a clang expression in an attribute to a til::SExpr.
390397
// Constructs the context from D, DeclExp, and SelfDecl.
391398
CapabilityExpr translateAttrExpr(const Expr *AttrExp, const NamedDecl *D,
@@ -394,8 +401,8 @@ class SExprBuilder {
394401

395402
CapabilityExpr translateAttrExpr(const Expr *AttrExp, CallingContext *Ctx);
396403

397-
// Translate a variable reference.
398-
til::LiteralPtr *createVariable(const VarDecl *VD);
404+
// Translate a VarDecl to its canonical TIL expression.
405+
til::SExpr *translateVariable(const VarDecl *VD, CallingContext *Ctx);
399406

400407
// Translate a clang statement or expression to a TIL expression.
401408
// Also performs substitution of variables; Ctx provides the context.
@@ -445,6 +452,7 @@ class SExprBuilder {
445452
const AbstractConditionalOperator *C, CallingContext *Ctx);
446453

447454
til::SExpr *translateDeclStmt(const DeclStmt *S, CallingContext *Ctx);
455+
til::SExpr *translateStmtExpr(const StmtExpr *SE, CallingContext *Ctx);
448456

449457
// Map from statements in the clang CFG to SExprs in the til::SCFG.
450458
using StatementMap = llvm::DenseMap<const Stmt *, til::SExpr *>;
@@ -501,6 +509,9 @@ class SExprBuilder {
501509
void mergeEntryMapBackEdge();
502510
void mergePhiNodesBackEdge(const CFGBlock *Blk);
503511

512+
// Returns true if a variable is assumed to be reassigned.
513+
bool isVariableReassigned(const VarDecl *VD);
514+
504515
private:
505516
// Set to true when parsing capability expressions, which get translated
506517
// inaccurately in order to hack around smart pointers etc.
@@ -531,6 +542,9 @@ class SExprBuilder {
531542
std::vector<til::Phi *> IncompleteArgs;
532543
til::BasicBlock *CurrentBB = nullptr;
533544
BlockInfo *CurrentBlockInfo = nullptr;
545+
546+
// Set caching local variables that are reassigned.
547+
std::optional<llvm::DenseSet<const VarDecl *>> ReassignedLocalVariables;
534548
};
535549

536550
#ifndef NDEBUG

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,11 +1141,10 @@ class ThreadSafetyAnalyzer {
11411141

11421142
void warnIfMutexNotHeld(const FactSet &FSet, const NamedDecl *D,
11431143
const Expr *Exp, AccessKind AK, Expr *MutexExp,
1144-
ProtectedOperationKind POK, til::LiteralPtr *Self,
1144+
ProtectedOperationKind POK, til::SExpr *Self,
11451145
SourceLocation Loc);
11461146
void warnIfMutexHeld(const FactSet &FSet, const NamedDecl *D, const Expr *Exp,
1147-
Expr *MutexExp, til::LiteralPtr *Self,
1148-
SourceLocation Loc);
1147+
Expr *MutexExp, til::SExpr *Self, SourceLocation Loc);
11491148

11501149
void checkAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK,
11511150
ProtectedOperationKind POK);
@@ -1599,7 +1598,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
15991598
}
16001599

16011600
void handleCall(const Expr *Exp, const NamedDecl *D,
1602-
til::LiteralPtr *Self = nullptr,
1601+
til::SExpr *Self = nullptr,
16031602
SourceLocation Loc = SourceLocation());
16041603
void examineArguments(const FunctionDecl *FD,
16051604
CallExpr::const_arg_iterator ArgBegin,
@@ -1629,7 +1628,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
16291628
/// of at least the passed in AccessKind.
16301629
void ThreadSafetyAnalyzer::warnIfMutexNotHeld(
16311630
const FactSet &FSet, const NamedDecl *D, const Expr *Exp, AccessKind AK,
1632-
Expr *MutexExp, ProtectedOperationKind POK, til::LiteralPtr *Self,
1631+
Expr *MutexExp, ProtectedOperationKind POK, til::SExpr *Self,
16331632
SourceLocation Loc) {
16341633
LockKind LK = getLockKindFromAccessKind(AK);
16351634
CapabilityExpr Cp = SxBuilder.translateAttrExpr(MutexExp, D, Exp, Self);
@@ -1688,8 +1687,7 @@ void ThreadSafetyAnalyzer::warnIfMutexNotHeld(
16881687
/// Warn if the LSet contains the given lock.
16891688
void ThreadSafetyAnalyzer::warnIfMutexHeld(const FactSet &FSet,
16901689
const NamedDecl *D, const Expr *Exp,
1691-
Expr *MutexExp,
1692-
til::LiteralPtr *Self,
1690+
Expr *MutexExp, til::SExpr *Self,
16931691
SourceLocation Loc) {
16941692
CapabilityExpr Cp = SxBuilder.translateAttrExpr(MutexExp, D, Exp, Self);
16951693
if (Cp.isInvalid()) {
@@ -1857,7 +1855,7 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
18571855
/// of an implicitly called cleanup function.
18581856
/// \param Loc If \p Exp = nullptr, the location.
18591857
void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
1860-
til::LiteralPtr *Self, SourceLocation Loc) {
1858+
til::SExpr *Self, SourceLocation Loc) {
18611859
CapExprSet ExclusiveLocksToAdd, SharedLocksToAdd;
18621860
CapExprSet ExclusiveLocksToRemove, SharedLocksToRemove, GenericLocksToRemove;
18631861
CapExprSet ScopedReqsAndExcludes;
@@ -1869,7 +1867,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
18691867
const auto *TagT = Exp->getType()->getAs<TagType>();
18701868
if (D->hasAttrs() && TagT && Exp->isPRValue()) {
18711869
til::LiteralPtr *Placeholder =
1872-
Analyzer->SxBuilder.createVariable(nullptr);
1870+
Analyzer->SxBuilder.createThisPlaceholder();
18731871
[[maybe_unused]] auto inserted =
18741872
Analyzer->ConstructedObjects.insert({Exp, Placeholder});
18751873
assert(inserted.second && "Are we visiting the same expression again?");
@@ -2545,7 +2543,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
25452543
}
25462544
if (UnderlyingLocks.empty())
25472545
continue;
2548-
CapabilityExpr Cp(SxBuilder.createVariable(Param), StringRef(),
2546+
CapabilityExpr Cp(SxBuilder.translateVariable(Param, nullptr),
2547+
StringRef(),
25492548
/*Neg=*/false, /*Reentrant=*/false);
25502549
auto ScopedEntry = std::make_unique<ScopedLockableFactEntry>(
25512550
Cp, Param->getLocation(), FactEntry::Declared);
@@ -2662,17 +2661,19 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
26622661
if (!DD->hasAttrs())
26632662
break;
26642663

2665-
LocksetBuilder.handleCall(nullptr, DD,
2666-
SxBuilder.createVariable(AD.getVarDecl()),
2667-
AD.getTriggerStmt()->getEndLoc());
2664+
LocksetBuilder.handleCall(
2665+
nullptr, DD,
2666+
SxBuilder.translateVariable(AD.getVarDecl(), nullptr),
2667+
AD.getTriggerStmt()->getEndLoc());
26682668
break;
26692669
}
26702670

26712671
case CFGElement::CleanupFunction: {
26722672
const CFGCleanupFunction &CF = BI.castAs<CFGCleanupFunction>();
2673-
LocksetBuilder.handleCall(/*Exp=*/nullptr, CF.getFunctionDecl(),
2674-
SxBuilder.createVariable(CF.getVarDecl()),
2675-
CF.getVarDecl()->getLocation());
2673+
LocksetBuilder.handleCall(
2674+
/*Exp=*/nullptr, CF.getFunctionDecl(),
2675+
SxBuilder.translateVariable(CF.getVarDecl(), nullptr),
2676+
CF.getVarDecl()->getLocation());
26762677
break;
26772678
}
26782679

clang/lib/Analysis/ThreadSafetyCommon.cpp

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "clang/AST/Expr.h"
2020
#include "clang/AST/ExprCXX.h"
2121
#include "clang/AST/OperationKinds.h"
22+
#include "clang/AST/RecursiveASTVisitor.h"
2223
#include "clang/AST/Stmt.h"
2324
#include "clang/AST/Type.h"
2425
#include "clang/Analysis/Analyses/ThreadSafetyTIL.h"
@@ -241,7 +242,17 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,
241242
return CapabilityExpr(E, AttrExp->getType(), Neg);
242243
}
243244

244-
til::LiteralPtr *SExprBuilder::createVariable(const VarDecl *VD) {
245+
til::SExpr *SExprBuilder::translateVariable(const VarDecl *VD,
246+
CallingContext *Ctx) {
247+
assert(VD);
248+
249+
QualType Ty = VD->getType();
250+
if (Ty->isPointerType() && VD->hasInit() && !isVariableReassigned(VD)) {
251+
// Substitute local pointer variables with their initializers if they are
252+
// explicitly const or never reassigned.
253+
return translate(VD->getInit(), Ctx);
254+
}
255+
245256
return new (Arena) til::LiteralPtr(VD);
246257
}
247258

@@ -313,6 +324,8 @@ til::SExpr *SExprBuilder::translate(const Stmt *S, CallingContext *Ctx) {
313324

314325
case Stmt::DeclStmtClass:
315326
return translateDeclStmt(cast<DeclStmt>(S), Ctx);
327+
case Stmt::StmtExprClass:
328+
return translateStmtExpr(cast<StmtExpr>(S), Ctx);
316329
default:
317330
break;
318331
}
@@ -353,6 +366,9 @@ til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE,
353366
: cast<ObjCMethodDecl>(D)->getCanonicalDecl()->getParamDecl(I);
354367
}
355368

369+
if (const auto *VarD = dyn_cast<VarDecl>(VD))
370+
return translateVariable(VarD, Ctx);
371+
356372
// For non-local variables, treat it as a reference to a named object.
357373
return new (Arena) til::LiteralPtr(VD);
358374
}
@@ -691,6 +707,15 @@ SExprBuilder::translateDeclStmt(const DeclStmt *S, CallingContext *Ctx) {
691707
return nullptr;
692708
}
693709

710+
til::SExpr *SExprBuilder::translateStmtExpr(const StmtExpr *SE,
711+
CallingContext *Ctx) {
712+
// The value of a statement expression is the value of the last statement,
713+
// which must be an expression.
714+
const CompoundStmt *CS = SE->getSubStmt();
715+
return CS->body_empty() ? new (Arena) til::Undefined(SE)
716+
: translate(CS->body_back(), Ctx);
717+
}
718+
694719
// If (E) is non-trivial, then add it to the current basic block, and
695720
// update the statement map so that S refers to E. Returns a new variable
696721
// that refers to E.
@@ -1012,6 +1037,100 @@ void SExprBuilder::exitCFG(const CFGBlock *Last) {
10121037
IncompleteArgs.clear();
10131038
}
10141039

1040+
bool SExprBuilder::isVariableReassigned(const VarDecl *VD) {
1041+
struct ReassignmentFinder : RecursiveASTVisitor<ReassignmentFinder> {
1042+
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
1043+
// Check for self-reference in the initializer. This is not strictly a
1044+
// reassignment, but undefined behaviour.
1045+
if (DRE->getDecl()->getCanonicalDecl() == InVarDecl)
1046+
ReassignedLocalVariables.insert(InVarDecl);
1047+
return true;
1048+
}
1049+
1050+
bool TraverseVarDecl(VarDecl *VD) {
1051+
if (!VD->hasInit() || !VD->isLocalVarDecl())
1052+
return true;
1053+
// Traverse the initializer to check for self-initialization.
1054+
InVarDecl = VD;
1055+
TraverseStmt(VD->getInit());
1056+
InVarDecl = nullptr;
1057+
return true;
1058+
}
1059+
1060+
bool VisitUnaryOperator(UnaryOperator *UO) {
1061+
if (UO->getOpcode() != UO_AddrOf)
1062+
return true;
1063+
// If the address of a variable is taken, it has "escaped", assume it may
1064+
// be reassigned.
1065+
const Expr *SubExpr = UO->getSubExpr()->IgnoreParenImpCasts();
1066+
if (const auto *DRE = dyn_cast<DeclRefExpr>(SubExpr))
1067+
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
1068+
if (VD->isLocalVarDecl())
1069+
ReassignedLocalVariables.insert(VD->getCanonicalDecl());
1070+
return true;
1071+
}
1072+
1073+
bool VisitBinaryOperator(BinaryOperator *BO) {
1074+
if (InVarDecl)
1075+
return true;
1076+
if (!BO->isAssignmentOp())
1077+
return true;
1078+
// If a variable appears as LHS of assignment, then it's a reassignment.
1079+
const auto *LHS = BO->getLHS()->IgnoreParenImpCasts();
1080+
if (const auto *DRE = dyn_cast<DeclRefExpr>(LHS))
1081+
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
1082+
if (VD->isLocalVarDecl())
1083+
ReassignedLocalVariables.insert(VD->getCanonicalDecl());
1084+
return true;
1085+
}
1086+
1087+
bool VisitCallExpr(CallExpr *CE) {
1088+
const FunctionDecl *FD = CE->getDirectCallee();
1089+
if (!FD)
1090+
return true;
1091+
for (unsigned Idx = 0; Idx < CE->getNumArgs(); ++Idx) {
1092+
if (Idx >= FD->getNumParams())
1093+
break;
1094+
const Expr *Arg = CE->getArg(Idx)->IgnoreParenImpCasts();
1095+
const ParmVarDecl *PVD = FD->getParamDecl(Idx);
1096+
QualType ParamType = PVD->getType();
1097+
// Potential reassignment if passed by non-const reference.
1098+
if (ParamType->isReferenceType() &&
1099+
!ParamType->getPointeeType().isConstQualified()) {
1100+
if (const auto *DRE = dyn_cast<DeclRefExpr>(Arg))
1101+
if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
1102+
if (VD->isLocalVarDecl())
1103+
ReassignedLocalVariables.insert(VD->getCanonicalDecl());
1104+
}
1105+
}
1106+
return true;
1107+
}
1108+
1109+
const VarDecl *InVarDecl = nullptr;
1110+
llvm::DenseSet<const VarDecl *> ReassignedLocalVariables;
1111+
};
1112+
1113+
if (VD->getType().isConstQualified())
1114+
return false; // Assume undefined-behavior freedom.
1115+
if (!VD->isLocalVarDecl())
1116+
return true; // Not a local variable (assume reassigned).
1117+
auto *FD = dyn_cast<FunctionDecl>(VD->getDeclContext());
1118+
if (!FD)
1119+
return true; // Assume reassigned.
1120+
1121+
if (LLVM_UNLIKELY(!ReassignedLocalVariables.has_value())) {
1122+
// Lazy init ReassignedLocalVariables set.
1123+
ReassignmentFinder Visitor;
1124+
// const_cast ok: FunctionDecl not modified.
1125+
Visitor.TraverseDecl(const_cast<FunctionDecl *>(FD));
1126+
ReassignedLocalVariables = std::move(Visitor.ReassignedLocalVariables);
1127+
}
1128+
1129+
// Use the canonical declaration to ensure consistent lookup in the cache.
1130+
VD = VD->getCanonicalDecl();
1131+
return ReassignedLocalVariables.value().contains(VD);
1132+
}
1133+
10151134
#ifndef NDEBUG
10161135
namespace {
10171136

clang/test/Sema/warn-thread-safety-analysis.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,13 @@ int main(void) {
184184
/// Cleanup functions
185185
{
186186
struct Mutex* const __attribute__((cleanup(unlock_scope))) scope = &mu1;
187-
mutex_exclusive_lock(scope); // Note that we have to lock through scope, because no alias analysis!
187+
mutex_exclusive_lock(scope); // Lock through scope works.
188188
// Cleanup happens automatically -> no warning.
189189
}
190+
{
191+
struct Mutex* const __attribute__((unused, cleanup(unlock_scope))) scope = &mu1;
192+
mutex_exclusive_lock(&mu1); // With basic alias analysis lock through mu1 also works.
193+
}
190194

191195
foo_.a_value = 0; // expected-warning {{writing variable 'a_value' requires holding mutex 'mu_' exclusively}}
192196
*foo_.a_ptr = 1; // expected-warning {{writing the value pointed to by 'a_ptr' requires holding mutex 'bar.other_mu' exclusively}}

0 commit comments

Comments
 (0)