blob: beb0acd6b3568b57174c1bf180bd57d34eb56c5c [file] [log] [blame] [view]
andybons6eaa0c0d2015-08-26 20:12:521# Clang Tool Refactoring
andybons3322f762015-08-24 21:37:092
andybons6eaa0c0d2015-08-26 20:12:523[TOC]
andybons3322f762015-08-24 21:37:094
dchengce2375e2016-01-12 01:09:075## Introduction
6
7Clang tools can help with global refactorings of Chromium code. Clang tools can
8take advantage of clang's AST to perform refactorings that would be impossible
9with a traditional find-and-replace regexp:
10
11* Constructing `scoped_ptr<T>` from `NULL`: <https://crbug.com/173286>
12* Implicit conversions of `scoped_refptr<T>` to `T*`: <https://crbug.com/110610>
13* Rename everything in Blink to follow Chromium style: <https://crbug.com/563793>
jdoerrie8c5b8252017-10-14 06:28:5814* Clean up of deprecated `base::Value` APIs: <https://crbug.com/581865>
dchengce2375e2016-01-12 01:09:0715
andybons6eaa0c0d2015-08-26 20:12:5216## Caveats
andybons3322f762015-08-24 21:37:0917
Daniel Cheng69413be2017-10-24 01:23:5718* Invocations of a clang tool runs on on only one build config at a time. For
19example, running the tool across a `target_os="win"` build won't update code
20that is guarded by `OS_POSIX`. Performing a global refactoring will often
21require running the tool once for each build config.
andybons6eaa0c0d2015-08-26 20:12:5222
23## Prerequisites
24
dchengce2375e2016-01-12 01:09:0725A Chromium checkout created with `fetch` should have everything needed.
andybons6eaa0c0d2015-08-26 20:12:5226
dchengce2375e2016-01-12 01:09:0727For convenience, add `third_party/llvm-build/Release+Asserts/bin` to `$PATH`.
andybons6eaa0c0d2015-08-26 20:12:5228
dchengce2375e2016-01-12 01:09:0729## Writing the tool
andybons6eaa0c0d2015-08-26 20:12:5230
dchengce2375e2016-01-12 01:09:0731LLVM uses C++11 and CMake. Source code for Chromium clang tools lives in
Daniel Cheng69413be2017-10-24 01:23:5732[//tools/clang]. It is generally easiest to use one of the already-written tools
Devon Loehr3b59be92025-03-10 14:16:1633as the base for writing a new tool; the tool in [//tools/clang/ast_rewriter] is
34designed for this purpose, and includes explanations its major parts.
andybons6eaa0c0d2015-08-26 20:12:5235
dchengce2375e2016-01-12 01:09:0736Chromium clang tools generally follow this pattern:
37
Daniel Cheng69413be2017-10-24 01:23:57381. Instantiate a
39 [`clang::ast_matchers::MatchFinder`][clang-docs-match-finder].
Devon Loehr3b59be92025-03-10 14:16:16402. Develop one or most [AST matchers][clang-matcher-tutorial] to locate the
41 patterns of interest.
42 1. `clang-query` is of great use for this part
433. Create a subclass of
Daniel Cheng69413be2017-10-24 01:23:5744 [`clang::ast_matchers::MatchFinder::MatchCallback`][clang-docs-match-callback]
Devon Loehr3b59be92025-03-10 14:16:1645 to determine what actions to take on each match, and register it with
46 `addMatcher()`.
dchengce2375e2016-01-12 01:09:07473. Create a new `clang::tooling::FrontendActionFactory` from the `MatchFinder`.
484. Run the action across the specified files with
Daniel Cheng69413be2017-10-24 01:23:5749 [`clang::tooling::ClangTool::run`][clang-docs-clang-tool-run].
505. Serialize generated [`clang::tooling::Replacement`][clang-docs-replacement]s
danakjef9f1fa2016-01-16 00:37:2851 to `stdout`.
dchengce2375e2016-01-12 01:09:0752
Devon Loehr3b59be92025-03-10 14:16:1653Useful references when writing the tool:
dchengce2375e2016-01-12 01:09:0754
Daniel Cheng69413be2017-10-24 01:23:5755* [Clang doxygen reference][clang-docs]
56* [Tutorial for building tools using LibTooling and
57 LibASTMatchers][clang-tooling-tutorial]
Devon Loehr3b59be92025-03-10 14:16:1658* [Tutorial for AST matchers][clang-matcher-tutorial]
59* [AST matcher reference][matcher-reference]
dchengce2375e2016-01-12 01:09:0760
61### Edit serialization format
Devon Loehr3b59be92025-03-10 14:16:1662Tools do not directly edit files; rather, they output a series of _edits_ to be
63applied later, which have the following format:
64
dchengce2375e2016-01-12 01:09:0765```
66==== BEGIN EDITS ====
Lukasz Anforowicze4d0e92f2020-05-06 20:47:1267r:::path/to/file/to/edit:::offset1:::length1:::replacement text
68r:::path/to/file/to/edit:::offset2:::length2:::replacement text
69r:::path/to/file2/to/edit:::offset3:::length3:::replacement text
70include-user-header:::path/to/file2/to/edit:::-1:::-1:::header/file/to/include.h
dchengce2375e2016-01-12 01:09:0771
72 ...
73
74==== END EDITS ====
andybons3322f762015-08-24 21:37:0975```
76
dchengce2375e2016-01-12 01:09:0777The header and footer are required. Each line between the header and footer
78represents one edit. Fields are separated by `:::`, and the first field must
Lukasz Anforowicze4d0e92f2020-05-06 20:47:1279be `r` (for replacement) or `include-user-header`.
80A deletion is an edit with no replacement text.
andybons6eaa0c0d2015-08-26 20:12:5281
lukaszaf9b89e72016-12-28 19:43:0682The edits are applied by [`apply_edits.py`](#Running), which understands certain
dchengce2375e2016-01-12 01:09:0783conventions:
84
lukaszaf9b89e72016-12-28 19:43:0685* The clang tool should munge newlines in replacement text to `\0`. The script
dchengce2375e2016-01-12 01:09:0786 knows to translate `\0` back to newlines when applying edits.
87* When removing an element from a 'list' (e.g. function parameters,
lukaszaf9b89e72016-12-28 19:43:0688 initializers), the clang tool should emit a deletion for just the element.
89 The script understands how to extend the deletion to remove commas, etc. as
dchengce2375e2016-01-12 01:09:0790 needed.
91
92TODO: Document more about `SourceLocation` and how spelling loc differs from
93expansion loc, etc.
94
95### Why not RefactoringTool?
danakjef9f1fa2016-01-16 00:37:2896While clang has a [`clang::tooling::RefactoringTool`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1RefactoringTool.html)
97to automatically apply the generated replacements and save the results, it
98doesn't work well for Chromium:
dchengce2375e2016-01-12 01:09:0799
Daniel Cheng82f80d62017-05-18 05:39:38100* Clang tools run actions serially, so run time scales poorly to tens of
dchengce2375e2016-01-12 01:09:07101 thousands of files.
102* A parsing error in any file (quite common in NaCl source) prevents any of
103 the generated replacements from being applied.
104
105## Building
106Synopsis:
danakjef9f1fa2016-01-16 00:37:28107
andybons6eaa0c0d2015-08-26 20:12:52108```shell
Victor Vianna1e01b5e2023-10-12 17:04:42109tools/clang/scripts/build.py --bootstrap --without-android --without-fuchsia \
dchengf2390712017-01-05 06:41:45110 --extra-tools rewrite_to_chrome_style
dchengce2375e2016-01-12 01:09:07111```
danakjef9f1fa2016-01-16 00:37:28112
Daniel Cheng69413be2017-10-24 01:23:57113Running this command builds the [Oilpan plugin][//tools/clang/blink_gc_plugin],
114the [Chrome style plugin][//tools/clang/plugins], and the [Blink to Chrome style
115rewriter][//tools/clang/rewrite_to_chrome_style]. Additional arguments to
Devon Loehr3b59be92025-03-10 14:16:16116`--extra-tools` should be the name of subdirectories in [//tools/clang]. The
117tool binary will be located in `third_party/llvm-build/Release+Asserts/bin`.
dchengce2375e2016-01-12 01:09:07118
danakj30d0f8c92016-01-28 00:26:33119It is important to use --bootstrap as there appear to be [bugs](https://crbug.com/580745)
120in the clang library this script produces if you build it with gcc, which is the default.
121
vabr9ed3f432017-06-09 07:30:42122Once clang is bootsrapped, incremental builds can be done by invoking `ninja` in
123the `third_party/llvm-build/Release+Asserts` directory. In particular,
124recompiling solely the tool you are writing can be accomplished by executing
125`ninja rewrite_to_chrome_style` (replace `rewrite_to_chrome_style` with your
126tool's name).
127
dchengce2375e2016-01-12 01:09:07128## Running
qyearsleyc0dc6f42016-12-02 22:13:39129First, build all Chromium targets to avoid failures due to missing dependencies
dchengce2375e2016-01-12 01:09:07130that are generated as part of the build:
danakjef9f1fa2016-01-16 00:37:28131
dchengce2375e2016-01-12 01:09:07132```shell
danakjca6b31b52016-12-22 22:05:53133ninja -C out/Debug # For non-Windows
134ninja -d keeprsp -C out/Debug # For Windows
lukaszaf9b89e72016-12-28 19:43:06135
136# experimental alternative:
Yannic Bonenberger748bb0d2018-07-02 21:10:06137$gen_targets = $(ninja -C out/Debug -t targets all \
lukaszaf9b89e72016-12-28 19:43:06138 | grep '^gen/[^: ]*\.[ch][pc]*:' \
Yannic Bonenberger748bb0d2018-07-02 21:10:06139 | cut -f 1 -d :)
lukaszaf9b89e72016-12-28 19:43:06140ninja -C out/Debug $gen_targets
danakjca6b31b52016-12-22 22:05:53141```
142
Kevin McNeed93068c2021-11-25 00:59:34143Note that running the clang tool with precompiled headers enabled currently
144produces errors. This can be avoided by setting
145`enable_precompiled_headers = false` in the build's gn args.
146
lukaszaf9b89e72016-12-28 19:43:06147Then run the actual clang tool to generate a list of edits:
Daniel Cheng9ce2a302016-01-16 01:17:57148
149```shell
Daniel Cheng51c55302017-05-04 00:39:16150tools/clang/scripts/run_tool.py --tool <path to tool> \
dchengce2375e2016-01-12 01:09:07151 --generate-compdb
Daniel Cheng51c55302017-05-04 00:39:16152 -p out/Debug <path 1> <path 2> ... >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07153```
andybons6eaa0c0d2015-08-26 20:12:52154
dchengce2375e2016-01-12 01:09:07155`--generate-compdb` can be omitted if the compile DB was already generated and
156the list of build flags and source files has not changed since generation.
157
Kevin McNeed93068c2021-11-25 00:59:34158If cross-compiling, specify `--target_os`. See `gn help target_os` for
159possible values. For example, when cross-compiling a Windows build on
160Linux/Mac, use `--target_os=win`.
161
dchengce2375e2016-01-12 01:09:07162`<path 1>`, `<path 2>`, etc are optional arguments to filter the files to run
lukaszaf9b89e72016-12-28 19:43:06163the tool against. This is helpful when sharding global refactorings into smaller
dchengce2375e2016-01-12 01:09:07164chunks. For example, the following command will run the `empty_string` tool
lukaszaf9b89e72016-12-28 19:43:06165against just the `.c`, `.cc`, `.cpp`, `.m`, `.mm` files in `//net`. Note that
166the filtering is not applied to the *output* of the tool - the tool can emit
Kevin McNeed93068c2021-11-25 00:59:34167edits that apply to files outside of `//net` (i.e. edits that apply to headers
168from `//base` that got included by source files in `//net`).
dchengce2375e2016-01-12 01:09:07169
170```shell
Daniel Cheng51c55302017-05-04 00:39:16171tools/clang/scripts/run_tool.py --tool empty_string \
Yannic Bonenberger748bb0d2018-07-02 21:10:06172 --generate-compdb \
Daniel Cheng51c55302017-05-04 00:39:16173 -p out/Debug net >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07174```
175
lukaszaf9b89e72016-12-28 19:43:06176Note that some header files might only be included from generated files (e.g.
177from only from some `.cpp` files under out/Debug/gen). To make sure that
178contents of such header files are processed by the clang tool, the clang tool
179needs to be run against the generated files. The only way to accomplish this
180today is to pass `--all` switch to `run_tool.py` - this will run the clang tool
181against all the sources from the compilation database.
182
183Finally, apply the edits as follows:
184
185```shell
186cat /tmp/list-of-edits.debug \
187 | tools/clang/scripts/extract_edits.py \
Daniel Cheng51c55302017-05-04 00:39:16188 | tools/clang/scripts/apply_edits.py -p out/Debug <path 1> <path 2> ...
lukaszaf9b89e72016-12-28 19:43:06189```
190
191The apply_edits.py tool will only apply edits to files actually under control of
192`git`. `<path 1>`, `<path 2>`, etc are optional arguments to further filter the
193files that the edits are applied to. Note that semantics of these filters is
194distinctly different from the arguments of `run_tool.py` filters - one set of
195filters controls which files are edited, the other set of filters controls which
196files the clang tool is run against.
197
dchengce2375e2016-01-12 01:09:07198## Debugging
199Dumping the AST for a file:
Daniel Cheng9ce2a302016-01-16 01:17:57200
andybons6eaa0c0d2015-08-26 20:12:52201```shell
Daniel Cheng69413be2017-10-24 01:23:57202clang++ -Xclang -ast-dump -std=c++14 foo.cc | less -R
andybons3322f762015-08-24 21:37:09203```
204
dchengce2375e2016-01-12 01:09:07205Using `clang-query` to dynamically test matchers (requires checking out
Devon Loehr3b59be92025-03-10 14:16:16206and building [clang-tools-extra][]; this should happen automatically).
207The binary is located in `third_party/llvm-build/Release+Asserts/bin`:
Daniel Cheng9ce2a302016-01-16 01:17:57208
andybons6eaa0c0d2015-08-26 20:12:52209```shell
dchengce2375e2016-01-12 01:09:07210clang-query -p path/to/compdb base/memory/ref_counted.cc
andybons3322f762015-08-24 21:37:09211```
212
Devon Loehr3b59be92025-03-10 14:16:16213If you're running it on a test file instead of a real one, the compdb is
214optional; it will complain but it still works. Test matchers against the
215specified file by running `match <matcher>`, or simply `m <matcher>`. Use of
216`rlwrap` is highly recommended.
217
dchengce2375e2016-01-12 01:09:07218`printf` debugging:
Daniel Cheng9ce2a302016-01-16 01:17:57219
dchengce2375e2016-01-12 01:09:07220```c++
221 clang::Decl* decl = result.Nodes.getNodeAs<clang::Decl>("decl");
222 decl->dumpColor();
223 clang::Stmt* stmt = result.Nodes.getNodeAs<clang::Stmt>("stmt");
224 stmt->dumpColor();
andybons3322f762015-08-24 21:37:09225```
Daniel Cheng9ce2a302016-01-16 01:17:57226
dchengce2375e2016-01-12 01:09:07227By default, the script hides the output of the tool. The easiest way to change
228that is to `return 1` from the `main()` function of the clang tool.
andybons6eaa0c0d2015-08-26 20:12:52229
230## Testing
dchengce2375e2016-01-12 01:09:07231Synposis:
Daniel Cheng9ce2a302016-01-16 01:17:57232
andybons6eaa0c0d2015-08-26 20:12:52233```shell
Ramin Halavatieb3f807a2017-08-02 05:31:31234tools/clang/scripts/test_tool.py <tool name> [--apply-edits]
andybons3322f762015-08-24 21:37:09235```
andybons6eaa0c0d2015-08-26 20:12:52236
dchengce2375e2016-01-12 01:09:07237The name of the tool binary and the subdirectory for the tool in
238`//tools/clang` must match. The test runner finds all files that match the
Ramin Halavatieb3f807a2017-08-02 05:31:31239pattern `//tools/clang/<tool name>/tests/*-original.cc`, and runs the tool
240across those files.
241If `--apply-edits` switch is presented, tool outputs are applied to respective
242files and compared to the `*-expected.cc` version. If there is a mismatch, the
243result is saved in `*-actual.cc`.
244When `--apply-edits` switch is not presented, tool outputs are compared to
245`*-expected.txt` and if different, the result is saved in `*-actual.txt`. Note
246that in this case, only one test file is expected.
Daniel Cheng69413be2017-10-24 01:23:57247
John Palmer046f9872021-05-24 01:24:56248[//tools/clang]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/
Devon Loehr3b59be92025-03-10 14:16:16249[//tools/clang/ast_rewriter]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/ast_rewriter
Daniel Cheng69413be2017-10-24 01:23:57250[clang-docs-match-finder]: http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder.html
251[clang-docs-match-callback]: http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder_1_1MatchCallback.html
252[matcher-reference]: http://clang.llvm.org/docs/LibASTMatchersReference.html
253[clang-docs-clang-tool-run]: http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1ClangTool.html#acec91f63b45ac7ee2d6c94cb9c10dab3
254[clang-docs-replacement]: http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1Replacement.html
255[clang-docs]: http://clang.llvm.org/doxygen/index.html
256[clang-tooling-tutorial]: http://clang.llvm.org/docs/LibASTMatchersTutorial.html
John Palmer046f9872021-05-24 01:24:56257[//tools/clang/blink_gc_plugin]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/blink_gc_plugin/
258[//tools/clang/plugins]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/plugins/
259[//tools/clang/rewrite_to_chrome_style]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/rewrite_to_chrome_style/
Devon Loehr3b59be92025-03-10 14:16:16260[clang-tools-extra]: (https://clang.llvm.org/extra/index.html)
261[clang-matcher-tutorial]: (https://clang.llvm.org/docs/LibASTMatchers.html#astmatchers-writing)