[clang-tools-extra] [clang-tidy] Add performance-substr-self-assignment check (PR #209657)
Andrew Gaul via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 30 22:04:33 PDT 2026
https://github.com/gaul updated https://github.com/llvm/llvm-project/pull/209657
>From d763d7c597574b39731aeff859e4d9c632908aa6 Mon Sep 17 00:00:00 2001
From: Andrew Gaul <andrew at gaul.org>
Date: Wed, 25 Mar 2026 23:50:04 -0400
Subject: [PATCH] [clang-tidy] Add performance-substr-self-assignment check
Finds `s = s.substr(...)` self-assignments that materialize an
unnecessary temporary string and rewrites the prefix-stripping forms
(`substr(pos)`, `substr(pos, npos)`) to an in-place `s.erase(0, pos)`.
The truncation form `s = s.substr(0, count)` is diagnosed without a
fix-it because `s.erase(count)` throws std::out_of_range where
`substr` clamps.
Co-Authored-By: Claude Fable 5 <noreply at anthropic.com>
---
.../clang-tidy/performance/CMakeLists.txt | 1 +
.../performance/PerformanceTidyModule.cpp | 3 +
.../performance/SubstrSelfAssignmentCheck.cpp | 127 ++++++++++++
.../performance/SubstrSelfAssignmentCheck.h | 44 +++++
clang-tools-extra/docs/ReleaseNotes.md | 6 +
.../docs/clang-tidy/checks/list.md | 1 +
.../performance/substr-self-assignment.md | 54 ++++++
.../checkers/Inputs/Headers/std/string | 1 +
.../performance/substr-self-assignment.cpp | 181 ++++++++++++++++++
9 files changed, 418 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h
create mode 100644 clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp
diff --git a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
index f55a6cf2800f3..7d70ab6ef1465 100644
--- a/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/performance/CMakeLists.txt
@@ -23,6 +23,7 @@ add_clang_library(clangTidyPerformanceModule STATIC
PerformanceTidyModule.cpp
PreferSingleCharOverloadsCheck.cpp
StringViewConversionsCheck.cpp
+ SubstrSelfAssignmentCheck.cpp
TriviallyDestructibleCheck.cpp
TypePromotionInMathFnCheck.cpp
UnnecessaryCopyInitializationCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
index 9eee02494be91..54d8e99a81ab1 100644
--- a/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp
@@ -25,6 +25,7 @@
#include "NoexceptSwapCheck.h"
#include "PreferSingleCharOverloadsCheck.h"
#include "StringViewConversionsCheck.h"
+#include "SubstrSelfAssignmentCheck.h"
#include "TriviallyDestructibleCheck.h"
#include "TypePromotionInMathFnCheck.h"
#include "UnnecessaryCopyInitializationCheck.h"
@@ -71,6 +72,8 @@ class PerformanceModule : public ClangTidyModule {
"performance-prefer-single-char-overloads");
CheckFactories.registerCheck<StringViewConversionsCheck>(
"performance-string-view-conversions");
+ CheckFactories.registerCheck<SubstrSelfAssignmentCheck>(
+ "performance-substr-self-assignment");
CheckFactories.registerCheck<TriviallyDestructibleCheck>(
"performance-trivially-destructible");
CheckFactories.registerCheck<TypePromotionInMathFnCheck>(
diff --git a/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp
new file mode 100644
index 0000000000000..ebdd186c99771
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.cpp
@@ -0,0 +1,127 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "SubstrSelfAssignmentCheck.h"
+#include "../utils/Matchers.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Tooling/FixIt.h"
+#include "llvm/ADT/STLExtras.h"
+#include <optional>
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::performance {
+
+SubstrSelfAssignmentCheck::SubstrSelfAssignmentCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ StringLikeClasses(utils::options::parseStringList(
+ Options.get("StringLikeClasses", "::std::basic_string"))) {}
+
+void SubstrSelfAssignmentCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "StringLikeClasses",
+ utils::options::serializeStringList(StringLikeClasses));
+}
+
+void SubstrSelfAssignmentCheck::registerMatchers(MatchFinder *Finder) {
+ const auto VarRef = declRefExpr(to(varDecl().bind("var"))).bind("lhs");
+
+ const auto StringClass =
+ cxxRecordDecl(hasAnyName(StringLikeClasses)).bind("string-class");
+
+ // The static member 'npos' of the matched string class. A bare name
+ // comparison is not enough: a local variable or parameter that happens to
+ // be called 'npos' carries a real count, which has no single-'erase'
+ // rewrite.
+ const auto NposDecl =
+ varDecl(hasName("npos"),
+ hasDeclContext(cxxRecordDecl(equalsBoundNode("string-class"))));
+
+ const auto Npos = expr(ignoringParenImpCasts(
+ mapAnyOf(declRefExpr, memberExpr).with(hasDeclaration(NposDecl))));
+
+ const auto SubstrCall =
+ cxxMemberCallExpr(
+ callee(cxxMethodDecl(hasName("substr"), ofClass(StringClass))),
+ on(declRefExpr(to(varDecl(equalsBoundNode("var"))))),
+ optionally(hasArgument(1, Npos.bind("npos-count"))))
+ .bind("substr");
+
+ // Match: s = s.substr(...), except in unevaluated contexts such as
+ // decltype or sizeof, where no temporary is ever materialized.
+ Finder->addMatcher(
+ cxxOperatorCallExpr(
+ hasOperatorName("="), hasArgument(0, VarRef),
+ hasArgument(1, SubstrCall),
+ unless(anyOf(hasAncestor(typeLoc()),
+ hasAncestor(expr(matchers::hasUnevaluatedContext())))))
+ .bind("assign"),
+ this);
+}
+
+void SubstrSelfAssignmentCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *AssignExpr =
+ Result.Nodes.getNodeAs<CXXOperatorCallExpr>("assign");
+ const auto *LHS = Result.Nodes.getNodeAs<DeclRefExpr>("lhs");
+ const auto *SubstrExpr = Result.Nodes.getNodeAs<CXXMemberCallExpr>("substr");
+
+ // Count only explicitly-written arguments (exclude CXXDefaultArgExpr).
+ const unsigned NumExplicitArgs =
+ llvm::count_if(SubstrExpr->arguments(), [](const Expr *Arg) {
+ return !isa<CXXDefaultArgExpr>(Arg);
+ });
+
+ // s = s.substr(pos) and s = s.substr(pos, npos) strip a prefix and can be
+ // rewritten as s.erase(0, pos). The truncation form s = s.substr(0, count)
+ // is diagnosed without a fix-it: s.erase(count) throws std::out_of_range
+ // for count > s.size() where substr merely clamps. The general
+ // two-argument form has no single-'erase' equivalent and is ignored.
+ // TODO: Also emit a fix-it for the truncation form when
+ // `count <= size()` can be proven.
+ // TODO(C++23): P2438R2 makes 's = std::move(s).substr(pos, count)' an
+ // exact, allocation-free rewrite for all forms; emit it as the fix-it in
+ // C++23 mode.
+ const bool IsPrefixStrip =
+ NumExplicitArgs == 1 ||
+ (NumExplicitArgs == 2 &&
+ Result.Nodes.getNodeAs<Expr>("npos-count") != nullptr);
+ bool IsTruncation = false;
+ if (!IsPrefixStrip && NumExplicitArgs == 2) {
+ const auto *PosLiteral =
+ dyn_cast<IntegerLiteral>(SubstrExpr->getArg(0)->IgnoreParenImpCasts());
+ IsTruncation = PosLiteral && PosLiteral->getValue() == 0;
+ }
+ if (!IsPrefixStrip && !IsTruncation)
+ return;
+
+ // Rewriting a macro expansion is unsafe; emit the warning without a fix-it.
+ // TODO: Only emit this fix-it when `pos <= size()` can be proven.
+ std::optional<std::string> Replacement;
+ if (IsPrefixStrip && !AssignExpr->getBeginLoc().isMacroID() &&
+ !AssignExpr->getEndLoc().isMacroID()) {
+ StringRef VarName = tooling::fixit::getText(*LHS, *Result.Context);
+ StringRef PosText =
+ tooling::fixit::getText(*SubstrExpr->getArg(0), *Result.Context);
+ if (!VarName.empty() && !PosText.empty())
+ Replacement = (VarName + ".erase(0, " + PosText + ")").str();
+ }
+
+ auto Diag = diag(AssignExpr->getOperatorLoc(),
+ "inefficient self-assignment via 'substr'; use 'erase' to "
+ "modify the string in-place");
+ if (Replacement)
+ Diag << FixItHint::CreateReplacement(AssignExpr->getSourceRange(),
+ *Replacement);
+}
+
+} // namespace clang::tidy::performance
diff --git a/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h
new file mode 100644
index 0000000000000..cf9dc425c44d2
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/performance/SubstrSelfAssignmentCheck.h
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_SUBSTRSELFASSIGNMENTCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_SUBSTRSELFASSIGNMENTCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+#include <vector>
+
+namespace clang::tidy::performance {
+
+/// Finds cases where a string variable is assigned the result of calling
+/// ``substr()`` on itself (e.g., ``s = s.substr(x, y)``). This pattern creates
+/// an unnecessary temporary string; the same effect can be achieved in-place
+/// using ``erase()``.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/performance/substr-self-assignment.html
+class SubstrSelfAssignmentCheck : public ClangTidyCheck {
+public:
+ SubstrSelfAssignmentCheck(StringRef Name, ClangTidyContext *Context);
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+ std::optional<TraversalKind> getCheckTraversalKind() const override {
+ return TK_IgnoreUnlessSpelledInSource;
+ }
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+ const std::vector<StringRef> StringLikeClasses;
+};
+
+} // namespace clang::tidy::performance
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_SUBSTRSELFASSIGNMENTCHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index 633418a2abb98..056f6cb0f13f6 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -113,6 +113,12 @@ infrastructure are described first, followed by tool-specific sections.
Finds calls to `value_or` (and alternative spellings `valueOr`,
`ValueOr`) on optional types where the return type is expensive to copy.
+- New {doc}`performance-substr-self-assignment
+ <clang-tidy/checks/performance/substr-self-assignment>` check.
+
+ Finds cases where a string variable is assigned the result of calling
+ `substr()` on itself.
+
- New {doc}`portability-avoid-pragma-comment
<clang-tidy/checks/portability/avoid-pragma-comment>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.md b/clang-tools-extra/docs/clang-tidy/checks/list.md
index 5a220b13eb599..4fcf554b483e1 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.md
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.md
@@ -368,6 +368,7 @@ readability/*
| {doc}`performance-noexcept-swap <performance/noexcept-swap>` | Yes |
| {doc}`performance-prefer-single-char-overloads <performance/prefer-single-char-overloads>` | Yes |
| {doc}`performance-string-view-conversions <performance/string-view-conversions>` | Yes |
+| {doc}`performance-substr-self-assignment <performance/substr-self-assignment>` | Yes |
| {doc}`performance-trivially-destructible <performance/trivially-destructible>` | Yes |
| {doc}`performance-type-promotion-in-math-fn <performance/type-promotion-in-math-fn>` | Yes |
| {doc}`performance-unnecessary-copy-initialization <performance/unnecessary-copy-initialization>` | Yes |
diff --git a/clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md b/clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md
new file mode 100644
index 0000000000000..7a568ade43c2a
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/performance/substr-self-assignment.md
@@ -0,0 +1,54 @@
+```{title} clang-tidy - performance-substr-self-assignment
+```
+
+# performance-substr-self-assignment
+
+Finds cases where a string variable is assigned the result of calling
+`substr()` on itself. This pattern materializes an unnecessary temporary
+string (an allocation, a copy of the surviving characters, and a
+deallocation) and discards the original capacity; the same effect can be
+achieved in-place with `erase()`.
+
+```cpp
+std::string s = "hello world";
+
+s = s.substr(5); // warning; fix-it: s.erase(0, 5)
+s = s.substr(5, std::string::npos); // warning; fix-it: s.erase(0, 5)
+s = s.substr(0, 3); // warning; no fix-it (see below)
+```
+
+The fix-it for the prefix-stripping forms replaces the assignment with
+`s.erase(0, pos)`. The two expressions differ only when `pos > s.size()`:
+`substr` throws `std::out_of_range`, while `erase(0, pos)` clamps and
+erases the whole string. Code that relies on that exception changes
+behavior under the fix-it.
+
+The truncation form `s = s.substr(0, count)` is diagnosed without a
+fix-it: the tempting replacement `s.erase(count)` throws
+`std::out_of_range` whenever `count > s.size()`, whereas `substr` clamps
+`count` and leaves the string unchanged. A safe manual rewrite is
+`s.resize(std::min(count, s.size()))`.
+
+In C++23, `s = std::move(s).substr(pos, count)` is an exact,
+allocation-free rewrite for every form: the rvalue `substr` overload
+([P2438R2](https://wg21.link/p2438r2)) constructs the result by moving
+from the string, reusing its buffer, and preserves the exception behavior
+of the original code. A future version of this check may suggest it
+automatically in C++23 mode. Note that the same spelling compiles before
+C++23 but silently performs a full copy.
+
+No diagnostic is emitted for the general form `s = s.substr(pos, count)`,
+which has no single-call in-place equivalent, nor in unevaluated contexts
+such as `decltype` or `sizeof`, where no temporary is ever materialized.
+Inside macro expansions the warning is emitted without a fix-it. Only
+self-assignments to plain variables are diagnosed; assignments through
+class members or pointers are not.
+
+## Options
+
+```{option} StringLikeClasses
+
+Semicolon-separated list of names of string-like classes. By default only
+`::std::basic_string` is considered. Classes listed here must provide
+`substr`, `erase`, and `npos` with `std::basic_string` semantics.
+```
diff --git a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string
index 766f240c655fb..667d48ad65334 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string
+++ b/clang-tools-extra/test/clang-tidy/checkers/Inputs/Headers/std/string
@@ -77,6 +77,7 @@ struct basic_string {
_Type& insert(size_type pos, const C* s);
_Type& insert(size_type pos, const C* s, size_type n);
+ _Type& erase(size_type pos = 0, size_type count = npos);
_Type substr(size_type pos = 0, size_type count = npos) const;
constexpr bool starts_with(std::basic_string_view<C, T> sv) const noexcept;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp b/clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp
new file mode 100644
index 0000000000000..848d43fb6748c
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/performance/substr-self-assignment.cpp
@@ -0,0 +1,181 @@
+// RUN: %check_clang_tidy %s performance-substr-self-assignment %t
+#include <string>
+
+void OneArg() {
+ std::string s = "hello world";
+
+ // Basic case: s = s.substr(pos)
+ s = s.substr(5);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place [performance-substr-self-assignment]
+ // CHECK-FIXES: s.erase(0, 5);
+
+ // With a variable as the argument.
+ int pos = 3;
+ s = s.substr(pos);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, pos);
+
+ // With a more complex expression.
+ s = s.substr(pos + 1);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, pos + 1);
+}
+
+void WholeStringCopies() {
+ std::string s = "hello world";
+
+ // A full self-copy via substr() has no erase() rewrite; not diagnosed.
+ s = s.substr();
+
+ // substr(0) is still a pointless full copy; the fix-it degenerates to a
+ // no-op erase.
+ s = s.substr(0);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, 0);
+}
+
+void TruncationNoFixIt() {
+ std::string s = "hello world";
+
+ // The truncation form s = s.substr(0, count) is diagnosed but gets no
+ // fix-it: 'erase(count)' throws std::out_of_range when count > size(),
+ // while 'substr(0, count)' clamps.
+ s = s.substr(0, 5);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s = s.substr(0, 5);
+
+ size_t count = 3;
+ s = s.substr(0, count);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s = s.substr(0, count);
+}
+
+void TwoArgWithNpos() {
+ std::string s = "hello world";
+
+ // s = s.substr(pos, npos) is equivalent to s = s.substr(pos).
+ s = s.substr(3, std::string::npos);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, 3);
+
+ int pos = 2;
+ s = s.substr(pos, std::string::npos);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, pos);
+
+ // Member-syntax spelling of npos.
+ s = s.substr(4, s.npos);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, 4);
+}
+
+void LocalVariableNamedNpos() {
+ std::string s = "hello world";
+
+ // A local variable named 'npos' is not std::basic_string::npos; the count
+ // is meaningful, so there is no single-'erase' rewrite and no warning.
+ size_t npos = 3;
+ s = s.substr(2, npos);
+}
+
+void ParamNamedNpos(std::string s, std::string::size_type npos) {
+ s = s.substr(1, npos);
+}
+
+void TwoArgGeneral() {
+ std::string s = "hello world";
+
+ // General two-argument case: no diagnostic (no single erase() equivalent).
+ s = s.substr(2, 3);
+
+ int pos = 1;
+ size_t len = 4;
+ s = s.substr(pos, len);
+}
+
+void Negatives() {
+ std::string s = "hello";
+ std::string t = "world";
+
+ // Different variables -- not a self-assignment.
+ s = t.substr(1);
+ s = t.substr(0, 3);
+
+ // Not an assignment to the same variable.
+ std::string r = s.substr(1);
+}
+
+// Unevaluated operands never materialize the temporary; no diagnostic.
+std::string GlobalStr;
+using UnevaluatedT = decltype(GlobalStr = GlobalStr.substr(1));
+
+void UnevaluatedContexts() {
+ std::string s = "hello";
+ (void)sizeof(s = s.substr(1));
+}
+
+struct Holder {
+ std::string Str;
+ // Self-assignment through a class member is not matched; only plain
+ // variables are.
+ void trim() { Str = Str.substr(2); }
+};
+
+template <typename T>
+void dependentType(T t) {
+ // Type-dependent: no diagnostic, including in instantiations.
+ t = t.substr(1);
+}
+void instantiate() { dependentType(std::string("hello")); }
+
+void WideString() {
+ std::wstring ws = L"hello world";
+
+ ws = ws.substr(3);
+ // CHECK-MESSAGES: [[@LINE-1]]:6: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: ws.erase(0, 3);
+
+ // Truncation form: warning only, no fix-it.
+ ws = ws.substr(0, 5);
+ // CHECK-MESSAGES: [[@LINE-1]]:6: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: ws = ws.substr(0, 5);
+}
+
+void Parenthesized() {
+ std::string s = "hello";
+
+ // Parenthesized object on the RHS -- should still match.
+ s = (s).substr(2);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, 2);
+
+ // Parenthesized substr call on the RHS.
+ s = (s.substr(3));
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, 3);
+
+ // Parenthesized left-hand side.
+ (s) = s.substr(1);
+ // CHECK-MESSAGES: [[@LINE-1]]:7: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, 1);
+}
+
+#define STRIP_PREFIX(str, n) str = str.substr(n)
+void MacroExpansion() {
+ std::string s = "hello";
+
+ // Diagnosed, but no fix-it: rewriting a macro expansion is unsafe.
+ STRIP_PREFIX(s, 2);
+ // CHECK-MESSAGES: [[@LINE-1]]:3: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: STRIP_PREFIX(s, 2);
+}
+
+#define OFFSET 2
+void MacroArgument() {
+ std::string s = "hello";
+
+ // Only the argument comes from a macro; the fix-it preserves its spelling.
+ s = s.substr(OFFSET);
+ // CHECK-MESSAGES: [[@LINE-1]]:5: warning: inefficient self-assignment via 'substr'; use 'erase' to modify the string in-place
+ // CHECK-FIXES: s.erase(0, OFFSET);
+}
More information about the cfe-commits
mailing list