Improved flow analysis in general and especially for "for" loops. Added more

tests.
This commit is contained in:
Kugel Fuhr
2025-07-20 09:07:31 +02:00
parent 8ac25376a0
commit 70c1bd5e3c
7 changed files with 192 additions and 38 deletions

View File

@@ -229,8 +229,10 @@ static int IfStatement (void)
g_defcodelabel (Label2);
}
/* Done */
return StmtFlags;
/* Done. We will never return "empty" for an if statement because of side
** effects when evaluating the condition.
*/
return StmtFlags & ~SF_EMPTY;
}
@@ -299,8 +301,10 @@ static int DoStatement (void)
}
}
/* "break" and "continue" are not relevant for the following code */
StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE);
/* "break" and "continue" are not relevant for the following code. "empty"
** is removed because of side effects when evaluating the condition.
*/
StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE | SF_EMPTY);
/* Done */
return StmtFlags;
@@ -393,8 +397,10 @@ static int WhileStatement (void)
*/
StmtFlags &= ~SF_MASK_UNREACH;
}
/* "break" and "continue" are not relevant for the following code */
StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE);
/* "break" and "continue" are not relevant for the following code. "empty"
** is removed because of side effects when evaluating the condition.
*/
StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE | SF_EMPTY);
/* Done */
return StmtFlags;
@@ -602,6 +608,11 @@ static int ForStatement (void)
/* Skip the closing paren */
ConsumeRParen ();
/* Output a warning if the loop body is never executed */
if (TestResult == TESTEXPR_FALSE) {
UnreachableCodeWarning ();
}
/* Loop body */
g_defcodelabel (BodyLabel);
StmtFlags = AnyStatement (&PendingToken, 0);
@@ -628,10 +639,30 @@ static int ForStatement (void)
/* Remove the loop from the loop stack */
DelLoop ();
/* If the condition is always true, any special statements are always
** executed. Otherwise we don't know.
/* Fix the flags for the loop. */
if (TestResult == TESTEXPR_TRUE) {
/* If the loop condition is always true, and we do not have a
** "break" statement, the loop won't terminate. So the only valid
** "unreach" flag is that for the endless loop. Otherwise - if there
** is a "break" statement, the code after the loop is reachable.
*/
StmtFlags &= ~SF_MASK_UNREACH;
if (!SF_Any_Break (StmtFlags)) {
StmtFlags |= (SF_OTHER | SF_ANY_OTHER);
}
} else {
/* If the loop condition is not always true, the code after the loop
** is always reachable.
*/
StmtFlags &= ~SF_MASK_UNREACH;
}
/* "break" and "continue" are not relevant for the following code. "empty"
** is removed because of side effects when evaluating the condition.
*/
return (TestResult == TESTEXPR_TRUE)? StmtFlags : SF_NONE;
StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE | SF_EMPTY);
/* Done */
return StmtFlags;
}
@@ -641,15 +672,23 @@ static int CompoundStatement (int* PendingToken, struct SwitchCtrl* Switch)
** function returns true if the last statement was a break or return.
*/
{
int OldStack;
unsigned OldBlockStackSize;
int StmtFlags;
/* Remember the stack at block entry */
int OldStack = StackPtr;
unsigned OldBlockStackSize = CollCount (&CurrentFunc->LocalsBlockStack);
/* Skip '{' */
NextToken ();
/* If the closing curly bracket follows we have an empty statement */
if (CurTok.Tok == TOK_RCURLY) {
NextToken ();
return SF_EMPTY;
}
/* Remember the stack at block entry */
OldStack = StackPtr;
OldBlockStackSize = CollCount (&CurrentFunc->LocalsBlockStack);
/* Enter a new lexical level */
EnterBlockLevel ();
@@ -742,36 +781,41 @@ int StatementBlock (struct SwitchCtrl* Switch)
LineInfo* LI1 = UseLineInfo (GetDiagnosticLI ());
int StmtFlags1 = AnyStatement (0, Switch);
int Unreachable = 0; /* True if code is unreachable */
int UnreachableWarning = 0; /* True if warning was output */
int Warning = 0; /* True if warning was output */
while (CurTok.Tok != TOK_RCURLY && CurTok.Tok != TOK_CEOF) {
LineInfo* LI2 = UseLineInfo (GetDiagnosticLI ());
int StmtFlags2 = AnyStatement (0, Switch);
if (!UnreachableWarning) {
/* If this statement is not already unreachable, check if the
** previous statement made it unreachable.
*/
if (!Unreachable) {
Unreachable = SF_Unreach (StmtFlags1);
}
/* If the previous statement made this one unreachable, but
** this one has a label, it is not unreachable.
*/
if (Unreachable && SF_Label (StmtFlags2)) {
Unreachable = 0;
}
/* If this statement is unreachable but not the empty
** statement, output a warning.
*/
if (Unreachable && !SF_Empty (StmtFlags2)) {
LIUnreachableCodeWarning (LI2);
UnreachableWarning = 1;
}
/* If this statement is not already unreachable, check if the
** previous statement made it unreachable.
*/
if (!Unreachable) {
Unreachable = SF_Unreach (StmtFlags1);
}
/* If the previous statement made this one unreachable, but this
** one has a label, it is not unreachable.
*/
if (Unreachable && SF_Label (StmtFlags2)) {
Unreachable = 0;
}
/* If this statement is unreachable but not the empty statement,
** and we didn't give a warning before, to that now
*/
if (Unreachable && !SF_Empty (StmtFlags2) && !Warning) {
LIUnreachableCodeWarning (LI2);
Warning = 1;
}
/* If the current statement wasn't unreachable update the flags */
if (!Unreachable) {
StmtFlags1 = SF_Any (StmtFlags1) | StmtFlags2;
}
/* Prepare for the next round */
if (LI1) {
ReleaseLineInfo (LI1);
}
LI1 = LI2;
StmtFlags1 = (StmtFlags1 & SF_MASK_ANY) | StmtFlags2;
}
if (LI1) {
ReleaseLineInfo (LI1);

View File

@@ -157,6 +157,11 @@ $(WORKDIR)/flow-do-01.$1.$2.prg: flow-do-01.c $(ISEQUAL) | $(WORKDIR)
$(CC65) -t sim$2 -$1 -o $$@ $$< $(NULLOUT) 2>$(WORKDIR)/flow-do-01.$1.$2.out
$(ISEQUAL) $(WORKDIR)/flow-do-01.$1.$2.out flow-do-01.ref
$(WORKDIR)/flow-for-01.$1.$2.prg: flow-for-01.c $(ISEQUAL) | $(WORKDIR)
$(if $(QUIET),echo misc/flow-for-01.$1.$2.prg)
$(CC65) -t sim$2 -$1 -o $$@ $$< $(NULLOUT) 2>$(WORKDIR)/flow-for-01.$1.$2.out
$(ISEQUAL) $(WORKDIR)/flow-for-01.$1.$2.out flow-for-01.ref
$(WORKDIR)/flow-if-01.$1.$2.prg: flow-if-01.c $(ISEQUAL) | $(WORKDIR)
$(if $(QUIET),echo misc/flow-if-01.$1.$2.prg)
$(CC65) -t sim$2 -$1 -o $$@ $$< $(NULLOUT) 2>$(WORKDIR)/flow-if-01.$1.$2.out

90
test/misc/flow-for-01.c Normal file
View File

@@ -0,0 +1,90 @@
int a;
static int f1(void)
{
for (;;) {
++a;
}
/* Unreachable */
a = 2;
return a;
}
static int f2(void)
{
for (;1;) {
++a;
}
/* Unreachable */
a = 2;
return a;
}
static int f3(void)
{
for (;;) {
++a;
if (a == 5) break;
}
/* Reachable */
a = 2;
return a;
}
static int f4(void)
{
for (;;) {
++a;
return a;
}
/* Unreachable */
a = 2;
}
static int f5(void)
{
for (;0;) {
/* Unreachable */
++a;
return a;
}
/* Reachable */
a = 2;
return 0;
}
static int f6(void)
{
for (;;) {
++a;
if (a == 4) goto L;
return a;
}
/* Reachable via L */
L: a = 2;
}
static int f7(void)
{
for (;0;) {
/* Unreachable but no warning */
}
a = 2;
return a;
}
static int f8(void)
{
for (;a;) {
return a;
}
/* Reachable */
a = 2;
return a;
}
int main(void)
{
return f1() + f2() + f3() + f4() + f5() + f6() + f7() + f8();
}

View File

@@ -0,0 +1,4 @@
flow-for-01.c:9: Warning: Unreachable code
flow-for-01.c:19: Warning: Unreachable code
flow-for-01.c:41: Warning: Unreachable code
flow-for-01.c:48: Warning: Unreachable code

View File

@@ -63,8 +63,18 @@ static int f6(void)
return a;
}
int main(void)
static int f7(void)
{
return f1() + f2() + f3() + f4() + f5() + f6();
while (a) {
return a;
}
/* Reachable */
a = 2;
return a;
}
int main(void)
{
return f1() + f2() + f3() + f4() + f5() + f6() + f7();
}

View File

@@ -22,7 +22,7 @@ static int f2(void)
/* Unreachable */
break;
}
/* Unreachable but no warning */
/* Unreachable */
a = 2;
return a;
}

View File

@@ -1,6 +1,7 @@
flow-while-02.c:8: Warning: Unreachable code
flow-while-02.c:12: Warning: Unreachable code
flow-while-02.c:23: Warning: Unreachable code
flow-while-02.c:26: Warning: Unreachable code
flow-while-02.c:38: Warning: Unreachable code
flow-while-02.c:50: Warning: Unreachable code
flow-while-02.c:66: Warning: Unreachable code