| //=======- RawPtrRefCallArgsChecker.cpp --------------------------*- C++ -*-==// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "ASTUtils.h" |
| #include "DiagOutputUtils.h" |
| #include "PtrTypesSemantics.h" |
| #include "RawPtrRefSafetyModel.h" |
| #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/Lex/Lexer.h" |
| #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
| #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| #include "clang/StaticAnalyzer/Core/Checker.h" |
| #include "llvm/Support/SaveAndRestore.h" |
| #include <optional> |
| |
| using namespace clang; |
| using namespace ento; |
| |
| namespace { |
| |
| class RawPtrRefCallArgsChecker |
| : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
| BugType Bug; |
| |
| TrivialFunctionAnalysis TFA; |
| EnsureFunctionAnalysis EFA; |
| |
| protected: |
| mutable BugReporter *BR; |
| const std::unique_ptr<PtrRefSafetyModel> Model; |
| |
| public: |
| RawPtrRefCallArgsChecker(const char *description, |
| std::unique_ptr<PtrRefSafetyModel> Model) |
| : Bug(this, description, "WebKit coding guidelines"), |
| Model(std::move(Model)) {} |
| |
| void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, |
| BugReporter &BRArg) const { |
| BR = &BRArg; |
| |
| // The calls to checkAST* from AnalysisConsumer don't |
| // visit template instantiations or lambda classes. We |
| // want to visit those, so we make our own RecursiveASTVisitor. |
| struct LocalVisitor : DynamicRecursiveASTVisitor { |
| const RawPtrRefCallArgsChecker *Checker; |
| Decl *DeclWithIssue{nullptr}; |
| |
| explicit LocalVisitor(const RawPtrRefCallArgsChecker *Checker) |
| : Checker(Checker) { |
| assert(Checker); |
| ShouldVisitTemplateInstantiations = true; |
| ShouldVisitImplicitCode = false; |
| } |
| |
| bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override { |
| if (isSmartPtrClass(safeGetName(Decl))) |
| return true; |
| return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(Decl); |
| } |
| |
| bool TraverseDecl(Decl *D) override { |
| llvm::SaveAndRestore SavedDecl(DeclWithIssue); |
| if (D && (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D))) |
| DeclWithIssue = D; |
| return DynamicRecursiveASTVisitor::TraverseDecl(D); |
| } |
| |
| bool VisitCallExpr(CallExpr *CE) override { |
| Checker->visitCallExpr(CE, DeclWithIssue); |
| return true; |
| } |
| |
| bool VisitCXXConstructExpr(CXXConstructExpr *CE) override { |
| Checker->visitConstructExpr(CE, DeclWithIssue); |
| return true; |
| } |
| |
| bool VisitTypedefDecl(TypedefDecl *TD) override { |
| if (auto *RTC = Checker->Model->retainTypeChecker()) |
| RTC->visitTypedef(TD); |
| return true; |
| } |
| |
| bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) override { |
| Checker->visitObjCMessageExpr(ObjCMsgExpr, DeclWithIssue); |
| return true; |
| } |
| }; |
| |
| LocalVisitor visitor(this); |
| if (auto *RTC = Model->retainTypeChecker()) |
| RTC->visitTranslationUnitDecl(TUD); |
| visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); |
| } |
| |
| template <typename CallOrConstrcut> |
| void visitCallOrConstructExpr(const CallOrConstrcut *CE, |
| const FunctionDecl *F, const Decl *D) const { |
| if (F) { |
| // Skip the first argument for overloaded member operators (e. g. lambda |
| // or std::function call operator). |
| unsigned ArgIdx = |
| isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F); |
| |
| if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) |
| checkThisArg(F, MemberCallExpr, D); |
| |
| if (ArgIdx) { |
| auto *Arg = CE->getArg(0); |
| QualType ArgType = Arg->getType().getCanonicalType(); |
| std::optional<bool> IsUnsafe = Model->isUnsafeType(ArgType); |
| if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Arg)) |
| reportBugOnThis(F, Arg, D); |
| } |
| |
| for (auto P = F->param_begin(); |
| P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) { |
| // TODO: attributes. |
| // if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>()) |
| // continue; |
| checkArg(F, CE->getArg(ArgIdx), (*P)->getType(), *P, D); |
| } |
| for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) { |
| auto *Arg = CE->getArg(ArgIdx); |
| checkArg(F, Arg, Arg->getType(), nullptr, D); |
| } |
| } |
| } |
| |
| void visitCallExpr(const CallExpr *CE, const Decl *D) const { |
| auto *Callee = CE->getDirectCallee(); |
| if (shouldSkipCall(CE, Callee)) |
| return; |
| |
| if (Callee) |
| visitCallOrConstructExpr(CE, Callee, D); |
| else if (auto *Decl = CE->getCalleeDecl()) { |
| if (auto *FnType = Decl->getFunctionType()) { |
| if (auto *ProtoType = dyn_cast<FunctionProtoType>(FnType)) { |
| if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) |
| checkThisArg(nullptr, MemberCallExpr, D); |
| unsigned ArgIdx = 0; |
| for (auto PT = ProtoType->param_type_begin(); |
| PT < ProtoType->param_type_end() && ArgIdx < CE->getNumArgs(); |
| ++PT, ++ArgIdx) |
| checkArg(nullptr, CE->getArg(ArgIdx), *PT, nullptr, D); |
| for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) { |
| auto *Arg = CE->getArg(ArgIdx); |
| checkArg(nullptr, Arg, Arg->getType(), nullptr, D); |
| } |
| } |
| } |
| } |
| } |
| |
| void visitConstructExpr(const CXXConstructExpr *CE, const Decl *D) const { |
| auto *Constructor = CE->getConstructor(); |
| if (shouldSkipCall(CE, Constructor)) |
| return; |
| if (Constructor) |
| visitCallOrConstructExpr(CE, Constructor, D); |
| } |
| |
| void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const { |
| if (BR->getSourceManager().isInSystemHeader(E->getExprLoc())) |
| return; |
| |
| if (auto *Receiver = E->getInstanceReceiver()) { |
| std::optional<bool> IsUnsafe = Model->isUnsafePtr(E->getReceiverType()); |
| if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Receiver)) { |
| if (isAllocInit(E)) |
| return; |
| auto SelectorName = E->getSelector().getNameForSlot(0); |
| if (SelectorName == "isEqual" || SelectorName == "isEqualToString") |
| return; |
| reportBugOnReceiver(E->getMethodDecl(), Receiver, D); |
| } |
| } |
| |
| auto *MethodDecl = E->getMethodDecl(); |
| if (!MethodDecl) |
| return; |
| |
| auto ArgCount = E->getNumArgs(); |
| for (unsigned i = 0; i < ArgCount; ++i) { |
| auto *Arg = E->getArg(i); |
| bool hasParam = i < MethodDecl->param_size(); |
| auto *Param = hasParam ? MethodDecl->getParamDecl(i) : nullptr; |
| auto ArgType = Arg->getType(); |
| std::optional<bool> IsUnsafe = Model->isUnsafePtr(ArgType); |
| if (!IsUnsafe || !(*IsUnsafe)) |
| continue; |
| if (isPtrOriginSafe(Arg)) |
| continue; |
| reportBug(MethodDecl, Arg, Param, D); |
| } |
| } |
| |
| void checkThisArg(const NamedDecl *Callee, |
| const CXXMemberCallExpr *MemberCallExpr, |
| const Decl *DeclWithIssue) const { |
| if (auto *MD = MemberCallExpr->getMethodDecl()) { |
| auto name = safeGetName(MD); |
| if (name == "ref" || name == "deref") |
| return; |
| if (name == "incrementCheckedPtrCount" || |
| name == "decrementCheckedPtrCount") |
| return; |
| } |
| auto *ThisExpr = MemberCallExpr->getImplicitObjectArgument(); |
| QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType(); |
| std::optional<bool> IsUnsafe = Model->isUnsafeType(ArgType); |
| if (!IsUnsafe || !*IsUnsafe) |
| return; |
| |
| if (isPtrOriginSafe(ThisExpr)) |
| return; |
| |
| reportBugOnThis(Callee, ThisExpr, DeclWithIssue); |
| } |
| |
| void checkArg(const NamedDecl *Callee, const Expr *Arg, QualType ParamType, |
| const ParmVarDecl *Param, const Decl *DeclWithIssue) const { |
| std::optional<bool> IsUncounted = Model->isUnsafePtr(ParamType); |
| if (!IsUncounted || !(*IsUncounted)) |
| return; |
| |
| if (auto *DefaultArg = dyn_cast<CXXDefaultArgExpr>(Arg)) |
| Arg = DefaultArg->getExpr(); |
| |
| if (isPtrOriginSafe(Arg)) |
| return; |
| |
| reportBug(Callee, Arg, Param, DeclWithIssue); |
| } |
| |
| bool isPtrOriginSafe(const Expr *Arg) const { |
| return tryToFindPtrOrigin( |
| Arg, /*StopAtFirstRefCountedObj=*/true, |
| [&](const clang::CXXRecordDecl *Record) { |
| return Model->isSafePtr(Record); |
| }, |
| [&](const clang::QualType T) { return Model->isSafePtrType(T); }, |
| [&](const clang::Decl *D) { |
| return Model->isSafeDecl(D, BR->getSourceManager()); |
| }, |
| [&](const clang::Expr *ArgOrigin, bool IsSafe) { |
| if (IsSafe) |
| return true; |
| if (isNullPtr(ArgOrigin)) |
| return true; |
| if (isa<IntegerLiteral>(ArgOrigin)) { |
| // FIXME: Check the value. |
| // foo(123) |
| return true; |
| } |
| if (isa<CXXBoolLiteralExpr>(ArgOrigin)) |
| return true; |
| if (isa<ObjCStringLiteral>(ArgOrigin)) |
| return true; |
| if (isASafeCallArg(ArgOrigin)) |
| return true; |
| if (EFA.isACallToEnsureFn(ArgOrigin)) { |
| auto *MCE = dyn_cast<CXXMemberCallExpr>(ArgOrigin); |
| assert(MCE); |
| if (isPtrOriginSafe(MCE->getImplicitObjectArgument())) |
| return true; |
| } |
| if (Model->isSafeExpr(ArgOrigin)) |
| return true; |
| return false; |
| }); |
| } |
| |
| template <typename CallOrConstruct> |
| bool shouldSkipCall(const CallOrConstruct *CE, |
| const FunctionDecl *Callee) const { |
| if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc())) |
| return true; |
| |
| if (Callee && TFA.isTrivial(Callee)) |
| return true; |
| |
| if (isTrivialBuiltinFunction(Callee)) |
| return true; |
| |
| if (CE->getNumArgs() == 0) |
| return false; |
| |
| // If an assignment is problematic we should warn about the sole existence |
| // of object on LHS. |
| if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(CE)) { |
| // Note: assignemnt to built-in type isn't derived from CallExpr. |
| if (MemberOp->getOperator() == |
| OO_Equal) { // Ignore assignment to Ref/RefPtr. |
| auto *callee = MemberOp->getDirectCallee(); |
| if (auto *calleeDecl = dyn_cast<CXXMethodDecl>(callee)) { |
| if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) { |
| if (Model->isSafePtr(classDecl)) |
| return true; |
| } |
| } |
| } |
| if (MemberOp->isAssignmentOp()) |
| return false; |
| } |
| |
| if (!Callee) |
| return false; |
| |
| if (isMethodOnWTFContainerType(Callee)) |
| return true; |
| |
| auto overloadedOperatorType = Callee->getOverloadedOperator(); |
| if (overloadedOperatorType == OO_EqualEqual || |
| overloadedOperatorType == OO_ExclaimEqual || |
| overloadedOperatorType == OO_LessEqual || |
| overloadedOperatorType == OO_GreaterEqual || |
| overloadedOperatorType == OO_Spaceship || |
| overloadedOperatorType == OO_AmpAmp || |
| overloadedOperatorType == OO_PipePipe) |
| return true; |
| |
| if (isCtorOfSafePtr(Callee) || isPtrConversion(Callee)) |
| return true; |
| |
| auto name = safeGetName(Callee); |
| if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" || |
| name == "is" || name == "equal" || name == "hash" || name == "isType" || |
| // FIXME: Most/all of these should be implemented via attributes. |
| name == "CFEqual" || name == "equalIgnoringASCIICase" || |
| name == "equalIgnoringASCIICaseCommon" || |
| name == "equalIgnoringNullity" || name == "toString") |
| return true; |
| |
| return false; |
| } |
| |
| bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const { |
| if (!isa<CXXMethodDecl>(Decl)) |
| return false; |
| auto *ClassDecl = Decl->getParent(); |
| if (!ClassDecl || !isa<CXXRecordDecl>(ClassDecl)) |
| return false; |
| |
| auto *NsDecl = ClassDecl->getParent(); |
| if (!NsDecl || !isa<NamespaceDecl>(NsDecl)) |
| return false; |
| |
| auto MethodName = safeGetName(Decl); |
| auto ClsNameStr = safeGetName(ClassDecl); |
| StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. |
| auto NamespaceName = safeGetName(NsDecl); |
| // FIXME: These should be implemented via attributes. |
| return NamespaceName == "WTF" && |
| (MethodName == "find" || MethodName == "findIf" || |
| MethodName == "reverseFind" || MethodName == "reverseFindIf" || |
| MethodName == "findIgnoringASCIICase" || MethodName == "get" || |
| MethodName == "inlineGet" || MethodName == "contains" || |
| MethodName == "containsIf" || |
| MethodName == "containsIgnoringASCIICase" || |
| MethodName == "startsWith" || MethodName == "endsWith" || |
| MethodName == "startsWithIgnoringASCIICase" || |
| MethodName == "endsWithIgnoringASCIICase" || |
| MethodName == "substring") && |
| (ClsName.ends_with("Vector") || ClsName.ends_with("Set") || |
| ClsName.ends_with("Map") || ClsName == "StringImpl" || |
| ClsName.ends_with("String")); |
| } |
| |
| void reportBug(const NamedDecl *Callee, const Expr *CallArg, |
| const ParmVarDecl *Param, const Decl *DeclWithIssue) const { |
| assert(CallArg); |
| |
| SmallString<100> Buf; |
| llvm::raw_svector_ostream Os(Buf); |
| |
| const std::string paramName = safeGetName(Param); |
| Os << "Function argument"; |
| printArgument(Os, CallArg); |
| if (!paramName.empty() || Callee) |
| Os << " ("; |
| if (!paramName.empty()) { |
| Os << "parameter "; |
| printQuotedQualifiedName(Os, Param); |
| } |
| if (Callee) { |
| if (!paramName.empty()) |
| Os << " "; |
| Os << "to "; |
| printQuotedQualifiedName(Os, Callee); |
| } |
| if (!paramName.empty() || Callee) |
| Os << ")"; |
| Os << " is a "; |
| auto *ArgType = CallArg->getType().getTypePtr(); |
| |
| if (printPointer(Os, ArgType) == PrintDeclKind::Pointer) { |
| auto *RTC = Model->retainTypeChecker(); |
| assert(RTC); |
| if (auto *Decl = RTC->getCanonicalDecl(CallArg->getType())) { |
| printQuotedQualifiedName(Os, Decl); |
| } else { |
| auto Typedef = ArgType->getAs<TypedefType>(); |
| assert(Typedef); |
| printQuotedQualifiedName(Os, Typedef->getDecl()); |
| } |
| } else { |
| Os << " "; |
| printTypeName(Os, CallArg->getType()); |
| } |
| |
| bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(CallArg) && Param; |
| const SourceLocation SrcLocToReport = |
| usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc() |
| : CallArg->getSourceRange().getBegin(); |
| |
| 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)); |
| } |
| |
| void reportBugOnThis(const NamedDecl *Callee, 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 << "Function argument"; |
| printArgument(Os, CallArg); |
| Os << " (parameter 'this'"; |
| if (Callee) { |
| Os << " to "; |
| printQuotedQualifiedName(Os, Callee); |
| } |
| Os << ") is a raw pointer to " << Model->typeName() << " "; |
| printTypeName(Os, CallArg->getType()); |
| |
| 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)); |
| } |
| |
| void reportBugOnReceiver(const NamedDecl *Callee, 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 << "Receiver"; |
| printArgument(Os, CallArg); |
| if (Callee) { |
| Os << " (to "; |
| printQuotedQualifiedName(Os, Callee); |
| Os << ")"; |
| } |
| Os << " is a raw pointer to " << Model->typeName() << " "; |
| printTypeName(Os, CallArg->getType()); |
| |
| 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)); |
| } |
| |
| void printArgument(llvm::raw_svector_ostream &Os, const Expr *Arg) const { |
| SmallString<100> Buf; |
| llvm::raw_svector_ostream ArgOs(Buf); |
| Arg->printPretty(ArgOs, /*Helper=*/nullptr, |
| BR->getContext().getPrintingPolicy()); |
| StringRef ArgCode = ArgOs.str(); |
| if (ArgCode.contains('\n')) |
| return; |
| ArgCode = ArgCode.take_front(50); |
| if (ArgCode.size() == 50) |
| Os << " '" << ArgCode << "...'"; |
| else |
| Os << " '" << ArgCode << "'"; |
| } |
| |
| enum class PrintDeclKind { Pointee, Pointer }; |
| PrintDeclKind printPointer(llvm::raw_svector_ostream &Os, |
| const Type *T) const { |
| // Retain/OS types are frequently spelled through a typedef (e.g. CFXXXRef); |
| // print the typedef name rather than desugaring to the pointee. |
| if (Model->retainTypeChecker() && isa<TypedefType>(T)) { |
| Os << Model->typeName() << " "; |
| return PrintDeclKind::Pointer; |
| } |
| T = T->getUnqualifiedDesugaredType(); |
| bool IsPtr = isa<PointerType, ObjCObjectPointerType>(T); |
| Os << "raw " << (IsPtr ? "pointer" : "reference") << " to " |
| << Model->typeName(); |
| return PrintDeclKind::Pointee; |
| } |
| }; |
| |
| class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { |
| public: |
| UncountedCallArgsChecker() |
| : RawPtrRefCallArgsChecker("Uncounted call argument for a raw " |
| "pointer/reference parameter", |
| makeRefPtrSafetyModel()) {} |
| }; |
| |
| class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { |
| public: |
| UncheckedCallArgsChecker() |
| : RawPtrRefCallArgsChecker("Unchecked call argument for a raw " |
| "pointer/reference parameter", |
| makeCheckedPtrSafetyModel()) {} |
| }; |
| |
| class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker { |
| public: |
| UnretainedCallArgsChecker() |
| : RawPtrRefCallArgsChecker("Unretained call argument for a raw " |
| "pointer/reference parameter", |
| makeRetainPtrSafetyModel()) {} |
| }; |
| |
| } // namespace |
| |
| void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { |
| Mgr.registerChecker<UncountedCallArgsChecker>(); |
| } |
| |
| bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) { |
| return true; |
| } |
| |
| void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) { |
| Mgr.registerChecker<UncheckedCallArgsChecker>(); |
| } |
| |
| bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) { |
| return true; |
| } |
| |
| void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) { |
| Mgr.registerChecker<UnretainedCallArgsChecker>(); |
| } |
| |
| bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) { |
| return true; |
| } |