blob: 11fd10adc0c33f8def42ea56d1f4fb8df52007e2 [file] [log] [blame]
// Copyright 2015 The Shaderc Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "libshaderc_util/message.h"
#include <algorithm>
#include <cstring>
#include <iostream>
#include <iterator>
namespace shaderc_util {
namespace {
// Given a message, deduces and returns its type. If the message type is
// recognized, advances *message past the prefix indicating the type. Otherwise,
// leaves *message unchanged and returns MessageType::Unknown.
MessageType DeduceMessageType(string_piece* message) {
static const char kErrorMessage[] = "ERROR: ";
static const char kWarningMessage[] = "WARNING: ";
static const char kGlobalWarningMessage[] = "Warning, ";
if (message->starts_with(kErrorMessage)) {
*message = message->substr(::strlen(kErrorMessage));
return MessageType::Error;
} else if (message->starts_with(kWarningMessage)) {
*message = message->substr(::strlen(kWarningMessage));
return MessageType::Warning;
} else if (message->starts_with(kGlobalWarningMessage)) {
*message = message->substr(::strlen(kGlobalWarningMessage));
return MessageType::GlobalWarning;
}
return MessageType::Unknown;
}
// Deduces a location specification from the given message. A location
// specification is of the form "<source-name>:<line-number>:". If the deduction
// is successful, returns true and updates source_name and line_number to the
// deduced source name and line numer respectively. The prefix standing for the
// location specification in message is skipped. Otherwise, returns false and
// keeps all parameters untouched.
bool DeduceLocationSpec(string_piece* message, string_piece* source_name,
string_piece* line_number) {
// TODO(antiagainst): we use ':' as a delimiter here. It may be a valid
// character in the filename. Also be aware of other special characters,
// for example, ' '.
string_piece rest(*message);
size_t colon_after_source = rest.find_first_of(':');
if (colon_after_source == string_piece::npos) return false;
string_piece source = rest.substr(0, colon_after_source);
rest = rest.substr(colon_after_source + 1);
size_t colon_after_line = rest.find_first_of(':');
if (source.size() == 1 && ::isalpha(source.front()) && rest.size() > 0 &&
rest.front() == '\\') {
// Handle Windows path.
colon_after_source += colon_after_line + 1;
source = message->substr(0, colon_after_source);
rest = rest.substr(colon_after_line + 1);
colon_after_line = rest.find_first_of(':');
}
if (colon_after_line == string_piece::npos) return false;
const string_piece line = rest.substr(0, colon_after_line);
if (!std::all_of(line.begin(), line.end(), ::isdigit)) return false;
*source_name = source;
*line_number = line;
*message = rest.substr(colon_after_line + 1).strip_whitespace();
return true;
}
// Returns true if the given message is a summary message.
bool IsSummaryMessage(const string_piece& message) {
const size_t space_loc = message.find_first_of(' ');
if (space_loc == string_piece::npos) return false;
const string_piece number = message.substr(0, space_loc);
const string_piece rest = message.substr(space_loc + 1);
if (!std::all_of(number.begin(), number.end(), ::isdigit)) return false;
if (!rest.starts_with("compilation errors.")) return false;
return true;
}
} // anonymous namespace
MessageType ParseGlslangOutput(const string_piece& message,
bool warnings_as_errors, bool suppress_warnings,
string_piece* source_name,
string_piece* line_number, string_piece* rest) {
string_piece rest_of_message(message);
source_name->clear();
line_number->clear();
rest->clear();
// The glslang warning/error messages are typically of the following form:
// <message-type> <location-specification> <message-body>
//
// <message-type> can be "WARNING:", "ERROR:", or "Warning, ". "WARNING:"
// means a warning message for a certain line, while "Warning, " means a
// global one.
//
// <location-specification> is of the form:
// <filename-or-string-number>:<line-number>:
// It doesn't exist if the warning/error message is a global one.
bool is_error = false;
// Handle <message-type>.
switch (DeduceMessageType(&rest_of_message)) {
case MessageType::Warning:
if (suppress_warnings) return MessageType::Ignored;
break;
case MessageType::Error:
is_error = true;
break;
case MessageType::GlobalWarning:
if (suppress_warnings) return MessageType::Ignored;
*rest = rest_of_message;
return warnings_as_errors ? MessageType::GlobalError
: MessageType::GlobalWarning;
case MessageType::Unknown:
*rest = rest_of_message;
return MessageType::Unknown;
default:
break;
}
rest_of_message = rest_of_message.strip_whitespace();
if (rest_of_message.empty()) return MessageType::Unknown;
// Now we have stripped the <message-type>. Try to see if we can find
// a <location-specification>.
if (DeduceLocationSpec(&rest_of_message, source_name, line_number)) {
*rest = rest_of_message;
return (is_error || warnings_as_errors) ? MessageType::Error
: MessageType::Warning;
} else {
// No <location-specification>. This is a global warning/error message.
// A special kind of global message is summary message, which should
// start with a number.
*rest = rest_of_message;
if (IsSummaryMessage(rest_of_message)) {
return (is_error || warnings_as_errors) ? MessageType::ErrorSummary
: MessageType::WarningSummary;
}
return (is_error || warnings_as_errors) ? MessageType::GlobalError
: MessageType::GlobalWarning;
}
return MessageType::Unknown;
}
bool PrintFilteredErrors(const string_piece& file_name,
std::ostream* error_stream, bool warnings_as_errors,
bool suppress_warnings, const char* error_list,
size_t* total_warnings, size_t* total_errors) {
const char* ignored_error_strings[] = {
"Warning, version 310 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Warning, version 400 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Warning, version 410 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Warning, version 420 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Warning, version 430 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Warning, version 440 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Warning, version 450 is not yet complete; most version-specific "
"features are present, but some are missing.",
"Linked vertex stage:", "Linked fragment stage:",
"Linked tessellation control stage:",
"Linked tessellation evaluation stage:", "Linked geometry stage:",
"Linked compute stage:", ""};
size_t existing_total_errors = *total_errors;
string_piece error_messages(error_list);
for (const string_piece& message : error_messages.get_fields('\n')) {
if (std::find(std::begin(ignored_error_strings),
std::end(ignored_error_strings),
message) == std::end(ignored_error_strings)) {
string_piece source_name;
string_piece line_number;
string_piece rest;
const MessageType type =
ParseGlslangOutput(message, warnings_as_errors, suppress_warnings,
&source_name, &line_number, &rest);
string_piece name = file_name;
if (!source_name.empty()) {
// -1 is the string number for the preamble injected by us.
name = source_name == "-1" ? "<command line>" : source_name;
}
switch (type) {
case MessageType::Error:
case MessageType::Warning:
assert(!name.empty() && !line_number.empty() && !rest.empty());
*error_stream << name << ":" << line_number << ": "
<< (type == MessageType::Error ? "error: "
: "warning: ")
<< rest.strip_whitespace() << std::endl;
*total_errors += type == MessageType::Error;
*total_warnings += type == MessageType::Warning;
break;
case MessageType::ErrorSummary:
case MessageType::WarningSummary:
break;
case MessageType::GlobalError:
case MessageType::GlobalWarning:
assert(!rest.empty());
*total_errors += type == MessageType::GlobalError;
*total_warnings += type == MessageType::GlobalWarning;
*error_stream << name << ": "
<< (type == MessageType::GlobalError ? "error"
: "warning")
<< ": " << rest.strip_whitespace() << std::endl;
break;
case MessageType::Unknown:
*error_stream << name << ":";
*error_stream << " " << message << std::endl;
break;
case MessageType::Ignored:
break;
}
}
}
return (existing_total_errors == *total_errors);
}
// Outputs the number of warnings and errors if there are any.
void OutputMessages(std::ostream* error_stream, size_t total_warnings,
size_t total_errors) {
if (total_warnings > 0 || total_errors > 0) {
if (total_warnings > 0 && total_errors > 0) {
*error_stream << total_warnings << " warning"
<< (total_warnings > 1 ? "s" : "") << " and "
<< total_errors << " error" << (total_errors > 1 ? "s" : "")
<< " generated." << std::endl;
} else if (total_warnings > 0) {
*error_stream << total_warnings << " warning"
<< (total_warnings > 1 ? "s" : "") << " generated."
<< std::endl;
} else if (total_errors > 0) {
*error_stream << total_errors << " error" << (total_errors > 1 ? "s" : "")
<< " generated." << std::endl;
}
}
}
} // namespace glslc