clang-tools 23.0.0git
StringViewConversionsCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/Expr.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13
14using namespace clang::ast_matchers;
15
17
18static auto getStringTypeMatcher(StringRef CharType) {
19 return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType))));
20}
21
23 // Matchers for std::basic_[w|u8|u16|u32]string[_view] families.
24 const auto IsStdString = getStringTypeMatcher("::std::basic_string");
25 const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view");
26
27 // Matches pointer to any character type (char*, etc.) or array of any
28 // character type (char[], etc.).
29 const auto IsCharPointerOrArray =
30 anyOf(hasType(pointerType(pointee(isAnyCharacter()))),
31 hasType(arrayType(hasElementType(isAnyCharacter()))));
32
33 const auto ImplicitlyConvertibleToStringView =
34 expr(anyOf(hasType(IsStdStringView), IsCharPointerOrArray,
35 hasType(IsStdString)))
36 .bind("originalStringView");
37
38 // Matches std::string construction from a string_view-convertible expression:
39 // - Direct construction: std::string{sv}, std::string{s}
40 // - Copy from existing string: std::string(s) where s is std::string
41 const auto RedundantStringConstruction = cxxConstructExpr(
42 hasType(IsStdString),
43 hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)),
44 unless(hasArgument(1, unless(cxxDefaultArgExpr()))));
45
46 // Matches functional cast syntax: std::string(expr):
47 // std::string(sv), std::string("literal")
48 const auto RedundantFunctionalCast = cxxFunctionalCastExpr(
49 hasType(IsStdString), hasDescendant(RedundantStringConstruction));
50
51 // Match method calls on std::string that modify or use the string,
52 // such as operator+, append(), substr(), c_str(), etc.
53 const auto HasStringOperatorCall = hasDescendant(cxxOperatorCallExpr(
54 hasOverloadedOperatorName("+"), hasType(IsStdString)));
55 const auto HasStringMethodCall =
56 hasDescendant(cxxMemberCallExpr(on(hasType(IsStdString))));
57
58 const auto IsCallReturningString = callExpr(hasType(IsStdString));
59 const auto IsImplicitStringViewFromCall =
60 cxxConstructExpr(hasType(IsStdStringView),
61 hasArgument(0, ignoringImplicit(IsCallReturningString)));
62
63 // Main matcher: finds function calls where:
64 // 1. A parameter has type string_view
65 // 2. The corresponding argument contains a redundant std::string construction
66 // (either functional cast syntax or direct construction/brace init)
67 // 3. The argument does NOT involve:
68 // - String concatenation with operator+ (string_view doesn't support it)
69 // - Method calls on the std::string (like append(), substr(), etc.)
70 Finder->addMatcher(
71 callExpr(forEachArgumentWithParam(
72 expr(hasType(IsStdStringView),
73 // Ignore cases where the argument is a function call
74 unless(ignoringParenImpCasts(IsImplicitStringViewFromCall)),
75 // Match either syntax for std::string construction
76 hasDescendant(expr(anyOf(RedundantFunctionalCast,
77 RedundantStringConstruction))
78 .bind("redundantExpr")),
79 // Exclude cases of std::string methods or operator+ calls
80 unless(anyOf(HasStringOperatorCall, HasStringMethodCall)))
81 .bind("expr"),
82 parmVarDecl(hasType(IsStdStringView)))),
83 this);
84}
85
86void StringViewConversionsCheck::check(const MatchFinder::MatchResult &Result) {
87 const auto *ParamExpr = Result.Nodes.getNodeAs<Expr>("expr");
88 const auto *RedundantExpr = Result.Nodes.getNodeAs<Expr>("redundantExpr");
89 const auto *OriginalExpr = Result.Nodes.getNodeAs<Expr>("originalStringView");
90 assert(RedundantExpr && ParamExpr && OriginalExpr);
91
92 // Sanity check. Verify that the redundant expression is the direct source of
93 // the argument, not part of a larger expression (e.g., std::string(sv) +
94 // "bar").
95 assert(ParamExpr->getSourceRange() == RedundantExpr->getSourceRange());
96
97 const StringRef OriginalText = Lexer::getSourceText(
98 CharSourceRange::getTokenRange(OriginalExpr->getSourceRange()),
99 *Result.SourceManager, getLangOpts());
100
101 if (OriginalText.empty())
102 return;
103
104 diag(RedundantExpr->getBeginLoc(),
105 "redundant conversion to %0 and then back to %1")
106 << RedundantExpr->getType() << ParamExpr->getType()
107 << FixItHint::CreateReplacement(RedundantExpr->getSourceRange(),
108 OriginalText);
109}
110
111} // namespace clang::tidy::performance
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static auto getStringTypeMatcher(StringRef CharType)