android_webview
apps
ash
base
build
build_overrides
buildtools
cc
chrome
chromecast
chromeos
clank
codelabs
components
content
crypto
dbus
device
docs
accessibility
autofill
chromeos
design
enterprise
experiments
fuchsia
gpu
graphics
images
infra
intl
ios
linux
login
mac
media
memory
memory-infra
patterns
privacy
privacy_budget
process
security
speed
speed_metrics
standards
telemetry_extension
testing
transcripts
ui
updater
webapps
website
webui
workflow
DIR_METADATA
OWNERS
README.md
accessibility.md
ad_tagging.md
adding_to_third_party.md
android_accessing_cpp_enums_in_java.md
android_accessing_cpp_features_in_java.md
android_accessing_cpp_switches_in_java.md
android_build_instructions.md
android_cast_build_instructions.md
android_debugging_instructions.md
android_dynamic_feature_modules.md
android_emulator.md
android_isolated_splits.md
android_jni_ownership_best_practices.md
android_logging.md
android_native_libraries.md
android_studio.md
angle_in_chromium.md
api_keys.md
asan.md
atom.md
benchmark_performance_regressions.md
bfcache.md
bitmap_pipeline.md
branch_gardener.md
building_old_revisions.md
callback.md
ccache_mac.md
chrome_browser_design_principles.md
chrome_os_logging.md
chrome_settings.md
chromedriver_status.md
chromeos_build_instructions.md
chromeos_glossary.md
chromium_browser_vs_google_chrome.md
cipd_and_3pp.md
cl_respect.md
cl_tips.md
clang.md
clang_code_coverage_wrapper.md
clang_format.md
clang_gardening.md
clang_sheriffing.md
clang_static_analyzer.md
clang_tidy.md
clang_tool_refactoring.md
clangd.md
clion.md
closure_compilation.md
cocoa_tips_and_tricks.md
code_review_owners.md
code_reviews.md
commit_checklist.md
component_build.md
configuration.md
contributing.md
cq_fault_attribution.md
cr_respect.md
cr_user_manual.md
cross_platform_ui.md
cygwin_dll_remapping_failure.md
dangling_ptr.md
dangling_ptr_guide.md
dbus_mojo_connection_service.md
debugging_with_crash_keys.md
dependencies.md
deterministic_builds.md
disassemble_code.md
documentation_best_practices.md
documentation_guidelines.md
early-hints.md
eclipse.md
emacs.md
erc_irc.md
flag_expiry.md
flag_guarding_guidelines.md
flag_ownership.md
frame_trees.md
gardener.md
gcs_dependencies.md
gdbinit.md
get_the_code.md
git_cookbook.md
git_submodules.md
git_tips.md
google_chrome_branded_builds.md
google_play_services.md
graphical_debugging_aid_chromium_views.md
gwp_asan.md
history_manipulation_intervention.md
how_cc_works.md
how_to_add_your_feature_flag.md
how_to_extend_web_test_framework.md
idn.md
initialize_blink_features.md
inlined_stack_traces.md
installation_at_vmware.md
ios_build_instructions.md
ios_infra.md
ios_voiceover.md
kiosk_mode.md
life_of_a_frame.md
lldbinit.md
mac_arm64.md
mac_build_instructions.md
mac_lld.md
modifying_session_history_serialization.md
modules.md
mojo_and_services.md
mojo_ipc_conversion.md
mojo_testing.md
native_relocations.md
navbar.md
navigation-request-navigation-state.gv
navigation-request-navigation-state.png
navigation.md
navigation_concepts.md
network_traffic_annotations.md
no_sources_assignment_filter.md
orderfile.md
origin_trials_integration.md
ozone_overview.md
parsing_test_results.md
pgo.md
piranha_plant.md
process_model_and_site_isolation.md
profiling.md
profiling_content_shell_on_android.md
proxy_auto_config.md
qtcreator.md
release_branch_guidance.md
render-frame-host-lifecycle-state.gv
render-frame-host-lifecycle-state.png
render_document.md
rust-unsafe.md
rust.md
seccomp_sandbox_crash_dumping.md
servicification.md
session_history.md
sheriff.md
shutdown.md
special_case_urls.md
static_initializers.md
sublime_ide.md
system_hardening_features.md
tab_helpers.md
threading_and_tasks.md
threading_and_tasks_faq.md
threading_and_tasks_testing.md
toolchain_support.md
tour_of_luci_ui.md
tpm_quick_ref.md
translation_screenshots.md
unretained_dangling_ptr_guide.md
unsafe_buffers.md
updating_clang.md
updating_clang_format_binaries.md
use_counter_wiki.md
useful_urls.md
user_data_dir.md
user_data_storage.md
user_handle_mapping.md
vanilla_msysgit_workflow.md
vscode.md
vscode_python.md
webview_policies.md
win_cross.md
win_order_files.md
windows_build_instructions.md
windows_native_window_occlusion_tracking.md
windows_pwa_integration.md
windows_shortcut_and_taskbar_handling.md
windows_split_dll.md
windows_virtual_desktop_handling.md
wmax_tokens.md
working_remotely_with_android.md
writing_clang_plugins.md
extensions
fuchsia_web
gin
google_apis
gpu
headless
infra
internal
ios
ios_internal
ipc
media
mojo
native_client
native_client_sdk
net
pdf
ppapi
printing
remoting
rlz
sandbox
services
signing_keys
skia
sql
storage
styleguide
testing
third_party
tools
ui
url
v8
webkit
.clang-format
.clang-tidy
.clangd
.git-blame-ignore-revs
.gitallowed
.gitattributes
.gitignore
.gitmodules
.gn
.mailmap
.rustfmt.toml
.vpython3
.yapfignore
ATL_OWNERS
AUTHORS
BUILD.gn
CODE_OF_CONDUCT.md
CPPLINT.cfg
CRYPTO_OWNERS
DEPS
DIR_METADATA
LICENSE
LICENSE.chromium_os
OWNERS
PRESUBMIT.py
PRESUBMIT_test.py
PRESUBMIT_test_mocks.py
README.md
WATCHLISTS
codereview.settings

This is an implementation of an AST rewriter based on clang's AST matchers, designed to be usable as a template for learning how to make such rewriters/beginning new ones. The rewriter itself replaces instances of `b ? "true" : "false"` with `base::ToString(b)` in C++ code, and was used to make several contributions to crbug.com/335797528. Change-Id: I0602694ed1acb424cc3e5eb17a8e89837ded4350 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6322829 Commit-Queue: Devon Loehr <dloehr@google.com> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Cr-Commit-Position: refs/heads/main@{#1430219}
262 lines
11 KiB
Markdown
262 lines
11 KiB
Markdown
# Clang Tool Refactoring
|
|
|
|
[TOC]
|
|
|
|
## Introduction
|
|
|
|
Clang tools can help with global refactorings of Chromium code. Clang tools can
|
|
take advantage of clang's AST to perform refactorings that would be impossible
|
|
with a traditional find-and-replace regexp:
|
|
|
|
* Constructing `scoped_ptr<T>` from `NULL`: <https://crbug.com/173286>
|
|
* Implicit conversions of `scoped_refptr<T>` to `T*`: <https://crbug.com/110610>
|
|
* Rename everything in Blink to follow Chromium style: <https://crbug.com/563793>
|
|
* Clean up of deprecated `base::Value` APIs: <https://crbug.com/581865>
|
|
|
|
## Caveats
|
|
|
|
* Invocations of a clang tool runs on on only one build config at a time. For
|
|
example, running the tool across a `target_os="win"` build won't update code
|
|
that is guarded by `OS_POSIX`. Performing a global refactoring will often
|
|
require running the tool once for each build config.
|
|
|
|
## Prerequisites
|
|
|
|
A Chromium checkout created with `fetch` should have everything needed.
|
|
|
|
For convenience, add `third_party/llvm-build/Release+Asserts/bin` to `$PATH`.
|
|
|
|
## Writing the tool
|
|
|
|
LLVM uses C++11 and CMake. Source code for Chromium clang tools lives in
|
|
[//tools/clang]. It is generally easiest to use one of the already-written tools
|
|
as the base for writing a new tool; the tool in [//tools/clang/ast_rewriter] is
|
|
designed for this purpose, and includes explanations its major parts.
|
|
|
|
Chromium clang tools generally follow this pattern:
|
|
|
|
1. Instantiate a
|
|
[`clang::ast_matchers::MatchFinder`][clang-docs-match-finder].
|
|
2. Develop one or most [AST matchers][clang-matcher-tutorial] to locate the
|
|
patterns of interest.
|
|
1. `clang-query` is of great use for this part
|
|
3. Create a subclass of
|
|
[`clang::ast_matchers::MatchFinder::MatchCallback`][clang-docs-match-callback]
|
|
to determine what actions to take on each match, and register it with
|
|
`addMatcher()`.
|
|
3. Create a new `clang::tooling::FrontendActionFactory` from the `MatchFinder`.
|
|
4. Run the action across the specified files with
|
|
[`clang::tooling::ClangTool::run`][clang-docs-clang-tool-run].
|
|
5. Serialize generated [`clang::tooling::Replacement`][clang-docs-replacement]s
|
|
to `stdout`.
|
|
|
|
Useful references when writing the tool:
|
|
|
|
* [Clang doxygen reference][clang-docs]
|
|
* [Tutorial for building tools using LibTooling and
|
|
LibASTMatchers][clang-tooling-tutorial]
|
|
* [Tutorial for AST matchers][clang-matcher-tutorial]
|
|
* [AST matcher reference][matcher-reference]
|
|
|
|
### Edit serialization format
|
|
Tools do not directly edit files; rather, they output a series of _edits_ to be
|
|
applied later, which have the following format:
|
|
|
|
```
|
|
==== BEGIN EDITS ====
|
|
r:::path/to/file/to/edit:::offset1:::length1:::replacement text
|
|
r:::path/to/file/to/edit:::offset2:::length2:::replacement text
|
|
r:::path/to/file2/to/edit:::offset3:::length3:::replacement text
|
|
include-user-header:::path/to/file2/to/edit:::-1:::-1:::header/file/to/include.h
|
|
|
|
...
|
|
|
|
==== END EDITS ====
|
|
```
|
|
|
|
The header and footer are required. Each line between the header and footer
|
|
represents one edit. Fields are separated by `:::`, and the first field must
|
|
be `r` (for replacement) or `include-user-header`.
|
|
A deletion is an edit with no replacement text.
|
|
|
|
The edits are applied by [`apply_edits.py`](#Running), which understands certain
|
|
conventions:
|
|
|
|
* The clang tool should munge newlines in replacement text to `\0`. The script
|
|
knows to translate `\0` back to newlines when applying edits.
|
|
* When removing an element from a 'list' (e.g. function parameters,
|
|
initializers), the clang tool should emit a deletion for just the element.
|
|
The script understands how to extend the deletion to remove commas, etc. as
|
|
needed.
|
|
|
|
TODO: Document more about `SourceLocation` and how spelling loc differs from
|
|
expansion loc, etc.
|
|
|
|
### Why not RefactoringTool?
|
|
While clang has a [`clang::tooling::RefactoringTool`](http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1RefactoringTool.html)
|
|
to automatically apply the generated replacements and save the results, it
|
|
doesn't work well for Chromium:
|
|
|
|
* Clang tools run actions serially, so run time scales poorly to tens of
|
|
thousands of files.
|
|
* A parsing error in any file (quite common in NaCl source) prevents any of
|
|
the generated replacements from being applied.
|
|
|
|
## Building
|
|
Synopsis:
|
|
|
|
```shell
|
|
tools/clang/scripts/build.py --bootstrap --without-android --without-fuchsia \
|
|
--extra-tools rewrite_to_chrome_style
|
|
```
|
|
|
|
Running this command builds the [Oilpan plugin][//tools/clang/blink_gc_plugin],
|
|
the [Chrome style plugin][//tools/clang/plugins], and the [Blink to Chrome style
|
|
rewriter][//tools/clang/rewrite_to_chrome_style]. Additional arguments to
|
|
`--extra-tools` should be the name of subdirectories in [//tools/clang]. The
|
|
tool binary will be located in `third_party/llvm-build/Release+Asserts/bin`.
|
|
|
|
It is important to use --bootstrap as there appear to be [bugs](https://crbug.com/580745)
|
|
in the clang library this script produces if you build it with gcc, which is the default.
|
|
|
|
Once clang is bootsrapped, incremental builds can be done by invoking `ninja` in
|
|
the `third_party/llvm-build/Release+Asserts` directory. In particular,
|
|
recompiling solely the tool you are writing can be accomplished by executing
|
|
`ninja rewrite_to_chrome_style` (replace `rewrite_to_chrome_style` with your
|
|
tool's name).
|
|
|
|
## Running
|
|
First, build all Chromium targets to avoid failures due to missing dependencies
|
|
that are generated as part of the build:
|
|
|
|
```shell
|
|
ninja -C out/Debug # For non-Windows
|
|
ninja -d keeprsp -C out/Debug # For Windows
|
|
|
|
# experimental alternative:
|
|
$gen_targets = $(ninja -C out/Debug -t targets all \
|
|
| grep '^gen/[^: ]*\.[ch][pc]*:' \
|
|
| cut -f 1 -d :)
|
|
ninja -C out/Debug $gen_targets
|
|
```
|
|
|
|
Note that running the clang tool with precompiled headers enabled currently
|
|
produces errors. This can be avoided by setting
|
|
`enable_precompiled_headers = false` in the build's gn args.
|
|
|
|
Then run the actual clang tool to generate a list of edits:
|
|
|
|
```shell
|
|
tools/clang/scripts/run_tool.py --tool <path to tool> \
|
|
--generate-compdb
|
|
-p out/Debug <path 1> <path 2> ... >/tmp/list-of-edits.debug
|
|
```
|
|
|
|
`--generate-compdb` can be omitted if the compile DB was already generated and
|
|
the list of build flags and source files has not changed since generation.
|
|
|
|
If cross-compiling, specify `--target_os`. See `gn help target_os` for
|
|
possible values. For example, when cross-compiling a Windows build on
|
|
Linux/Mac, use `--target_os=win`.
|
|
|
|
`<path 1>`, `<path 2>`, etc are optional arguments to filter the files to run
|
|
the tool against. This is helpful when sharding global refactorings into smaller
|
|
chunks. For example, the following command will run the `empty_string` tool
|
|
against just the `.c`, `.cc`, `.cpp`, `.m`, `.mm` files in `//net`. Note that
|
|
the filtering is not applied to the *output* of the tool - the tool can emit
|
|
edits that apply to files outside of `//net` (i.e. edits that apply to headers
|
|
from `//base` that got included by source files in `//net`).
|
|
|
|
```shell
|
|
tools/clang/scripts/run_tool.py --tool empty_string \
|
|
--generate-compdb \
|
|
-p out/Debug net >/tmp/list-of-edits.debug
|
|
```
|
|
|
|
Note that some header files might only be included from generated files (e.g.
|
|
from only from some `.cpp` files under out/Debug/gen). To make sure that
|
|
contents of such header files are processed by the clang tool, the clang tool
|
|
needs to be run against the generated files. The only way to accomplish this
|
|
today is to pass `--all` switch to `run_tool.py` - this will run the clang tool
|
|
against all the sources from the compilation database.
|
|
|
|
Finally, apply the edits as follows:
|
|
|
|
```shell
|
|
cat /tmp/list-of-edits.debug \
|
|
| tools/clang/scripts/extract_edits.py \
|
|
| tools/clang/scripts/apply_edits.py -p out/Debug <path 1> <path 2> ...
|
|
```
|
|
|
|
The apply_edits.py tool will only apply edits to files actually under control of
|
|
`git`. `<path 1>`, `<path 2>`, etc are optional arguments to further filter the
|
|
files that the edits are applied to. Note that semantics of these filters is
|
|
distinctly different from the arguments of `run_tool.py` filters - one set of
|
|
filters controls which files are edited, the other set of filters controls which
|
|
files the clang tool is run against.
|
|
|
|
## Debugging
|
|
Dumping the AST for a file:
|
|
|
|
```shell
|
|
clang++ -Xclang -ast-dump -std=c++14 foo.cc | less -R
|
|
```
|
|
|
|
Using `clang-query` to dynamically test matchers (requires checking out
|
|
and building [clang-tools-extra][]; this should happen automatically).
|
|
The binary is located in `third_party/llvm-build/Release+Asserts/bin`:
|
|
|
|
```shell
|
|
clang-query -p path/to/compdb base/memory/ref_counted.cc
|
|
```
|
|
|
|
If you're running it on a test file instead of a real one, the compdb is
|
|
optional; it will complain but it still works. Test matchers against the
|
|
specified file by running `match <matcher>`, or simply `m <matcher>`. Use of
|
|
`rlwrap` is highly recommended.
|
|
|
|
`printf` debugging:
|
|
|
|
```c++
|
|
clang::Decl* decl = result.Nodes.getNodeAs<clang::Decl>("decl");
|
|
decl->dumpColor();
|
|
clang::Stmt* stmt = result.Nodes.getNodeAs<clang::Stmt>("stmt");
|
|
stmt->dumpColor();
|
|
```
|
|
|
|
By default, the script hides the output of the tool. The easiest way to change
|
|
that is to `return 1` from the `main()` function of the clang tool.
|
|
|
|
## Testing
|
|
Synposis:
|
|
|
|
```shell
|
|
tools/clang/scripts/test_tool.py <tool name> [--apply-edits]
|
|
```
|
|
|
|
The name of the tool binary and the subdirectory for the tool in
|
|
`//tools/clang` must match. The test runner finds all files that match the
|
|
pattern `//tools/clang/<tool name>/tests/*-original.cc`, and runs the tool
|
|
across those files.
|
|
If `--apply-edits` switch is presented, tool outputs are applied to respective
|
|
files and compared to the `*-expected.cc` version. If there is a mismatch, the
|
|
result is saved in `*-actual.cc`.
|
|
When `--apply-edits` switch is not presented, tool outputs are compared to
|
|
`*-expected.txt` and if different, the result is saved in `*-actual.txt`. Note
|
|
that in this case, only one test file is expected.
|
|
|
|
[//tools/clang]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/
|
|
[//tools/clang/ast_rewriter]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/ast_rewriter
|
|
[clang-docs-match-finder]: http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder.html
|
|
[clang-docs-match-callback]: http://clang.llvm.org/doxygen/classclang_1_1ast__matchers_1_1MatchFinder_1_1MatchCallback.html
|
|
[matcher-reference]: http://clang.llvm.org/docs/LibASTMatchersReference.html
|
|
[clang-docs-clang-tool-run]: http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1ClangTool.html#acec91f63b45ac7ee2d6c94cb9c10dab3
|
|
[clang-docs-replacement]: http://clang.llvm.org/doxygen/classclang_1_1tooling_1_1Replacement.html
|
|
[clang-docs]: http://clang.llvm.org/doxygen/index.html
|
|
[clang-tooling-tutorial]: http://clang.llvm.org/docs/LibASTMatchersTutorial.html
|
|
[//tools/clang/blink_gc_plugin]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/blink_gc_plugin/
|
|
[//tools/clang/plugins]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/plugins/
|
|
[//tools/clang/rewrite_to_chrome_style]: https://chromium.googlesource.com/chromium/src/+/main/tools/clang/rewrite_to_chrome_style/
|
|
[clang-tools-extra]: (https://clang.llvm.org/extra/index.html)
|
|
[clang-matcher-tutorial]: (https://clang.llvm.org/docs/LibASTMatchers.html#astmatchers-writing)
|