[check] Check includes using <bracket> style as well as "quotes"

This teaches the gn check tool to consider include directives using the
system <bracket> style in addition to those using the "quote" style.
The handling of the two is identical except that when examining a
system style include the directory of the file containing the include
is not implicitly considered an include directory.

This behavior is disabled by default and can be enabled in three ways:

1.) Set the variable 'check_system_includes' to true in a build's .gn
configuration file.
2.) Pass the '--system' flag to the 'gn check' command
3.) Pass the '=system' value to the '--check' switch for gen:
  gn gen --check=system

Change-Id: I12a3697efe5443de4ebabdc7968f0b367ca2c54d
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/6200
Commit-Queue: James Robinson <jamesr@google.com>
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: Brett Wilson <brettw@chromium.org>
diff --git a/src/gn/c_include_iterator.cc b/src/gn/c_include_iterator.cc
index babf1ac..f8626e1 100644
--- a/src/gn/c_include_iterator.cc
+++ b/src/gn/c_include_iterator.cc
@@ -126,8 +126,8 @@
 
 CIncludeIterator::~CIncludeIterator() = default;
 
-bool CIncludeIterator::GetNextIncludeString(std::string_view* out,
-                                            LocationRange* location) {
+bool CIncludeIterator::GetNextIncludeString(
+    IncludeStringWithLocation* include) {
   std::string_view line;
   int cur_line_number = 0;
   while (lines_since_last_include_ <= kMaxNonIncludeLines &&
@@ -135,15 +135,17 @@
     std::string_view include_contents;
     int begin_char;
     IncludeType type = ExtractInclude(line, &include_contents, &begin_char);
-    if (type == INCLUDE_USER && !HasNoCheckAnnotation(line)) {
-      // Only count user includes for now.
-      *out = include_contents;
-      *location = LocationRange(
+    if (HasNoCheckAnnotation(line))
+      continue;
+    if (type != INCLUDE_NONE) {
+      include->contents = include_contents;
+      include->location = LocationRange(
           Location(input_file_, cur_line_number, begin_char,
                    -1 /* TODO(scottmg): Is this important? */),
           Location(input_file_, cur_line_number,
                    begin_char + static_cast<int>(include_contents.size()),
                    -1 /* TODO(scottmg): Is this important? */));
+      include->system_style_include = (type == INCLUDE_SYSTEM);
 
       lines_since_last_include_ = 0;
       return true;
diff --git a/src/gn/c_include_iterator.h b/src/gn/c_include_iterator.h
index 0c379e1..325d57e 100644
--- a/src/gn/c_include_iterator.h
+++ b/src/gn/c_include_iterator.h
@@ -10,14 +10,17 @@
 #include <string_view>
 
 #include "base/macros.h"
+#include "gn/location.h"
 
 class InputFile;
-class LocationRange;
+
+struct IncludeStringWithLocation {
+  std::string_view contents;
+  LocationRange location;
+  bool system_style_include = false;
+};
 
 // Iterates through #includes in C source and header files.
-//
-// This only returns includes we want to check, which is user includes with
-// double-quotes: #include "..."
 class CIncludeIterator {
  public:
   // The InputFile pointed to must outlive this class.
@@ -27,7 +30,7 @@
   // Fills in the string with the contents of the next include, and the
   // location with where it came from, and returns true, or returns false if
   // there are no more includes.
-  bool GetNextIncludeString(std::string_view* out, LocationRange* location);
+  bool GetNextIncludeString(IncludeStringWithLocation* include);
 
   // Maximum numbef of non-includes we'll tolerate before giving up. This does
   // not count comments or preprocessor.
diff --git a/src/gn/c_include_iterator_unittest.cc b/src/gn/c_include_iterator_unittest.cc
index 8bb7336..b162ced 100644
--- a/src/gn/c_include_iterator_unittest.cc
+++ b/src/gn/c_include_iterator_unittest.cc
@@ -44,27 +44,35 @@
 
   CIncludeIterator iter(&file);
 
-  std::string_view contents;
-  LocationRange range;
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("foo/bar.h", contents);
-  EXPECT_TRUE(RangeIs(range, 3, 11, 20)) << range.begin().Describe(true);
+  IncludeStringWithLocation include;
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 3, 11, 20)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
 
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("foo/baz.h", contents);
-  EXPECT_TRUE(RangeIs(range, 7, 12, 21)) << range.begin().Describe(true);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("stdio.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 5, 11, 18)) << include.location.begin().Describe(true);
+  EXPECT_TRUE(include.system_style_include);
 
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("la/deda.h", contents);
-  EXPECT_TRUE(RangeIs(range, 8, 11, 20)) << range.begin().Describe(true);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/baz.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 7, 12, 21)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
+
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("la/deda.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 8, 11, 20)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
 
   // The line annotated with "nogncheck" should be skipped.
 
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("weird_mac_import.h", contents);
-  EXPECT_TRUE(RangeIs(range, 10, 10, 28)) << range.begin().Describe(true);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("weird_mac_import.h", include.contents);
+  EXPECT_TRUE(RangeIs(include.location, 10, 10, 28)) << include.location.begin().Describe(true);
+  EXPECT_FALSE(include.system_style_include);
 
-  EXPECT_FALSE(iter.GetNextIncludeString(&contents, &range));
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
 }
 
 // Tests that we don't search for includes indefinitely.
@@ -77,12 +85,11 @@
   InputFile file(SourceFile("//foo.cc"));
   file.SetContents(buffer);
 
-  std::string_view contents;
-  LocationRange range;
+  IncludeStringWithLocation include;
 
   CIncludeIterator iter(&file);
-  EXPECT_FALSE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_TRUE(contents.empty());
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
+  EXPECT_TRUE(include.contents.empty());
 }
 
 // Don't count blank lines, comments, and preprocessor when giving up.
@@ -99,12 +106,11 @@
   InputFile file(SourceFile("//foo.cc"));
   file.SetContents(buffer);
 
-  std::string_view contents;
-  LocationRange range;
+  IncludeStringWithLocation include;
 
   CIncludeIterator iter(&file);
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("foo/bar.h", contents);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
 }
 
 // Tests that we'll tolerate some small numbers of non-includes interspersed
@@ -113,28 +119,27 @@
   const size_t kSkip = CIncludeIterator::kMaxNonIncludeLines - 2;
   const size_t kGroupCount = 100;
 
-  std::string include("foo/bar.h");
+  std::string include_str("foo/bar.h");
 
   // Allow a series of includes with blanks in between.
   std::string buffer;
   for (size_t group = 0; group < kGroupCount; group++) {
     for (size_t i = 0; i < kSkip; i++)
       buffer.append("foo\n");
-    buffer.append("#include \"" + include + "\"\n");
+    buffer.append("#include \"" + include_str + "\"\n");
   }
 
   InputFile file(SourceFile("//foo.cc"));
   file.SetContents(buffer);
 
-  std::string_view contents;
-  LocationRange range;
+  IncludeStringWithLocation include;
 
   CIncludeIterator iter(&file);
   for (size_t group = 0; group < kGroupCount; group++) {
-    EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-    EXPECT_EQ(include, std::string(contents));
+    EXPECT_TRUE(iter.GetNextIncludeString(&include));
+    EXPECT_EQ(include_str, std::string(include.contents));
   }
-  EXPECT_FALSE(iter.GetNextIncludeString(&contents, &range));
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
 }
 
 // Tests that comments of the form
@@ -152,12 +157,11 @@
   InputFile file(SourceFile("//foo.cc"));
   file.SetContents(buffer);
 
-  std::string_view contents;
-  LocationRange range;
+  IncludeStringWithLocation include;
 
   CIncludeIterator iter(&file);
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("foo/bar.h", contents);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
 }
 
 // Tests that spaces between the hash and directive are ignored.
@@ -167,12 +171,11 @@
   InputFile file(SourceFile("//foo.cc"));
   file.SetContents(buffer);
 
-  std::string_view contents;
-  LocationRange range;
+  IncludeStringWithLocation include;
 
   CIncludeIterator iter(&file);
-  EXPECT_TRUE(iter.GetNextIncludeString(&contents, &range));
-  EXPECT_EQ("foo/bar.h", contents);
+  EXPECT_TRUE(iter.GetNextIncludeString(&include));
+  EXPECT_EQ("foo/bar.h", include.contents);
 
-  EXPECT_FALSE(iter.GetNextIncludeString(&contents, &range));
+  EXPECT_FALSE(iter.GetNextIncludeString(&include));
 }
diff --git a/src/gn/command_check.cc b/src/gn/command_check.cc
index 9e175cf..f8630ca 100644
--- a/src/gn/command_check.cc
+++ b/src/gn/command_check.cc
@@ -99,8 +99,7 @@
     - Includes with a "nogncheck" annotation are skipped (see
       "gn help nogncheck").
 
-    - Only includes using "quotes" are checked. <brackets> are assumed to be
-      system includes.
+    - Includes using both "quotes" and <brackets> are checked.
 
     - Include paths are assumed to be relative to any of the "include_dirs" for
       the target (including the implicit current dir).
@@ -224,9 +223,12 @@
   const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
   bool force = cmdline->HasSwitch("force");
   bool check_generated = cmdline->HasSwitch("check-generated");
+  bool check_system = setup->check_system_includes() ||
+                      cmdline->HasSwitch("check-system");
 
   if (!CheckPublicHeaders(&setup->build_settings(), all_targets,
-                          targets_to_check, force, check_generated))
+                          targets_to_check, force, check_generated,
+                          check_system))
     return 1;
 
   if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kQuiet)) {
@@ -246,11 +248,12 @@
 bool CheckPublicHeaders(const BuildSettings* build_settings,
                         const std::vector<const Target*>& all_targets,
                         const std::vector<const Target*>& to_check,
-                        bool force_check, bool check_generated) {
+                        bool force_check, bool check_generated,
+                        bool check_system) {
   ScopedTrace trace(TraceItem::TRACE_CHECK_HEADERS, "Check headers");
 
   scoped_refptr<HeaderChecker> header_checker(
-      new HeaderChecker(build_settings, all_targets, check_generated));
+      new HeaderChecker(build_settings, all_targets, check_generated, check_system));
 
   std::vector<Err> header_errors;
   header_checker->Run(to_check, force_check, &header_errors);
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index bae2e4b..fb33f30 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -321,8 +321,9 @@
   Or it can be a directory relative to the current directory such as:
       out/foo
 
-  "gn gen --check" is the same as running "gn check". See "gn help check"
-  for documentation on that mode.
+  "gn gen --check" is the same as running "gn check". "gn gen --check=system" is
+  the same as running "gn check --check-system".  See "gn help check" for
+  documentation on that mode.
 
   See "gn help switches" for the common command-line switches.
 
@@ -454,8 +455,11 @@
 
   const base::CommandLine* command_line =
       base::CommandLine::ForCurrentProcess();
-  if (command_line->HasSwitch(kSwitchCheck))
+  if (command_line->HasSwitch(kSwitchCheck)) {
     setup->set_check_public_headers(true);
+    if (command_line->GetSwitchValueASCII(kSwitchCheck) == "system")
+      setup->set_check_system_includes(true);
+  }
 
   // Cause the load to also generate the ninja files for each target.
   TargetWriteInfo write_info;
diff --git a/src/gn/commands.h b/src/gn/commands.h
index 76c806e..d9b6862 100644
--- a/src/gn/commands.h
+++ b/src/gn/commands.h
@@ -146,7 +146,8 @@
                         const std::vector<const Target*>& all_targets,
                         const std::vector<const Target*>& to_check,
                         bool force_check,
-                        bool check_generated);
+                        bool check_generated,
+                        bool check_system);
 
 // Filters the given list of targets by the given pattern list.
 void FilterTargetsByPatterns(const std::vector<const Target*>& input,
diff --git a/src/gn/header_checker.cc b/src/gn/header_checker.cc
index e80cdeb..212f581 100644
--- a/src/gn/header_checker.cc
+++ b/src/gn/header_checker.cc
@@ -118,9 +118,11 @@
 
 HeaderChecker::HeaderChecker(const BuildSettings* build_settings,
                              const std::vector<const Target*>& targets,
-                             bool check_generated)
+                             bool check_generated,
+                             bool check_system)
     : build_settings_(build_settings),
       check_generated_(check_generated),
+      check_system_(check_system),
       lock_(),
       task_count_cv_() {
   for (auto* target : targets)
@@ -244,21 +246,28 @@
 }
 
 SourceFile HeaderChecker::SourceFileForInclude(
-    const std::string_view& relative_file_path,
+    const IncludeStringWithLocation& include,
     const std::vector<SourceDir>& include_dirs,
     const InputFile& source_file,
-    const LocationRange& range,
     Err* err) const {
   using base::FilePath;
 
-  Value relative_file_value(nullptr, std::string(relative_file_path));
-  auto it = std::find_if(
-      include_dirs.begin(), include_dirs.end(),
-      [relative_file_value, err, this](const SourceDir& dir) -> bool {
+  Value relative_file_value(nullptr, std::string(include.contents));
+
+  auto find_predicate = [relative_file_value, err, this](const SourceDir& dir) -> bool {
         SourceFile include_file =
             dir.ResolveRelativeFile(relative_file_value, err);
         return file_map_.find(include_file) != file_map_.end();
-      });
+      };
+  if (!include.system_style_include) {
+    const SourceDir& file_dir = source_file.dir();
+    if (find_predicate(file_dir)) {
+      return file_dir.ResolveRelativeFile(relative_file_value, err);
+    }
+  }
+
+  auto it = std::find_if(
+      include_dirs.begin(), include_dirs.end(), find_predicate);
 
   if (it != include_dirs.end())
     return it->ResolveRelativeFile(relative_file_value, err);
@@ -298,7 +307,6 @@
   input_file.SetContents(contents);
 
   std::vector<SourceDir> include_dirs;
-  include_dirs.push_back(file.GetDir());
   for (ConfigValuesIterator iter(from_target); !iter.done(); iter.Next()) {
     const std::vector<SourceDir>& target_include_dirs =
         iter.cur().include_dirs();
@@ -308,17 +316,24 @@
 
   size_t error_count_before = errors->size();
   CIncludeIterator iter(&input_file);
-  std::string_view current_include;
-  LocationRange range;
+
+  IncludeStringWithLocation include;
 
   std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
-  while (iter.GetNextIncludeString(&current_include, &range)) {
+
+  while (iter.GetNextIncludeString(&include)) {
+    if (include.system_style_include && !check_system_)
+      continue;
+
     Err err;
-    SourceFile include = SourceFileForInclude(current_include, include_dirs,
-                                              input_file, range, &err);
-    if (!include.is_null())
-      CheckInclude(from_target, input_file, include, range,
+    SourceFile included_file = SourceFileForInclude(include,
+                                                    include_dirs,
+                                                    input_file,
+                                                    &err);
+    if (!included_file.is_null()) {
+      CheckInclude(from_target, input_file, included_file, include.location,
                    &no_dependency_cache, errors);
+    }
   }
 
   return errors->size() == error_count_before;
diff --git a/src/gn/header_checker.h b/src/gn/header_checker.h
index 393e988..9e10652 100644
--- a/src/gn/header_checker.h
+++ b/src/gn/header_checker.h
@@ -17,12 +17,12 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/ref_counted.h"
+#include "gn/c_include_iterator.h"
 #include "gn/err.h"
 #include "gn/source_dir.h"
 
 class BuildSettings;
 class InputFile;
-class LocationRange;
 class SourceFile;
 class Target;
 
@@ -54,7 +54,8 @@
   // has generated them.
   HeaderChecker(const BuildSettings* build_settings,
                 const std::vector<const Target*>& targets,
-                bool check_generated = false);
+                bool check_generated,
+                bool check_system);
 
   // Runs the check. The targets in to_check will be checked.
   //
@@ -112,10 +113,9 @@
   bool IsFileInOuputDir(const SourceFile& file) const;
 
   // Resolves the contents of an include to a SourceFile.
-  SourceFile SourceFileForInclude(const std::string_view& relative_file_path,
+  SourceFile SourceFileForInclude(const IncludeStringWithLocation& include,
                                   const std::vector<SourceDir>& include_dirs,
                                   const InputFile& source_file,
-                                  const LocationRange& range,
                                   Err* err) const;
 
   // from_target is the target the file was defined from. It will be used in
@@ -182,6 +182,8 @@
 
   bool check_generated_;
 
+  bool check_system_;
+
   // Maps source files to targets it appears in (usually just one target).
   FileMap file_map_;
 
diff --git a/src/gn/header_checker_unittest.cc b/src/gn/header_checker_unittest.cc
index 8edfa89..ba59f5d 100644
--- a/src/gn/header_checker_unittest.cc
+++ b/src/gn/header_checker_unittest.cc
@@ -54,6 +54,13 @@
   }
 
  protected:
+  scoped_refptr<HeaderChecker> CreateChecker() {
+    bool check_generated = false;
+    bool check_system = true;
+    return base::MakeRefCounted<HeaderChecker>(
+        setup_.build_settings(), targets_, check_generated, check_system);
+  }
+
   TestWithScope setup_;
 
   // Some headers that are automatically set up with a public dependency chain.
@@ -73,8 +80,7 @@
 }
 
 TEST_F(HeaderCheckerTest, IsDependencyOf) {
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
 
   // Add a target P ("private") that privately depends on C, and hook up the
   // chain so that A -> P -> C. A will depend on C via two different paths.
@@ -189,8 +195,7 @@
   otc.sources().push_back(otc_header);
   EXPECT_TRUE(otc.OnResolved(&err));
 
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
 
   std::set<std::pair<const Target*, const Target*>> no_dependency_cache;
   // A file in target A can't include a header from D because A has no
@@ -254,8 +259,7 @@
   // marked as not permitted.
   bool is_permitted = false;
   HeaderChecker::Chain chain;
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
   EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted));
 
   EXPECT_FALSE(is_permitted);
@@ -267,7 +271,7 @@
   // Hook up D to the existing public A -> B -> C chain to make a long one, and
   // search for D again.
   c_.public_deps().push_back(LabelTargetPair(&d_));
-  checker = new HeaderChecker(setup_.build_settings(), targets_);
+  checker = CreateChecker();
   chain.clear();
   EXPECT_TRUE(checker->IsDependencyOf(&d_, &a_, &chain, &is_permitted));
 
@@ -290,8 +294,7 @@
   SourceFile a_public("//a_public.h");
   a_.sources().push_back(a_public);
 
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
 
   // A depends on B. So B normally can't include headers from A.
   std::vector<Err> errors;
@@ -317,50 +320,73 @@
       SourceDir("/c/custom_include/"), SourceDir("//"), SourceDir("//subdir")};
   a_.sources().push_back(SourceFile("//lib/header1.h"));
   b_.sources().push_back(SourceFile("/c/custom_include/header2.h"));
+  d_.sources().push_back(SourceFile("/d/subdir/header3.h"));
 
-  InputFile dummy_input_file(SourceFile("//some_file.cc"));
+  InputFile dummy_input_file(SourceFile("/d/subdir/some_file.cc"));
   dummy_input_file.SetContents(std::string());
-  LocationRange dummy_range;
 
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
   {
     Err err;
+    IncludeStringWithLocation include;
+    include.contents = "lib/header1.h";
     SourceFile source_file = checker->SourceFileForInclude(
-        "lib/header1.h", kIncludeDirs, dummy_input_file, dummy_range, &err);
+        include, kIncludeDirs, dummy_input_file, &err);
     EXPECT_FALSE(err.has_error());
     EXPECT_EQ(SourceFile("//lib/header1.h"), source_file);
   }
 
   {
     Err err;
+    IncludeStringWithLocation include;
+    include.contents = "header2.h";
     SourceFile source_file = checker->SourceFileForInclude(
-        "header2.h", kIncludeDirs, dummy_input_file, dummy_range, &err);
+        include, kIncludeDirs, dummy_input_file, &err);
     EXPECT_FALSE(err.has_error());
     EXPECT_EQ(SourceFile("/c/custom_include/header2.h"), source_file);
   }
+
+  // A non system style include should find a header file in the same directory
+  // as the source file, regardless of include dirs.
+  {
+    Err err;
+    IncludeStringWithLocation include;
+    include.contents = "header3.h";
+    include.system_style_include = false;
+    SourceFile source_file = checker->SourceFileForInclude(
+        include, kIncludeDirs, dummy_input_file, &err);
+    EXPECT_FALSE(err.has_error());
+    EXPECT_EQ(SourceFile("/d/subdir/header3.h"), source_file);
+  }
+
+  // A system style include should *not* find a header file in the same
+  // directory as the source file if that directory is not in the include dirs.
+  {
+    Err err;
+    IncludeStringWithLocation include;
+    include.contents = "header3.h";
+    include.system_style_include = true;
+    SourceFile source_file = checker->SourceFileForInclude(
+        include, kIncludeDirs, dummy_input_file, &err);
+    EXPECT_TRUE(source_file.is_null());
+    EXPECT_FALSE(err.has_error());
+  }
 }
 
 TEST_F(HeaderCheckerTest, SourceFileForInclude_FileNotFound) {
   using base::FilePath;
   const char kFileContents[] = "Some dummy contents";
   const std::vector<SourceDir> kIncludeDirs = {SourceDir("//")};
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
 
   Err err;
   InputFile input_file(SourceFile("//input.cc"));
   input_file.SetContents(std::string(kFileContents));
-  const int kLineNumber = 10;
-  const int kColumnNumber = 16;
-  const int kLength = 8;
-  const int kByteNumber = 100;
-  LocationRange range(
-      Location(&input_file, kLineNumber, kColumnNumber, kByteNumber),
-      Location(&input_file, kLineNumber, kColumnNumber + kLength, kByteNumber));
 
-  SourceFile source_file = checker->SourceFileForInclude(
-      "header.h", kIncludeDirs, input_file, range, &err);
+  IncludeStringWithLocation include;
+  include.contents = "header.h";
+  SourceFile source_file =
+      checker->SourceFileForInclude(include, kIncludeDirs, input_file, &err);
   EXPECT_TRUE(source_file.is_null());
   EXPECT_FALSE(err.has_error());
 }
@@ -383,8 +409,7 @@
   ASSERT_FALSE(err.has_error());
 
   // Must be after setting everything up for it to find the files.
-  scoped_refptr<HeaderChecker> checker(
-      new HeaderChecker(setup_.build_settings(), targets_));
+  auto checker = CreateChecker();
 
   // B should not be allowed to include C's private header.
   std::vector<Err> errors;
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
index 4390f0a..57ef5ee 100644
--- a/src/gn/setup.cc
+++ b/src/gn/setup.cc
@@ -77,6 +77,14 @@
       The format of this list is identical to that of "visibility" so see "gn
       help visibility" for examples.
 
+  check_system_includes [optional]
+      Boolean to control whether system style includes are checked by default
+      when running "gn check" or "gn gen --check".  System style includes are
+      includes that use angle brackets <> instead of double quotes "". If this
+      setting is omitted or set to false, these includes will be ignored by
+      default. They can be checked explicitly by running
+      "gn check --check-system" or "gn gen --check=system"
+
   exec_script_whitelist [optional]
       A list of .gn/.gni files (not labels) that have permission to call the
       exec_script function. If this list is defined, calls to exec_script will
@@ -422,7 +430,7 @@
     }
 
     if (!commands::CheckPublicHeaders(&build_settings_, all_targets, to_check,
-                                      false, false)) {
+                                      false, false, check_system_includes_)) {
       return false;
     }
   }
@@ -818,6 +826,17 @@
     }
   }
 
+  const Value* check_system_includes_value =
+      dotfile_scope_.GetValue("check_system_includes", true);
+  if (check_system_includes_value) {
+    if (!check_system_includes_value->VerifyTypeIs(Value::BOOLEAN, &err)) {
+      err.PrintToStdout();
+      return false;
+    }
+    check_system_includes_ = check_system_includes_value->boolean_value();
+  }
+
+
   // Fill exec_script_whitelist.
   const Value* exec_script_whitelist_value =
       dotfile_scope_.GetValue("exec_script_whitelist", true);
diff --git a/src/gn/setup.h b/src/gn/setup.h
index 967dda9..25389f1 100644
--- a/src/gn/setup.h
+++ b/src/gn/setup.h
@@ -83,6 +83,12 @@
   // headers to be checked. Defaults to false.
   void set_check_public_headers(bool s) { check_public_headers_ = s; }
 
+  // After a successful run, setting this will additionally cause system style
+  // includes to be checked. Defaults to false.
+  void set_check_system_includes(bool s) { check_system_includes_ = s; }
+
+  bool check_system_includes() const { return check_system_includes_; }
+
   // Before DoSetup, setting this will generate an empty args.gn if
   // it does not exist and set up correct dependencies for it.
   void set_gen_empty_args(bool ge) { gen_empty_args_ = ge; }
@@ -148,6 +154,7 @@
   SourceFile root_build_file_;
 
   bool check_public_headers_ = false;
+  bool check_system_includes_ = false;
 
   // See getter for info.
   std::unique_ptr<std::vector<LabelPattern>> check_patterns_;