mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-20 10:12:06 +00:00
link: Suppress cascading errors.
This commit is contained in:
@@ -62,9 +62,17 @@ static int32_t asl(int32_t value, int32_t shiftamt)
|
|||||||
return (uint32_t)value << shiftamt;
|
return (uint32_t)value << shiftamt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This is an "empty"-type stack */
|
/*
|
||||||
|
* This is an "empty"-type stack. Apart from the actual values, we also remember
|
||||||
|
* whether the value is a placeholder inserted for error recovery. This allows
|
||||||
|
* us to avoid cascading errors.
|
||||||
|
*
|
||||||
|
* The best way to think about this is a stack of (value, errorFlag) pairs.
|
||||||
|
* They are only separated for reasons of memory efficiency.
|
||||||
|
*/
|
||||||
struct RPNStack {
|
struct RPNStack {
|
||||||
int32_t *buf;
|
int32_t *values;
|
||||||
|
bool *errorFlags;
|
||||||
size_t size;
|
size_t size;
|
||||||
size_t capacity;
|
size_t capacity;
|
||||||
} stack;
|
} stack;
|
||||||
@@ -72,8 +80,9 @@ struct RPNStack {
|
|||||||
static inline void initRPNStack(void)
|
static inline void initRPNStack(void)
|
||||||
{
|
{
|
||||||
stack.capacity = 64;
|
stack.capacity = 64;
|
||||||
stack.buf = malloc(sizeof(*stack.buf) * stack.capacity);
|
stack.values = malloc(sizeof(*stack.values) * stack.capacity);
|
||||||
if (!stack.buf)
|
stack.errorFlags = malloc(sizeof(*stack.errorFlags) * stack.capacity);
|
||||||
|
if (!stack.values || !stack.errorFlags)
|
||||||
err(1, "Failed to init RPN stack");
|
err(1, "Failed to init RPN stack");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,7 +91,7 @@ static inline void clearRPNStack(void)
|
|||||||
stack.size = 0;
|
stack.size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pushRPN(int32_t value)
|
static void pushRPN(int32_t value, bool comesFromError)
|
||||||
{
|
{
|
||||||
if (stack.size >= stack.capacity) {
|
if (stack.size >= stack.capacity) {
|
||||||
static const size_t increase_factor = 2;
|
static const size_t increase_factor = 2;
|
||||||
@@ -91,33 +100,42 @@ static void pushRPN(int32_t value)
|
|||||||
errx(1, "Overflow in RPN stack resize");
|
errx(1, "Overflow in RPN stack resize");
|
||||||
|
|
||||||
stack.capacity *= increase_factor;
|
stack.capacity *= increase_factor;
|
||||||
stack.buf =
|
stack.values =
|
||||||
realloc(stack.buf, sizeof(*stack.buf) * stack.capacity);
|
realloc(stack.values, sizeof(*stack.values) * stack.capacity);
|
||||||
|
stack.errorFlags =
|
||||||
|
realloc(stack.errorFlags, sizeof(*stack.errorFlags) * stack.capacity);
|
||||||
/*
|
/*
|
||||||
* Static analysis tools complain that the capacity might become
|
* Static analysis tools complain that the capacity might become
|
||||||
* zero due to overflow, but fail to realize that it's caught by
|
* zero due to overflow, but fail to realize that it's caught by
|
||||||
* the overflow check above. Hence the stringent check below.
|
* the overflow check above. Hence the stringent check below.
|
||||||
*/
|
*/
|
||||||
if (!stack.buf || !stack.capacity)
|
if (!stack.values || !stack.errorFlags || !stack.capacity)
|
||||||
err(1, "Failed to resize RPN stack");
|
err(1, "Failed to resize RPN stack");
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.buf[stack.size] = value;
|
stack.values[stack.size] = value;
|
||||||
|
stack.errorFlags[stack.size] = comesFromError;
|
||||||
stack.size++;
|
stack.size++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This flag tracks whether the RPN op that is currently being evaluated
|
||||||
|
// has popped any values with the error flag set.
|
||||||
|
static bool isError = false;
|
||||||
|
|
||||||
static int32_t popRPN(struct FileStackNode const *node, uint32_t lineNo)
|
static int32_t popRPN(struct FileStackNode const *node, uint32_t lineNo)
|
||||||
{
|
{
|
||||||
if (stack.size == 0)
|
if (stack.size == 0)
|
||||||
fatal(node, lineNo, "Internal error, RPN stack empty");
|
fatal(node, lineNo, "Internal error, RPN stack empty");
|
||||||
|
|
||||||
stack.size--;
|
stack.size--;
|
||||||
return stack.buf[stack.size];
|
isError |= stack.errorFlags[stack.size];
|
||||||
|
return stack.values[stack.size];
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void freeRPNStack(void)
|
static inline void freeRPNStack(void)
|
||||||
{
|
{
|
||||||
free(stack.buf);
|
free(stack.values);
|
||||||
|
free(stack.errorFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* RPN operators */
|
/* RPN operators */
|
||||||
@@ -149,6 +167,8 @@ static struct Symbol const *getSymbol(struct Symbol const * const *symbolList,
|
|||||||
* @param patch The patch to compute the value of
|
* @param patch The patch to compute the value of
|
||||||
* @param section The section the patch is contained in
|
* @param section The section the patch is contained in
|
||||||
* @return The patch's value
|
* @return The patch's value
|
||||||
|
* @return isError Set if an error occurred during evaluation, and further
|
||||||
|
* errors caused by the value should be suppressed.
|
||||||
*/
|
*/
|
||||||
static int32_t computeRPNExpr(struct Patch const *patch,
|
static int32_t computeRPNExpr(struct Patch const *patch,
|
||||||
struct Symbol const * const *fileSymbols)
|
struct Symbol const * const *fileSymbols)
|
||||||
@@ -166,6 +186,8 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
patch->src, patch->lineNo);
|
patch->src, patch->lineNo);
|
||||||
int32_t value;
|
int32_t value;
|
||||||
|
|
||||||
|
isError = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Friendly reminder:
|
* Friendly reminder:
|
||||||
* Be VERY careful with two `popRPN` in the same expression.
|
* Be VERY careful with two `popRPN` in the same expression.
|
||||||
@@ -191,7 +213,9 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
case RPN_DIV:
|
case RPN_DIV:
|
||||||
value = popRPN();
|
value = popRPN();
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
error(patch->src, patch->lineNo, "Division by 0");
|
if (!isError)
|
||||||
|
error(patch->src, patch->lineNo, "Division by 0");
|
||||||
|
isError = true;
|
||||||
popRPN();
|
popRPN();
|
||||||
value = INT32_MAX;
|
value = INT32_MAX;
|
||||||
} else {
|
} else {
|
||||||
@@ -201,7 +225,9 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
case RPN_MOD:
|
case RPN_MOD:
|
||||||
value = popRPN();
|
value = popRPN();
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
error(patch->src, patch->lineNo, "Modulo by 0");
|
if (!isError)
|
||||||
|
error(patch->src, patch->lineNo, "Modulo by 0");
|
||||||
|
isError = true;
|
||||||
popRPN();
|
popRPN();
|
||||||
value = 0;
|
value = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -280,11 +306,13 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"Requested BANK() of symbol \"%s\", which was not found",
|
"Requested BANK() of symbol \"%s\", which was not found",
|
||||||
fileSymbols[value]->name);
|
fileSymbols[value]->name);
|
||||||
|
isError = true;
|
||||||
value = 1;
|
value = 1;
|
||||||
} else if (!symbol->section) {
|
} else if (!symbol->section) {
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"Requested BANK() of non-label symbol \"%s\"",
|
"Requested BANK() of non-label symbol \"%s\"",
|
||||||
fileSymbols[value]->name);
|
fileSymbols[value]->name);
|
||||||
|
isError = true;
|
||||||
value = 1;
|
value = 1;
|
||||||
} else {
|
} else {
|
||||||
value = symbol->section->bank;
|
value = symbol->section->bank;
|
||||||
@@ -302,6 +330,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"Requested BANK() of section \"%s\", which was not found",
|
"Requested BANK() of section \"%s\", which was not found",
|
||||||
name);
|
name);
|
||||||
|
isError = true;
|
||||||
value = 1;
|
value = 1;
|
||||||
} else {
|
} else {
|
||||||
value = sect->bank;
|
value = sect->bank;
|
||||||
@@ -312,6 +341,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
if (!patch->pcSection) {
|
if (!patch->pcSection) {
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"PC has no bank outside a section");
|
"PC has no bank outside a section");
|
||||||
|
isError = true;
|
||||||
value = 1;
|
value = 1;
|
||||||
} else {
|
} else {
|
||||||
value = patch->pcSection->bank;
|
value = patch->pcSection->bank;
|
||||||
@@ -320,11 +350,13 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
|
|
||||||
case RPN_HRAM:
|
case RPN_HRAM:
|
||||||
value = popRPN();
|
value = popRPN();
|
||||||
if (value < 0
|
if (!isError && (value < 0
|
||||||
|| (value > 0xFF && value < 0xFF00)
|
|| (value > 0xFF && value < 0xFF00)
|
||||||
|| value > 0xFFFF)
|
|| value > 0xFFFF)) {
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"Value %" PRId32 " is not in HRAM range", value);
|
"Value %" PRId32 " is not in HRAM range", value);
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
value &= 0xFF;
|
value &= 0xFF;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -333,9 +365,12 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
/* Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
|
/* Acceptable values are 0x00, 0x08, 0x10, ..., 0x38
|
||||||
* They can be easily checked with a bitmask
|
* They can be easily checked with a bitmask
|
||||||
*/
|
*/
|
||||||
if (value & ~0x38)
|
if (value & ~0x38) {
|
||||||
error(patch->src, patch->lineNo,
|
if (!isError)
|
||||||
"Value %" PRId32 " is not a RST vector", value);
|
error(patch->src, patch->lineNo,
|
||||||
|
"Value %" PRId32 " is not a RST vector", value);
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
value |= 0xC7;
|
value |= 0xC7;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -357,6 +392,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"PC has no value outside a section");
|
"PC has no value outside a section");
|
||||||
value = 0;
|
value = 0;
|
||||||
|
isError = true;
|
||||||
} else {
|
} else {
|
||||||
value = patch->pcOffset + patch->pcSection->org;
|
value = patch->pcOffset + patch->pcSection->org;
|
||||||
}
|
}
|
||||||
@@ -366,6 +402,7 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
if (!symbol) {
|
if (!symbol) {
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"Unknown symbol \"%s\"", fileSymbols[value]->name);
|
"Unknown symbol \"%s\"", fileSymbols[value]->name);
|
||||||
|
isError = true;
|
||||||
} else {
|
} else {
|
||||||
value = symbol->value;
|
value = symbol->value;
|
||||||
/* Symbols attached to sections have offsets */
|
/* Symbols attached to sections have offsets */
|
||||||
@@ -376,13 +413,14 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pushRPN(value);
|
pushRPN(value, isError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stack.size > 1)
|
if (stack.size > 1)
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"RPN stack has %zu entries on exit, not 1", stack.size);
|
"RPN stack has %zu entries on exit, not 1", stack.size);
|
||||||
|
|
||||||
|
isError = false;
|
||||||
return popRPN();
|
return popRPN();
|
||||||
|
|
||||||
#undef popRPN
|
#undef popRPN
|
||||||
@@ -394,10 +432,12 @@ void patch_CheckAssertions(struct Assertion *assert)
|
|||||||
initRPNStack();
|
initRPNStack();
|
||||||
|
|
||||||
while (assert) {
|
while (assert) {
|
||||||
if (!computeRPNExpr(&assert->patch,
|
int32_t value = computeRPNExpr(&assert->patch,
|
||||||
(struct Symbol const * const *)
|
(struct Symbol const * const *)assert->fileSymbols);
|
||||||
assert->fileSymbols)) {
|
enum AssertionType type = assert->patch.type;
|
||||||
switch ((enum AssertionType)assert->patch.type) {
|
|
||||||
|
if (!isError && !value) {
|
||||||
|
switch (type) {
|
||||||
case ASSERT_FATAL:
|
case ASSERT_FATAL:
|
||||||
fatal(assert->patch.src, assert->patch.lineNo, "%s",
|
fatal(assert->patch.src, assert->patch.lineNo, "%s",
|
||||||
assert->message[0] ? assert->message
|
assert->message[0] ? assert->message
|
||||||
@@ -415,6 +455,11 @@ void patch_CheckAssertions(struct Assertion *assert)
|
|||||||
: "assert failure");
|
: "assert failure");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else if (isError && type == ASSERT_FATAL) {
|
||||||
|
fatal(assert->patch.src, assert->patch.lineNo,
|
||||||
|
"couldn't evaluate assertion%s%s",
|
||||||
|
assert->message[0] ? ": " : "",
|
||||||
|
assert->message);
|
||||||
}
|
}
|
||||||
struct Assertion *next = assert->next;
|
struct Assertion *next = assert->next;
|
||||||
|
|
||||||
@@ -450,7 +495,7 @@ static void applyFilePatches(struct Section *section, struct Section *dataSectio
|
|||||||
+ patch->pcOffset + 1;
|
+ patch->pcOffset + 1;
|
||||||
int16_t jumpOffset = value - address;
|
int16_t jumpOffset = value - address;
|
||||||
|
|
||||||
if (jumpOffset < -128 || jumpOffset > 127)
|
if (!isError && (jumpOffset < -128 || jumpOffset > 127))
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"jr target out of reach (expected -129 < %" PRId16 " < 128)",
|
"jr target out of reach (expected -129 < %" PRId16 " < 128)",
|
||||||
jumpOffset);
|
jumpOffset);
|
||||||
@@ -467,8 +512,8 @@ static void applyFilePatches(struct Section *section, struct Section *dataSectio
|
|||||||
[PATCHTYPE_LONG] = {4, INT32_MIN, INT32_MAX}
|
[PATCHTYPE_LONG] = {4, INT32_MIN, INT32_MAX}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (value < types[patch->type].min
|
if (!isError && (value < types[patch->type].min
|
||||||
|| value > types[patch->type].max)
|
|| value > types[patch->type].max))
|
||||||
error(patch->src, patch->lineNo,
|
error(patch->src, patch->lineNo,
|
||||||
"Value %#" PRIx32 "%s is not %u-bit",
|
"Value %#" PRIx32 "%s is not %u-bit",
|
||||||
value, value < 0 ? " (maybe negative?)" : "",
|
value, value < 0 ? " (maybe negative?)" : "",
|
||||||
|
|||||||
2
test/link/cascading-errors-fatal-assert.asm
Normal file
2
test/link/cascading-errors-fatal-assert.asm
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
assert FATAL, UnknownSymbol == 42
|
||||||
|
assert WeDontReachHere
|
||||||
3
test/link/cascading-errors-fatal-assert.out
Normal file
3
test/link/cascading-errors-fatal-assert.out
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
error: cascading-errors-fatal-assert.asm(1): Unknown symbol "UnknownSymbol"
|
||||||
|
fatal: cascading-errors-fatal-assert.asm(1): couldn't evaluate assertion
|
||||||
|
Linking aborted after 2 errors
|
||||||
19
test/link/cascading-errors.asm
Normal file
19
test/link/cascading-errors.asm
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
SECTION "zero", ROM0[$0]
|
||||||
|
Zero:
|
||||||
|
|
||||||
|
; Pin the section such that a jr to 0 is out of range
|
||||||
|
SECTION "test", ROM0[$1000]
|
||||||
|
;; XXX: the fallback value used is the index of the symbol (in the object file?)
|
||||||
|
;; Is this intended?
|
||||||
|
dw Bar
|
||||||
|
dw Foo / Bar
|
||||||
|
dw Foo / Zero
|
||||||
|
|
||||||
|
rst Foo
|
||||||
|
|
||||||
|
jr NonExist
|
||||||
|
|
||||||
|
ldh a, [hNonExist + $200]
|
||||||
|
|
||||||
|
assert Foo == 42
|
||||||
|
assert WARN, Bar == 42
|
||||||
11
test/link/cascading-errors.out
Normal file
11
test/link/cascading-errors.out
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
error: cascading-errors.asm(18): Unknown symbol "Foo"
|
||||||
|
error: cascading-errors.asm(19): Unknown symbol "Bar"
|
||||||
|
error: cascading-errors.asm(16): Unknown symbol "hNonExist"
|
||||||
|
error: cascading-errors.asm(14): Unknown symbol "NonExist"
|
||||||
|
error: cascading-errors.asm(12): Unknown symbol "Foo"
|
||||||
|
error: cascading-errors.asm(10): Unknown symbol "Foo"
|
||||||
|
error: cascading-errors.asm(10): Division by 0
|
||||||
|
error: cascading-errors.asm(9): Unknown symbol "Foo"
|
||||||
|
error: cascading-errors.asm(9): Unknown symbol "Bar"
|
||||||
|
error: cascading-errors.asm(8): Unknown symbol "Bar"
|
||||||
|
Linking failed with 10 errors
|
||||||
Reference in New Issue
Block a user