diff options
Diffstat (limited to 'chromium/tools/clang')
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, ¯o_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 |