1 //===--- UseDesignatedInitializersCheck.cpp - clang-tidy ------------------===// 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-exception 6 // 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 21 using namespace clang::ast_matchers; 22 23 namespace clang::tidy::modernize { 24 25 static constexpr char IgnoreSingleElementAggregatesName[] = 26 "IgnoreSingleElementAggregates"; 27 static constexpr bool IgnoreSingleElementAggregatesDefault = true; 28 29 static constexpr char RestrictToPODTypesName[] = "RestrictToPODTypes"; 30 static constexpr bool RestrictToPODTypesDefault = false; 31 32 static constexpr char IgnoreMacrosName[] = "IgnoreMacros"; 33 static constexpr bool IgnoreMacrosDefault = true; 34 35 static constexpr char StrictCStandardComplianceName[] = 36 "StrictCStandardCompliance"; 37 static constexpr bool StrictCStandardComplianceDefault = true; 38 39 static constexpr char StrictCppStandardComplianceName[] = 40 "StrictCppStandardCompliance"; 41 static constexpr bool StrictCppStandardComplianceDefault = true; 42 43 namespace { 44 45 struct Designators { 46 47 Designators(const InitListExpr *InitList) : InitList(InitList) { 48 assert(InitList->isSyntacticForm()); 49 }; 50 51 unsigned size() { return getCached().size(); } 52 53 std::optional<llvm::StringRef> operator[](const SourceLocation &Location) { 54 const auto &Designators = getCached(); 55 const auto Result = Designators.find(Location); 56 if (Result == Designators.end()) 57 return {}; 58 const llvm::StringRef Designator = Result->getSecond(); 59 return (Designator.front() == '.' ? Designator.substr(1) : Designator) 60 .trim("\0"); // Trim NULL characters appearing on Windows in the 61 // name. 62 } 63 64 private: 65 using LocationToNameMap = llvm::DenseMap<clang::SourceLocation, std::string>; 66 67 std::optional<LocationToNameMap> CachedDesignators; 68 const InitListExpr *InitList; 69 70 LocationToNameMap &getCached() { 71 return CachedDesignators ? *CachedDesignators 72 : CachedDesignators.emplace( 73 utils::getUnwrittenDesignators(InitList)); 74 } 75 }; 76 77 unsigned getNumberOfDesignated(const InitListExpr *SyntacticInitList) { 78 return llvm::count_if(*SyntacticInitList, [](auto *InitExpr) { 79 return isa<DesignatedInitExpr>(InitExpr); 80 }); 81 } 82 83 AST_MATCHER(CXXRecordDecl, isAggregate) { 84 return Node.hasDefinition() && Node.isAggregate(); 85 } 86 87 AST_MATCHER(CXXRecordDecl, isPOD) { 88 return Node.hasDefinition() && Node.isPOD(); 89 } 90 91 AST_MATCHER(InitListExpr, isFullyDesignated) { 92 if (const InitListExpr *SyntacticForm = 93 Node.isSyntacticForm() ? &Node : Node.getSyntacticForm()) 94 return getNumberOfDesignated(SyntacticForm) == SyntacticForm->getNumInits(); 95 return true; 96 } 97 98 AST_MATCHER(InitListExpr, hasMoreThanOneElement) { 99 return Node.getNumInits() > 1; 100 } 101 102 } // namespace 103 104 UseDesignatedInitializersCheck::UseDesignatedInitializersCheck( 105 StringRef Name, ClangTidyContext *Context) 106 : ClangTidyCheck(Name, Context), IgnoreSingleElementAggregates(Options.get( 107 IgnoreSingleElementAggregatesName, 108 IgnoreSingleElementAggregatesDefault)), 109 RestrictToPODTypes( 110 Options.get(RestrictToPODTypesName, RestrictToPODTypesDefault)), 111 IgnoreMacros( 112 Options.getLocalOrGlobal(IgnoreMacrosName, IgnoreMacrosDefault)), 113 StrictCStandardCompliance(Options.get(StrictCStandardComplianceName, 114 StrictCStandardComplianceDefault)), 115 StrictCppStandardCompliance( 116 Options.get(StrictCppStandardComplianceName, 117 StrictCppStandardComplianceDefault)) {} 118 119 void UseDesignatedInitializersCheck::registerMatchers(MatchFinder *Finder) { 120 const auto HasBaseWithFields = 121 hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl())))); 122 Finder->addMatcher( 123 initListExpr( 124 hasType(cxxRecordDecl(RestrictToPODTypes ? isPOD() : isAggregate(), 125 unless(HasBaseWithFields)) 126 .bind("type")), 127 IgnoreSingleElementAggregates ? hasMoreThanOneElement() : anything(), 128 unless(isFullyDesignated())) 129 .bind("init"), 130 this); 131 } 132 133 void 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 and 149 // a single fix-it for the whole expression. 150 if (0 == NumberOfDesignated) { 151 if (IgnoreMacros && InitList->getBeginLoc().isMacroID()) 152 return; 153 { 154 DiagnosticBuilder Diag = 155 diag(InitList->getLBraceLoc(), 156 "use designated initializer list to initialize %0"); 157 Diag << Type << 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), the 171 // 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, we 180 // 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() << *Designator 187 << FixItHint::CreateInsertion(InitExpr->getBeginLoc(), 188 ("." + *Designator + "=").str()); 189 } 190 } 191 } 192 193 void 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::modernize 205