mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-23 09:52:20 -05:00
444 lines
16 KiB
C++
444 lines
16 KiB
C++
|
/**
|
||
|
* ZAPD Warning- and Error-handling system
|
||
|
* =======================================
|
||
|
*
|
||
|
* This provides a common standard way to write ZAPD warnings/errors, which should be used for all
|
||
|
* such. It will pretty-print them in a uniform way, with styles defined in the header.
|
||
|
*
|
||
|
* Warnings/errors should be constructed using the macros given in the header; there are now plenty
|
||
|
* of examples in the codebase of how to do this. Their purposes are noted above each category in
|
||
|
* the header. Each warning has a type, one of the ones in warningStringToInitMap, or
|
||
|
* WarningType::Always, which is used for warnings that cannot be disabled and do not display a
|
||
|
* type.
|
||
|
*
|
||
|
* Currently there are three levels of alert a warning can have:
|
||
|
* - Off (does not display anything)
|
||
|
* - Warn (print a warning but continue processing)
|
||
|
* - Err (behave like an error, i.e. print and throw an exception to crash ZAPD when occurs)
|
||
|
*
|
||
|
* Flag use:
|
||
|
* - -Wfoo enables warnings of type foo
|
||
|
* - -Wno-foo disables warnings of type foo
|
||
|
* - -Werror=foo escalates foo to behave like an error
|
||
|
* - -Weverything enables all warnings
|
||
|
* - -Werror escalates all enabled warnings to errors
|
||
|
*
|
||
|
* Errors do not have types, and will always throw an exception; they cannot be disabled.
|
||
|
*
|
||
|
* Format
|
||
|
* ===
|
||
|
* Each printed warning/error contains the same three sections:
|
||
|
* - Preamble: automatically generated; the content varies depending on category. It will print the
|
||
|
* file and function that the warning is from, and information about the files being processed
|
||
|
* or extracted.
|
||
|
* - Header: begins with 'warning: ' or 'error:', should contain essential information about the
|
||
|
* warning/error, ends with the warning type if applicable. Printed with emphasis to make it
|
||
|
* stand out. Does not start with a capital letter or end with a '.'
|
||
|
* - Body (optional): indented, should contain further diagnostic information useful for identifying
|
||
|
* and fixing the warning/error. Can be a sentence with captialisation and '.' on the end.
|
||
|
*
|
||
|
* Please think of what the end user will find most useful when writing the header and body, and try
|
||
|
* to keep it brief without sacrificing important information! Also remember that if the user is
|
||
|
* only looking at stderr, they will normally have no other context.
|
||
|
*
|
||
|
* Warning vs error
|
||
|
* ===
|
||
|
* The principle that we have operated on so far is
|
||
|
* - issue a warning if ZAPD will still be able to produce a valid, compilable C file that will
|
||
|
* match
|
||
|
* - if this cannot happen, use an error.
|
||
|
* but at the end of the day, it is up to the programmer's discretion what it should be possible to
|
||
|
* disable.
|
||
|
*
|
||
|
* Documentation
|
||
|
* ===
|
||
|
* Remember that all warnings also need to be documented in the README.md. The help is generated
|
||
|
* automatically.
|
||
|
*/
|
||
|
#include "WarningHandler.h"
|
||
|
|
||
|
#include <cassert>
|
||
|
#include "Globals.h"
|
||
|
#include "Utils/StringHelper.h"
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
WarningType type;
|
||
|
WarningLevel defaultLevel;
|
||
|
std::string description;
|
||
|
} WarningInfoInit;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
WarningLevel level;
|
||
|
std::string name;
|
||
|
std::string description;
|
||
|
} WarningInfo;
|
||
|
|
||
|
/**
|
||
|
* Master list of all default warning types and features
|
||
|
*
|
||
|
* To add a warning type, fill in a new row of this map. Think carefully about what its default
|
||
|
* level should be, and try and make the description both brief and informative: it is used in the
|
||
|
* help message, so again, think about what the end user needs to know.
|
||
|
*/
|
||
|
// clang-format off
|
||
|
static const std::unordered_map<std::string, WarningInfoInit> warningStringToInitMap = {
|
||
|
{"deprecated", {WarningType::Deprecated,
|
||
|
#ifdef DEPRECATION_ON
|
||
|
WarningLevel::Warn,
|
||
|
#else
|
||
|
WarningLevel::Off,
|
||
|
#endif
|
||
|
"Deprecated features"}},
|
||
|
{"unaccounted", {WarningType::Unaccounted, WarningLevel::Off, "Large blocks of unaccounted"}},
|
||
|
{"missing-offsets", {WarningType::MissingOffsets, WarningLevel::Warn, "Offset attribute missing in XML tag"}},
|
||
|
{"intersection", {WarningType::Intersection, WarningLevel::Warn, "Two assets intersect"}},
|
||
|
{"missing-attribute", {WarningType::MissingAttribute, WarningLevel::Warn, "Required attribute missing in XML tag"}},
|
||
|
{"invalid-attribute-value", {WarningType::InvalidAttributeValue, WarningLevel::Err, "Attribute declared in XML is wrong"}},
|
||
|
{"unknown-attribute", {WarningType::UnknownAttribute, WarningLevel::Warn, "Unknown attribute in XML entry tag"}},
|
||
|
{"invalid-xml", {WarningType::InvalidXML, WarningLevel::Err, "XML has syntax errors"}},
|
||
|
{"invalid-jpeg", {WarningType::InvalidJPEG, WarningLevel::Err, "JPEG file does not conform to the game's format requirements"}},
|
||
|
{"invalid-png", {WarningType::InvalidPNG, WarningLevel::Err, "Issues arising when processing PNG data"}},
|
||
|
{"invalid-extracted-data", {WarningType::InvalidExtractedData, WarningLevel::Err, "Extracted data does not have correct form"}},
|
||
|
{"missing-segment", {WarningType::MissingSegment, WarningLevel::Warn, "Segment not given in File tag in XML"}},
|
||
|
{"hardcoded-pointer", {WarningType::HardcodedPointer, WarningLevel::Warn, "ZAPD lacks the info to make a symbol, so must output a hardcoded pointer"}},
|
||
|
{"not-implemented", {WarningType::NotImplemented, WarningLevel::Warn, "ZAPD does not currently support this feature"}},
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Map constructed at runtime to contain the warning features as set by the user using -W flags.
|
||
|
*/
|
||
|
static std::unordered_map<WarningType, WarningInfo> warningTypeToInfoMap;
|
||
|
|
||
|
void WarningHandler::ConstructTypeToInfoMap() {
|
||
|
for (auto& entry : warningStringToInitMap) {
|
||
|
warningTypeToInfoMap[entry.second.type] = {entry.second.defaultLevel, entry.first, entry.second.description};
|
||
|
}
|
||
|
warningTypeToInfoMap[WarningType::Always] = {WarningLevel::Warn, "always", "you shouldn't be reading this"};
|
||
|
assert(warningTypeToInfoMap.size() == static_cast<size_t>(WarningType::Max));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialises the main warning type map and reads flags passed to set each warning type's level.
|
||
|
*/
|
||
|
void WarningHandler::Init(int argc, char* argv[]) {
|
||
|
ConstructTypeToInfoMap();
|
||
|
|
||
|
bool werror = false;
|
||
|
for (int i = 1; i < argc; i++) {
|
||
|
// If it doesn't start with "-W" skip it.
|
||
|
if (argv[i][0] != '-' || argv[i][1] != 'W' || argv[i][2] == '\0') {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
WarningLevel warningTypeOn = WarningLevel::Warn;
|
||
|
size_t startingIndex = 2;
|
||
|
|
||
|
// "-Wno-"
|
||
|
if (argv[i][2] == 'n' && argv[i][3] == 'o' && argv[i][4] == '-' && argv[i][5] != '\0') {
|
||
|
warningTypeOn = WarningLevel::Off;
|
||
|
startingIndex = 5;
|
||
|
}
|
||
|
|
||
|
// Read starting after the "-W" or "-Wno-"
|
||
|
std::string_view currentArgv = &argv[i][startingIndex];
|
||
|
|
||
|
if (currentArgv == "error") {
|
||
|
werror = warningTypeOn != WarningLevel::Off;
|
||
|
} else if (currentArgv == "everything") {
|
||
|
for (auto& it: warningTypeToInfoMap) {
|
||
|
if (it.second.level <= WarningLevel::Warn) {
|
||
|
it.second.level = warningTypeOn;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// "-Werror=" / "-Wno-error=" parser
|
||
|
if (currentArgv.rfind("error=", 0) == 0) {
|
||
|
// Read starting after the "error=" part
|
||
|
currentArgv = &argv[i][startingIndex + 6];
|
||
|
warningTypeOn = warningTypeOn != WarningLevel::Off ? WarningLevel::Err : WarningLevel::Warn;
|
||
|
}
|
||
|
|
||
|
auto it = warningStringToInitMap.find(std::string(currentArgv));
|
||
|
if (it != warningStringToInitMap.end()) {
|
||
|
warningTypeToInfoMap[it->second.type].level = warningTypeOn;
|
||
|
}
|
||
|
else {
|
||
|
HANDLE_WARNING(WarningType::Always, StringHelper::Sprintf("unknown warning flag '%s'", argv[i]), "");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (werror) {
|
||
|
for (auto& it: warningTypeToInfoMap) {
|
||
|
if (it.second.level >= WarningLevel::Warn) {
|
||
|
it.second.level = WarningLevel::Err;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool WarningHandler::IsWarningEnabled(WarningType warnType) {
|
||
|
assert(static_cast<size_t>(warnType) >= 0 && warnType < WarningType::Max);
|
||
|
|
||
|
return warningTypeToInfoMap.at(warnType).level != WarningLevel::Off;
|
||
|
}
|
||
|
|
||
|
bool WarningHandler::WasElevatedToError(WarningType warnType) {
|
||
|
assert(static_cast<size_t>(warnType) >= 0 && warnType < WarningType::Max);
|
||
|
|
||
|
if (!IsWarningEnabled(warnType)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return warningTypeToInfoMap.at(warnType).level >= WarningLevel::Err;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print file/line/function info for debugging
|
||
|
*/
|
||
|
void WarningHandler::FunctionPreamble(const char* filename, int32_t line, const char* function) {
|
||
|
if (Globals::Instance->verbosity >= VerbosityLevel::VERBOSITY_DEBUG) {
|
||
|
fprintf(stderr, "%s:%i: in function %s:\n", filename, line, function);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the information about the file(s) being processed (XML for extraction, png etc. for building)
|
||
|
*/
|
||
|
void WarningHandler::ProcessedFilePreamble() {
|
||
|
if (Globals::Instance->inputPath != "") {
|
||
|
fprintf(stderr, "When processing file %s: ", Globals::Instance->inputPath.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print information about the binary file being extracted
|
||
|
*/
|
||
|
void WarningHandler::ExtractedFilePreamble(const ZFile *parent, const ZResource* res, const uint32_t offset) {
|
||
|
fprintf(stderr, "in input binary file %s, ", parent->GetName().c_str());
|
||
|
if (res != nullptr) {
|
||
|
fprintf(stderr, "resource '%s' at ", res->GetName().c_str());
|
||
|
}
|
||
|
fprintf(stderr, "offset 0x%06X: \n\t", offset);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct the rest of the message, after warning:/error. The message is filled in one character at a time, with indents added after newlines
|
||
|
*/
|
||
|
std::string WarningHandler::ConstructMessage(std::string message, const std::string& header, const std::string& body) {
|
||
|
message.reserve(message.size() + header.size() + body.size() + 10 * (sizeof(HANG_INDT) - 1));
|
||
|
message += StringHelper::Sprintf(HILITE("%s"), header.c_str());
|
||
|
message += "\n";
|
||
|
|
||
|
if (body == "") {
|
||
|
return message;
|
||
|
}
|
||
|
|
||
|
message += HANG_INDT;
|
||
|
for (const char* ptr = body.c_str(); *ptr != '\0'; ptr++) {
|
||
|
message += *ptr;
|
||
|
if (*ptr == '\n') {
|
||
|
message += HANG_INDT;
|
||
|
}
|
||
|
}
|
||
|
message += "\n";
|
||
|
|
||
|
return message;
|
||
|
}
|
||
|
|
||
|
/* Error module functions */
|
||
|
|
||
|
void WarningHandler::PrintErrorAndThrow(const std::string& header, const std::string& body) {
|
||
|
std::string errorMsg = ERR_FMT("error: ");
|
||
|
throw std::runtime_error(ConstructMessage(errorMsg, header, body));
|
||
|
}
|
||
|
|
||
|
/* Error types, to be used via the macros */
|
||
|
|
||
|
void WarningHandler::ErrorType(WarningType warnType, const std::string& header, const std::string& body) {
|
||
|
std::string headerMsg = header;
|
||
|
|
||
|
for (const auto& iter: warningStringToInitMap) {
|
||
|
if (iter.second.type == warnType) {
|
||
|
headerMsg += StringHelper::Sprintf(" [%s]", iter.first.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
PrintErrorAndThrow(headerMsg, body);
|
||
|
}
|
||
|
|
||
|
void WarningHandler::Error_Plain(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) {
|
||
|
FunctionPreamble(filename, line, function);
|
||
|
|
||
|
ErrorType(warnType, header, body);
|
||
|
}
|
||
|
|
||
|
void WarningHandler::Error_Process(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) {
|
||
|
FunctionPreamble(filename, line, function);
|
||
|
ProcessedFilePreamble();
|
||
|
|
||
|
ErrorType(warnType, header, body);
|
||
|
}
|
||
|
|
||
|
void WarningHandler::Error_Resource(const char* filename, int32_t line, const char* function, WarningType warnType, const ZFile *parent, const ZResource* res, const uint32_t offset, const std::string& header, const std::string& body) {
|
||
|
assert(parent != nullptr);
|
||
|
|
||
|
FunctionPreamble(filename, line, function);
|
||
|
ProcessedFilePreamble();
|
||
|
ExtractedFilePreamble(parent, res, offset);
|
||
|
|
||
|
ErrorType(warnType, header, body);
|
||
|
}
|
||
|
|
||
|
/* Warning module functions */
|
||
|
|
||
|
void WarningHandler::PrintWarningBody(const std::string& header, const std::string& body) {
|
||
|
std::string errorMsg = WARN_FMT("warning: ");
|
||
|
fprintf(stderr, "%s", ConstructMessage(errorMsg, header, body).c_str());
|
||
|
}
|
||
|
|
||
|
void WarningHandler::WarningTypeAndChooseEscalate(WarningType warnType, const std::string& header, const std::string& body) {
|
||
|
std::string headerMsg = header;
|
||
|
|
||
|
for (const auto& iter: warningStringToInitMap) {
|
||
|
if (iter.second.type == warnType) {
|
||
|
headerMsg += StringHelper::Sprintf(" [-W%s]", iter.first.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (WasElevatedToError(warnType)) {
|
||
|
PrintErrorAndThrow(headerMsg, body);
|
||
|
} else {
|
||
|
PrintWarningBody(headerMsg, body);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Warning types, to be used via the macros */
|
||
|
|
||
|
void WarningHandler::Warning_Plain(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) {
|
||
|
if (!IsWarningEnabled(warnType)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FunctionPreamble(filename, line, function);
|
||
|
|
||
|
WarningTypeAndChooseEscalate(warnType, header, body);
|
||
|
}
|
||
|
|
||
|
void WarningHandler::Warning_Process(const char* filename, int32_t line, const char* function, WarningType warnType, const std::string& header, const std::string& body) {
|
||
|
if (!IsWarningEnabled(warnType)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FunctionPreamble(filename, line, function);
|
||
|
ProcessedFilePreamble();
|
||
|
|
||
|
WarningTypeAndChooseEscalate(warnType, header, body);
|
||
|
}
|
||
|
|
||
|
void WarningHandler::Warning_Resource(const char* filename, int32_t line, const char* function, WarningType warnType, const ZFile *parent, const ZResource* res, const uint32_t offset, const std::string& header, const std::string& body) {
|
||
|
assert(parent != nullptr);
|
||
|
|
||
|
if (!IsWarningEnabled(warnType)) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
FunctionPreamble(filename, line, function);
|
||
|
ProcessedFilePreamble();
|
||
|
ExtractedFilePreamble(parent, res, offset);
|
||
|
|
||
|
WarningTypeAndChooseEscalate(warnType, header, body);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Help-related functions */
|
||
|
|
||
|
#include <set>
|
||
|
|
||
|
/**
|
||
|
* Print each warning name, default status, and description using the init map
|
||
|
*/
|
||
|
void WarningHandler::PrintHelp() {
|
||
|
std::set<std::string> sortedKeys;
|
||
|
WarningInfoInit warningInfo;
|
||
|
uint32_t columnWidth = 25;
|
||
|
std::string dt;
|
||
|
|
||
|
// Sort keys through the magic of `set`, to print in alphabetical order
|
||
|
for (auto& it : warningStringToInitMap) {
|
||
|
sortedKeys.insert(it.first);
|
||
|
}
|
||
|
|
||
|
printf("\nWarning types ( * means enabled by default)\n");
|
||
|
for (auto& key : sortedKeys) {
|
||
|
warningInfo = warningStringToInitMap.at(key);
|
||
|
if (warningInfo.defaultLevel <= WarningLevel::Warn) {
|
||
|
dt = "-W";
|
||
|
dt += key;
|
||
|
if (warningInfo.defaultLevel == WarningLevel::Warn) {
|
||
|
dt += " *";
|
||
|
}
|
||
|
printf(HELP_DT_INDT "%-*s", columnWidth, dt.c_str());
|
||
|
|
||
|
if (dt.length() + 2 > columnWidth) {
|
||
|
printf("\n" HELP_DT_INDT "%-*s", columnWidth, "");
|
||
|
}
|
||
|
printf("%s\n", warningInfo.description.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
printf("\nDefault errors\n");
|
||
|
for (auto& key : sortedKeys) {
|
||
|
if (warningInfo.defaultLevel > WarningLevel::Warn) {
|
||
|
dt = "-W";
|
||
|
dt += key;
|
||
|
printf(HELP_DT_INDT "%-*s", columnWidth, dt.c_str());
|
||
|
|
||
|
if (dt.length() + 2 > columnWidth) {
|
||
|
printf("\n" HELP_DT_INDT "%*s", columnWidth, "");
|
||
|
}
|
||
|
printf("%s\n", warningInfo.description.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
printf("\n");
|
||
|
printf("Other\n" HELP_DT_INDT "-Weverything will enable all existing warnings.\n" HELP_DT_INDT "-Werror will promote all warnings to errors.\n");
|
||
|
|
||
|
printf("\n");
|
||
|
printf("Warnings can be disabled using -Wno-... instead of -W...; -Weverything will override any -Wno-... flags passed before it.\n");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print which warnings are currently enabled
|
||
|
*/
|
||
|
void WarningHandler::PrintWarningsDebugInfo()
|
||
|
{
|
||
|
std::string dt;
|
||
|
|
||
|
printf("Warnings status:\n");
|
||
|
for (auto& it: warningTypeToInfoMap) {
|
||
|
dt = it.second.name;
|
||
|
dt += ": ";
|
||
|
|
||
|
printf(HELP_DT_INDT "%-25s", dt.c_str());
|
||
|
switch (it.second.level)
|
||
|
{
|
||
|
case WarningLevel::Off:
|
||
|
printf(VT_FGCOL(LIGHTGRAY) "Off" VT_RST);
|
||
|
break;
|
||
|
case WarningLevel::Warn:
|
||
|
printf(VT_FGCOL(YELLOW) "Warn" VT_RST);
|
||
|
break;
|
||
|
case WarningLevel::Err:
|
||
|
printf(VT_FGCOL(RED) "Err" VT_RST);
|
||
|
break;
|
||
|
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|