blob: c7e2d9662d8b80bbdc4cc37f1197b48a6349c58a [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This is implementation of a clang tool that rewrites raw pointer fields into
// raw_ptr<T>:
// Pointee* field_
// becomes:
// raw_ptr<Pointee> field_
//
// Note that the tool always emits two kinds of output:
// 1. Fields to exclude:
// - FilteredExprWriter
// 2. Edit/replacement directives:
// - FieldDeclRewriter
// - AffectedExprRewriter
// The rewriter is expected to be used twice, in two passes:
// 1. Output from the 1st pass should be used to generate fields-to-ignore.txt
// (or to augment the manually created exclusion list file)
// 2. The 2nd pass should use fields-to-ignore.txt from the first pass as input
// for the --exclude-fields cmdline parameter. The output from the 2nd pass
// can be used to perform the actual rewrite via extract_edits.py and
// apply_edits.py.
//
// For more details, see the doc here:
// https://docs.google.com/document/d/1chTvr3fSofQNV_PDPEHRyUgcJCQBgTDOOBriW9gIm9M
#include <assert.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/Lexer.h"
#include "clang/Lex/MacroArgs.h"
#include "clang/Lex/PPCallbacks.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Refactoring.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorOr.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TargetSelect.h"
using namespace clang::ast_matchers;
namespace {
// Include path that needs to be added to all the files where raw_ptr<...>
// replaces a raw pointer.
const char kIncludePath[] = "base/memory/raw_ptr.h";
// Name of a cmdline parameter that can be used to specify a file listing fields
// that should not be rewritten to use raw_ptr<T>.
//
// See also:
// - OutputSectionHelper
// - FilterFile
const char kExcludeFieldsParamName[] = "exclude-fields";
// Name of a cmdline parameter that can be used to specify a file listing
// regular expressions describing paths that should be excluded from the
// rewrite.
//
// See also:
// - PathFilterFile
const char kExcludePathsParamName[] = "exclude-paths";
// OutputSectionHelper helps gather and emit a section of output.
//
// The section of output is delimited in a way that makes it easy to extract it
// with sed like so:
// $ DELIM = ...
// $ cat ~/scratch/rewriter.out \
// | sed '/^==== BEGIN $DELIM ====$/,/^==== END $DELIM ====$/{//!b};d' \
// | sort | uniq > ~/scratch/some-out-of-band-output.txt
// (For DELIM="EDITS", there is also tools/clang/scripts/extract_edits.py.)
//
// Each output line is deduped and may be followed by optional comment tags:
// Some filter # tag1, tag2
// Another filter # tag1, tag2, tag3
// An output line with no comment tags
//
// The output lines are sorted. This helps provide deterministic output (even
// if AST matchers start firing in a different order after benign clang
// changes).
//
// See also:
// - FilterFile
// - OutputHelper
class OutputSectionHelper {
public:
explicit OutputSectionHelper(llvm::StringRef output_delimiter)
: output_delimiter_(output_delimiter.str()) {}
OutputSectionHelper(const OutputSectionHelper&) = delete;
OutputSectionHelper& operator=(const OutputSectionHelper&) = delete;
void Add(llvm::StringRef output_line, llvm::StringRef tag = "") {
// Look up |tags| associated with |output_line|. As a side effect of the
// lookup, |output_line| will be inserted if it wasn't already present in
// the map.
llvm::StringSet<>& tags = output_line_to_tags_[output_line];
if (!tag.empty())
tags.insert(tag);
}
void Emit() {
if (output_line_to_tags_.empty())
return;
llvm::outs() << "==== BEGIN " << output_delimiter_ << " ====\n";
for (const llvm::StringRef& output_line :
GetSortedKeys(output_line_to_tags_)) {
llvm::outs() << output_line;
const llvm::StringSet<>& tags = output_line_to_tags_[output_line];
if (!tags.empty()) {
std::vector<llvm::StringRef> sorted_tags = GetSortedKeys(tags);
std::string tags_comment =
llvm::join(sorted_tags.begin(), sorted_tags.end(), ", ");
llvm::outs() << " # " << tags_comment;
}
llvm::outs() << "\n";
}
llvm::outs() << "==== END " << output_delimiter_ << " ====\n";
}
private:
template <typename TValue>
std::vector<llvm::StringRef> GetSortedKeys(
const llvm::StringMap<TValue>& map) {
std::vector<llvm::StringRef> sorted(map.keys().begin(), map.keys().end());
std::sort(sorted.begin(), sorted.end());
return sorted;
}
std::string output_delimiter_;
llvm::StringMap<llvm::StringSet<>> output_line_to_tags_;
};
// Output format is documented in //docs/clang_tool_refactoring.md
class OutputHelper : public clang::tooling::SourceFileCallbacks {
public:
OutputHelper()
: edits_helper_("EDITS"), field_decl_filter_helper_("FIELD FILTERS") {}
~OutputHelper() = default;
OutputHelper(const OutputHelper&) = delete;
OutputHelper& operator=(const OutputHelper&) = delete;
void AddReplacement(const clang::SourceManager& source_manager,
const clang::SourceRange& replacement_range,
std::string replacement_text,
bool should_add_include = false) {
clang::tooling::Replacement replacement(
source_manager, clang::CharSourceRange::getCharRange(replacement_range),
replacement_text);
llvm::StringRef file_path = replacement.getFilePath();
if (file_path.empty())
return;
std::replace(replacement_text.begin(), replacement_text.end(), '\n', '\0');
std::string replacement_directive = llvm::formatv(
"r:::{0}:::{1}:::{2}:::{3}", file_path, replacement.getOffset(),
replacement.getLength(), replacement_text);
edits_helper_.Add(replacement_directive);
if (should_add_include) {
std::string include_directive = llvm::formatv(
"include-user-header:::{0}:::-1:::-1:::{1}", file_path, kIncludePath);
edits_helper_.Add(include_directive);
}
}
void AddFilteredField(const clang::FieldDecl& field_decl,
llvm::StringRef filter_tag) {
std::string qualified_name = field_decl.getQualifiedNameAsString();
field_decl_filter_helper_.Add(qualified_name, filter_tag);
}
private:
// clang::tooling::SourceFileCallbacks override:
bool handleBeginSource(clang::CompilerInstance& compiler) override {
const clang::FrontendOptions& frontend_options = compiler.getFrontendOpts();
assert((frontend_options.Inputs.size() == 1) &&
"run_tool.py should invoke the rewriter one file at a time");
const clang::FrontendInputFile& input_file = frontend_options.Inputs[0];
assert(input_file.isFile() &&
"run_tool.py should invoke the rewriter on actual files");
current_language_ = input_file.getKind().getLanguage();
return true; // Report that |handleBeginSource| succeeded.
}
// clang::tooling::SourceFileCallbacks override:
void handleEndSource() override {
if (ShouldSuppressOutput())
return;
edits_helper_.Emit();
field_decl_filter_helper_.Emit();
}
bool ShouldSuppressOutput() {
switch (current_language_) {
case clang::Language::Unknown:
case clang::Language::Asm:
case clang::Language::LLVM_IR:
case clang::Language::OpenCL:
case clang::Language::CUDA:
case clang::Language::RenderScript:
case clang::Language::HIP:
// Rewriter can't handle rewriting the current input language.
return true;
case clang::Language::C:
case clang::Language::ObjC:
// raw_ptr<T> requires C++. In particular, attempting to #include
// "base/memory/raw_ptr.h" from C-only compilation units will lead
// to compilation errors.
return true;
case clang::Language::CXX:
case clang::Language::OpenCLCXX:
case clang::Language::ObjCXX:
return false;
}
assert(false && "Unrecognized clang::Language");
return true;
}
OutputSectionHelper edits_helper_;
OutputSectionHelper field_decl_filter_helper_;
clang::Language current_language_ = clang::Language::Unknown;
};
llvm::StringRef GetFilePath(const clang::SourceManager& source_manager,
const clang::FieldDecl& field_decl) {
clang::SourceLocation loc = field_decl.getSourceRange().getBegin();
if (loc.isInvalid() || !loc.isFileID())
return llvm::StringRef();
clang::FileID file_id = source_manager.getDecomposedLoc(loc).first;
const clang::FileEntry* file_entry =
source_manager.getFileEntryForID(file_id);
if (!file_entry)
return llvm::StringRef();
return file_entry->getName();
}
AST_MATCHER(clang::FieldDecl, isInThirdPartyLocation) {
llvm::StringRef file_path =
GetFilePath(Finder->getASTContext().getSourceManager(), Node);
// Blink is part of the Chromium git repo, even though it contains
// "third_party" in its path.
if (file_path.contains("third_party/blink/"))
return false;
// Otherwise, just check if the paths contains the "third_party" substring.
// We don't want to rewrite content of such paths even if they are in the main
// Chromium git repository.
return file_path.contains("third_party");
}
AST_MATCHER(clang::FieldDecl, isInGeneratedLocation) {
llvm::StringRef file_path =
GetFilePath(Finder->getASTContext().getSourceManager(), Node);
return file_path.startswith("gen/") || file_path.contains("/gen/");
}
// Represents a filter file specified via cmdline.
class FilterFile {
public:
explicit FilterFile(const llvm::cl::opt<std::string>& cmdline_param) {
ParseInputFile(cmdline_param);
}
FilterFile(const FilterFile&) = delete;
FilterFile& operator=(const FilterFile&) = delete;
// Returns true if any of the filter file lines is exactly equal to |line|.
bool ContainsLine(llvm::StringRef line) const {
auto it = file_lines_.find(line);
return it != file_lines_.end();
}
// Returns true if |string_to_match| matches based on the filter file lines.
// Filter file lines can contain both inclusions and exclusions in the filter.
// Only returns true if |string_to_match| both matches an inclusion filter and
// is *not* matched by an exclusion filter.
bool ContainsSubstringOf(llvm::StringRef string_to_match) const {
if (!inclusion_substring_regex_.hasValue()) {
std::vector<std::string> regex_escaped_inclusion_file_lines;
std::vector<std::string> regex_escaped_exclusion_file_lines;
regex_escaped_inclusion_file_lines.reserve(file_lines_.size());
for (const llvm::StringRef& file_line : file_lines_.keys()) {
if (file_line.startswith("!")) {
regex_escaped_exclusion_file_lines.push_back(
llvm::Regex::escape(file_line.substr(1)));
} else {
regex_escaped_inclusion_file_lines.push_back(
llvm::Regex::escape(file_line));
}
}
std::string inclusion_substring_regex_pattern =
llvm::join(regex_escaped_inclusion_file_lines.begin(),
regex_escaped_inclusion_file_lines.end(), "|");
inclusion_substring_regex_.emplace(inclusion_substring_regex_pattern);
std::string exclusion_substring_regex_pattern =
llvm::join(regex_escaped_exclusion_file_lines.begin(),
regex_escaped_exclusion_file_lines.end(), "|");
exclusion_substring_regex_.emplace(exclusion_substring_regex_pattern);
}
return inclusion_substring_regex_->match(string_to_match) &&
!exclusion_substring_regex_->match(string_to_match);
}
private:
// Expected file format:
// - '#' character starts a comment (which gets ignored).
// - Blank or whitespace-only or comment-only lines are ignored.
// - Other lines are expected to contain a fully-qualified name of a field
// like:
// autofill::AddressField::address1_ # some comment
// - Templates are represented without template arguments, like:
// WTF::HashTable::table_ # some comment
void ParseInputFile(const llvm::cl::opt<std::string>& cmdline_param) {
std::string filepath = cmdline_param;
if (filepath.empty())
return;
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> file_or_err =
llvm::MemoryBuffer::getFile(filepath);
if (std::error_code err = file_or_err.getError()) {
llvm::errs() << "ERROR: Cannot open the file specified in --"
<< cmdline_param.ArgStr << " argument: " << filepath << ": "
<< err.message() << "\n";
assert(false);
return;
}
llvm::line_iterator it(**file_or_err, true /* SkipBlanks */, '#');
for (; !it.is_at_eof(); ++it) {
llvm::StringRef line = *it;
// Remove trailing comments.
size_t comment_start_pos = line.find('#');
if (comment_start_pos != llvm::StringRef::npos)
line = line.substr(0, comment_start_pos);
line = line.trim();
if (line.empty())
continue;
file_lines_.insert(line);
}
}
// Stores all file lines (after stripping comments and blank lines).
llvm::StringSet<> file_lines_;
// |file_lines_| is partitioned based on whether the line starts with a !
// (exclusion line) or not (inclusion line). Inclusion lines specify things to
// be matched by the filter. The exclusion lines specify what to force exclude
// from the filter. Lazily-constructed regex that matches strings that contain
// any of the inclusion lines in |file_lines_|.
mutable llvm::Optional<llvm::Regex> inclusion_substring_regex_;
// Lazily-constructed regex that matches strings that contain any of the
// exclusion lines in |file_lines_|.
mutable llvm::Optional<llvm::Regex> exclusion_substring_regex_;
};
AST_MATCHER_P(clang::FieldDecl,
isFieldDeclListedInFilterFile,
const FilterFile*,
Filter) {
return Filter->ContainsLine(Node.getQualifiedNameAsString());
}
AST_MATCHER_P(clang::FieldDecl,
isInLocationListedInFilterFile,
const FilterFile*,
Filter) {
llvm::StringRef file_path =
GetFilePath(Finder->getASTContext().getSourceManager(), Node);
return Filter->ContainsSubstringOf(file_path);
}
AST_MATCHER(clang::Decl, isInExternCContext) {
return Node.getLexicalDeclContext()->isExternCContext();
}
// Given:
// template <typename T, typename T2> class MyTemplate {}; // Node1 and Node4
// template <typename T2> class MyTemplate<int, T2> {}; // Node2
// template <> class MyTemplate<int, char> {}; // Node3
// void foo() {
// // This creates implicit template specialization (Node4) out of the
// // explicit template definition (Node1).
// MyTemplate<bool, double> v;
// }
// with the following AST nodes:
// ClassTemplateDecl MyTemplate - Node1
// | |-CXXRecordDecl class MyTemplate definition
// | `-ClassTemplateSpecializationDecl class MyTemplate definition - Node4
// ClassTemplatePartialSpecializationDecl class MyTemplate definition - Node2
// ClassTemplateSpecializationDecl class MyTemplate definition - Node3
//
// Matches AST node 4, but not AST node2 nor node3.
AST_MATCHER(clang::ClassTemplateSpecializationDecl,
isImplicitClassTemplateSpecialization) {
return !Node.isExplicitSpecialization();
}
// Matches CXXRecordDecls that are classified as trivial:
// https://en.cppreference.com/w/cpp/named_req/TrivialType
AST_MATCHER(clang::CXXRecordDecl, isTrivial) {
return Node.isTrivial();
}
// Given:
// template <typename T, typename T2> void foo(T t, T2 t2) {}; // N1 and N4
// template <typename T2> void foo<int, T2>(int t, T2 t) {}; // N2
// template <> void foo<int, char>(int t, char t2) {}; // N3
// void foo() {
// // This creates implicit template specialization (N4) out of the
// // explicit template definition (N1).
// foo<bool, double>(true, 1.23);
// }
// with the following AST nodes:
// FunctionTemplateDecl foo
// |-FunctionDecl 0x191da68 foo 'void (T, T2)' // N1
// `-FunctionDecl 0x194bf08 foo 'void (bool, double)' // N4
// FunctionTemplateDecl foo
// `-FunctionDecl foo 'void (int, T2)' // N2
// FunctionDecl foo 'void (int, char)' // N3
//
// Matches AST node N4, but not AST nodes N1, N2 nor N3.
AST_MATCHER(clang::FunctionDecl, isImplicitFunctionTemplateSpecialization) {
switch (Node.getTemplateSpecializationKind()) {
case clang::TSK_ImplicitInstantiation:
return true;
case clang::TSK_Undeclared:
case clang::TSK_ExplicitSpecialization:
case clang::TSK_ExplicitInstantiationDeclaration:
case clang::TSK_ExplicitInstantiationDefinition:
return false;
}
}
AST_MATCHER(clang::Type, anyCharType) {
return Node.isAnyCharacterType();
}
AST_POLYMORPHIC_MATCHER(isInMacroLocation,
AST_POLYMORPHIC_SUPPORTED_TYPES(clang::Decl,
clang::Stmt,
clang::TypeLoc)) {
return Node.getBeginLoc().isMacroID();
}
static bool IsAnnotated(const clang::Decl* decl,
const std::string& expected_annotation) {
clang::AnnotateAttr* attr = decl->getAttr<clang::AnnotateAttr>();
return attr && (attr->getAnnotation() == expected_annotation);
}
AST_MATCHER(clang::Decl, IsExclusionAnnotated) {
return IsAnnotated(&Node, "raw_ptr_exclusion");
}
// If |field_decl| declares a field in an implicit template specialization, then
// finds and returns the corresponding FieldDecl from the template definition.
// Otherwise, just returns the original |field_decl| argument.
const clang::FieldDecl* GetExplicitDecl(const clang::FieldDecl* field_decl) {
if (field_decl->isAnonymousStructOrUnion())
return field_decl; // Safe fallback - |field_decl| is not a pointer field.
const clang::CXXRecordDecl* record_decl =
clang::dyn_cast<clang::CXXRecordDecl>(field_decl->getParent());
if (!record_decl)
return field_decl; // Non-C++ records are never template instantiations.
const clang::CXXRecordDecl* pattern_decl =
record_decl->getTemplateInstantiationPattern();
if (!pattern_decl)
return field_decl; // |pattern_decl| is not a template instantiation.
if (record_decl->getTemplateSpecializationKind() !=
clang::TemplateSpecializationKind::TSK_ImplicitInstantiation) {
return field_decl; // |field_decl| was in an *explicit* specialization.
}
// Find the field decl with the same name in |pattern_decl|.
clang::DeclContextLookupResult lookup_result =
pattern_decl->lookup(field_decl->getDeclName());
assert(!lookup_result.empty());
const clang::NamedDecl* found_decl = lookup_result.front();
assert(found_decl);
field_decl = clang::dyn_cast<clang::FieldDecl>(found_decl);
assert(field_decl);
return field_decl;
}
// Given:
// template <typename T>
// class MyTemplate {
// T field; // This is an explicit field declaration.
// };
// void foo() {
// // This creates implicit template specialization for MyTemplate,
// // including an implicit |field| declaration.
// MyTemplate<int> v;
// v.field = 123;
// }
// and
// innerMatcher that will match the explicit |T field| declaration (but not
// necessarily the implicit template declarations),
// hasExplicitFieldDecl(innerMatcher) will match both explicit and implicit
// field declarations.
//
// For example, |member_expr_matcher| below will match |v.field| in the example
// above, even though the type of |v.field| is |int|, rather than |T| (matched
// by substTemplateTypeParmType()):
// auto explicit_field_decl_matcher =
// fieldDecl(hasType(substTemplateTypeParmType()));
// auto member_expr_matcher = memberExpr(member(fieldDecl(
// hasExplicitFieldDecl(explicit_field_decl_matcher))))
AST_MATCHER_P(clang::FieldDecl,
hasExplicitFieldDecl,
clang::ast_matchers::internal::Matcher<clang::FieldDecl>,
InnerMatcher) {
const clang::FieldDecl* explicit_field_decl = GetExplicitDecl(&Node);
return InnerMatcher.matches(*explicit_field_decl, Finder, Builder);
}
// If |original_param| declares a parameter in an implicit template
// specialization of a function or method, then finds and returns the
// corresponding ParmVarDecl from the template definition. Otherwise, just
// returns the |original_param| argument.
//
// Note: nullptr may be returned in rare, unimplemented cases.
const clang::ParmVarDecl* GetExplicitDecl(
const clang::ParmVarDecl* original_param) {
const clang::FunctionDecl* original_func =
clang::dyn_cast<clang::FunctionDecl>(original_param->getDeclContext());
if (!original_func) {
// |!original_func| may happen when the ParmVarDecl is part of a
// FunctionType, but not part of a FunctionDecl:
// base::RepeatingCallback<void(int parm_var_decl_here)>
//
// In theory, |parm_var_decl_here| can also represent an implicit template
// specialization in this scenario. OTOH, it should be rare + shouldn't
// matter for this rewriter, so for now let's just return the
// |original_param|.
//
// TODO: Implement support for this scenario.
return nullptr;
}
const clang::FunctionDecl* pattern_func =
original_func->getTemplateInstantiationPattern();
if (!pattern_func) {
// |original_func| is not a template instantiation - return the
// |original_param|.
return original_param;
}
// See if |pattern_func| has a parameter that is a template parameter pack.
bool has_param_pack = false;
unsigned int index_of_param_pack = std::numeric_limits<unsigned int>::max();
for (unsigned int i = 0; i < pattern_func->getNumParams(); i++) {
const clang::ParmVarDecl* pattern_param = pattern_func->getParamDecl(i);
if (!pattern_param->isParameterPack())
continue;
if (has_param_pack) {
// TODO: Implement support for multiple parameter packs.
return nullptr;
}
has_param_pack = true;
index_of_param_pack = i;
}
// Find and return the corresponding ParmVarDecl from |pattern_func|.
unsigned int original_index = original_param->getFunctionScopeIndex();
unsigned int pattern_index = std::numeric_limits<unsigned int>::max();
if (!has_param_pack) {
pattern_index = original_index;
} else {
// |original_func| has parameters that look like this:
// l1, l2, l3, p1, p2, p3, t1, t2, t3
// where
// lN is a leading, non-pack parameter
// pN is an expansion of a template parameter pack
// tN is a trailing, non-pack parameter
// Using the knowledge above, let's adjust |pattern_index| as needed.
unsigned int leading_param_num = index_of_param_pack; // How many |lN|.
unsigned int pack_expansion_num = // How many |pN| above.
original_func->getNumParams() - pattern_func->getNumParams() + 1;
if (original_index < leading_param_num) {
// |original_param| is a leading, non-pack parameter.
pattern_index = original_index;
} else if (leading_param_num <= original_index &&
original_index < (leading_param_num + pack_expansion_num)) {
// |original_param| is an expansion of a template pack parameter.
pattern_index = index_of_param_pack;
} else if ((leading_param_num + pack_expansion_num) <= original_index) {
// |original_param| is a trailing, non-pack parameter.
pattern_index = original_index - pack_expansion_num + 1;
}
}
assert(pattern_index < pattern_func->getNumParams());
return pattern_func->getParamDecl(pattern_index);
}
AST_MATCHER_P(clang::ParmVarDecl,
hasExplicitParmVarDecl,
clang::ast_matchers::internal::Matcher<clang::ParmVarDecl>,
InnerMatcher) {
const clang::ParmVarDecl* explicit_param = GetExplicitDecl(&Node);
if (!explicit_param) {
// Rare, unimplemented case - fall back to returning "no match".
return false;
}
return InnerMatcher.matches(*explicit_param, Finder, Builder);
}
// Returns |true| if and only if:
// 1. |a| and |b| are in the same file (e.g. |false| is returned if any location
// is within macro scratch space or a similar location; similarly |false| is
// returned if |a| and |b| are in different files).
// 2. |a| and |b| overlap.
bool IsOverlapping(const clang::SourceManager& source_manager,
const clang::SourceRange& a,
const clang::SourceRange& b) {
clang::FullSourceLoc a1(a.getBegin(), source_manager);
clang::FullSourceLoc a2(a.getEnd(), source_manager);
clang::FullSourceLoc b1(b.getBegin(), source_manager);
clang::FullSourceLoc b2(b.getEnd(), source_manager);
// Are all locations in a file?
if (!a1.isFileID() || !a2.isFileID() || !b1.isFileID() || !b2.isFileID())
return false;
// Are all locations in the same file?
if (a1.getFileID() != a2.getFileID() || a2.getFileID() != b1.getFileID() ||
b1.getFileID() != b2.getFileID()) {
return false;
}
// Check the 2 cases below:
// 1. A: |============|
// B: |===============|
// a1 b1 a2 b2
// or
// 2. A: |====================|
// B: |=======|
// a1 b1 b2 a2
bool b1_is_inside_a_range = a1.getFileOffset() <= b1.getFileOffset() &&
b1.getFileOffset() <= a2.getFileOffset();
// Check the 2 cases below:
// 1. B: |============|
// A: |===============|
// b1 a1 b2 a2
// or
// 2. B: |====================|
// A: |=======|
// b1 a1 a2 b2
bool a1_is_inside_b_range = b1.getFileOffset() <= a1.getFileOffset() &&
a1.getFileOffset() <= b2.getFileOffset();
return b1_is_inside_a_range || a1_is_inside_b_range;
}
// Matcher for FieldDecl that has a SourceRange that overlaps other declarations
// within the parent RecordDecl.
//
// Given
// struct MyStruct {
// int f;
// int f2, f3;
// struct S { int x } f4;
// };
// - doesn't match |f|
// - matches |f2| and |f3| (which overlap each other's location)
// - matches |f4| (which overlaps the location of |S|)
AST_MATCHER(clang::FieldDecl, overlapsOtherDeclsWithinRecordDecl) {
const clang::FieldDecl& self = Node;
const clang::SourceManager& source_manager =
Finder->getASTContext().getSourceManager();
const clang::RecordDecl* record_decl = self.getParent();
if (!record_decl)
return false;
clang::SourceRange self_range(self.getBeginLoc(), self.getEndLoc());
auto is_overlapping_sibling = [&](const clang::Decl* other_decl) {
if (other_decl == &self)
return false;
clang::SourceRange other_range(other_decl->getBeginLoc(),
other_decl->getEndLoc());
return IsOverlapping(source_manager, self_range, other_range);
};
bool has_sibling_with_overlapping_location =
std::any_of(record_decl->decls_begin(), record_decl->decls_end(),
is_overlapping_sibling);
return has_sibling_with_overlapping_location;
}
// Matches clang::Type if
// 1) it represents a RecordDecl with a FieldDecl that matches the InnerMatcher
// (*all* such FieldDecls will be matched)
// or
// 2) it represents an array or a RecordDecl that nests the case #1
// (this recurses to any depth).
AST_MATCHER_P(clang::QualType,
typeWithEmbeddedFieldDecl,
clang::ast_matchers::internal::Matcher<clang::FieldDecl>,
InnerMatcher) {
const clang::Type* type =
Node.getDesugaredType(Finder->getASTContext()).getTypePtrOrNull();
if (!type)
return false;
if (const clang::CXXRecordDecl* record_decl = type->getAsCXXRecordDecl()) {
auto matcher = recordDecl(forEach(fieldDecl(hasExplicitFieldDecl(anyOf(
InnerMatcher, hasType(typeWithEmbeddedFieldDecl(InnerMatcher)))))));
return matcher.matches(*record_decl, Finder, Builder);
}
if (type->isArrayType()) {
const clang::ArrayType* array_type =
Finder->getASTContext().getAsArrayType(Node);
auto matcher = typeWithEmbeddedFieldDecl(InnerMatcher);
return matcher.matches(array_type->getElementType(), Finder, Builder);
}
return false;
}
// forEachInitExprWithFieldDecl matches InitListExpr if it
// 1) evaluates to a RecordType
// 2) has a InitListExpr + FieldDecl pair that matches the submatcher args.
//
// forEachInitExprWithFieldDecl is based on and very similar to the builtin
// forEachArgumentWithParam matcher.
AST_MATCHER_P2(clang::InitListExpr,
forEachInitExprWithFieldDecl,
clang::ast_matchers::internal::Matcher<clang::Expr>,
init_expr_matcher,
clang::ast_matchers::internal::Matcher<clang::FieldDecl>,
field_decl_matcher) {
const clang::InitListExpr& init_list_expr = Node;
const clang::Type* type = init_list_expr.getType()
.getDesugaredType(Finder->getASTContext())
.getTypePtrOrNull();
if (!type)
return false;
const clang::CXXRecordDecl* record_decl = type->getAsCXXRecordDecl();
if (!record_decl)
return false;
bool is_matching = false;
clang::ast_matchers::internal::BoundNodesTreeBuilder result;
const llvm::SmallVector<const clang::FieldDecl*> field_decls(
record_decl->fields());
for (unsigned i = 0; i < init_list_expr.getNumInits(); i++) {
const clang::Expr* expr = init_list_expr.getInit(i);
const clang::FieldDecl* field_decl = nullptr;
if (const clang::ImplicitValueInitExpr* implicit_value_init_expr =
clang::dyn_cast<clang::ImplicitValueInitExpr>(expr)) {
continue; // Do not match implicit value initializers.
} else if (const clang::DesignatedInitExpr* designated_init_expr =
clang::dyn_cast<clang::DesignatedInitExpr>(expr)) {
// Nested designators are unsupported by C++.
if (designated_init_expr->size() != 1)
break;
expr = designated_init_expr->getInit();
field_decl = designated_init_expr->getDesignator(0)->getField();
} else {
if (i >= field_decls.size())
break;
field_decl = field_decls[i];
}
clang::ast_matchers::internal::BoundNodesTreeBuilder field_matches(
*Builder);
if (field_decl_matcher.matches(*field_decl, Finder, &field_matches)) {
clang::ast_matchers::internal::BoundNodesTreeBuilder expr_matches(
field_matches);
if (init_expr_matcher.matches(*expr, Finder, &expr_matches)) {
result.addMatch(expr_matches);
is_matching = true;
}
}
}
*Builder = std::move(result);
return is_matching;
}
// Rewrites |SomeClass* field| (matched as "affectedFieldDecl") into
// |raw_ptr<SomeClass> field| and for each file rewritten in such way adds an
// |#include "base/memory/raw_ptr.h"|.
class FieldDeclRewriter : public MatchFinder::MatchCallback {
public:
explicit FieldDeclRewriter(OutputHelper* output_helper)
: output_helper_(output_helper) {}
FieldDeclRewriter(const FieldDeclRewriter&) = delete;
FieldDeclRewriter& operator=(const FieldDeclRewriter&) = delete;
void run(const MatchFinder::MatchResult& result) override {
const clang::ASTContext& ast_context = *result.Context;
const clang::SourceManager& source_manager = *result.SourceManager;
const clang::FieldDecl* field_decl =
result.Nodes.getNodeAs<clang::FieldDecl>("affectedFieldDecl");
assert(field_decl && "matcher should bind 'fieldDecl'");
const clang::TypeSourceInfo* type_source_info =
field_decl->getTypeSourceInfo();
assert(type_source_info && "assuming |type_source_info| is always present");
clang::QualType pointer_type = type_source_info->getType();
assert(type_source_info->getType()->isPointerType() &&
"matcher should only match pointer types");
// Calculate the |replacement_range|.
//
// Consider the following example:
// const Pointee* const field_name_;
// ^--------------------^ = |replacement_range|
// ^ = |field_decl->getLocation()|
// ^ = |field_decl->getBeginLoc()|
// ^ = PointerTypeLoc::getStarLoc
// ^------^ = TypeLoc::getSourceRange
//
// We get the |replacement_range| in a bit clumsy way, because clang docs
// for QualifiedTypeLoc explicitly say that these objects "intentionally do
// not provide source location for type qualifiers".
clang::SourceRange replacement_range(field_decl->getBeginLoc(),
field_decl->getLocation());
// Calculate |replacement_text|.
std::string replacement_text = GenerateNewText(ast_context, pointer_type);
if (field_decl->isMutable())
replacement_text.insert(0, "mutable ");
// Generate and print a replacement.
output_helper_->AddReplacement(source_manager, replacement_range,
replacement_text,
true /* should_add_include */);
}
private:
std::string GenerateNewText(const clang::ASTContext& ast_context,
const clang::QualType& pointer_type) {
std::string result;
assert(pointer_type->isPointerType() && "caller must pass a pointer type!");
clang::QualType pointee_type = pointer_type->getPointeeType();
// Preserve qualifiers.
assert(!pointer_type.isRestrictQualified() &&
"|restrict| is a C-only qualifier and raw_ptr<T> needs C++");
if (pointer_type.isConstQualified())
result += "const ";
if (pointer_type.isVolatileQualified())
result += "volatile ";
// Convert pointee type to string.
clang::PrintingPolicy printing_policy(ast_context.getLangOpts());
printing_policy.SuppressScope = 1; // s/blink::Pointee/Pointee/
std::string pointee_type_as_string =
pointee_type.getAsString(printing_policy);
result += llvm::formatv("raw_ptr<{0}> ", pointee_type_as_string);
return result;
}
OutputHelper* const output_helper_;
};
// Rewrites |my_struct.ptr_field| (matched as "affectedMemberExpr") into
// |my_struct.ptr_field.get()|.
class AffectedExprRewriter : public MatchFinder::MatchCallback {
public:
explicit AffectedExprRewriter(OutputHelper* output_helper)
: output_helper_(output_helper) {}
AffectedExprRewriter(const AffectedExprRewriter&) = delete;
AffectedExprRewriter& operator=(const AffectedExprRewriter&) = delete;
void run(const MatchFinder::MatchResult& result) override {
const clang::SourceManager& source_manager = *result.SourceManager;
const clang::MemberExpr* member_expr =
result.Nodes.getNodeAs<clang::MemberExpr>("affectedMemberExpr");
assert(member_expr && "matcher should bind 'affectedMemberExpr'");
clang::SourceLocation member_name_start = member_expr->getMemberLoc();
size_t member_name_length = member_expr->getMemberDecl()->getName().size();
clang::SourceLocation insertion_loc =
member_name_start.getLocWithOffset(member_name_length);
clang::SourceRange replacement_range(insertion_loc, insertion_loc);
output_helper_->AddReplacement(source_manager, replacement_range, ".get()");
}
private:
OutputHelper* const output_helper_;
};
// Emits problematic fields (matched as "affectedFieldDecl") as filtered fields.
class FilteredExprWriter : public MatchFinder::MatchCallback {
public:
FilteredExprWriter(OutputHelper* output_helper, llvm::StringRef filter_tag)
: output_helper_(output_helper), filter_tag_(filter_tag) {}
FilteredExprWriter(const FilteredExprWriter&) = delete;
FilteredExprWriter& operator=(const FilteredExprWriter&) = delete;
void run(const MatchFinder::MatchResult& result) override {
const clang::FieldDecl* field_decl =
result.Nodes.getNodeAs<clang::FieldDecl>("affectedFieldDecl");
assert(field_decl && "matcher should bind 'affectedFieldDecl'");
output_helper_->AddFilteredField(*field_decl, filter_tag_);
}
private:
OutputHelper* const output_helper_;
llvm::StringRef filter_tag_;
};
} // namespace
int main(int argc, const char* argv[]) {
// TODO(dcheng): Clang tooling should do this itself.
// http://llvm.org/bugs/show_bug.cgi?id=21627
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmParser();
llvm::cl::OptionCategory category(
"rewrite_raw_ptr_fields: changes |T* field_| to |raw_ptr<T> field_|.");
llvm::cl::opt<std::string> exclude_fields_param(
kExcludeFieldsParamName, llvm::cl::value_desc("filepath"),
llvm::cl::desc("file listing fields to be blocked (not rewritten)"));
llvm::cl::opt<std::string> exclude_paths_param(
kExcludePathsParamName, llvm::cl::value_desc("filepath"),
llvm::cl::desc("file listing paths to be blocked (not rewritten)"));
llvm::Expected<clang::tooling::CommonOptionsParser> options =
clang::tooling::CommonOptionsParser::create(argc, argv, category);
assert(static_cast<bool>(options)); // Should not return an error.
clang::tooling::ClangTool tool(options->getCompilations(),
options->getSourcePathList());
MatchFinder match_finder;
OutputHelper output_helper;
// Supported pointer types =========
// Given
// struct MyStrict {
// int* int_ptr;
// int i;
// int (*func_ptr)();
// int (MyStruct::* member_func_ptr)(char);
// int (*ptr_to_array_of_ints)[123]
// };
// matches |int*|, but not the other types.
auto supported_pointer_types_matcher =
pointerType(unless(pointee(hasUnqualifiedDesugaredType(
anyOf(functionType(), memberPointerType(), arrayType())))));
// Implicit field declarations =========
// Matches field declarations that do not explicitly appear in the source
// code:
// 1. fields of classes generated by the compiler to back capturing lambdas,
// 2. fields within an implicit class or function template specialization
// (e.g. when a template is instantiated by a bit of code and there's no
// explicit specialization for it).
auto implicit_class_specialization_matcher =
classTemplateSpecializationDecl(isImplicitClassTemplateSpecialization());
auto implicit_function_specialization_matcher =
functionDecl(isImplicitFunctionTemplateSpecialization());
auto implicit_field_decl_matcher = fieldDecl(hasParent(cxxRecordDecl(anyOf(
isLambda(), implicit_class_specialization_matcher,
hasAncestor(decl(anyOf(implicit_class_specialization_matcher,
implicit_function_specialization_matcher)))))));
// Field declarations =========
// Given
// struct S {
// int* y;
// };
// matches |int* y|. Doesn't match:
// - non-pointer types
// - fields of lambda-supporting classes
// - fields listed in the --exclude-fields cmdline param or located in paths
// matched by --exclude-paths cmdline param
// - "implicit" fields (i.e. field decls that are not explicitly present in
// the source code)
FilterFile fields_to_exclude(exclude_fields_param);
FilterFile paths_to_exclude(exclude_paths_param);
auto field_decl_matcher =
fieldDecl(
allOf(hasType(supported_pointer_types_matcher),
unless(anyOf(isExpansionInSystemHeader(), isInExternCContext(),
isInThirdPartyLocation(), isInGeneratedLocation(),
isInLocationListedInFilterFile(&paths_to_exclude),
isFieldDeclListedInFilterFile(&fields_to_exclude),
IsExclusionAnnotated(),
implicit_field_decl_matcher))))
.bind("affectedFieldDecl");
FieldDeclRewriter field_decl_rewriter(&output_helper);
match_finder.addMatcher(field_decl_matcher, &field_decl_rewriter);
// Matches expressions that used to return a value of type |SomeClass*|
// but after the rewrite return an instance of |raw_ptr<SomeClass>|.
// Many such expressions might need additional changes after the rewrite:
// - Some expressions (printf args, const_cast args, etc.) might need |.get()|
// appended.
// - Using such expressions in specific contexts (e.g. as in-out arguments or
// as a return value of a function returning references) may require
// additional work and should cause related fields to be emitted as
// candidates for the --field-filter-file parameter.
auto affected_member_expr_matcher =
memberExpr(member(fieldDecl(hasExplicitFieldDecl(field_decl_matcher))))
.bind("affectedMemberExpr");
auto affected_expr_matcher = ignoringImplicit(affected_member_expr_matcher);
// Places where |.get()| needs to be appended =========
// Given
// void foo(const S& s) {
// printf("%p", s.y);
// const_cast<...>(s.y)
// reinterpret_cast<...>(s.y)
// }
// matches the |s.y| expr if it matches the |affected_expr_matcher| above.
//
// See also testcases in tests/affected-expr-original.cc
auto affected_expr_that_needs_fixing_matcher = expr(allOf(
affected_expr_matcher,
hasParent(expr(anyOf(callExpr(callee(functionDecl(isVariadic()))),
cxxConstCastExpr(), cxxReinterpretCastExpr())))));
AffectedExprRewriter affected_expr_rewriter(&output_helper);
match_finder.addMatcher(affected_expr_that_needs_fixing_matcher,
&affected_expr_rewriter);
// Affected ternary operator args =========
// Given
// void foo(const S& s) {
// cond ? s.y : ...
// }
// binds the |s.y| expr if it matches the |affected_expr_matcher| above.
//
// See also testcases in tests/affected-expr-original.cc
auto affected_ternary_operator_arg_matcher =
conditionalOperator(eachOf(hasTrueExpression(affected_expr_matcher),
hasFalseExpression(affected_expr_matcher)));
match_finder.addMatcher(affected_ternary_operator_arg_matcher,
&affected_expr_rewriter);
// Affected string binary operator =========
// Given
// struct S { const char* y; }
// void foo(const S& s) {
// std::string other;
// bool v1 = s.y == other;
// std::string v2 = s.y + other;
// }
// binds the |s.y| expr if it matches the |affected_expr_matcher| above.
//
// See also testcases in tests/affected-expr-original.cc
auto std_string_expr_matcher =
expr(hasType(cxxRecordDecl(hasName("::std::basic_string"))));
auto affected_string_binary_operator_arg_matcher = cxxOperatorCallExpr(
hasAnyOverloadedOperatorName("+", "==", "!=", "<", "<=", ">", ">="),
hasAnyArgument(std_string_expr_matcher),
forEachArgumentWithParam(affected_expr_matcher, parmVarDecl()));
match_finder.addMatcher(affected_string_binary_operator_arg_matcher,
&affected_expr_rewriter);
// Calls to templated functions =========
// Given
// struct S { int* y; };
// template <typename T>
// void templatedFunc(T* arg) {}
// void foo(const S& s) {
// templatedFunc(s.y);
// }
// binds the |s.y| expr if it matches the |affected_expr_matcher| above.
//
// See also testcases in tests/affected-expr-original.cc
auto templated_function_arg_matcher = forEachArgumentWithParam(
affected_expr_matcher,
parmVarDecl(allOf(
hasType(qualType(allOf(findAll(qualType(substTemplateTypeParmType())),
unless(referenceType())))),
unless(hasAncestor(functionDecl(hasName("Unretained")))))));
match_finder.addMatcher(callExpr(templated_function_arg_matcher),
&affected_expr_rewriter);
// TODO(lukasza): It is unclear why |traverse| below is needed. Maybe it can
// be removed if https://bugs.llvm.org/show_bug.cgi?id=46287 is fixed.
match_finder.addMatcher(
traverse(clang::TraversalKind::TK_AsIs,
cxxConstructExpr(templated_function_arg_matcher)),
&affected_expr_rewriter);
// Calls to constructors via an implicit cast =========
// Given
// struct I { I(int*) {} };
// void bar(I i) {}
// struct S { int* y; };
// void foo(const S& s) {
// bar(s.y); // implicit cast from |s.y| to I.
// }
// binds the |s.y| expr if it matches the |affected_expr_matcher| above.
//
// See also testcases in tests/affected-expr-original.cc
auto implicit_ctor_expr_matcher = cxxConstructExpr(allOf(
anyOf(hasParent(materializeTemporaryExpr()),
hasParent(implicitCastExpr())),
hasDeclaration(
cxxConstructorDecl(allOf(parameterCountIs(1), unless(isExplicit())))),
forEachArgumentWithParam(affected_expr_matcher, parmVarDecl())));
match_finder.addMatcher(implicit_ctor_expr_matcher, &affected_expr_rewriter);
// |auto| type declarations =========
// Given
// struct S { int* y; };
// void foo(const S& s) {
// auto* p = s.y;
// }
// binds the |s.y| expr if it matches the |affected_expr_matcher| above.
//
// See also testcases in tests/affected-expr-original.cc
auto auto_var_decl_matcher = declStmt(forEach(
varDecl(allOf(hasType(pointerType(pointee(autoType()))),
hasInitializer(anyOf(
affected_expr_matcher,
initListExpr(hasInit(0, affected_expr_matcher))))))));
match_finder.addMatcher(auto_var_decl_matcher, &affected_expr_rewriter);
// address-of(affected-expr) =========
// Given
// ... &s.y ...
// matches the |s.y| expr if it matches the |affected_member_expr_matcher|
// above.
//
// See also the testcases in tests/gen-in-out-arg-test.cc.
auto affected_addr_of_expr_matcher = expr(allOf(
affected_expr_matcher, hasParent(unaryOperator(hasOperatorName("&")))));
FilteredExprWriter filtered_addr_of_expr_writer(&output_helper, "addr-of");
match_finder.addMatcher(affected_addr_of_expr_matcher,
&filtered_addr_of_expr_writer);
// in-out reference arg =========
// Given
// struct S { SomeClass* ptr_field; };
// void f(SomeClass*& in_out_arg) { ... }
// template <typename T> void f2(T&& rvalue_ref_arg) { ... }
// template <typename... Ts> void f3(Ts&&... rvalue_ref_args) { ... }
// void bar() {
// S s;
// foo(s.ptr_field)
// }
// matches the |s.ptr_field| expr if it matches the
// |affected_member_expr_matcher| and is passed as a function argument that
// has |FooBar*&| type (like |f|, but unlike |f2| and |f3|).
//
// See also the testcases in tests/gen-in-out-arg-test.cc.
auto affected_in_out_ref_arg_matcher = callExpr(forEachArgumentWithParam(
affected_expr_matcher, hasExplicitParmVarDecl(hasType(qualType(
allOf(referenceType(pointee(pointerType())),
unless(rValueReferenceType())))))));
FilteredExprWriter filtered_in_out_ref_arg_writer(&output_helper,
"in-out-param-ref");
match_finder.addMatcher(affected_in_out_ref_arg_matcher,
&filtered_in_out_ref_arg_writer);
// See the doc comment for the overlapsOtherDeclsWithinRecordDecl matcher
// and the testcases in tests/gen-overlaps-test.cc.
auto overlapping_field_decl_matcher = fieldDecl(
allOf(field_decl_matcher, overlapsOtherDeclsWithinRecordDecl()));
FilteredExprWriter overlapping_field_decl_writer(&output_helper,
"overlapping");
match_finder.addMatcher(overlapping_field_decl_matcher,
&overlapping_field_decl_writer);
// Matches fields initialized with a non-nullptr value in a constexpr
// constructor. See also the testcase in tests/gen-constexpr-test.cc.
auto non_nullptr_expr_matcher =
expr(unless(ignoringImplicit(cxxNullPtrLiteralExpr())));
auto constexpr_ctor_field_initializer_matcher = cxxConstructorDecl(
allOf(isConstexpr(), forEachConstructorInitializer(allOf(
forField(field_decl_matcher),
withInitializer(non_nullptr_expr_matcher)))));
FilteredExprWriter constexpr_ctor_field_initializer_writer(
&output_helper, "constexpr-ctor-field-initializer");
match_finder.addMatcher(constexpr_ctor_field_initializer_matcher,
&constexpr_ctor_field_initializer_writer);
// Matches constexpr initializer list expressions that initialize a rewritable
// field with a non-nullptr value. For more details and rationale see the
// testcases in tests/gen-constexpr-test.cc.
auto constexpr_var_initializer_matcher = varDecl(
allOf(isConstexpr(),
hasInitializer(findAll(initListExpr(forEachInitExprWithFieldDecl(
non_nullptr_expr_matcher,
hasExplicitFieldDecl(field_decl_matcher)))))));
FilteredExprWriter constexpr_var_initializer_writer(
&output_helper, "constexpr-var-initializer");
match_finder.addMatcher(constexpr_var_initializer_matcher,
&constexpr_var_initializer_writer);
// See the doc comment for the isInMacroLocation matcher
// and the testcases in tests/gen-macro-test.cc.
auto macro_field_decl_matcher =
fieldDecl(allOf(field_decl_matcher, isInMacroLocation()));
FilteredExprWriter macro_field_decl_writer(&output_helper, "macro");
match_finder.addMatcher(macro_field_decl_matcher, &macro_field_decl_writer);
// See the doc comment for the anyCharType matcher
// and the testcases in tests/gen-char-test.cc.
auto char_ptr_field_decl_matcher = fieldDecl(allOf(
field_decl_matcher,
hasType(pointerType(pointee(qualType(allOf(
isConstQualified(), hasUnqualifiedDesugaredType(anyCharType()))))))));
FilteredExprWriter char_ptr_field_decl_writer(&output_helper, "const-char");
match_finder.addMatcher(char_ptr_field_decl_matcher,
&char_ptr_field_decl_writer);
// See the testcases in tests/gen-global-destructor-test.cc.
auto global_destructor_matcher =
varDecl(allOf(hasGlobalStorage(),
hasType(typeWithEmbeddedFieldDecl(field_decl_matcher))));
FilteredExprWriter global_destructor_writer(&output_helper, "global-scope");
match_finder.addMatcher(global_destructor_matcher, &global_destructor_writer);
// Matches fields in unions (both directly rewritable fields as well as union
// fields that embed a struct that contains a rewritable field). See also the
// testcases in tests/gen-unions-test.cc.
auto union_field_decl_matcher = recordDecl(allOf(
isUnion(), forEach(fieldDecl(anyOf(field_decl_matcher,
hasType(typeWithEmbeddedFieldDecl(
field_decl_matcher)))))));
FilteredExprWriter union_field_decl_writer(&output_helper, "union");
match_finder.addMatcher(union_field_decl_matcher, &union_field_decl_writer);
// Matches rewritable fields of struct `SomeStruct` if that struct happens to
// be a destination type of a `reinterpret_cast<SomeStruct*>` cast and is a
// trivial type (otherwise `reinterpret_cast<SomeStruct*>` wouldn't be valid
// before the rewrite if it skipped non-trivial constructors).
auto reinterpret_cast_struct_matcher =
cxxReinterpretCastExpr(hasDestinationType(pointerType(pointee(
hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(
allOf(forEach(field_decl_matcher), isTrivial())))))))));
FilteredExprWriter reinterpret_cast_struct_writer(
&output_helper, "reinterpret-cast-trivial-type");
match_finder.addMatcher(reinterpret_cast_struct_matcher,
&reinterpret_cast_struct_writer);
// Prepare and run the tool.
std::unique_ptr<clang::tooling::FrontendActionFactory> factory =
clang::tooling::newFrontendActionFactory(&match_finder, &output_helper);
int result = tool.run(factory.get());
if (result != 0)
return result;
return 0;
}