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