brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.4 KiB · d2760ca Raw
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