mirror of
https://github.com/gbdev/rgbds.git
synced 2025-11-21 18:52:07 +00:00
Merge pull request #473 from ISSOtm/shift_ub
Remove undefined behavior from shifts
This commit is contained in:
@@ -205,28 +205,52 @@ void constexpr_BinaryOp(struct ConstExpression *expr,
|
||||
result = value1 & value2;
|
||||
break;
|
||||
case T_OP_SHL:
|
||||
if (value1 < 0)
|
||||
warning(WARNING_SHIFT, "Left shift of negative value: %d",
|
||||
value1);
|
||||
|
||||
if (value2 < 0)
|
||||
fatalerror("Shift by negative value: %d",
|
||||
value2);
|
||||
else if (value2 >= 32)
|
||||
fatalerror("Shift by too big value: %d",
|
||||
value2);
|
||||
|
||||
result = (uint32_t)value1 << value2;
|
||||
break;
|
||||
case T_OP_SHR:
|
||||
if (value2 < 0)
|
||||
fatalerror("Shift by negative value: %d",
|
||||
value2);
|
||||
else if (value2 >= 32)
|
||||
fatalerror("Shift by too big value: %d",
|
||||
value2);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting %s by negative amount %d",
|
||||
op == T_OP_SHL ? "left" : "right",
|
||||
value2);
|
||||
if (op == T_OP_SHR) {
|
||||
value2 = -value2; /* Right shift == neg left */
|
||||
|
||||
result = value1 >> value2;
|
||||
if (value1 < 0)
|
||||
warning(WARNING_SHIFT, "Shifting negative value %d",
|
||||
value1);
|
||||
}
|
||||
|
||||
if (value2 >= 0) {
|
||||
// Shift left
|
||||
if (value2 >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %d",
|
||||
value2);
|
||||
result = 0;
|
||||
} else {
|
||||
/*
|
||||
* Use unsigned to force a bitwise shift
|
||||
* Casting back is OK because the types
|
||||
* implement two's complement behavior
|
||||
*/
|
||||
result = (uint32_t)value1 << value2;
|
||||
}
|
||||
} else {
|
||||
// Shift right
|
||||
value2 = -value2;
|
||||
if (value2 >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %d",
|
||||
value2);
|
||||
result = value1 < 0 ? -1 : 0;
|
||||
} else if (value1 >= 0) {
|
||||
result = value1 >> value2;
|
||||
} else {
|
||||
/*
|
||||
* The C standard leaves shifting right
|
||||
* negative values undefined, so use a
|
||||
* left shift manually sign-extended
|
||||
*/
|
||||
result = (uint32_t)value1 >> value2 |
|
||||
-((uint32_t)1 << (32 - value2));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case T_OP_MUL:
|
||||
result = (uint32_t)value1 * (uint32_t)value2;
|
||||
|
||||
@@ -409,22 +409,59 @@ void rpn_AND(struct Expression *expr, const struct Expression *src1,
|
||||
expr->nRPNPatchSize++;
|
||||
}
|
||||
|
||||
static int32_t shift(int32_t shiftee, int32_t amount)
|
||||
{
|
||||
if (shiftee < 0)
|
||||
warning(WARNING_SHIFT, "Shifting negative value %d", shiftee);
|
||||
|
||||
if (amount >= 0) {
|
||||
// Left shift
|
||||
if (amount >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by large amount %d",
|
||||
amount);
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Use unsigned to force a bitwise shift
|
||||
* Casting back is OK because the types implement two's
|
||||
* complement behavior
|
||||
*/
|
||||
return (uint32_t)shiftee << amount;
|
||||
}
|
||||
} else {
|
||||
// Right shift
|
||||
amount = -amount;
|
||||
if (amount >= 32) {
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by large amount %d",
|
||||
amount);
|
||||
return shiftee < 0 ? -1 : 0;
|
||||
|
||||
} else if (shiftee >= 0) {
|
||||
return shiftee >> amount;
|
||||
|
||||
} else {
|
||||
/*
|
||||
* The C standard leaves shifting right negative values
|
||||
* undefined, so use a left shift manually sign-extended
|
||||
*/
|
||||
return (uint32_t)shiftee >> amount
|
||||
| -((uint32_t)1 << (32 - amount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rpn_SHL(struct Expression *expr, const struct Expression *src1,
|
||||
const struct Expression *src2)
|
||||
{
|
||||
mergetwoexpressions(expr, src1, src2);
|
||||
|
||||
if (!expr->isReloc) {
|
||||
if (src1->nVal < 0)
|
||||
warning(WARNING_SHIFT, "Left shift of negative value: %d",
|
||||
src1->nVal);
|
||||
|
||||
if (src2->nVal < 0)
|
||||
fatalerror("Shift by negative value: %d", src2->nVal);
|
||||
else if (src2->nVal >= 32)
|
||||
fatalerror("Shift by too big value: %d", src2->nVal);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting left by negative value: %d",
|
||||
src2->nVal);
|
||||
|
||||
expr->nVal = ((uint32_t)src1->nVal << src2->nVal);
|
||||
expr->nVal = shift(src1->nVal, src2->nVal);
|
||||
}
|
||||
|
||||
pushbyte(expr, RPN_SHL);
|
||||
@@ -438,11 +475,10 @@ void rpn_SHR(struct Expression *expr, const struct Expression *src1,
|
||||
|
||||
if (!expr->isReloc) {
|
||||
if (src2->nVal < 0)
|
||||
fatalerror("Shift by negative value: %d", src2->nVal);
|
||||
else if (src2->nVal >= 32)
|
||||
fatalerror("Shift by too big value: %d", src2->nVal);
|
||||
warning(WARNING_SHIFT_AMOUNT, "Shifting right by negative value: %d",
|
||||
src2->nVal);
|
||||
|
||||
expr->nVal = (src1->nVal >> src2->nVal);
|
||||
expr->nVal = shift(src1->nVal, -src2->nVal);
|
||||
}
|
||||
|
||||
pushbyte(expr, RPN_SHR);
|
||||
|
||||
@@ -36,6 +36,7 @@ static enum WarningState const defaultWarnings[NB_WARNINGS] = {
|
||||
WARNING_DISABLED, /* Obsolete things */
|
||||
WARNING_DISABLED, /* Shifting undefined behavior */
|
||||
WARNING_ENABLED, /* User warnings */
|
||||
WARNING_DISABLED, /* Strange shift amount */
|
||||
};
|
||||
|
||||
static enum WarningState warningStates[NB_WARNINGS];
|
||||
@@ -70,6 +71,7 @@ static char const *warningFlags[NB_WARNINGS_ALL] = {
|
||||
"obsolete",
|
||||
"shift",
|
||||
"user",
|
||||
"shift-amount",
|
||||
|
||||
/* Meta warnings */
|
||||
"all",
|
||||
@@ -106,6 +108,7 @@ static uint8_t const _weverythingCommands[] = {
|
||||
WARNING_OBSOLETE,
|
||||
WARNING_SHIFT,
|
||||
WARNING_USER,
|
||||
WARNING_SHIFT_AMOUNT,
|
||||
META_WARNING_DONE
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,47 @@
|
||||
|
||||
#include "extern/err.h"
|
||||
|
||||
static int32_t asl(int32_t value, int32_t shiftamt); // Forward decl for below
|
||||
static int32_t asr(int32_t value, int32_t shiftamt)
|
||||
{
|
||||
uint32_t uvalue = value;
|
||||
|
||||
// Get the easy cases out of the way
|
||||
if (shiftamt == 0)
|
||||
return value;
|
||||
if (value == 0 || shiftamt <= -32)
|
||||
return 0;
|
||||
if (shiftamt > 31)
|
||||
return (value < 0) ? -1 : 0;
|
||||
if (shiftamt < 0)
|
||||
return asl(value, -shiftamt);
|
||||
if (value > 0)
|
||||
return uvalue >> shiftamt;
|
||||
|
||||
{
|
||||
// Calculate an OR mask for sign extension
|
||||
// 1->0x80000000, 2->0xC0000000, ..., 31->0xFFFFFFFE
|
||||
uint32_t shiftamt_high_bits = -((uint32_t)1 << (32 - shiftamt));
|
||||
|
||||
return (uvalue >> shiftamt) | shiftamt_high_bits;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t asl(int32_t value, int32_t shiftamt)
|
||||
{
|
||||
// Repeat the easy cases here to avoid INT_MIN funny business
|
||||
if (shiftamt == 0)
|
||||
return value;
|
||||
if (value == 0 || shiftamt >= 32)
|
||||
return 0;
|
||||
if (shiftamt < -31)
|
||||
return (value < 0) ? -1 : 0;
|
||||
if (shiftamt < 0)
|
||||
return asr(value, -shiftamt);
|
||||
|
||||
return (uint32_t)value << shiftamt;
|
||||
}
|
||||
|
||||
/* This is an "empty"-type stack */
|
||||
struct RPNStack {
|
||||
int32_t *buf;
|
||||
@@ -182,14 +223,13 @@ static int32_t computeRPNExpr(struct Patch const *patch,
|
||||
value = popRPN() <= value;
|
||||
break;
|
||||
|
||||
/* FIXME: sanitize shifts */
|
||||
case RPN_SHL:
|
||||
value = popRPN();
|
||||
value = popRPN() << value;
|
||||
value = asl(popRPN(), value);
|
||||
break;
|
||||
case RPN_SHR:
|
||||
value = popRPN();
|
||||
value = popRPN() >> value;
|
||||
value = asr(popRPN(), value);
|
||||
break;
|
||||
|
||||
case RPN_BANK_SYM:
|
||||
|
||||
Reference in New Issue
Block a user