Skip to content

Commit

Permalink
[clang][TSA] Thread safety cleanup functions
Browse files Browse the repository at this point in the history
Consider cleanup functions in thread safety analysis.

Differential Revision: https://reviews.llvm.org/D152504
  • Loading branch information
tbaederr committed Sep 19, 2023
1 parent 5f476b8 commit cf8e189
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 6 deletions.
2 changes: 1 addition & 1 deletion clang/include/clang/Analysis/Analyses/ThreadSafetyCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ class SExprBuilder {
unsigned NumArgs = 0;

// Function arguments
const Expr *const *FunArgs = nullptr;
llvm::PointerUnion<const Expr *const *, til::SExpr *> FunArgs = nullptr;

// is Self referred to with -> or .?
bool SelfArrow = false;
Expand Down
12 changes: 11 additions & 1 deletion clang/lib/Analysis/ThreadSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1773,7 +1773,8 @@ void BuildLockset::checkPtAccess(const Expr *Exp, AccessKind AK,
///
/// \param Exp The call expression.
/// \param D The callee declaration.
/// \param Self If \p Exp = nullptr, the implicit this argument.
/// \param Self If \p Exp = nullptr, the implicit this argument or the argument
/// of an implicitly called cleanup function.
/// \param Loc If \p Exp = nullptr, the location.
void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
til::LiteralPtr *Self, SourceLocation Loc) {
Expand Down Expand Up @@ -2417,6 +2418,15 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
AD.getTriggerStmt()->getEndLoc());
break;
}

case CFGElement::CleanupFunction: {
const CFGCleanupFunction &CF = BI.castAs<CFGCleanupFunction>();
LocksetBuilder.handleCall(/*Exp=*/nullptr, CF.getFunctionDecl(),
SxBuilder.createVariable(CF.getVarDecl()),
CF.getVarDecl()->getLocation());
break;
}

case CFGElement::TemporaryDtor: {
auto TD = BI.castAs<CFGTemporaryDtor>();

Expand Down
19 changes: 15 additions & 4 deletions clang/lib/Analysis/ThreadSafetyCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ static StringRef ClassifyDiagnostic(QualType VDT) {
/// \param D The declaration to which the attribute is attached.
/// \param DeclExp An expression involving the Decl to which the attribute
/// is attached. E.g. the call to a function.
/// \param Self S-expression to substitute for a \ref CXXThisExpr.
/// \param Self S-expression to substitute for a \ref CXXThisExpr in a call,
/// or argument to a cleanup function.
CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,
const NamedDecl *D,
const Expr *DeclExp,
Expand Down Expand Up @@ -144,7 +145,11 @@ CapabilityExpr SExprBuilder::translateAttrExpr(const Expr *AttrExp,

if (Self) {
assert(!Ctx.SelfArg && "Ambiguous self argument");
Ctx.SelfArg = Self;
assert(isa<FunctionDecl>(D) && "Self argument requires function");
if (isa<CXXMethodDecl>(D))
Ctx.SelfArg = Self;
else
Ctx.FunArgs = Self;

// If the attribute has no arguments, then assume the argument is "this".
if (!AttrExp)
Expand Down Expand Up @@ -312,8 +317,14 @@ til::SExpr *SExprBuilder::translateDeclRefExpr(const DeclRefExpr *DRE,
? (cast<FunctionDecl>(D)->getCanonicalDecl() == Canonical)
: (cast<ObjCMethodDecl>(D)->getCanonicalDecl() == Canonical)) {
// Substitute call arguments for references to function parameters
assert(I < Ctx->NumArgs);
return translate(Ctx->FunArgs[I], Ctx->Prev);
if (const Expr *const *FunArgs =
Ctx->FunArgs.dyn_cast<const Expr *const *>()) {
assert(I < Ctx->NumArgs);
return translate(FunArgs[I], Ctx->Prev);
}

assert(I == 0);
return Ctx->FunArgs.get<til::SExpr *>();
}
}
// Map the param back to the param of the original function declaration
Expand Down
9 changes: 9 additions & 0 deletions clang/test/Sema/warn-thread-safety-analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ int get_value(int *p) SHARED_LOCKS_REQUIRED(foo_.mu_){
return *p;
}

void unlock_scope(struct Mutex *const *mu) __attribute__((release_capability(**mu)));

int main(void) {

Foo_fun1(1); // expected-warning{{calling function 'Foo_fun1' requires holding mutex 'mu2'}} \
Expand Down Expand Up @@ -127,6 +129,13 @@ int main(void) {
// expected-note@-1{{mutex released here}}
mutex_shared_unlock(&mu1); // expected-warning {{releasing mutex 'mu1' that was not held}}

/// Cleanup functions
{
struct Mutex* const __attribute__((cleanup(unlock_scope))) scope = &mu1;
mutex_exclusive_lock(scope); // Note that we have to lock through scope, because no alias analysis!
// Cleanup happens automatically -> no warning.
}

return 0;
}

Expand Down

0 comments on commit cf8e189

Please sign in to comment.