Skip to content

Commit 7bcd142

Browse files
committed
Thread Safety Analysis: Basic capability alias-analysis
Add basic alias analysis for capabilities by reusing LocalVariableMap, which tracks currently valid definitions of variables. Aliases created through complex control flow are not tracked. This implementation would satisfy the basic needs of addressing the concerns for Linux kernel application [1]. For example, the analysis will no longer generate false positives for cases such as (and many others): 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 } Given the analysis is now able to identify potentially unsafe patterns it was not able to identify previously (see added FIXME test case for an example), mark alias resolution as a "beta" feature behind the flag `-Wthread-safety-beta`. Fixing LocalVariableMap: It was found that LocalVariableMap was not properly tracking loop-invariant aliases: the old implementation failed because the merge logic compared raw VarDefinition IDs. The algorithm for handling back-edges (in createReferenceContext()) generates new 'reference' definitions for loop-scoped variables. Later ID comparison caused alias invalidation at back-edge merges (in intersectBackEdge()) and at subsequent forward-merges with non-loop paths (in intersectContexts()). Fix LocalVariableMap by adding the getCanonicalDefinitionID() helper that resolves any definition ID down to its non-reference base. As a result, a variable's definition is preserved across control-flow merges as long as its underlying canonical definition remains the same. Link: https://lore.kernel.org/all/CANpmjNPquO=W1JAh1FNQb8pMQjgeZAKCPQUAd7qUg=5pjJ6x=Q@mail.gmail.com/ [1]
1 parent bc65352 commit 7bcd142

File tree

5 files changed

+544
-31
lines changed

5 files changed

+544
-31
lines changed

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "llvm/ADT/PointerUnion.h"
3636
#include "llvm/ADT/SmallVector.h"
3737
#include "llvm/Support/Casting.h"
38+
#include <functional>
3839
#include <sstream>
3940
#include <string>
4041
#include <utility>
@@ -386,6 +387,11 @@ class SExprBuilder {
386387
SelfVar->setKind(til::Variable::VK_SFun);
387388
}
388389

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

395401
CapabilityExpr translateAttrExpr(const Expr *AttrExp, CallingContext *Ctx);
396402

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

400406
// Translate a clang statement or expression to a TIL expression.
401407
// Also performs substitution of variables; Ctx provides the context.
@@ -412,6 +418,10 @@ class SExprBuilder {
412418
const til::SCFG *getCFG() const { return Scfg; }
413419
til::SCFG *getCFG() { return Scfg; }
414420

421+
void setLookupLocalVarExpr(std::function<const Expr *(const NamedDecl *)> F) {
422+
LookupLocalVarExpr = std::move(F);
423+
}
424+
415425
private:
416426
// We implement the CFGVisitor API
417427
friend class CFGWalker;
@@ -445,6 +455,7 @@ class SExprBuilder {
445455
const AbstractConditionalOperator *C, CallingContext *Ctx);
446456

447457
til::SExpr *translateDeclStmt(const DeclStmt *S, CallingContext *Ctx);
458+
til::SExpr *translateStmtExpr(const StmtExpr *SE, CallingContext *Ctx);
448459

449460
// Map from statements in the clang CFG to SExprs in the til::SCFG.
450461
using StatementMap = llvm::DenseMap<const Stmt *, til::SExpr *>;
@@ -531,6 +542,11 @@ class SExprBuilder {
531542
std::vector<til::Phi *> IncompleteArgs;
532543
til::BasicBlock *CurrentBB = nullptr;
533544
BlockInfo *CurrentBlockInfo = nullptr;
545+
546+
// Recursion guard.
547+
llvm::DenseSet<const ValueDecl *> VarsBeingTranslated;
548+
// Context-dependent lookup of currently valid definitions of local variables.
549+
std::function<const Expr *(const NamedDecl *)> LookupLocalVarExpr;
534550
};
535551

536552
#ifndef NDEBUG

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "llvm/ADT/DenseMap.h"
4040
#include "llvm/ADT/ImmutableMap.h"
4141
#include "llvm/ADT/STLExtras.h"
42+
#include "llvm/ADT/ScopeExit.h"
4243
#include "llvm/ADT/SmallVector.h"
4344
#include "llvm/ADT/StringRef.h"
4445
#include "llvm/Support/Allocator.h"
@@ -537,6 +538,13 @@ class LocalVariableMap {
537538
protected:
538539
friend class VarMapBuilder;
539540

541+
// Resolve any definition ID down to its non-reference base ID.
542+
unsigned getCanonicalDefinitionID(unsigned ID) {
543+
while (ID > 0 && VarDefinitions[ID].isReference())
544+
ID = VarDefinitions[ID].Ref;
545+
return ID;
546+
}
547+
540548
// Get the current context index
541549
unsigned getContextIndex() { return SavedContexts.size()-1; }
542550

@@ -621,6 +629,7 @@ class VarMapBuilder : public ConstStmtVisitor<VarMapBuilder> {
621629

622630
void VisitDeclStmt(const DeclStmt *S);
623631
void VisitBinaryOperator(const BinaryOperator *BO);
632+
void VisitCallExpr(const CallExpr *CE);
624633
};
625634

626635
} // namespace
@@ -666,6 +675,56 @@ void VarMapBuilder::VisitBinaryOperator(const BinaryOperator *BO) {
666675
}
667676
}
668677

678+
// Invalidates local variable definitions if variable escaped.
679+
void VarMapBuilder::VisitCallExpr(const CallExpr *CE) {
680+
const FunctionDecl *FD = CE->getDirectCallee();
681+
if (!FD)
682+
return;
683+
684+
// Heuristic for likely-benign functions that pass by mutable reference. This
685+
// is needed to avoid a slew of false positives due to mutable reference
686+
// passing where the captured reference is usually passed on by-value.
687+
if (const IdentifierInfo *II = FD->getIdentifier()) {
688+
// Any kind of std::bind-like functions.
689+
if (II->isStr("bind") || II->isStr("bind_front"))
690+
return;
691+
}
692+
693+
// Invalidate local variable definitions that are passed by non-const
694+
// reference or non-const pointer.
695+
for (unsigned Idx = 0; Idx < CE->getNumArgs(); ++Idx) {
696+
if (Idx >= FD->getNumParams())
697+
break;
698+
699+
const Expr *Arg = CE->getArg(Idx)->IgnoreParenImpCasts();
700+
const ParmVarDecl *PVD = FD->getParamDecl(Idx);
701+
QualType ParamType = PVD->getType();
702+
703+
// Potential reassignment if passed by non-const reference / pointer.
704+
const ValueDecl *VDec = nullptr;
705+
if (ParamType->isReferenceType() &&
706+
!ParamType->getPointeeType().isConstQualified()) {
707+
if (const auto *DRE = dyn_cast<DeclRefExpr>(Arg))
708+
VDec = DRE->getDecl();
709+
} else if (ParamType->isPointerType() &&
710+
!ParamType->getPointeeType().isConstQualified()) {
711+
Arg = Arg->IgnoreParenCasts();
712+
if (const auto *UO = dyn_cast<UnaryOperator>(Arg)) {
713+
if (UO->getOpcode() == UO_AddrOf) {
714+
const Expr *SubE = UO->getSubExpr()->IgnoreParenCasts();
715+
if (const auto *DRE = dyn_cast<DeclRefExpr>(SubE))
716+
VDec = DRE->getDecl();
717+
}
718+
}
719+
}
720+
721+
if (VDec && Ctx.lookup(VDec)) {
722+
Ctx = VMap->clearDefinition(VDec, Ctx);
723+
VMap->saveContext(CE, Ctx);
724+
}
725+
}
726+
}
727+
669728
// Computes the intersection of two contexts. The intersection is the
670729
// set of variables which have the same definition in both contexts;
671730
// variables with different definitions are discarded.
@@ -674,11 +733,16 @@ LocalVariableMap::intersectContexts(Context C1, Context C2) {
674733
Context Result = C1;
675734
for (const auto &P : C1) {
676735
const NamedDecl *Dec = P.first;
677-
const unsigned *i2 = C2.lookup(Dec);
678-
if (!i2) // variable doesn't exist on second path
736+
const unsigned *I2 = C2.lookup(Dec);
737+
if (!I2) {
738+
// The variable doesn't exist on second path.
679739
Result = removeDefinition(Dec, Result);
680-
else if (*i2 != P.second) // variable exists, but has different definition
740+
} else if (getCanonicalDefinitionID(P.second) !=
741+
getCanonicalDefinitionID(*I2)) {
742+
// If canonical definitions mismatch the underlying definitions are
743+
// different, invalidate.
681744
Result = clearDefinition(Dec, Result);
745+
}
682746
}
683747
return Result;
684748
}
@@ -698,13 +762,22 @@ LocalVariableMap::Context LocalVariableMap::createReferenceContext(Context C) {
698762
// createReferenceContext.
699763
void LocalVariableMap::intersectBackEdge(Context C1, Context C2) {
700764
for (const auto &P : C1) {
701-
unsigned i1 = P.second;
702-
VarDefinition *VDef = &VarDefinitions[i1];
765+
const unsigned I1 = P.second;
766+
VarDefinition *VDef = &VarDefinitions[I1];
703767
assert(VDef->isReference());
704768

705-
const unsigned *i2 = C2.lookup(P.first);
706-
if (!i2 || (*i2 != i1))
707-
VDef->Ref = 0; // Mark this variable as undefined
769+
const unsigned *I2 = C2.lookup(P.first);
770+
if (!I2) {
771+
// Variable does not exist at the end of the loop, invalidate.
772+
VDef->Ref = 0;
773+
continue;
774+
}
775+
776+
// Compare the canonical IDs. This correctly handles chains of references
777+
// and determines if the variable is truly loop-invariant.
778+
if (getCanonicalDefinitionID(VDef->Ref) != getCanonicalDefinitionID(*I2)) {
779+
VDef->Ref = 0; // Mark this variable as undefined
780+
}
708781
}
709782
}
710783

@@ -1196,11 +1269,10 @@ class ThreadSafetyAnalyzer {
11961269

11971270
void warnIfMutexNotHeld(const FactSet &FSet, const NamedDecl *D,
11981271
const Expr *Exp, AccessKind AK, Expr *MutexExp,
1199-
ProtectedOperationKind POK, til::LiteralPtr *Self,
1272+
ProtectedOperationKind POK, til::SExpr *Self,
12001273
SourceLocation Loc);
12011274
void warnIfMutexHeld(const FactSet &FSet, const NamedDecl *D, const Expr *Exp,
1202-
Expr *MutexExp, til::LiteralPtr *Self,
1203-
SourceLocation Loc);
1275+
Expr *MutexExp, til::SExpr *Self, SourceLocation Loc);
12041276

12051277
void checkAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK,
12061278
ProtectedOperationKind POK);
@@ -1596,6 +1668,16 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result,
15961668
const CFGBlockInfo *PredBlockInfo = &BlockInfo[PredBlock->getBlockID()];
15971669
const LocalVarContext &LVarCtx = PredBlockInfo->ExitContext;
15981670

1671+
// Temporarily set the lookup context for SExprBuilder.
1672+
SxBuilder.setLookupLocalVarExpr([&](const NamedDecl *D) -> const Expr * {
1673+
if (!Handler.issueBetaWarnings())
1674+
return nullptr;
1675+
auto Ctx = LVarCtx;
1676+
return LocalVarMap.lookupExpr(D, Ctx);
1677+
});
1678+
auto Cleanup = llvm::make_scope_exit(
1679+
[this] { SxBuilder.setLookupLocalVarExpr(nullptr); });
1680+
15991681
const auto *Exp = getTrylockCallExpr(Cond, LVarCtx, Negate);
16001682
if (!Exp)
16011683
return;
@@ -1652,7 +1734,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
16521734
}
16531735

16541736
void handleCall(const Expr *Exp, const NamedDecl *D,
1655-
til::LiteralPtr *Self = nullptr,
1737+
til::SExpr *Self = nullptr,
16561738
SourceLocation Loc = SourceLocation());
16571739
void examineArguments(const FunctionDecl *FD,
16581740
CallExpr::const_arg_iterator ArgBegin,
@@ -1664,7 +1746,17 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
16641746
const FactSet &FunctionExitFSet)
16651747
: ConstStmtVisitor<BuildLockset>(), Analyzer(Anlzr), FSet(Info.EntrySet),
16661748
FunctionExitFSet(FunctionExitFSet), LVarCtx(Info.EntryContext),
1667-
CtxIndex(Info.EntryIndex) {}
1749+
CtxIndex(Info.EntryIndex) {
1750+
Analyzer->SxBuilder.setLookupLocalVarExpr(
1751+
[this](const NamedDecl *D) -> const Expr * {
1752+
if (!Analyzer->Handler.issueBetaWarnings())
1753+
return nullptr;
1754+
auto Ctx = LVarCtx;
1755+
return Analyzer->LocalVarMap.lookupExpr(D, Ctx);
1756+
});
1757+
}
1758+
1759+
~BuildLockset() { Analyzer->SxBuilder.setLookupLocalVarExpr(nullptr); }
16681760

16691761
void VisitUnaryOperator(const UnaryOperator *UO);
16701762
void VisitBinaryOperator(const BinaryOperator *BO);
@@ -1682,7 +1774,7 @@ class BuildLockset : public ConstStmtVisitor<BuildLockset> {
16821774
/// of at least the passed in AccessKind.
16831775
void ThreadSafetyAnalyzer::warnIfMutexNotHeld(
16841776
const FactSet &FSet, const NamedDecl *D, const Expr *Exp, AccessKind AK,
1685-
Expr *MutexExp, ProtectedOperationKind POK, til::LiteralPtr *Self,
1777+
Expr *MutexExp, ProtectedOperationKind POK, til::SExpr *Self,
16861778
SourceLocation Loc) {
16871779
LockKind LK = getLockKindFromAccessKind(AK);
16881780
CapabilityExpr Cp = SxBuilder.translateAttrExpr(MutexExp, D, Exp, Self);
@@ -1741,8 +1833,7 @@ void ThreadSafetyAnalyzer::warnIfMutexNotHeld(
17411833
/// Warn if the LSet contains the given lock.
17421834
void ThreadSafetyAnalyzer::warnIfMutexHeld(const FactSet &FSet,
17431835
const NamedDecl *D, const Expr *Exp,
1744-
Expr *MutexExp,
1745-
til::LiteralPtr *Self,
1836+
Expr *MutexExp, til::SExpr *Self,
17461837
SourceLocation Loc) {
17471838
CapabilityExpr Cp = SxBuilder.translateAttrExpr(MutexExp, D, Exp, Self);
17481839
if (Cp.isInvalid()) {
@@ -1910,7 +2001,7 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp,
19102001
/// of an implicitly called cleanup function.
19112002
/// \param Loc If \p Exp = nullptr, the location.
19122003
void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
1913-
til::LiteralPtr *Self, SourceLocation Loc) {
2004+
til::SExpr *Self, SourceLocation Loc) {
19142005
CapExprSet ExclusiveLocksToAdd, SharedLocksToAdd;
19152006
CapExprSet ExclusiveLocksToRemove, SharedLocksToRemove, GenericLocksToRemove;
19162007
CapExprSet ScopedReqsAndExcludes;
@@ -1922,7 +2013,7 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
19222013
const auto *TagT = Exp->getType()->getAs<TagType>();
19232014
if (D->hasAttrs() && TagT && Exp->isPRValue()) {
19242015
til::LiteralPtr *Placeholder =
1925-
Analyzer->SxBuilder.createVariable(nullptr);
2016+
Analyzer->SxBuilder.createThisPlaceholder();
19262017
[[maybe_unused]] auto inserted =
19272018
Analyzer->ConstructedObjects.insert({Exp, Placeholder});
19282019
assert(inserted.second && "Are we visiting the same expression again?");
@@ -2216,6 +2307,9 @@ void BuildLockset::examineArguments(const FunctionDecl *FD,
22162307
}
22172308

22182309
void BuildLockset::VisitCallExpr(const CallExpr *Exp) {
2310+
// adjust the context
2311+
LVarCtx = Analyzer->LocalVarMap.getNextContext(CtxIndex, Exp, LVarCtx);
2312+
22192313
if (const auto *CE = dyn_cast<CXXMemberCallExpr>(Exp)) {
22202314
const auto *ME = dyn_cast<MemberExpr>(CE->getCallee());
22212315
// ME can be null when calling a method pointer
@@ -2603,7 +2697,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
26032697
}
26042698
if (UnderlyingLocks.empty())
26052699
continue;
2606-
CapabilityExpr Cp(SxBuilder.createVariable(Param), StringRef(),
2700+
CapabilityExpr Cp(SxBuilder.translateVariable(Param, nullptr),
2701+
StringRef(),
26072702
/*Neg=*/false, /*Reentrant=*/false);
26082703
auto *ScopedEntry = FactMan.createFact<ScopedLockableFactEntry>(
26092704
Cp, Param->getLocation(), FactEntry::Declared,
@@ -2721,17 +2816,19 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
27212816
if (!DD->hasAttrs())
27222817
break;
27232818

2724-
LocksetBuilder.handleCall(nullptr, DD,
2725-
SxBuilder.createVariable(AD.getVarDecl()),
2726-
AD.getTriggerStmt()->getEndLoc());
2819+
LocksetBuilder.handleCall(
2820+
nullptr, DD,
2821+
SxBuilder.translateVariable(AD.getVarDecl(), nullptr),
2822+
AD.getTriggerStmt()->getEndLoc());
27272823
break;
27282824
}
27292825

27302826
case CFGElement::CleanupFunction: {
27312827
const CFGCleanupFunction &CF = BI.castAs<CFGCleanupFunction>();
2732-
LocksetBuilder.handleCall(/*Exp=*/nullptr, CF.getFunctionDecl(),
2733-
SxBuilder.createVariable(CF.getVarDecl()),
2734-
CF.getVarDecl()->getLocation());
2828+
LocksetBuilder.handleCall(
2829+
/*Exp=*/nullptr, CF.getFunctionDecl(),
2830+
SxBuilder.translateVariable(CF.getVarDecl(), nullptr),
2831+
CF.getVarDecl()->getLocation());
27352832
break;
27362833
}
27372834

0 commit comments

Comments
 (0)