164 lines · cpp
1//===- RedundantStringInitCheck.cpp - clang-tidy ----------------*- 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 "RedundantStringInitCheck.h"10#include "../utils/Matchers.h"11#include "../utils/OptionsUtils.h"12#include "clang/ASTMatchers/ASTMatchers.h"13#include <optional>14 15using namespace clang::ast_matchers;16using namespace clang::tidy::matchers;17 18namespace clang::tidy::readability {19 20const char DefaultStringNames[] =21 "::std::basic_string_view;::std::basic_string";22 23static std::vector<StringRef> removeNamespaces(ArrayRef<StringRef> Names) {24 std::vector<StringRef> Result;25 Result.reserve(Names.size());26 for (const StringRef Name : Names) {27 const StringRef::size_type ColonPos = Name.rfind(':');28 Result.push_back(29 Name.drop_front(ColonPos == StringRef::npos ? 0 : ColonPos + 1));30 }31 return Result;32}33 34static const CXXConstructExpr *35getConstructExpr(const CXXCtorInitializer &CtorInit) {36 const Expr *InitExpr = CtorInit.getInit();37 if (const auto *CleanUpExpr = dyn_cast<ExprWithCleanups>(InitExpr))38 InitExpr = CleanUpExpr->getSubExpr();39 return dyn_cast<CXXConstructExpr>(InitExpr);40}41 42static std::optional<SourceRange>43getConstructExprArgRange(const CXXConstructExpr &Construct) {44 SourceLocation B, E;45 for (const Expr *Arg : Construct.arguments()) {46 if (B.isInvalid())47 B = Arg->getBeginLoc();48 if (Arg->getEndLoc().isValid())49 E = Arg->getEndLoc();50 }51 if (B.isInvalid() || E.isInvalid())52 return std::nullopt;53 return SourceRange(B, E);54}55 56RedundantStringInitCheck::RedundantStringInitCheck(StringRef Name,57 ClangTidyContext *Context)58 : ClangTidyCheck(Name, Context),59 StringNames(utils::options::parseStringList(60 Options.get("StringNames", DefaultStringNames))) {}61 62void RedundantStringInitCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {63 Options.store(Opts, "StringNames", DefaultStringNames);64}65 66void RedundantStringInitCheck::registerMatchers(MatchFinder *Finder) {67 const auto HasStringTypeName = hasAnyName(StringNames);68 const auto HasStringCtorName = hasAnyName(removeNamespaces(StringNames));69 70 // Match string constructor.71 const auto StringConstructorExpr = expr(72 anyOf(cxxConstructExpr(argumentCountIs(1),73 hasDeclaration(cxxMethodDecl(HasStringCtorName))),74 // If present, the second argument is the alloc object which must75 // not be present explicitly.76 cxxConstructExpr(argumentCountIs(2),77 hasDeclaration(cxxMethodDecl(HasStringCtorName)),78 hasArgument(1, cxxDefaultArgExpr()))));79 80 // Match a string constructor expression with an empty string literal.81 const auto EmptyStringCtorExpr = cxxConstructExpr(82 StringConstructorExpr,83 hasArgument(0, ignoringParenImpCasts(stringLiteral(hasSize(0)))));84 85 const auto EmptyStringCtorExprWithTemporaries =86 cxxConstructExpr(StringConstructorExpr,87 hasArgument(0, ignoringImplicit(EmptyStringCtorExpr)));88 89 const auto StringType = hasType(hasUnqualifiedDesugaredType(90 recordType(hasDeclaration(cxxRecordDecl(HasStringTypeName)))));91 const auto EmptyStringInit = traverse(92 TK_AsIs, expr(ignoringImplicit(anyOf(93 EmptyStringCtorExpr, EmptyStringCtorExprWithTemporaries))));94 95 // Match a variable declaration with an empty string literal as initializer.96 // Examples:97 // string foo = "";98 // string bar("");99 Finder->addMatcher(100 traverse(TK_AsIs,101 namedDecl(varDecl(StringType, hasInitializer(EmptyStringInit))102 .bind("vardecl"),103 unless(parmVarDecl()))),104 this);105 // Match a field declaration with an empty string literal as initializer.106 Finder->addMatcher(107 namedDecl(fieldDecl(StringType, hasInClassInitializer(EmptyStringInit))108 .bind("fieldDecl")),109 this);110 // Matches Constructor Initializers with an empty string literal as111 // initializer.112 // Examples:113 // Foo() : SomeString("") {}114 Finder->addMatcher(115 cxxCtorInitializer(116 isWritten(),117 forField(allOf(StringType, optionally(hasInClassInitializer(118 EmptyStringInit.bind("empty_init"))))),119 withInitializer(EmptyStringInit))120 .bind("ctorInit"),121 this);122}123 124void RedundantStringInitCheck::check(const MatchFinder::MatchResult &Result) {125 if (const auto *VDecl = Result.Nodes.getNodeAs<VarDecl>("vardecl")) {126 // VarDecl's getSourceRange() spans 'string foo = ""' or 'string bar("")'.127 // So start at getLocation() to span just 'foo = ""' or 'bar("")'.128 const SourceRange ReplaceRange(VDecl->getLocation(), VDecl->getEndLoc());129 diag(VDecl->getLocation(), "redundant string initialization")130 << FixItHint::CreateReplacement(ReplaceRange, VDecl->getName());131 }132 if (const auto *FDecl = Result.Nodes.getNodeAs<FieldDecl>("fieldDecl")) {133 // FieldDecl's getSourceRange() spans 'string foo = ""'.134 // So start at getLocation() to span just 'foo = ""'.135 const SourceRange ReplaceRange(FDecl->getLocation(), FDecl->getEndLoc());136 diag(FDecl->getLocation(), "redundant string initialization")137 << FixItHint::CreateReplacement(ReplaceRange, FDecl->getName());138 }139 if (const auto *CtorInit =140 Result.Nodes.getNodeAs<CXXCtorInitializer>("ctorInit")) {141 if (const FieldDecl *Member = CtorInit->getMember()) {142 if (!Member->hasInClassInitializer() ||143 Result.Nodes.getNodeAs<Expr>("empty_init")) {144 // The String isn't declared in the class with an initializer or its145 // declared with a redundant initializer, which will be removed. Either146 // way the string will be default initialized, therefore we can remove147 // the constructor initializer entirely.148 diag(CtorInit->getMemberLocation(), "redundant string initialization")149 << FixItHint::CreateRemoval(CtorInit->getSourceRange());150 return;151 }152 }153 const CXXConstructExpr *Construct = getConstructExpr(*CtorInit);154 if (!Construct)155 return;156 if (std::optional<SourceRange> RemovalRange =157 getConstructExprArgRange(*Construct))158 diag(CtorInit->getMemberLocation(), "redundant string initialization")159 << FixItHint::CreateRemoval(*RemovalRange);160 }161}162 163} // namespace clang::tidy::readability164