blob: 2911a1aba70e027661da85dc218c159ed078e3bb [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
lukaszaf9b89e72016-12-28 19:43:06128Then run the actual clang tool to generate a list of edits:
Daniel Cheng9ce2a302016-01-16 01:17:57129
130```shell
Daniel Cheng51c55302017-05-04 00:39:16131tools/clang/scripts/run_tool.py --tool <path to tool> \
dchengce2375e2016-01-12 01:09:07132 --generate-compdb
Daniel Cheng51c55302017-05-04 00:39:16133 -p out/Debug <path 1> <path 2> ... >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07134```
andybons6eaa0c0d2015-08-26 20:12:52135
dchengce2375e2016-01-12 01:09:07136`--generate-compdb` can be omitted if the compile DB was already generated and
137the list of build flags and source files has not changed since generation.
138
139`<path 1>`, `<path 2>`, etc are optional arguments to filter the files to run
lukaszaf9b89e72016-12-28 19:43:06140the tool against. This is helpful when sharding global refactorings into smaller
dchengce2375e2016-01-12 01:09:07141chunks. For example, the following command will run the `empty_string` tool
lukaszaf9b89e72016-12-28 19:43:06142against just the `.c`, `.cc`, `.cpp`, `.m`, `.mm` files in `//net`. Note that
143the filtering is not applied to the *output* of the tool - the tool can emit
144edits that apply to files outside of `//cc` (i.e. edits that apply to headers
145from `//base` that got included by source files in `//cc`).
dchengce2375e2016-01-12 01:09:07146
147```shell
Daniel Cheng51c55302017-05-04 00:39:16148tools/clang/scripts/run_tool.py --tool empty_string \
dchengce2375e2016-01-12 01:09:07149 --generated-compdb \
Daniel Cheng51c55302017-05-04 00:39:16150 -p out/Debug net >/tmp/list-of-edits.debug
dchengce2375e2016-01-12 01:09:07151```
152
lukaszaf9b89e72016-12-28 19:43:06153Note that some header files might only be included from generated files (e.g.
154from only from some `.cpp` files under out/Debug/gen). To make sure that
155contents of such header files are processed by the clang tool, the clang tool
156needs to be run against the generated files. The only way to accomplish this
157today is to pass `--all` switch to `run_tool.py` - this will run the clang tool
158against all the sources from the compilation database.
159
160Finally, apply the edits as follows:
161
162```shell
163cat /tmp/list-of-edits.debug \
164 | tools/clang/scripts/extract_edits.py \
Daniel Cheng51c55302017-05-04 00:39:16165 | tools/clang/scripts/apply_edits.py -p out/Debug <path 1> <path 2> ...
lukaszaf9b89e72016-12-28 19:43:06166```
167
168The apply_edits.py tool will only apply edits to files actually under control of
169`git`. `<path 1>`, `<path 2>`, etc are optional arguments to further filter the
170files that the edits are applied to. Note that semantics of these filters is
171distinctly different from the arguments of `run_tool.py` filters - one set of
172filters controls which files are edited, the other set of filters controls which
173files the clang tool is run against.
174
dchengce2375e2016-01-12 01:09:07175## Debugging
176Dumping the AST for a file:
Daniel Cheng9ce2a302016-01-16 01:17:57177
andybons6eaa0c0d2015-08-26 20:12:52178```shell
andybons3322f762015-08-24 21:37:09179clang++ -cc1 -ast-dump foo.cc
180```
181
dchengce2375e2016-01-12 01:09:07182Using `clang-query` to dynamically test matchers (requires checking out
183and building [clang-tools-extras](https://github.com/llvm-mirror/clang-tools-extra)):
Daniel Cheng9ce2a302016-01-16 01:17:57184
andybons6eaa0c0d2015-08-26 20:12:52185```shell
dchengce2375e2016-01-12 01:09:07186clang-query -p path/to/compdb base/memory/ref_counted.cc
andybons3322f762015-08-24 21:37:09187```
188
dchengce2375e2016-01-12 01:09:07189`printf` debugging:
Daniel Cheng9ce2a302016-01-16 01:17:57190
dchengce2375e2016-01-12 01:09:07191```c++
192 clang::Decl* decl = result.Nodes.getNodeAs<clang::Decl>("decl");
193 decl->dumpColor();
194 clang::Stmt* stmt = result.Nodes.getNodeAs<clang::Stmt>("stmt");
195 stmt->dumpColor();
andybons3322f762015-08-24 21:37:09196```
Daniel Cheng9ce2a302016-01-16 01:17:57197
dchengce2375e2016-01-12 01:09:07198By default, the script hides the output of the tool. The easiest way to change
199that is to `return 1` from the `main()` function of the clang tool.
andybons6eaa0c0d2015-08-26 20:12:52200
201## Testing
dchengce2375e2016-01-12 01:09:07202Synposis:
Daniel Cheng9ce2a302016-01-16 01:17:57203
andybons6eaa0c0d2015-08-26 20:12:52204```shell
lukasza1333f1b92016-08-27 00:24:43205tools/clang/scripts/test_tool.py <tool name>
andybons3322f762015-08-24 21:37:09206```
andybons6eaa0c0d2015-08-26 20:12:52207
dchengce2375e2016-01-12 01:09:07208The name of the tool binary and the subdirectory for the tool in
209`//tools/clang` must match. The test runner finds all files that match the
210pattern `//tools/clang/<tool name>/tests/*-original.cc`, runs the tool across
211those files, and compared it to the `*-expected.cc` version. If there is a
212mismatch, the result is saved in `*-actual.cc`.