blob: 9b49df11b3a9c7c57b7bf1815967bb5dcd3c0113 [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
90* Clang tools run actions serially, so runtime scales poorly to tens of
91 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
dchengce2375e2016-01-12 01:09:07113## Running
qyearsleyc0dc6f42016-12-02 22:13:39114First, build all Chromium targets to avoid failures due to missing dependencies
dchengce2375e2016-01-12 01:09:07115that are generated as part of the build:
danakjef9f1fa2016-01-16 00:37:28116
dchengce2375e2016-01-12 01:09:07117```shell
danakjca6b31b52016-12-22 22:05:53118ninja -C out/Debug # For non-Windows
119ninja -d keeprsp -C out/Debug # For Windows
lukaszaf9b89e72016-12-28 19:43:06120
121# experimental alternative:
122$gen_targets = $(ninja -C out/gn -t targets all \
123 | grep '^gen/[^: ]*\.[ch][pc]*:' \
124 | cut -f 1 -d :`)
125ninja -C out/Debug $gen_targets
danakjca6b31b52016-12-22 22:05:53126```
127
128On Windows, generate the compile DB first, and after making any source changes.
129Then omit the `--generate-compdb` in later steps.
130
131```shell
132tools/clang/scripts/generate_win_compdb.py out/Debug
andybons3322f762015-08-24 21:37:09133```
andybons6eaa0c0d2015-08-26 20:12:52134
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
dchengce2375e2016-01-12 01:09:07138tools/clang/scripts/run_tool.py <toolname> \
139 --generate-compdb
lukaszaf9b89e72016-12-28 19:43:06140 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
155tools/clang/scripts/run_tool.py empty_string \
156 --generated-compdb \
lukaszaf9b89e72016-12-28 19:43:06157 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 \
172 | tools/clang/scripts/apply_edits.py out/Debug <path 1> <path 2> ...
173```
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
lukasza1333f1b92016-08-27 00:24:43212tools/clang/scripts/test_tool.py <tool name>
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
217pattern `//tools/clang/<tool name>/tests/*-original.cc`, runs the tool across
218those files, and compared it to the `*-expected.cc` version. If there is a
219mismatch, the result is saved in `*-actual.cc`.