205 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 "UseDesignatedInitializersCheck.h"10#include "../utils/DesignatedInitializers.h"11#include "clang/AST/APValue.h"12#include "clang/AST/Decl.h"13#include "clang/AST/Expr.h"14#include "clang/AST/Stmt.h"15#include "clang/ASTMatchers/ASTMatchFinder.h"16#include "clang/ASTMatchers/ASTMatchers.h"17#include "clang/ASTMatchers/ASTMatchersMacros.h"18#include "clang/Basic/Diagnostic.h"19#include "clang/Lex/Lexer.h"20 21using namespace clang::ast_matchers;22 23namespace clang::tidy::modernize {24 25static constexpr char IgnoreSingleElementAggregatesName[] =26 "IgnoreSingleElementAggregates";27static constexpr bool IgnoreSingleElementAggregatesDefault = true;28 29static constexpr char RestrictToPODTypesName[] = "RestrictToPODTypes";30static constexpr bool RestrictToPODTypesDefault = false;31 32static constexpr char IgnoreMacrosName[] = "IgnoreMacros";33static constexpr bool IgnoreMacrosDefault = true;34 35static constexpr char StrictCStandardComplianceName[] =36 "StrictCStandardCompliance";37static constexpr bool StrictCStandardComplianceDefault = true;38 39static constexpr char StrictCppStandardComplianceName[] =40 "StrictCppStandardCompliance";41static constexpr bool StrictCppStandardComplianceDefault = true;42 43static unsigned getNumberOfDesignated(const InitListExpr *SyntacticInitList) {44 return llvm::count_if(*SyntacticInitList, [](auto *InitExpr) {45 return isa<DesignatedInitExpr>(InitExpr);46 });47}48 49namespace {50 51struct Designators {52 Designators(const InitListExpr *InitList) : InitList(InitList) {53 assert(InitList->isSyntacticForm());54 };55 56 unsigned size() { return getCached().size(); }57 58 std::optional<llvm::StringRef> operator[](const SourceLocation &Location) {59 const auto &Designators = getCached();60 const auto Result = Designators.find(Location);61 if (Result == Designators.end())62 return {};63 const llvm::StringRef Designator = Result->getSecond();64 return (Designator.front() == '.' ? Designator.substr(1) : Designator)65 .trim("\0"); // Trim NULL characters appearing on Windows in the66 // name.67 }68 69private:70 using LocationToNameMap = llvm::DenseMap<clang::SourceLocation, std::string>;71 72 std::optional<LocationToNameMap> CachedDesignators;73 const InitListExpr *InitList;74 75 LocationToNameMap &getCached() {76 return CachedDesignators ? *CachedDesignators77 : CachedDesignators.emplace(78 utils::getUnwrittenDesignators(InitList));79 }80};81 82AST_MATCHER(CXXRecordDecl, isAggregate) {83 return Node.hasDefinition() && Node.isAggregate();84}85 86AST_MATCHER(CXXRecordDecl, isPOD) {87 return Node.hasDefinition() && Node.isPOD();88}89 90AST_MATCHER(InitListExpr, isFullyDesignated) {91 if (const InitListExpr *SyntacticForm =92 Node.isSyntacticForm() ? &Node : Node.getSyntacticForm())93 return getNumberOfDesignated(SyntacticForm) == SyntacticForm->getNumInits();94 return true;95}96 97AST_MATCHER(InitListExpr, hasMoreThanOneElement) {98 return Node.getNumInits() > 1;99}100 101} // namespace102 103UseDesignatedInitializersCheck::UseDesignatedInitializersCheck(104 StringRef Name, ClangTidyContext *Context)105 : ClangTidyCheck(Name, Context), IgnoreSingleElementAggregates(Options.get(106 IgnoreSingleElementAggregatesName,107 IgnoreSingleElementAggregatesDefault)),108 RestrictToPODTypes(109 Options.get(RestrictToPODTypesName, RestrictToPODTypesDefault)),110 IgnoreMacros(Options.get(IgnoreMacrosName, IgnoreMacrosDefault)),111 StrictCStandardCompliance(Options.get(StrictCStandardComplianceName,112 StrictCStandardComplianceDefault)),113 StrictCppStandardCompliance(114 Options.get(StrictCppStandardComplianceName,115 StrictCppStandardComplianceDefault)) {}116 117void UseDesignatedInitializersCheck::registerMatchers(MatchFinder *Finder) {118 const auto HasBaseWithFields =119 hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl()))));120 Finder->addMatcher(121 initListExpr(122 hasType(hasUnqualifiedDesugaredType(recordType(hasDeclaration(123 cxxRecordDecl(124 RestrictToPODTypes ? isPOD() : isAggregate(),125 unless(anyOf(HasBaseWithFields, hasName("::std::array"))))126 .bind("type"))))),127 IgnoreSingleElementAggregates ? hasMoreThanOneElement() : anything(),128 unless(isFullyDesignated()))129 .bind("init"),130 this);131}132 133void UseDesignatedInitializersCheck::check(134 const MatchFinder::MatchResult &Result) {135 const auto *InitList = Result.Nodes.getNodeAs<InitListExpr>("init");136 const auto *Type = Result.Nodes.getNodeAs<CXXRecordDecl>("type");137 if (!Type || !InitList)138 return;139 const auto *SyntacticInitList = InitList->getSyntacticForm();140 if (!SyntacticInitList)141 return;142 Designators Designators{SyntacticInitList};143 const unsigned NumberOfDesignated = getNumberOfDesignated(SyntacticInitList);144 if (SyntacticInitList->getNumInits() - NumberOfDesignated >145 Designators.size())146 return;147 148 // If the whole initializer list is un-designated, issue only one warning and149 // a single fix-it for the whole expression.150 if (0 == NumberOfDesignated) {151 if (IgnoreMacros && InitList->getBeginLoc().isMacroID())152 return;153 {154 const DiagnosticBuilder Diag =155 diag(InitList->getLBraceLoc(),156 "use designated initializer list to initialize %0");157 Diag << InitList->getType() << InitList->getSourceRange();158 for (const Stmt *InitExpr : *SyntacticInitList) {159 const auto Designator = Designators[InitExpr->getBeginLoc()];160 if (Designator && !Designator->empty())161 Diag << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),162 ("." + *Designator + "=").str());163 }164 }165 diag(Type->getBeginLoc(), "aggregate type is defined here",166 DiagnosticIDs::Note);167 return;168 }169 170 // In case that only a few elements are un-designated (not all as before), the171 // check offers dedicated issues and fix-its for each of them.172 for (const auto *InitExpr : *SyntacticInitList) {173 if (isa<DesignatedInitExpr>(InitExpr))174 continue;175 if (IgnoreMacros && InitExpr->getBeginLoc().isMacroID())176 continue;177 const auto Designator = Designators[InitExpr->getBeginLoc()];178 if (!Designator || Designator->empty()) {179 // There should always be a designator. If there's unexpectedly none, we180 // at least report a generic diagnostic.181 diag(InitExpr->getBeginLoc(), "use designated init expression")182 << InitExpr->getSourceRange();183 } else {184 diag(InitExpr->getBeginLoc(),185 "use designated init expression to initialize field '%0'")186 << InitExpr->getSourceRange() << *Designator187 << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),188 ("." + *Designator + "=").str());189 }190 }191}192 193void UseDesignatedInitializersCheck::storeOptions(194 ClangTidyOptions::OptionMap &Opts) {195 Options.store(Opts, IgnoreSingleElementAggregatesName,196 IgnoreSingleElementAggregates);197 Options.store(Opts, RestrictToPODTypesName, RestrictToPODTypes);198 Options.store(Opts, IgnoreMacrosName, IgnoreMacros);199 Options.store(Opts, StrictCStandardComplianceName, StrictCStandardCompliance);200 Options.store(Opts, StrictCppStandardComplianceName,201 StrictCppStandardCompliance);202}203 204} // namespace clang::tidy::modernize205