From cf899f7a7cc19661861344d2b34cc98f10273cd9 Mon Sep 17 00:00:00 2001 From: Akim Demaille Date: Sun, 7 Mar 2021 10:01:53 +0100 Subject: [PATCH] yacc: fix push parser When a pstate is used for multiple successive parses, some state may leak from one run into the following one. That was introduced in 330552ea499ca474f65967160e9d4e50265f9631 "yacc.c: push: don't clear the parser state when accepting/rejecting". Reported by Ryan https://lists.gnu.org/r/bug-bison/2021-03/msg00000.html * data/skeletons/yacc.c (yypush_parse): We reusing a pstate from a previous run, do behave as if it were the first run. * tests/push.at (Pstate reuse): Check this. --- NEWS | 8 ++- data/skeletons/yacc.c | 12 ++-- tests/push.at | 127 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 139 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index 9598c0c2..454eba25 100644 --- a/NEWS +++ b/NEWS @@ -4,11 +4,17 @@ GNU Bison NEWS ** Bug fixes +*** Reused Push Parsers + + When a push-parser state structure is used for multiple parses, it was + possible for some state to leak from one run into the following one. + *** Fix Table Generation In some very rare conditions, when there are many useless tokens, it was possible to generate incorrect parsers. + * Noteworthy changes in release 3.7.5 (2021-01-24) [stable] ** Bug fixes @@ -290,7 +296,7 @@ GNU Bison NEWS parse errors, since `yynerrs` was also reset. This can be especially troublesome when used in autocompletion, since a parser with error recovery would suggest (irrelevant) expected tokens even if there were - failure. + failures. Now the parser state can be examined when parsing is finished. The parser state is reset when starting a new parse. diff --git a/data/skeletons/yacc.c b/data/skeletons/yacc.c index 43476ace..afdb5085 100644 --- a/data/skeletons/yacc.c +++ b/data/skeletons/yacc.c @@ -1573,14 +1573,16 @@ yyparse (]m4_ifset([b4_parse_param], [b4_formals(b4_parse_param)], [void])[)]])[ switch (yyps->yynew) { - case 2: - yypstate_clear (yyps); - goto case_0; - - case_0: case 0: yyn = yypact[yystate]; goto yyread_pushed_token; + + case 2: + yypstate_clear (yyps); + break; + + default: + break; }]])[ YYDPRINTF ((stderr, "Starting parse\n")); diff --git a/tests/push.at b/tests/push.at index cd3e113e..0cfb346e 100644 --- a/tests/push.at +++ b/tests/push.at @@ -25,7 +25,7 @@ AT_BANNER([[Push Parsing Tests]]) AT_SETUP([[Memory Leak for Early Deletion]]) # Requires Valgrind. -AT_BISON_OPTION_PUSHDEFS +AT_BISON_OPTION_PUSHDEFS([%define api.push-pull push]) AT_DATA_GRAMMAR([[input.y]], [[ %{ @@ -144,7 +144,7 @@ AT_CLEANUP AT_SETUP([[Unsupported Skeletons]]) -AT_BISON_OPTION_PUSHDEFS +AT_BISON_OPTION_PUSHDEFS([%define api.push-pull push]) AT_DATA([[input.y]], [[%glr-parser %define api.push-pull push @@ -158,3 +158,126 @@ AT_BISON_CHECK([[input.y]], [[1]], [], ]]) AT_CLEANUP + + +## -------------- ## +## Pstate reuse. ## +## -------------- ## + +AT_SETUP([[Pstate reuse]]) + +# Make sure that when a single pstate is used for multiple successive +# parses, no state from a previous run leaks into the following one. +# +# See https://lists.gnu.org/r/bug-bison/2021-03/msg00000.html. + +AT_BISON_OPTION_PUSHDEFS([%define api.push-pull push]) +AT_DATA_GRAMMAR([[input.y]], +[[%code top { + #include + #include + + static char *string_concat (char *a, char *b); + ]AT_YYERROR_DECLARE[ +} + +%define parse.trace +%define api.pure full +%define api.push-pull push +%expect 0 + +%union { + char *sval; +}; +%destructor { free ($$); } +%printer { fprintf (yyo, "%s", $$); } + +%token RAW +%token EOL + +%type text + +%% + +line + : text EOL { printf ("text: %s\n", $1); free ($1); YYACCEPT; }; + +text + : RAW { $$ = $1; } + | text RAW { $$ = string_concat ($1, $2); } + ; + +%% +]AT_YYERROR_DEFINE[ + +static char * +string_concat (char *a, char *b) +{ + size_t la = strlen (a); + size_t lb = strlen (b); + char *res = YY_CAST (char *, malloc (la + lb + 1)); + strcpy (res, a); + strcpy (res + la, b); + free (a); + free (b); + return res; +} + +static int +push (yypstate *ps, yytoken_kind_t kind, const char *str) +{ + YYSTYPE lval; + lval.sval = str ? strdup (str) : YY_NULLPTR; + switch (yypush_parse (ps, kind, &lval)) + { + case 0: + return 0; + case YYPUSH_MORE: + // parsing incomplete, but valid; parser not reset + return 0; + case 1: + // YYABORT or syntax invalid; parser is reset + fprintf (stderr, "invalid input, but no error was thrown\n"); + return 1; + case 2: + // memory exhaustion; parser is reset + fprintf (stderr, "memory exhaustion during yypush_parse\n"); + return 1; + } + return 1; +} + +int +main (void) +{ + yydebug = !!getenv ("YYDEBUG"); + yypstate *ps = yypstate_new (); + +#define PUSH(Kind, Val) \ + do { \ + if (push (ps, Kind, Val)) \ + return 1; \ + } while (0) + + PUSH (RAW, "te"); + PUSH (RAW, "xt"); + PUSH (EOL, YY_NULLPTR); + + PUSH (RAW, "te"); + PUSH (RAW, "xt"); + PUSH (EOL, YY_NULLPTR); + + yypstate_delete (ps); + + return 0; +} +]]) + +AT_FULL_COMPILE([input]) +AT_CHECK([./input], 0, +[[text: text +text: text +]]) + +AT_BISON_OPTION_POPDEFS +AT_CLEANUP