517 lines · cpp
1//=======- RawPtrRefCallArgsChecker.cpp --------------------------*- C++ -*-==//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "ASTUtils.h"10#include "DiagOutputUtils.h"11#include "PtrTypesSemantics.h"12#include "clang/AST/Decl.h"13#include "clang/AST/DeclCXX.h"14#include "clang/AST/DynamicRecursiveASTVisitor.h"15#include "clang/Analysis/DomainSpecific/CocoaConventions.h"16#include "clang/Basic/SourceLocation.h"17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"18#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"19#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"20#include "clang/StaticAnalyzer/Core/Checker.h"21#include "llvm/Support/SaveAndRestore.h"22#include <optional>23 24using namespace clang;25using namespace ento;26 27namespace {28 29class RawPtrRefCallArgsChecker30 : public Checker<check::ASTDecl<TranslationUnitDecl>> {31 BugType Bug;32 33 TrivialFunctionAnalysis TFA;34 EnsureFunctionAnalysis EFA;35 36protected:37 mutable BugReporter *BR;38 mutable std::optional<RetainTypeChecker> RTC;39 40public:41 RawPtrRefCallArgsChecker(const char *description)42 : Bug(this, description, "WebKit coding guidelines") {}43 44 virtual std::optional<bool> isUnsafeType(QualType) const = 0;45 virtual std::optional<bool> isUnsafePtr(QualType) const = 0;46 virtual bool isSafePtr(const CXXRecordDecl *Record) const = 0;47 virtual bool isSafePtrType(const QualType type) const = 0;48 virtual bool isSafeExpr(const Expr *) const { return false; }49 virtual bool isSafeDecl(const Decl *) const { return false; }50 virtual const char *ptrKind() const = 0;51 52 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,53 BugReporter &BRArg) const {54 BR = &BRArg;55 56 // The calls to checkAST* from AnalysisConsumer don't57 // visit template instantiations or lambda classes. We58 // want to visit those, so we make our own RecursiveASTVisitor.59 struct LocalVisitor : DynamicRecursiveASTVisitor {60 const RawPtrRefCallArgsChecker *Checker;61 Decl *DeclWithIssue{nullptr};62 63 explicit LocalVisitor(const RawPtrRefCallArgsChecker *Checker)64 : Checker(Checker) {65 assert(Checker);66 ShouldVisitTemplateInstantiations = true;67 ShouldVisitImplicitCode = false;68 }69 70 bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override {71 if (isSmartPtrClass(safeGetName(Decl)))72 return true;73 return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(Decl);74 }75 76 bool TraverseDecl(Decl *D) override {77 llvm::SaveAndRestore SavedDecl(DeclWithIssue);78 if (D && (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)))79 DeclWithIssue = D;80 return DynamicRecursiveASTVisitor::TraverseDecl(D);81 }82 83 bool VisitCallExpr(CallExpr *CE) override {84 Checker->visitCallExpr(CE, DeclWithIssue);85 return true;86 }87 88 bool VisitTypedefDecl(TypedefDecl *TD) override {89 if (Checker->RTC)90 Checker->RTC->visitTypedef(TD);91 return true;92 }93 94 bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) override {95 Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue);96 return true;97 }98 };99 100 LocalVisitor visitor(this);101 if (RTC)102 RTC->visitTranslationUnitDecl(TUD);103 visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));104 }105 106 void visitCallExpr(const CallExpr *CE, const Decl *D) const {107 if (shouldSkipCall(CE))108 return;109 110 if (auto *F = CE->getDirectCallee()) {111 // Skip the first argument for overloaded member operators (e. g. lambda112 // or std::function call operator).113 unsigned ArgIdx =114 isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);115 116 if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) {117 if (auto *MD = MemberCallExpr->getMethodDecl()) {118 auto name = safeGetName(MD);119 if (name == "ref" || name == "deref")120 return;121 if (name == "incrementCheckedPtrCount" ||122 name == "decrementCheckedPtrCount")123 return;124 }125 auto *E = MemberCallExpr->getImplicitObjectArgument();126 QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType();127 std::optional<bool> IsUnsafe = isUnsafeType(ArgType);128 if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(E))129 reportBugOnThis(E, D);130 }131 132 for (auto P = F->param_begin();133 // FIXME: Also check variadic function parameters.134 // FIXME: Also check default function arguments. Probably a different135 // checker. In case there are default arguments the call can have136 // fewer arguments than the callee has parameters.137 P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) {138 // TODO: attributes.139 // if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>())140 // continue;141 142 QualType ArgType = (*P)->getType();143 // FIXME: more complex types (arrays, references to raw pointers, etc)144 std::optional<bool> IsUncounted = isUnsafePtr(ArgType);145 if (!IsUncounted || !(*IsUncounted))146 continue;147 148 const auto *Arg = CE->getArg(ArgIdx);149 150 if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))151 Arg = defaultArg->getExpr();152 153 if (isPtrOriginSafe(Arg))154 continue;155 156 reportBug(Arg, *P, D);157 }158 for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) {159 const auto *Arg = CE->getArg(ArgIdx);160 auto ArgType = Arg->getType();161 std::optional<bool> IsUncounted = isUnsafePtr(ArgType);162 if (!IsUncounted || !(*IsUncounted))163 continue;164 165 if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))166 Arg = defaultArg->getExpr();167 168 if (isPtrOriginSafe(Arg))169 continue;170 171 reportBug(Arg, nullptr, D);172 }173 }174 }175 176 void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const {177 if (BR->getSourceManager().isInSystemHeader(E->getExprLoc()))178 return;179 180 auto Selector = E->getSelector();181 if (auto *Receiver = E->getInstanceReceiver()) {182 std::optional<bool> IsUnsafe = isUnsafePtr(E->getReceiverType());183 if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) {184 if (auto *InnerMsg = dyn_cast<ObjCMessageExpr>(Receiver)) {185 auto InnerSelector = InnerMsg->getSelector();186 if (InnerSelector.getNameForSlot(0) == "alloc" &&187 Selector.getNameForSlot(0).starts_with("init"))188 return;189 }190 reportBugOnReceiver(Receiver, D);191 }192 }193 194 auto *MethodDecl = E->getMethodDecl();195 if (!MethodDecl)196 return;197 198 auto ArgCount = E->getNumArgs();199 for (unsigned i = 0; i < ArgCount; ++i) {200 auto *Arg = E->getArg(i);201 bool hasParam = i < MethodDecl->param_size();202 auto *Param = hasParam ? MethodDecl->getParamDecl(i) : nullptr;203 auto ArgType = Arg->getType();204 std::optional<bool> IsUnsafe = isUnsafePtr(ArgType);205 if (!IsUnsafe || !(*IsUnsafe))206 continue;207 if (isPtrOriginSafe(Arg))208 continue;209 reportBug(Arg, Param, D);210 }211 }212 213 bool isPtrOriginSafe(const Expr *Arg) const {214 return tryToFindPtrOrigin(215 Arg, /*StopAtFirstRefCountedObj=*/true,216 [&](const clang::CXXRecordDecl *Record) { return isSafePtr(Record); },217 [&](const clang::QualType T) { return isSafePtrType(T); },218 [&](const clang::Decl *D) { return isSafeDecl(D); },219 [&](const clang::Expr *ArgOrigin, bool IsSafe) {220 if (IsSafe)221 return true;222 if (isNullPtr(ArgOrigin))223 return true;224 if (isa<IntegerLiteral>(ArgOrigin)) {225 // FIXME: Check the value.226 // foo(123)227 return true;228 }229 if (isa<CXXBoolLiteralExpr>(ArgOrigin))230 return true;231 if (isa<ObjCStringLiteral>(ArgOrigin))232 return true;233 if (isASafeCallArg(ArgOrigin))234 return true;235 if (EFA.isACallToEnsureFn(ArgOrigin))236 return true;237 if (isSafeExpr(ArgOrigin))238 return true;239 return false;240 });241 }242 243 bool shouldSkipCall(const CallExpr *CE) const {244 const auto *Callee = CE->getDirectCallee();245 246 if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc()))247 return true;248 249 if (Callee && TFA.isTrivial(Callee) && !Callee->isVirtualAsWritten())250 return true;251 252 if (isTrivialBuiltinFunction(Callee))253 return true;254 255 if (CE->getNumArgs() == 0)256 return false;257 258 // If an assignment is problematic we should warn about the sole existence259 // of object on LHS.260 if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(CE)) {261 // Note: assignemnt to built-in type isn't derived from CallExpr.262 if (MemberOp->getOperator() ==263 OO_Equal) { // Ignore assignment to Ref/RefPtr.264 auto *callee = MemberOp->getDirectCallee();265 if (auto *calleeDecl = dyn_cast<CXXMethodDecl>(callee)) {266 if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) {267 if (isSafePtr(classDecl))268 return true;269 }270 }271 }272 if (MemberOp->isAssignmentOp())273 return false;274 }275 276 if (!Callee)277 return false;278 279 if (isMethodOnWTFContainerType(Callee))280 return true;281 282 auto overloadedOperatorType = Callee->getOverloadedOperator();283 if (overloadedOperatorType == OO_EqualEqual ||284 overloadedOperatorType == OO_ExclaimEqual ||285 overloadedOperatorType == OO_LessEqual ||286 overloadedOperatorType == OO_GreaterEqual ||287 overloadedOperatorType == OO_Spaceship ||288 overloadedOperatorType == OO_AmpAmp ||289 overloadedOperatorType == OO_PipePipe)290 return true;291 292 if (isCtorOfSafePtr(Callee) || isPtrConversion(Callee))293 return true;294 295 auto name = safeGetName(Callee);296 if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" ||297 name == "is" || name == "equal" || name == "hash" || name == "isType" ||298 // FIXME: Most/all of these should be implemented via attributes.299 name == "CFEqual" || name == "equalIgnoringASCIICase" ||300 name == "equalIgnoringASCIICaseCommon" ||301 name == "equalIgnoringNullity" || name == "toString")302 return true;303 304 return false;305 }306 307 bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const {308 if (!isa<CXXMethodDecl>(Decl))309 return false;310 auto *ClassDecl = Decl->getParent();311 if (!ClassDecl || !isa<CXXRecordDecl>(ClassDecl))312 return false;313 314 auto *NsDecl = ClassDecl->getParent();315 if (!NsDecl || !isa<NamespaceDecl>(NsDecl))316 return false;317 318 auto MethodName = safeGetName(Decl);319 auto ClsNameStr = safeGetName(ClassDecl);320 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.321 auto NamespaceName = safeGetName(NsDecl);322 // FIXME: These should be implemented via attributes.323 return NamespaceName == "WTF" &&324 (MethodName == "find" || MethodName == "findIf" ||325 MethodName == "reverseFind" || MethodName == "reverseFindIf" ||326 MethodName == "findIgnoringASCIICase" || MethodName == "get" ||327 MethodName == "inlineGet" || MethodName == "contains" ||328 MethodName == "containsIf" ||329 MethodName == "containsIgnoringASCIICase" ||330 MethodName == "startsWith" || MethodName == "endsWith" ||331 MethodName == "startsWithIgnoringASCIICase" ||332 MethodName == "endsWithIgnoringASCIICase" ||333 MethodName == "substring") &&334 (ClsName.ends_with("Vector") || ClsName.ends_with("Set") ||335 ClsName.ends_with("Map") || ClsName == "StringImpl" ||336 ClsName.ends_with("String"));337 }338 339 void reportBug(const Expr *CallArg, const ParmVarDecl *Param,340 const Decl *DeclWithIssue) const {341 assert(CallArg);342 343 SmallString<100> Buf;344 llvm::raw_svector_ostream Os(Buf);345 346 const std::string paramName = safeGetName(Param);347 Os << "Call argument";348 if (!paramName.empty()) {349 Os << " for parameter ";350 printQuotedQualifiedName(Os, Param);351 }352 Os << " is " << ptrKind() << " and unsafe.";353 354 bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(CallArg) && Param;355 const SourceLocation SrcLocToReport =356 usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc()357 : CallArg->getSourceRange().getBegin();358 359 PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());360 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);361 Report->addRange(CallArg->getSourceRange());362 Report->setDeclWithIssue(DeclWithIssue);363 BR->emitReport(std::move(Report));364 }365 366 void reportBugOnThis(const Expr *CallArg, const Decl *DeclWithIssue) const {367 assert(CallArg);368 369 const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();370 371 SmallString<100> Buf;372 llvm::raw_svector_ostream Os(Buf);373 Os << "Call argument for 'this' parameter is " << ptrKind();374 Os << " and unsafe.";375 376 PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());377 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);378 Report->addRange(CallArg->getSourceRange());379 Report->setDeclWithIssue(DeclWithIssue);380 BR->emitReport(std::move(Report));381 }382 383 void reportBugOnReceiver(const Expr *CallArg,384 const Decl *DeclWithIssue) const {385 assert(CallArg);386 387 const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();388 389 SmallString<100> Buf;390 llvm::raw_svector_ostream Os(Buf);391 Os << "Receiver is " << ptrKind() << " and unsafe.";392 393 PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());394 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);395 Report->addRange(CallArg->getSourceRange());396 Report->setDeclWithIssue(DeclWithIssue);397 BR->emitReport(std::move(Report));398 }399};400 401class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker {402public:403 UncountedCallArgsChecker()404 : RawPtrRefCallArgsChecker("Uncounted call argument for a raw "405 "pointer/reference parameter") {}406 407 std::optional<bool> isUnsafeType(QualType QT) const final {408 return isUncounted(QT);409 }410 411 std::optional<bool> isUnsafePtr(QualType QT) const final {412 return isUncountedPtr(QT.getCanonicalType());413 }414 415 bool isSafePtr(const CXXRecordDecl *Record) const final {416 return isRefCounted(Record) || isCheckedPtr(Record);417 }418 419 bool isSafePtrType(const QualType type) const final {420 return isRefOrCheckedPtrType(type);421 }422 423 const char *ptrKind() const final { return "uncounted"; }424};425 426class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker {427public:428 UncheckedCallArgsChecker()429 : RawPtrRefCallArgsChecker("Unchecked call argument for a raw "430 "pointer/reference parameter") {}431 432 std::optional<bool> isUnsafeType(QualType QT) const final {433 return isUnchecked(QT);434 }435 436 std::optional<bool> isUnsafePtr(QualType QT) const final {437 return isUncheckedPtr(QT.getCanonicalType());438 }439 440 bool isSafePtr(const CXXRecordDecl *Record) const final {441 return isRefCounted(Record) || isCheckedPtr(Record);442 }443 444 bool isSafePtrType(const QualType type) const final {445 return isRefOrCheckedPtrType(type);446 }447 448 bool isSafeExpr(const Expr *E) const final {449 return isExprToGetCheckedPtrCapableMember(E);450 }451 452 const char *ptrKind() const final { return "unchecked"; }453};454 455class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker {456public:457 UnretainedCallArgsChecker()458 : RawPtrRefCallArgsChecker("Unretained call argument for a raw "459 "pointer/reference parameter") {460 RTC = RetainTypeChecker();461 }462 463 std::optional<bool> isUnsafeType(QualType QT) const final {464 return RTC->isUnretained(QT);465 }466 467 std::optional<bool> isUnsafePtr(QualType QT) const final {468 return RTC->isUnretained(QT);469 }470 471 bool isSafePtr(const CXXRecordDecl *Record) const final {472 return isRetainPtrOrOSPtr(Record);473 }474 475 bool isSafePtrType(const QualType type) const final {476 return isRetainPtrOrOSPtrType(type);477 }478 479 bool isSafeExpr(const Expr *E) const final {480 return ento::cocoa::isCocoaObjectRef(E->getType()) &&481 isa<ObjCMessageExpr>(E);482 }483 484 bool isSafeDecl(const Decl *D) const final {485 // Treat NS/CF globals in system header as immortal.486 return BR->getSourceManager().isInSystemHeader(D->getLocation());487 }488 489 const char *ptrKind() const final { return "unretained"; }490};491 492} // namespace493 494void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {495 Mgr.registerChecker<UncountedCallArgsChecker>();496}497 498bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {499 return true;500}501 502void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) {503 Mgr.registerChecker<UncheckedCallArgsChecker>();504}505 506bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) {507 return true;508}509 510void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) {511 Mgr.registerChecker<UnretainedCallArgsChecker>();512}513 514bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) {515 return true;516}517