167 lines · cpp
1//===----------------------------------------------------------------------===//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 "AvoidCArraysCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12#include "clang/ASTMatchers/ASTMatchers.h"13 14using namespace clang::ast_matchers;15 16namespace clang::tidy::modernize {17 18template <typename TargetType, typename NodeType>19static const TargetType *getAs(const NodeType *Node) {20 if constexpr (std::is_same_v<NodeType, clang::DynTypedNode>)21 return Node->template get<TargetType>();22 else23 return llvm::dyn_cast<TargetType>(Node);24}25 26namespace {27 28AST_MATCHER(clang::TypeLoc, hasValidBeginLoc) {29 return Node.getBeginLoc().isValid();30}31 32AST_MATCHER_P(clang::TypeLoc, hasType,33 clang::ast_matchers::internal::Matcher<clang::Type>,34 InnerMatcher) {35 const clang::Type *TypeNode = Node.getTypePtr();36 return TypeNode != nullptr &&37 InnerMatcher.matches(*TypeNode, Finder, Builder);38}39 40AST_MATCHER(clang::RecordDecl, isExternCContext) {41 return Node.isExternCContext();42}43 44AST_MATCHER(clang::ParmVarDecl, isArgvOfMain) {45 const clang::DeclContext *DC = Node.getDeclContext();46 const auto *FD = llvm::dyn_cast<clang::FunctionDecl>(DC);47 return FD ? FD->isMain() : false;48}49 50AST_MATCHER(clang::TypeLoc, isWithinImplicitTemplateInstantiation) {51 const auto IsImplicitTemplateInstantiation = [](const auto *Node) {52 const auto IsImplicitInstantiation = [](const auto *Node) {53 return (Node != nullptr) && (Node->getTemplateSpecializationKind() ==54 TSK_ImplicitInstantiation);55 };56 return (IsImplicitInstantiation(getAs<clang::CXXRecordDecl>(Node)) ||57 IsImplicitInstantiation(getAs<clang::FunctionDecl>(Node)) ||58 IsImplicitInstantiation(getAs<clang::VarDecl>(Node)));59 };60 61 DynTypedNodeList ParentNodes = Finder->getASTContext().getParents(Node);62 const clang::NamedDecl *ParentDecl = nullptr;63 while (!ParentNodes.empty()) {64 const DynTypedNode &ParentNode = ParentNodes[0];65 if (IsImplicitTemplateInstantiation(&ParentNode))66 return true;67 68 // in case of a `NamedDecl` as parent node, it is more efficient to proceed69 // with the upward traversal via DeclContexts (see below) instead of via70 // parent nodes71 if ((ParentDecl = ParentNode.template get<clang::NamedDecl>()))72 break;73 74 ParentNodes = Finder->getASTContext().getParents(ParentNode);75 }76 77 if (ParentDecl != nullptr) {78 const clang::DeclContext *DeclContext = ParentDecl->getDeclContext();79 while (DeclContext != nullptr) {80 if (IsImplicitTemplateInstantiation(DeclContext))81 return true;82 DeclContext = DeclContext->getParent();83 }84 }85 86 return false;87}88 89} // namespace90 91AvoidCArraysCheck::AvoidCArraysCheck(StringRef Name, ClangTidyContext *Context)92 : ClangTidyCheck(Name, Context),93 AllowStringArrays(Options.get("AllowStringArrays", false)) {}94 95void AvoidCArraysCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {96 Options.store(Opts, "AllowStringArrays", AllowStringArrays);97}98 99void AvoidCArraysCheck::registerMatchers(MatchFinder *Finder) {100 ast_matchers::internal::Matcher<TypeLoc> IgnoreStringArrayIfNeededMatcher =101 anything();102 if (AllowStringArrays)103 IgnoreStringArrayIfNeededMatcher =104 unless(typeLoc(loc(hasCanonicalType(incompleteArrayType(105 hasElementType(isAnyCharacter())))),106 hasParent(varDecl(hasInitializer(stringLiteral()),107 unless(parmVarDecl())))));108 109 Finder->addMatcher(110 typeLoc(hasValidBeginLoc(), hasType(arrayType()),111 optionally(hasParent(parmVarDecl().bind("param_decl"))),112 unless(anyOf(hasParent(parmVarDecl(isArgvOfMain())),113 hasParent(varDecl(isExternC())),114 hasParent(fieldDecl(115 hasParent(recordDecl(isExternCContext())))),116 hasAncestor(functionDecl(isExternC())),117 isWithinImplicitTemplateInstantiation())),118 IgnoreStringArrayIfNeededMatcher)119 .bind("typeloc"),120 this);121 122 Finder->addMatcher(123 templateArgumentLoc(hasTypeLoc(124 typeLoc(hasType(arrayType())).bind("template_arg_array_typeloc"))),125 this);126}127 128void AvoidCArraysCheck::check(const MatchFinder::MatchResult &Result) {129 TypeLoc ArrayTypeLoc{};130 131 if (const auto *MatchedTypeLoc = Result.Nodes.getNodeAs<TypeLoc>("typeloc"))132 ArrayTypeLoc = *MatchedTypeLoc;133 134 if (const auto *TemplateArgArrayTypeLoc =135 Result.Nodes.getNodeAs<TypeLoc>("template_arg_array_typeloc"))136 ArrayTypeLoc = *TemplateArgArrayTypeLoc;137 138 assert(!ArrayTypeLoc.isNull());139 140 const bool IsInParam =141 Result.Nodes.getNodeAs<ParmVarDecl>("param_decl") != nullptr;142 const bool IsVLA = ArrayTypeLoc.getTypePtr()->isVariableArrayType();143 enum class RecommendType { Array, Vector, Span };144 llvm::SmallVector<const char *> RecommendTypes{};145 if (IsVLA) {146 RecommendTypes.push_back("'std::vector'");147 } else if (ArrayTypeLoc.getTypePtr()->isIncompleteArrayType() && IsInParam) {148 // in function parameter, we also don't know the size of149 // IncompleteArrayType.150 if (Result.Context->getLangOpts().CPlusPlus20)151 RecommendTypes.push_back("'std::span'");152 else {153 RecommendTypes.push_back("'std::array'");154 RecommendTypes.push_back("'std::vector'");155 }156 } else {157 RecommendTypes.push_back("'std::array'");158 }159 160 diag(ArrayTypeLoc.getBeginLoc(),161 "do not declare %select{C-style|C VLA}0 arrays, use %1 instead")162 << IsVLA << llvm::join(RecommendTypes, " or ")163 << ArrayTypeLoc.getSourceRange();164}165 166} // namespace clang::tidy::modernize167