brintos

brintos / llvm-project-archived public Read only

0
0
Text · 10.3 KiB · c5dad61 Raw
304 lines · cpp
1//===- StdVariantChecker.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 "clang/AST/Type.h"10#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"11#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"12#include "clang/StaticAnalyzer/Core/Checker.h"13#include "clang/StaticAnalyzer/Core/CheckerManager.h"14#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"15#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"16#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"17#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"18#include "llvm/ADT/FoldingSet.h"19#include "llvm/ADT/StringRef.h"20#include <optional>21 22#include "TaggedUnionModeling.h"23 24using namespace clang;25using namespace ento;26using namespace tagged_union_modeling;27 28REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType)29 30namespace clang::ento::tagged_union_modeling {31 32static const CXXConstructorDecl *33getConstructorDeclarationForCall(const CallEvent &Call) {34  const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call);35  if (!ConstructorCall)36    return nullptr;37 38  return ConstructorCall->getDecl();39}40 41bool isCopyConstructorCall(const CallEvent &Call) {42  if (const CXXConstructorDecl *ConstructorDecl =43          getConstructorDeclarationForCall(Call))44    return ConstructorDecl->isCopyConstructor();45  return false;46}47 48bool isCopyAssignmentCall(const CallEvent &Call) {49  const Decl *CopyAssignmentDecl = Call.getDecl();50 51  if (const auto *AsMethodDecl =52          dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl))53    return AsMethodDecl->isCopyAssignmentOperator();54  return false;55}56 57bool isMoveConstructorCall(const CallEvent &Call) {58  const CXXConstructorDecl *ConstructorDecl =59      getConstructorDeclarationForCall(Call);60  if (!ConstructorDecl)61    return false;62 63  return ConstructorDecl->isMoveConstructor();64}65 66bool isMoveAssignmentCall(const CallEvent &Call) {67  const Decl *CopyAssignmentDecl = Call.getDecl();68 69  const auto *AsMethodDecl =70      dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl);71  if (!AsMethodDecl)72    return false;73 74  return AsMethodDecl->isMoveAssignmentOperator();75}76 77static bool isStdType(const Type *Type, llvm::StringRef TypeName) {78  auto *Decl = Type->getAsRecordDecl();79  if (!Decl)80    return false;81  return (Decl->getName() == TypeName) && Decl->isInStdNamespace();82}83 84bool isStdVariant(const Type *Type) {85  return isStdType(Type, llvm::StringLiteral("variant"));86}87 88} // end of namespace clang::ento::tagged_union_modeling89 90static std::optional<ArrayRef<TemplateArgument>>91getTemplateArgsFromVariant(const Type *VariantType) {92  const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>();93  while (TempSpecType && TempSpecType->isTypeAlias())94    TempSpecType =95        TempSpecType->getAliasedType()->getAs<TemplateSpecializationType>();96  if (!TempSpecType)97    return {};98 99  return TempSpecType->template_arguments();100}101 102static std::optional<QualType>103getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) {104  std::optional<ArrayRef<TemplateArgument>> VariantTemplates =105      getTemplateArgsFromVariant(varType);106  if (!VariantTemplates)107    return {};108 109  return (*VariantTemplates)[i].getAsType();110}111 112static bool isVowel(char a) {113  switch (a) {114  case 'a':115  case 'e':116  case 'i':117  case 'o':118  case 'u':119    return true;120  default:121    return false;122  }123}124 125static llvm::StringRef indefiniteArticleBasedOnVowel(char a) {126  if (isVowel(a))127    return "an";128  return "a";129}130 131class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> {132  // Call descriptors to find relevant calls133  CallDescription VariantConstructor{CDM::CXXMethod,134                                     {"std", "variant", "variant"}};135  CallDescription VariantAssignmentOperator{CDM::CXXMethod,136                                            {"std", "variant", "operator="}};137  CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1};138 139  BugType BadVariantType{this, "BadVariantType", "BadVariantType"};140 141public:142  ProgramStateRef checkRegionChanges(ProgramStateRef State,143                                     const InvalidatedSymbols *,144                                     ArrayRef<const MemRegion *>,145                                     ArrayRef<const MemRegion *> Regions,146                                     const LocationContext *,147                                     const CallEvent *Call) const {148    if (!Call)149      return State;150 151    return removeInformationStoredForDeadInstances<VariantHeldTypeMap>(152        *Call, State, Regions);153  }154 155  bool evalCall(const CallEvent &Call, CheckerContext &C) const {156    // Check if the call was not made from a system header. If it was then157    // we do an early return because it is part of the implementation.158    if (Call.isCalledFromSystemHeader())159      return false;160 161    if (StdGet.matches(Call))162      return handleStdGetCall(Call, C);163 164    // First check if a constructor call is happening. If it is a165    // constructor call, check if it is an std::variant constructor call.166    bool IsVariantConstructor =167        isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call);168    bool IsVariantAssignmentOperatorCall =169        isa<CXXMemberOperatorCall>(Call) &&170        VariantAssignmentOperator.matches(Call);171 172    if (IsVariantConstructor || IsVariantAssignmentOperatorCall) {173      if (Call.getNumArgs() == 0 && IsVariantConstructor) {174        handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C);175        return true;176      }177 178      // FIXME Later this checker should be extended to handle constructors179      // with multiple arguments.180      if (Call.getNumArgs() != 1)181        return false;182 183      SVal ThisSVal;184      if (IsVariantConstructor) {185        const auto &AsConstructorCall = cast<CXXConstructorCall>(Call);186        ThisSVal = AsConstructorCall.getCXXThisVal();187      } else if (IsVariantAssignmentOperatorCall) {188        const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call);189        ThisSVal = AsMemberOpCall.getCXXThisVal();190      } else {191        return false;192      }193 194      handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal);195      return true;196    }197    return false;198  }199 200private:201  // The default constructed std::variant must be handled separately202  // by default the std::variant is going to hold a default constructed instance203  // of the first type of the possible types204  void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall,205                                CheckerContext &C) const {206    SVal ThisSVal = ConstructorCall->getCXXThisVal();207 208    const auto *const ThisMemRegion = ThisSVal.getAsRegion();209    if (!ThisMemRegion)210      return;211 212    std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant(213        ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0);214    if (!DefaultType)215      return;216 217    ProgramStateRef State = C.getState();218    State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType);219    C.addTransition(State);220  }221 222  bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const {223    ProgramStateRef State = C.getState();224 225    SVal ArgSVal = Call.getArgSVal(0);226    if (ArgSVal.isUnknown())227      return false;228 229    const auto &ArgType =230        ArgSVal.getType(C.getASTContext())->getPointeeType().getTypePtr();231    // We have to make sure that the argument is an std::variant.232    // There is another std::get with std::pair argument233    if (!isStdVariant(ArgType))234      return false;235 236    // Get the mem region of the argument std::variant and look up the type237    // information that we know about it.238    const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion();239    const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion);240    if (!StoredType)241      return false;242 243    const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr());244    const FunctionDecl *FD = CE->getDirectCallee();245    if (FD->getTemplateSpecializationArgs()->size() < 1)246      return false;247 248    const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0];249    // std::get's first template parameter can be the type we want to get250    // out of the std::variant or a natural number which is the position of251    // the requested type in the argument type list of the std::variant's252    // argument.253    QualType RetrievedType;254    switch (TypeOut.getKind()) {255    case TemplateArgument::ArgKind::Type:256      RetrievedType = TypeOut.getAsType();257      break;258    case TemplateArgument::ArgKind::Integral:259      // In the natural number case we look up which type corresponds to the260      // number.261      if (std::optional<QualType> NthTemplate =262              getNthTemplateTypeArgFromVariant(263                  ArgType, TypeOut.getAsIntegral().getSExtValue())) {264        RetrievedType = *NthTemplate;265        break;266      }267      [[fallthrough]];268    default:269      return false;270    }271 272    QualType RetrievedCanonicalType = RetrievedType.getCanonicalType();273    QualType StoredCanonicalType = StoredType->getCanonicalType();274    if (RetrievedCanonicalType == StoredCanonicalType)275      return true;276 277    ExplodedNode *ErrNode = C.generateNonFatalErrorNode();278    if (!ErrNode)279      return false;280    llvm::SmallString<128> Str;281    llvm::raw_svector_ostream OS(Str);282    std::string StoredTypeName = StoredType->getAsString();283    std::string RetrievedTypeName = RetrievedType.getAsString();284    OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held "285       << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'"286       << StoredTypeName << "\', not "287       << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'"288       << RetrievedTypeName << "\'";289    auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(),290                                                      ErrNode);291    C.emitReport(std::move(R));292    return true;293  }294};295 296bool clang::ento::shouldRegisterStdVariantChecker(297    clang::ento::CheckerManager const &mgr) {298  return true;299}300 301void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) {302  mgr.registerChecker<StdVariantChecker>();303}304