Home | History | Annotate | Download | only in src
      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