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