Automating recurring modernization suggestions so they can be applied consistently.
Status: Ongoing | Merged PR: #116033
The Origin Story
Years ago, while working on Chromium, Peter Kasting introduced me to clang-tidy. During code reviews he'd point out patterns that could be automated - things he'd seen hundreds of times, anti-patterns that kept appearing, modernization opportunities too tedious to do by hand.
I started keeping my own scratchpad, jotting down his suggestions and ideas. That document sat there for years - until I recently picked it back up.
Why clang-tidy Matters for Large Codebases
In a large C++ codebase such as Chromium, recurring modernization patterns are difficult to apply consistently by hand. clang-tidy can identify a specific pattern, explain the replacement, and in suitable cases generate the edit automatically.
Chromium actively uses clang-tidy for ongoing modernization. The base::StringPiece to std::string_view migration, the adoption of std::optional, the move to starts_with/ends_with - all of these are driven by clang-tidy checks that find patterns and transform them at scale.
Extending modernize-use-starts-ends-with
The merged PR extends an existing check to detect a common pattern:
// Before: Creates a temporary string, then compares
if (str.substr(0, 5) == "hello") { ... }
// After: Direct comparison, no allocation
if (str.starts_with("hello")) { ... }
The starts_with method was added in C++20. It's clearer, faster (no temporary string), and expresses intent better.
The PR (#116033) extended modernize-use-starts-ends-with to catch substr patterns. The review process was educational - the LLVM community suggested integrating it into the existing check rather than creating a new one.
Key patterns now detected:
str.substr(0, n) == "prefix"โstr.starts_with("prefix")str.substr(0, prefix.size()) == prefixโstr.starts_with(prefix)str.substr(0, strlen("foo")) == "foo"โstr.starts_with("foo")
Candidate Checks
Some older review suggestions became practical once C++20 and C++23 facilities were available. The following PRs explore those patterns:
Modernization checks:
- OPEN modernize-use-span-param - Suggest
std::spanfor pointer+size parameter pairs - OPEN modernize-use-aggregate - Use aggregate initialization where possible
- OPEN modernize-use-algorithm - Replace raw loops with standard algorithms
- OPEN modernize-use-size-type - Use proper size types for container indices
- OPEN modernize-pointer-to-span - Convert pointer arithmetic to span operations
- OPEN modernize-use-return-value - Use
[[nodiscard]]return values properly
Readability checks:
- OPEN readability-use-span-first-last - Use
span.first(n)instead ofspan.subspan(0, n) - OPEN readability-stringview-substr - Prefer
string_viewsubstr patterns - OPEN readability-pointer-to-ref - Prefer references over pointers when appropriate
Enhancements:
- OPEN modernize-pass-by-value extension - Handle function body local copies
- OPEN modernize-make-shared options - Configurable smart pointer types
The Chromium Connection
A code-search sample shows how often several of these patterns occur in Chromium:
| Pattern | Check | Instances |
|---|---|---|
.substr(0, n) == |
modernize-use-starts-ends-with |
~730 |
.find(x) == 0 |
modernize-use-starts-ends-with |
~1,100 |
.compare(0, n) |
modernize-use-starts-ends-with |
~50 |
.subspan(0, n) |
readability-use-span-first-last |
~10 |
These are candidate matches, not automatic proof that every occurrence should change. The check still needs to account for types, semantics, and cases where a fix would reduce clarity.
Links
The useful part of the scratchpad was preserving concrete review examples until the language and tooling could express them reliably.
Thanks to:
- Peter Kasting for the original ideas and introducing me to clang-tidy
- Nicolas van Kempen for patient review and guidance on my first PR
- Eugene Zelenko, HerrCai0907, 5chmidti, and PiotrZSL for thorough code reviews