/** * 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 #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 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 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(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(warnType) >= 0 && warnType < WarningType::Max); return warningTypeToInfoMap.at(warnType).level != WarningLevel::Off; } bool WarningHandler::WasElevatedToError(WarningType warnType) { assert(static_cast(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 /** * Print each warning name, default status, and description using the init map */ void WarningHandler::PrintHelp() { std::set 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"); }