// SPDX-License-Identifier: MIT #ifndef RGBDS_DIAGNOSTICS_HPP #define RGBDS_DIAGNOSTICS_HPP #include #include #include #include #include #include #include #include #include #include "helpers.hpp" #include "itertools.hpp" [[gnu::format(printf, 1, 2)]] void warnx(char const *fmt, ...); enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED }; struct WarningState { WarningAbled state; WarningAbled error; void update(WarningState other); }; std::pair> getInitialWarningState(std::string &flag); template struct WarningFlag { char const *name; L level; }; enum WarningBehavior { DISABLED, ENABLED, ERROR }; template struct ParamWarning { W firstID; W lastID; uint8_t defaultLevel; }; template struct DiagnosticsState { WarningState flagStates[W::NB_WARNINGS]; WarningState metaStates[W::NB_WARNINGS]; bool warningsEnabled = true; bool warningsAreErrors = false; }; template struct Diagnostics { std::vector> metaWarnings; std::vector> warningFlags; std::vector> paramWarnings; DiagnosticsState state; WarningBehavior getWarningBehavior(W id) const; std::string processWarningFlag(char const *flag); }; template WarningBehavior Diagnostics::getWarningBehavior(W id) const { // Check if warnings are globally disabled if (!state.warningsEnabled) { return WarningBehavior::DISABLED; } // Get the state of this warning flag WarningState const &flagState = state.flagStates[id]; WarningState const &metaState = state.metaStates[id]; // If subsequent checks determine that the warning flag is enabled, this checks whether it has // -Werror without -Wno-error= or -Wno-error=, which makes it into an error bool warningIsError = state.warningsAreErrors && flagState.error != WARNING_DISABLED && metaState.error != WARNING_DISABLED; WarningBehavior enabledBehavior = warningIsError ? WarningBehavior::ERROR : WarningBehavior::ENABLED; // First, check the state of the specific warning flag if (flagState.state == WARNING_DISABLED) { // -Wno- return WarningBehavior::DISABLED; } if (flagState.error == WARNING_ENABLED) { // -Werror= return WarningBehavior::ERROR; } if (flagState.state == WARNING_ENABLED) { // -W return enabledBehavior; } // If no flag is specified, check the state of the "meta" flags that affect this warning flag if (metaState.state == WARNING_DISABLED) { // -Wno- return WarningBehavior::DISABLED; } if (metaState.error == WARNING_ENABLED) { // -Werror= return WarningBehavior::ERROR; } if (metaState.state == WARNING_ENABLED) { // -W return enabledBehavior; } // If no meta flag is specified, check the default state of this warning flag if (warningFlags[id].level == L::LEVEL_DEFAULT) { // enabled by default return enabledBehavior; } // No flag enables this warning, explicitly or implicitly return WarningBehavior::DISABLED; } template std::string Diagnostics::processWarningFlag(char const *flag) { std::string rootFlag = flag; // Check for `-Werror` or `-Wno-error` to return early if (rootFlag == "error") { // `-Werror` promotes warnings to errors state.warningsAreErrors = true; return rootFlag; } else if (rootFlag == "no-error") { // `-Wno-error` disables promotion of warnings to errors state.warningsAreErrors = false; return rootFlag; } auto [flagState, param] = getInitialWarningState(rootFlag); // Try to match the flag against a parametric warning // If there was an equals sign, it will have set `param`; if not, `param` will be 0, // which applies to all levels for (ParamWarning const ¶mWarning : paramWarnings) { W baseID = paramWarning.firstID; uint8_t maxParam = paramWarning.lastID - baseID + 1; assume(paramWarning.defaultLevel <= maxParam); if (rootFlag != warningFlags[baseID].name) { continue; } // If making the warning an error but param is 0, set to the maximum // This accommodates `-Werror=`, but also `-Werror==0`, which is // thus filtered out by the caller. // A param of 0 makes sense for disabling everything, but neither for // enabling nor "erroring". Use the default for those. if (!param.has_value() || *param == 0) { param = paramWarning.defaultLevel; } else if (*param > maxParam) { warnx( "Invalid warning flag parameter \"%s=%" PRIu32 "\"; capping at maximum %" PRIu8, rootFlag.c_str(), *param, maxParam ); *param = maxParam; } // Set the first to enabled/error, and disable the rest for (uint32_t ofs = 0; ofs < maxParam; ofs++) { if (WarningState &warning = state.flagStates[baseID + ofs]; ofs < *param) { warning.update(flagState); } else { warning.state = WARNING_DISABLED; } } return rootFlag; } if (param.has_value()) { warnx("Unknown warning flag parameter \"%s=%" PRIu32 "\"", rootFlag.c_str(), *param); return rootFlag; } // Try to match against a "meta" warning for (WarningFlag const &metaWarning : metaWarnings) { if (rootFlag != metaWarning.name) { continue; } // Set each of the warning flags that meets this level for (W id : EnumSeq(W::NB_WARNINGS)) { if (metaWarning.level >= warningFlags[id].level) { state.metaStates[id].update(flagState); } } return rootFlag; } // Try to match against a "normal" flag for (W id : EnumSeq(W::NB_PLAIN_WARNINGS)) { if (rootFlag == warningFlags[id].name) { state.flagStates[id].update(flagState); return rootFlag; } } warnx("Unknown warning flag \"%s\"", rootFlag.c_str()); return rootFlag; } #endif // RGBDS_DIAGNOSTICS_HPP