Implement INCLUDE_ONCE directive (#1481)

Identify files by (device, inode), not by path, so that symlinks,
relative paths, case-insensitive paths, or other edge cases
do not result in double includes.
This commit is contained in:
sukus
2024-09-08 06:02:02 +02:00
committed by GitHub
parent 11f0e88b30
commit 5f07095f6d
8 changed files with 62 additions and 8 deletions

View File

@@ -17,6 +17,12 @@
#include "asm/lexer.hpp" #include "asm/lexer.hpp"
enum IncludeType {
INCLUDE_NORMAL,
INCLUDE_PRE,
INCLUDE_ONCE
};
struct FileStackNode { struct FileStackNode {
FileStackNodeType type; FileStackNodeType type;
Either< Either<
@@ -64,7 +70,7 @@ void fstk_SetPreIncludeFile(std::string const &path);
std::optional<std::string> fstk_FindFile(std::string const &path); std::optional<std::string> fstk_FindFile(std::string const &path);
bool yywrap(); bool yywrap();
void fstk_RunInclude(std::string const &path, bool updateStateNow); void fstk_RunInclude(std::string const &path, IncludeType type);
void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs); void fstk_RunMacro(std::string const &macroName, std::shared_ptr<MacroArgs> macroArgs);
void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span); void fstk_RunRept(uint32_t count, int32_t reptLineNo, ContentSpan const &span);
void fstk_RunFor( void fstk_RunFor(

View File

@@ -2158,7 +2158,17 @@ calls infinitely (or until you run out of memory, whichever comes first).
INCLUDE "irq.inc" INCLUDE "irq.inc"
.Ed .Ed
.Pp .Pp
You may also implicitly You may also ensure a file only gets included once by using
.Ic INCLUDE_ONCE
instead of
.Ic INCLUDE .
This will skip including a file if it has already been included before (with
.Ic INCLUDE ,
.Ic INCLUDE_ONCE ,
or
.Fl P ) .
.Pp
You can implicitly
.Ic INCLUDE .Ic INCLUDE
a file before the source file with the a file before the source file with the
.Fl P .Fl P

View File

@@ -6,9 +6,11 @@
#include <errno.h> #include <errno.h>
#include <inttypes.h> #include <inttypes.h>
#include <memory> #include <memory>
#include <set>
#include <stack> #include <stack>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <utility>
#include "error.hpp" #include "error.hpp"
#include "helpers.hpp" #include "helpers.hpp"
@@ -45,8 +47,8 @@ size_t maxRecursionDepth;
// The first include path for `fstk_FindFile` to try is none at all // The first include path for `fstk_FindFile` to try is none at all
static std::vector<std::string> includePaths = {""}; static std::vector<std::string> includePaths = {""};
static std::string preIncludeName; static std::string preIncludeName;
static std::set<std::pair<dev_t, ino_t>> includedFiles;
std::string const &FileStackNode::dump(uint32_t curLineNo) const { std::string const &FileStackNode::dump(uint32_t curLineNo) const {
if (data.holds<std::vector<uint32_t>>()) { if (data.holds<std::vector<uint32_t>>()) {
@@ -291,11 +293,11 @@ static Context &newReptContext(int32_t reptLineNo, ContentSpan const &span, uint
return context; return context;
} }
void fstk_RunInclude(std::string const &path, bool preInclude) { void fstk_RunInclude(std::string const &path, IncludeType type) {
std::optional<std::string> fullPath = fstk_FindFile(path); std::optional<std::string> fullPath = fstk_FindFile(path);
if (!fullPath) { if (!fullPath) {
if (generatedMissingIncludes && !preInclude) { if (generatedMissingIncludes && type != INCLUDE_PRE) {
if (verbose) if (verbose)
printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno)); printf("Aborting (-MG) on INCLUDE file '%s' (%s)\n", path.c_str(), strerror(errno));
failedOnMissingInclude = true; failedOnMissingInclude = true;
@@ -305,6 +307,24 @@ void fstk_RunInclude(std::string const &path, bool preInclude) {
return; return;
} }
// The pair of device ID and serial number uniquely identify a file, with `stat()`
// following symbolic links to identify the actual file.
struct stat statBuf;
if (stat(fullPath->c_str(), &statBuf) != 0) {
error("Failed to stat file '%s': %s\n", fullPath->c_str(), strerror(errno));
return;
}
std::pair<dev_t, ino_t> inode{statBuf.st_dev, statBuf.st_ino};
if (type == INCLUDE_ONCE && includedFiles.find(inode) != includedFiles.end()) {
if (verbose) {
printf("File '%s' already included, skipping INCLUDE_ONCE", path.c_str());
}
return;
}
includedFiles.insert(inode);
if (!newFileContext(*fullPath, false)) if (!newFileContext(*fullPath, false))
fatalerror("Failed to set up lexer for file include\n"); fatalerror("Failed to set up lexer for file include\n");
} }
@@ -395,5 +415,5 @@ void fstk_Init(std::string const &mainPath, size_t maxDepth) {
maxRecursionDepth = maxDepth; maxRecursionDepth = maxDepth;
if (!preIncludeName.empty()) if (!preIncludeName.empty())
fstk_RunInclude(preIncludeName, true); fstk_RunInclude(preIncludeName, INCLUDE_PRE);
} }

View File

@@ -256,6 +256,7 @@ static std::unordered_map<std::string, int, CaseInsensitive, CaseInsensitive> ke
{"INCHARMAP", T_(OP_INCHARMAP) }, {"INCHARMAP", T_(OP_INCHARMAP) },
{"INCLUDE", T_(POP_INCLUDE) }, {"INCLUDE", T_(POP_INCLUDE) },
{"INCLUDE_ONCE", T_(POP_INCLUDE_ONCE) },
{"PRINT", T_(POP_PRINT) }, {"PRINT", T_(POP_PRINT) },
{"PRINTLN", T_(POP_PRINTLN) }, {"PRINTLN", T_(POP_PRINTLN) },
{"EXPORT", T_(POP_EXPORT) }, {"EXPORT", T_(POP_EXPORT) },

View File

@@ -236,6 +236,7 @@
%token POP_IF "IF" %token POP_IF "IF"
%token POP_INCBIN "INCBIN" %token POP_INCBIN "INCBIN"
%token POP_INCLUDE "INCLUDE" %token POP_INCLUDE "INCLUDE"
%token POP_INCLUDE_ONCE "INCLUDE_ONCE"
%token POP_LOAD "LOAD" %token POP_LOAD "LOAD"
%token POP_MACRO "MACRO" %token POP_MACRO "MACRO"
%token POP_NEWCHARMAP "NEWCHARMAP" %token POP_NEWCHARMAP "NEWCHARMAP"
@@ -464,6 +465,7 @@ line_directive:
| for | for
| break | break
| include | include
| include_once
| if | if
// It's important that all of these require being at line start for `skipIfBlock` // It's important that all of these require being at line start for `skipIfBlock`
| elif | elif
@@ -1140,7 +1142,15 @@ export_def:
include: include:
label POP_INCLUDE string endofline { label POP_INCLUDE string endofline {
fstk_RunInclude($3, false); fstk_RunInclude($3, INCLUDE_NORMAL);
if (failedOnMissingInclude)
YYACCEPT;
}
;
include_once:
label POP_INCLUDE_ONCE string endofline {
fstk_RunInclude($3, INCLUDE_ONCE);
if (failedOnMissingInclude) if (failedOnMissingInclude)
YYACCEPT; YYACCEPT;
} }

View File

@@ -0,0 +1,3 @@
INCLUDE_ONCE "include-once.inc"
INCLUDE_ONCE "include-once.inc"
INCLUDE_ONCE "include-link.inc"

View File

@@ -0,0 +1 @@
DEF HELLO EQU 1

View File

@@ -11,9 +11,12 @@ input="$(mktemp)"
output="$(mktemp)" output="$(mktemp)"
errput="$(mktemp)" errput="$(mktemp)"
# Create a symbolic link for the `include-once.asm` test case.
ln include-once.inc include-link.inc
# Immediate expansion is the desired behavior. # Immediate expansion is the desired behavior.
# shellcheck disable=SC2064 # shellcheck disable=SC2064
trap "rm -f ${o@Q} ${gb@Q} ${input@Q} ${output@Q} ${errput@Q}" EXIT trap "rm -f ${o@Q} ${gb@Q} ${input@Q} ${output@Q} ${errput@Q} include-link.inc" EXIT
tests=0 tests=0
failed=0 failed=0