316 lines · cpp
1//=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- 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// This file defines a CheckNSError, a flow-insensitive check10// that determines if an Objective-C class interface correctly returns11// a non-void return type.12//13// File under feature request PR 2600.14//15//===----------------------------------------------------------------------===//16 17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"18#include "clang/AST/Decl.h"19#include "clang/AST/DeclObjC.h"20#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"21#include "clang/StaticAnalyzer/Core/Checker.h"22#include "clang/StaticAnalyzer/Core/CheckerManager.h"23#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"24#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"25#include "llvm/ADT/SmallString.h"26#include "llvm/Support/raw_ostream.h"27#include <optional>28 29using namespace clang;30using namespace ento;31 32static bool IsNSError(QualType T, IdentifierInfo *II);33static bool IsCFError(QualType T, IdentifierInfo *II);34 35//===----------------------------------------------------------------------===//36// NSErrorMethodChecker37//===----------------------------------------------------------------------===//38 39namespace {40class NSErrorMethodChecker41 : public Checker< check::ASTDecl<ObjCMethodDecl> > {42 mutable IdentifierInfo *II = nullptr;43 44public:45 NSErrorMethodChecker() = default;46 47 void checkASTDecl(const ObjCMethodDecl *D,48 AnalysisManager &mgr, BugReporter &BR) const;49};50}51 52void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D,53 AnalysisManager &mgr,54 BugReporter &BR) const {55 if (!D->isThisDeclarationADefinition())56 return;57 if (!D->getReturnType()->isVoidType())58 return;59 60 if (!II)61 II = &D->getASTContext().Idents.get("NSError");62 63 bool hasNSError = false;64 for (const auto *I : D->parameters()) {65 if (IsNSError(I->getType(), II)) {66 hasNSError = true;67 break;68 }69 }70 71 if (hasNSError) {72 const char *err = "Method accepting NSError** "73 "should have a non-void return value to indicate whether or not an "74 "error occurred";75 PathDiagnosticLocation L =76 PathDiagnosticLocation::create(D, BR.getSourceManager());77 BR.EmitBasicReport(D, this, "Bad return type when passing NSError**",78 "Coding conventions (Apple)", err, L);79 }80}81 82//===----------------------------------------------------------------------===//83// CFErrorFunctionChecker84//===----------------------------------------------------------------------===//85 86namespace {87class CFErrorFunctionChecker88 : public Checker< check::ASTDecl<FunctionDecl> > {89 mutable IdentifierInfo *II;90 91public:92 CFErrorFunctionChecker() : II(nullptr) {}93 94 void checkASTDecl(const FunctionDecl *D,95 AnalysisManager &mgr, BugReporter &BR) const;96};97}98 99static bool hasReservedReturnType(const FunctionDecl *D) {100 if (isa<CXXConstructorDecl>(D))101 return true;102 103 // operators delete and delete[] are required to have 'void' return type104 auto OperatorKind = D->getOverloadedOperator();105 return OperatorKind == OO_Delete || OperatorKind == OO_Array_Delete;106}107 108void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D,109 AnalysisManager &mgr,110 BugReporter &BR) const {111 if (!D->doesThisDeclarationHaveABody())112 return;113 if (!D->getReturnType()->isVoidType())114 return;115 if (hasReservedReturnType(D))116 return;117 118 if (!II)119 II = &D->getASTContext().Idents.get("CFErrorRef");120 121 bool hasCFError = false;122 for (auto *I : D->parameters()) {123 if (IsCFError(I->getType(), II)) {124 hasCFError = true;125 break;126 }127 }128 129 if (hasCFError) {130 const char *err = "Function accepting CFErrorRef* "131 "should have a non-void return value to indicate whether or not an "132 "error occurred";133 PathDiagnosticLocation L =134 PathDiagnosticLocation::create(D, BR.getSourceManager());135 BR.EmitBasicReport(D, this, "Bad return type when passing CFErrorRef*",136 "Coding conventions (Apple)", err, L);137 }138}139 140//===----------------------------------------------------------------------===//141// NSOrCFErrorDerefChecker142//===----------------------------------------------------------------------===//143 144namespace {145class NSOrCFErrorDerefChecker146 : public CheckerFamily<check::Location,147 check::Event<ImplicitNullDerefEvent>> {148 mutable IdentifierInfo *NSErrorII = nullptr;149 mutable IdentifierInfo *CFErrorII = nullptr;150 151public:152 CheckerFrontendWithBugType NSError{"NSError** null dereference",153 "Coding conventions (Apple)"};154 CheckerFrontendWithBugType CFError{"CFErrorRef* null dereference",155 "Coding conventions (Apple)"};156 157 StringRef getDebugTag() const override { return "NSOrCFErrorDerefChecker"; }158 159 void checkLocation(SVal loc, bool isLoad, const Stmt *S,160 CheckerContext &C) const;161 void checkEvent(ImplicitNullDerefEvent event) const;162};163}164 165typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag;166REGISTER_TRAIT_WITH_PROGRAMSTATE(NSErrorOut, ErrorOutFlag)167REGISTER_TRAIT_WITH_PROGRAMSTATE(CFErrorOut, ErrorOutFlag)168 169template <typename T>170static bool hasFlag(SVal val, ProgramStateRef state) {171 if (SymbolRef sym = val.getAsSymbol())172 if (const unsigned *attachedFlags = state->get<T>(sym))173 return *attachedFlags;174 return false;175}176 177template <typename T>178static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) {179 // We tag the symbol that the SVal wraps.180 if (SymbolRef sym = val.getAsSymbol())181 C.addTransition(state->set<T>(sym, true));182}183 184static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) {185 const StackFrameContext * SFC = C.getStackFrame();186 if (std::optional<loc::MemRegionVal> X = val.getAs<loc::MemRegionVal>()) {187 const MemRegion* R = X->getRegion();188 if (const VarRegion *VR = R->getAs<VarRegion>())189 if (const auto *StackSpace =190 VR->getMemorySpaceAs<StackArgumentsSpaceRegion>(C.getState()))191 if (StackSpace->getStackFrame() == SFC)192 return VR->getValueType();193 }194 195 return QualType();196}197 198void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad,199 const Stmt *S,200 CheckerContext &C) const {201 if (!isLoad)202 return;203 if (loc.isUndef() || !isa<Loc>(loc))204 return;205 206 ASTContext &Ctx = C.getASTContext();207 ProgramStateRef state = C.getState();208 209 // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting210 // SVal so that we can later check it when handling the211 // ImplicitNullDerefEvent event.212 // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of213 // function ?214 215 QualType parmT = parameterTypeFromSVal(loc, C);216 if (parmT.isNull())217 return;218 219 if (!NSErrorII)220 NSErrorII = &Ctx.Idents.get("NSError");221 if (!CFErrorII)222 CFErrorII = &Ctx.Idents.get("CFErrorRef");223 224 if (NSError.isEnabled() && IsNSError(parmT, NSErrorII)) {225 setFlag<NSErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C);226 return;227 }228 229 if (CFError.isEnabled() && IsCFError(parmT, CFErrorII)) {230 setFlag<CFErrorOut>(state, state->getSVal(loc.castAs<Loc>()), C);231 return;232 }233}234 235void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const {236 if (event.IsLoad)237 return;238 239 SVal loc = event.Location;240 ProgramStateRef state = event.SinkNode->getState();241 BugReporter &BR = *event.BR;242 243 bool isNSError = hasFlag<NSErrorOut>(loc, state);244 bool isCFError = false;245 if (!isNSError)246 isCFError = hasFlag<CFErrorOut>(loc, state);247 248 if (!(isNSError || isCFError))249 return;250 251 // Storing to possible null NSError/CFErrorRef out parameter.252 SmallString<128> Buf;253 llvm::raw_svector_ostream os(Buf);254 255 os << "Potential null dereference. According to coding standards ";256 os << (isNSError257 ? "in 'Creating and Returning NSError Objects' the parameter"258 : "documented in CoreFoundation/CFError.h the parameter");259 260 os << " may be null";261 262 const BugType &BT = isNSError ? NSError : CFError;263 BR.emitReport(264 std::make_unique<PathSensitiveBugReport>(BT, os.str(), event.SinkNode));265}266 267static bool IsNSError(QualType T, IdentifierInfo *II) {268 269 const PointerType* PPT = T->getAs<PointerType>();270 if (!PPT)271 return false;272 273 const ObjCObjectPointerType* PT =274 PPT->getPointeeType()->getAs<ObjCObjectPointerType>();275 276 if (!PT)277 return false;278 279 const ObjCInterfaceDecl *ID = PT->getInterfaceDecl();280 281 // FIXME: Can ID ever be NULL?282 if (ID)283 return II == ID->getIdentifier();284 285 return false;286}287 288static bool IsCFError(QualType T, IdentifierInfo *II) {289 const PointerType* PPT = T->getAs<PointerType>();290 if (!PPT) return false;291 292 const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>();293 if (!TT) return false;294 295 return TT->getDecl()->getIdentifier() == II;296}297 298// This source file implements two user-facing checkers ("osx.cocoa.NSError"299// and "osx.coreFoundation.CFError") which are both implemented as the300// combination of two `CheckerFrontend`s that are registered under the same301// name (but otherwise act independently). Among these 2+2 `CheckerFrontend`s302// two are coming from the checker family `NSOrCFErrorDerefChecker` while the303// other two (the `ADDITIONAL_PART`s) are small standalone checkers.304#define REGISTER_CHECKER(NAME, ADDITIONAL_PART) \305 void ento::register##NAME##Checker(CheckerManager &Mgr) { \306 Mgr.getChecker<NSOrCFErrorDerefChecker>()->NAME.enable(Mgr); \307 Mgr.registerChecker<ADDITIONAL_PART>(); \308 } \309 \310 bool ento::shouldRegister##NAME##Checker(const CheckerManager &) { \311 return true; \312 }313 314REGISTER_CHECKER(NSError, NSErrorMethodChecker)315REGISTER_CHECKER(CFError, CFErrorFunctionChecker)316