-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[alpha.webkit.UnretainedCallArgsChecker] Add a checker for NS or CF type call arguments. #128586
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
90403f7
ceeba57
ab640f7
5d6b018
6d5a42e
9f089c6
5f232d2
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 |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
#include "clang/AST/Decl.h" | ||
#include "clang/AST/DeclCXX.h" | ||
#include "clang/AST/DynamicRecursiveASTVisitor.h" | ||
#include "clang/Analysis/DomainSpecific/CocoaConventions.h" | ||
#include "clang/Basic/SourceLocation.h" | ||
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" | ||
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" | ||
|
@@ -35,6 +36,9 @@ class RawPtrRefCallArgsChecker | |
TrivialFunctionAnalysis TFA; | ||
EnsureFunctionAnalysis EFA; | ||
|
||
protected: | ||
mutable std::optional<RetainTypeChecker> RTC; | ||
|
||
public: | ||
RawPtrRefCallArgsChecker(const char *description) | ||
: Bug(this, description, "WebKit coding guidelines") {} | ||
|
@@ -80,9 +84,22 @@ class RawPtrRefCallArgsChecker | |
Checker->visitCallExpr(CE, DeclWithIssue); | ||
return true; | ||
} | ||
|
||
bool VisitTypedefDecl(TypedefDecl *TD) override { | ||
if (Checker->RTC) | ||
Checker->RTC->visitTypedef(TD); | ||
return true; | ||
} | ||
|
||
bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) override { | ||
Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue); | ||
return true; | ||
} | ||
}; | ||
|
||
LocalVisitor visitor(this); | ||
if (RTC) | ||
RTC->visitTranslationUnitDecl(TUD); | ||
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); | ||
} | ||
|
||
|
@@ -122,7 +139,7 @@ class RawPtrRefCallArgsChecker | |
// if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>()) | ||
// continue; | ||
|
||
QualType ArgType = (*P)->getType().getCanonicalType(); | ||
QualType ArgType = (*P)->getType(); | ||
// FIXME: more complex types (arrays, references to raw pointers, etc) | ||
std::optional<bool> IsUncounted = isUnsafePtr(ArgType); | ||
if (!IsUncounted || !(*IsUncounted)) | ||
|
@@ -141,6 +158,42 @@ class RawPtrRefCallArgsChecker | |
} | ||
} | ||
|
||
void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const { | ||
if (BR->getSourceManager().isInSystemHeader(E->getExprLoc())) | ||
return; | ||
|
||
auto Selector = E->getSelector(); | ||
if (auto *Receiver = E->getInstanceReceiver()->IgnoreParenCasts()) { | ||
std::optional<bool> IsUnsafe = isUnsafePtr(E->getReceiverType()); | ||
if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) { | ||
if (auto *InnerMsg = dyn_cast<ObjCMessageExpr>(Receiver)) { | ||
auto InnerSelector = InnerMsg->getSelector(); | ||
if (InnerSelector.getNameForSlot(0) == "alloc" && | ||
Selector.getNameForSlot(0).starts_with("init")) | ||
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. Should this also check for 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 don't think so. As far as I know, "init" is never capitalized. |
||
return; | ||
} | ||
reportBugOnReceiver(Receiver, D); | ||
} | ||
} | ||
|
||
auto *MethodDecl = E->getMethodDecl(); | ||
if (!MethodDecl) | ||
return; | ||
|
||
auto ArgCount = E->getNumArgs(); | ||
for (unsigned i = 0; i < ArgCount && i < MethodDecl->param_size(); ++i) { | ||
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. Do we have a test case for variable length params? 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. Oh, that's a good point. Indeed this code doesn't work for variadic functions. Fixed. |
||
auto *Arg = E->getArg(i); | ||
auto *Param = MethodDecl->getParamDecl(i); | ||
auto ArgType = Param->getType(); | ||
std::optional<bool> IsUnsafe = isUnsafePtr(ArgType); | ||
if (!IsUnsafe || !(*IsUnsafe)) | ||
continue; | ||
if (isPtrOriginSafe(Arg)) | ||
continue; | ||
reportBug(Arg, Param, D); | ||
} | ||
} | ||
|
||
bool isPtrOriginSafe(const Expr *Arg) const { | ||
return tryToFindPtrOrigin( | ||
Arg, /*StopAtFirstRefCountedObj=*/true, | ||
|
@@ -158,6 +211,8 @@ class RawPtrRefCallArgsChecker | |
// foo(NULL) | ||
return true; | ||
} | ||
if (isa<ObjCStringLiteral>(ArgOrigin)) | ||
return true; | ||
if (isASafeCallArg(ArgOrigin)) | ||
return true; | ||
if (EFA.isACallToEnsureFn(ArgOrigin)) | ||
|
@@ -212,7 +267,7 @@ class RawPtrRefCallArgsChecker | |
overloadedOperatorType == OO_PipePipe) | ||
return true; | ||
|
||
if (isCtorOfRefCounted(Callee)) | ||
if (isCtorOfSafePtr(Callee)) | ||
return true; | ||
|
||
auto name = safeGetName(Callee); | ||
|
@@ -304,6 +359,23 @@ class RawPtrRefCallArgsChecker | |
Report->setDeclWithIssue(DeclWithIssue); | ||
BR->emitReport(std::move(Report)); | ||
} | ||
|
||
void reportBugOnReceiver(const Expr *CallArg, | ||
const Decl *DeclWithIssue) const { | ||
assert(CallArg); | ||
|
||
const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin(); | ||
|
||
SmallString<100> Buf; | ||
llvm::raw_svector_ostream Os(Buf); | ||
Os << "Reciever is " << ptrKind() << " and unsafe."; | ||
|
||
PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); | ||
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc); | ||
Report->addRange(CallArg->getSourceRange()); | ||
Report->setDeclWithIssue(DeclWithIssue); | ||
BR->emitReport(std::move(Report)); | ||
} | ||
}; | ||
|
||
class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { | ||
|
@@ -317,7 +389,7 @@ class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { | |
} | ||
|
||
std::optional<bool> isUnsafePtr(QualType QT) const final { | ||
return isUncountedPtr(QT); | ||
return isUncountedPtr(QT.getCanonicalType()); | ||
} | ||
|
||
bool isSafePtr(const CXXRecordDecl *Record) const final { | ||
|
@@ -342,7 +414,7 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { | |
} | ||
|
||
std::optional<bool> isUnsafePtr(QualType QT) const final { | ||
return isUncheckedPtr(QT); | ||
return isUncheckedPtr(QT.getCanonicalType()); | ||
} | ||
|
||
bool isSafePtr(const CXXRecordDecl *Record) const final { | ||
|
@@ -356,6 +428,33 @@ class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { | |
const char *ptrKind() const final { return "unchecked"; } | ||
}; | ||
|
||
class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker { | ||
public: | ||
UnretainedCallArgsChecker() | ||
: RawPtrRefCallArgsChecker("Unretained call argument for a raw " | ||
"pointer/reference parameter") { | ||
RTC = RetainTypeChecker(); | ||
} | ||
|
||
std::optional<bool> isUnsafeType(QualType QT) const final { | ||
return RTC->isUnretained(QT); | ||
} | ||
|
||
std::optional<bool> isUnsafePtr(QualType QT) const final { | ||
return RTC->isUnretained(QT); | ||
} | ||
|
||
bool isSafePtr(const CXXRecordDecl *Record) const final { | ||
return isRetainPtr(Record); | ||
} | ||
|
||
bool isSafePtrType(const QualType type) const final { | ||
return isRetainPtrType(type); | ||
} | ||
|
||
const char *ptrKind() const final { return "unretained"; } | ||
}; | ||
|
||
} // namespace | ||
|
||
void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { | ||
|
@@ -373,3 +472,11 @@ void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) { | |
bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) { | ||
return true; | ||
} | ||
|
||
void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) { | ||
Mgr.registerChecker<UnretainedCallArgsChecker>(); | ||
} | ||
|
||
bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) { | ||
return true; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UnretainedCallArgsChecker -fobjc-arc -verify %s | ||
|
||
#import "objc-mock-types.h" | ||
|
||
SomeObj *provide(); | ||
CFMutableArrayRef provide_cf(); | ||
void someFunction(); | ||
|
||
namespace raw_ptr { | ||
|
||
void foo() { | ||
[provide() doWork]; | ||
CFArrayAppendValue(provide_cf(), nullptr); | ||
// expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}} | ||
} | ||
|
||
} // namespace raw_ptr | ||
|
||
@interface AnotherObj : NSObject | ||
- (void)foo:(SomeObj *)obj; | ||
@end | ||
|
||
@implementation AnotherObj | ||
- (void)foo:(SomeObj*)obj { | ||
[obj doWork]; | ||
[provide() doWork]; | ||
CFArrayAppendValue(provide_cf(), nullptr); | ||
// expected-warning@-1{{Call argument for parameter 'theArray' is unretained and unsafe [alpha.webkit.UnretainedCallArgsChecker]}} | ||
} | ||
@end |
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.
This ignores kCF~ and _kCF constants.
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.
Do the kCF~ and _kCF constants need to be handled or are they supposed to be ignored? Add a code comment clarifying this?
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.
They need to be treated as safe. Will add a comment.