mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 18:22:07 +00:00
Factor out program-independent warning diagnostic code (#1725)
This commit is contained in:
1
Makefile
1
Makefile
@@ -66,6 +66,7 @@ rgbasm_obj := \
|
|||||||
src/asm/warning.o \
|
src/asm/warning.o \
|
||||||
src/extern/getopt.o \
|
src/extern/getopt.o \
|
||||||
src/extern/utf8decoder.o \
|
src/extern/utf8decoder.o \
|
||||||
|
src/diagnostics.o \
|
||||||
src/error.o \
|
src/error.o \
|
||||||
src/linkdefs.o \
|
src/linkdefs.o \
|
||||||
src/opmath.o \
|
src/opmath.o \
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
extern bool verbose;
|
extern bool verbose;
|
||||||
extern bool warnings; // True to enable warnings, false to disable them.
|
|
||||||
|
|
||||||
extern FILE *dependFile;
|
extern FILE *dependFile;
|
||||||
extern std::string targetFileName;
|
extern std::string targetFileName;
|
||||||
|
|||||||
@@ -3,8 +3,17 @@
|
|||||||
#ifndef RGBDS_ASM_WARNING_HPP
|
#ifndef RGBDS_ASM_WARNING_HPP
|
||||||
#define RGBDS_ASM_WARNING_HPP
|
#define RGBDS_ASM_WARNING_HPP
|
||||||
|
|
||||||
|
#include "diagnostics.hpp"
|
||||||
|
|
||||||
extern unsigned int nbErrors, maxErrors;
|
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 {
|
enum WarningID {
|
||||||
WARNING_ASSERT, // Assertions
|
WARNING_ASSERT, // Assertions
|
||||||
WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
|
WARNING_BACKWARDS_FOR, // `FOR` loop with backwards range
|
||||||
@@ -43,24 +52,7 @@ enum WarningID {
|
|||||||
NB_WARNINGS,
|
NB_WARNINGS,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum WarningAbled { WARNING_DEFAULT, WARNING_ENABLED, WARNING_DISABLED };
|
extern Diagnostics<WarningLevel, WarningID> warnings;
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Used to warn the user about problems that don't prevent the generation of
|
// Used to warn the user about problems that don't prevent the generation of
|
||||||
// valid code.
|
// valid code.
|
||||||
|
|||||||
201
include/diagnostics.hpp
Normal file
201
include/diagnostics.hpp
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#ifndef RGBDS_DIAGNOSTICS_HPP
|
||||||
|
#define RGBDS_DIAGNOSTICS_HPP
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <optional>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<WarningState, std::optional<uint8_t>> getInitialWarningState(std::string &flag);
|
||||||
|
|
||||||
|
template<typename L>
|
||||||
|
struct WarningFlag {
|
||||||
|
char const *name;
|
||||||
|
L level;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WarningBehavior { DISABLED, ENABLED, ERROR };
|
||||||
|
|
||||||
|
template<typename W>
|
||||||
|
struct ParamWarning {
|
||||||
|
W firstID;
|
||||||
|
W lastID;
|
||||||
|
uint8_t defaultLevel;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename W>
|
||||||
|
struct DiagnosticsState {
|
||||||
|
WarningState flagStates[W::NB_WARNINGS];
|
||||||
|
WarningState metaStates[W::NB_WARNINGS];
|
||||||
|
bool warningsEnabled = true;
|
||||||
|
bool warningsAreErrors = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename L, typename W>
|
||||||
|
struct Diagnostics {
|
||||||
|
std::vector<WarningFlag<L>> metaWarnings;
|
||||||
|
std::vector<WarningFlag<L>> warningFlags;
|
||||||
|
std::vector<ParamWarning<W>> paramWarnings;
|
||||||
|
DiagnosticsState<W> state;
|
||||||
|
|
||||||
|
WarningBehavior getWarningBehavior(W id) const;
|
||||||
|
std::string processWarningFlag(char const *flag);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename L, typename W>
|
||||||
|
WarningBehavior Diagnostics<L, W>::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=<flag> or -Wno-error=<meta>, 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-<flag>
|
||||||
|
return WarningBehavior::DISABLED;
|
||||||
|
}
|
||||||
|
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
||||||
|
return WarningBehavior::ERROR;
|
||||||
|
}
|
||||||
|
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
||||||
|
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-<meta>
|
||||||
|
return WarningBehavior::DISABLED;
|
||||||
|
}
|
||||||
|
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
||||||
|
return WarningBehavior::ERROR;
|
||||||
|
}
|
||||||
|
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
||||||
|
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<typename L, typename W>
|
||||||
|
std::string Diagnostics<L, W>::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=<flag>`, but also `-Werror=<flag>=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 <param> 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<L> 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
|
||||||
@@ -48,6 +48,7 @@ set(rgbasm_src
|
|||||||
"asm/symbol.cpp"
|
"asm/symbol.cpp"
|
||||||
"asm/warning.cpp"
|
"asm/warning.cpp"
|
||||||
"extern/utf8decoder.cpp"
|
"extern/utf8decoder.cpp"
|
||||||
|
"diagnostics.cpp"
|
||||||
"linkdefs.cpp"
|
"linkdefs.cpp"
|
||||||
"opmath.cpp"
|
"opmath.cpp"
|
||||||
"util.cpp"
|
"util.cpp"
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ bool generatePhonyDeps = false; // -MP
|
|||||||
std::string targetFileName; // -MQ, -MT
|
std::string targetFileName; // -MQ, -MT
|
||||||
bool failedOnMissingInclude = false;
|
bool failedOnMissingInclude = false;
|
||||||
bool verbose = false; // -v
|
bool verbose = false; // -v
|
||||||
bool warnings = true; // -w
|
|
||||||
|
|
||||||
// Escapes Make-special chars from a string
|
// Escapes Make-special chars from a string
|
||||||
static std::string make_escape(std::string &str) {
|
static std::string make_escape(std::string &str) {
|
||||||
@@ -330,7 +329,7 @@ int main(int argc, char *argv[]) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'w':
|
case 'w':
|
||||||
warnings = false;
|
warnings.state.warningsEnabled = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
unsigned long maxValue;
|
unsigned long maxValue;
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ struct OptStackEntry {
|
|||||||
char gfxDigits[4];
|
char gfxDigits[4];
|
||||||
uint8_t fixPrecision;
|
uint8_t fixPrecision;
|
||||||
uint8_t fillByte;
|
uint8_t fillByte;
|
||||||
bool warningsAreErrors;
|
|
||||||
size_t maxRecursionDepth;
|
size_t maxRecursionDepth;
|
||||||
Diagnostics warningStates;
|
DiagnosticsState<WarningID> warningStates;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::stack<OptStackEntry> stack;
|
static std::stack<OptStackEntry> stack;
|
||||||
@@ -47,7 +46,9 @@ void opt_R(size_t newDepth) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void opt_W(char const *flag) {
|
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) {
|
void opt_Parse(char const *s) {
|
||||||
@@ -158,9 +159,7 @@ void opt_Push() {
|
|||||||
|
|
||||||
entry.fillByte = fillByte; // Pulled from section.hpp
|
entry.fillByte = fillByte; // Pulled from section.hpp
|
||||||
|
|
||||||
// Both of these pulled from warning.hpp
|
entry.warningStates = warnings.state; // Pulled from warning.hpp
|
||||||
entry.warningsAreErrors = warningsAreErrors;
|
|
||||||
entry.warningStates = warningStates;
|
|
||||||
|
|
||||||
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
|
entry.maxRecursionDepth = maxRecursionDepth; // Pulled from fstack.h
|
||||||
|
|
||||||
@@ -182,9 +181,8 @@ void opt_Pop() {
|
|||||||
opt_Q(entry.fixPrecision);
|
opt_Q(entry.fixPrecision);
|
||||||
opt_R(entry.maxRecursionDepth);
|
opt_R(entry.maxRecursionDepth);
|
||||||
|
|
||||||
// opt_W does not apply a whole warning state; it processes one flag string
|
// `opt_W` does not apply a whole warning state; it processes one flag string
|
||||||
warningsAreErrors = entry.warningsAreErrors;
|
warnings.state = entry.warningStates;
|
||||||
warningStates = entry.warningStates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void opt_CheckStack() {
|
void opt_CheckStack() {
|
||||||
|
|||||||
@@ -20,28 +20,14 @@
|
|||||||
unsigned int nbErrors = 0;
|
unsigned int nbErrors = 0;
|
||||||
unsigned int maxErrors = 0;
|
unsigned int maxErrors = 0;
|
||||||
|
|
||||||
Diagnostics warningStates;
|
// clang-format off: nested initializers
|
||||||
bool warningsAreErrors;
|
Diagnostics<WarningLevel, WarningID> warnings = {
|
||||||
|
.metaWarnings = {
|
||||||
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
|
|
||||||
};
|
|
||||||
|
|
||||||
struct WarningFlag {
|
|
||||||
char const *name;
|
|
||||||
WarningLevel level;
|
|
||||||
};
|
|
||||||
|
|
||||||
static WarningFlag const metaWarnings[] = {
|
|
||||||
{"all", LEVEL_ALL },
|
{"all", LEVEL_ALL },
|
||||||
{"extra", LEVEL_EXTRA },
|
{"extra", LEVEL_EXTRA },
|
||||||
{"everything", LEVEL_EVERYTHING},
|
{"everything", LEVEL_EVERYTHING},
|
||||||
};
|
},
|
||||||
|
.warningFlags = {
|
||||||
static WarningFlag const warningFlags[NB_WARNINGS] = {
|
|
||||||
{"assert", LEVEL_DEFAULT },
|
{"assert", LEVEL_DEFAULT },
|
||||||
{"backwards-for", LEVEL_ALL },
|
{"backwards-for", LEVEL_ALL },
|
||||||
{"builtin-args", LEVEL_ALL },
|
{"builtin-args", LEVEL_ALL },
|
||||||
@@ -68,232 +54,16 @@ static WarningFlag const warningFlags[NB_WARNINGS] = {
|
|||||||
{"truncation", LEVEL_EXTRA },
|
{"truncation", LEVEL_EXTRA },
|
||||||
{"unmapped-char", LEVEL_DEFAULT },
|
{"unmapped-char", LEVEL_DEFAULT },
|
||||||
{"unmapped-char", LEVEL_ALL },
|
{"unmapped-char", LEVEL_ALL },
|
||||||
};
|
},
|
||||||
|
.paramWarnings = {
|
||||||
static const struct {
|
|
||||||
WarningID firstID;
|
|
||||||
WarningID lastID;
|
|
||||||
uint8_t defaultLevel;
|
|
||||||
} paramWarnings[] = {
|
|
||||||
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
{WARNING_NUMERIC_STRING_1, WARNING_NUMERIC_STRING_2, 1},
|
||||||
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
{WARNING_PURGE_1, WARNING_PURGE_2, 1},
|
||||||
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
{WARNING_TRUNCATION_1, WARNING_TRUNCATION_2, 2},
|
||||||
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
{WARNING_UNMAPPED_CHAR_1, WARNING_UNMAPPED_CHAR_2, 1},
|
||||||
|
},
|
||||||
|
.state = DiagnosticsState<WarningID>(),
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
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=<flag> or -Wno-error=<meta>, 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-<flag>
|
|
||||||
return WarningBehavior::DISABLED;
|
|
||||||
}
|
|
||||||
if (flagState.error == WARNING_ENABLED) { // -Werror=<flag>
|
|
||||||
return WarningBehavior::ERROR;
|
|
||||||
}
|
|
||||||
if (flagState.state == WARNING_ENABLED) { // -W<flag>
|
|
||||||
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-<meta>
|
|
||||||
return WarningBehavior::DISABLED;
|
|
||||||
}
|
|
||||||
if (metaState.error == WARNING_ENABLED) { // -Werror=<meta>
|
|
||||||
return WarningBehavior::ERROR;
|
|
||||||
}
|
|
||||||
if (metaState.state == WARNING_ENABLED) { // -W<meta>
|
|
||||||
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=<flag>` 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=<flag>` 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-<flag>` disables the flag
|
|
||||||
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
|
||||||
rootFlag.erase(0, literal_strlen("no-"));
|
|
||||||
} else {
|
|
||||||
// `-W<flag>` enables the flag
|
|
||||||
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for an `=` parameter to process as a parametric warning
|
|
||||||
// `-Wno-<flag>` and `-Wno-error=<flag>` 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<flag>=0` is equivalent to `-Wno-<flag>`
|
|
||||||
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=<flag>`, but also `-Werror=<flag>=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 <param> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printDiag(
|
void printDiag(
|
||||||
char const *fmt, va_list args, char const *type, char const *flagfmt, char const *flag
|
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, ...) {
|
void warning(WarningID id, char const *fmt, ...) {
|
||||||
char const *flag = warningFlags[id].name;
|
char const *flag = warnings.warningFlags[id].name;
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
||||||
switch (getWarningBehavior(id)) {
|
switch (warnings.getWarningBehavior(id)) {
|
||||||
case WarningBehavior::DISABLED:
|
case WarningBehavior::DISABLED:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
86
src/diagnostics.cpp
Normal file
86
src/diagnostics.cpp
Normal file
@@ -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<WarningState, std::optional<uint8_t>> getInitialWarningState(std::string &flag) {
|
||||||
|
// Check for prefixes that affect what the flag does
|
||||||
|
WarningState state;
|
||||||
|
if (flag.starts_with("error=")) {
|
||||||
|
// `-Werror=<flag>` 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=<flag>` 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-<flag>` disables the flag
|
||||||
|
state = {.state = WARNING_DISABLED, .error = WARNING_DEFAULT};
|
||||||
|
flag.erase(0, literal_strlen("no-"));
|
||||||
|
} else {
|
||||||
|
// `-W<flag>` enables the flag
|
||||||
|
state = {.state = WARNING_ENABLED, .error = WARNING_DEFAULT};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for an `=` parameter to process as a parametric warning
|
||||||
|
// `-Wno-<flag>` and `-Wno-error=<flag>` 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<flag>=0` is equivalent to `-Wno-<flag>`
|
||||||
|
if (param == 0) {
|
||||||
|
state.state = WARNING_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasParam) {
|
||||||
|
return {state, param};
|
||||||
|
}
|
||||||
|
return {state, std::nullopt};
|
||||||
|
}
|
||||||
@@ -135,7 +135,7 @@ void fatal(FileStackNode const *where, uint32_t lineNo, char const *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Short options
|
// 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
|
// Equivalent long options
|
||||||
// Please keep in the same order as short opts.
|
// Please keep in the same order as short opts.
|
||||||
|
|||||||
Reference in New Issue
Block a user