blob: 8ee26f090edb116009bc3117c70ee56e97f7882e [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>
14
andybons6eaa0c0d2015-08-26 20:12:5215## Caveats
andybons3322f762015-08-24 21:37:0916
dchengce2375e2016-01-12 01:09:0717An invocation of the clang tool runs on one build config. Code that only
18compiles on one platform or code that is guarded by a set of compile-time flags
19can be problematic. Performing a global refactoring typically requires running
20the tool once in each build config with code that needs to be updated.
21
22Other minor issues:
23
24* Requires a git checkout.
andybons6eaa0c0d2015-08-26 20:12:5225
26## Prerequisites
27
dchengce2375e2016-01-12 01:09:0728A Chromium checkout created with `fetch` should have everything needed.
andybons6eaa0c0d2015-08-26 20:12:5229
dchengce2375e2016-01-12 01:09:0730For convenience, add `third_party/llvm-build/Release+Asserts/bin` to `$PATH`.
andybons6eaa0c0d2015-08-26 20:12:5231
dchengce2375e2016-01-12 01:09:0732## Writing the tool
andybons6eaa0c0d2015-08-26 20:12:5233
dchengce2375e2016-01-12 01:09:0734LLVM uses C++11 and CMake. Source code for Chromium clang tools lives in
35[//tools/clang](https://chromium.googlesource.com/chromium/src/tools/clang/+/master).
36It is generally easiest to use one of the already-written tools as the base for
37writing a new tool.
andybons6eaa0c0d2015-08-26 20:12:5238
dchengce2375e2016-01-12 01:09:0739Chromium clang tools generally follow this pattern:
40
danakjef9f1fa2016-01-16 00:37:28411. Instantiate a [`clang::ast_matchers::MatchFinder`](http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder.html).
422. Call `addMatcher()` to register [`clang::ast_matchers::MatchFinder::MatchCallback`](http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder_1_1MatchCallback.html)
43 actions to execute when [matching](http://clang.llvm.org/docs/LibASTMatchersReference.html)
44 the AST.
dchengce2375e2016-01-12 01:09:07453. Create a new `clang::tooling::FrontendActionFactory` from the `MatchFinder`.
464. Run the action across the specified files with
47 [`clang::tooling::ClangTool::run`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1ClangTool.html#acec91f63b45ac7ee2d6c94cb9c10dab3).
danakjef9f1fa2016-01-16 00:37:28485. Serialize generated [`clang::tooling::Replacement`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1Replacement.html)s
49 to `stdout`.
dchengce2375e2016-01-12 01:09:0750
51Other useful references when writing the tool:
52
53* [Clang doxygen reference](http://clang.llvm.org/doxygen/index.html)
54* [Tutorial for building tools using LibTooling and LibASTMatchers](http://clang.llvm.org/docs/LibASTMatchersTutorial.html)
55
56### Edit serialization format
57```
58==== BEGIN EDITS ====
59r:::path/to/file1:::offset1:::length1:::replacement text
60r:::path/to/file2:::offset2:::length2:::replacement text
61
62 ...
63
64==== END EDITS ====
andybons3322f762015-08-24 21:37:0965```
66
dchengce2375e2016-01-12 01:09:0767The header and footer are required. Each line between the header and footer
68represents one edit. Fields are separated by `:::`, and the first field must
69be `r` (for replacement). In the future, this may be extended to handle header
70insertion/removal. A deletion is an edit with no replacement text.
andybons6eaa0c0d2015-08-26 20:12:5271
lukaszaf9b89e72016-12-28 19:43:0672The edits are applied by [`apply_edits.py`](#Running), which understands certain
dchengce2375e2016-01-12 01:09:0773conventions:
74
lukaszaf9b89e72016-12-28 19:43:0675* The clang tool should munge newlines in replacement text to `\0`. The script
dchengce2375e2016-01-12 01:09:0776 knows to translate `\0` back to newlines when applying edits.
77* When removing an element from a 'list' (e.g. function parameters,
lukaszaf9b89e72016-12-28 19:43:0678 initializers), the clang tool should emit a deletion for just the element.
79 The script understands how to extend the deletion to remove commas, etc. as
dchengce2375e2016-01-12 01:09:0780 needed.
81
82TODO: Document more about `SourceLocation` and how spelling loc differs from
83expansion loc, etc.
84
85### Why not RefactoringTool?
danakjef9f1fa2016-01-16 00:37:2886While clang has a [`clang::tooling::RefactoringTool`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1RefactoringTool.html)
87to automatically apply the generated replacements and save the results, it
88doesn't work well for Chromium:
dchengce2375e2016-01-12 01:09:0789
Daniel Cheng82f80d62017-05-18 05:39:3890* Clang tools run actions serially, so run time scales poorly to tens of
dchengce2375e2016-01-12 01:09:0791 thousands of files.
92* A parsing error in any file (quite common in NaCl source) prevents any of
93 the generated replacements from being applied.
94
95## Building
96Synopsis:
danakjef9f1fa2016-01-16 00:37:2897
andybons6eaa0c0d2015-08-26 20:12:5298```shell
danakj30d0f8c92016-01-28 00:26:3399tools/clang/scripts/update.py --bootstrap --force-local-build --without-android \
dchengf2390712017-01-05 06:41:45100 --extra-tools rewrite_to_chrome_style
dchengce2375e2016-01-12 01:09:07101```
danakjef9f1fa2016-01-16 00:37:28102
dchengce2375e2016-01-12 01:09:07103Running this command builds the [Oilpan plugin](https://chromium.googlesource.com/chromium/src/+/master/tools/clang/blink_gc_plugin/),
104the [Chrome style
105plugin](https://chromium.googlesource.com/chromium/src/+/master/tools/clang/plugins/),
dchengf2390712017-01-05 06:41:45106and 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:07107subdirectories in
108[//tools/clang](https://chromium.googlesource.com/chromium/src/+/master/tools/clang).
dchengce2375e2016-01-12 01:09:07109
danakj30d0f8c92016-01-28 00:26:33110It is important to use --bootstrap as there appear to be [bugs](https://crbug.com/580745)
111in the clang library this script produces if you build it with gcc, which is the default.
112
vabr9ed3f432017-06-09 07:30:42113Once clang is bootsrapped, incremental builds can be done by invoking `ninja` in
114the `third_party/llvm-build/Release+Asserts` directory. In particular,
115recompiling solely the tool you are writing can be accomplished by executing
116`ninja rewrite_to_chrome_style` (replace `rewrite_to_chrome_style` with your
117tool's name).
118
dchengce2375e2016-01-12 01:09:07119## Running
qyearsleyc0dc6f42016-12-02 22:13:39120First, build all Chromium targets to avoid failures due to missing dependencies
dchengce2375e2016-01-12 01:09:07121that are generated as part of the build:
danakjef9f1fa2016-01-16 00:37:28122
dchengce2375e2016-01-12 01:09:07123```shell
danakjca6b31b52016-12-22 22:05:53124ninja -C out/Debug # For non-Windows
125ninja -d keeprsp -C out/Debug # For Windows
lukaszaf9b89e72016-12-28 19:43:06126
127# experimental alternative:
128$gen_targets = $(ninja -C out/gn -t targets all \
129 | grep '^gen/[^: ]*\.[ch][pc]*:' \
130 | cut -f 1 -d :`)
131ninja -C out/Debug $gen_targets
danakjca6b31b52016-12-22 22:05:53132```
133
lukaszaf9b89e72016-12-28 19:43:06134Then run the actual clang tool to generate a list of edits:
Daniel Cheng9ce2a302016-01-16 01:17:57135
136```shell
Daniel Cheng51c55302017-05-04 00:39:16137tools/clang/scripts/run_tool.py --tool <path to tool> \
dchengce2375e2016-01-12 01:09:07138 --generate-compdb
Daniel Cheng51c55302017-05-04 00:39:16139 -p out/Debug <path 1> <path 2> ... >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07140```
andybons6eaa0c0d2015-08-26 20:12:52141
dchengce2375e2016-01-12 01:09:07142`--generate-compdb` can be omitted if the compile DB was already generated and
143the list of build flags and source files has not changed since generation.
144
145`<path 1>`, `<path 2>`, etc are optional arguments to filter the files to run
lukaszaf9b89e72016-12-28 19:43:06146the tool against. This is helpful when sharding global refactorings into smaller
dchengce2375e2016-01-12 01:09:07147chunks. For example, the following command will run the `empty_string` tool
lukaszaf9b89e72016-12-28 19:43:06148against just the `.c`, `.cc`, `.cpp`, `.m`, `.mm` files in `//net`. Note that
149the filtering is not applied to the *output* of the tool - the tool can emit
150edits that apply to files outside of `//cc` (i.e. edits that apply to headers
151from `//base` that got included by source files in `//cc`).
dchengce2375e2016-01-12 01:09:07152
153```shell
Daniel Cheng51c55302017-05-04 00:39:16154tools/clang/scripts/run_tool.py --tool empty_string \
dchengce2375e2016-01-12 01:09:07155 --generated-compdb \
Daniel Cheng51c55302017-05-04 00:39:16156 -p out/Debug net >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07157```
158
lukaszaf9b89e72016-12-28 19:43:06159Note that some header files might only be included from generated files (e.g.
160from only from some `.cpp` files under out/Debug/gen). To make sure that
161contents of such header files are processed by the clang tool, the clang tool
162needs to be run against the generated files. The only way to accomplish this
163today is to pass `--all` switch to `run_tool.py` - this will run the clang tool
164against all the sources from the compilation database.
165
166Finally, apply the edits as follows:
167
168```shell
169cat /tmp/list-of-edits.debug \
170 | tools/clang/scripts/extract_edits.py \
Daniel Cheng51c55302017-05-04 00:39:16171 | tools/clang/scripts/apply_edits.py -p out/Debug <path 1> <path 2> ...
lukaszaf9b89e72016-12-28 19:43:06172```
173
174The apply_edits.py tool will only apply edits to files actually under control of
175`git`. `<path 1>`, `<path 2>`, etc are optional arguments to further filter the
176files that the edits are applied to. Note that semantics of these filters is
177distinctly different from the arguments of `run_tool.py` filters - one set of
178filters controls which files are edited, the other set of filters controls which
179files the clang tool is run against.
180
dchengce2375e2016-01-12 01:09:07181## Debugging
182Dumping the AST for a file:
Daniel Cheng9ce2a302016-01-16 01:17:57183
andybons6eaa0c0d2015-08-26 20:12:52184```shell
andybons3322f762015-08-24 21:37:09185clang++ -cc1 -ast-dump foo.cc
186```
187
dchengce2375e2016-01-12 01:09:07188Using `clang-query` to dynamically test matchers (requires checking out
189and building [clang-tools-extras](https://github.com/llvm-mirror/clang-tools-extra)):
Daniel Cheng9ce2a302016-01-16 01:17:57190
andybons6eaa0c0d2015-08-26 20:12:52191```shell
dchengce2375e2016-01-12 01:09:07192clang-query -p path/to/compdb base/memory/ref_counted.cc
andybons3322f762015-08-24 21:37:09193```
194
dchengce2375e2016-01-12 01:09:07195`printf` debugging:
Daniel Cheng9ce2a302016-01-16 01:17:57196
dchengce2375e2016-01-12 01:09:07197```c++
198 clang::Decl* decl = result.Nodes.getNodeAs<clang::Decl>("decl");
199 decl->dumpColor();
200 clang::Stmt* stmt = result.Nodes.getNodeAs<clang::Stmt>("stmt");
201 stmt->dumpColor();
andybons3322f762015-08-24 21:37:09202```
Daniel Cheng9ce2a302016-01-16 01:17:57203
dchengce2375e2016-01-12 01:09:07204By default, the script hides the output of the tool. The easiest way to change
205that is to `return 1` from the `main()` function of the clang tool.
andybons6eaa0c0d2015-08-26 20:12:52206
207## Testing
dchengce2375e2016-01-12 01:09:07208Synposis:
Daniel Cheng9ce2a302016-01-16 01:17:57209
andybons6eaa0c0d2015-08-26 20:12:52210```shell
Ramin Halavatieb3f807a2017-08-02 05:31:31211tools/clang/scripts/test_tool.py <tool name> [--apply-edits]
andybons3322f762015-08-24 21:37:09212```
andybons6eaa0c0d2015-08-26 20:12:52213
dchengce2375e2016-01-12 01:09:07214The name of the tool binary and the subdirectory for the tool in
215`//tools/clang` must match. The test runner finds all files that match the
Ramin Halavatieb3f807a2017-08-02 05:31:31216pattern `//tools/clang/<tool name>/tests/*-original.cc`, and runs the tool
217across those files.
218If `--apply-edits` switch is presented, tool outputs are applied to respective
219files and compared to the `*-expected.cc` version. If there is a mismatch, the
220result is saved in `*-actual.cc`.
221When `--apply-edits` switch is not presented, tool outputs are compared to
222`*-expected.txt` and if different, the result is saved in `*-actual.txt`. Note
223that in this case, only one test file is expected.