ref: 11a6a81169389fd0972921611cc204ec51aaaaee
parent: 0ce66009c1e56b2820c893d2916481b71690294e
author: Rangi <[email protected]>
date: Sun Oct 31 13:47:31 EDT 2021
Implement -Wtruncation=level (#931) * Implement -Wtruncation=level -Wtruncation=0 is the same as the current -Wno-truncation. -Wtruncation=2 is the same as the current -Wtruncation. -Wtruncation=1 is the new default; it's less strict, allowing N-bit values to be between -2**N and 2**N (exclusive). * Implement generic "parametrized warning" system * Test more `Wtruncation` variants Co-authored-by: ISSOtm <[email protected]>
--- a/include/asm/warning.h
+++ b/include/asm/warning.h
@@ -21,39 +21,48 @@
};
enum WarningID {
- WARNING_ASSERT, /* Assertions */
- WARNING_BACKWARDS_FOR, /* `for` loop with backwards range */
- WARNING_BUILTIN_ARG, /* Invalid args to builtins */
- WARNING_CHARMAP_REDEF, /* Charmap entry re-definition */
- WARNING_DIV, /* Division undefined behavior */
- WARNING_EMPTY_DATA_DIRECTIVE, /* `db`, `dw` or `dl` directive without data in ROM */
- WARNING_EMPTY_MACRO_ARG, /* Empty macro argument */
- WARNING_EMPTY_STRRPL, /* Empty second argument in `STRRPL` */
- WARNING_LARGE_CONSTANT, /* Constants too large */
- WARNING_LONG_STR, /* String too long for internal buffers */
- WARNING_MACRO_SHIFT, /* Shift past available arguments in macro */
- WARNING_NESTED_COMMENT, /* Comment-start delimiter in a block comment */
- WARNING_OBSOLETE, /* Obsolete things */
- WARNING_SHIFT, /* Shifting undefined behavior */
- WARNING_SHIFT_AMOUNT, /* Strange shift amount */
- WARNING_TRUNCATION, /* Implicit truncation loses some bits */
- WARNING_USER, /* User warnings */
+ WARNING_ASSERT, // Assertions
+ WARNING_BACKWARDS_FOR, // `for` loop with backwards range
+ WARNING_BUILTIN_ARG, // Invalid args to builtins
+ WARNING_CHARMAP_REDEF, // Charmap entry re-definition
+ WARNING_DIV, // Division undefined behavior
+ WARNING_EMPTY_DATA_DIRECTIVE, // `db`, `dw` or `dl` directive without data in ROM
+ WARNING_EMPTY_MACRO_ARG, // Empty macro argument
+ WARNING_EMPTY_STRRPL, // Empty second argument in `STRRPL`
+ WARNING_LARGE_CONSTANT, // Constants too large
+ WARNING_LONG_STR, // String too long for internal buffers
+ WARNING_MACRO_SHIFT, // Shift past available arguments in macro
+ WARNING_NESTED_COMMENT, // Comment-start delimiter in a block comment
+ WARNING_OBSOLETE, // Obsolete things
+ WARNING_SHIFT, // Shifting undefined behavior
+ WARNING_SHIFT_AMOUNT, // Strange shift amount
+ WARNING_USER, // User warnings
- NB_WARNINGS,
+ NB_PLAIN_WARNINGS,
- /* Warnings past this point are "meta" warnings */
- WARNING_ALL = NB_WARNINGS,
+ // Warnings past this point are "parametric" warnings, only mapping to a single flag
+#define PARAM_WARNINGS_START NB_PLAIN_WARNINGS
+ // Implicit truncation loses some bits
+ WARNING_TRUNCATION_1 = PARAM_WARNINGS_START,
+ WARNING_TRUNCATION_2,
+
+ NB_PLAIN_AND_PARAM_WARNINGS,
+#define NB_PARAM_WARNINGS (NB_PLAIN_AND_PARAM_WARNINGS - PARAM_WARNINGS_START)
+
+ // Warnings past this point are "meta" warnings
+#define META_WARNINGS_START NB_PLAIN_AND_PARAM_WARNINGS
+ WARNING_ALL = META_WARNINGS_START,
WARNING_EXTRA,
WARNING_EVERYTHING,
- NB_WARNINGS_ALL
-#define NB_META_WARNINGS (NB_WARNINGS_ALL - NB_WARNINGS)
+ NB_WARNINGS,
+#define NB_META_WARNINGS (NB_WARNINGS - META_WARNINGS_START)
};
-extern enum WarningState warningStates[NB_WARNINGS];
+extern enum WarningState warningStates[NB_PLAIN_AND_PARAM_WARNINGS];
extern bool warningsAreErrors;
-void processWarningFlag(char const *flag);
+void processWarningFlag(char *flag);
/*
* Used to warn the user about problems that don't prevent the generation of
--- a/src/asm/opt.c
+++ b/src/asm/opt.c
@@ -17,7 +17,8 @@
bool haltnop;
bool optimizeLoads;
bool warningsAreErrors;
- enum WarningState warningStates[NB_WARNINGS];
+ // Don't be confused: we use the size of the **global variable** `warningStates`!
+ enum WarningState warningStates[sizeof(warningStates)];
struct OptStackEntry *next;
};
@@ -48,7 +49,7 @@
optimizeLoads = optimize;
}
-void opt_W(char const *flag)
+void opt_W(char *flag)
{
processWarningFlag(flag);
}
--- a/src/asm/rgbasm.1
+++ b/src/asm/rgbasm.1
@@ -248,10 +248,20 @@
Use a division by 2**N instead.
.It Fl Wshift-amount
Warn when a shift's operand is negative or greater than 32.
-.It Fl Wno-truncation
+.It Fl Wtruncation=
Warn when an implicit truncation (for example,
-.Ic db )
-loses some bits.
+.Ic db
+to an 8-bit value) loses some bits.
+.Fl Wtruncation=0
+or
+.Fl Wno-truncation
+disables this warning.
+.Fl Wtruncation=1
+warns when an N-bit value's absolute value is 2**N or greater.
+.Fl Wtruncation=2
+or just
+.Fl Wtruncation
+also warns when an N-bit value is below -2**(N-1), which will not fit in two's complement encoding.
.It Fl Wno-user
Warn when the
.Ic WARN
--- a/src/asm/rpn.c
+++ b/src/asm/rpn.c
@@ -284,8 +284,10 @@
if (rpn_isKnown(expr)) {
int32_t val = expr->val;
- if (val < -(1 << (n - 1)) || val >= 1 << n)
- warning(WARNING_TRUNCATION, "Expression must be %u-bit\n", n);
+ if (val <= -(1 << n) || val >= 1 << n)
+ warning(WARNING_TRUNCATION_1, "Expression must be %u-bit\n", n);
+ else if (val < -(1 << (n - 1)))
+ warning(WARNING_TRUNCATION_2, "Expression must be %u-bit\n", n);
}
}
--- a/src/asm/warning.c
+++ b/src/asm/warning.c
@@ -6,6 +6,7 @@
* SPDX-License-Identifier: MIT
*/
+#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
@@ -21,7 +22,7 @@
unsigned int nbErrors = 0;
-static enum WarningState const defaultWarnings[NB_WARNINGS] = {
+static const enum WarningState defaultWarnings[ARRAY_SIZE(warningStates)] = {
[WARNING_ASSERT] = WARNING_ENABLED,
[WARNING_BACKWARDS_FOR] = WARNING_DISABLED,
[WARNING_BUILTIN_ARG] = WARNING_DISABLED,
@@ -37,11 +38,13 @@
[WARNING_OBSOLETE] = WARNING_ENABLED,
[WARNING_SHIFT] = WARNING_DISABLED,
[WARNING_SHIFT_AMOUNT] = WARNING_DISABLED,
- [WARNING_TRUNCATION] = WARNING_ENABLED,
[WARNING_USER] = WARNING_ENABLED,
+
+ [WARNING_TRUNCATION_1] = WARNING_ENABLED,
+ [WARNING_TRUNCATION_2] = WARNING_DISABLED,
};
-enum WarningState warningStates[NB_WARNINGS];
+enum WarningState warningStates[ARRAY_SIZE(warningStates)];
bool warningsAreErrors; /* Set if `-Werror` was specified */
@@ -64,7 +67,7 @@
return state;
}
-static char const *warningFlags[NB_WARNINGS_ALL] = {
+static const char * const warningFlags[NB_WARNINGS] = {
"assert",
"backwards-for",
"builtin-args",
@@ -80,15 +83,63 @@
"obsolete",
"shift",
"shift-amount",
- "truncation",
"user",
+ // Parametric warnings
+ "truncation",
+ "truncation",
+
/* Meta warnings */
"all",
"extra",
- "everything" /* Especially useful for testing */
+ "everything", /* Especially useful for testing */
};
+static const struct {
+ char const *name;
+ uint8_t nbLevels;
+ uint8_t defaultLevel;
+} paramWarnings[] = {
+ { "truncation", 2, 2 },
+};
+
+static bool tryProcessParamWarning(char const *flag, uint8_t param, enum WarningState state)
+{
+ enum WarningID baseID = PARAM_WARNINGS_START;
+
+ for (size_t i = 0; i < ARRAY_SIZE(paramWarnings); i++) {
+ uint8_t maxParam = paramWarnings[i].nbLevels;
+
+ if (!strcmp(paramWarnings[i].name, flag)) { // 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 == 0 && state != WARNING_DISABLED) {
+ param = paramWarnings[i].defaultLevel;
+ } else if (param > maxParam) {
+ if (param != 255) // Don't warn if already capped
+ warnx("Got parameter %" PRIu8
+ " for warning flag \"%s\", but the maximum is %"
+ PRIu8 "; capping.\n",
+ param, flag, maxParam);
+ param = maxParam;
+ }
+
+ // Set the first <param> to enabled/error, and disable the rest
+ for (uint8_t ofs = 0; ofs < maxParam; ofs++) {
+ warningStates[baseID + ofs] =
+ ofs < param ? state : WARNING_DISABLED;
+ }
+ return true;
+ }
+
+ baseID += maxParam;
+ }
+ return false;
+}
+
enum MetaWarningCommand {
META_WARNING_DONE = NB_WARNINGS
};
@@ -102,6 +153,8 @@
WARNING_EMPTY_STRRPL,
WARNING_LARGE_CONSTANT,
WARNING_LONG_STR,
+ WARNING_NESTED_COMMENT,
+ WARNING_OBSOLETE,
META_WARNING_DONE
};
@@ -110,6 +163,9 @@
WARNING_EMPTY_MACRO_ARG,
WARNING_MACRO_SHIFT,
WARNING_NESTED_COMMENT,
+ WARNING_OBSOLETE,
+ WARNING_TRUNCATION_1,
+ WARNING_TRUNCATION_2,
META_WARNING_DONE
};
@@ -128,7 +184,8 @@
WARNING_OBSOLETE,
WARNING_SHIFT,
WARNING_SHIFT_AMOUNT,
- /* WARNING_TRUNCATION, */
+ WARNING_TRUNCATION_1,
+ WARNING_TRUNCATION_2,
/* WARNING_USER, */
META_WARNING_DONE
};
@@ -139,12 +196,12 @@
_weverythingCommands
};
-void processWarningFlag(char const *flag)
+void processWarningFlag(char *flag)
{
static bool setError = false;
/* First, try to match against a "meta" warning */
- for (enum WarningID id = NB_WARNINGS; id < NB_WARNINGS_ALL; id++) {
+ for (enum WarningID id = META_WARNINGS_START; id < NB_WARNINGS; id++) {
/* TODO: improve the matching performance? */
if (!strcmp(flag, warningFlags[id])) {
/* We got a match! */
@@ -152,7 +209,7 @@
errx(1, "Cannot make meta warning \"%s\" into an error",
flag);
- uint8_t const *ptr = metaWarningCommands[id - NB_WARNINGS];
+ uint8_t const *ptr = metaWarningCommands[id - META_WARNINGS_START];
for (;;) {
if (*ptr == META_WARNING_DONE)
@@ -168,7 +225,7 @@
/* If it's not a meta warning, specially check against `-Werror` */
if (!strncmp(flag, "error", strlen("error"))) {
- char const *errorFlag = flag + strlen("error");
+ char *errorFlag = flag + strlen("error");
switch (*errorFlag) {
case '\0':
@@ -189,14 +246,60 @@
/* Well, it's either a normal warning or a mistake */
- /* Check if this is a negation */
- bool isNegation = !strncmp(flag, "no-", strlen("no-")) && !setError;
- char const *rootFlag = isNegation ? flag + strlen("no-") : flag;
enum WarningState state = setError ? WARNING_ERROR :
- isNegation ? WARNING_DISABLED : WARNING_ENABLED;
+ /* Not an error, then check if this is a negation */
+ strncmp(flag, "no-", strlen("no-")) ? WARNING_ENABLED
+ : WARNING_DISABLED;
+ char const *rootFlag = state == WARNING_DISABLED ? flag + strlen("no-") : flag;
+ // Is this a "parametric" warning?
+ if (state != WARNING_DISABLED) { // The `no-` form cannot be parametrized
+ // First, check if there is an "equals" sign followed by a decimal number
+ char *equals = strchr(rootFlag, '=');
+
+ if (equals && equals[1] != '\0') { // Ignore an equal sign at the very end as well
+ // Is the rest of the string a decimal number?
+ // We want to avoid `strtoul`'s whitespace and sign, so we parse manually
+ uint8_t param = 0;
+ char const *ptr = 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\n",
+ flag);
+ warned = true; // Only warn once, cap always
+ param = 255;
+ continue;
+ }
+ param = param * 10 + (*ptr - '0');
+
+ ptr++;
+ } while (*ptr);
+
+ // If we managed to the end of the string, check that the warning indeed
+ // accepts a parameter
+ if (*ptr == '\0') {
+ if (setError && param == 0) {
+ warnx("Ignoring nonsensical warning flag \"%s\"\n", flag);
+ return;
+ }
+ *equals = '\0'; // Truncate the param at the '='
+ if (tryProcessParamWarning(rootFlag, param,
+ param == 0 ? WARNING_DISABLED : state))
+ return;
+ }
+ }
+ }
+
/* Try to match the flag against a "normal" flag */
- for (enum WarningID id = 0; id < NB_WARNINGS; id++) {
+ for (enum WarningID id = 0; id < NB_PLAIN_WARNINGS; id++) {
if (!strcmp(rootFlag, warningFlags[id])) {
/* We got a match! */
warningStates[id] = state;
@@ -203,6 +306,11 @@
return;
}
}
+
+ // Lastly, this might be a "parametric" warning without an equals sign
+ // If it is, treat the param as 1 if enabling, or 0 if disabling
+ if (tryProcessParamWarning(rootFlag, 0, state))
+ return;
warnx("Unknown warning `%s`", flag);
}
--- /dev/null
+++ b/test/asm/warn-truncation.asm
@@ -1,0 +1,38 @@
+FOO_F EQU 5
+BAR_F EQU 7
+
+
+SECTION "RAM", WRAMX[$d500]
+
+wLabel::
+
+
+SECTION "ROM", ROM0
+
+MACRO try
+ OPT \1
+ ; no warning
+ db 1 << FOO_F
+ db $ff ^ (1 << FOO_F)
+ db ~(1 << FOO_F)
+ db 1 << BAR_F
+ db $ff ^ (1 << BAR_F)
+ dw wLabel
+ dw $10000 - wLabel
+ ; warn at level 1
+ db 1 << 8
+ db ~(1 << 8)
+ dw $1d200
+ dw -$1d200
+ ; warn at level 2
+ db ~(1 << BAR_F)
+ dw -wLabel
+ENDM
+
+ try Wno-truncation
+ try Wtruncation
+ try Wtruncation=0
+ try Wtruncation=1
+ try Wtruncation=2
+ try Werror=truncation=1
+ try Werror=truncation=2
--- /dev/null
+++ b/test/asm/warn-truncation.err
@@ -1,0 +1,52 @@
+warning: warn-truncation.asm(33) -> warn-truncation.asm::try(23): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(33) -> warn-truncation.asm::try(24): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(33) -> warn-truncation.asm::try(25): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(33) -> warn-truncation.asm::try(26): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(33) -> warn-truncation.asm::try(28): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(33) -> warn-truncation.asm::try(29): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(35) -> warn-truncation.asm::try(23): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(35) -> warn-truncation.asm::try(24): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(35) -> warn-truncation.asm::try(25): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(35) -> warn-truncation.asm::try(26): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(36) -> warn-truncation.asm::try(23): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(36) -> warn-truncation.asm::try(24): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(36) -> warn-truncation.asm::try(25): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(36) -> warn-truncation.asm::try(26): [-Wtruncation]
+ Expression must be 16-bit
+warning: warn-truncation.asm(36) -> warn-truncation.asm::try(28): [-Wtruncation]
+ Expression must be 8-bit
+warning: warn-truncation.asm(36) -> warn-truncation.asm::try(29): [-Wtruncation]
+ Expression must be 16-bit
+ERROR: warn-truncation.asm(37) -> warn-truncation.asm::try(23): [-Werror=truncation]
+ Expression must be 8-bit
+ERROR: warn-truncation.asm(37) -> warn-truncation.asm::try(24): [-Werror=truncation]
+ Expression must be 8-bit
+ERROR: warn-truncation.asm(37) -> warn-truncation.asm::try(25): [-Werror=truncation]
+ Expression must be 16-bit
+ERROR: warn-truncation.asm(37) -> warn-truncation.asm::try(26): [-Werror=truncation]
+ Expression must be 16-bit
+ERROR: warn-truncation.asm(38) -> warn-truncation.asm::try(23): [-Werror=truncation]
+ Expression must be 8-bit
+ERROR: warn-truncation.asm(38) -> warn-truncation.asm::try(24): [-Werror=truncation]
+ Expression must be 8-bit
+ERROR: warn-truncation.asm(38) -> warn-truncation.asm::try(25): [-Werror=truncation]
+ Expression must be 16-bit
+ERROR: warn-truncation.asm(38) -> warn-truncation.asm::try(26): [-Werror=truncation]
+ Expression must be 16-bit
+ERROR: warn-truncation.asm(38) -> warn-truncation.asm::try(28): [-Werror=truncation]
+ Expression must be 8-bit
+ERROR: warn-truncation.asm(38) -> warn-truncation.asm::try(29): [-Werror=truncation]
+ Expression must be 16-bit