/src/shaderc/libshaderc_util/src/message.cc
Line | Count | Source |
1 | | // Copyright 2015 The Shaderc Authors. All rights reserved. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include "libshaderc_util/message.h" |
16 | | |
17 | | #include <algorithm> |
18 | | #include <cstring> |
19 | | #include <iostream> |
20 | | #include <iterator> |
21 | | |
22 | | namespace shaderc_util { |
23 | | |
24 | | namespace { |
25 | | |
26 | | // Given a message, deduces and returns its type. If the message type is |
27 | | // recognized, advances *message past the prefix indicating the type. Otherwise, |
28 | | // leaves *message unchanged and returns MessageType::Unknown. |
29 | 673k | MessageType DeduceMessageType(string_piece* message) { |
30 | 673k | static const char kErrorMessage[] = "ERROR: "; |
31 | 673k | static const char kWarningMessage[] = "WARNING: "; |
32 | 673k | static const char kGlobalWarningMessage[] = "Warning, "; |
33 | | |
34 | 673k | if (message->starts_with(kErrorMessage)) { |
35 | 294k | *message = message->substr(::strlen(kErrorMessage)); |
36 | 294k | return MessageType::Error; |
37 | 379k | } else if (message->starts_with(kWarningMessage)) { |
38 | 186 | *message = message->substr(::strlen(kWarningMessage)); |
39 | 186 | return MessageType::Warning; |
40 | 379k | } else if (message->starts_with(kGlobalWarningMessage)) { |
41 | 0 | *message = message->substr(::strlen(kGlobalWarningMessage)); |
42 | 0 | return MessageType::GlobalWarning; |
43 | 0 | } |
44 | 379k | return MessageType::Unknown; |
45 | 673k | } |
46 | | |
47 | | // Deduces a location specification from the given message. A location |
48 | | // specification is of the form "<source-name>:<line-number>:" and a trailing |
49 | | // space. If the deduction is successful, returns true and updates source_name |
50 | | // and line_number to the deduced source name and line numer respectively. The |
51 | | // prefix standing for the location specification in message is skipped. |
52 | | // Otherwise, returns false and keeps all parameters untouched. |
53 | | bool DeduceLocationSpec(string_piece* message, string_piece* source_name, |
54 | 294k | string_piece* line_number) { |
55 | 294k | if (!message || message->empty()) { |
56 | 0 | return false; |
57 | 0 | } |
58 | | |
59 | | // When we find a pattern like this: |
60 | | // colon |
61 | | // digits |
62 | | // colon |
63 | | // space |
64 | | // Then deduce that the source_name is the text before the first colon, |
65 | | // the line number is the digits, and the message is the text after the |
66 | | // second colon. |
67 | | |
68 | 294k | const size_t size = message->size(); |
69 | 294k | if (size <= 4) { |
70 | | // A valid message must have a colon, a digit, a colon, and a space. |
71 | 0 | return false; |
72 | 0 | } |
73 | | // The last possible position of the first colon. |
74 | 294k | const size_t first_colon_cutoff = size - 4; |
75 | | // The last possible position of the second colon. |
76 | 294k | const size_t next_colon_cutoff = size - 2; |
77 | | |
78 | 294k | for (size_t first_colon_pos = message->find_first_of(':'), next_colon_pos = 0; |
79 | | |
80 | | // There is a first colon, and it's not too close to the end |
81 | 295k | (first_colon_pos != string_piece::npos) && |
82 | 293k | (first_colon_pos <= first_colon_cutoff); |
83 | | |
84 | | // Try the next pair of colons. |
85 | 294k | first_colon_pos = next_colon_pos) { |
86 | | // We're guaranteed to have at least 3 more characters. |
87 | | // Guarantee progress toward the end of the string. |
88 | 293k | next_colon_pos = message->find_first_of(':', first_colon_pos + 1); |
89 | 293k | if ((next_colon_pos == string_piece::npos) || |
90 | 293k | (next_colon_pos > next_colon_cutoff)) { |
91 | | // No valid solution. |
92 | 424 | return false; |
93 | 424 | } |
94 | 293k | if (first_colon_pos + 1 == next_colon_pos) { |
95 | | // There is no room for digits. |
96 | 0 | continue; |
97 | 0 | } |
98 | 293k | if ((message->data()[next_colon_pos + 1] != ' ')) { |
99 | | // There is no space character after the second colon. |
100 | 531 | continue; |
101 | 531 | } |
102 | 292k | if (message->find_first_not_of("0123456789", first_colon_pos + 1) == |
103 | 292k | next_colon_pos) { |
104 | | // We found the first solution. |
105 | 292k | *source_name = message->substr(0, first_colon_pos); |
106 | 292k | *line_number = message->substr(first_colon_pos + 1, |
107 | 292k | next_colon_pos - 1 - first_colon_pos); |
108 | 292k | *message = message->substr(next_colon_pos + 2); |
109 | 292k | return true; |
110 | 292k | } |
111 | 292k | } |
112 | | |
113 | 1.58k | return false; |
114 | 294k | } |
115 | | |
116 | | // Returns true if the given message is a summary message. |
117 | 2.00k | bool IsSummaryMessage(const string_piece& message) { |
118 | 2.00k | const size_t space_loc = message.find_first_of(' '); |
119 | 2.00k | if (space_loc == string_piece::npos) return false; |
120 | 2.00k | const string_piece number = message.substr(0, space_loc); |
121 | 2.00k | const string_piece rest = message.substr(space_loc + 1); |
122 | 2.00k | if (!std::all_of(number.begin(), number.end(), ::isdigit)) return false; |
123 | 1.58k | if (!rest.starts_with("compilation errors.")) return false; |
124 | 1.58k | return true; |
125 | 1.58k | } |
126 | | |
127 | | } // anonymous namespace |
128 | | |
129 | | MessageType ParseGlslangOutput(const string_piece& message, |
130 | | bool warnings_as_errors, bool suppress_warnings, |
131 | | string_piece* source_name, |
132 | 673k | string_piece* line_number, string_piece* rest) { |
133 | 673k | string_piece rest_of_message(message); |
134 | 673k | source_name->clear(); |
135 | 673k | line_number->clear(); |
136 | 673k | rest->clear(); |
137 | | |
138 | | // The glslang warning/error messages are typically of the following form: |
139 | | // <message-type> <location-specification> <message-body> |
140 | | // |
141 | | // <message-type> can be "WARNING:", "ERROR:", or "Warning, ". "WARNING:" |
142 | | // means a warning message for a certain line, while "Warning, " means a |
143 | | // global one. |
144 | | // |
145 | | // <location-specification> is of the form: |
146 | | // <filename-or-string-number>:<line-number>: |
147 | | // It doesn't exist if the warning/error message is a global one. |
148 | | // |
149 | | // See Glslang's TInfoSink class implementation for details. |
150 | | |
151 | 673k | bool is_error = false; |
152 | | |
153 | | // Handle <message-type>. |
154 | 673k | switch (DeduceMessageType(&rest_of_message)) { |
155 | 186 | case MessageType::Warning: |
156 | 186 | if (suppress_warnings) return MessageType::Ignored; |
157 | 75 | break; |
158 | 294k | case MessageType::Error: |
159 | 294k | is_error = true; |
160 | 294k | break; |
161 | 0 | case MessageType::GlobalWarning: |
162 | 0 | if (suppress_warnings) return MessageType::Ignored; |
163 | 0 | *rest = rest_of_message; |
164 | 0 | return warnings_as_errors ? MessageType::GlobalError |
165 | 0 | : MessageType::GlobalWarning; |
166 | 379k | case MessageType::Unknown: |
167 | 379k | *rest = rest_of_message; |
168 | 379k | return MessageType::Unknown; |
169 | 0 | default: |
170 | 0 | break; |
171 | 673k | } |
172 | | |
173 | 294k | rest_of_message = rest_of_message.strip_whitespace(); |
174 | 294k | if (rest_of_message.empty()) return MessageType::Unknown; |
175 | | |
176 | | // Now we have stripped the <message-type>. Try to see if we can find |
177 | | // a <location-specification>. |
178 | 294k | if (DeduceLocationSpec(&rest_of_message, source_name, line_number)) { |
179 | 292k | *rest = rest_of_message; |
180 | 292k | return (is_error || warnings_as_errors) ? MessageType::Error |
181 | 292k | : MessageType::Warning; |
182 | 292k | } else { |
183 | | // No <location-specification>. This is a global warning/error message. |
184 | | // A special kind of global message is summary message, which should |
185 | | // start with a number. |
186 | 2.00k | *rest = rest_of_message; |
187 | 2.00k | if (IsSummaryMessage(rest_of_message)) { |
188 | 1.58k | return (is_error || warnings_as_errors) ? MessageType::ErrorSummary |
189 | 1.58k | : MessageType::WarningSummary; |
190 | 1.58k | } |
191 | 428 | return (is_error || warnings_as_errors) ? MessageType::GlobalError |
192 | 428 | : MessageType::GlobalWarning; |
193 | 2.00k | } |
194 | 0 | return MessageType::Unknown; |
195 | 294k | } |
196 | | |
197 | | bool PrintFilteredErrors(const string_piece& file_name, |
198 | | std::ostream* error_stream, bool warnings_as_errors, |
199 | | bool suppress_warnings, const char* error_list, |
200 | 2.25k | size_t* total_warnings, size_t* total_errors) { |
201 | 2.25k | const char* ignored_error_strings[] = { |
202 | 2.25k | "Warning, version 310 is not yet complete; most version-specific " |
203 | 2.25k | "features are present, but some are missing.", |
204 | 2.25k | "Warning, version 400 is not yet complete; most version-specific " |
205 | 2.25k | "features are present, but some are missing.", |
206 | 2.25k | "Warning, version 410 is not yet complete; most version-specific " |
207 | 2.25k | "features are present, but some are missing.", |
208 | 2.25k | "Warning, version 420 is not yet complete; most version-specific " |
209 | 2.25k | "features are present, but some are missing.", |
210 | 2.25k | "Warning, version 430 is not yet complete; most version-specific " |
211 | 2.25k | "features are present, but some are missing.", |
212 | 2.25k | "Warning, version 440 is not yet complete; most version-specific " |
213 | 2.25k | "features are present, but some are missing.", |
214 | 2.25k | "Warning, version 450 is not yet complete; most version-specific " |
215 | 2.25k | "features are present, but some are missing.", |
216 | 2.25k | "Linked vertex stage:", "Linked fragment stage:", |
217 | 2.25k | "Linked tessellation control stage:", |
218 | 2.25k | "Linked tessellation evaluation stage:", "Linked geometry stage:", |
219 | 2.25k | "Linked compute stage:", ""}; |
220 | 2.25k | size_t existing_total_errors = *total_errors; |
221 | 2.25k | string_piece error_messages(error_list); |
222 | 675k | for (const string_piece& message : error_messages.get_fields('\n')) { |
223 | 675k | if (std::find(std::begin(ignored_error_strings), |
224 | 675k | std::end(ignored_error_strings), |
225 | 675k | message) == std::end(ignored_error_strings)) { |
226 | 673k | string_piece source_name; |
227 | 673k | string_piece line_number; |
228 | 673k | string_piece rest; |
229 | 673k | const MessageType type = |
230 | 673k | ParseGlslangOutput(message, warnings_as_errors, suppress_warnings, |
231 | 673k | &source_name, &line_number, &rest); |
232 | 673k | string_piece name = file_name; |
233 | 673k | if (!source_name.empty()) { |
234 | | // -1 is the string number for the preamble injected by us. |
235 | 292k | name = source_name == "-1" ? "<command line>" : source_name; |
236 | 292k | } |
237 | 673k | switch (type) { |
238 | 292k | case MessageType::Error: |
239 | 292k | case MessageType::Warning: |
240 | 292k | assert(!name.empty() && !line_number.empty() && !rest.empty()); |
241 | 292k | *error_stream << name << ":" << line_number << ": " |
242 | 292k | << (type == MessageType::Error ? "error: " |
243 | 292k | : "warning: ") |
244 | 292k | << rest.strip_whitespace() << std::endl; |
245 | 292k | *total_errors += type == MessageType::Error; |
246 | 292k | *total_warnings += type == MessageType::Warning; |
247 | 292k | break; |
248 | 1.58k | case MessageType::ErrorSummary: |
249 | 1.58k | case MessageType::WarningSummary: |
250 | 1.58k | break; |
251 | 428 | case MessageType::GlobalError: |
252 | 428 | case MessageType::GlobalWarning: |
253 | 428 | assert(!rest.empty()); |
254 | 428 | *total_errors += type == MessageType::GlobalError; |
255 | 428 | *total_warnings += type == MessageType::GlobalWarning; |
256 | 428 | *error_stream << name << ": " |
257 | 428 | << (type == MessageType::GlobalError ? "error" |
258 | 428 | : "warning") |
259 | 428 | << ": " << rest.strip_whitespace() << std::endl; |
260 | 428 | break; |
261 | 379k | case MessageType::Unknown: |
262 | 379k | *error_stream << name << ":"; |
263 | 379k | *error_stream << " " << message << std::endl; |
264 | 379k | break; |
265 | 111 | case MessageType::Ignored: |
266 | 111 | break; |
267 | 673k | } |
268 | 673k | } |
269 | 675k | } |
270 | 2.25k | return (existing_total_errors == *total_errors); |
271 | 2.25k | } |
272 | | |
273 | | // Outputs the number of warnings and errors if there are any. |
274 | | void OutputMessages(std::ostream* error_stream, size_t total_warnings, |
275 | 0 | size_t total_errors) { |
276 | 0 | if (total_warnings > 0 || total_errors > 0) { |
277 | 0 | if (total_warnings > 0 && total_errors > 0) { |
278 | 0 | *error_stream << total_warnings << " warning" |
279 | 0 | << (total_warnings > 1 ? "s" : "") << " and " |
280 | 0 | << total_errors << " error" << (total_errors > 1 ? "s" : "") |
281 | 0 | << " generated." << std::endl; |
282 | 0 | } else if (total_warnings > 0) { |
283 | 0 | *error_stream << total_warnings << " warning" |
284 | 0 | << (total_warnings > 1 ? "s" : "") << " generated." |
285 | 0 | << std::endl; |
286 | 0 | } else if (total_errors > 0) { |
287 | 0 | *error_stream << total_errors << " error" << (total_errors > 1 ? "s" : "") |
288 | 0 | << " generated." << std::endl; |
289 | 0 | } |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | } // namespace glslc |