Skip to content

Commit e53ddc9

Browse files
committed
[clang][ThreadSafety] Enforce trylock function return type
With this change, Clang will generate an error when functions or methods with disallowed return types are annotated as trylock functions. The only allowed return types are boolean, integer, and pointer types. This effect is that annotating a constructor, destructor, void-returning function, etc. will fail to compile, where it silently passed before. Issue #92408
1 parent 4cf1a19 commit e53ddc9

File tree

9 files changed

+76
-26
lines changed

9 files changed

+76
-26
lines changed

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,10 +420,14 @@ TRY_ACQUIRE(<bool>, ...), TRY_ACQUIRE_SHARED(<bool>, ...)
420420
*Previously:* ``EXCLUSIVE_TRYLOCK_FUNCTION``, ``SHARED_TRYLOCK_FUNCTION``
421421

422422
These are attributes on a function or method that tries to acquire the given
423-
capability, and returns a boolean value indicating success or failure.
424-
The first argument must be ``true`` or ``false``, to specify which return value
425-
indicates success, and the remaining arguments are interpreted in the same way
426-
as ``ACQUIRE``. See :ref:`mutexheader`, below, for example uses.
423+
capability, and returns a boolean, integer, or pointer value indicating success
424+
or failure.
425+
426+
The attribute's first argument must be ``true`` or ``false``, to specify which
427+
return value indicates success. Integer literals are also accepted, but the
428+
analysis effectively converts them to boolean before use. The remaining
429+
arguments are interpreted in the same way as ``ACQUIRE``. See
430+
:ref:`mutexheader`, below, for example uses.
427431

428432
Because the analysis doesn't support conditional locking, a capability is
429433
treated as acquired after the first branch on the return value of a try-acquire

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3721,7 +3721,8 @@ def warn_attribute_wrong_decl_type : Warning<
37213721
"|types and namespaces"
37223722
"|variables, functions and classes"
37233723
"|kernel functions"
3724-
"|non-K&R-style functions}2">,
3724+
"|non-K&R-style functions"
3725+
"|functions that return bool, integer, or a pointer type}2">,
37253726
InGroup<IgnoredAttributes>;
37263727
def err_attribute_wrong_decl_type : Error<warn_attribute_wrong_decl_type.Summary>;
37273728
def warn_type_attribute_wrong_type : Warning<

clang/include/clang/Sema/ParsedAttr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,7 @@ enum AttributeDeclKind {
11011101
ExpectedFunctionVariableOrClass,
11021102
ExpectedKernelFunction,
11031103
ExpectedFunctionWithProtoType,
1104+
ExpectedFunctionReturningBoolIntegerOrPointer,
11041105
};
11051106

11061107
inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "clang/Analysis/Analyses/ThreadSafetyUtil.h"
3333
#include "clang/Analysis/AnalysisDeclContext.h"
3434
#include "clang/Analysis/CFG.h"
35+
#include "clang/Basic/AttrKinds.h"
3536
#include "clang/Basic/Builtins.h"
3637
#include "clang/Basic/LLVM.h"
3738
#include "clang/Basic/OperatorKinds.h"
@@ -1366,6 +1367,8 @@ void ThreadSafetyAnalyzer::getMutexIDs(CapExprSet &Mtxs, AttrType *Attr,
13661367
branch = BLE->getValue();
13671368
else if (const auto *ILE = dyn_cast_or_null<IntegerLiteral>(BrE))
13681369
branch = ILE->getValue().getBoolValue();
1370+
else
1371+
llvm_unreachable("Trylock success values must be bool or integer.");
13691372

13701373
int branchnum = branch ? 0 : 1;
13711374
if (Neg)

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "clang/Basic/CharInfo.h"
2626
#include "clang/Basic/Cuda.h"
2727
#include "clang/Basic/DarwinSDKInfo.h"
28+
#include "clang/Basic/DiagnosticSema.h"
2829
#include "clang/Basic/HLSLRuntime.h"
2930
#include "clang/Basic/IdentifierTable.h"
3031
#include "clang/Basic/LangOptions.h"
@@ -69,6 +70,7 @@
6970
#include "llvm/Support/MathExtras.h"
7071
#include "llvm/Support/raw_ostream.h"
7172
#include "llvm/TargetParser/Triple.h"
73+
#include <cassert>
7274
#include <optional>
7375

7476
using namespace clang;
@@ -605,6 +607,9 @@ static void handleAllocSizeAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
605607

606608
static bool checkTryLockFunAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
607609
SmallVectorImpl<Expr *> &Args) {
610+
// The attribute's first argument indicates which value the annotated function
611+
// will return when it has successfully acquired the lock. Semantically, it's
612+
// a boolean value, but we'll also accept integers.
608613
if (!AL.checkAtLeastNumArgs(S, 1))
609614
return false;
610615

@@ -614,9 +619,23 @@ static bool checkTryLockFunAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
614619
return false;
615620
}
616621

617-
// check that all arguments are lockable objects
622+
// Check that all remaining arguments are lockable objects.
618623
checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 1);
619624

625+
// Check that the attribute is applied to a function.
626+
if (!D->isFunctionOrFunctionTemplate()) {
627+
return false;
628+
}
629+
// Check that the function returns a boolean, integer, or pointer.
630+
QualType ReturnType = D->getAsFunction()->getReturnType();
631+
if (!ReturnType->isBooleanType() && !ReturnType->isIntegerType() &&
632+
!ReturnType->isPointerType()) {
633+
S.Diag(AL.getLoc(), diag::err_attribute_wrong_decl_type)
634+
<< AL << AL.isRegularKeywordAttribute()
635+
<< ExpectedFunctionReturningBoolIntegerOrPointer;
636+
return false;
637+
}
638+
620639
return true;
621640
}
622641

clang/test/Sema/attr-capabilities.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,14 @@ void Func18(void) __attribute__((release_capability())) {} // expected-warning {
5454
void Func19(void) __attribute__((release_shared_capability())) {} // expected-warning {{'release_shared_capability' attribute without capability arguments can only be applied to non-static methods of a class}}
5555
void Func20(void) __attribute__((release_generic_capability())) {} // expected-warning {{'release_generic_capability' attribute without capability arguments can only be applied to non-static methods of a class}}
5656

57-
void Func21(void) __attribute__((try_acquire_capability(1))) {} // expected-warning {{'try_acquire_capability' attribute without capability arguments can only be applied to non-static methods of a class}}
58-
void Func22(void) __attribute__((try_acquire_shared_capability(1))) {} // expected-warning {{'try_acquire_shared_capability' attribute without capability arguments can only be applied to non-static methods of a class}}
57+
int Func21(void) __attribute__((try_acquire_capability(1))) {} // expected-warning {{'try_acquire_capability' attribute without capability arguments can only be applied to non-static methods of a class}}
58+
int Func22(void) __attribute__((try_acquire_shared_capability(1))) {} // expected-warning {{'try_acquire_shared_capability' attribute without capability arguments can only be applied to non-static methods of a class}}
5959

60-
void Func23(void) __attribute__((try_acquire_capability(1, GUI))) {}
61-
void Func24(void) __attribute__((try_acquire_shared_capability(1, GUI))) {}
60+
int Func23(void) __attribute__((try_acquire_capability(1, GUI))) {}
61+
int Func24(void) __attribute__((try_acquire_shared_capability(1, GUI))) {}
6262

63-
void Func25(void) __attribute__((try_acquire_capability())) {} // expected-error {{'try_acquire_capability' attribute takes at least 1 argument}}
64-
void Func26(void) __attribute__((try_acquire_shared_capability())) {} // expected-error {{'try_acquire_shared_capability' attribute takes at least 1 argument}}
63+
int Func25(void) __attribute__((try_acquire_capability())) {} // expected-error {{'try_acquire_capability' attribute takes at least 1 argument}}
64+
int Func26(void) __attribute__((try_acquire_shared_capability())) {} // expected-error {{'try_acquire_shared_capability' attribute takes at least 1 argument}}
6565

6666
// Test that boolean logic works with capability attributes
6767
void Func27(void) __attribute__((requires_capability(!GUI)));

clang/test/SemaCXX/warn-thread-safety-analysis.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3657,8 +3657,8 @@ class Foo {
36573657
int a GUARDED_BY(mu_);
36583658
bool c;
36593659

3660-
int tryLockMutexI() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu_);
3661-
Mutex* tryLockMutexP() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu_);
3660+
bool tryLockMutexI() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu_);
3661+
Mutex *tryLockMutexP() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu_);
36623662
void unlock() UNLOCK_FUNCTION(mu_);
36633663

36643664
void test1();

clang/test/SemaCXX/warn-thread-safety-parsing.cpp

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -716,15 +716,17 @@ int slf_function_bad_7() SHARED_LOCK_FUNCTION(0); // \
716716
#error "Should support exclusive_trylock_function attribute"
717717
#endif
718718

719-
// takes a mandatory boolean or integer argument specifying the retval
720-
// plus an optional list of locks (vars/fields)
719+
// The attribute takes a mandatory boolean or integer argument specifying the
720+
// retval plus an optional list of locks (vars/fields). The annotated function's
721+
// return type should be boolean or integer.
721722

722723
void etf_function() __attribute__((exclusive_trylock_function)); // \
723724
// expected-error {{'exclusive_trylock_function' attribute takes at least 1 argument}}
724725

725-
void etf_function_args() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu2);
726+
void etf_function_args() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu2); // \
727+
// expected-error {{'exclusive_trylock_function' attribute only applies to functions that return bool, integer, or a pointer type}}
726728

727-
void etf_function_arg() EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
729+
int etf_function_arg() EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
728730
// expected-warning {{'exclusive_trylock_function' attribute without capability arguments can only be applied to non-static methods of a class}}
729731

730732
int etf_testfn(int y) EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
@@ -743,10 +745,23 @@ class EtfFoo {
743745
private:
744746
int test_field EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
745747
// expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
746-
void test_method() EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
748+
bool test_method() EXCLUSIVE_TRYLOCK_FUNCTION(1); // \
747749
// expected-warning {{'exclusive_trylock_function' attribute without capability arguments refers to 'this', but 'EtfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
748750
};
749751

752+
// It does not make sense to annotate constructors/destructors as exclusive
753+
// trylock functions because they cannot return a value. See
754+
// <https://github.com/llvm/llvm-project/issues/92408>.
755+
class EtfConstructorDestructor {
756+
EtfConstructorDestructor() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu); // \
757+
// expected-error {{'exclusive_trylock_function' attribute only applies to functions that return bool, integer, or a pointer type}}
758+
759+
~EtfConstructorDestructor() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu); // \
760+
// expected-error {{'exclusive_trylock_function' attribute only applies to functions that return bool, integer, or a pointer type}}
761+
762+
Mutex mu;
763+
};
764+
750765
class EXCLUSIVE_TRYLOCK_FUNCTION(1) EtfTestClass { // \
751766
// expected-warning {{'exclusive_trylock_function' attribute only applies to functions}}
752767
};
@@ -756,7 +771,14 @@ void etf_fun_params(int lvar EXCLUSIVE_TRYLOCK_FUNCTION(1)); // \
756771

757772
// Check argument parsing.
758773

759-
// legal attribute arguments
774+
// legal permutations of the first argument and function return type
775+
int etf_func_args_succ_1_ret_int() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu2);
776+
bool etf_func_args_succ_1_ret_bool() EXCLUSIVE_TRYLOCK_FUNCTION(1, mu2);
777+
int etf_func_args_succ_true_ret_int() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu2);
778+
bool etf_func_args_succ_true_ret_bool() EXCLUSIVE_TRYLOCK_FUNCTION(true, mu2);
779+
bool etf_func_args_succ_false() EXCLUSIVE_TRYLOCK_FUNCTION(false, mu2);
780+
781+
// legal capability attribute arguments
760782
int etf_function_1() EXCLUSIVE_TRYLOCK_FUNCTION(1, muWrapper.mu);
761783
int etf_function_2() EXCLUSIVE_TRYLOCK_FUNCTION(1, muDoubleWrapper.muWrapper->mu);
762784
int etf_function_3() EXCLUSIVE_TRYLOCK_FUNCTION(1, muWrapper.getMu());
@@ -799,9 +821,9 @@ int etf_function_bad_6() EXCLUSIVE_TRYLOCK_FUNCTION(1, umu); // \
799821
void stf_function() __attribute__((shared_trylock_function)); // \
800822
// expected-error {{'shared_trylock_function' attribute takes at least 1 argument}}
801823

802-
void stf_function_args() SHARED_TRYLOCK_FUNCTION(1, mu2);
824+
bool stf_function_args() SHARED_TRYLOCK_FUNCTION(1, mu2);
803825

804-
void stf_function_arg() SHARED_TRYLOCK_FUNCTION(1); // \
826+
bool stf_function_arg() SHARED_TRYLOCK_FUNCTION(1); // \
805827
// expected-warning {{'shared_trylock_function' attribute without capability arguments can only be applied to non-static methods of a class}}
806828

807829
int stf_testfn(int y) SHARED_TRYLOCK_FUNCTION(1); // \
@@ -824,7 +846,7 @@ class StfFoo {
824846
private:
825847
int test_field SHARED_TRYLOCK_FUNCTION(1); // \
826848
// expected-warning {{'shared_trylock_function' attribute only applies to functions}}
827-
void test_method() SHARED_TRYLOCK_FUNCTION(1); // \
849+
bool test_method() SHARED_TRYLOCK_FUNCTION(1); // \
828850
// expected-warning {{'shared_trylock_function' attribute without capability arguments refers to 'this', but 'StfFoo' isn't annotated with 'capability' or 'scoped_lockable' attribute}}
829851
};
830852

clang/unittests/AST/ASTImporterTest.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7851,7 +7851,7 @@ TEST_P(ImportAttributes, ImportAcquireCapability) {
78517851
TEST_P(ImportAttributes, ImportTryAcquireCapability) {
78527852
TryAcquireCapabilityAttr *FromAttr, *ToAttr;
78537853
importAttr<FunctionDecl>(
7854-
"void test(int A1, int A2) __attribute__((try_acquire_capability(1, A1, "
7854+
"bool test(int A1, int A2) __attribute__((try_acquire_capability(1, A1, "
78557855
"A2)));",
78567856
FromAttr, ToAttr);
78577857
checkImported(FromAttr->getSuccessValue(), ToAttr->getSuccessValue());
@@ -7948,7 +7948,7 @@ TEST_P(ImportAttributes, ImportAssertSharedLock) {
79487948
TEST_P(ImportAttributes, ImportExclusiveTrylockFunction) {
79497949
ExclusiveTrylockFunctionAttr *FromAttr, *ToAttr;
79507950
importAttr<FunctionDecl>(
7951-
"void test(int A1, int A2) __attribute__((exclusive_trylock_function(1, "
7951+
"bool test(int A1, int A2) __attribute__((exclusive_trylock_function(1, "
79527952
"A1, A2)));",
79537953
FromAttr, ToAttr);
79547954
checkImported(FromAttr->getSuccessValue(), ToAttr->getSuccessValue());
@@ -7958,7 +7958,7 @@ TEST_P(ImportAttributes, ImportExclusiveTrylockFunction) {
79587958
TEST_P(ImportAttributes, ImportSharedTrylockFunction) {
79597959
SharedTrylockFunctionAttr *FromAttr, *ToAttr;
79607960
importAttr<FunctionDecl>(
7961-
"void test(int A1, int A2) __attribute__((shared_trylock_function(1, A1, "
7961+
"bool test(int A1, int A2) __attribute__((shared_trylock_function(1, A1, "
79627962
"A2)));",
79637963
FromAttr, ToAttr);
79647964
checkImported(FromAttr->getSuccessValue(), ToAttr->getSuccessValue());

0 commit comments

Comments
 (0)