• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "gn/c_include_iterator.h"
6 
7 #include <iterator>
8 
9 #include "base/logging.h"
10 #include "base/strings/string_util.h"
11 #include "gn/input_file.h"
12 #include "gn/location.h"
13 
14 namespace {
15 
16 enum IncludeType {
17   INCLUDE_NONE,
18   INCLUDE_SYSTEM,  // #include <...>
19   INCLUDE_USER     // #include "..."
20 };
21 
22 // Returns a new string piece referencing the same buffer as the argument, but
23 // with leading space trimmed. This only checks for space and tab characters
24 // since we're dealing with lines in C source files.
TrimLeadingWhitespace(std::string_view str)25 std::string_view TrimLeadingWhitespace(std::string_view str) {
26   size_t new_begin = 0;
27   while (new_begin < str.size() &&
28          (str[new_begin] == ' ' || str[new_begin] == '\t'))
29     new_begin++;
30   return str.substr(new_begin);
31 }
32 
33 // We don't want to count comment lines and preprocessor lines toward our
34 // "max lines to look at before giving up" since the beginnings of some files
35 // may have a lot of comments.
36 //
37 // We only handle C-style "//" comments since this is the normal commenting
38 // style used in Chrome, and do so pretty stupidly. We don't want to write a
39 // full C++ parser here, we're just trying to get a good heuristic for checking
40 // the file.
41 //
42 // We assume the line has leading whitespace trimmed. We also assume that empty
43 // lines have already been filtered out.
ShouldCountTowardNonIncludeLines(std::string_view line)44 bool ShouldCountTowardNonIncludeLines(std::string_view line) {
45   if (base::StartsWith(line, "//", base::CompareCase::SENSITIVE))
46     return false;  // Don't count comments.
47   if (base::StartsWith(line, "/*", base::CompareCase::SENSITIVE) ||
48       base::StartsWith(line, " *", base::CompareCase::SENSITIVE))
49     return false;  // C-style comment blocks with stars along the left side.
50   if (base::StartsWith(line, "#", base::CompareCase::SENSITIVE))
51     return false;  // Don't count preprocessor.
52   if (base::ContainsOnlyChars(line, base::kWhitespaceASCII))
53     return false;  // Don't count whitespace lines.
54   return true;     // Count everything else.
55 }
56 
57 // Given a line, checks to see if it looks like an include or import and
58 // extract the path. The type of include is returned. Returns INCLUDE_NONE on
59 // error or if this is not an include line.
60 //
61 // The 1-based character number on the line that the include was found at
62 // will be filled into *begin_char.
ExtractInclude(std::string_view line,std::string_view * path,int * begin_char)63 IncludeType ExtractInclude(std::string_view line,
64                            std::string_view* path,
65                            int* begin_char) {
66   static const char kInclude[] = "include";
67   static const size_t kIncludeLen = std::size(kInclude) - 1;  // No null.
68   static const char kImport[] = "import";
69   static const size_t kImportLen = std::size(kImport) - 1;  // No null.
70 
71   std::string_view trimmed = TrimLeadingWhitespace(line);
72   if (trimmed.empty())
73     return INCLUDE_NONE;
74 
75   if (trimmed[0] != '#')
76     return INCLUDE_NONE;
77 
78   trimmed = TrimLeadingWhitespace(trimmed.substr(1));
79 
80   std::string_view contents;
81   if (base::StartsWith(trimmed, std::string_view(kInclude, kIncludeLen),
82                        base::CompareCase::SENSITIVE))
83     contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen));
84   else if (base::StartsWith(trimmed, std::string_view(kImport, kImportLen),
85                             base::CompareCase::SENSITIVE))
86     contents = TrimLeadingWhitespace(trimmed.substr(kImportLen));
87 
88   if (contents.empty())
89     return INCLUDE_NONE;
90 
91   IncludeType type = INCLUDE_NONE;
92   char terminating_char = 0;
93   if (contents[0] == '"') {
94     type = INCLUDE_USER;
95     terminating_char = '"';
96   } else if (contents[0] == '<') {
97     type = INCLUDE_SYSTEM;
98     terminating_char = '>';
99   } else {
100     return INCLUDE_NONE;
101   }
102 
103   // Count everything to next "/> as the contents.
104   size_t terminator_index = contents.find(terminating_char, 1);
105   if (terminator_index == std::string_view::npos)
106     return INCLUDE_NONE;
107 
108   *path = contents.substr(1, terminator_index - 1);
109   // Note: one based so we do "+ 1".
110   *begin_char = static_cast<int>(path->data() - line.data()) + 1;
111   return type;
112 }
113 
114 // Returns true if this line has a "nogncheck" comment associated with it.
HasNoCheckAnnotation(std::string_view line)115 bool HasNoCheckAnnotation(std::string_view line) {
116   return line.find("nogncheck") != std::string_view::npos;
117 }
118 
119 }  // namespace
120 
121 const int CIncludeIterator::kMaxNonIncludeLines = 10;
122 
CIncludeIterator(const InputFile * input)123 CIncludeIterator::CIncludeIterator(const InputFile* input)
124     : input_file_(input), file_(input->contents()) {}
125 
126 CIncludeIterator::~CIncludeIterator() = default;
127 
GetNextIncludeString(IncludeStringWithLocation * include)128 bool CIncludeIterator::GetNextIncludeString(
129     IncludeStringWithLocation* include) {
130   std::string_view line;
131   int cur_line_number = 0;
132   while (lines_since_last_include_ <= kMaxNonIncludeLines &&
133          GetNextLine(&line, &cur_line_number)) {
134     std::string_view include_contents;
135     int begin_char;
136     IncludeType type = ExtractInclude(line, &include_contents, &begin_char);
137     if (HasNoCheckAnnotation(line))
138       continue;
139     if (type != INCLUDE_NONE) {
140       include->contents = include_contents;
141       include->location = LocationRange(
142           Location(input_file_, cur_line_number, begin_char),
143           Location(input_file_, cur_line_number,
144                    begin_char + static_cast<int>(include_contents.size())));
145       include->system_style_include = (type == INCLUDE_SYSTEM);
146 
147       lines_since_last_include_ = 0;
148       return true;
149     }
150 
151     if (ShouldCountTowardNonIncludeLines(line))
152       lines_since_last_include_++;
153   }
154   return false;
155 }
156 
GetNextLine(std::string_view * line,int * line_number)157 bool CIncludeIterator::GetNextLine(std::string_view* line, int* line_number) {
158   if (offset_ == file_.size())
159     return false;
160 
161   size_t begin = offset_;
162   while (offset_ < file_.size() && file_[offset_] != '\n')
163     offset_++;
164   line_number_++;
165 
166   *line = file_.substr(begin, offset_ - begin);
167   *line_number = line_number_;
168 
169   // If we didn't hit EOF, skip past the newline for the next one.
170   if (offset_ < file_.size())
171     offset_++;
172   return true;
173 }
174