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::starts_with(line, "//"))
46 return false; // Don't count comments.
47 if (base::starts_with(line, "/*") || base::starts_with(line, " *"))
48 return false; // C-style comment blocks with stars along the left side.
49 if (base::starts_with(line, "#"))
50 return false; // Don't count preprocessor.
51 if (base::ContainsOnlyChars(line, base::kWhitespaceASCII))
52 return false; // Don't count whitespace lines.
53 return true; // Count everything else.
54 }
55
56 // Given a line, checks to see if it looks like an include or import and
57 // extract the path. The type of include is returned. Returns INCLUDE_NONE on
58 // error or if this is not an include line.
59 //
60 // The 1-based character number on the line that the include was found at
61 // will be filled into *begin_char.
ExtractInclude(std::string_view line,std::string_view * path,int * begin_char)62 IncludeType ExtractInclude(std::string_view line,
63 std::string_view* path,
64 int* begin_char) {
65 static const char kInclude[] = "include";
66 static const size_t kIncludeLen = std::size(kInclude) - 1; // No null.
67 static const char kImport[] = "import";
68 static const size_t kImportLen = std::size(kImport) - 1; // No null.
69
70 std::string_view trimmed = TrimLeadingWhitespace(line);
71 if (trimmed.empty())
72 return INCLUDE_NONE;
73
74 if (trimmed[0] != '#')
75 return INCLUDE_NONE;
76
77 trimmed = TrimLeadingWhitespace(trimmed.substr(1));
78
79 std::string_view contents;
80 if (base::starts_with(trimmed, std::string_view(kInclude, kIncludeLen)))
81 contents = TrimLeadingWhitespace(trimmed.substr(kIncludeLen));
82 else if (base::starts_with(trimmed, std::string_view(kImport, kImportLen)))
83 contents = TrimLeadingWhitespace(trimmed.substr(kImportLen));
84
85 if (contents.empty())
86 return INCLUDE_NONE;
87
88 IncludeType type = INCLUDE_NONE;
89 char terminating_char = 0;
90 if (contents[0] == '"') {
91 type = INCLUDE_USER;
92 terminating_char = '"';
93 } else if (contents[0] == '<') {
94 type = INCLUDE_SYSTEM;
95 terminating_char = '>';
96 } else {
97 return INCLUDE_NONE;
98 }
99
100 // Count everything to next "/> as the contents.
101 size_t terminator_index = contents.find(terminating_char, 1);
102 if (terminator_index == std::string_view::npos)
103 return INCLUDE_NONE;
104
105 *path = contents.substr(1, terminator_index - 1);
106 // Note: one based so we do "+ 1".
107 *begin_char = static_cast<int>(path->data() - line.data()) + 1;
108 return type;
109 }
110
111 // Returns true if this line has a "nogncheck" comment associated with it.
HasNoCheckAnnotation(std::string_view line)112 bool HasNoCheckAnnotation(std::string_view line) {
113 return line.find("nogncheck") != std::string_view::npos;
114 }
115
116 } // namespace
117
118 const int CIncludeIterator::kMaxNonIncludeLines = 10;
119
CIncludeIterator(const InputFile * input)120 CIncludeIterator::CIncludeIterator(const InputFile* input)
121 : input_file_(input), file_(input->contents()) {}
122
123 CIncludeIterator::~CIncludeIterator() = default;
124
GetNextIncludeString(IncludeStringWithLocation * include)125 bool CIncludeIterator::GetNextIncludeString(
126 IncludeStringWithLocation* include) {
127 std::string_view line;
128 int cur_line_number = 0;
129 while (lines_since_last_include_ <= kMaxNonIncludeLines &&
130 GetNextLine(&line, &cur_line_number)) {
131 std::string_view include_contents;
132 int begin_char;
133 IncludeType type = ExtractInclude(line, &include_contents, &begin_char);
134 if (HasNoCheckAnnotation(line))
135 continue;
136 if (type != INCLUDE_NONE) {
137 include->contents = include_contents;
138 include->location = LocationRange(
139 Location(input_file_, cur_line_number, begin_char),
140 Location(input_file_, cur_line_number,
141 begin_char + static_cast<int>(include_contents.size())));
142 include->system_style_include = (type == INCLUDE_SYSTEM);
143
144 lines_since_last_include_ = 0;
145 return true;
146 }
147
148 if (ShouldCountTowardNonIncludeLines(line))
149 lines_since_last_include_++;
150 }
151 return false;
152 }
153
GetNextLine(std::string_view * line,int * line_number)154 bool CIncludeIterator::GetNextLine(std::string_view* line, int* line_number) {
155 if (offset_ == file_.size())
156 return false;
157
158 size_t begin = offset_;
159 while (offset_ < file_.size() && file_[offset_] != '\n')
160 offset_++;
161 line_number_++;
162
163 *line = file_.substr(begin, offset_ - begin);
164 *line_number = line_number_;
165
166 // If we didn't hit EOF, skip past the newline for the next one.
167 if (offset_ < file_.size())
168 offset_++;
169 return true;
170 }
171