Merge pull request #1913 from acqn/Diagnostics

[cc65] Improved diagnostics on div-by-zero/bitwise-shift in unevaluated context and overall
This commit is contained in:
Bob Andrews 2022-11-18 19:55:51 +01:00 committed by GitHub
commit 3513342445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 387 additions and 211 deletions

View File

@ -315,12 +315,41 @@ static void OpAssignBitField (const GenDesc* Gen, ExprDesc* Expr, const char* Op
} else if (Gen->Func == g_sub) {
g_dec (Flags | CF_CONST, Expr2.IVal);
} else {
if (Expr2.IVal == 0) {
/* Check for div by zero/mod by zero */
if (Gen->Func == g_div) {
Error ("Division by zero");
} else if (Gen->Func == g_mod) {
Error ("Modulo operation with zero");
if (!ED_IsUneval (Expr)) {
if (Expr2.IVal == 0) {
/* Check for div by zero/mod by zero */
if (Gen->Func == g_div) {
Warning ("Division by zero");
} else if (Gen->Func == g_mod) {
Warning ("Modulo operation with zero");
}
} else if (Gen->Func == g_asl || Gen->Func == g_asr) {
const Type* CalType = IntPromotion (Expr->Type);
unsigned ExprBits = BitSizeOf (CalType);
/* If the shift count is greater than or equal to the width of the
** promoted left operand, the behaviour is undefined according to
** the standard.
*/
if (Expr2.IVal < 0) {
Warning ("Negative shift count %ld treated as %u for %s",
Expr2.IVal,
(unsigned)Expr2.IVal & (ExprBits - 1),
GetBasicTypeName (CalType));
} else if (Expr2.IVal >= (long)ExprBits) {
Warning ("Shift count %ld >= width of %s treated as %u",
Expr2.IVal,
GetBasicTypeName (CalType),
(unsigned)Expr2.IVal & (ExprBits - 1));
}
/* Here we simply "wrap" the shift count around the width */
Expr2.IVal &= ExprBits - 1;
/* Additional check for bit-fields */
if (Expr2.IVal >= (long)Expr->Type->A.B.Width) {
Warning ("Shift count %ld >= width of bit-field", Expr2.IVal);
}
}
}
@ -495,12 +524,42 @@ static void OpAssignArithmetic (const GenDesc* Gen, ExprDesc* Expr, const char*
} else if (Gen->Func == g_sub) {
g_dec (Flags | CF_CONST, Expr2.IVal);
} else {
if (Expr2.IVal == 0) {
/* Check for div by zero/mod by zero */
if (Gen->Func == g_div) {
Error ("Division by zero");
} else if (Gen->Func == g_mod) {
Error ("Modulo operation with zero");
if (!ED_IsUneval (Expr)) {
if (Expr2.IVal == 0 && !ED_IsUneval (Expr)) {
/* Check for div by zero/mod by zero */
if (Gen->Func == g_div) {
Warning ("Division by zero");
} else if (Gen->Func == g_mod) {
Warning ("Modulo operation with zero");
}
} else if (Gen->Func == g_asl || Gen->Func == g_asr) {
const Type* CalType = IntPromotion (Expr->Type);
unsigned ExprBits = BitSizeOf (CalType);
/* If the shift count is greater than or equal to the width of the
** promoted left operand, the behaviour is undefined according to
** the standard.
*/
if (Expr2.IVal < 0) {
Warning ("Negative shift count %ld treated as %u for %s",
Expr2.IVal,
(unsigned)Expr2.IVal & (ExprBits - 1),
GetBasicTypeName (CalType));
} else if (Expr2.IVal >= (long)ExprBits) {
Warning ("Shift count %ld >= width of %s treated as %u",
Expr2.IVal,
GetBasicTypeName (CalType),
(unsigned)Expr2.IVal & (ExprBits - 1));
}
/* Here we simply "wrap" the shift count around the width */
Expr2.IVal &= ExprBits - 1;
/* Additional check for bit width */
if (Expr2.IVal >= (long)BitSizeOf (Expr->Type)) {
Warning ("Shift count %ld >= width of %s",
Expr2.IVal, GetBasicTypeName (Expr->Type));
}
}
}
Gen->Func (Flags | CF_CONST, Expr2.IVal);

View File

@ -203,6 +203,14 @@ unsigned long GetIntegerTypeMax (const Type* Type)
unsigned BitSizeOf (const Type* T)
/* Return the size (in bit-width) of a data type */
{
return IsTypeBitField (T) ? T->A.B.Width : CHAR_BITS * SizeOf (T);
}
unsigned SizeOf (const Type* T)
/* Compute size (in bytes) of object represented by type array */
{
@ -288,6 +296,17 @@ unsigned PSizeOf (const Type* T)
unsigned CheckedBitSizeOf (const Type* T)
/* Return the size (in bit-width) of a data type. If the size is zero, emit an
** error and return some valid size instead (so the rest of the compiler
** doesn't have to work with invalid sizes).
*/
{
return IsTypeBitField (T) ? T->A.B.Width : CHAR_BITS * CheckedSizeOf (T);
}
unsigned CheckedSizeOf (const Type* T)
/* Return the size (in bytes) of a data type. If the size is zero, emit an
** error and return some valid size instead (so the rest of the compiler

View File

@ -287,12 +287,21 @@ unsigned long GetIntegerTypeMax (const Type* Type);
** The type must have a known size.
*/
unsigned BitSizeOf (const Type* T);
/* Return the size (in bit-width) of a data type */
unsigned SizeOf (const Type* T);
/* Compute size (in bytes) of object represented by type array */
unsigned PSizeOf (const Type* T);
/* Compute size (in bytes) of pointee object */
unsigned CheckedBitSizeOf (const Type* T);
/* Return the size (in bit-width) of a data type. If the size is zero, emit an
** error and return some valid size instead (so the rest of the compiler
** doesn't have to work with invalid sizes).
*/
unsigned CheckedSizeOf (const Type* T);
/* Return the size (in bytes) of a data type. If the size is zero, emit an
** error and return some valid size instead (so the rest of the compiler

View File

@ -2177,6 +2177,10 @@ static void hie_internal (const GenDesc* Ops, /* List of generators */
/* Check for const operands */
if (lconst && rconst) {
/* Evaluate the result for operands */
unsigned long Val1 = Expr->IVal;
unsigned long Val2 = Expr2.IVal;
/* Both operands are constant, remove the generated code */
RemoveCode (&Mark1);
@ -2184,80 +2188,51 @@ static void hie_internal (const GenDesc* Ops, /* List of generators */
Expr->Type = ArithmeticConvert (Expr->Type, Expr2.Type);
/* Handle the op differently for signed and unsigned types */
if (IsSignSigned (Expr->Type)) {
/* Evaluate the result for signed operands */
signed long Val1 = Expr->IVal;
signed long Val2 = Expr2.IVal;
switch (Tok) {
case TOK_OR:
Expr->IVal = (Val1 | Val2);
break;
case TOK_XOR:
Expr->IVal = (Val1 ^ Val2);
break;
case TOK_AND:
Expr->IVal = (Val1 & Val2);
break;
case TOK_STAR:
Expr->IVal = (Val1 * Val2);
break;
case TOK_DIV:
if (Val2 == 0) {
Error ("Division by zero");
Expr->IVal = 0x7FFFFFFF;
switch (Tok) {
case TOK_OR:
Expr->IVal = (Val1 | Val2);
break;
case TOK_XOR:
Expr->IVal = (Val1 ^ Val2);
break;
case TOK_AND:
Expr->IVal = (Val1 & Val2);
break;
case TOK_STAR:
Expr->IVal = (Val1 * Val2);
break;
case TOK_DIV:
if (Val2 == 0) {
if (!ED_IsUneval (Expr)) {
Warning ("Division by zero");
}
Expr->IVal = 0xFFFFFFFF;
} else {
/* Handle signed and unsigned operands differently */
if (IsSignSigned (Expr->Type)) {
Expr->IVal = ((long)Val1 / (long)Val2);
} else {
Expr->IVal = (Val1 / Val2);
}
break;
case TOK_MOD:
if (Val2 == 0) {
Error ("Modulo operation with zero");
Expr->IVal = 0;
}
break;
case TOK_MOD:
if (Val2 == 0) {
if (!ED_IsUneval (Expr)) {
Warning ("Modulo operation with zero");
}
Expr->IVal = 0;
} else {
/* Handle signed and unsigned operands differently */
if (IsSignSigned (Expr->Type)) {
Expr->IVal = ((long)Val1 % (long)Val2);
} else {
Expr->IVal = (Val1 % Val2);
}
break;
default:
Internal ("hie_internal: got token 0x%X\n", Tok);
}
} else {
/* Evaluate the result for unsigned operands */
unsigned long Val1 = Expr->IVal;
unsigned long Val2 = Expr2.IVal;
switch (Tok) {
case TOK_OR:
Expr->IVal = (Val1 | Val2);
break;
case TOK_XOR:
Expr->IVal = (Val1 ^ Val2);
break;
case TOK_AND:
Expr->IVal = (Val1 & Val2);
break;
case TOK_STAR:
Expr->IVal = (Val1 * Val2);
break;
case TOK_DIV:
if (Val2 == 0) {
Error ("Division by zero");
Expr->IVal = 0xFFFFFFFF;
} else {
Expr->IVal = (Val1 / Val2);
}
break;
case TOK_MOD:
if (Val2 == 0) {
Error ("Modulo operation with zero");
Expr->IVal = 0;
} else {
Expr->IVal = (Val1 % Val2);
}
break;
default:
Internal ("hie_internal: got token 0x%X\n", Tok);
}
}
break;
default:
Internal ("hie_internal: got token 0x%X\n", Tok);
}
/* Limit the calculated value to the range of its type */
@ -2314,10 +2289,12 @@ static void hie_internal (const GenDesc* Ops, /* List of generators */
/* Second value is constant - check for div */
type |= CF_CONST;
rtype |= CF_CONST;
if (Tok == TOK_DIV && Expr2.IVal == 0) {
Error ("Division by zero");
} else if (Tok == TOK_MOD && Expr2.IVal == 0) {
Error ("Modulo operation with zero");
if (Expr2.IVal == 0 && !ED_IsUneval (Expr)) {
if (Tok == TOK_DIV) {
Warning ("Division by zero");
} else if (Tok == TOK_MOD) {
Warning ("Modulo operation with zero");
}
}
if ((Gen->Flags & GEN_NOPUSH) != 0) {
RemoveCode (&Mark2);
@ -3913,17 +3890,6 @@ static void hieQuest (ExprDesc* Expr)
ED_Init (&Expr3);
Expr3.Flags = Flags;
NextToken ();
/* Convert non-integer constant to boolean constant, so that we may just
** check it in the same way.
*/
if (ED_IsConstTrue (Expr)) {
ED_MakeConstBool (Expr, 1);
} else if (ED_IsConstFalse (Expr)) {
ED_MakeConstBool (Expr, 0);
}
if (!ConstantCond) {
/* Condition codes not set, request a test */
ED_RequireTest (Expr);
@ -3935,6 +3901,15 @@ static void hieQuest (ExprDesc* Expr)
FalseLab = GetLocalLabel ();
g_falsejump (CF_NONE, FalseLab);
} else {
/* Convert non-integer constant to boolean constant, so that we
** may just check it in an easier way later.
*/
if (ED_IsConstTrue (Expr)) {
ED_MakeConstBool (Expr, 1);
} else if (ED_IsConstFalse (Expr)) {
ED_MakeConstBool (Expr, 0);
}
/* Constant boolean subexpression could still have deferred inc/dec
** operations, so just flush their side-effects at this sequence point.
*/
@ -3943,9 +3918,18 @@ static void hieQuest (ExprDesc* Expr)
if (Expr->IVal == 0) {
/* Remember the current code position */
GetCodePos (&SkippedBranch);
/* Expr2 is unevaluated when the condition is false */
Expr2.Flags |= E_EVAL_UNEVAL;
} else {
/* Expr3 is unevaluated when the condition is true */
Expr3.Flags |= E_EVAL_UNEVAL;
}
}
/* Skip the question mark */
NextToken ();
/* Parse second expression. Remember for later if it is a NULL pointer
** expression, then load it into the primary.
*/
@ -3977,26 +3961,22 @@ static void hieQuest (ExprDesc* Expr)
/* Jump around the evaluation of the third expression */
TrueLab = GetLocalLabel ();
ConsumeColon ();
g_jump (TrueLab);
/* Jump here if the first expression was false */
g_defcodelabel (FalseLab);
} else {
if (Expr->IVal == 0) {
/* Expr2 is unevaluated when the condition is false */
Expr2.Flags |= E_EVAL_UNEVAL;
/* Remove the load code of Expr2 */
RemoveCode (&SkippedBranch);
} else {
/* Remember the current code position */
GetCodePos (&SkippedBranch);
}
ConsumeColon();
}
ConsumeColon ();
/* Parse third expression. Remember for later if it is a NULL pointer
** expression, then load it into the primary.
*/
@ -4022,9 +4002,6 @@ static void hieQuest (ExprDesc* Expr)
Expr3.Type = PtrConversion (Expr3.Type);
if (ConstantCond && Expr->IVal != 0) {
/* Expr3 is unevaluated when the condition is true */
Expr3.Flags |= E_EVAL_UNEVAL;
/* Remove the load code of Expr3 */
RemoveCode (&SkippedBranch);
}

View File

@ -305,97 +305,60 @@ static void PPhie_internal (const token_t* Ops, /* List of generators */
if (PPEvaluationEnabled && !PPEvaluationFailed) {
/* Evaluate the result for operands */
unsigned long Val1 = Expr->IVal;
unsigned long Val2 = Rhs.IVal;
/* If either side is unsigned, the result is unsigned */
Expr->Flags |= Rhs.Flags & PPEXPR_UNSIGNED;
/* Handle the op differently for signed and unsigned integers */
if ((Expr->Flags & PPEXPR_UNSIGNED) == 0) {
/* Evaluate the result for signed operands */
signed long Val1 = Expr->IVal;
signed long Val2 = Rhs.IVal;
switch (Tok) {
case TOK_OR:
Expr->IVal = (Val1 | Val2);
break;
case TOK_XOR:
Expr->IVal = (Val1 ^ Val2);
break;
case TOK_AND:
Expr->IVal = (Val1 & Val2);
break;
case TOK_PLUS:
Expr->IVal = (Val1 + Val2);
break;
case TOK_MINUS:
Expr->IVal = (Val1 - Val2);
break;
case TOK_MUL:
Expr->IVal = (Val1 * Val2);
break;
case TOK_DIV:
if (Val2 == 0) {
PPError ("Division by zero");
Expr->IVal = 0;
switch (Tok) {
case TOK_OR:
Expr->IVal = (Val1 | Val2);
break;
case TOK_XOR:
Expr->IVal = (Val1 ^ Val2);
break;
case TOK_AND:
Expr->IVal = (Val1 & Val2);
break;
case TOK_PLUS:
Expr->IVal = (Val1 + Val2);
break;
case TOK_MINUS:
Expr->IVal = (Val1 - Val2);
break;
case TOK_MUL:
Expr->IVal = (Val1 * Val2);
break;
case TOK_DIV:
if (Val2 == 0) {
PPError ("Division by zero");
Expr->IVal = 0;
} else {
/* Handle signed and unsigned operands differently */
if ((Expr->Flags & PPEXPR_UNSIGNED) == 0) {
Expr->IVal = ((long)Val1 / (long)Val2);
} else {
Expr->IVal = (Val1 / Val2);
}
break;
case TOK_MOD:
if (Val2 == 0) {
PPError ("Modulo operation with zero");
Expr->IVal = 0;
}
break;
case TOK_MOD:
if (Val2 == 0) {
PPError ("Modulo operation with zero");
Expr->IVal = 0;
} else {
/* Handle signed and unsigned operands differently */
if ((Expr->Flags & PPEXPR_UNSIGNED) == 0) {
Expr->IVal = ((long)Val1 % (long)Val2);
} else {
Expr->IVal = (Val1 % Val2);
}
break;
default:
Internal ("PPhie_internal: got token 0x%X\n", Tok);
}
} else {
/* Evaluate the result for unsigned operands */
unsigned long Val1 = Expr->IVal;
unsigned long Val2 = Rhs.IVal;
switch (Tok) {
case TOK_OR:
Expr->IVal = (Val1 | Val2);
break;
case TOK_XOR:
Expr->IVal = (Val1 ^ Val2);
break;
case TOK_AND:
Expr->IVal = (Val1 & Val2);
break;
case TOK_PLUS:
Expr->IVal = (Val1 + Val2);
break;
case TOK_MINUS:
Expr->IVal = (Val1 - Val2);
break;
case TOK_MUL:
Expr->IVal = (Val1 * Val2);
break;
case TOK_DIV:
if (Val2 == 0) {
PPError ("Division by zero");
Expr->IVal = 0;
} else {
Expr->IVal = (Val1 / Val2);
}
break;
case TOK_MOD:
if (Val2 == 0) {
PPError ("Modulo operation with zero");
Expr->IVal = 0;
} else {
Expr->IVal = (Val1 % Val2);
}
break;
default:
Internal ("PPhie_internal: got token 0x%X\n", Tok);
}
}
break;
default:
Internal ("PPhie_internal: got token 0x%X\n", Tok);
}
}
}

View File

@ -139,20 +139,34 @@ void ShiftExpr (struct ExprDesc* Expr)
/* Remove the code that pushes the rhs onto the stack. */
RemoveCode (&Mark2);
/* If the shift count is greater or equal than the bit count of
** the operand, the behaviour is undefined according to the
** standard.
/* If the shift count is greater than or equal to the width of the
** promoted left operand, the behaviour is undefined according to
** the standard.
*/
if (Expr2.IVal < 0) {
if (!ED_IsUneval (Expr)) {
if (Expr2.IVal < 0) {
Warning ("Negative shift count %ld treated as %u for %s",
Expr2.IVal,
(unsigned)Expr2.IVal & (ExprBits - 1),
GetBasicTypeName (ResultType));
} else if (Expr2.IVal >= (long) ExprBits) {
Warning ("Shift count %ld >= width of %s treated as %u",
Expr2.IVal,
GetBasicTypeName (ResultType),
(unsigned)Expr2.IVal & (ExprBits - 1));
}
}
Warning ("Shift count '%ld' is negative", Expr2.IVal);
Expr2.IVal &= ExprBits - 1;
} else if (Expr2.IVal >= (long) ExprBits) {
Warning ("Shift count '%ld' >= width of type", Expr2.IVal);
Expr2.IVal &= ExprBits - 1;
/* Here we simply "wrap" the shift count around the width */
Expr2.IVal &= ExprBits - 1;
/* Additional check for bit-fields */
if (IsTypeBitField (Expr->Type) &&
Tok == TOK_SHR &&
Expr2.IVal >= (long) Expr->Type->A.B.Width) {
if (!ED_IsUneval (Expr)) {
Warning ("Right-shift count %ld >= width of bit-field", Expr2.IVal);
}
}
/* If the shift count is zero, nothing happens. If the left hand

View File

@ -64,12 +64,6 @@ $(WORKDIR)/int-static-1888.$1.$2.prg: int-static-1888.c | $(WORKDIR)
$(if $(QUIET),echo misc/int-static-1888.$1.$2.prg)
$(NOT) $(CC65) -t sim$2 -$1 -o $$@ $$< $(NULLERR)
# should compile, but gives an error
$(WORKDIR)/bug1768.$1.$2.prg: bug1768.c | $(WORKDIR)
@echo "FIXME: " $$@ "currently does not compile."
$(if $(QUIET),echo misc/bug1768.$1.$2.prg)
$(NOT) $(CC65) -t sim$2 -$1 -o $$@ $$< $(NULLERR)
# should compile, but gives an error
$(WORKDIR)/bug760.$1.$2.prg: bug760.c | $(WORKDIR)
@echo "FIXME: " $$@ "currently does not compile."
@ -138,6 +132,14 @@ $(WORKDIR)/bug1265.$1.$2.prg: bug1265.c | $(WORKDIR)
$(LD65) -t sim$2 -o $$@ $$(@:.prg=.o) sim$2.lib $(NULLERR)
$(SIM65) $(SIM65FLAGS) $$@ $(NULLOUT) $(NULLERR)
# this one requires -Werror
$(WORKDIR)/bug1768.$1.$2.prg: bug1768.c | $(WORKDIR)
$(if $(QUIET),echo misc/bug1768.$1.$2.prg)
$(CC65) -Werror -t sim$2 -$1 -o $$(@:.prg=.s) $$< $(NULLERR)
$(CA65) -t sim$2 -o $$(@:.prg=.o) $$(@:.prg=.s) $(NULLERR)
$(LD65) -t sim$2 -o $$@ $$(@:.prg=.o) sim$2.lib $(NULLERR)
$(SIM65) $(SIM65FLAGS) $$@ $(NULLOUT) $(NULLERR)
# should compile, but then hangs in an endless loop
$(WORKDIR)/endless.$1.$2.prg: endless.c | $(WORKDIR)
$(if $(QUIET),echo misc/endless.$1.$2.prg)

View File

@ -1,14 +1,147 @@
/*
Copyright 2021-2022, The cc65 Authors
#include <stdlib.h>
This software is provided "as-is", without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
int a = 1 || (8 / 0);
int b = 0 && (8 % 0);
int c = 1 ? 42 : (0 % 0);
int d = 1 || a / 0;
int e = 0 && b % 0;
int f = 1 ? 42 : (a %= 0, b /= 0);
Permission is granted to anyone to use this software for any purpose,
including commercial applications; and, to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated, but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
/*
Test of operations in unevaluated context resulted from 'sizeof' and
short-circuited code-paths in AND, OR and conditional operations.
See also:
https://github.com/cc65/cc65/issues/1768#issuecomment-1175221466
*/
#include <stdio.h>
static int failures;
#define TEST(EXPR)\
{\
int acc = 0;\
acc += sizeof((EXPR), 0);\
acc += (0 && (EXPR));\
acc += (1 || (EXPR));\
acc += (0 ? (EXPR) : 0);\
acc += (1 ? 0 : (EXPR));\
if (acc == 0) {\
printf("acc = %d\n", acc);\
++failures;\
}\
}
/* Division by zero/modulo with zero */
void test_1(void)
{
int i;
int j;
TEST((i / 0) | (j % 0))
}
/* Division by zero/modulo with zero */
void test_2(void)
{
int i;
int j;
TEST((i /= 0) | (j %= 0))
}
/* Shift by too wide counts */
void test_3(void)
{
int i;
int j;
TEST((i << 32) | (j >> 32))
}
/* Shift by too wide counts */
void test_4(void)
{
int i;
int j;
TEST((i <<= 32) | (j >>= 32))
}
/* Shift by negative counts */
void test_5(void)
{
int i;
int j;
TEST((i << -1) | (j >> -1))
}
/* Shift by negative counts */
void test_6(void)
{
int i;
int j;
TEST((i <<= -1) | (j >>= -1))
}
/* Shift bit-fields */
void test_7(void)
{
struct S {
long i : 24; /* Will be promoted to 32-bit integer in calculation */
long j : 8; /* Will be promoted to 16-bit integer in calculation */
} s;
long k;
s.i = 1;
printf("%u\n", sizeof(s.i << 24));
s.i = 2;
k = s.i << 16;
if (k != 0x00020000L) {
printf("k = %ld, expected: %ld\n", k, 0x00020000L);
}
TEST(s.j >> 16)
}
/* Shift bit-fields */
void test_8(void)
{
struct S {
long i : 24; /* Will be promoted to 32-bit integer in calculation */
long j : 8; /* Will be promoted to 16-bit integer in calculation */
} s;
long k;
s.i = 3;
printf("%u\n", sizeof(s.i << 24));
s.i = 4;
k = s.i <<= 16;
if (k != 0x00040000L) {
printf("k = %ld, expected: %ld\n", k, 0x00040000L);
}
TEST(s.j >>= 8)
}
/* Do all tests */
int main(void)
{
return EXIT_SUCCESS;
test_1();
test_2();
test_3();
test_4();
test_5();
test_6();
test_7();
test_8();
printf("Failures: %d\n", failures);
return failures;
}