summaryrefslogtreecommitdiff
path: root/chromium/tools/clang
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/tools/clang')
-rw-r--r--chromium/tools/clang/blink_gc_plugin/BadPatternFinder.cpp59
-rw-r--r--chromium/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp10
-rw-r--r--chromium/tools/clang/blink_gc_plugin/BlinkGCPluginOptions.h2
-rw-r--r--chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.cpp26
-rw-r--r--chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.h7
-rwxr-xr-xchromium/tools/clang/blink_gc_plugin/process-graph.py39
-rwxr-xr-xchromium/tools/clang/pylib/clang/compile_db.py2
-rwxr-xr-xchromium/tools/clang/pylib/clang/compile_db_test.py6
-rwxr-xr-xchromium/tools/clang/pylib/clang/plugin_testing.py6
-rw-r--r--chromium/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp857
-rw-r--r--chromium/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt163
-rwxr-xr-xchromium/tools/clang/rewrite_raw_ptr_fields/rewrite.sh88
-rwxr-xr-xchromium/tools/clang/scripts/apply_edits.py26
-rwxr-xr-xchromium/tools/clang/scripts/apply_edits_test.py11
-rwxr-xr-xchromium/tools/clang/scripts/build.py102
-rwxr-xr-xchromium/tools/clang/scripts/clang_tidy_tool.py14
-rwxr-xr-xchromium/tools/clang/scripts/expand_thin_archives.py124
-rwxr-xr-xchromium/tools/clang/scripts/goma_ld.py2
-rwxr-xr-xchromium/tools/clang/scripts/goma_link.py89
-rwxr-xr-xchromium/tools/clang/scripts/goma_link_unit_tests.py78
-rwxr-xr-xchromium/tools/clang/scripts/update.py4
-rwxr-xr-xchromium/tools/clang/scripts/upload_revision.py3
22 files changed, 1346 insertions, 372 deletions
diff --git a/chromium/tools/clang/blink_gc_plugin/BadPatternFinder.cpp b/chromium/tools/clang/blink_gc_plugin/BadPatternFinder.cpp
index 2992d8078a2..f3cdb86589f 100644
--- a/chromium/tools/clang/blink_gc_plugin/BadPatternFinder.cpp
+++ b/chromium/tools/clang/blink_gc_plugin/BadPatternFinder.cpp
@@ -83,62 +83,6 @@ class OptionalGarbageCollectedMatcher : public MatchFinder::MatchCallback {
DiagnosticsReporter& diagnostics_;
};
-class MissingMixinMarker : public MatchFinder::MatchCallback {
- public:
- explicit MissingMixinMarker(DiagnosticsReporter& diagnostics)
- : diagnostics_(diagnostics) {}
-
- void Register(MatchFinder& match_finder) {
- auto class_missing_mixin_marker = cxxRecordDecl(
- decl().bind("bad_class"),
- // Definition of a garbage-collected class
- isDefinition(),
- isDerivedFrom(cxxRecordDecl(decl().bind("gc_base_class"),
- hasName("::blink::GarbageCollected"))),
- // ...which derives some mixin...
- isDerivedFrom(cxxRecordDecl(decl().bind("mixin_base_class"),
- hasName("::blink::GarbageCollectedMixin"))),
- // ...and doesn't use USING_GARBAGE_COLLECTED_MIXIN
- unless(anyOf(isSameOrDerivedFrom(has(typedefNameDecl(
- hasName("HasUsingGarbageCollectedMixinMacro")))),
- isSameOrDerivedFrom(has(
- fieldDecl(hasName("mixin_constructor_marker_")))))),
- // ...and might end up actually being constructed
- unless(hasMethod(isPure())), unless(matchesName("::SameSizeAs")));
- match_finder.addDynamicMatcher(class_missing_mixin_marker, this);
- }
-
- void run(const MatchFinder::MatchResult& result) {
- auto* bad_class = result.Nodes.getNodeAs<clang::CXXRecordDecl>("bad_class");
- auto* gc_base_class =
- result.Nodes.getNodeAs<clang::CXXRecordDecl>("gc_base_class");
- auto* mixin_base_class =
- result.Nodes.getNodeAs<clang::CXXRecordDecl>("mixin_base_class");
-
- clang::CXXBasePaths paths;
- if (!bad_class->isDerivedFrom(mixin_base_class, paths))
- return;
- const auto& path = paths.front();
-
- // It's most useful to describe the most derived "mixin" class (i.e. which
- // does not derive the concrete GarbageCollected base).
- auto mixin_it = std::find_if(
- path.begin(), path.end(),
- [gc_base_class](const clang::CXXBasePathElement& path_element) {
- return !path_element.Class->isDerivedFrom(gc_base_class);
- });
- const clang::CXXRecordDecl* mixin_class = mixin_it->Class;
- diagnostics_.MissingMixinMarker(bad_class, mixin_class, path.begin()->Base);
-
- ++mixin_it;
- for (auto it = path.begin() + 1; it != mixin_it; ++it)
- diagnostics_.MissingMixinMarkerNote(it->Base);
- }
-
- private:
- DiagnosticsReporter& diagnostics_;
-};
-
} // namespace
void FindBadPatterns(clang::ASTContext& ast_context,
@@ -151,8 +95,5 @@ void FindBadPatterns(clang::ASTContext& ast_context,
OptionalGarbageCollectedMatcher optional_gc(diagnostics);
optional_gc.Register(match_finder);
- MissingMixinMarker missing_mixin_marker(diagnostics);
- missing_mixin_marker.Register(match_finder);
-
match_finder.matchAST(ast_context);
}
diff --git a/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp b/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp
index e4233a4f48a..068cbc69373 100644
--- a/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp
+++ b/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginConsumer.cpp
@@ -89,6 +89,7 @@ BlinkGCPluginConsumer::BlinkGCPluginConsumer(
// Ignore GC implementation files.
options_.ignored_directories.push_back("/heap/");
+ options_.allowed_directories.push_back("/test/");
}
void BlinkGCPluginConsumer::HandleTranslationUnit(ASTContext& context) {
@@ -633,9 +634,14 @@ bool BlinkGCPluginConsumer::InIgnoredDirectory(RecordInfo* info) {
#if defined(_WIN32)
std::replace(filename.begin(), filename.end(), '\\', '/');
#endif
- for (const auto& dir : options_.ignored_directories)
- if (filename.find(dir) != std::string::npos)
+ for (const auto& ignored_dir : options_.ignored_directories)
+ if (filename.find(ignored_dir) != std::string::npos) {
+ for (const auto& allowed_dir : options_.allowed_directories) {
+ if (filename.find(allowed_dir) != std::string::npos)
+ return false;
+ }
return true;
+ }
return false;
}
diff --git a/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginOptions.h b/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginOptions.h
index e05100bc1ad..a9cae8d6811 100644
--- a/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginOptions.h
+++ b/chromium/tools/clang/blink_gc_plugin/BlinkGCPluginOptions.h
@@ -29,6 +29,8 @@ struct BlinkGCPluginOptions {
std::set<std::string> ignored_classes;
std::set<std::string> checked_namespaces;
std::vector<std::string> ignored_directories;
+ // |allowed_directories| overrides |ignored_directories|.
+ std::vector<std::string> allowed_directories;
};
#endif // TOOLS_BLINK_GC_PLUGIN_BLINK_GC_PLUGIN_OPTIONS_H_
diff --git a/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.cpp b/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.cpp
index 05fe834b978..88f2ab4177b 100644
--- a/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.cpp
+++ b/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.cpp
@@ -145,13 +145,6 @@ const char kOptionalUsedWithGC[] =
"[blink-gc] Disallowed construction of %0 found; %1 is a garbage-collected "
"type. optional cannot hold garbage-collected objects.";
-const char kMissingMixinMarker[] =
- "[blink-gc] Garbage-collected class %0 derives mixin class %1. "
- "You must add USING_GARBAGE_COLLECTED_MIXIN(%2).";
-
-const char kMissingMixinMarkerNote[] =
- "[blink-gc] Mixin base class derived here:";
-
} // namespace
DiagnosticBuilder DiagnosticsReporter::ReportDiagnostic(
@@ -253,10 +246,6 @@ DiagnosticsReporter::DiagnosticsReporter(
diagnostic_.getCustomDiagID(getErrorLevel(), kUniquePtrUsedWithGC);
diag_optional_used_with_gc_ =
diagnostic_.getCustomDiagID(getErrorLevel(), kOptionalUsedWithGC);
- diag_missing_mixin_marker_ =
- diagnostic_.getCustomDiagID(getErrorLevel(), kMissingMixinMarker);
- diag_missing_mixin_marker_note_ = diagnostic_.getCustomDiagID(
- DiagnosticsEngine::Note, kMissingMixinMarkerNote);
}
bool DiagnosticsReporter::hasErrorOccurred() const
@@ -552,18 +541,3 @@ void DiagnosticsReporter::OptionalUsedWithGC(
ReportDiagnostic(expr->getBeginLoc(), diag_optional_used_with_gc_)
<< optional << gc_type << expr->getSourceRange();
}
-
-void DiagnosticsReporter::MissingMixinMarker(
- const clang::CXXRecordDecl* bad_class,
- const clang::CXXRecordDecl* mixin_class,
- const clang::CXXBaseSpecifier* first_base) {
- ReportDiagnostic(first_base->getBaseTypeLoc(), diag_missing_mixin_marker_)
- << bad_class << mixin_class << bad_class->getName()
- << first_base->getSourceRange();
-}
-
-void DiagnosticsReporter::MissingMixinMarkerNote(
- const clang::CXXBaseSpecifier* base) {
- ReportDiagnostic(base->getBaseTypeLoc(), diag_missing_mixin_marker_note_)
- << base->getSourceRange();
-}
diff --git a/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.h b/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.h
index ca585d5a046..fea2d87755f 100644
--- a/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.h
+++ b/chromium/tools/clang/blink_gc_plugin/DiagnosticsReporter.h
@@ -82,11 +82,6 @@ class DiagnosticsReporter {
void OptionalUsedWithGC(const clang::Expr* expr,
const clang::CXXRecordDecl* optional,
const clang::CXXRecordDecl* gc_type);
- void MissingMixinMarker(const clang::CXXRecordDecl* bad_class,
- const clang::CXXRecordDecl* mixin_class,
- const clang::CXXBaseSpecifier* first_base);
- void MissingMixinMarkerNote(const clang::CXXBaseSpecifier* base);
-
private:
clang::DiagnosticBuilder ReportDiagnostic(
clang::SourceLocation location,
@@ -144,8 +139,6 @@ class DiagnosticsReporter {
unsigned diag_unique_ptr_used_with_gc_;
unsigned diag_optional_used_with_gc_;
- unsigned diag_missing_mixin_marker_;
- unsigned diag_missing_mixin_marker_note_;
};
#endif // TOOLS_BLINK_GC_PLUGIN_DIAGNOSTICS_REPORTER_H_
diff --git a/chromium/tools/clang/blink_gc_plugin/process-graph.py b/chromium/tools/clang/blink_gc_plugin/process-graph.py
index 865a234342a..39e53f18e12 100755
--- a/chromium/tools/clang/blink_gc_plugin/process-graph.py
+++ b/chromium/tools/clang/blink_gc_plugin/process-graph.py
@@ -4,7 +4,8 @@
# found in the LICENSE file.
from __future__ import print_function
-import argparse, os, sys, json, subprocess, pickle, StringIO
+from StringIO import StringIO
+import argparse, os, sys, json, subprocess, pickle
parser = argparse.ArgumentParser(
description =
@@ -57,6 +58,14 @@ ignored_cycles = []
# Global flag to determine exit code.
global_reported_error = False
+try:
+ # Python3 remove sys.maxint.
+ maxint = sys.maxint
+except AttributeError:
+ # Also see https://stackoverflow.com/a/13795777/4052492.
+ maxint = sys.maxsize
+
+
def set_reported_error(value):
global global_reported_error
global_reported_error = value
@@ -109,18 +118,20 @@ class Node:
else:
self.edges[new_edge.key] = new_edge
def super_edges(self):
- return [ e for e in self.edges.itervalues() if e.is_super() ]
+ return [e for e in self.edges.values() if e.is_super()]
+
def subclass_edges(self):
- return [ e for e in self.edges.itervalues() if e.is_subclass() ]
+ return [e for e in self.edges.values() if e.is_subclass()]
+
def reset(self):
- self.cost = sys.maxint
+ self.cost = maxint
self.visited = False
self.path = None
self.counts = {}
for ptr in ptr_types:
self.counts[ptr] = 0
def update_counts(self):
- for e in self.edges.itervalues():
+ for e in self.edges.values():
inc_ptr(e.dst, e.ptr)
# Representation of directed graph edges.
@@ -199,7 +210,7 @@ def copy_super_edges(edge):
copy_super_edges(e)
# Copy strong super-class edges (ignoring sub-class edges) to the sub class.
sub_node = graph[edge.src]
- for e in super_node.edges.itervalues():
+ for e in super_node.edges.values():
if e.keeps_alive() and not e.is_subclass():
new_edge = Edge(
src = sub_node.name,
@@ -222,16 +233,16 @@ def copy_super_edges(edge):
super_node.edges[sub_edge.key] = sub_edge
def complete_graph():
- for node in graph.itervalues():
+ for node in graph.values():
for edge in node.super_edges():
copy_super_edges(edge)
- for edge in node.edges.itervalues():
+ for edge in node.edges.values():
if edge.is_root():
roots.append(edge)
log("Copied edges down <super> edges for %d graph nodes" % global_inc_copy)
def reset_graph():
- for n in graph.itervalues():
+ for n in graph.values():
n.reset()
def shortest_path(start, end):
@@ -243,7 +254,7 @@ def shortest_path(start, end):
current.visited = True
if current == end or current.cost >= end.cost + 1:
return
- for e in current.edges.itervalues():
+ for e in current.edges.values():
if not e.keeps_alive():
continue
dst = graph.get(e.dst)
@@ -276,7 +287,7 @@ def detect_cycles():
continue
# Find the shortest path from the root target (dst) to its host (src)
shortest_path(dst, src)
- if src.cost < sys.maxint:
+ if src.cost < maxint:
report_cycle(root_edge)
def is_ignored_cycle(cycle):
@@ -307,7 +318,7 @@ def report_cycle(root_edge):
for p in path:
if len(p.loc) > max_loc:
max_loc = len(p.loc)
- out = StringIO.StringIO()
+ out = StringIO()
for p in path[:-1]:
print((p.loc + ':').ljust(max_loc + 1), p, file=out)
sout = out.getvalue()
@@ -370,7 +381,7 @@ def print_stats():
gc_managed = []
hierarchies = []
- for node in graph.itervalues():
+ for node in graph.values():
node.update_counts()
for sup in node.super_edges():
if sup.dst in gcref_bases:
@@ -395,7 +406,7 @@ def print_stats():
hierarchies.append((base, stats))
print("\nHierarchies in transition (RefCountedGarbageCollected):")
- hierarchies.sort(key=lambda (n,s): -s['classes'])
+ hierarchies.sort(key=lambda n: -n[1]['classes'])
for (node, stats) in hierarchies:
total = stats['mem'] + stats['ref'] + stats['raw']
print(
diff --git a/chromium/tools/clang/pylib/clang/compile_db.py b/chromium/tools/clang/pylib/clang/compile_db.py
index 46220af8b90..50decf71907 100755
--- a/chromium/tools/clang/pylib/clang/compile_db.py
+++ b/chromium/tools/clang/pylib/clang/compile_db.py
@@ -67,7 +67,7 @@ def _ProcessEntry(entry):
match = _RSP_RE.search(entry['command'])
if match:
rsp_path = os.path.join(entry['directory'], match.group(2))
- rsp_contents = file(rsp_path).read()
+ rsp_contents = open(rsp_path).read()
entry['command'] = ''.join([
entry['command'][:match.start(1)],
rsp_contents,
diff --git a/chromium/tools/clang/pylib/clang/compile_db_test.py b/chromium/tools/clang/pylib/clang/compile_db_test.py
index 493f2f815db..9ec818bbb5e 100755
--- a/chromium/tools/clang/pylib/clang/compile_db_test.py
+++ b/chromium/tools/clang/pylib/clang/compile_db_test.py
@@ -62,7 +62,11 @@ class CompileDbTest(unittest.TestCase):
_TEST_COMPILE_DB)
# Assert no changes were made.
- self.assertItemsEqual(processed_compile_db, _TEST_COMPILE_DB)
+ try:
+ # assertItemsEqual is renamed assertCountEqual in Python3.
+ self.assertCountEqual(processed_compile_db, _TEST_COMPILE_DB)
+ except AttributeError:
+ self.assertItemsEqual(processed_compile_db, _TEST_COMPILE_DB)
def testProcessForWindows(self):
sys.platform = 'win32'
diff --git a/chromium/tools/clang/pylib/clang/plugin_testing.py b/chromium/tools/clang/pylib/clang/plugin_testing.py
index 7082ac87978..6b2e6067118 100755
--- a/chromium/tools/clang/pylib/clang/plugin_testing.py
+++ b/chromium/tools/clang/pylib/clang/plugin_testing.py
@@ -63,7 +63,7 @@ class ClangPluginTest(object):
cmd = clang_cmd[:]
try:
# Some tests need to run with extra flags.
- cmd.extend(file('%s.flags' % test_name).read().split())
+ cmd.extend(open('%s.flags' % test_name).read().split())
except IOError:
pass
cmd.append(test)
@@ -85,7 +85,9 @@ class ClangPluginTest(object):
def RunOneTest(self, test_name, cmd):
try:
- actual = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+ actual = subprocess.check_output(cmd,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True)
except subprocess.CalledProcessError as e:
# Some plugin tests intentionally trigger compile errors, so just ignore
# an exit code that indicates failure.
diff --git a/chromium/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp b/chromium/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp
index eb51290ca28..f94ce4e03f3 100644
--- a/chromium/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp
+++ b/chromium/tools/clang/rewrite_raw_ptr_fields/RewriteRawPtrFields.cpp
@@ -8,11 +8,26 @@
// becomes:
// CheckedPtr<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>
@@ -22,6 +37,7 @@
#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"
@@ -32,6 +48,7 @@
#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"
@@ -48,56 +65,588 @@ namespace {
// replaces a raw pointer.
const char kIncludePath[] = "base/memory/checked_ptr.h";
-// Output format is documented in //docs/clang_tool_refactoring.md
-class ReplacementsPrinter {
+// Name of a cmdline parameter that can be used to specify a file listing fields
+// that should not be rewritten to use CheckedPtr<T>.
+//
+// See also:
+// - OutputSectionHelper
+// - FieldDeclFilterFile
+const char kExcludeFieldsParamName[] = "exclude-fields";
+
+// 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:
+// - FieldDeclFilterFile
+// - OutputHelper
+class OutputSectionHelper {
public:
- ReplacementsPrinter() { llvm::outs() << "==== BEGIN EDITS ====\n"; }
+ explicit OutputSectionHelper(llvm::StringRef output_delimiter)
+ : output_delimiter_(output_delimiter.str()) {}
- ~ReplacementsPrinter() { llvm::outs() << "==== END EDITS ====\n"; }
+ 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];
- void PrintReplacement(const clang::SourceManager& source_manager,
- const clang::SourceRange& replacement_range,
- std::string replacement_text) {
+ 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;
+
+ 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');
- llvm::outs() << "r:::" << file_path << ":::" << replacement.getOffset()
- << ":::" << replacement.getLength()
- << ":::" << replacement_text << "\n";
+ 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 was_inserted = false;
- std::tie(std::ignore, was_inserted) =
- files_with_already_added_includes_.insert(file_path.str());
- if (was_inserted)
- llvm::outs() << "include-user-header:::" << file_path
- << ":::-1:::-1:::" << kIncludePath << "\n";
+ 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:
+ // CheckedPtr requires C++. In particular, attempting to #include
+ // "base/memory/checked_ptr.h" from C-only compilation units will lead
+ // to compilation errors.
+ return true;
+
+ case clang::Language::CXX:
+ 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;
+
+ // V8 needs to be considered "third party", even though its paths do not
+ // contain the "third_party" substring. In particular, the rewriter should
+ // not append |.get()| to references to |v8::RegisterState::pc|, because
+ // //v8/include/v8.h will *not* get rewritten.
+ if (file_path.contains("v8/include/"))
+ return true;
+
+ // Otherwise, just check if the paths contains the "third_party" substring.
+ 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, that can be used to filter
+// out specific FieldDecls.
+//
+// See also:
+// - kExcludeFieldsParamName
+// - OutputSectionHelper
+class FieldDeclFilterFile {
+ public:
+ explicit FieldDeclFilterFile(const std::string& filepath) {
+ if (!filepath.empty())
+ ParseInputFile(filepath);
+ }
+
+ bool Contains(const clang::FieldDecl& field_decl) const {
+ std::string qualified_name = field_decl.getQualifiedNameAsString();
+ auto it = fields_to_filter_.find(qualified_name);
+ return it != fields_to_filter_.end();
}
private:
- std::set<std::string> files_with_already_added_includes_;
+ // 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 std::string& filepath) {
+ 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 --"
+ << kExcludeFieldsParamName << " 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;
+
+ fields_to_filter_.insert(line);
+ }
+ }
+
+ // Stores fully-namespace-qualified names of fields matched by the filter.
+ llvm::StringSet<> fields_to_filter_;
};
-AST_MATCHER(clang::ClassTemplateSpecializationDecl, isImplicitSpecialization) {
+AST_MATCHER_P(clang::FieldDecl,
+ isListedInFilterFile,
+ FieldDeclFilterFile,
+ Filter) {
+ return Filter.Contains(Node);
+}
+
+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();
}
+// 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();
+}
+
+// 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;
+}
+
+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::Callback<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();
+ 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;
+}
+
+// Rewrites |SomeClass* field| (matched as "affectedFieldDecl") into
+// |CheckedPtr<SomeClass> field| and for each file rewritten in such way adds an
+// |#include "base/memory/checked_ptr.h"|.
class FieldDeclRewriter : public MatchFinder::MatchCallback {
public:
- explicit FieldDeclRewriter(ReplacementsPrinter* replacements_printer)
- : replacements_printer_(replacements_printer) {}
+ explicit FieldDeclRewriter(OutputHelper* output_helper)
+ : output_helper_(output_helper) {}
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>("fieldDecl");
+ result.Nodes.getNodeAs<clang::FieldDecl>("affectedFieldDecl");
assert(field_decl && "matcher should bind 'fieldDecl'");
const clang::TypeSourceInfo* type_source_info =
@@ -112,7 +661,7 @@ class FieldDeclRewriter : public MatchFinder::MatchCallback {
//
// Consider the following example:
// const Pointee* const field_name_;
- // ^-------------------^ = |replacement_range|
+ // ^--------------------^ = |replacement_range|
// ^ = |field_decl->getLocation()|
// ^ = |field_decl->getBeginLoc()|
// ^ = PointerTypeLoc::getStarLoc
@@ -121,9 +670,8 @@ class FieldDeclRewriter : public MatchFinder::MatchCallback {
// 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().getLocWithOffset(-1));
+ clang::SourceRange replacement_range(field_decl->getBeginLoc(),
+ field_decl->getLocation());
// Calculate |replacement_text|.
std::string replacement_text = GenerateNewText(ast_context, pointer_type);
@@ -131,8 +679,9 @@ class FieldDeclRewriter : public MatchFinder::MatchCallback {
replacement_text.insert(0, "mutable ");
// Generate and print a replacement.
- replacements_printer_->PrintReplacement(source_manager, replacement_range,
- replacement_text);
+ output_helper_->AddReplacement(source_manager, replacement_range,
+ replacement_text,
+ true /* should_add_include */);
}
private:
@@ -156,12 +705,59 @@ class FieldDeclRewriter : public MatchFinder::MatchCallback {
printing_policy.SuppressScope = 1; // s/blink::Pointee/Pointee/
std::string pointee_type_as_string =
pointee_type.getAsString(printing_policy);
- result += llvm::formatv("CheckedPtr<{0}>", pointee_type_as_string);
+ result += llvm::formatv("CheckedPtr<{0}> ", pointee_type_as_string);
return result;
}
- ReplacementsPrinter* const replacements_printer_;
+ 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) {}
+
+ 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) {}
+
+ 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
@@ -173,19 +769,21 @@ int main(int argc, const char* argv[]) {
llvm::InitializeNativeTargetAsmParser();
llvm::cl::OptionCategory category(
"rewrite_raw_ptr_fields: changes |T* field_| to |CheckedPtr<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)"));
clang::tooling::CommonOptionsParser options(argc, argv, category);
clang::tooling::ClangTool tool(options.getCompilations(),
options.getSourcePathList());
MatchFinder match_finder;
- ReplacementsPrinter replacements_printer;
+ OutputHelper output_helper;
// Supported pointer types =========
// Given
// struct MyStrict {
// int* int_ptr;
// int i;
- // char* char_ptr;
// int (*func_ptr)();
// int (MyStruct::* member_func_ptr)(char);
// int (*ptr_to_array_of_ints)[123]
@@ -196,21 +794,25 @@ int main(int argc, const char* argv[]) {
recordType(hasDeclaration(cxxRecordDecl(
hasMethod(allOf(hasOverloadedOperatorName("new"), isDeleted())))));
auto supported_pointer_types_matcher =
- pointerType(unless(pointee(hasUnqualifiedDesugaredType(anyOf(
- record_with_deleted_allocation_operator_type_matcher, functionType(),
- memberPointerType(), anyCharType(), arrayType())))));
+ pointerType(unless(pointee(hasUnqualifiedDesugaredType(
+ anyOf(record_with_deleted_allocation_operator_type_matcher,
+ 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 template specialization (e.g. when a
- // template is instantiated by a bit of code and there's no explicit
- // specialization for it).
+ // 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(), classTemplateSpecializationDecl(isImplicitSpecialization()),
- hasAncestor(
- classTemplateSpecializationDecl(isImplicitSpecialization()))))));
+ isLambda(), implicit_class_specialization_matcher,
+ hasAncestor(decl(anyOf(implicit_class_specialization_matcher,
+ implicit_function_specialization_matcher)))))));
// Field declarations =========
// Given
@@ -220,16 +822,181 @@ int main(int argc, const char* argv[]) {
// matches |int* y|. Doesn't match:
// - non-pointer types
// - fields of lambda-supporting classes
+ // - fields listed in the --exclude-fields cmdline param
+ // - "implicit" fields (i.e. field decls that are not explicitly present in
+ // the source code)
+ FieldDeclFilterFile fields_to_exclude(exclude_fields_param);
auto field_decl_matcher =
- fieldDecl(allOf(hasType(supported_pointer_types_matcher),
- unless(implicit_field_decl_matcher)))
- .bind("fieldDecl");
- FieldDeclRewriter field_decl_rewriter(&replacements_printer);
+ fieldDecl(
+ allOf(hasType(supported_pointer_types_matcher),
+ unless(anyOf(isInThirdPartyLocation(), isInGeneratedLocation(),
+ isExpansionInSystemHeader(), isInExternCContext(),
+ isListedInFilterFile(fields_to_exclude),
+ 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 |CheckedPtr<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_implicit_expr_matcher = implicitCastExpr(has(expr(anyOf(
+ // Only single implicitCastExpr is present in case of:
+ // |auto* v = s.ptr_field;|
+ expr(affected_member_expr_matcher),
+ // 2nd nested implicitCastExpr is present in case of:
+ // |const auto* v = s.ptr_field;|
+ expr(implicitCastExpr(has(affected_member_expr_matcher)))))));
+ auto affected_expr_matcher =
+ expr(anyOf(affected_member_expr_matcher, affected_implicit_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);
+
+ // 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(hasType(qualType(allOf(
+ findAll(qualType(substTemplateTypeParmType())),
+ unless(referenceType()))))));
+ 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::ast_type_traits::TK_AsIs,
+ cxxConstructExpr(templated_function_arg_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.bind("expr"),
+ 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);
+
+ // 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(
+ hasUnqualifiedDesugaredType(anyCharType()))))));
+ FilteredExprWriter char_ptr_field_decl_writer(&output_helper, "char");
+ match_finder.addMatcher(char_ptr_field_decl_matcher,
+ &char_ptr_field_decl_writer);
+
// Prepare and run the tool.
std::unique_ptr<clang::tooling::FrontendActionFactory> factory =
- clang::tooling::newFrontendActionFactory(&match_finder);
+ clang::tooling::newFrontendActionFactory(&match_finder, &output_helper);
int result = tool.run(factory.get());
if (result != 0)
return result;
diff --git a/chromium/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt b/chromium/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt
new file mode 100644
index 00000000000..b8cc64f3bb3
--- /dev/null
+++ b/chromium/tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt
@@ -0,0 +1,163 @@
+# File that lists fields that should be ignored when running the
+# rewrite_raw_ptr_fields tool on Chromium sources.
+#
+# Each non-comment, non-empty line should specify a fully-namespace qualified
+# field like:
+# my_namespace::my_subnamespace::MyStruct::my_field_
+#
+# There is no need to specify template arguments:
+# my_namespace::my_subnamespace::MyTemplate::my_field_
+
+# Populated manually - need to keep it constexpr:
+base::BasicStringPiece::ptr_
+base::CheckedContiguousIterator::current_
+base::CheckedContiguousIterator::end_
+base::CheckedContiguousIterator::start_
+base::CheckedContiguousRange::container_
+base::FeatureParam<bool, false>::feature
+base::FeatureParam<double, false>::feature
+base::FeatureParam<int, false>::feature
+base::FeatureParam<std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>>, false>::feature
+base::FeatureParam<type-parameter-0-0, true>::feature
+base::FeatureParam<type-parameter-0-0, true>::options
+base::span::data_
+crash_reporter::internal::CrashKeyStringCombinedImpl::breakpad_key_
+crash_reporter::internal::CrashKeyStringCombinedImpl::crashpad_key_
+crash_reporter::internal::CrashKeyStringImpl::index_array_
+crash_reporter::internal::CrashKeyStringImpl::index_array_
+flags_ui::FeatureEntry::FeatureVariation::params
+gfx::VectorIcon::reps
+gfx::VectorIconRep::path
+safe_browsing::(anonymous namespace)::(anonymous struct)::feature
+tracing::MessageInfo::accepted_field_ids
+tracing::MessageInfo::sub_messages
+ui::(anonymous namespace)::I18nTestParam::test
+
+# Populated manually - double implicit-cast required.
+PaintManager::instance_
+
+# Populated manually - needed to avoid global constructors
+base::(anonymous namespace)::Provider::next
+
+# Populated manually - needed for lock annotations
+base::trace_event::TraceLog::OptionalAutoLock::lock_
+
+# Populated manually to avoid hitting tokens limit explicitly
+# enforced in check.cc:
+# // check.h is a widely included header and its size has significant impact on
+# // build time. Try not to raise this limit unless absolutely necessary. See
+# // https://chromium.googlesource.com/chromium/src/+/HEAD/docs/wmax_tokens.md
+# #pragma clang max_tokens_here 17000
+logging::CheckError::log_message_
+
+# Populated manually - templates make it difficult for the rewriter to see that
+# |.get()| needs to be appended.
+blink::xpath::EvaluationContext::node
+WTF::HashTableAddResult::container_
+WTF::HashTableAddResult::stored_value
+
+# Populated manually - conflicting types in an initializer list
+WebUITabStripContainerView::tab_counter_
+
+# Populated manually to avoid returning a reference to a temporary T* (result of
+# implicitly casting CheckedPtr<T> to T*). This covers functions that return
+# |SomeClass*&| and local variables with that type.
+AllTabContentsesList::Iterator::cur_
+blink::NormalPage::CompactionContext::current_page_
+blink::Worklist::PrivateSegmentHolder::private_pop_segment
+blink::Worklist::PrivateSegmentHolder::private_push_segment
+content::(anonymous namespace)::AudioStreamMonitorTest::monitor_
+content::AudioStreamMonitor::clock_
+remoting::ChromotingHostTest::client1_
+remoting::ChromotingHostTest::client2_
+WTF::ICUConverterWrapper::converter
+
+# Populated manually - always holds a pointer to an OilPan-allocated memory.
+blink::PersistentBase::raw_
+blink::SVGFilterBuilder::last_effect_
+
+# Populated manually - the rewriter has trouble appending |.get()| inside macros
+# that work with |XDisplay*|.
+extensions::GlobalShortcutListenerX11::x_display_
+gl::GLVisualPickerGLX::display_
+media::(anonymous namespace)::UserInputMonitorLinuxCore::x_record_display_
+media::cast::test::LinuxOutputWindow::display_
+remoting::(anonymous namespace)::InputInjectorX11::Core::display_
+remoting::(anonymous namespace)::LocalHotkeyInputMonitorX11::Core::x_record_display_
+remoting::(anonymous namespace)::LocalMouseInputMonitorX11::Core::x_record_display_
+remoting::ClipboardX11::display_
+remoting::XServerClipboard::display_
+Simulator::display_
+ui::ClipboardX11::X11Details::x_display_
+ui::SelectionRequestorTest::x_display_
+ui::X11EventSource::display_
+ui::X11MenuRegistrar::xdisplay_
+ui::X11WorkspaceHandler::xdisplay_
+ui::XDisplayManager::xdisplay_
+ui::XOSExchangeDataProvider::x_display_
+ui::XVisualManager::display_
+ui::XWindow::xdisplay_
+views::test::(anonymous namespace)::UIControlsDesktopX11::x_display_
+viz::SkiaOutputDeviceX11::display_
+x11::Connection::display_
+
+# Populated manually - static_cast not related by inheritance.
+gtk::GtkKeyBindingsHandler::fake_window_
+gtk::GtkKeyBindingsHandler::handler_
+gtk::SelectFileDialogImplGTK::preview_
+net::(anonymous namespace)::SettingGetterImplGSettings::client_
+net::(anonymous namespace)::SettingGetterImplGSettings::http_client_
+net::(anonymous namespace)::SettingGetterImplGSettings::https_client_
+net::(anonymous namespace)::SettingGetterImplGSettings::ftp_client_
+net::(anonymous namespace)::SettingGetterImplGSettings::socks_client_
+remoting::(anonymous namespace)::DisconnectWindowGtk::disconnect_window_
+remoting::(anonymous namespace)::DisconnectWindowGtk::message_
+remoting::(anonymous namespace)::GtkFileChooserOnUiThread::file_dialog_
+remoting::(anonymous namespace)::It2MeConfirmationDialogLinux::confirmation_window_
+remoting::ContinueWindowGtk::continue_window_
+ui::AXPlatformNodeAuraLinux::atk_hyperlink_
+PrintDialogGtk::dialog_
+
+# Populated manually - using nmap or base::AllocPages directly
+blink::GCInfoTable::table_
+disk_cache::MappedFile::buffer_
+
+# Populated manually:
+# 1. This guarantees that both fields of the union are skipped
+# (otherwise, |characters8| might be skipped because of anyCharType
+# heuristic, but |characters16| might be rewritte)
+# 2. Note that this is one of the cases where the code may write one
+# field of the union and then read another. According to the spec
+# this may result in undefined behavior.
+# https://en.cppreference.com/w/cpp/language/union: "It's undefined behavior
+# to read from the member of the union that wasn't most recently written."
+blink::VTTScanner::(anonymous union)::characters8
+blink::VTTScanner::(anonymous union)::characters16
+
+# Populated manually, because of in-out-arg usage.
+blink::PaintController::IdAsHashKey::client
+ui::AXPlatformNodeAuraLinux::document_parent_
+ui::AXPlatformNodeAuraLinux::atk_object_
+
+# Populated manually - problems related to passing to a templated && parameter,
+# which is later forwarded to something that doesn't vibe with CheckedPtr. The
+# rewriter has no visibility into where the parameter is forwarded to.
+ProfileDestroyer::profile_
+(anonymous namespace)::BluetoothLowEnergyApiTest::mock_adapter_
+base::trace_event::TraceValue::as_convertable
+content::RenderProcessHostImpl::browser_context_
+device::BluetoothDevice::adapter_
+
+# Populated manually - problems related to lambdas with no return type, where
+# the return value is CheckedPtr, but variable/parameter receiving the lambda
+# expects the raw pointer type.
+vr::LocationBarState::vector_icon
+vr::OmniboxSuggestion::icon
+
+# Populated manually - these pointers are assigned invalid address (with top
+# bits sets), which CheckedPtr is unable to handle, leading to run-time crashes.
+(anonymous namespace)::TlsVectorEntry::data
+blink::(anonymous namespace)::ThreadMarker::creating_thread_
+blink::ControlKey::name_
+performance_manager::frame_priority::BoostingVoteAggregator::Edge::src_
+performance_manager::frame_priority::BoostingVoteAggregator::Edge::dst_
diff --git a/chromium/tools/clang/rewrite_raw_ptr_fields/rewrite.sh b/chromium/tools/clang/rewrite_raw_ptr_fields/rewrite.sh
new file mode 100755
index 00000000000..e932c3248d5
--- /dev/null
+++ b/chromium/tools/clang/rewrite_raw_ptr_fields/rewrite.sh
@@ -0,0 +1,88 @@
+#!/bin/bash
+# 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.
+
+# IMPORTANT! Before running this script you have to perform these steps:
+# 1. Run `mkdir ~/scratch`
+# 2. Run `gn args out/{your_out_dir}` and set the following options:
+# use_goma = false
+# # this can be skipped if you do this in build/config/clang/clang.gni
+# clang_use_chrome_plugins = false
+#
+# (You can do these steps only once, as long as you don't delete the directories
+# between rewrites.)
+#
+# For more fine-grained instructions, see:
+# https://docs.google.com/document/d/1chTvr3fSofQNV_PDPEHRyUgcJCQBgTDOOBriW9gIm9M/edit?ts=5e9549a2#heading=h.fjdnrdg1gcty
+
+set -e # makes the script quit on any command failure
+
+OUT_DIR="out/rewrite"
+if [ "$1" != "" ]
+then
+ OUT_DIR="$1"
+fi
+
+COMPILE_DIRS=.
+EDIT_DIRS=.
+
+# Save llvm-build as it is about to be overwritten.
+mv third_party/llvm-build third_party/llvm-build-upstream
+
+# Build and test the rewriter.
+echo "*** Building the rewriter ***"
+time tools/clang/scripts/build.py \
+ --without-android \
+ --without-fuchsia \
+ --extra-tools rewrite_raw_ptr_fields
+tools/clang/rewrite_raw_ptr_fields/tests/run_all_tests.py
+
+# Build generated files that a successful compilation depends on.
+echo "*** Preparing targets ***"
+gn gen $OUT_DIR
+GEN_H_TARGETS=`ninja -C $OUT_DIR -t targets all | grep '^gen/.*\(\.h\|inc\|css_tokenizer_codepoints.cc\)' | cut -d : -f 1`
+time ninja -C $OUT_DIR $GEN_H_TARGETS
+
+# A preliminary rewriter run in a special mode that generates a list of fields
+# to ignore. These fields would likely lead to compiler errors if rewritten.
+echo "*** Generating the ignore list ***"
+time tools/clang/scripts/run_tool.py \
+ --tool rewrite_raw_ptr_fields \
+ --generate-compdb \
+ -p $OUT_DIR \
+ $COMPILE_DIRS > ~/scratch/rewriter.out
+cat ~/scratch/rewriter.out \
+ | sed '/^==== BEGIN FIELD FILTERS ====$/,/^==== END FIELD FILTERS ====$/{//!b};d' \
+ | sort | uniq > ~/scratch/automated-fields-to-ignore.txt
+cat ~/scratch/automated-fields-to-ignore.txt \
+ tools/clang/rewrite_raw_ptr_fields/manual-fields-to-ignore.txt \
+ >> ~/scratch/combined-fields-to-ignore.txt
+
+# Main rewrite.
+echo "*** Running the main rewrite phase ***"
+time tools/clang/scripts/run_tool.py \
+ --tool rewrite_raw_ptr_fields \
+ --tool-arg=--exclude-fields=$HOME/scratch/combined-fields-to-ignore.txt \
+ -p $OUT_DIR \
+ $COMPILE_DIRS > ~/scratch/rewriter.main.out
+
+# Apply edits generated by the main rewrite.
+echo "*** Applying edits ***"
+cat ~/scratch/rewriter.main.out | \
+ tools/clang/scripts/extract_edits.py | \
+ tools/clang/scripts/apply_edits.py -p $OUT_DIR $EDIT_DIRS
+
+# Revert directories that are known to be troublesome and/or not needed.
+git checkout -- base/allocator/ # prevent cycles; CheckedPtr uses allocator
+git checkout -- ppapi/ # lots of legacy C and pre-C++11 code
+git checkout -- tools/ # not built into Chrome
+git checkout -- net/tools/ # not built into Chrome
+
+# Format sources, as many lines are likely over 80 chars now.
+echo "*** Formatting ***"
+time git cl format
+
+# Restore llvm-build. Without this, your future builds will be painfully slow.
+rm -r -f third_party/llvm-build
+mv third_party/llvm-build-upstream third_party/llvm-build
diff --git a/chromium/tools/clang/scripts/apply_edits.py b/chromium/tools/clang/scripts/apply_edits.py
index f6e9dcb16c6..a49a739792b 100755
--- a/chromium/tools/clang/scripts/apply_edits.py
+++ b/chromium/tools/clang/scripts/apply_edits.py
@@ -76,7 +76,7 @@ def _ParseEditsFromStdin(build_directory):
if not os.path.isfile(path):
resolved_path = os.path.realpath(os.path.join(build_directory, path))
else:
- resolved_path = path
+ resolved_path = os.path.realpath(path)
if not os.path.isfile(resolved_path):
sys.stderr.write('Edit applies to a non-existent file: %s\n' % path)
@@ -197,12 +197,24 @@ def _InsertNonSystemIncludeHeader(filepath, header_line_to_add, contents):
def _ApplyReplacement(filepath, contents, edit, last_edit):
- if (last_edit is not None and edit.edit_type == last_edit.edit_type
- and edit.offset == last_edit.offset and edit.length == last_edit.length):
- raise ValueError(('Conflicting replacement text: ' +
- '%s at offset %d, length %d: "%s" != "%s"\n') %
- (filepath, edit.offset, edit.length, edit.replacement,
- last_edit.replacement))
+ assert (edit.edit_type == 'r')
+ assert ((last_edit is None) or (last_edit.edit_type == 'r'))
+
+ if last_edit is not None:
+ if edit.offset == last_edit.offset and edit.length == last_edit.length:
+ assert (edit.replacement != last_edit.replacement)
+ raise ValueError(('Conflicting replacement text: ' +
+ '%s at offset %d, length %d: "%s" != "%s"\n') %
+ (filepath, edit.offset, edit.length, edit.replacement,
+ last_edit.replacement))
+
+ if edit.offset + edit.length > last_edit.offset:
+ raise ValueError(
+ ('Overlapping replacements: ' +
+ '%s at offset %d, length %d: "%s" and ' +
+ 'offset %d, length %d: "%s"\n') %
+ (filepath, edit.offset, edit.length, edit.replacement,
+ last_edit.offset, last_edit.length, last_edit.replacement))
contents[edit.offset:edit.offset + edit.length] = edit.replacement
if not edit.replacement:
diff --git a/chromium/tools/clang/scripts/apply_edits_test.py b/chromium/tools/clang/scripts/apply_edits_test.py
index c6cb552860d..d8edd45f869 100755
--- a/chromium/tools/clang/scripts/apply_edits_test.py
+++ b/chromium/tools/clang/scripts/apply_edits_test.py
@@ -574,6 +574,17 @@ class ApplyReplacementTest(unittest.TestCase):
with self.assertRaisesRegexp(ValueError, expected_msg_regex):
_ApplyEdit(old_text, edit)
+ def testOverlappingReplacement(self):
+ old_text = "123 456 789"
+ last = _CreateReplacement(old_text, "456 789", "foo")
+ edit = _CreateReplacement(old_text, "123 456", "bar")
+ expected_msg_regex = 'Overlapping replacements'
+ expected_msg_regex += '.*some_file.cc'
+ expected_msg_regex += '.*offset 0, length 7.*"bar"'
+ expected_msg_regex += '.*offset 4, length 7.*"foo"'
+ with self.assertRaisesRegexp(ValueError, expected_msg_regex):
+ _ApplyEdit(old_text, edit, last_edit=last)
+
if __name__ == '__main__':
unittest.main()
diff --git a/chromium/tools/clang/scripts/build.py b/chromium/tools/clang/scripts/build.py
index 2425e0b705b..3f2af8b3cf9 100755
--- a/chromium/tools/clang/scripts/build.py
+++ b/chromium/tools/clang/scripts/build.py
@@ -14,6 +14,7 @@ from __future__ import print_function
import argparse
import glob
+import io
import json
import os
import pipes
@@ -215,7 +216,7 @@ def CreateChromeToolsShim():
tool detection logic munges them in a weird way."""
assert not any(i in os.path.basename(CHROME_TOOLS_SHIM_DIR) for i in '-_')
os.mkdir(CHROME_TOOLS_SHIM_DIR)
- with file(os.path.join(CHROME_TOOLS_SHIM_DIR, 'CMakeLists.txt'), 'w') as f:
+ with open(os.path.join(CHROME_TOOLS_SHIM_DIR, 'CMakeLists.txt'), 'w') as f:
f.write('# Automatically generated by tools/clang/scripts/update.py. ' +
'Do not edit.\n')
f.write('# Since tools/clang is located in another directory, use the \n')
@@ -326,7 +327,8 @@ def VerifyVersionOfBuiltClangMatchesVERSION():
clang = os.path.join(LLVM_BUILD_DIR, 'bin', 'clang')
if sys.platform == 'win32':
clang += '-cl.exe'
- version_out = subprocess.check_output([clang, '--version'])
+ version_out = subprocess.check_output([clang, '--version'],
+ universal_newlines=True)
version_out = re.match(r'clang version ([0-9.]+)', version_out).group(1)
if version_out != RELEASE_VERSION:
print(('unexpected clang version %s (not %s), '
@@ -339,33 +341,29 @@ def CopyLibstdcpp(args, build_dir):
if not args.gcc_toolchain:
return
# Find libstdc++.so.6
- libstdcpp = subprocess.check_output(
- [os.path.join(args.gcc_toolchain, 'bin', 'g++'),
- '-print-file-name=libstdc++.so.6']).rstrip()
+ libstdcpp = subprocess.check_output([
+ os.path.join(args.gcc_toolchain, 'bin', 'g++'),
+ '-print-file-name=libstdc++.so.6'
+ ],
+ universal_newlines=True).rstrip()
# Copy libstdc++.so.6 into the build dir so that the built binaries can find
# it. Binaries get their rpath set to $origin/../lib/. For clang, lld,
# etc. that live in the bin/ directory, this means they expect to find the .so
- # in their neighbouring lib/ dir. For other tools however, this doesn't work
- # since those exeuctables are spread out into different directories.
- # TODO(hans): Unit tests don't get rpath set at all, unittests/ copying
- # below doesn't help at the moment.
+ # in their neighbouring lib/ dir.
+ # For unit tests we pass -Wl,-rpath to the linker pointing to the lib64 dir
+ # in the gcc toolchain, via LLVM_LOCAL_RPATH below.
+ # The two fuzzer tests are weird in that they copy the fuzzer binary from bin/
+ # into the test tree under a different name. To make the relative rpath in
+ # them work, copy libstdc++ to the copied location for now.
+ # TODO(thakis): Instead, make the upstream lit.local.cfg.py for these 2 tests
+ # check if the binary contains an rpath and if so disable the tests.
for d in ['lib',
'test/tools/llvm-isel-fuzzer/lib',
- 'test/tools/llvm-opt-fuzzer/lib',
- 'unittests/CodeGen/lib',
- 'unittests/DebugInfo/lib',
- 'unittests/ExecutionEngine/lib',
- 'unittests/Support/lib',
- 'unittests/Target/lib',
- 'unittests/Transforms/lib',
- 'unittests/lib',
- 'unittests/tools/lib',
- 'unittests/tools/llvm-exegesis/lib']:
+ 'test/tools/llvm-opt-fuzzer/lib']:
EnsureDirExists(os.path.join(build_dir, d))
CopyFile(libstdcpp, os.path.join(build_dir, d))
-
def gn_arg(v):
if v == 'True':
return True
@@ -437,7 +435,16 @@ def main():
# Don't buffer stdout, so that print statements are immediately flushed.
- sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
+ # LLVM tests print output without newlines, so with buffering they won't be
+ # immediately printed.
+ major, _, _, _, _ = sys.version_info
+ if major == 3:
+ # Python3 only allows unbuffered output for binary streams. This
+ # workaround comes from https://stackoverflow.com/a/181654/4052492.
+ sys.stdout = io.TextIOWrapper(open(sys.stdout.fileno(), 'wb', 0),
+ write_through=True)
+ else:
+ sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
# The gnuwin package also includes curl, which is needed to interact with the
# github API below.
@@ -478,14 +485,6 @@ def main():
# LLVM_ENABLE_LLD).
cc, cxx, lld = None, None, None
- if args.gcc_toolchain:
- # Use the specified gcc installation for building.
- cc = os.path.join(args.gcc_toolchain, 'bin', 'gcc')
- cxx = os.path.join(args.gcc_toolchain, 'bin', 'g++')
- if not os.access(cc, os.X_OK):
- print('Invalid --gcc-toolchain: ' + args.gcc_toolchain)
- return 1
-
cflags = []
cxxflags = []
ldflags = []
@@ -519,6 +518,18 @@ def main():
'-DLLVM_INCLUDE_GO_TESTS=OFF',
]
+ if args.gcc_toolchain:
+ # Use the specified gcc installation for building.
+ cc = os.path.join(args.gcc_toolchain, 'bin', 'gcc')
+ cxx = os.path.join(args.gcc_toolchain, 'bin', 'g++')
+ if not os.access(cc, os.X_OK):
+ print('Invalid --gcc-toolchain: ' + args.gcc_toolchain)
+ return 1
+ base_cmake_args += [
+ '-DLLVM_LOCAL_RPATH=' + os.path.join(args.gcc_toolchain, 'lib64')
+ ]
+
+
if sys.platform == 'darwin':
# For libc++, we only want the headers.
base_cmake_args.extend([
@@ -531,7 +542,7 @@ def main():
if args.gcc_toolchain:
# Don't use the custom gcc toolchain when building compiler-rt tests; those
# tests are built with the just-built Clang, and target both i386 and x86_64
- # for example, so should ust the system's libstdc++.
+ # for example, so should use the system's libstdc++.
base_cmake_args.append(
'-DCOMPILER_RT_TEST_COMPILER_CFLAGS=--gcc-toolchain=')
@@ -867,28 +878,10 @@ def main():
CopyDirectoryContents(rt_lib_src_dir, rt_lib_dst_dir)
if args.with_android:
- make_toolchain = os.path.join(
- ANDROID_NDK_DIR, 'build', 'tools', 'make_standalone_toolchain.py')
# TODO(thakis): Now that the NDK uses clang, try to build all archs in
- # one LLVM build instead of making 3 different toolchains and building
- # 3 times.
+ # one LLVM build instead of building 3 times.
+ toolchain_dir = ANDROID_NDK_DIR + '/toolchains/llvm/prebuilt/linux-x86_64'
for target_arch in ['aarch64', 'arm', 'i686']:
- # Make standalone Android toolchain for target_arch.
- toolchain_dir = os.path.join(
- LLVM_BUILD_DIR, 'android-toolchain-' + target_arch)
- api_level = '21' if target_arch == 'aarch64' else '19'
- RunCommand([
- make_toolchain,
- '--api=' + api_level,
- '--force',
- '--install-dir=%s' % toolchain_dir,
- '--stl=libc++',
- '--arch=' + {
- 'aarch64': 'arm64',
- 'arm': 'arm',
- 'i686': 'x86',
- }[target_arch]])
-
# Build compiler-rt runtimes needed for Android in a separate build tree.
build_dir = os.path.join(LLVM_BUILD_DIR, 'android-' + target_arch)
if not os.path.exists(build_dir):
@@ -897,10 +890,13 @@ def main():
target_triple = target_arch
if target_arch == 'arm':
target_triple = 'armv7'
+ api_level = '21' if target_arch == 'aarch64' else '19'
target_triple += '-linux-android' + api_level
- cflags = ['--target=%s' % target_triple,
- '--sysroot=%s/sysroot' % toolchain_dir,
- '-B%s' % toolchain_dir]
+ cflags = [
+ '--target=' + target_triple,
+ '--sysroot=%s/sysroot' % toolchain_dir,
+ '--gcc-toolchain=' + toolchain_dir,
+ ]
android_args = base_cmake_args + [
'-DCMAKE_C_COMPILER=' + os.path.join(LLVM_BUILD_DIR, 'bin/clang'),
'-DCMAKE_CXX_COMPILER=' + os.path.join(LLVM_BUILD_DIR, 'bin/clang++'),
diff --git a/chromium/tools/clang/scripts/clang_tidy_tool.py b/chromium/tools/clang/scripts/clang_tidy_tool.py
index 57e54f7049d..afbe12bb45d 100755
--- a/chromium/tools/clang/scripts/clang_tidy_tool.py
+++ b/chromium/tools/clang/scripts/clang_tidy_tool.py
@@ -107,8 +107,7 @@ def RunClangTidy(checks, header_filter, auto_fix, clang_src_dir,
subprocess.check_call(args)
-def RunClangTidyDiff(checks, header_filter, auto_fix, clang_src_dir,
- clang_build_dir, out_dir):
+def RunClangTidyDiff(checks, auto_fix, clang_src_dir, clang_build_dir, out_dir):
"""Invoke the |clang-tidy-diff.py| script over the diff from stdin."""
clang_tidy_diff_script = os.path.join(
clang_src_dir, 'clang-tools-extra', 'clang-tidy', 'tool',
@@ -129,9 +128,6 @@ def RunClangTidyDiff(checks, header_filter, auto_fix, clang_src_dir,
if checks:
args.append('-checks={}'.format(checks))
- if header_filter:
- args.append('-header-filter={}'.format(header_filter))
-
if auto_fix:
args.append('-fix')
@@ -202,11 +198,9 @@ def main():
]
if args.diff:
steps += [
- ('Running clang-tidy on diff',
- lambda: RunClangTidyDiff(args.checks, args.header_filter,
- args.auto_fix, args.clang_src_dir,
- args.clang_build_dir, args.OUT_DIR,
- args.NINJA_TARGET)),
+ ('Running clang-tidy on diff', lambda: RunClangTidyDiff(
+ args.checks, args.auto_fix, args.clang_src_dir, args.
+ clang_build_dir, args.OUT_DIR)),
]
else:
steps += [
diff --git a/chromium/tools/clang/scripts/expand_thin_archives.py b/chromium/tools/clang/scripts/expand_thin_archives.py
index ede95921f77..b51393587da 100755
--- a/chromium/tools/clang/scripts/expand_thin_archives.py
+++ b/chromium/tools/clang/scripts/expand_thin_archives.py
@@ -12,120 +12,10 @@ from __future__ import print_function
from __future__ import unicode_literals
import argparse
-import errno
-import io
-import os
-import re
import sys
-COMPILER_RE = re.compile('clang')
-LINKER_RE = re.compile('l(?:ld|ink)')
-LIB_RE = re.compile('.*\\.(?:a|lib)', re.IGNORECASE)
-OBJ_RE = re.compile(b'(.*)\\.(o(?:bj)?)', re.IGNORECASE)
-THIN_AR_LFN_RE = re.compile('/([0-9]+)')
-
-
-def ensure_dir(path):
- """
- Creates path as a directory if it does not already exist.
- """
- try:
- os.makedirs(path)
- except OSError as e:
- if e.errno != errno.EEXIST:
- raise
-
-
-def thin_archive(path):
- """
- Returns True if path refers to a thin archive (ar file), False if not.
- """
- with open(path, 'rb') as f:
- return f.read(8) == b'!<thin>\n'
-
-
-def write_rsp(path, params):
- """
- Writes params to a newly created response file at path.
- """
- ensure_dir(os.path.basename(path))
- with open(path, 'wb') as f:
- f.write(b'\n'.join(params))
-
-
-def names_in_archive(path):
- """
- Yields the member names in the archive file at path.
- """
- with open(path, 'rb') as f:
- long_names = None
- f.seek(8, io.SEEK_CUR)
- while True:
- file_id = f.read(16)
- if len(file_id) == 0:
- break
- f.seek(32, io.SEEK_CUR)
- m = THIN_AR_LFN_RE.match(file_id)
- if long_names and m:
- name_pos = long(m.group(1))
- name_end = long_names.find('/\n', name_pos)
- name = long_names[name_pos:name_end]
- else:
- name = file_id
- try:
- size = long(f.read(10))
- except:
- sys.stderr.write('While parsing %r, pos %r\n' % (path, f.tell()))
- raise
- # Two entries are special: '/' and '//'. The former is
- # the symbol table, which we skip. The latter is the long
- # file name table, which we read.
- # Anything else is a filename entry which we yield.
- # Every file record ends with two terminating characters
- # which we skip.
- seek_distance = 2
- if file_id == '/ ':
- # Skip symbol table.
- seek_distance += size + (size & 1)
- elif file_id == '// ':
- # Read long name table.
- f.seek(2, io.SEEK_CUR)
- long_names = f.read(size)
- seek_distance = size & 1
- else:
- yield name
- f.seek(seek_distance, io.SEEK_CUR)
-
-
-def expand_args(args, linker_prefix=''):
- """
- Yields the parameters in args, with thin archives replaced by a sequence of
- '-start-lib', the member names, and '-end-lib'. This is used to get a command
- line where members of thin archives are mentioned explicitly.
- """
- for arg in args:
- if len(arg) > 1 and arg[0] == '@':
- for x in expand_rsp(arg[1:], linker_prefix):
- yield x
- elif LIB_RE.match(arg) and os.path.exists(arg) and thin_archive(arg):
- yield(linker_prefix + '-start-lib')
- for name in names_in_archive(arg):
- yield(os.path.dirname(arg) + '/' + name)
- yield(linker_prefix + '-end-lib')
- else:
- yield(arg)
-
-
-def expand_rsp(rspname, linker_prefix=''):
- """
- Yields the parameters found in the response file at rspname, with thin
- archives replaced by a sequence of '-start-lib', the member names,
- and '-end-lib'. This is used to get a command line where members of
- thin archives are mentioned explicitly.
- """
- with open(rspname) as f:
- for x in expand_args(f.read().split(), linker_prefix):
- yield x
+from goma_link import GomaLinkWindows
+from goma_ld import GomaLinkUnix
def main(argv):
@@ -152,11 +42,19 @@ def main(argv):
cmdline = cmdline[1:]
linker_prefix = args.linker_prefix
+ if linker_prefix == '-Wl,':
+ linker = GomaLinkUnix()
+ else:
+ linker = GomaLinkWindows()
+
+ rsp_expanded = list(linker.expand_args_rsps(cmdline))
+ expanded_args = list(linker.expand_thin_archives(rsp_expanded))
+
if args.output:
output = open(args.output, 'w')
else:
output = sys.stdout
- for arg in expand_args(cmdline, linker_prefix=linker_prefix):
+ for arg in expanded_args:
output.write('%s\n' % (arg,))
if args.output:
output.close()
diff --git a/chromium/tools/clang/scripts/goma_ld.py b/chromium/tools/clang/scripts/goma_ld.py
index 9a7365bf90c..576dfa9f9d2 100755
--- a/chromium/tools/clang/scripts/goma_ld.py
+++ b/chromium/tools/clang/scripts/goma_ld.py
@@ -26,6 +26,8 @@ class GomaLinkUnix(goma_link.GomaLinkBase):
WL = '-Wl,'
TLTO = '-plugin-opt=thinlto'
SEP = '='
+ DATA_SECTIONS = '-fdata-sections'
+ FUNCTION_SECTIONS = '-ffunction-sections'
GROUP_RE = re.compile(WL + '--(?:end|start)-group')
MACHINE_RE = re.compile('-m([0-9]+)')
OBJ_PATH = '-plugin-opt=obj-path' + SEP
diff --git a/chromium/tools/clang/scripts/goma_link.py b/chromium/tools/clang/scripts/goma_link.py
index 7c4e5cd9534..8bf194e4bdb 100755
--- a/chromium/tools/clang/scripts/goma_link.py
+++ b/chromium/tools/clang/scripts/goma_link.py
@@ -218,6 +218,10 @@ class GomaLinkBase(object):
jobs = None
# These constants should work across platforms.
+ DATA_SECTIONS_RE = re.compile('-f(no-)?data-sections|[-/]Gw(-)?',
+ re.IGNORECASE)
+ FUNCTION_SECTIONS_RE = re.compile('-f(no-)?function-sections|[-/]Gy(-)?',
+ re.IGNORECASE)
LIB_RE = re.compile('.*\\.(?:a|lib)', re.IGNORECASE)
LTO_RE = re.compile('|'.join((
'-fsanitize=cfi.*',
@@ -227,8 +231,36 @@ class GomaLinkBase(object):
'-Wl,--lto.*',
'-Wl,--thin.*',
)))
+ MLLVM_RE = re.compile('(?:-Wl,)?([-/]mllvm)[:=]?(.*)', re.IGNORECASE)
OBJ_RE = re.compile('(.*)\\.(o(?:bj)?)', re.IGNORECASE)
+ def transform_codegen_param(self, param):
+ return self.transform_codegen_param_common(param)
+
+ def transform_codegen_param_common(self, param):
+ """
+ If param is a parameter relevant to code generation, returns the
+ parameter in a form that is suitable to pass to clang. For values
+ of param that are not relevant to code generation, returns None.
+ """
+ match = self.MACHINE_RE.match(param)
+ if match and match.group(1).lower() in ['x86', 'i386', 'arm', '32']:
+ return ['-m32']
+ match = self.MLLVM_RE.match(param)
+ if match:
+ if match.group(2):
+ return ['-mllvm', match.group(2)]
+ else:
+ return ['-mllvm']
+ if (param.startswith('-f') and not param.startswith('-flto')
+ and not param.startswith('-fsanitize')
+ and not param.startswith('-fthinlto')
+ and not param.startswith('-fwhole-program')):
+ return [param]
+ if param.startswith('-g'):
+ return [param]
+ return None
+
def output_path(self, args):
"""
Analyzes command line arguments in args and returns the output
@@ -354,33 +386,11 @@ class GomaLinkBase(object):
]
final_params = []
in_mllvm = [False]
- optlevel = [2]
-
- MLLVM_RE = re.compile('(?:-Wl,)?([-/]mllvm)[:=]?(.*)', re.IGNORECASE)
- def transform_codegen_param(param):
- """
- If param is a parameter relevant to code generation, returns the
- parameter in a form that is suitable to pass to clang. For values
- of param that are not relevant to code generation, returns None.
- """
- match = self.MACHINE_RE.match(param)
- if match and match.group(1).lower() in ['x86', 'i386', 'arm', '32']:
- return ['-m32']
- match = MLLVM_RE.match(param)
- if match:
- if match.group(2):
- return ['-mllvm', match.group(2)]
- else:
- return ['-mllvm']
- if (param.startswith('-f') and not param.startswith('-flto')
- and not param.startswith('-fsanitize')
- and not param.startswith('-fthinlto')
- and not param.startswith('-fwhole-program')):
- return [param]
- if param.startswith('-g'):
- return [param]
- return None
+ # Defaults that match those for local linking.
+ optlevel = [2]
+ data_sections = [True]
+ function_sections = [True]
def extract_opt_level(param):
"""
@@ -422,17 +432,24 @@ class GomaLinkBase(object):
return
# Check for params that affect code generation.
- cg_param = transform_codegen_param(param)
+ cg_param = self.transform_codegen_param(param)
if cg_param:
codegen_params.extend(cg_param)
# No return here, we still want to check for -mllvm.
# Check for -mllvm.
- match = MLLVM_RE.match(param)
+ match = self.MLLVM_RE.match(param)
if match and not match.group(2):
# Next parameter will be the thing to pass to LLVM.
in_mllvm[0] = True
+ # Parameters that override defaults disable the defaults; the
+ # final value is set by passing through the parameter.
+ if self.DATA_SECTIONS_RE.match(param):
+ data_sections[0] = False
+ if self.FUNCTION_SECTIONS_RE.match(param):
+ function_sections[0] = False
+
helper()
if self.GROUP_RE.match(param):
return
@@ -452,7 +469,7 @@ class GomaLinkBase(object):
elif not self.LTO_RE.match(param):
final_params.append(param)
- index_params.append(self.WL + self.PREFIX_REPLACE + ';' + obj_dir)
+ index_params.append(self.WL + self.PREFIX_REPLACE + ';' + obj_dir + '/')
i = 0
while i < len(args):
x = args[i]
@@ -471,6 +488,10 @@ class GomaLinkBase(object):
return None
codegen_params.append('-O' + str(optlevel[0]))
+ if data_sections[0]:
+ codegen_params.append(self.DATA_SECTIONS)
+ if function_sections[0]:
+ codegen_params.append(self.FUNCTION_SECTIONS)
if use_common_objects:
splitfile = None
@@ -623,6 +644,8 @@ class GomaLinkWindows(GomaLinkBase):
WL = ''
TLTO = '-thinlto'
SEP = ':'
+ DATA_SECTIONS = '-Gw'
+ FUNCTION_SECTIONS = '-Gy'
GROUP_RE = re.compile(WL + '--(?:end|start)-group')
MACHINE_RE = re.compile('[-/]machine:(.*)', re.IGNORECASE)
OBJ_PATH = '-lto-obj-path' + SEP
@@ -641,6 +664,14 @@ class GomaLinkWindows(GomaLinkBase):
'tls_edit.exe',
}
+ def transform_codegen_param(self, param):
+ # In addition to parameters handled by transform_codegen_param_common,
+ # we pass on parameters that start in 'G' or 'Q', which are
+ # MSVC-style parameters that affect code generation.
+ if len(param) >= 2 and param[0] in ['-', '/'] and param[1] in ['G', 'Q']:
+ return [param]
+ return self.transform_codegen_param_common(param)
+
def process_output_param(self, args, i):
"""
If args[i] is a parameter that specifies the output file,
diff --git a/chromium/tools/clang/scripts/goma_link_unit_tests.py b/chromium/tools/clang/scripts/goma_link_unit_tests.py
index e35179d36fb..255ff6ad1f6 100755
--- a/chromium/tools/clang/scripts/goma_link_unit_tests.py
+++ b/chromium/tools/clang/scripts/goma_link_unit_tests.py
@@ -125,6 +125,84 @@ class GomaLinkUnitTest(unittest.TestCase):
self.assertNotIn('-flto=thin', result.final_params)
+ def test_codegen_params_default(self):
+ with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
+ result = goma_ld.GomaLinkUnix().analyze_expanded_args(
+ ['clang', 'foo.o', 'bar.o', '-o', 'foo'], 'foo', 'clang', 'lto.foo',
+ 'common', False)
+ # Codegen optimization level should default to 2.
+ self.assertIn('-O2', result.codegen_params)
+ # -fdata-sections and -ffunction-sections default to on to match the
+ # behavior of local linking.
+ self.assertIn('-fdata-sections', result.codegen_params)
+ self.assertIn('-ffunction-sections', result.codegen_params)
+
+ def test_codegen_params_default_cl(self):
+ with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
+ result = goma_link.GomaLinkWindows().analyze_expanded_args(
+ ['clang-cl', 'foo.obj', 'bar.obj', '-Fefoo.exe'], 'foo.exe',
+ 'clang-cl', 'lto.foo', 'common', False)
+ # Codegen optimization level should default to 2.
+ self.assertIn('-O2', result.codegen_params)
+ # -Gw and -Gy default to on to match the behavior of local linking.
+ self.assertIn('-Gw', result.codegen_params)
+ self.assertIn('-Gy', result.codegen_params)
+
+ def test_codegen_params_no_data_sections(self):
+ with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
+ result = goma_ld.GomaLinkUnix().analyze_expanded_args(
+ ['clang', '-fno-data-sections', 'foo.o', 'bar.o', '-o', 'foo'], 'foo',
+ 'clang', 'lto.foo', 'common', False)
+ self.assertNotIn('-fdata-sections', result.codegen_params)
+ self.assertIn('-ffunction-sections', result.codegen_params)
+
+ def test_codegen_params_no_function_sections(self):
+ with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
+ result = goma_ld.GomaLinkUnix().analyze_expanded_args(
+ ['clang', '-fno-function-sections', 'foo.o', 'bar.o', '-o', 'foo'],
+ 'foo', 'clang', 'lto.foo', 'common', False)
+ self.assertIn('-fdata-sections', result.codegen_params)
+ self.assertNotIn('-ffunction-sections', result.codegen_params)
+
+ def test_codegen_params_no_data_sections_cl(self):
+ with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
+ result = goma_link.GomaLinkWindows().analyze_expanded_args(
+ ['clang-cl', '/Gw-', 'foo.obj', 'bar.obj', '/Fefoo.exe'], 'foo.exe',
+ 'clang-cl', 'lto.foo', 'common', False)
+ self.assertNotIn('-fdata-sections', result.codegen_params)
+ self.assertNotIn('-Gw', result.codegen_params)
+ self.assertNotIn('/Gw', result.codegen_params)
+ self.assertIn('-Gy', result.codegen_params)
+
+ def test_codegen_params_no_function_sections_cl(self):
+ with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
+ result = goma_link.GomaLinkWindows().analyze_expanded_args(
+ ['clang-cl', '/Gy-', 'foo.obj', 'bar.obj', '/Fefoo.exe'], 'foo.exe',
+ 'clang-cl', 'lto.foo', 'common', False)
+ self.assertIn('-Gw', result.codegen_params)
+ self.assertNotIn('-ffunction-sections', result.codegen_params)
+ self.assertNotIn('-Gy', result.codegen_params)
+ self.assertNotIn('/Gy', result.codegen_params)
+
+ def test_codegen_params_explicit_data_and_function_sections(self):
+ with FakeFs(bitcode_files=['foo.o'], other_files=['bar.o']):
+ result = goma_ld.GomaLinkUnix().analyze_expanded_args([
+ 'clang', '-ffunction-sections', '-fdata-sections', 'foo.o', 'bar.o',
+ '-o', 'foo'
+ ], 'foo', 'clang', 'lto.foo', 'common', False)
+ self.assertIn('-fdata-sections', result.codegen_params)
+ self.assertIn('-ffunction-sections', result.codegen_params)
+
+ def test_codegen_params_explicit_data_and_function_sections_cl(self):
+ with FakeFs(bitcode_files=['foo.obj'], other_files=['bar.obj']):
+ result = goma_link.GomaLinkWindows().analyze_expanded_args(
+ ['clang-cl', '/Gy', '-Gw', 'foo.obj', 'bar.obj', '/Fefoo.exe'],
+ 'foo.exe', 'clang-cl', 'lto.foo', 'common', False)
+ self.assertIn('-Gw', result.codegen_params)
+ self.assertIn('/Gy', result.codegen_params)
+ self.assertNotIn('-fdata-sections', result.codegen_params)
+ self.assertNotIn('-ffunction-sections', result.codegen_params)
+
def test_ensure_file_no_dir(self):
with named_directory() as d, working_directory(d):
self.assertFalse(os.path.exists('test'))
diff --git a/chromium/tools/clang/scripts/update.py b/chromium/tools/clang/scripts/update.py
index 83e0ede51fb..c9bdcf3ae20 100755
--- a/chromium/tools/clang/scripts/update.py
+++ b/chromium/tools/clang/scripts/update.py
@@ -37,8 +37,8 @@ import zipfile
# Do NOT CHANGE this if you don't know what you're doing -- see
# https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang.md
# Reverting problematic clang rolls is safe, though.
-CLANG_REVISION = '99ac9ce7016d701b43b8f0c308dc3463da57d983'
-CLANG_SVN_REVISION = 'n353803'
+CLANG_REVISION = '4e813bbdf'
+CLANG_SVN_REVISION = 'n356902'
CLANG_SUB_REVISION = 1
PACKAGE_VERSION = '%s-%s-%s' % (CLANG_SVN_REVISION, CLANG_REVISION[:8],
diff --git a/chromium/tools/clang/scripts/upload_revision.py b/chromium/tools/clang/scripts/upload_revision.py
index cbe0c8b961b..9cd4484cd2b 100755
--- a/chromium/tools/clang/scripts/upload_revision.py
+++ b/chromium/tools/clang/scripts/upload_revision.py
@@ -29,6 +29,7 @@ CHROMIUM_DIR = os.path.abspath(os.path.join(THIS_DIR, '..', '..', '..'))
# Keep lines in here at <= 72 columns, else they wrap in gerrit.
COMMIT_FOOTER = \
'''
+TODO: Add bug number.
Cq-Include-Trybots: chromium/try:mac_chromium_asan_rel_ng
Cq-Include-Trybots: chromium/try:linux_chromium_cfi_rel_ng
@@ -39,7 +40,7 @@ Cq-Include-Trybots: chromium/try:linux-chromeos-dbg,win-asan
Cq-Include-Trybots: chromium/try:chromeos-amd64-generic-cfi-thin-lto-rel
Cq-Include-Trybots: chromium/try:linux_chromium_compile_dbg_32_ng
Cq-Include-Trybots: chromium/try:win7-rel,win-angle-deqp-rel-32
-Cq-Include-Trybots: chromium/try:win-angle-deqp-rel-64
+Cq-Include-Trybots: chromium/try:win-angle-deqp-rel-64,linux_angle_deqp_rel_ng
Cq-Include-Trybots: chromium/try:dawn-win10-x86-deps-rel
Cq-Include-Trybots: chrome/try:iphone-device,ipad-device
Cq-Include-Trybots: chrome/try:linux-chromeos-chrome