Merge pull request #2743 from kugelfuhr/kugelfuhr/include-guard

Do not read a file twice if it has an include guard
This commit is contained in:
Bob Andrews
2025-06-27 16:16:51 +02:00
committed by GitHub
4 changed files with 278 additions and 176 deletions

View File

@@ -40,6 +40,7 @@
/* common */
#include "check.h"
#include "coll.h"
#include "debugflag.h"
#include "filestat.h"
#include "fname.h"
#include "print.h"
@@ -77,17 +78,6 @@ char NextC = '\0';
/* Maximum count of nested includes */
#define MAX_INC_NESTING 16
/* Struct that describes an input file */
typedef struct IFile IFile;
struct IFile {
unsigned Index; /* File index */
unsigned Usage; /* Usage counter */
unsigned long Size; /* File size */
unsigned long MTime; /* Time of last modification */
InputType Type; /* Type of input file */
char Name[1]; /* Name of file (dynamically allocated) */
};
/* Struct that describes an active input file */
typedef struct AFile AFile;
struct AFile {
@@ -132,11 +122,13 @@ static IFile* NewIFile (const char* Name, InputType Type)
IFile* IF = (IFile*) xmalloc (sizeof (IFile) + Len);
/* Initialize the fields */
IF->Index = CollCount (&IFiles) + 1;
IF->Usage = 0;
IF->Size = 0;
IF->MTime = 0;
IF->Type = Type;
IF->Index = CollCount (&IFiles) + 1;
IF->Usage = 0;
IF->Size = 0;
IF->MTime = 0;
IF->Type = Type;
IF->GFlags = IG_NONE;
SB_Init (&IF->GuardMacro);
memcpy (IF->Name, Name, Len+1);
/* Insert the new structure into the IFile collection */
@@ -280,7 +272,7 @@ void OpenMainFile (const char* Name)
SetPPIfStack (&MainFile->IfStack);
/* Begin PP for this file */
PreprocessBegin ();
PreprocessBegin (IF);
/* Allocate the input line buffer */
Line = NewStrBuf ();
@@ -318,11 +310,21 @@ void OpenIncludeFile (const char* Name, InputType IT)
}
/* Search the list of all input files for this file. If we don't find
** it, create a new IFile object.
** it, create a new IFile object. If we do already know the file and it
** has an include guard, check for the include guard before opening the
** file.
*/
IF = FindFile (N);
if (IF == 0) {
IF = NewIFile (N, IT);
} else if ((IF->GFlags & IG_ISGUARDED) != 0 &&
IsMacro (SB_GetConstBuf (&IF->GuardMacro))) {
if (Debug) {
printf ("Include guard %s found for \"%s\" - won't include it again\n",
SB_GetConstBuf (&IF->GuardMacro),
Name);
}
return;
}
/* We don't need N any longer, since we may now use IF->Name */
@@ -346,7 +348,7 @@ void OpenIncludeFile (const char* Name, InputType IT)
SetPPIfStack (&AF->IfStack);
/* Begin PP for this file */
PreprocessBegin ();
PreprocessBegin (IF);
}
@@ -356,27 +358,24 @@ void CloseIncludeFile (void)
** NULL if this was the main file.
*/
{
AFile* Input;
/* Get the currently active input file and remove it from set of active
** files. CollPop will FAIL if the collection is empty so no need to
** check this here.
*/
AFile* Input = CollPop (&AFiles);
/* Get the number of active input files */
unsigned AFileCount = CollCount (&AFiles);
/* Determine the file that is active after closing this one. We never
** actually close the main file, since it is needed for errors found after
** compilation is completed.
*/
AFile* NextInput = (CollCount (&AFiles) > 0)? CollLast (&AFiles) : Input;
/* Must have an input file when called */
PRECONDITION (AFileCount > 0);
/* End preprocessor in this file */
PreprocessEnd ();
/* Get the current active input file */
Input = CollLast (&AFiles);
/* End preprocessing for the current input file */
PreprocessEnd (NextInput->Input);
/* Close the current input file (we're just reading so no error check) */
fclose (Input->F);
/* Delete the last active file from the active file collection */
--AFileCount;
CollDelete (&AFiles, AFileCount);
/* If we had added an extra search path for this AFile, remove it */
if (Input->SearchPath) {
PopSearchPath (UsrIncSearchPath);
@@ -385,10 +384,9 @@ void CloseIncludeFile (void)
/* Delete the active file structure */
FreeAFile (Input);
/* Use previous file with PP if it is not the main file */
if (AFileCount > 0) {
Input = CollLast (&AFiles);
SetPPIfStack (&Input->IfStack);
/* If we've switched files, use the if stack from the previous file */
if (Input != NextInput) {
SetPPIfStack (&NextInput->IfStack);
}
}

View File

@@ -65,6 +65,28 @@ typedef enum {
IT_USRINC = 0x04, /* User include file (using "") */
} InputType;
/* A bitmapped set of flags for include guard processing in the preprocessor */
typedef enum {
IG_NONE = 0x00,
IG_NEWFILE = 0x01, /* File processing started */
IG_ISGUARDED = 0x02, /* File contains an include guard */
IG_GUARDCLOSED = 0x04, /* Include guard was closed */
IG_COMPLETE = IG_ISGUARDED | IG_GUARDCLOSED,
} GuardFlags;
/* Struct that describes an input file */
typedef struct IFile IFile;
struct IFile {
unsigned Index; /* File index */
unsigned Usage; /* Usage counter */
unsigned long Size; /* File size */
unsigned long MTime; /* Time of last modification */
InputType Type; /* Type of input file */
GuardFlags GFlags; /* Flags for include guard processing */
StrBuf GuardMacro; /* Include guard macro name */
char Name[1]; /* Name of file (dynamically allocated) */
};
/* The current input line */
extern StrBuf* Line;

View File

@@ -93,6 +93,10 @@
#define IFCOND_SKIP 0x01U
#define IFCOND_ELSE 0x02U
#define IFCOND_NEEDTERM 0x04U
#define IFCOND_ISGUARD 0x08U
/* Current input file */
static IFile* CurInput = 0;
/* Current PP if stack */
static PPIfStack* PPStack;
@@ -2701,7 +2705,7 @@ Error_Handler:
static int PushIf (int Skip, int Invert, int Cond)
static int PushIf (int Skip, int Invert, int Cond, unsigned Flags)
/* Push a new if level onto the if stack */
{
/* Check for an overflow of the if stack */
@@ -2713,10 +2717,10 @@ static int PushIf (int Skip, int Invert, int Cond)
/* Push the #if condition */
++PPStack->Index;
if (Skip) {
PPStack->Stack[PPStack->Index] = IFCOND_SKIP | IFCOND_NEEDTERM;
PPStack->Stack[PPStack->Index] = IFCOND_SKIP | IFCOND_NEEDTERM | Flags;
return 1;
} else {
PPStack->Stack[PPStack->Index] = IFCOND_NONE | IFCOND_NEEDTERM;
PPStack->Stack[PPStack->Index] = IFCOND_NONE | IFCOND_NEEDTERM | Flags;
return (Invert ^ Cond);
}
}
@@ -2794,29 +2798,40 @@ static int DoIf (int Skip)
}
/* Set the #if condition according to the expression result */
return PushIf (Skip, 1, Expr.IVal != 0);
return PushIf (Skip, 1, Expr.IVal != 0, IFCOND_NONE);
}
static int DoIfDef (int skip, int flag)
/* Process #ifdef if flag == 1, or #ifndef if flag == 0. */
static int DoIfDef (int Skip, int Flag)
/* Process #ifdef if Flag == 1, or #ifndef if Flag == 0. */
{
int Value = 0;
int IsDef = 0;
unsigned GuardFlag = IFCOND_NONE;
if (!skip) {
if (!Skip) {
ident Ident;
SkipWhitespace (0);
if (MacName (Ident)) {
CheckForBadIdent (Ident, IS_Get (&Standard), 0);
Value = IsMacro (Ident);
IsDef = IsMacro (Ident);
/* Check for extra tokens */
CheckExtraTokens (flag ? "ifdef" : "ifndef");
CheckExtraTokens (Flag ? "ifdef" : "ifndef");
/* Check if this is an include guard. This is the case if we have
** a #ifndef directive at the start of a new file and the macro
** is not defined. If so, remember it.
*/
if (Flag == 0 && (CurInput->GFlags & IG_NEWFILE) != 0 && !IsDef) {
CurInput->GFlags |= IG_ISGUARDED;
SB_CopyStr (&CurInput->GuardMacro, Ident);
SB_Terminate (&CurInput->GuardMacro);
GuardFlag = IFCOND_ISGUARD;
}
}
}
return PushIf (skip, flag, Value);
return PushIf (Skip, Flag, IsDef, GuardFlag);
}
@@ -3070,18 +3085,31 @@ static int ParseDirectives (unsigned ModeFlags)
int PPSkip = 0;
ident Directive;
/* Skip white space at the beginning of the first line */
/* Skip white space at the beginning of the line */
int Whitespace = SkipWhitespace (0);
/* Check for stuff to skip */
while (CurC == '\0' || CurC == '#' || PPSkip) {
/* If a #ifndef that was assumed to be an include guard was closed
** recently, we may not have anything else following in the file
** besides whitespace otherwise the assumption was false and we don't
** actually have an include guard.
*/
if (CurC != '\0' && (CurInput->GFlags & IG_COMPLETE) == IG_COMPLETE) {
CurInput->GFlags &= ~IG_ISGUARDED;
}
/* Check for preprocessor lines lines */
if (CurC == '#') {
unsigned IfCond;
/* Skip the hash and following white space */
NextChar ();
SkipWhitespace (0);
if (CurC == '\0') {
/* Ignore the empty preprocessor directive */
CurInput->GFlags &= ~IG_NEWFILE;
continue;
}
if (!IsSym (Directive)) {
@@ -3089,145 +3117,175 @@ static int ParseDirectives (unsigned ModeFlags)
PPError ("Preprocessor directive expected");
}
ClearLine ();
} else {
switch (FindPPDirectiveType (Directive)) {
CurInput->GFlags &= ~IG_NEWFILE;
continue;
}
switch (FindPPDirectiveType (Directive)) {
case PPD_DEFINE:
if (!PPSkip) {
DoDefine ();
}
break;
case PPD_DEFINE:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
DoDefine ();
}
break;
case PPD_ELIF:
if (PPStack->Index >= 0) {
if ((PPStack->Stack[PPStack->Index] & IFCOND_ELSE) == 0) {
/* Handle as #else/#if combination */
if ((PPStack->Stack[PPStack->Index] & IFCOND_SKIP) == 0) {
PPSkip = !PPSkip;
}
PPStack->Stack[PPStack->Index] |= IFCOND_ELSE;
PPSkip = DoIf (PPSkip);
case PPD_ELIF:
CurInput->GFlags &= ~IG_NEWFILE;
if (PPStack->Index >= 0) {
unsigned char PPCond = PPStack->Stack[PPStack->Index];
if ((PPCond & IFCOND_ELSE) == 0) {
/* Handle as #else/#if combination */
if ((PPCond & IFCOND_SKIP) == 0) {
PPSkip = !PPSkip;
}
PPStack->Stack[PPStack->Index] |= IFCOND_ELSE;
PPSkip = DoIf (PPSkip);
/* #elif doesn't need a terminator */
PPStack->Stack[PPStack->Index] &= ~IFCOND_NEEDTERM;
} else {
PPError ("Duplicate #else/#elif");
/* #elif doesn't need a terminator */
PPStack->Stack[PPStack->Index] &= ~IFCOND_NEEDTERM;
/* An include guard cannot have a #elif */
if (PPCond & IFCOND_ISGUARD) {
CurInput->GFlags &= ~IG_ISGUARDED;
}
} else {
PPError ("Unexpected #elif");
PPError ("Duplicate #else/#elif");
}
break;
} else {
PPError ("Unexpected #elif");
}
break;
case PPD_ELSE:
if (PPStack->Index >= 0) {
if ((PPStack->Stack[PPStack->Index] & IFCOND_ELSE) == 0) {
if ((PPStack->Stack[PPStack->Index] & IFCOND_SKIP) == 0) {
PPSkip = !PPSkip;
}
PPStack->Stack[PPStack->Index] |= IFCOND_ELSE;
/* Check for extra tokens */
CheckExtraTokens ("else");
} else {
PPError ("Duplicate #else");
case PPD_ELSE:
CurInput->GFlags &= ~IG_NEWFILE;
if (PPStack->Index >= 0) {
unsigned char PPCond = PPStack->Stack[PPStack->Index];
if ((PPCond & IFCOND_ELSE) == 0) {
if ((PPCond & IFCOND_SKIP) == 0) {
PPSkip = !PPSkip;
}
} else {
PPError ("Unexpected '#else'");
}
break;
PPStack->Stack[PPStack->Index] |= IFCOND_ELSE;
case PPD_ENDIF:
if (PPStack->Index >= 0) {
/* Remove any clauses on top of stack that do not
** need a terminating #endif.
*/
while (PPStack->Index >= 0 &&
(PPStack->Stack[PPStack->Index] & IFCOND_NEEDTERM) == 0) {
--PPStack->Index;
/* An include guard cannot have a #else */
if (PPCond & IFCOND_ISGUARD) {
CurInput->GFlags &= ~IG_ISGUARDED;
}
/* Stack may not be empty here or something is wrong */
CHECK (PPStack->Index >= 0);
/* Remove the clause that needs a terminator */
PPSkip = (PPStack->Stack[PPStack->Index--] & IFCOND_SKIP) != 0;
/* Check for extra tokens */
CheckExtraTokens ("endif");
CheckExtraTokens ("else");
} else {
PPError ("Unexpected '#endif'");
PPError ("Duplicate #else");
}
break;
} else {
PPError ("Unexpected '#else'");
}
break;
case PPD_ERROR:
if (!PPSkip) {
DoError ();
case PPD_ENDIF:
CurInput->GFlags &= ~IG_NEWFILE;
if (PPStack->Index >= 0) {
/* Remove any clauses on top of stack that do not
** need a terminating #endif.
*/
while (PPStack->Index >= 0 &&
(PPStack->Stack[PPStack->Index] & IFCOND_NEEDTERM) == 0) {
--PPStack->Index;
}
break;
case PPD_IF:
PPSkip = DoIf (PPSkip);
break;
/* Stack may not be empty here or something is wrong */
CHECK (PPStack->Index >= 0);
case PPD_IFDEF:
PPSkip = DoIfDef (PPSkip, 1);
break;
case PPD_IFNDEF:
PPSkip = DoIfDef (PPSkip, 0);
break;
case PPD_INCLUDE:
if (!PPSkip) {
DoInclude ();
/* Remove the clause that needs a terminator */
IfCond = PPStack->Stack[PPStack->Index--];
PPSkip = (IfCond & IFCOND_SKIP) != 0;
if (IfCond & IFCOND_ISGUARD) {
CurInput->GFlags |= IG_GUARDCLOSED;
}
break;
case PPD_LINE:
if (!PPSkip) {
DoLine ();
}
break;
/* Check for extra tokens */
CheckExtraTokens ("endif");
} else {
PPError ("Unexpected '#endif'");
}
break;
case PPD_PRAGMA:
if (!PPSkip) {
if ((ModeFlags & MSM_IN_ARG_LIST) == 0) {
DoPragma ();
return Whitespace;
} else {
PPError ("Embedded #pragma directive within macro arguments is unsupported");
}
}
break;
case PPD_ERROR:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
DoError ();
}
break;
case PPD_UNDEF:
if (!PPSkip) {
DoUndef ();
}
break;
case PPD_IF:
CurInput->GFlags &= ~IG_NEWFILE;
PPSkip = DoIf (PPSkip);
break;
case PPD_WARNING:
/* #warning is a non standard extension */
if (IS_Get (&Standard) > STD_C99) {
if (!PPSkip) {
DoWarning ();
}
case PPD_IFDEF:
CurInput->GFlags &= ~IG_NEWFILE;
PPSkip = DoIfDef (PPSkip, 1);
break;
case PPD_IFNDEF:
PPSkip = DoIfDef (PPSkip, 0);
CurInput->GFlags &= ~IG_NEWFILE;
break;
case PPD_INCLUDE:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
DoInclude ();
}
break;
case PPD_LINE:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
DoLine ();
}
break;
case PPD_PRAGMA:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
if ((ModeFlags & MSM_IN_ARG_LIST) == 0) {
DoPragma ();
return Whitespace;
} else {
if (!PPSkip) {
PPError ("Preprocessor directive expected");
}
ClearLine ();
PPError ("Embedded #pragma directive within macro arguments is unsupported");
}
break;
}
break;
default:
case PPD_UNDEF:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
DoUndef ();
}
break;
case PPD_WARNING:
CurInput->GFlags &= ~IG_NEWFILE;
/* #warning is a non standard extension */
if (IS_Get (&Standard) > STD_C99) {
if (!PPSkip) {
DoWarning ();
}
} else {
if (!PPSkip) {
PPError ("Preprocessor directive expected");
}
ClearLine ();
}
}
}
break;
default:
CurInput->GFlags &= ~IG_NEWFILE;
if (!PPSkip) {
PPError ("Preprocessor directive expected");
}
ClearLine ();
}
}
if (NextLine () == 0) {
break;
@@ -3236,6 +3294,15 @@ static int ParseDirectives (unsigned ModeFlags)
Whitespace = SkipWhitespace (0) || Whitespace;
}
/* If a #ifndef that was assumed to be an include guard was closed
** recently, we may not have anything else following in the file
** besides whitespace otherwise the assumption was false and we don't
** actually have an include guard.
*/
if (CurC != '\0' && (CurInput->GFlags & IG_COMPLETE) == IG_COMPLETE) {
CurInput->GFlags &= ~IG_ISGUARDED;
}
return Whitespace;
}
@@ -3346,6 +3413,7 @@ void Preprocess (void)
OLine = PLine;
ParseDirectives (MSM_MULTILINE);
OLine = 0;
CurInput->GFlags &= ~IG_NEWFILE;
/* Add the source info to preprocessor output if needed */
AddPreLine (PLine);
@@ -3414,9 +3482,13 @@ void ContinueLine (void)
void PreprocessBegin (void)
/* Initialize preprocessor with current file */
void PreprocessBegin (IFile* Input)
/* Initialize the preprocessor for a new input file */
{
/* Remember the new input file and flag it as new */
CurInput = Input;
CurInput->GFlags |= IG_NEWFILE;
/* Reset #if depth */
PPStack->Index = -1;
@@ -3429,9 +3501,14 @@ void PreprocessBegin (void)
void PreprocessEnd (void)
/* Preprocessor done with current file */
void PreprocessEnd (IFile* Input)
/* Preprocessor done with current file. The parameter is the file we're
** switching back to.
*/
{
/* Switch back to the old input file */
CurInput = Input;
/* Check for missing #endif */
while (PPStack->Index >= 0) {
if ((PPStack->Stack[PPStack->Index] & IFCOND_NEEDTERM) != 0) {

View File

@@ -54,6 +54,9 @@ struct PPIfStack {
int Index;
};
/* Forward */
struct IFile;
/*****************************************************************************/
@@ -62,29 +65,31 @@ struct PPIfStack {
void HandleSpecialMacro (Macro* M, const char* Name);
/* Handle special "magic" macros that may change */
void Preprocess (void);
/* Preprocess a line */
void SetPPIfStack (PPIfStack* Stack);
/* Specify which PP #if stack to use */
void ContinueLine (void);
/* Continue the current line ended with a '\\' */
void PreprocessBegin (void);
/* Initialize preprocessor with current file */
void PreprocessEnd (void);
/* Preprocessor done with current file */
void InitPreprocess (void);
/* Init preprocessor */
void DonePreprocess (void);
/* Done with preprocessor */
void HandleSpecialMacro (Macro* M, const char* Name);
/* Handle special "magic" macros that may change */
void SetPPIfStack (PPIfStack* Stack);
/* Specify which PP #if stack to use */
void ContinueLine (void);
/* Continue the current line ended with a '\\' */
void PreprocessBegin (struct IFile* Input);
/* Initialize the preprocessor for a new input file */
void PreprocessEnd (struct IFile* Input);
/* Preprocessor done with current file. The parameter is the file we're
** switching back to.
*/