1 /* GNU m4 -- A simple macro processor
2 
3    Copyright (C) 1989-1994, 2006-2014, 2016-2017, 2020-2021 Free Software
4    Foundation, Inc.
5 
6    This file is part of GNU M4.
7 
8    GNU M4 is free software: you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation, either version 3 of the License, or
11    (at your option) any later version.
12 
13    GNU M4 is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <https://www.gnu.org/licenses/>.
20 */
21 
22 /* This module handles frozen files.  */
23 
24 #include "m4.h"
25 
26 /*-------------------------------------------------------------------.
27 | Destructively reverse a symbol list and return the reversed list.  |
28 `-------------------------------------------------------------------*/
29 
30 static symbol *
reverse_symbol_list(symbol * sym)31 reverse_symbol_list (symbol *sym)
32 {
33   symbol *result;
34   symbol *next;
35 
36   result = NULL;
37   while (sym)
38     {
39       next = SYMBOL_STACK (sym);
40       SYMBOL_STACK (sym) = result;
41       result = sym;
42       sym = next;
43     }
44   return result;
45 }
46 
47 static void
freeze_symbol(symbol * sym,void * arg)48 freeze_symbol (symbol *sym, void *arg)
49 {
50   symbol *s = sym;
51   FILE *file = arg;
52   const builtin *bp;
53 
54   /* Process all entries in one stack, from the last to the first.
55      This order ensures that, at reload time, pushdef's will be
56      executed with the oldest definitions first.  */
57 
58   s = reverse_symbol_list (s);
59   for (sym = s; sym; sym = SYMBOL_STACK (sym))
60     {
61       switch (SYMBOL_TYPE (sym))
62         {
63         case TOKEN_TEXT:
64           xfprintf (file, "T%d,%d\n",
65                     (int) strlen (SYMBOL_NAME (sym)),
66                     (int) strlen (SYMBOL_TEXT (sym)));
67           fputs (SYMBOL_NAME (sym), file);
68           fputs (SYMBOL_TEXT (sym), file);
69           fputc ('\n', file);
70           break;
71 
72         case TOKEN_FUNC:
73           bp = find_builtin_by_addr (SYMBOL_FUNC (sym));
74           if (bp == NULL)
75             {
76               M4ERROR ((warning_status, 0, "\
77 INTERNAL ERROR: builtin not found in builtin table!"));
78               abort ();
79             }
80           xfprintf (file, "F%d,%d\n",
81                     (int) strlen (SYMBOL_NAME (sym)),
82                     (int) strlen (bp->name));
83           fputs (SYMBOL_NAME (sym), file);
84           fputs (bp->name, file);
85           fputc ('\n', file);
86           break;
87 
88         case TOKEN_VOID:
89           /* Ignore placeholder tokens that exist due to traceon.  */
90           break;
91 
92         default:
93           M4ERROR ((warning_status, 0, "\
94 INTERNAL ERROR: bad token data type in freeze_symbol ()"));
95           abort ();
96           break;
97         }
98     }
99 
100   /* Reverse the stack once more, putting it back as it was.  */
101   reverse_symbol_list (s);
102 }
103 
104 /*------------------------------------------------.
105 | Produce a frozen state to the given file NAME.  |
106 `------------------------------------------------*/
107 
108 void
produce_frozen_state(const char * name)109 produce_frozen_state (const char *name)
110 {
111   FILE *file;
112 
113   file = fopen (name, O_BINARY ? "wbe" : "we");
114   if (!file)
115     m4_failure (errno, _("cannot open `%s'"), name);
116 
117   /* Write a recognizable header.  */
118 
119   xfprintf (file, "# This is a frozen state file generated by %s\n",
120            PACKAGE_STRING);
121   xfprintf (file, "V1\n");
122 
123   /* Dump quote delimiters.  */
124 
125   if (strcmp (lquote.string, DEF_LQUOTE) || strcmp (rquote.string, DEF_RQUOTE))
126     {
127       xfprintf (file, "Q%d,%d\n", (int) lquote.length, (int) rquote.length);
128       fputs (lquote.string, file);
129       fputs (rquote.string, file);
130       fputc ('\n', file);
131     }
132 
133   /* Dump comment delimiters.  */
134 
135   if (strcmp (bcomm.string, DEF_BCOMM) || strcmp (ecomm.string, DEF_ECOMM))
136     {
137       xfprintf (file, "C%d,%d\n", (int) bcomm.length, (int) ecomm.length);
138       fputs (bcomm.string, file);
139       fputs (ecomm.string, file);
140       fputc ('\n', file);
141     }
142 
143   /* Dump all symbols.  */
144 
145   hack_all_symbols (freeze_symbol, file);
146 
147   /* Let diversions be issued from output.c module, its cleaner to have this
148      piece of code there.  */
149 
150   freeze_diversions (file);
151 
152   /* All done.  */
153 
154   fputs ("# End of frozen state file\n", file);
155   if (close_stream (file) != 0)
156     m4_failure (errno, _("unable to create frozen state"));
157 }
158 
159 /*----------------------------------------------------------------------.
160 | Issue a message saying that some character is an EXPECTED character.  |
161 `----------------------------------------------------------------------*/
162 
163 static void
issue_expect_message(int expected)164 issue_expect_message (int expected)
165 {
166   if (expected == '\n')
167     m4_failure (0, _("expecting line feed in frozen file"));
168   else
169     m4_failure (0, _("expecting character `%c' in frozen file"), expected);
170 }
171 
172 /*-------------------------------------------------.
173 | Reload a frozen state from the given file NAME.  |
174 `-------------------------------------------------*/
175 
176 /* We are seeking speed, here.  */
177 
178 void
reload_frozen_state(const char * name)179 reload_frozen_state (const char *name)
180 {
181   FILE *file;
182   int character;
183   int operation;
184   char *string[2];
185   int allocated[2];
186   int number[2];
187   const builtin *bp;
188   bool advance_line = true;
189 
190 #define GET_CHARACTER                                           \
191   do                                                            \
192     {                                                           \
193       if (advance_line)                                         \
194         {                                                       \
195           current_line++;                                       \
196           advance_line = false;                                 \
197         }                                                       \
198       (character = getc (file));                                \
199       if (character == '\n')                                    \
200         advance_line = true;                                    \
201     }                                                           \
202   while (0)
203 
204 #define GET_NUMBER(Number, AllowNeg)                            \
205   do                                                            \
206     {                                                           \
207       unsigned int n = 0;                                       \
208       while (c_isdigit (character) && n <= INT_MAX / 10U)       \
209         {                                                       \
210           n = 10 * n + character - '0';                         \
211           GET_CHARACTER;                                        \
212         }                                                       \
213       if (((AllowNeg) ? INT_MIN : INT_MAX) + 0U < n             \
214           || c_isdigit (character))                             \
215         m4_failure (0, _("integer overflow in frozen file"));   \
216       (Number) = n;                                             \
217     }                                                           \
218   while (0)
219 
220 #define VALIDATE(Expected)                                      \
221   do                                                            \
222     {                                                           \
223       if (character != (Expected))                              \
224         issue_expect_message (Expected);                        \
225     }                                                           \
226   while (0)
227 
228   /* Skip comments (`#' at beginning of line) and blank lines, setting
229      character to the next directive or to EOF.  */
230 
231 #define GET_DIRECTIVE                                           \
232   do                                                            \
233     {                                                           \
234       GET_CHARACTER;                                            \
235       if (character == '#')                                     \
236         {                                                       \
237           while (character != EOF && character != '\n')         \
238             GET_CHARACTER;                                      \
239           VALIDATE ('\n');                                      \
240         }                                                       \
241     }                                                           \
242   while (character == '\n')
243 
244 #define GET_STRING(i)                                                   \
245   do                                                                    \
246     {                                                                   \
247       void *tmp;                                                        \
248       char *p;                                                          \
249       if (number[(i)] + 1 > allocated[(i)])                             \
250         {                                                               \
251           free (string[(i)]);                                           \
252           allocated[(i)] = number[(i)] + 1;                             \
253           string[(i)] = xcharalloc ((size_t) allocated[(i)]);           \
254         }                                                               \
255       if (number[(i)] > 0                                               \
256           && !fread (string[(i)], (size_t) number[(i)], 1, file))       \
257         m4_failure (0, _("premature end of frozen file"));              \
258       string[(i)][number[(i)]] = '\0';                                  \
259       p = string[(i)];                                                  \
260       while ((tmp = memchr(p, '\n', number[(i)] - (p - string[(i)]))))  \
261         {                                                               \
262           current_line++;                                               \
263           p = (char *) tmp + 1;                                         \
264         }                                                               \
265     }                                                                   \
266   while (0)
267 
268   file = m4_path_search (name, NULL);
269   if (file == NULL)
270     m4_failure (errno, _("cannot open %s"), name);
271   current_file = name;
272 
273   allocated[0] = 100;
274   string[0] = xcharalloc ((size_t) allocated[0]);
275   allocated[1] = 100;
276   string[1] = xcharalloc ((size_t) allocated[1]);
277 
278   /* Validate format version.  Only `1' is acceptable for now.  */
279   GET_DIRECTIVE;
280   VALIDATE ('V');
281   GET_CHARACTER;
282   GET_NUMBER (number[0], false);
283   if (number[0] > 1)
284     M4ERROR ((EXIT_MISMATCH, 0,
285               _("frozen file version %d greater than max supported of 1"),
286               number[0]));
287   else if (number[0] < 1)
288     m4_failure (0, _("ill-formed frozen file, version directive expected"));
289   VALIDATE ('\n');
290 
291   GET_DIRECTIVE;
292   while (character != EOF)
293     {
294       switch (character)
295         {
296         default:
297           m4_failure (0, _("ill-formed frozen file"));
298 
299         case 'C':
300         case 'D':
301         case 'F':
302         case 'T':
303         case 'Q':
304           operation = character;
305           GET_CHARACTER;
306 
307           /* Get string lengths.  Accept a negative diversion number.  */
308 
309           if (operation == 'D' && character == '-')
310             {
311               GET_CHARACTER;
312               GET_NUMBER (number[0], true);
313               number[0] = -number[0];
314             }
315           else
316             GET_NUMBER (number[0], false);
317           VALIDATE (',');
318           GET_CHARACTER;
319           GET_NUMBER (number[1], false);
320           VALIDATE ('\n');
321 
322           if (operation != 'D')
323             GET_STRING (0);
324           GET_STRING (1);
325           GET_CHARACTER;
326           VALIDATE ('\n');
327 
328           /* Act according to operation letter.  */
329 
330           switch (operation)
331             {
332             case 'C':
333 
334               /* Change comment strings.  */
335 
336               set_comment (string[0], string[1]);
337               break;
338 
339             case 'D':
340 
341               /* Select a diversion and add a string to it.  */
342 
343               make_diversion (number[0]);
344               if (number[1] > 0)
345                 output_text (string[1], number[1]);
346               break;
347 
348             case 'F':
349 
350               /* Enter a macro having a builtin function as a definition.  */
351 
352               bp = find_builtin_by_name (string[1]);
353               define_builtin (string[0], bp, SYMBOL_PUSHDEF);
354               break;
355 
356             case 'T':
357 
358               /* Enter a macro having an expansion text as a definition.  */
359 
360               define_user_macro (string[0], string[1], SYMBOL_PUSHDEF);
361               break;
362 
363             case 'Q':
364 
365               /* Change quote strings.  */
366 
367               set_quotes (string[0], string[1]);
368               break;
369 
370             default:
371 
372               /* Cannot happen.  */
373 
374               break;
375             }
376           break;
377 
378         }
379       GET_DIRECTIVE;
380     }
381 
382   free (string[0]);
383   free (string[1]);
384   if (close_stream (file) != 0)
385     m4_failure (errno, _("unable to read frozen state"));
386   current_file = NULL;
387   current_line = 0;
388 
389 #undef GET_CHARACTER
390 #undef GET_DIRECTIVE
391 #undef GET_NUMBER
392 #undef VALIDATE
393 #undef GET_STRING
394 }
395