Skip to content

Commit d698760

Browse files
authored
[clang][ThreadSafety] Revert stricter typing on trylock attributes (#97293)
This PR reverts #95290 and the one-liner followup PR #96494. I received some substantial feedback on #95290, which I plan to address in a future PR. I've also received feedback that because the change emits errors where they were not emitted before, we should at least have a flag to disable the stricter warnings.
1 parent 51d87aa commit d698760

File tree

10 files changed

+82
-370
lines changed

10 files changed

+82
-370
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@ C++ Specific Potentially Breaking Changes
7171
7272
To fix this, update libstdc++ to version 14.1.1 or greater.
7373

74-
- Clang now emits errors when Thread Safety Analysis trylock attributes are
75-
applied to functions or methods with incompatible return values, such as
76-
constructors, destructors, and void-returning functions. This only affects the
77-
``TRY_ACQUIRE`` and ``TRY_ACQUIRE_SHARED`` attributes (and any synonyms).
78-
7974
ABI Changes in This Version
8075
---------------------------
8176
- Fixed Microsoft name mangling of implicitly defined variables used for thread
@@ -754,11 +749,6 @@ Bug Fixes in This Version
754749

755750
- Fixed `static_cast` to array of unknown bound. Fixes (#GH62863).
756751

757-
- Clang's Thread Safety Analysis now evaluates trylock success arguments of enum
758-
types rather than silently defaulting to false. This fixes a class of false
759-
negatives where the analysis failed to detect unchecked access to guarded
760-
data.
761-
762752
Bug Fixes to Compiler Builtins
763753
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
764754

clang/docs/ThreadSafetyAnalysis.rst

Lines changed: 4 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -420,17 +420,10 @@ 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, integer, or pointer value indicating success
424-
or failure.
425-
426-
The attribute's first argument defines whether a zero or non-zero return value
427-
indicates success. Syntactically, it accepts ``NULL`` or ``nullptr``, ``bool``
428-
and ``int`` literals, as well as enumerator values. *The analysis only cares
429-
whether this success value is zero or non-zero.* This leads to some subtle
430-
consequences, discussed in the next section.
431-
432-
The remaining arguments are interpreted in the same way as ``ACQUIRE``. See
433-
:ref:`mutexheader`, below, for example uses.
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.
434427

435428
Because the analysis doesn't support conditional locking, a capability is
436429
treated as acquired after the first branch on the return value of a try-acquire
@@ -452,44 +445,6 @@ function.
452445
}
453446
}
454447

455-
Subtle Consequences of Non-Boolean Success Values
456-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
457-
458-
The trylock attributes accept non-boolean expressions for the success value, but
459-
the analysis only cares whether the value is zero or non-zero.
460-
461-
Suppose you define an enum with two non-zero enumerators: ``LockAcquired = 1``
462-
and ``LockNotAcquired = 2``. If your trylock function returns ``LockAcquired``
463-
on success and ``LockNotAcquired`` on failure, the analysis may fail to detect
464-
access to guarded data without holding the mutex because they are both non-zero.
465-
466-
.. code-block:: c++
467-
468-
// *** Beware: this code demonstrates incorrect usage. ***
469-
470-
enum TrylockResult { LockAcquired = 1, LockNotAcquired = 2 };
471-
472-
class CAPABILITY("mutex") Mutex {
473-
public:
474-
TrylockResult TryLock() TRY_ACQUIRE(LockAcquired);
475-
};
476-
477-
Mutex mu;
478-
int a GUARDED_BY(mu);
479-
480-
void foo() {
481-
if (mu.TryLock()) { // This branch satisfies the analysis, but permits
482-
// unguarded access to `a`!
483-
a = 0;
484-
mu.Unlock();
485-
}
486-
}
487-
488-
It's also possible to return a pointer from the trylock function. Similarly, all
489-
that matters is whether the success value is zero or non-zero. For instance, a
490-
success value of `true` means the function returns a non-null pointer on
491-
success.
492-
493448

494449
ASSERT_CAPABILITY(...) and ASSERT_SHARED_CAPABILITY(...)
495450
--------------------------------------------------------

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3282,23 +3282,11 @@ def warn_aligned_attr_underaligned : Warning<err_alignas_underaligned.Summary>,
32823282
def err_attribute_sizeless_type : Error<
32833283
"%0 attribute cannot be applied to sizeless type %1">;
32843284
def err_attribute_argument_n_type : Error<
3285-
"%0 attribute requires parameter %1 to be %select{"
3286-
"int or bool"
3287-
"|an integer constant"
3288-
"|a string"
3289-
"|an identifier"
3290-
"|a constant expression"
3291-
"|a builtin function"
3292-
"|nullptr or a bool, int, or enum literal}2">;
3285+
"%0 attribute requires parameter %1 to be %select{int or bool|an integer "
3286+
"constant|a string|an identifier|a constant expression|a builtin function}2">;
32933287
def err_attribute_argument_type : Error<
3294-
"%0 attribute requires %select{"
3295-
"int or bool"
3296-
"|an integer constant"
3297-
"|a string"
3298-
"|an identifier"
3299-
"|a constant expression"
3300-
"|a builtin function"
3301-
"|a pointer or a bool, int, or enum literal}1">;
3288+
"%0 attribute requires %select{int or bool|an integer "
3289+
"constant|a string|an identifier}1">;
33023290
def err_attribute_argument_out_of_range : Error<
33033291
"%0 attribute requires integer constant between %1 and %2 inclusive">;
33043292
def err_init_priority_object_attr : Error<
@@ -3750,8 +3738,7 @@ def warn_attribute_wrong_decl_type : Warning<
37503738
"|types and namespaces"
37513739
"|variables, functions and classes"
37523740
"|kernel functions"
3753-
"|non-K&R-style functions"
3754-
"|functions that return bool, integer, or a pointer type}2">,
3741+
"|non-K&R-style functions}2">,
37553742
InGroup<IgnoredAttributes>;
37563743
def err_attribute_wrong_decl_type : Error<warn_attribute_wrong_decl_type.Summary>;
37573744
def warn_type_attribute_wrong_type : Warning<

clang/include/clang/Sema/ParsedAttr.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,7 +1083,6 @@ enum AttributeArgumentNType {
10831083
AANT_ArgumentIdentifier,
10841084
AANT_ArgumentConstantExpr,
10851085
AANT_ArgumentBuiltinFunction,
1086-
AANT_ArgumentNullptrOrBoolIntOrEnumLiteral,
10871086
};
10881087

10891088
/// These constants match the enumerated choices of
@@ -1102,7 +1101,6 @@ enum AttributeDeclKind {
11021101
ExpectedFunctionVariableOrClass,
11031102
ExpectedKernelFunction,
11041103
ExpectedFunctionWithProtoType,
1105-
ExpectedFunctionReturningPointerBoolIntOrEnum,
11061104
};
11071105

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

clang/lib/Analysis/ThreadSafety.cpp

Lines changed: 36 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
#include "clang/Basic/OperatorKinds.h"
3838
#include "clang/Basic/SourceLocation.h"
3939
#include "clang/Basic/Specifiers.h"
40-
#include "llvm/ADT/APSInt.h"
4140
#include "llvm/ADT/ArrayRef.h"
4241
#include "llvm/ADT/DenseMap.h"
4342
#include "llvm/ADT/ImmutableMap.h"
@@ -1035,10 +1034,9 @@ class ThreadSafetyAnalyzer {
10351034

10361035
template <class AttrType>
10371036
void getMutexIDs(CapExprSet &Mtxs, AttrType *Attr, const Expr *Exp,
1038-
const NamedDecl *D, const CFGBlock *PredBlock,
1039-
const CFGBlock *CurrBlock,
1040-
const ASTContext &TrylockAttrContext,
1041-
Expr *TrylockAttrSuccessValue, bool Neg);
1037+
const NamedDecl *D,
1038+
const CFGBlock *PredBlock, const CFGBlock *CurrBlock,
1039+
Expr *BrE, bool Neg);
10421040

10431041
const CallExpr* getTrylockCallExpr(const Stmt *Cond, LocalVarContext C,
10441042
bool &Negate);
@@ -1361,18 +1359,17 @@ void ThreadSafetyAnalyzer::getMutexIDs(CapExprSet &Mtxs, AttrType *Attr,
13611359
const Expr *Exp, const NamedDecl *D,
13621360
const CFGBlock *PredBlock,
13631361
const CFGBlock *CurrBlock,
1364-
const ASTContext &TrylockAttrContext,
1365-
Expr *TrylockAttrSuccess,
1366-
bool Neg) {
1367-
// Evaluate the trylock's success value as a boolean.
1368-
bool trylockSuccessValue = false;
1369-
if (!TrylockAttrSuccess->EvaluateAsBooleanCondition(
1370-
trylockSuccessValue, TrylockAttrContext,
1371-
/*InConstantContext=*/true)) {
1372-
llvm_unreachable("Trylock success value could not be evaluated.");
1373-
}
1374-
1375-
const int branchnum = Neg ? !!trylockSuccessValue : !trylockSuccessValue;
1362+
Expr *BrE, bool Neg) {
1363+
// Find out which branch has the lock
1364+
bool branch = false;
1365+
if (const auto *BLE = dyn_cast_or_null<CXXBoolLiteralExpr>(BrE))
1366+
branch = BLE->getValue();
1367+
else if (const auto *ILE = dyn_cast_or_null<IntegerLiteral>(BrE))
1368+
branch = ILE->getValue().getBoolValue();
1369+
1370+
int branchnum = branch ? 0 : 1;
1371+
if (Neg)
1372+
branchnum = !branchnum;
13761373

13771374
// If we've taken the trylock branch, then add the lock
13781375
int i = 0;
@@ -1393,15 +1390,8 @@ static bool getStaticBooleanValue(Expr *E, bool &TCond) {
13931390
} else if (const auto *ILE = dyn_cast<IntegerLiteral>(E)) {
13941391
TCond = ILE->getValue().getBoolValue();
13951392
return true;
1396-
} else if (auto *CE = dyn_cast<ImplicitCastExpr>(E)) {
1393+
} else if (auto *CE = dyn_cast<ImplicitCastExpr>(E))
13971394
return getStaticBooleanValue(CE->getSubExpr(), TCond);
1398-
} else if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
1399-
if (auto *ECD = dyn_cast<EnumConstantDecl>(DRE->getDecl())) {
1400-
llvm::APSInt IV = ECD->getInitVal();
1401-
TCond = IV.getBoolValue();
1402-
return true;
1403-
}
1404-
}
14051395
return false;
14061396
}
14071397

@@ -1507,27 +1497,27 @@ void ThreadSafetyAnalyzer::getEdgeLockset(FactSet& Result,
15071497
// If the condition is a call to a Trylock function, then grab the attributes
15081498
for (const auto *Attr : FunDecl->attrs()) {
15091499
switch (Attr->getKind()) {
1510-
case attr::TryAcquireCapability: {
1511-
auto *A = cast<TryAcquireCapabilityAttr>(Attr);
1512-
getMutexIDs(A->isShared() ? SharedLocksToAdd : ExclusiveLocksToAdd, A,
1513-
Exp, FunDecl, PredBlock, CurrBlock, FunDecl->getASTContext(),
1514-
A->getSuccessValue(), Negate);
1515-
break;
1516-
};
1517-
case attr::ExclusiveTrylockFunction: {
1518-
const auto *A = cast<ExclusiveTrylockFunctionAttr>(Attr);
1519-
getMutexIDs(ExclusiveLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock,
1520-
FunDecl->getASTContext(), A->getSuccessValue(), Negate);
1521-
break;
1522-
}
1523-
case attr::SharedTrylockFunction: {
1524-
const auto *A = cast<SharedTrylockFunctionAttr>(Attr);
1525-
getMutexIDs(SharedLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock,
1526-
FunDecl->getASTContext(), A->getSuccessValue(), Negate);
1527-
break;
1528-
}
1529-
default:
1530-
break;
1500+
case attr::TryAcquireCapability: {
1501+
auto *A = cast<TryAcquireCapabilityAttr>(Attr);
1502+
getMutexIDs(A->isShared() ? SharedLocksToAdd : ExclusiveLocksToAdd, A,
1503+
Exp, FunDecl, PredBlock, CurrBlock, A->getSuccessValue(),
1504+
Negate);
1505+
break;
1506+
};
1507+
case attr::ExclusiveTrylockFunction: {
1508+
const auto *A = cast<ExclusiveTrylockFunctionAttr>(Attr);
1509+
getMutexIDs(ExclusiveLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock,
1510+
A->getSuccessValue(), Negate);
1511+
break;
1512+
}
1513+
case attr::SharedTrylockFunction: {
1514+
const auto *A = cast<SharedTrylockFunctionAttr>(Attr);
1515+
getMutexIDs(SharedLocksToAdd, A, Exp, FunDecl, PredBlock, CurrBlock,
1516+
A->getSuccessValue(), Negate);
1517+
break;
1518+
}
1519+
default:
1520+
break;
15311521
}
15321522
}
15331523

clang/lib/Sema/SemaDeclAttr.cpp

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
#include "clang/AST/ASTContext.h"
1515
#include "clang/AST/ASTMutationListener.h"
1616
#include "clang/AST/CXXInheritance.h"
17-
#include "clang/AST/Decl.h"
1817
#include "clang/AST/DeclCXX.h"
1918
#include "clang/AST/DeclObjC.h"
2019
#include "clang/AST/DeclTemplate.h"
@@ -26,7 +25,6 @@
2625
#include "clang/Basic/CharInfo.h"
2726
#include "clang/Basic/Cuda.h"
2827
#include "clang/Basic/DarwinSDKInfo.h"
29-
#include "clang/Basic/DiagnosticSema.h"
3028
#include "clang/Basic/HLSLRuntime.h"
3129
#include "clang/Basic/IdentifierTable.h"
3230
#include "clang/Basic/LangOptions.h"
@@ -67,13 +65,10 @@
6765
#include "llvm/Demangle/Demangle.h"
6866
#include "llvm/IR/Assumptions.h"
6967
#include "llvm/MC/MCSectionMachO.h"
70-
#include "llvm/Support/Casting.h"
7168
#include "llvm/Support/Error.h"
72-
#include "llvm/Support/ErrorHandling.h"
7369
#include "llvm/Support/MathExtras.h"
7470
#include "llvm/Support/raw_ostream.h"
7571
#include "llvm/TargetParser/Triple.h"
76-
#include <cassert>
7772
#include <optional>
7873

7974
using namespace clang;
@@ -171,6 +166,13 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
171166
return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation);
172167
}
173168

169+
/// Check if the passed-in expression is of type int or bool.
170+
static bool isIntOrBool(Expr *Exp) {
171+
QualType QT = Exp->getType();
172+
return QT->isBooleanType() || QT->isIntegerType();
173+
}
174+
175+
174176
// Check to see if the type is a smart pointer of some kind. We assume
175177
// it's a smart pointer if it defines both operator-> and operator*.
176178
static bool threadSafetyCheckIsSmartPointer(Sema &S, const RecordType* RT) {
@@ -606,31 +608,15 @@ static bool checkTryLockFunAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL,
606608
if (!AL.checkAtLeastNumArgs(S, 1))
607609
return false;
608610

609-
// The attribute's first argument defines the success value.
610-
const Expr *SuccessArg = AL.getArgAsExpr(0);
611-
if (!isa<CXXNullPtrLiteralExpr>(SuccessArg) &&
612-
!isa<GNUNullExpr>(SuccessArg) && !isa<CXXBoolLiteralExpr>(SuccessArg) &&
613-
!isa<IntegerLiteral>(SuccessArg) && !SuccessArg->getEnumConstantDecl()) {
611+
if (!isIntOrBool(AL.getArgAsExpr(0))) {
614612
S.Diag(AL.getLoc(), diag::err_attribute_argument_n_type)
615-
<< AL << 1 << AANT_ArgumentNullptrOrBoolIntOrEnumLiteral;
613+
<< AL << 1 << AANT_ArgumentIntOrBool;
616614
return false;
617615
}
618616

619-
// All remaining arguments must be lockable objects.
617+
// check that all arguments are lockable objects
620618
checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 1);
621619

622-
// The function must return a pointer, boolean, integer, or enum. We already
623-
// know that `D` is a function because `ExclusiveTrylockFunction` and friends
624-
// are defined in Attr.td with subject lists that only include functions.
625-
QualType ReturnType = D->getAsFunction()->getReturnType();
626-
if (!ReturnType->isPointerType() && !ReturnType->isBooleanType() &&
627-
!ReturnType->isIntegerType() && !ReturnType->isEnumeralType()) {
628-
S.Diag(AL.getLoc(), diag::err_attribute_wrong_decl_type)
629-
<< AL << AL.isRegularKeywordAttribute()
630-
<< ExpectedFunctionReturningPointerBoolIntOrEnum;
631-
return false;
632-
}
633-
634620
return true;
635621
}
636622

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-
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}}
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}}
5959

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

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}}
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}}
6565

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

0 commit comments

Comments
 (0)