diff --git a/Makefile b/Makefile index f6a76b72..abe9395b 100644 --- a/Makefile +++ b/Makefile @@ -66,6 +66,7 @@ rgbasm_obj := \ src/asm/warning.o \ src/extern/getopt.o \ src/extern/utf8decoder.o \ + src/diagnostics.o \ src/error.o \ src/linkdefs.o \ src/opmath.o \ diff --git a/include/asm/main.hpp b/include/asm/main.hpp index 0c29c5ec..890da1f8 100644 --- a/include/asm/main.hpp +++ b/include/asm/main.hpp @@ -7,7 +7,6 @@ #include extern bool verbose; -extern bool warnings; // True to enable warnings, false to disable them. extern FILE *dependFile; extern std::string targetFileName; diff --git a/include/asm/warning.hpp b/include/asm/warning.hpp index 0bbc927a..9b202f36 100644 --- a/include/asm/warning.hpp +++ b/include/asm/warning.hpp @@ -3,8 +3,17 @@ #ifndef RGBDS_ASM_WARNING_HPP #define RGBDS_ASM_WARNING_HPP +#include "diagnostics.hpp" + extern unsigned int nbErrors, maxErrors; +enum WarningLevel { + LEVEL_DEFAULT, // Warnings that are enabled by default + LEVEL_ALL, // Warnings that probably indicate an error + LEVEL_EXTRA, // Warnings that are less likely to indicate an error + LEVEL_EVERYTHING, // Literally every warning +}; + enum WarningID { WARNING_ASSERT, // Assertions WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range @@ -43,24 +52,7 @@ enum WarningID { NB_WARNINGS, }; -enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED }; - -struct WarningState { - WarningAbled state; - WarningAbled error; - - void update(WarningState other); -}; - -struct Diagnostics { - WarningState flagStates[NB_WARNINGS]; - WarningState metaStates[NB_WARNINGS]; -}; - -extern Diagnostics warningStates; -extern bool warningsAreErrors; - -void processWarningFlag(char const *flag); +extern Diagnostics warnings; // Used to warn the user about problems that don't prevent the generation of // valid code. diff --git a/include/diagnostics.hpp b/include/diagnostics.hpp new file mode 100644 index 00000000..824c1454 --- /dev/null +++ b/include/diagnostics.hpp @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT + +#ifndef RGBDS_DIAGNOSTICS_HPP +#define RGBDS_DIAGNOSTICS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "error.hpp" +#include "helpers.hpp" +#include "itertools.hpp" + +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 (auto const ¶mWarning : paramWarnings) { + W baseID = paramWarning.firstID; + uint8_t maxParam = paramWarning.lastID - baseID + 1; + assume(paramWarning.defaultLevel <= maxParam); + + if (rootFlag == warningFlags[baseID].name) { // Match! + // 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) { + if (*param != 255) { // Don't warn if already capped + warnx( + "Invalid parameter %" PRIu8 + " for warning flag \"%s\"; capping at maximum %" PRIu8, + *param, + rootFlag.c_str(), + maxParam + ); + } + *param = maxParam; + } + + // Set the first to enabled/error, and disable the rest + for (uint8_t ofs = 0; ofs < maxParam; ofs++) { + WarningState &warning = state.flagStates[baseID + ofs]; + if (ofs < *param) { + warning.update(flagState); + } else { + warning.state = WARNING_DISABLED; + } + } + return rootFlag; + } + } + + // Try to match against a non-parametric warning, unless there was an equals sign + if (!param.has_value()) { + // Try to match against a "meta" warning + for (WarningFlag const &metaWarning : metaWarnings) { + if (rootFlag == metaWarning.name) { + // 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 the flag 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\"", flag); + return rootFlag; +} + +#endif // RGBDS_DIAGNOSTICS_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7f1f51c9..0ba9518b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -48,6 +48,7 @@ set(rgbasm_src "asm/symbol.cpp" "asm/warning.cpp" "extern/utf8decoder.cpp" + "diagnostics.cpp" "linkdefs.cpp" "opmath.cpp" "util.cpp" diff --git a/src/asm/main.cpp b/src/asm/main.cpp index cc985628..08db0170 100644 --- a/src/asm/main.cpp +++ b/src/asm/main.cpp @@ -28,7 +28,6 @@ bool generatePhonyDeps = false; // -MP std::string targetFileName; // -MQ, -MT bool failedOnMissingInclude = false; bool verbose = false; // -v -bool warnings = true; // -w // Escapes Make-special chars from a string static std::string make_escape(std::string &str) { @@ -330,7 +329,7 @@ int main(int argc, char *argv[]) { break; case 'w': - warnings = false; + warnings.state.warningsEnabled = false; break; unsigned long maxValue; diff --git a/src/asm/opt.cpp b/src/asm/opt.cpp index dfd773f9..dc189a7d 100644 --- a/src/asm/opt.cpp +++ b/src/asm/opt.cpp @@ -18,9 +18,8 @@ struct OptStackEntry { char gfxDigits[4]; uint8_t fixPrecision; uint8_t fillByte; - bool warningsAreErrors; size_t maxRecursionDepth; - Diagnostics warningStates; + DiagnosticsState warningStates; }; static std::stack stack; @@ -47,7 +46,9 @@ void opt_R(size_t newDepth) { } void opt_W(char const *flag) { - processWarningFlag(flag); + if (warnings.processWarningFlag(flag) == "numeric-string") { + warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n"); + } } void opt_Parse(char const *s) { @@ -158,9 +159,7 @@ void opt_Push() { entry.fillByte = fillByte; // Pulled from section.hpp - // Both of these pulled from warning.hpp - entry.warningsAreErrors = warningsAreErrors; - entry.warningStates = warningStates; + entry.warningStates = warnings.state; // Pulled from warning.hpp entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h @@ -182,9 +181,8 @@ void opt_Pop() { opt_Q(entry.fixPrecision); opt_R(entry.maxRecursionDepth); - // opt_W does not apply a whole warning state; it processes one flag string - warningsAreErrors = entry.warningsAreErrors; - warningStates = entry.warningStates; + // `opt_W` does not apply a whole warning state; it processes one flag string + warnings.state = entry.warningStates; } void opt_CheckStack() { diff --git a/src/asm/warning.cpp b/src/asm/warning.cpp index 39a67026..bb6636d3 100644 --- a/src/asm/warning.cpp +++ b/src/asm/warning.cpp @@ -20,280 +20,50 @@ unsigned int nbErrors = 0; unsigned int maxErrors = 0; -Diagnostics warningStates; -bool warningsAreErrors; - -enum WarningLevel { - LEVEL_DEFAULT, // Warnings that are enabled by default - LEVEL_ALL, // Warnings that probably indicate an error - LEVEL_EXTRA, // Warnings that are less likely to indicate an error - LEVEL_EVERYTHING, // Literally every warning +// clang-format off: nested initializers +Diagnostics warnings = { + .metaWarnings = { + {"all", LEVEL_ALL }, + {"extra", LEVEL_EXTRA }, + {"everything", LEVEL_EVERYTHING}, + }, + .warningFlags = { + {"assert", LEVEL_DEFAULT }, + {"backwards-for", LEVEL_ALL }, + {"builtin-args", LEVEL_ALL }, + {"charmap-redef", LEVEL_ALL }, + {"div", LEVEL_EVERYTHING}, + {"empty-data-directive", LEVEL_ALL }, + {"empty-macro-arg", LEVEL_EXTRA }, + {"empty-strrpl", LEVEL_ALL }, + {"large-constant", LEVEL_ALL }, + {"macro-shift", LEVEL_EXTRA }, + {"nested-comment", LEVEL_DEFAULT }, + {"obsolete", LEVEL_DEFAULT }, + {"shift", LEVEL_EVERYTHING}, + {"shift-amount", LEVEL_EVERYTHING}, + {"unmatched-directive", LEVEL_EXTRA }, + {"unterminated-load", LEVEL_EXTRA }, + {"user", LEVEL_DEFAULT }, + // Parametric warnings + {"numeric-string", LEVEL_EVERYTHING}, + {"numeric-string", LEVEL_EVERYTHING}, + {"purge", LEVEL_DEFAULT }, + {"purge", LEVEL_ALL }, + {"truncation", LEVEL_DEFAULT }, + {"truncation", LEVEL_EXTRA }, + {"unmapped-char", LEVEL_DEFAULT }, + {"unmapped-char", LEVEL_ALL }, + }, + .paramWarnings = { + {WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1}, + {WARNING_PURGE_1, WARNING_PURGE_2, 1}, + {WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2}, + {WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1}, + }, + .state = DiagnosticsState(), }; - -struct WarningFlag { - char const *name; - WarningLevel level; -}; - -static WarningFlag const metaWarnings[] = { - {"all", LEVEL_ALL }, - {"extra", LEVEL_EXTRA }, - {"everything", LEVEL_EVERYTHING}, -}; - -static WarningFlag const warningFlags[NB_WARNINGS] = { - {"assert", LEVEL_DEFAULT }, - {"backwards-for", LEVEL_ALL }, - {"builtin-args", LEVEL_ALL }, - {"charmap-redef", LEVEL_ALL }, - {"div", LEVEL_EVERYTHING}, - {"empty-data-directive", LEVEL_ALL }, - {"empty-macro-arg", LEVEL_EXTRA }, - {"empty-strrpl", LEVEL_ALL }, - {"large-constant", LEVEL_ALL }, - {"macro-shift", LEVEL_EXTRA }, - {"nested-comment", LEVEL_DEFAULT }, - {"obsolete", LEVEL_DEFAULT }, - {"shift", LEVEL_EVERYTHING}, - {"shift-amount", LEVEL_EVERYTHING}, - {"unmatched-directive", LEVEL_EXTRA }, - {"unterminated-load", LEVEL_EXTRA }, - {"user", LEVEL_DEFAULT }, - // Parametric warnings - {"numeric-string", LEVEL_EVERYTHING}, - {"numeric-string", LEVEL_EVERYTHING}, - {"purge", LEVEL_DEFAULT }, - {"purge", LEVEL_ALL }, - {"truncation", LEVEL_DEFAULT }, - {"truncation", LEVEL_EXTRA }, - {"unmapped-char", LEVEL_DEFAULT }, - {"unmapped-char", LEVEL_ALL }, -}; - -static const struct { - WarningID firstID; - WarningID lastID; - uint8_t defaultLevel; -} paramWarnings[] = { - {WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1}, - {WARNING_PURGE_1, WARNING_PURGE_2, 1}, - {WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2}, - {WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1}, -}; - -enum WarningBehavior { DISABLED, ENABLED, ERROR }; - -static WarningBehavior getWarningBehavior(WarningID id) { - // Check if warnings are globally disabled - if (!warnings) { - return WarningBehavior::DISABLED; - } - - // Get the state of this warning flag - WarningState const &flagState = warningStates.flagStates[id]; - WarningState const &metaState = warningStates.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 = 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 == LEVEL_DEFAULT) { // enabled by default - return enabledBehavior; - } - - // No flag enables this warning, explicitly or implicitly - return WarningBehavior::DISABLED; -} - -void WarningState::update(WarningState other) { - if (other.state != WARNING_DEFAULT) { - state = other.state; - } - if (other.error != WARNING_DEFAULT) { - error = other.error; - } -} - -void 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 - warningsAreErrors = true; - return; - } else if (rootFlag == "no-error") { - // `-Wno-error` disables promotion of warnings to errors - warningsAreErrors = false; - return; - } - - // Check for prefixes that affect what the flag does - WarningState state; - if (rootFlag.starts_with("error=")) { - // `-Werror=` enables the flag as an error - state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED}; - rootFlag.erase(0, literal_strlen("error=")); - } else if (rootFlag.starts_with("no-error=")) { - // `-Wno-error=` prevents the flag from being an error, - // without affecting whether it is enabled - state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED}; - rootFlag.erase(0, literal_strlen("no-error=")); - } else if (rootFlag.starts_with("no-")) { - // `-Wno-` disables the flag - state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT}; - rootFlag.erase(0, literal_strlen("no-")); - } else { - // `-W` enables the flag - state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT}; - } - - // Check for an `=` parameter to process as a parametric warning - // `-Wno-` and `-Wno-error=` negation cannot have an `=` parameter, but without a - // parameter, the 0 value will apply to all levels of a parametric warning - uint8_t param = 0; - bool hasParam = false; - if (state.state == WARNING_ENABLED) { - // First, check if there is an "equals" sign followed by a decimal number - // Ignore an equal sign at the very end of the string - if (auto equals = rootFlag.find('='); - equals != rootFlag.npos && equals != rootFlag.size() - 1) { - hasParam = true; - - // Is the rest of the string a decimal number? - // We want to avoid `strtoul`'s whitespace and sign, so we parse manually - char const *ptr = rootFlag.c_str() + equals + 1; - bool warned = false; - - // The `if`'s condition above ensures that this will run at least once - do { - // If we don't have a digit, bail - if (*ptr < '0' || *ptr > '9') { - break; - } - // Avoid overflowing! - if (param > UINT8_MAX - (*ptr - '0')) { - if (!warned) { - warnx("Invalid warning flag \"%s\": capping parameter at 255", flag); - } - warned = true; // Only warn once, cap always - param = 255; - continue; - } - param = param * 10 + (*ptr - '0'); - - ptr++; - } while (*ptr); - - // If we reached the end of the string, truncate it at the '=' - if (*ptr == '\0') { - rootFlag.resize(equals); - // `-W=0` is equivalent to `-Wno-` - if (param == 0) { - state.state = WARNING_DISABLED; - } - } - } - } - - // 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 (auto const ¶mWarning : paramWarnings) { - WarningID baseID = paramWarning.firstID; - uint8_t maxParam = paramWarning.lastID - baseID + 1; - assume(paramWarning.defaultLevel <= maxParam); - - if (rootFlag == warningFlags[baseID].name) { // Match! - if (rootFlag == "numeric-string") { - warning(WARNING_OBSOLETE, "Warning flag \"numeric-string\" is deprecated\n"); - } - - // 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 == 0) { - param = paramWarning.defaultLevel; - } else if (param > maxParam) { - if (param != 255) { // Don't warn if already capped - warnx( - "Invalid parameter %" PRIu8 - " for warning flag \"%s\"; capping at maximum %" PRIu8, - param, - rootFlag.c_str(), - maxParam - ); - } - param = maxParam; - } - - // Set the first to enabled/error, and disable the rest - for (uint8_t ofs = 0; ofs < maxParam; ofs++) { - WarningState &warning = warningStates.flagStates[baseID + ofs]; - if (ofs < param) { - warning.update(state); - } else { - warning.state = WARNING_DISABLED; - } - } - return; - } - } - - // Try to match against a non-parametric warning, unless there was an equals sign - if (!hasParam) { - // Try to match against a "meta" warning - for (WarningFlag const &metaWarning : metaWarnings) { - if (rootFlag == metaWarning.name) { - // Set each of the warning flags that meets this level - for (WarningID id : EnumSeq(NB_WARNINGS)) { - if (metaWarning.level >= warningFlags[id].level) { - warningStates.metaStates[id].update(state); - } - } - return; - } - } - - // Try to match the flag against a "normal" flag - for (WarningID id : EnumSeq(NB_PLAIN_WARNINGS)) { - if (rootFlag == warningFlags[id].name) { - warningStates.flagStates[id].update(state); - return; - } - } - } - - warnx("Unknown warning flag \"%s\"", flag); -} +// clang-format on void printDiag( char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag @@ -338,12 +108,12 @@ void fatalerror(char const *fmt, ...) { } void warning(WarningID id, char const *fmt, ...) { - char const *flag = warningFlags[id].name; + char const *flag = warnings.warningFlags[id].name; va_list args; va_start(args, fmt); - switch (getWarningBehavior(id)) { + switch (warnings.getWarningBehavior(id)) { case WarningBehavior::DISABLED: break; diff --git a/src/diagnostics.cpp b/src/diagnostics.cpp new file mode 100644 index 00000000..21d6b657 --- /dev/null +++ b/src/diagnostics.cpp @@ -0,0 +1,86 @@ +#include "diagnostics.hpp" + +void WarningState::update(WarningState other) { + if (other.state != WARNING_DEFAULT) { + state = other.state; + } + if (other.error != WARNING_DEFAULT) { + error = other.error; + } +} + +std::pair> getInitialWarningState(std::string &flag) { + // Check for prefixes that affect what the flag does + WarningState state; + if (flag.starts_with("error=")) { + // `-Werror=` enables the flag as an error + state = {.state = WARNING_ENABLED, .error = WARNING_ENABLED}; + flag.erase(0, literal_strlen("error=")); + } else if (flag.starts_with("no-error=")) { + // `-Wno-error=` prevents the flag from being an error, + // without affecting whether it is enabled + state = {.state = WARNING_DEFAULT, .error = WARNING_DISABLED}; + flag.erase(0, literal_strlen("no-error=")); + } else if (flag.starts_with("no-")) { + // `-Wno-` disables the flag + state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT}; + flag.erase(0, literal_strlen("no-")); + } else { + // `-W` enables the flag + state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT}; + } + + // Check for an `=` parameter to process as a parametric warning + // `-Wno-` and `-Wno-error=` negation cannot have an `=` parameter, but without a + // parameter, the 0 value will apply to all levels of a parametric warning + uint8_t param = 0; + bool hasParam = false; + if (state.state == WARNING_ENABLED) { + // First, check if there is an "equals" sign followed by a decimal number + // Ignore an equal sign at the very end of the string + if (auto equals = flag.find('='); equals != flag.npos && equals != flag.size() - 1) { + hasParam = true; + + // Is the rest of the string a decimal number? + // We want to avoid `strtoul`'s whitespace and sign, so we parse manually + char const *ptr = flag.c_str() + equals + 1; + bool warned = false; + + // The `if`'s condition above ensures that this will run at least once + do { + // If we don't have a digit, bail + if (*ptr < '0' || *ptr > '9') { + break; + } + // Avoid overflowing! + if (param > UINT8_MAX - (*ptr - '0')) { + if (!warned) { + warnx( + "Invalid warning flag \"%s\": capping parameter at 255", flag.c_str() + ); + } + warned = true; // Only warn once, cap always + param = 255; + continue; + } + param = param * 10 + (*ptr - '0'); + + ptr++; + } while (*ptr); + + // If we reached the end of the string, truncate it at the '=' + if (*ptr == '\0') { + flag.resize(equals); + // `-W=0` is equivalent to `-Wno-` + if (param == 0) { + state.state = WARNING_DISABLED; + } + } + } + } + + if (hasParam) { + return {state, param}; + } + return {state, std::nullopt}; +} diff --git a/src/link/main.cpp b/src/link/main.cpp index 04ab3402..397352d1 100644 --- a/src/link/main.cpp +++ b/src/link/main.cpp @@ -135,7 +135,7 @@ void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) { } // Short options -static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvWwx"; +static char const *optstring = "dhl:m:Mn:O:o:p:S:tVvwx"; // Equivalent long options // Please keep in the same order as short opts.