diff options
Diffstat (limited to 'contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp')
| -rw-r--r-- | contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp | 300 | 
1 files changed, 300 insertions, 0 deletions
| diff --git a/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp new file mode 100644 index 000000000000..19877964bd90 --- /dev/null +++ b/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/StdVariantChecker.cpp @@ -0,0 +1,300 @@ +//===- StdVariantChecker.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 "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include <optional> +#include <string_view> + +#include "TaggedUnionModeling.h" + +using namespace clang; +using namespace ento; +using namespace tagged_union_modeling; + +REGISTER_MAP_WITH_PROGRAMSTATE(VariantHeldTypeMap, const MemRegion *, QualType) + +namespace clang::ento::tagged_union_modeling { + +const CXXConstructorDecl * +getConstructorDeclarationForCall(const CallEvent &Call) { +  const auto *ConstructorCall = dyn_cast<CXXConstructorCall>(&Call); +  if (!ConstructorCall) +    return nullptr; + +  return ConstructorCall->getDecl(); +} + +bool isCopyConstructorCall(const CallEvent &Call) { +  if (const CXXConstructorDecl *ConstructorDecl = +          getConstructorDeclarationForCall(Call)) +    return ConstructorDecl->isCopyConstructor(); +  return false; +} + +bool isCopyAssignmentCall(const CallEvent &Call) { +  const Decl *CopyAssignmentDecl = Call.getDecl(); + +  if (const auto *AsMethodDecl = +          dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl)) +    return AsMethodDecl->isCopyAssignmentOperator(); +  return false; +} + +bool isMoveConstructorCall(const CallEvent &Call) { +  const CXXConstructorDecl *ConstructorDecl = +      getConstructorDeclarationForCall(Call); +  if (!ConstructorDecl) +    return false; + +  return ConstructorDecl->isMoveConstructor(); +} + +bool isMoveAssignmentCall(const CallEvent &Call) { +  const Decl *CopyAssignmentDecl = Call.getDecl(); + +  const auto *AsMethodDecl = +      dyn_cast_or_null<CXXMethodDecl>(CopyAssignmentDecl); +  if (!AsMethodDecl) +    return false; + +  return AsMethodDecl->isMoveAssignmentOperator(); +} + +bool isStdType(const Type *Type, llvm::StringRef TypeName) { +  auto *Decl = Type->getAsRecordDecl(); +  if (!Decl) +    return false; +  return (Decl->getName() == TypeName) && Decl->isInStdNamespace(); +} + +bool isStdVariant(const Type *Type) { +  return isStdType(Type, llvm::StringLiteral("variant")); +} + +} // end of namespace clang::ento::tagged_union_modeling + +static std::optional<ArrayRef<TemplateArgument>> +getTemplateArgsFromVariant(const Type *VariantType) { +  const auto *TempSpecType = VariantType->getAs<TemplateSpecializationType>(); +  if (!TempSpecType) +    return {}; + +  return TempSpecType->template_arguments(); +} + +static std::optional<QualType> +getNthTemplateTypeArgFromVariant(const Type *varType, unsigned i) { +  std::optional<ArrayRef<TemplateArgument>> VariantTemplates = +      getTemplateArgsFromVariant(varType); +  if (!VariantTemplates) +    return {}; + +  return (*VariantTemplates)[i].getAsType(); +} + +static bool isVowel(char a) { +  switch (a) { +  case 'a': +  case 'e': +  case 'i': +  case 'o': +  case 'u': +    return true; +  default: +    return false; +  } +} + +static llvm::StringRef indefiniteArticleBasedOnVowel(char a) { +  if (isVowel(a)) +    return "an"; +  return "a"; +} + +class StdVariantChecker : public Checker<eval::Call, check::RegionChanges> { +  // Call descriptors to find relevant calls +  CallDescription VariantConstructor{CDM::CXXMethod, +                                     {"std", "variant", "variant"}}; +  CallDescription VariantAssignmentOperator{CDM::CXXMethod, +                                            {"std", "variant", "operator="}}; +  CallDescription StdGet{CDM::SimpleFunc, {"std", "get"}, 1, 1}; + +  BugType BadVariantType{this, "BadVariantType", "BadVariantType"}; + +public: +  ProgramStateRef checkRegionChanges(ProgramStateRef State, +                                     const InvalidatedSymbols *, +                                     ArrayRef<const MemRegion *>, +                                     ArrayRef<const MemRegion *> Regions, +                                     const LocationContext *, +                                     const CallEvent *Call) const { +    if (!Call) +      return State; + +    return removeInformationStoredForDeadInstances<VariantHeldTypeMap>( +        *Call, State, Regions); +  } + +  bool evalCall(const CallEvent &Call, CheckerContext &C) const { +    // Check if the call was not made from a system header. If it was then +    // we do an early return because it is part of the implementation. +    if (Call.isCalledFromSystemHeader()) +      return false; + +    if (StdGet.matches(Call)) +      return handleStdGetCall(Call, C); + +    // First check if a constructor call is happening. If it is a +    // constructor call, check if it is an std::variant constructor call. +    bool IsVariantConstructor = +        isa<CXXConstructorCall>(Call) && VariantConstructor.matches(Call); +    bool IsVariantAssignmentOperatorCall = +        isa<CXXMemberOperatorCall>(Call) && +        VariantAssignmentOperator.matches(Call); + +    if (IsVariantConstructor || IsVariantAssignmentOperatorCall) { +      if (Call.getNumArgs() == 0 && IsVariantConstructor) { +        handleDefaultConstructor(cast<CXXConstructorCall>(&Call), C); +        return true; +      } + +      // FIXME Later this checker should be extended to handle constructors +      // with multiple arguments. +      if (Call.getNumArgs() != 1) +        return false; + +      SVal ThisSVal; +      if (IsVariantConstructor) { +        const auto &AsConstructorCall = cast<CXXConstructorCall>(Call); +        ThisSVal = AsConstructorCall.getCXXThisVal(); +      } else if (IsVariantAssignmentOperatorCall) { +        const auto &AsMemberOpCall = cast<CXXMemberOperatorCall>(Call); +        ThisSVal = AsMemberOpCall.getCXXThisVal(); +      } else { +        return false; +      } + +      handleConstructorAndAssignment<VariantHeldTypeMap>(Call, C, ThisSVal); +      return true; +    } +    return false; +  } + +private: +  // The default constructed std::variant must be handled separately +  // by default the std::variant is going to hold a default constructed instance +  // of the first type of the possible types +  void handleDefaultConstructor(const CXXConstructorCall *ConstructorCall, +                                CheckerContext &C) const { +    SVal ThisSVal = ConstructorCall->getCXXThisVal(); + +    const auto *const ThisMemRegion = ThisSVal.getAsRegion(); +    if (!ThisMemRegion) +      return; + +    std::optional<QualType> DefaultType = getNthTemplateTypeArgFromVariant( +        ThisSVal.getType(C.getASTContext())->getPointeeType().getTypePtr(), 0); +    if (!DefaultType) +      return; + +    ProgramStateRef State = ConstructorCall->getState(); +    State = State->set<VariantHeldTypeMap>(ThisMemRegion, *DefaultType); +    C.addTransition(State); +  } + +  bool handleStdGetCall(const CallEvent &Call, CheckerContext &C) const { +    ProgramStateRef State = Call.getState(); + +    const auto &ArgType = Call.getArgSVal(0) +                              .getType(C.getASTContext()) +                              ->getPointeeType() +                              .getTypePtr(); +    // We have to make sure that the argument is an std::variant. +    // There is another std::get with std::pair argument +    if (!isStdVariant(ArgType)) +      return false; + +    // Get the mem region of the argument std::variant and look up the type +    // information that we know about it. +    const MemRegion *ArgMemRegion = Call.getArgSVal(0).getAsRegion(); +    const QualType *StoredType = State->get<VariantHeldTypeMap>(ArgMemRegion); +    if (!StoredType) +      return false; + +    const CallExpr *CE = cast<CallExpr>(Call.getOriginExpr()); +    const FunctionDecl *FD = CE->getDirectCallee(); +    if (FD->getTemplateSpecializationArgs()->size() < 1) +      return false; + +    const auto &TypeOut = FD->getTemplateSpecializationArgs()->asArray()[0]; +    // std::get's first template parameter can be the type we want to get +    // out of the std::variant or a natural number which is the position of +    // the requested type in the argument type list of the std::variant's +    // argument. +    QualType RetrievedType; +    switch (TypeOut.getKind()) { +    case TemplateArgument::ArgKind::Type: +      RetrievedType = TypeOut.getAsType(); +      break; +    case TemplateArgument::ArgKind::Integral: +      // In the natural number case we look up which type corresponds to the +      // number. +      if (std::optional<QualType> NthTemplate = +              getNthTemplateTypeArgFromVariant( +                  ArgType, TypeOut.getAsIntegral().getSExtValue())) { +        RetrievedType = *NthTemplate; +        break; +      } +      [[fallthrough]]; +    default: +      return false; +    } + +    QualType RetrievedCanonicalType = RetrievedType.getCanonicalType(); +    QualType StoredCanonicalType = StoredType->getCanonicalType(); +    if (RetrievedCanonicalType == StoredCanonicalType) +      return true; + +    ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); +    if (!ErrNode) +      return false; +    llvm::SmallString<128> Str; +    llvm::raw_svector_ostream OS(Str); +    std::string StoredTypeName = StoredType->getAsString(); +    std::string RetrievedTypeName = RetrievedType.getAsString(); +    OS << "std::variant " << ArgMemRegion->getDescriptiveName() << " held " +       << indefiniteArticleBasedOnVowel(StoredTypeName[0]) << " \'" +       << StoredTypeName << "\', not " +       << indefiniteArticleBasedOnVowel(RetrievedTypeName[0]) << " \'" +       << RetrievedTypeName << "\'"; +    auto R = std::make_unique<PathSensitiveBugReport>(BadVariantType, OS.str(), +                                                      ErrNode); +    C.emitReport(std::move(R)); +    return true; +  } +}; + +bool clang::ento::shouldRegisterStdVariantChecker( +    clang::ento::CheckerManager const &mgr) { +  return true; +} + +void clang::ento::registerStdVariantChecker(clang::ento::CheckerManager &mgr) { +  mgr.registerChecker<StdVariantChecker>(); +} | 
