Skip to content

[clang] Warn on mismatched RequiresCapability attributes #67520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Warn on RequiresCapability attribute mismatch
  • Loading branch information
tbaederr committed Nov 22, 2024
commit e89c66ced10f0d785d41d3b11c8447388c41e16e
3 changes: 3 additions & 0 deletions clang/include/clang/Analysis/Analyses/ThreadSafety.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,9 @@ class ThreadSafetyHandler {
/// Warn that there is a cycle in acquired_before/after dependencies.
virtual void handleBeforeAfterCycle(Name L1Name, SourceLocation Loc) {}

virtual void handleAttributeMismatch(const NamedDecl *D1,
const NamedDecl *D2) {}

/// Called by the analysis when starting analysis of a function.
/// Used to issue suggestions for changes to annotations.
virtual void enterFunction(const FunctionDecl *FD) {}
Expand Down
4 changes: 3 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -4004,6 +4004,9 @@ def err_attribute_argument_out_of_bounds_extra_info : Error<
"%plural{0:no parameters to index into|"
"1:can only be 1, since there is one parameter|"
":must be between 1 and %2}2">;
def warn_attribute_mismatch : Warning<
"attribute mismatch between function declarations of %0">,
InGroup<ThreadSafetyAttributes>, DefaultIgnore;

// Thread Safety Analysis
def warn_unlock_but_no_lock : Warning<"releasing %0 '%1' that was not held">,
Expand Down Expand Up @@ -4055,7 +4058,6 @@ def warn_acquired_before_after_cycle : Warning<
"cycle in acquired_before/after dependencies, starting with '%0'">,
InGroup<ThreadSafetyAnalysis>, DefaultIgnore;


// Thread safety warnings negative capabilities
def warn_acquire_requires_negative_cap : Warning<
"acquiring %0 '%1' requires negative capability '%2'">,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ByteCode/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Function::ParamDescriptor Function::getParamDescriptor(unsigned Offset) const {
}

SourceInfo Function::getSource(CodePtr PC) const {
llvm::errs() << __PRETTY_FUNCTION__ << '\n';
assert(PC >= getCodeBegin() && "PC does not belong to this function");
assert(PC <= getCodeEnd() && "PC Does not belong to this function");
assert(hasBody() && "Function has no body");
Expand Down
25 changes: 25 additions & 0 deletions clang/lib/Analysis/ThreadSafety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,8 @@ class ThreadSafetyAnalyzer {
ProtectedOperationKind POK);
void checkPtAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK,
ProtectedOperationKind POK);

void checkMismatchedFunctionAttrs(const NamedDecl *ND);
};

} // namespace
Expand Down Expand Up @@ -2263,6 +2265,27 @@ static bool neverReturns(const CFGBlock *B) {
return false;
}

void ThreadSafetyAnalyzer::checkMismatchedFunctionAttrs(const NamedDecl *ND) {
auto collectCapabilities = [&](const Decl *D) {
CapExprSet Caps;
for (const auto *A : D->specific_attrs<RequiresCapabilityAttr>()) {
for (const Expr *E : A->args())
Caps.push_back_nodup(SxBuilder.translateAttrExpr(E, nullptr));
}
return Caps;
};

CapExprSet NDArgs = collectCapabilities(ND);
for (const Decl *D = ND->getPreviousDecl(); D; D = D->getPreviousDecl()) {
CapExprSet DArgs = collectCapabilities(D);

for (const auto &[A, B] : zip_longest(NDArgs, DArgs)) {
if (!A || !B || !A->equals(*B))
Handler.handleAttributeMismatch(ND, cast<NamedDecl>(D));
}
Copy link
Member

Choose a reason for hiding this comment

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

Since we inherit the attributes and deduplicate capabilities here via push_back_nodup, this seems to be equivalent to just comparing sizes. We would only have to go through the vectors if we actually wanted to find the missing attribute.

Equivalently, we could also collect the capabilities from attributes on the first declaration, and then on subsequent declarations check non-inherited attributes against the capability set of the first declaration. That would also make it easy to point out which capability is missing on the first declaration. So we can print a better diagnostic.

}
}

/// Check a function's CFG for thread-safety violations.
///
/// We traverse the blocks in the CFG, compute the set of mutexes that are held
Expand All @@ -2282,6 +2305,8 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
const NamedDecl *D = walker.getDecl();
CurrentFunction = dyn_cast<FunctionDecl>(D);

checkMismatchedFunctionAttrs(D);

if (D->hasAttr<NoThreadSafetyAnalysisAttr>())
return;

Expand Down
10 changes: 10 additions & 0 deletions clang/lib/Sema/AnalysisBasedWarnings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2065,6 +2065,16 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler {
Warnings.emplace_back(std::move(Warning), getNotes());
}

void handleAttributeMismatch(const NamedDecl *ThisDecl,
const NamedDecl *PrevDecl) override {
PartialDiagnosticAt Warning(ThisDecl->getLocation(),
S.PDiag(diag::warn_attribute_mismatch)
<< ThisDecl);
PartialDiagnosticAt Note(PrevDecl->getLocation(),
S.PDiag(diag::note_previous_decl) << PrevDecl);
Warnings.emplace_back(std::move(Warning), getNotes(Note));
}

void enterFunction(const FunctionDecl* FD) override {
CurrentFunction = FD;
}
Expand Down
12 changes: 6 additions & 6 deletions clang/test/SemaCXX/warn-thread-safety-analysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2198,8 +2198,8 @@ namespace FunctionDefinitionTest {
class Foo {
public:
void foo1();
void foo2();
void foo3(Foo *other);
void foo2() EXCLUSIVE_LOCKS_REQUIRED(mu_);
void foo3(Foo *other) EXCLUSIVE_LOCKS_REQUIRED(other->mu_);

template<class T>
void fooT1(const T& dummy1);
Expand Down Expand Up @@ -2249,8 +2249,8 @@ void fooF1(Foo *f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_) {
f->a = 1;
}

void fooF2(Foo *f);
void fooF2(Foo *f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_) {
void fooF2(Foo *f); // expected-note {{declared here}}
void fooF2(Foo *f) EXCLUSIVE_LOCKS_REQUIRED(f->mu_) { // expected-warning {{attribute mismatch between function declarations of 'fooF2'}}
f->a = 2;
}

Expand All @@ -2269,9 +2269,9 @@ void test() {
Foo myFoo;

myFoo.foo2(); // \
// expected-warning {{calling function 'foo2' requires holding mutex 'myFoo.mu_' exclusively}}
// expected-warning 2{{calling function 'foo2' requires holding mutex 'myFoo.mu_' exclusively}}
myFoo.foo3(&myFoo); // \
// expected-warning {{calling function 'foo3' requires holding mutex 'myFoo.mu_' exclusively}}
// expected-warning 2{{calling function 'foo3' requires holding mutex 'myFoo.mu_' exclusively}}
myFoo.fooT1(dummy); // \
// expected-warning {{calling function 'fooT1<int>' requires holding mutex 'myFoo.mu_' exclusively}}

Expand Down