================ @@ -0,0 +1,144 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "StringViewConversionsCheck.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::performance { + +static auto getStringTypeMatcher(StringRef CharType) { + return hasCanonicalType(hasDeclaration(cxxRecordDecl(hasName(CharType)))); +} + +void StringViewConversionsCheck::registerMatchers(MatchFinder *Finder) { + // Matchers for std::basic_string[_view] families + // (includes std::string, std::wstring, std::u8string, etc.) + const auto IsStdString = getStringTypeMatcher("::std::basic_string"); + const auto IsStdStringView = getStringTypeMatcher("::std::basic_string_view"); + + // Matches pointer to any character type (char*, const char*, wchar_t*, etc.) + // or array of any character type (char[], char[N], etc.) + const auto IsCharPointerOrArray = + anyOf(hasType(pointerType(pointee(isAnyCharacter()))), + hasType(arrayType(hasElementType(isAnyCharacter())))); + + // Matches expressions that can be implicitly converted to string_view: + // - string_view itself + // - string literals ("hello", L"hello", u8"hello", etc.) + // - character pointers (const char*, char*) + // - character arrays (char[], char[N]) + const auto ImplicitlyConvertibleToStringView = + expr(anyOf(hasType(IsStdStringView), stringLiteral(), + IsCharPointerOrArray)) + .bind("originalStringView"); + + // Matches std::string construction from an expression that is + // implicitly convertible to string_view. + // Excludes copy and move constructors to avoid false positives. + const auto RedundantStringConstruction = cxxConstructExpr( + hasType(IsStdString), + hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)), + unless(hasDeclaration(cxxConstructorDecl(isCopyConstructor()))), + unless(hasDeclaration(cxxConstructorDecl(isMoveConstructor())))); + + // Matches functional cast syntax: std::string(expr) + // This produces CXXFunctionalCastExpr in the AST. + const auto RedundantFunctionalCast = + cxxFunctionalCastExpr(hasType(IsStdString), + hasDescendant(RedundantStringConstruction)) + .bind("redundantExpr"); + + // Matches brace initialization syntax: std::string{expr} + // This produces CXXTemporaryObjectExpr in the AST. + const auto RedundantTemporaryObject = + cxxTemporaryObjectExpr( + hasType(IsStdString), + hasArgument(0, ignoringImplicit(ImplicitlyConvertibleToStringView)), + unless(hasDeclaration(cxxConstructorDecl(isCopyConstructor()))), + unless(hasDeclaration(cxxConstructorDecl(isMoveConstructor())))) + .bind("redundantExpr"); + + // Main matcher: finds function calls where an argument: + // 1. Has type string_view (after implicit conversions) + // 2. Contains a redundant std::string construction (either syntax) + // 3. Does NOT contain operator+ (which requires std::string operands, + // so the conversion would not be redundant in that case) + // + // Example patterns detected: + // foo(std::string(sv)) - string_view -> string -> string_view + // foo(std::string{"lit"}) - literal -> string -> string_view + // + // Example patterns excluded: + // foo(std::string(sv) + "bar") - operator+ needs std::string + Finder->addMatcher( + callExpr( + forEachArgumentWithParam( + expr(hasType(IsStdStringView), + hasDescendant(expr(anyOf(RedundantFunctionalCast, + RedundantTemporaryObject))), + // Exclude cases where std::string is used with operator+ + // since string_view doesn't support concatenation. + unless(hasDescendant(cxxOperatorCallExpr( + hasOverloadedOperatorName("+"), hasType(IsStdString))))) + .bind("expr"), + parmVarDecl(hasType(IsStdStringView)))) + .bind("call"), + this); +} + +void StringViewConversionsCheck::check(const MatchFinder::MatchResult &Result) { + // Get the full argument expression passed to the function. + // This has type string_view after implicit conversions. + const auto *ParamExpr = Result.Nodes.getNodeAs<Expr>("expr"); + if (!ParamExpr) + return; + + // Get the redundant std::string construction expression. + // This is either CXXFunctionalCastExpr for std::string(x) syntax + // or CXXTemporaryObjectExpr for std::string{x} syntax. + const auto *RedundantExpr = Result.Nodes.getNodeAs<Expr>("redundantExpr"); + if (!RedundantExpr) + return; + + // Get the original expression that was passed to std::string constructor. + // This is what we want to use as the replacement. + const auto *OriginalExpr = Result.Nodes.getNodeAs<Expr>("originalStringView"); + if (!OriginalExpr) + return; + + // Sanity check. Verify that the redundant expression is the direct source of + // the argument, not part of a larger expression (e.g., std::string(sv) + + // "bar"). If source ranges don't match, there's something between the string + // construction and the function argument, so we shouldn't transform. + if (ParamExpr->getSourceRange() != RedundantExpr->getSourceRange()) + return; ---------------- localspook wrote:
If this isn't something we expect to ever happen, I think it makes more sense to `assert` it https://github.com/llvm/llvm-project/pull/174288 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
