blob: 765f37621e63a7ed93e6278fa5a5efbbc4a5c43b [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
dchengce2375e2016-01-12 01:09:0718An invocation of the clang tool runs on one build config. Code that only
19compiles on one platform or code that is guarded by a set of compile-time flags
20can be problematic. Performing a global refactoring typically requires running
21the tool once in each build config with code that needs to be updated.
22
23Other minor issues:
24
25* Requires a git checkout.
andybons6eaa0c0d2015-08-26 20:12:5226
27## Prerequisites
28
dchengce2375e2016-01-12 01:09:0729A Chromium checkout created with `fetch` should have everything needed.
andybons6eaa0c0d2015-08-26 20:12:5230
dchengce2375e2016-01-12 01:09:0731For convenience, add `third_party/llvm-build/Release+Asserts/bin` to `$PATH`.
andybons6eaa0c0d2015-08-26 20:12:5232
dchengce2375e2016-01-12 01:09:0733## Writing the tool
andybons6eaa0c0d2015-08-26 20:12:5234
dchengce2375e2016-01-12 01:09:0735LLVM uses C++11 and CMake. Source code for Chromium clang tools lives in
36[//tools/clang](https://chromium.googlesource.com/chromium/src/tools/clang/+/master).
37It is generally easiest to use one of the already-written tools as the base for
38writing a new tool.
andybons6eaa0c0d2015-08-26 20:12:5239
dchengce2375e2016-01-12 01:09:0740Chromium clang tools generally follow this pattern:
41
danakjef9f1fa2016-01-16 00:37:28421. Instantiate a [`clang::ast_matchers::MatchFinder`](http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder.html).
432. Call `addMatcher()` to register [`clang::ast_matchers::MatchFinder::MatchCallback`](http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder_1_1MatchCallback.html)
44 actions to execute when [matching](http://clang.llvm.org/docs/LibASTMatchersReference.html)
45 the AST.
dchengce2375e2016-01-12 01:09:07463. Create a new `clang::tooling::FrontendActionFactory` from the `MatchFinder`.
474. Run the action across the specified files with
48 [`clang::tooling::ClangTool::run`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1ClangTool.html#acec91f63b45ac7ee2d6c94cb9c10dab3).
danakjef9f1fa2016-01-16 00:37:28495. Serialize generated [`clang::tooling::Replacement`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1Replacement.html)s
50 to `stdout`.
dchengce2375e2016-01-12 01:09:0751
52Other useful references when writing the tool:
53
54* [Clang doxygen reference](http://clang.llvm.org/doxygen/index.html)
55* [Tutorial for building tools using LibTooling and LibASTMatchers](http://clang.llvm.org/docs/LibASTMatchersTutorial.html)
56
57### Edit serialization format
58```
59==== BEGIN EDITS ====
60r:::path/to/file1:::offset1:::length1:::replacement text
61r:::path/to/file2:::offset2:::length2:::replacement text
62
63 ...
64
65==== END EDITS ====
andybons3322f762015-08-24 21:37:0966```
67
dchengce2375e2016-01-12 01:09:0768The header and footer are required. Each line between the header and footer
69represents one edit. Fields are separated by `:::`, and the first field must
70be `r` (for replacement). In the future, this may be extended to handle header
71insertion/removal. A deletion is an edit with no replacement text.
andybons6eaa0c0d2015-08-26 20:12:5272
lukaszaf9b89e72016-12-28 19:43:0673The edits are applied by [`apply_edits.py`](#Running), which understands certain
dchengce2375e2016-01-12 01:09:0774conventions:
75
lukaszaf9b89e72016-12-28 19:43:0676* The clang tool should munge newlines in replacement text to `\0`. The script
dchengce2375e2016-01-12 01:09:0777 knows to translate `\0` back to newlines when applying edits.
78* When removing an element from a 'list' (e.g. function parameters,
lukaszaf9b89e72016-12-28 19:43:0679 initializers), the clang tool should emit a deletion for just the element.
80 The script understands how to extend the deletion to remove commas, etc. as
dchengce2375e2016-01-12 01:09:0781 needed.
82
83TODO: Document more about `SourceLocation` and how spelling loc differs from
84expansion loc, etc.
85
86### Why not RefactoringTool?
danakjef9f1fa2016-01-16 00:37:2887While clang has a [`clang::tooling::RefactoringTool`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1RefactoringTool.html)
88to automatically apply the generated replacements and save the results, it
89doesn't work well for Chromium:
dchengce2375e2016-01-12 01:09:0790
Daniel Cheng82f80d62017-05-18 05:39:3891* Clang tools run actions serially, so run time scales poorly to tens of
dchengce2375e2016-01-12 01:09:0792 thousands of files.
93* A parsing error in any file (quite common in NaCl source) prevents any of
94 the generated replacements from being applied.
95
96## Building
97Synopsis:
danakjef9f1fa2016-01-16 00:37:2898
andybons6eaa0c0d2015-08-26 20:12:5299```shell
danakj30d0f8c92016-01-28 00:26:33100tools/clang/scripts/update.py --bootstrap --force-local-build --without-android \
dchengf2390712017-01-05 06:41:45101 --extra-tools rewrite_to_chrome_style
dchengce2375e2016-01-12 01:09:07102```
danakjef9f1fa2016-01-16 00:37:28103
dchengce2375e2016-01-12 01:09:07104Running this command builds the [Oilpan plugin](https://chromium.googlesource.com/chromium/src/+/master/tools/clang/blink_gc_plugin/),
105the [Chrome style
106plugin](https://chromium.googlesource.com/chromium/src/+/master/tools/clang/plugins/),
dchengf2390712017-01-05 06:41:45107and the [Blink to Chrome style rewriter](https://chromium.googlesource.com/chromium/src/+/master/tools/clang/rewrite_to_chrome_style/). Additional arguments to `--extra-tools` should be the name of
dchengce2375e2016-01-12 01:09:07108subdirectories in
109[//tools/clang](https://chromium.googlesource.com/chromium/src/+/master/tools/clang).
dchengce2375e2016-01-12 01:09:07110
danakj30d0f8c92016-01-28 00:26:33111It is important to use --bootstrap as there appear to be [bugs](https://crbug.com/580745)
112in the clang library this script produces if you build it with gcc, which is the default.
113
vabr9ed3f432017-06-09 07:30:42114Once clang is bootsrapped, incremental builds can be done by invoking `ninja` in
115the `third_party/llvm-build/Release+Asserts` directory. In particular,
116recompiling solely the tool you are writing can be accomplished by executing
117`ninja rewrite_to_chrome_style` (replace `rewrite_to_chrome_style` with your
118tool's name).
119
dchengce2375e2016-01-12 01:09:07120## Running
qyearsleyc0dc6f42016-12-02 22:13:39121First, build all Chromium targets to avoid failures due to missing dependencies
dchengce2375e2016-01-12 01:09:07122that are generated as part of the build:
danakjef9f1fa2016-01-16 00:37:28123
dchengce2375e2016-01-12 01:09:07124```shell
danakjca6b31b52016-12-22 22:05:53125ninja -C out/Debug # For non-Windows
126ninja -d keeprsp -C out/Debug # For Windows
lukaszaf9b89e72016-12-28 19:43:06127
128# experimental alternative:
129$gen_targets = $(ninja -C out/gn -t targets all \
130 | grep '^gen/[^: ]*\.[ch][pc]*:' \
131 | cut -f 1 -d :`)
132ninja -C out/Debug $gen_targets
danakjca6b31b52016-12-22 22:05:53133```
134
lukaszaf9b89e72016-12-28 19:43:06135Then run the actual clang tool to generate a list of edits:
Daniel Cheng9ce2a302016-01-16 01:17:57136
137```shell
Daniel Cheng51c55302017-05-04 00:39:16138tools/clang/scripts/run_tool.py --tool <path to tool> \
dchengce2375e2016-01-12 01:09:07139 --generate-compdb
Daniel Cheng51c55302017-05-04 00:39:16140 -p out/Debug <path 1> <path 2> ... >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07141```
andybons6eaa0c0d2015-08-26 20:12:52142
dchengce2375e2016-01-12 01:09:07143`--generate-compdb` can be omitted if the compile DB was already generated and
144the list of build flags and source files has not changed since generation.
145
146`<path 1>`, `<path 2>`, etc are optional arguments to filter the files to run
lukaszaf9b89e72016-12-28 19:43:06147the tool against. This is helpful when sharding global refactorings into smaller
dchengce2375e2016-01-12 01:09:07148chunks. For example, the following command will run the `empty_string` tool
lukaszaf9b89e72016-12-28 19:43:06149against just the `.c`, `.cc`, `.cpp`, `.m`, `.mm` files in `//net`. Note that
150the filtering is not applied to the *output* of the tool - the tool can emit
151edits that apply to files outside of `//cc` (i.e. edits that apply to headers
152from `//base` that got included by source files in `//cc`).
dchengce2375e2016-01-12 01:09:07153
154```shell
Daniel Cheng51c55302017-05-04 00:39:16155tools/clang/scripts/run_tool.py --tool empty_string \
dchengce2375e2016-01-12 01:09:07156 --generated-compdb \
Daniel Cheng51c55302017-05-04 00:39:16157 -p out/Debug net >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07158```
159
lukaszaf9b89e72016-12-28 19:43:06160Note that some header files might only be included from generated files (e.g.
161from only from some `.cpp` files under out/Debug/gen). To make sure that
162contents of such header files are processed by the clang tool, the clang tool
163needs to be run against the generated files. The only way to accomplish this
164today is to pass `--all` switch to `run_tool.py` - this will run the clang tool
165against all the sources from the compilation database.
166
167Finally, apply the edits as follows:
168
169```shell
170cat /tmp/list-of-edits.debug \
171 | tools/clang/scripts/extract_edits.py \
Daniel Cheng51c55302017-05-04 00:39:16172 | tools/clang/scripts/apply_edits.py -p out/Debug <path 1> <path 2> ...
lukaszaf9b89e72016-12-28 19:43:06173```
174
175The apply_edits.py tool will only apply edits to files actually under control of
176`git`. `<path 1>`, `<path 2>`, etc are optional arguments to further filter the
177files that the edits are applied to. Note that semantics of these filters is
178distinctly different from the arguments of `run_tool.py` filters - one set of
179filters controls which files are edited, the other set of filters controls which
180files the clang tool is run against.
181
dchengce2375e2016-01-12 01:09:07182## Debugging
183Dumping the AST for a file:
Daniel Cheng9ce2a302016-01-16 01:17:57184
andybons6eaa0c0d2015-08-26 20:12:52185```shell
andybons3322f762015-08-24 21:37:09186clang++ -cc1 -ast-dump foo.cc
187```
188
dchengce2375e2016-01-12 01:09:07189Using `clang-query` to dynamically test matchers (requires checking out
190and building [clang-tools-extras](https://github.com/llvm-mirror/clang-tools-extra)):
Daniel Cheng9ce2a302016-01-16 01:17:57191
andybons6eaa0c0d2015-08-26 20:12:52192```shell
dchengce2375e2016-01-12 01:09:07193clang-query -p path/to/compdb base/memory/ref_counted.cc
andybons3322f762015-08-24 21:37:09194```
195
dchengce2375e2016-01-12 01:09:07196`printf` debugging:
Daniel Cheng9ce2a302016-01-16 01:17:57197
dchengce2375e2016-01-12 01:09:07198```c++
199 clang::Decl* decl = result.Nodes.getNodeAs<clang::Decl>("decl");
200 decl->dumpColor();
201 clang::Stmt* stmt = result.Nodes.getNodeAs<clang::Stmt>("stmt");
202 stmt->dumpColor();
andybons3322f762015-08-24 21:37:09203```
Daniel Cheng9ce2a302016-01-16 01:17:57204
dchengce2375e2016-01-12 01:09:07205By default, the script hides the output of the tool. The easiest way to change
206that is to `return 1` from the `main()` function of the clang tool.
andybons6eaa0c0d2015-08-26 20:12:52207
208## Testing
dchengce2375e2016-01-12 01:09:07209Synposis:
Daniel Cheng9ce2a302016-01-16 01:17:57210
andybons6eaa0c0d2015-08-26 20:12:52211```shell
Ramin Halavatieb3f807a2017-08-02 05:31:31212tools/clang/scripts/test_tool.py <tool name> [--apply-edits]
andybons3322f762015-08-24 21:37:09213```
andybons6eaa0c0d2015-08-26 20:12:52214
dchengce2375e2016-01-12 01:09:07215The name of the tool binary and the subdirectory for the tool in
216`//tools/clang` must match. The test runner finds all files that match the
Ramin Halavatieb3f807a2017-08-02 05:31:31217pattern `//tools/clang/<tool name>/tests/*-original.cc`, and runs the tool
218across those files.
219If `--apply-edits` switch is presented, tool outputs are applied to respective
220files and compared to the `*-expected.cc` version. If there is a mismatch, the
221result is saved in `*-actual.cc`.
222When `--apply-edits` switch is not presented, tool outputs are compared to
223`*-expected.txt` and if different, the result is saved in `*-actual.txt`. Note
224that in this case, only one test file is expected.