shithub: rgbds

Download patch

ref: 4ef27a0d239a9752c176031f1665ffa5f4abbba0
parent: 89dc14fcaf0de602acdc5ea3259a6eafee213a34
parent: 476ccc9f6bc0d292f0336132670de0dfacee7aef
author: Eldred Habert <[email protected]>
date: Sun Sep 1 22:21:16 EDT 2019

Merge pull request #411 from ISSOtm/recursion_limit

Add a recursion limit

--- a/include/asm/fstack.h
+++ b/include/asm/fstack.h
@@ -35,6 +35,8 @@
 	uint32_t nREPTBlockSize;
 };
 
+extern unsigned int nMaxRecursionDepth;
+
 void fstk_RunInclude(char *tzFileName);
 void fstk_RunMacroArg(int32_t s);
 void fstk_Init(char *s);
--- a/include/asm/lexer.h
+++ b/include/asm/lexer.h
@@ -40,6 +40,13 @@
 	LEX_STATE_MACROARGS
 };
 
+struct sStringExpansionPos {
+	char *tzName;
+	char *pBuffer;
+	char *pBufferPos;
+	struct sStringExpansionPos *pParent;
+};
+
 #define INITIAL		0
 #define macroarg	3
 
@@ -62,14 +69,16 @@
 void lex_Init(void);
 void lex_AddStrings(const struct sLexInitString *lex);
 void lex_SetBuffer(char *buffer, uint32_t len);
+void lex_BeginStringExpansion(const char *tzName);
 int yywrap(void);
 int yylex(void);
 void yyunput(char c);
-void yyunputstr(char *s);
+void yyunputstr(const char *s);
 void yyskipbytes(uint32_t count);
 void yyunputbytes(uint32_t count);
 
 extern YY_BUFFER_STATE pCurrentBuffer;
+extern struct sStringExpansionPos *pCurrentStringExpansion;
 
 void upperstring(char *s);
 void lowerstring(char *s);
--- a/src/asm/fstack.c
+++ b/src/asm/fstack.c
@@ -28,6 +28,8 @@
 #include "types.h"
 
 static struct sContext *pFileStack;
+static unsigned int nFileStackDepth;
+unsigned int nMaxRecursionDepth;
 static struct sSymbol *pCurrentMacro;
 static YY_BUFFER_STATE CurrentFlexHandle;
 static FILE *pCurrentFile;
@@ -51,6 +53,8 @@
 #define STAT_isMacroArg		2
 #define STAT_isREPTBlock	3
 
+/* Max context stack size */
+
 /*
  * Context push and pop
  */
@@ -58,6 +62,9 @@
 {
 	struct sContext **ppFileStack;
 
+	if (++nFileStackDepth > nMaxRecursionDepth)
+		fatalerror("Recursion limit (%d) exceeded", nMaxRecursionDepth);
+
 	ppFileStack = &pFileStack;
 	while (*ppFileStack)
 		ppFileStack = &((*ppFileStack)->pNext);
@@ -154,6 +161,8 @@
 		fatalerror("%s: Internal error.", __func__);
 	}
 
+	nFileStackDepth--;
+
 	free(*ppLastFile);
 	*ppLastFile = NULL;
 	yy_switch_to_buffer(CurrentFlexHandle);
@@ -413,6 +422,7 @@
 		if (pCurrentFile == NULL)
 			err(1, "Unable to open file '%s'", pFileName);
 	}
+	nFileStackDepth = 0;
 
 	nMacroCount = 0;
 	nCurrentStatus = STAT_isInclude;
--- a/src/asm/globlex.c
+++ b/src/asm/globlex.c
@@ -188,11 +188,11 @@
 }
 
 /*
- * If the symbol name ends before the end of the macro arg, return true
- * and point "rest" to the rest of the macro arg.
- * Otherwise, return false.
+ * If the symbol name ends before the end of the macro arg,
+ * return a pointer to the rest of the macro arg.
+ * Otherwise, return NULL.
  */
-bool AppendMacroArg(char whichArg, char *dest, size_t *destIndex, char **rest)
+char *AppendMacroArg(char whichArg, char *dest, size_t *destIndex)
 {
 	char *marg;
 
@@ -222,14 +222,13 @@
 			dest[*destIndex] = ch;
 			(*destIndex)++;
 		} else {
-			*rest = marg;
-			return true;
+			return marg;
 		}
 
 		marg++;
 	}
 
-	return false;
+	return NULL;
 }
 
 uint32_t ParseSymbol(char *src, uint32_t size)
@@ -251,7 +250,9 @@
 			 */
 			ch = src[srcIndex++];
 
-			if (AppendMacroArg(ch, dest, &destIndex, &rest))
+			rest = AppendMacroArg(ch, dest, &destIndex);
+			/* If the symbol's end was in the middle of the token */
+			if (rest)
 				break;
 		} else {
 			if (destIndex >= MAXSYMLEN)
@@ -262,27 +263,34 @@
 
 	dest[destIndex] = 0;
 
+	/* Tell the lexer we read all bytes that we did */
+	yyskipbytes(srcIndex);
+
+	/*
+	 * If an escape's expansion left some chars after the symbol's end,
+	 * such as the `::` in a `Backup\1` expanded to `BackupCamX::`,
+	 * put those into the buffer.
+	 * Note that this NEEDS to be done after the `yyskipbytes` above.
+	 */
+	if (rest)
+		yyunputstr(rest);
+
+	/* If the symbol is an EQUS, expand it */
 	if (!oDontExpandStrings && sym_isString(dest)) {
 		char *s;
 
-		yyskipbytes(srcIndex);
+		lex_BeginStringExpansion(dest);
 
-		if (rest)
-			yyunputstr(rest);
-
+		/* Feed the symbol's contents into the buffer */
 		yyunputstr(s = sym_GetStringValue(dest));
 
+		/* Lines inserted this way shall not increase nLineNo */
 		while (*s) {
 			if (*s++ == '\n')
-				nLineNo -= 1;
+				nLineNo--;
 		}
 		return 0;
 	}
-
-	yyskipbytes(srcIndex);
-
-	if (rest)
-		yyunputstr(rest);
 
 	strcpy(yylval.tzSym, dest);
 	return 1;
--- a/src/asm/lexer.c
+++ b/src/asm/lexer.c
@@ -51,6 +51,9 @@
 uint32_t nFloating;
 enum eLexerState lexerstate = LEX_STATE_NORMAL;
 
+struct sStringExpansionPos *pCurrentStringExpansion;
+static unsigned int nNbStringExpansions;
+
 /* UTF-8 byte order mark */
 static const unsigned char bom[BOM_SIZE] = { 0xEF, 0xBB, 0xBF };
 
@@ -88,19 +91,51 @@
 	*(--pLexBuffer) = c;
 }
 
-void yyunputstr(char *s)
+void yyunputstr(const char *s)
 {
-	int32_t i, len;
+	int32_t len;
 
 	len = strlen(s);
 
-	if (pLexBuffer - len < pLexBufferRealStart)
+	/*
+	 * It would be undefined behavior to subtract `len` from pLexBuffer and
+	 * potentially have it point outside of pLexBufferRealStart's buffer,
+	 * this is why the check is done this way.
+	 * Refer to https://github.com/rednex/rgbds/pull/411#discussion_r319779797
+	 */
+	if (pLexBuffer - pLexBufferRealStart < len)
 		fatalerror("Buffer safety margin exceeded");
 
-	for (i = len - 1; i >= 0; i--)
-		*(--pLexBuffer) = s[i];
+	pLexBuffer -= len;
+
+	memcpy(pLexBuffer, s, len);
 }
 
+/*
+ * Marks that a new string expansion with name `tzName` ends here
+ * Enforces recursion depth
+ */
+void lex_BeginStringExpansion(const char *tzName)
+{
+	if (++nNbStringExpansions > nMaxRecursionDepth)
+		fatalerror("Recursion limit (%d) exceeded", nMaxRecursionDepth);
+
+	struct sStringExpansionPos *pNewStringExpansion =
+		malloc(sizeof(*pNewStringExpansion));
+	char *tzNewExpansionName = strdup(tzName);
+
+	if (!pNewStringExpansion || !tzNewExpansionName)
+		fatalerror("Could not allocate memory to expand '%s'",
+			   tzName);
+
+	pNewStringExpansion->tzName = tzNewExpansionName;
+	pNewStringExpansion->pBuffer = pLexBufferRealStart;
+	pNewStringExpansion->pBufferPos = pLexBuffer;
+	pNewStringExpansion->pParent = pCurrentStringExpansion;
+
+	pCurrentStringExpansion = pNewStringExpansion;
+}
+
 void yy_switch_to_buffer(YY_BUFFER_STATE buf)
 {
 	pCurrentBuffer = buf;
@@ -423,6 +458,9 @@
 
 	nLexMaxLength = 0;
 	nFloating = 0;
+
+	pCurrentStringExpansion = NULL;
+	nNbStringExpansions = 0;
 }
 
 void lex_AddStrings(const struct sLexInitString *lex)
@@ -967,12 +1005,30 @@
 
 int yylex(void)
 {
+	int returnedChar;
 	switch (lexerstate) {
 	case LEX_STATE_NORMAL:
-		return yylex_NORMAL();
+		returnedChar = yylex_NORMAL();
+		break;
 	case LEX_STATE_MACROARGS:
-		return yylex_MACROARGS();
+		returnedChar = yylex_MACROARGS();
+		break;
 	default:
 		fatalerror("%s: Internal error.", __func__);
 	}
+
+	/* Check if string expansions were fully read */
+	while (pCurrentStringExpansion
+	    && pCurrentStringExpansion->pBuffer == pLexBufferRealStart
+	    && pCurrentStringExpansion->pBufferPos <= pLexBuffer) {
+		struct sStringExpansionPos *pParent =
+			pCurrentStringExpansion->pParent;
+		free(pCurrentStringExpansion->tzName);
+		free(pCurrentStringExpansion);
+
+		pCurrentStringExpansion = pParent;
+		nNbStringExpansions--;
+	}
+
+	return returnedChar;
 }
--- a/src/asm/main.c
+++ b/src/asm/main.c
@@ -289,7 +289,8 @@
 {
 	printf(
 "usage: rgbasm [-EhLVvw] [-b chars] [-Dname[=value]] [-g chars] [-i path]\n"
-"              [-M dependfile] [-o outfile] [-p pad_value] file.asm\n");
+"              [-M dependfile] [-o outfile] [-p pad_value]\n"
+"              [-r recursion_depth] file.asm\n");
 	exit(1);
 }
 
@@ -316,6 +317,8 @@
 
 	/* yydebug=1; */
 
+	nMaxRecursionDepth = 64;
+
 	DefaultOptions.gbgfx[0] = '0';
 	DefaultOptions.gbgfx[1] = '1';
 	DefaultOptions.gbgfx[2] = '2';
@@ -333,7 +336,7 @@
 
 	newopt = CurrentOptions;
 
-	while ((ch = getopt(argc, argv, "b:D:Eg:hi:LM:o:p:Vvw")) != -1) {
+	while ((ch = getopt(argc, argv, "b:D:Eg:hi:LM:o:p:r:Vvw")) != -1) {
 		switch (ch) {
 		case 'b':
 			if (strlen(optarg) == 2) {
@@ -386,6 +389,12 @@
 			if (newopt.fillchar < 0 || newopt.fillchar > 0xFF)
 				errx(1, "Argument for option 'p' must be between 0 and 0xFF");
 
+			break;
+		case 'r':
+			nMaxRecursionDepth = strtoul(optarg, &ep, 0);
+
+			if (optarg[0] == '\0' || *ep != '\0')
+				errx(1, "Invalid argument for option 'r'");
 			break;
 		case 'V':
 			printf("rgbasm %s\n", get_package_version_string());
--- a/src/asm/rgbasm.1
+++ b/src/asm/rgbasm.1
@@ -21,6 +21,7 @@
 .Op Fl M Ar dependfile
 .Op Fl o Ar outfile
 .Op Fl p Ar pad_value
+.Op Fl r Ar recursion_depth
 .Ar file
 .Sh DESCRIPTION
 The
@@ -77,6 +78,8 @@
 .It Fl p Ar pad_value
 When padding an image, pad with this value.
 The default is 0x00.
+.It Fl r Ar recursion_depth
+Specifies the recursion depth at which RGBASM will assume being in an infinite loop.
 .It Fl V
 Print the version of the program and exit.
 .It Fl v
--- a/src/asm/rgbasm.5
+++ b/src/asm/rgbasm.5
@@ -455,8 +455,7 @@
 .Sy Important note :
 An EQUS can be expanded to a string that contains another EQUS
 and it will be expanded as well.
-This means that, if you aren't careful, you may trap the assembler into an
-infinite loop if there's a circular dependency in the expansions.
+If this creates an infinite loop, RGBASM will error out once a certain depth is reached. See the -r command-line option.
 Also, a MACRO can have inside an EQUS which references the same MACRO, which has
 the same problem.
 .Pp
--- /dev/null
+++ b/test/asm/equs-recursion.asm
@@ -1,0 +1,2 @@
+recurse EQUS "recurse"
+recurse
\ No newline at end of file
--- /dev/null
+++ b/test/asm/equs-recursion.out
@@ -1,0 +1,2 @@
+ERROR: equs-recursion.asm(2):
+    Recursion limit (64) exceeded
--- /dev/null
+++ b/test/asm/equs-recursion.out.pipe
@@ -1,0 +1,2 @@
+ERROR: -(2):
+    Recursion limit (64) exceeded
--- /dev/null
+++ b/test/asm/include-recursion.asm
@@ -1,0 +1,1 @@
+INCLUDE "include-recursion.asm"
--- /dev/null
+++ b/test/asm/include-recursion.out
@@ -1,0 +1,2 @@
+ERROR: include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1):
+    Recursion limit (64) exceeded
--- /dev/null
+++ b/test/asm/include-recursion.out.pipe
@@ -1,0 +1,2 @@
+ERROR: -(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1) -> include-recursion.asm(1):
+    Recursion limit (64) exceeded
--- /dev/null
+++ b/test/asm/macro-recursion.asm
@@ -1,0 +1,4 @@
+recurse: MACRO
+	recurse
+ENDM
+	recurse
\ No newline at end of file
--- /dev/null
+++ b/test/asm/macro-recursion.out
@@ -1,0 +1,2 @@
+ERROR: macro-recursion.asm(4) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1):
+    Recursion limit (64) exceeded
--- /dev/null
+++ b/test/asm/macro-recursion.out.pipe
@@ -1,0 +1,2 @@
+ERROR: -(4) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1) -> recurse(1):
+    Recursion limit (64) exceeded