411 lines · cpp
1//=======- RawPtrRefMemberChecker.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 "DiagOutputUtils.h"10#include "PtrTypesSemantics.h"11#include "clang/AST/Decl.h"12#include "clang/AST/DeclCXX.h"13#include "clang/AST/DynamicRecursiveASTVisitor.h"14#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"15#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"16#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"17#include "clang/StaticAnalyzer/Core/Checker.h"18#include "llvm/Support/Casting.h"19#include <optional>20 21using namespace clang;22using namespace ento;23 24namespace {25 26class RawPtrRefMemberChecker27 : public Checker<check::ASTDecl<TranslationUnitDecl>> {28private:29 BugType Bug;30 mutable BugReporter *BR;31 mutable llvm::DenseSet<const ObjCIvarDecl *> IvarDeclsToIgnore;32 33protected:34 mutable std::optional<RetainTypeChecker> RTC;35 36public:37 RawPtrRefMemberChecker(const char *description)38 : Bug(this, description, "WebKit coding guidelines") {}39 40 virtual std::optional<bool> isUnsafePtr(QualType,41 bool ignoreARC = false) const = 0;42 virtual const char *typeName() const = 0;43 virtual const char *invariant() const = 0;44 45 void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,46 BugReporter &BRArg) const {47 BR = &BRArg;48 49 // The calls to checkAST* from AnalysisConsumer don't50 // visit template instantiations or lambda classes. We51 // want to visit those, so we make our own RecursiveASTVisitor.52 struct LocalVisitor : ConstDynamicRecursiveASTVisitor {53 const RawPtrRefMemberChecker *Checker;54 explicit LocalVisitor(const RawPtrRefMemberChecker *Checker)55 : Checker(Checker) {56 assert(Checker);57 ShouldVisitTemplateInstantiations = true;58 ShouldVisitImplicitCode = false;59 }60 61 bool VisitTypedefDecl(const TypedefDecl *TD) override {62 if (Checker->RTC)63 Checker->RTC->visitTypedef(TD);64 return true;65 }66 67 bool VisitRecordDecl(const RecordDecl *RD) override {68 Checker->visitRecordDecl(RD);69 return true;70 }71 72 bool VisitObjCContainerDecl(const ObjCContainerDecl *CD) override {73 Checker->visitObjCDecl(CD);74 return true;75 }76 };77 78 LocalVisitor visitor(this);79 if (RTC)80 RTC->visitTranslationUnitDecl(TUD);81 visitor.TraverseDecl(TUD);82 }83 84 void visitRecordDecl(const RecordDecl *RD) const {85 if (shouldSkipDecl(RD))86 return;87 88 for (auto *Member : RD->fields())89 visitMember(Member, RD);90 }91 92 void visitMember(const FieldDecl *Member, const RecordDecl *RD) const {93 auto QT = Member->getType();94 const Type *MemberType = QT.getTypePtrOrNull();95 96 while (MemberType) {97 auto IsUnsafePtr = isUnsafePtr(QT);98 if (IsUnsafePtr && *IsUnsafePtr)99 break;100 if (!MemberType->isPointerType())101 return;102 QT = MemberType->getPointeeType();103 MemberType = QT.getTypePtrOrNull();104 }105 106 if (!MemberType)107 return;108 109 if (auto *MemberCXXRD = MemberType->getPointeeCXXRecordDecl())110 reportBug(Member, MemberType, MemberCXXRD, RD);111 else if (auto *ObjCDecl = getObjCDecl(MemberType))112 reportBug(Member, MemberType, ObjCDecl, RD);113 }114 115 ObjCInterfaceDecl *getObjCDecl(const Type *TypePtr) const {116 auto *PointeeType = TypePtr->getPointeeType().getTypePtrOrNull();117 if (!PointeeType)118 return nullptr;119 auto *Desugared = PointeeType->getUnqualifiedDesugaredType();120 if (!Desugared)121 return nullptr;122 if (auto *ObjCType = dyn_cast<ObjCInterfaceType>(Desugared))123 return ObjCType->getDecl();124 if (auto *ObjCType = dyn_cast<ObjCObjectType>(Desugared))125 return ObjCType->getInterface();126 return nullptr;127 }128 129 void visitObjCDecl(const ObjCContainerDecl *CD) const {130 if (BR->getSourceManager().isInSystemHeader(CD->getLocation()))131 return;132 133 if (auto *ID = dyn_cast<ObjCImplementationDecl>(CD)) {134 ObjCContainerDecl::PropertyMap map;135 CD->collectPropertiesToImplement(map);136 for (auto it : map)137 visitObjCPropertyDecl(CD, it.second);138 139 if (auto *Interface = ID->getClassInterface()) {140 for (auto *Ivar : Interface->ivars())141 visitIvarDecl(CD, Ivar);142 }143 for (auto *PropImpl : ID->property_impls())144 visitPropImpl(CD, PropImpl);145 for (auto *Ivar : ID->ivars())146 visitIvarDecl(CD, Ivar);147 return;148 }149 }150 151 void visitIvarDecl(const ObjCContainerDecl *CD,152 const ObjCIvarDecl *Ivar) const {153 if (BR->getSourceManager().isInSystemHeader(Ivar->getLocation()))154 return;155 156 if (IvarDeclsToIgnore.contains(Ivar))157 return;158 159 auto QT = Ivar->getType();160 const Type *IvarType = QT.getTypePtrOrNull();161 if (!IvarType)162 return;163 164 auto IsUnsafePtr = isUnsafePtr(QT);165 if (!IsUnsafePtr || !*IsUnsafePtr)166 return;167 168 IvarDeclsToIgnore.insert(Ivar);169 170 if (auto *MemberCXXRD = IvarType->getPointeeCXXRecordDecl())171 reportBug(Ivar, IvarType, MemberCXXRD, CD);172 else if (auto *ObjCDecl = getObjCDecl(IvarType))173 reportBug(Ivar, IvarType, ObjCDecl, CD);174 }175 176 void visitObjCPropertyDecl(const ObjCContainerDecl *CD,177 const ObjCPropertyDecl *PD) const {178 if (BR->getSourceManager().isInSystemHeader(PD->getLocation()))179 return;180 181 if (const ObjCInterfaceDecl *ID = dyn_cast<ObjCInterfaceDecl>(CD)) {182 if (!RTC || !RTC->defaultSynthProperties() ||183 ID->isObjCRequiresPropertyDefs())184 return;185 }186 187 auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PD);188 if (!IsUnsafe)189 return;190 191 if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())192 reportBug(PD, PropType, MemberCXXRD, CD);193 else if (auto *ObjCDecl = getObjCDecl(PropType))194 reportBug(PD, PropType, ObjCDecl, CD);195 }196 197 void visitPropImpl(const ObjCContainerDecl *CD,198 const ObjCPropertyImplDecl *PID) const {199 if (BR->getSourceManager().isInSystemHeader(PID->getLocation()))200 return;201 202 if (PID->getPropertyImplementation() != ObjCPropertyImplDecl::Synthesize)203 return;204 205 auto *PropDecl = PID->getPropertyDecl();206 if (auto *IvarDecl = PID->getPropertyIvarDecl()) {207 if (IvarDeclsToIgnore.contains(IvarDecl))208 return;209 IvarDeclsToIgnore.insert(IvarDecl);210 }211 auto [IsUnsafe, PropType] = isPropImplUnsafePtr(PropDecl);212 if (!IsUnsafe)213 return;214 215 if (auto *MemberCXXRD = PropType->getPointeeCXXRecordDecl())216 reportBug(PropDecl, PropType, MemberCXXRD, CD);217 else if (auto *ObjCDecl = getObjCDecl(PropType))218 reportBug(PropDecl, PropType, ObjCDecl, CD);219 }220 221 std::pair<bool, const Type *>222 isPropImplUnsafePtr(const ObjCPropertyDecl *PD) const {223 if (!PD)224 return {false, nullptr};225 226 auto QT = PD->getType();227 const Type *PropType = QT.getTypePtrOrNull();228 if (!PropType)229 return {false, nullptr};230 231 // "assign" property doesn't retain even under ARC so treat it as unsafe.232 bool ignoreARC =233 !PD->isReadOnly() && PD->getSetterKind() == ObjCPropertyDecl::Assign;234 auto IsUnsafePtr = isUnsafePtr(QT, ignoreARC);235 return {IsUnsafePtr && *IsUnsafePtr && !PD->isRetaining(), PropType};236 }237 238 bool shouldSkipDecl(const RecordDecl *RD) const {239 if (!RD->isThisDeclarationADefinition())240 return true;241 242 if (RD->isImplicit())243 return true;244 245 if (RD->isLambda())246 return true;247 248 // If the construct doesn't have a source file, then it's not something249 // we want to diagnose.250 const auto RDLocation = RD->getLocation();251 if (!RDLocation.isValid())252 return true;253 254 const auto Kind = RD->getTagKind();255 if (Kind != TagTypeKind::Struct && Kind != TagTypeKind::Class &&256 Kind != TagTypeKind::Union)257 return true;258 259 // Ignore CXXRecords that come from system headers.260 if (BR->getSourceManager().isInSystemHeader(RDLocation))261 return true;262 263 // Ref-counted smartpointers actually have raw-pointer to uncounted type as264 // a member but we trust them to handle it correctly.265 auto CXXRD = llvm::dyn_cast_or_null<CXXRecordDecl>(RD);266 if (CXXRD && isSmartPtr(CXXRD))267 return true;268 269 return false;270 }271 272 template <typename DeclType, typename PointeeType, typename ParentDeclType>273 void reportBug(const DeclType *Member, const Type *MemberType,274 const PointeeType *Pointee,275 const ParentDeclType *ClassCXXRD) const {276 assert(Member);277 assert(MemberType);278 assert(Pointee);279 280 SmallString<100> Buf;281 llvm::raw_svector_ostream Os(Buf);282 283 if (isa<ObjCContainerDecl>(ClassCXXRD)) {284 if (isa<ObjCPropertyDecl>(Member))285 Os << "Property ";286 else287 Os << "Instance variable ";288 } else289 Os << "Member variable ";290 printQuotedName(Os, Member);291 Os << " in ";292 printQuotedQualifiedName(Os, ClassCXXRD);293 if (Member->getType().getTypePtrOrNull() == MemberType)294 Os << " is a ";295 else296 Os << " contains a ";297 if (printPointer(Os, MemberType) == PrintDeclKind::Pointer) {298 auto Typedef = MemberType->getAs<TypedefType>();299 assert(Typedef);300 printQuotedQualifiedName(Os, Typedef->getDecl());301 } else302 printQuotedQualifiedName(Os, Pointee);303 Os << "; " << invariant() << ".";304 305 PathDiagnosticLocation BSLoc(Member->getSourceRange().getBegin(),306 BR->getSourceManager());307 auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);308 Report->addRange(Member->getSourceRange());309 BR->emitReport(std::move(Report));310 }311 312 enum class PrintDeclKind { Pointee, Pointer };313 virtual PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,314 const Type *T) const {315 T = T->getUnqualifiedDesugaredType();316 bool IsPtr = isa<PointerType>(T) || isa<ObjCObjectPointerType>(T);317 Os << (IsPtr ? "raw pointer" : "reference") << " to " << typeName() << " ";318 return PrintDeclKind::Pointee;319 }320};321 322class NoUncountedMemberChecker final : public RawPtrRefMemberChecker {323public:324 NoUncountedMemberChecker()325 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "326 "reference-countable type") {}327 328 std::optional<bool> isUnsafePtr(QualType QT, bool) const final {329 return isUncountedPtr(QT.getCanonicalType());330 }331 332 const char *typeName() const final { return "ref-countable type"; }333 334 const char *invariant() const final {335 return "member variables must be Ref, RefPtr, WeakRef, or WeakPtr";336 }337};338 339class NoUncheckedPtrMemberChecker final : public RawPtrRefMemberChecker {340public:341 NoUncheckedPtrMemberChecker()342 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "343 "checked-pointer capable type") {}344 345 std::optional<bool> isUnsafePtr(QualType QT, bool) const final {346 return isUncheckedPtr(QT.getCanonicalType());347 }348 349 const char *typeName() const final { return "CheckedPtr capable type"; }350 351 const char *invariant() const final {352 return "member variables must be a CheckedPtr, CheckedRef, WeakRef, or "353 "WeakPtr";354 }355};356 357class NoUnretainedMemberChecker final : public RawPtrRefMemberChecker {358public:359 NoUnretainedMemberChecker()360 : RawPtrRefMemberChecker("Member variable is a raw-pointer/reference to "361 "retainable type") {362 RTC = RetainTypeChecker();363 }364 365 std::optional<bool> isUnsafePtr(QualType QT, bool ignoreARC) const final {366 return RTC->isUnretained(QT, ignoreARC);367 }368 369 const char *typeName() const final { return "retainable type"; }370 371 const char *invariant() const final {372 return "member variables must be a RetainPtr or OSObjectPtr";373 }374 375 PrintDeclKind printPointer(llvm::raw_svector_ostream &Os,376 const Type *T) const final {377 if (!isa<ObjCObjectPointerType>(T) && T->getAs<TypedefType>()) {378 Os << typeName() << " ";379 return PrintDeclKind::Pointer;380 }381 return RawPtrRefMemberChecker::printPointer(Os, T);382 }383};384 385} // namespace386 387void ento::registerNoUncountedMemberChecker(CheckerManager &Mgr) {388 Mgr.registerChecker<NoUncountedMemberChecker>();389}390 391bool ento::shouldRegisterNoUncountedMemberChecker(const CheckerManager &Mgr) {392 return true;393}394 395void ento::registerNoUncheckedPtrMemberChecker(CheckerManager &Mgr) {396 Mgr.registerChecker<NoUncheckedPtrMemberChecker>();397}398 399bool ento::shouldRegisterNoUncheckedPtrMemberChecker(400 const CheckerManager &Mgr) {401 return true;402}403 404void ento::registerNoUnretainedMemberChecker(CheckerManager &Mgr) {405 Mgr.registerChecker<NoUnretainedMemberChecker>();406}407 408bool ento::shouldRegisterNoUnretainedMemberChecker(const CheckerManager &Mgr) {409 return true;410}411