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 MessageType DeduceMessageType(string_piece* message) { 30 static const char kErrorMessage[] = "ERROR: "; 31 static const char kWarningMessage[] = "WARNING: "; 32 static const char kGlobalWarningMessage[] = "Warning, "; 33 34 if (message->starts_with(kErrorMessage)) { 35 *message = message->substr(::strlen(kErrorMessage)); 36 return MessageType::Error; 37 } else if (message->starts_with(kWarningMessage)) { 38 *message = message->substr(::strlen(kWarningMessage)); 39 return MessageType::Warning; 40 } else if (message->starts_with(kGlobalWarningMessage)) { 41 *message = message->substr(::strlen(kGlobalWarningMessage)); 42 return MessageType::GlobalWarning; 43 } 44 return MessageType::Unknown; 45 } 46 47 // Deduces a location specification from the given message. A location 48 // specification is of the form "<source-name>:<line-number>:". If the deduction 49 // is successful, returns true and updates source_name and line_number to the 50 // deduced source name and line numer respectively. The prefix standing for the 51 // location specification in message is skipped. Otherwise, returns false and 52 // keeps all parameters untouched. 53 bool DeduceLocationSpec(string_piece* message, string_piece* source_name, 54 string_piece* line_number) { 55 // TODO(antiagainst): we use ':' as a delimiter here. It may be a valid 56 // character in the filename. Also be aware of other special characters, 57 // for example, ' '. 58 string_piece rest(*message); 59 size_t colon_after_source = rest.find_first_of(':'); 60 if (colon_after_source == string_piece::npos) return false; 61 62 string_piece source = rest.substr(0, colon_after_source); 63 rest = rest.substr(colon_after_source + 1); 64 size_t colon_after_line = rest.find_first_of(':'); 65 if (source.size() == 1 && ::isalpha(source.front()) && rest.size() > 0 && 66 rest.front() == '\\') { 67 // Handle Windows path. 68 colon_after_source += colon_after_line + 1; 69 source = message->substr(0, colon_after_source); 70 rest = rest.substr(colon_after_line + 1); 71 colon_after_line = rest.find_first_of(':'); 72 } 73 74 if (colon_after_line == string_piece::npos) return false; 75 const string_piece line = rest.substr(0, colon_after_line); 76 77 if (!std::all_of(line.begin(), line.end(), ::isdigit)) return false; 78 79 *source_name = source; 80 *line_number = line; 81 *message = rest.substr(colon_after_line + 1).strip_whitespace(); 82 return true; 83 } 84 85 // Returns true if the given message is a summary message. 86 bool IsSummaryMessage(const string_piece& message) { 87 const size_t space_loc = message.find_first_of(' '); 88 if (space_loc == string_piece::npos) return false; 89 const string_piece number = message.substr(0, space_loc); 90 const string_piece rest = message.substr(space_loc + 1); 91 if (!std::all_of(number.begin(), number.end(), ::isdigit)) return false; 92 if (!rest.starts_with("compilation errors.")) return false; 93 return true; 94 } 95 96 } // anonymous namespace 97 98 MessageType ParseGlslangOutput(const string_piece& message, 99 bool warnings_as_errors, bool suppress_warnings, 100 string_piece* source_name, 101 string_piece* line_number, string_piece* rest) { 102 string_piece rest_of_message(message); 103 source_name->clear(); 104 line_number->clear(); 105 rest->clear(); 106 107 // The glslang warning/error messages are typically of the following form: 108 // <message-type> <location-specification> <message-body> 109 // 110 // <message-type> can be "WARNING:", "ERROR:", or "Warning, ". "WARNING:" 111 // means a warning message for a certain line, while "Warning, " means a 112 // global one. 113 // 114 // <location-specification> is of the form: 115 // <filename-or-string-number>:<line-number>: 116 // It doesn't exist if the warning/error message is a global one. 117 118 bool is_error = false; 119 120 // Handle <message-type>. 121 switch (DeduceMessageType(&rest_of_message)) { 122 case MessageType::Warning: 123 if (suppress_warnings) return MessageType::Ignored; 124 break; 125 case MessageType::Error: 126 is_error = true; 127 break; 128 case MessageType::GlobalWarning: 129 if (suppress_warnings) return MessageType::Ignored; 130 *rest = rest_of_message; 131 return warnings_as_errors ? MessageType::GlobalError 132 : MessageType::GlobalWarning; 133 case MessageType::Unknown: 134 *rest = rest_of_message; 135 return MessageType::Unknown; 136 default: 137 break; 138 } 139 140 rest_of_message = rest_of_message.strip_whitespace(); 141 if (rest_of_message.empty()) return MessageType::Unknown; 142 143 // Now we have stripped the <message-type>. Try to see if we can find 144 // a <location-specification>. 145 if (DeduceLocationSpec(&rest_of_message, source_name, line_number)) { 146 *rest = rest_of_message; 147 return (is_error || warnings_as_errors) ? MessageType::Error 148 : MessageType::Warning; 149 } else { 150 // No <location-specification>. This is a global warning/error message. 151 // A special kind of global message is summary message, which should 152 // start with a number. 153 *rest = rest_of_message; 154 if (IsSummaryMessage(rest_of_message)) { 155 return (is_error || warnings_as_errors) ? MessageType::ErrorSummary 156 : MessageType::WarningSummary; 157 } 158 return (is_error || warnings_as_errors) ? MessageType::GlobalError 159 : MessageType::GlobalWarning; 160 } 161 return MessageType::Unknown; 162 } 163 164 bool PrintFilteredErrors(const string_piece& file_name, 165 std::ostream* error_stream, bool warnings_as_errors, 166 bool suppress_warnings, const char* error_list, 167 size_t* total_warnings, size_t* total_errors) { 168 const char* ignored_error_strings[] = { 169 "Warning, version 310 is not yet complete; most version-specific " 170 "features are present, but some are missing.", 171 "Warning, version 400 is not yet complete; most version-specific " 172 "features are present, but some are missing.", 173 "Warning, version 410 is not yet complete; most version-specific " 174 "features are present, but some are missing.", 175 "Warning, version 420 is not yet complete; most version-specific " 176 "features are present, but some are missing.", 177 "Warning, version 430 is not yet complete; most version-specific " 178 "features are present, but some are missing.", 179 "Warning, version 440 is not yet complete; most version-specific " 180 "features are present, but some are missing.", 181 "Warning, version 450 is not yet complete; most version-specific " 182 "features are present, but some are missing.", 183 "Linked vertex stage:", "Linked fragment stage:", 184 "Linked tessellation control stage:", 185 "Linked tessellation evaluation stage:", "Linked geometry stage:", 186 "Linked compute stage:", ""}; 187 size_t existing_total_errors = *total_errors; 188 string_piece error_messages(error_list); 189 for (const string_piece& message : error_messages.get_fields('\n')) { 190 if (std::find(std::begin(ignored_error_strings), 191 std::end(ignored_error_strings), 192 message) == std::end(ignored_error_strings)) { 193 string_piece source_name; 194 string_piece line_number; 195 string_piece rest; 196 const MessageType type = 197 ParseGlslangOutput(message, warnings_as_errors, suppress_warnings, 198 &source_name, &line_number, &rest); 199 string_piece name = file_name; 200 if (!source_name.empty()) { 201 // -1 is the string number for the preamble injected by us. 202 name = source_name == "-1" ? "<command line>" : source_name; 203 } 204 switch (type) { 205 case MessageType::Error: 206 case MessageType::Warning: 207 assert(!name.empty() && !line_number.empty() && !rest.empty()); 208 *error_stream << name << ":" << line_number << ": " 209 << (type == MessageType::Error ? "error: " 210 : "warning: ") 211 << rest.strip_whitespace() << std::endl; 212 *total_errors += type == MessageType::Error; 213 *total_warnings += type == MessageType::Warning; 214 break; 215 case MessageType::ErrorSummary: 216 case MessageType::WarningSummary: 217 break; 218 case MessageType::GlobalError: 219 case MessageType::GlobalWarning: 220 assert(!rest.empty()); 221 *total_errors += type == MessageType::GlobalError; 222 *total_warnings += type == MessageType::GlobalWarning; 223 *error_stream << name << ": " 224 << (type == MessageType::GlobalError ? "error" 225 : "warning") 226 << ": " << rest.strip_whitespace() << std::endl; 227 break; 228 case MessageType::Unknown: 229 *error_stream << name << ":"; 230 *error_stream << " " << message << std::endl; 231 break; 232 case MessageType::Ignored: 233 break; 234 } 235 } 236 } 237 return (existing_total_errors == *total_errors); 238 } 239 240 // Outputs the number of warnings and errors if there are any. 241 void OutputMessages(std::ostream* error_stream, size_t total_warnings, 242 size_t total_errors) { 243 if (total_warnings > 0 || total_errors > 0) { 244 if (total_warnings > 0 && total_errors > 0) { 245 *error_stream << total_warnings << " warning" 246 << (total_warnings > 1 ? "s" : "") << " and " 247 << total_errors << " error" << (total_errors > 1 ? "s" : "") 248 << " generated." << std::endl; 249 } else if (total_warnings > 0) { 250 *error_stream << total_warnings << " warning" 251 << (total_warnings > 1 ? "s" : "") << " generated." 252 << std::endl; 253 } else if (total_errors > 0) { 254 *error_stream << total_errors << " error" << (total_errors > 1 ? "s" : "") 255 << " generated." << std::endl; 256 } 257 } 258 } 259 260 } // namespace glslc 261