-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang][ThreadSafety] Check trylock function success and return types #95290
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
#include "clang/AST/ASTContext.h" | ||
#include "clang/AST/ASTMutationListener.h" | ||
#include "clang/AST/CXXInheritance.h" | ||
#include "clang/AST/Decl.h" | ||
#include "clang/AST/DeclCXX.h" | ||
#include "clang/AST/DeclObjC.h" | ||
#include "clang/AST/DeclTemplate.h" | ||
|
@@ -25,6 +26,7 @@ | |
#include "clang/Basic/CharInfo.h" | ||
#include "clang/Basic/Cuda.h" | ||
#include "clang/Basic/DarwinSDKInfo.h" | ||
#include "clang/Basic/DiagnosticSema.h" | ||
#include "clang/Basic/HLSLRuntime.h" | ||
#include "clang/Basic/IdentifierTable.h" | ||
#include "clang/Basic/LangOptions.h" | ||
|
@@ -65,10 +67,13 @@ | |
#include "llvm/Demangle/Demangle.h" | ||
#include "llvm/IR/Assumptions.h" | ||
#include "llvm/MC/MCSectionMachO.h" | ||
#include "llvm/Support/Casting.h" | ||
#include "llvm/Support/Error.h" | ||
#include "llvm/Support/ErrorHandling.h" | ||
#include "llvm/Support/MathExtras.h" | ||
#include "llvm/Support/raw_ostream.h" | ||
#include "llvm/TargetParser/Triple.h" | ||
#include <cassert> | ||
#include <optional> | ||
|
||
using namespace clang; | ||
|
@@ -166,13 +171,6 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum, | |
return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation); | ||
} | ||
|
||
/// Check if the passed-in expression is of type int or bool. | ||
static bool isIntOrBool(Expr *Exp) { | ||
QualType QT = Exp->getType(); | ||
return QT->isBooleanType() || QT->isIntegerType(); | ||
} | ||
|
||
|
||
// Check to see if the type is a smart pointer of some kind. We assume | ||
// it's a smart pointer if it defines both operator-> and operator*. | ||
static bool threadSafetyCheckIsSmartPointer(Sema &S, const RecordType* RT) { | ||
|
@@ -608,15 +606,31 @@ static bool checkTryLockFunAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL, | |
if (!AL.checkAtLeastNumArgs(S, 1)) | ||
return false; | ||
|
||
if (!isIntOrBool(AL.getArgAsExpr(0))) { | ||
// The attribute's first argument defines the success value. | ||
const Expr *SuccessArg = AL.getArgAsExpr(0); | ||
if (!isa<CXXNullPtrLiteralExpr>(SuccessArg) && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see the appeal of Though it seems strange that one would return a pointer, and In any event, since we want to able to express both the null and non-null case, conversion to |
||
!isa<GNUNullExpr>(SuccessArg) && !isa<CXXBoolLiteralExpr>(SuccessArg) && | ||
!isa<IntegerLiteral>(SuccessArg) && !SuccessArg->getEnumConstantDecl()) { | ||
S.Diag(AL.getLoc(), diag::err_attribute_argument_n_type) | ||
<< AL << 1 << AANT_ArgumentIntOrBool; | ||
<< AL << 1 << AANT_ArgumentNullptrOrBoolIntOrEnumLiteral; | ||
return false; | ||
} | ||
|
||
// check that all arguments are lockable objects | ||
// All remaining arguments must be lockable objects. | ||
checkAttrArgsAreCapabilityObjs(S, D, AL, Args, 1); | ||
|
||
// The function must return a pointer, boolean, integer, or enum. We already | ||
// know that `D` is a function because `ExclusiveTrylockFunction` and friends | ||
// are defined in Attr.td with subject lists that only include functions. | ||
QualType ReturnType = D->getAsFunction()->getReturnType(); | ||
if (!ReturnType->isPointerType() && !ReturnType->isBooleanType() && | ||
!ReturnType->isIntegerType() && !ReturnType->isEnumeralType()) { | ||
S.Diag(AL.getLoc(), diag::err_attribute_wrong_decl_type) | ||
<< AL << AL.isRegularKeywordAttribute() | ||
<< ExpectedFunctionReturningPointerBoolIntOrEnum; | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of prefer the old code here. Regardless of what we accept as return type, we only care about the return type converted to
bool
, so other values don't really make sense as of now. (We need theIntegerLiteral
for C.)What we should document is that this isn't an equality comparison, but a comparison after conversion to
bool
. (Or whatever the equivalent for C is. Basically what is allowed in anif
condition.)Should we support integer/enumeration values properly, that would of course change the picture. In that case we can allow enumerators in the attribute, and the return type must be explicitly (?) convertible to type of the attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback, @aaronpuchert! I've reverted the change and I'll work through these comments for a reland.
My instinct is to explicitly disallow enumerator success expressions. Currently, the analysis can produce both false negatives and false positives when enumerator success expressions are used: https://godbolt.org/z/MPYdK6hYb. To my knowledge, enumerators were never actually supported in success expressions, so in some sense this will only break code that was already broken. However, I do want to be sensitive to downstream breakage based on my experience with this PR!
One suggestion was to add a flag that disables any new errors, the goal being to enabling large code bases to incrementally adapt to the new behavior. I'm just not sure how this flag would actually work.
@asmok-g, do you have any thoughts? How are breaking changes such as this typically handled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed.
It depends. If the incorrect behavior is being relied upon, or the correction could produce hard-to-find issues like different behavior at runtime, then it could make sense to keep it. But here I find it unlikely that someone relies on this, as it does not properly work, and the additional error would at most make compilation fail and would be trivial to fix.
I'd argue that the change isn't breaking anything, it's merely diagnosing a case that was already not appropriately handled.