1 /* Support for fixing grammar files.
2 
3    Copyright (C) 2019-2021 Free Software Foundation, Inc.
4 
5    This file is part of Bison, the GNU Compiler Compiler.
6 
7    This program is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
19 
20 #include <config.h>
21 
22 #include "fixits.h"
23 
24 #include <error.h>
25 #include <get-errno.h>
26 #include <gl_array_list.h>
27 #include <gl_xlist.h>
28 #include <progname.h>
29 #include <quote.h>
30 #include <quotearg.h>
31 #include <vasnprintf.h>
32 
33 #include "system.h"
34 
35 #include "files.h"
36 #include "getargs.h"
37 
38 typedef struct
39 {
40   location location;
41   char *fix;
42 } fixit;
43 
44 gl_list_t fixits = NULL;
45 
46 static fixit *
fixit_new(location const * loc,char const * fix)47 fixit_new (location const *loc, char const* fix)
48 {
49   fixit *res = xmalloc (sizeof *res);
50   res->location = *loc;
51   res->fix = xstrdup (fix);
52   return res;
53 }
54 
55 static int
fixit_cmp(const fixit * a,const fixit * b)56 fixit_cmp (const  fixit *a, const fixit *b)
57 {
58   return location_cmp (a->location, b->location);
59 }
60 
61 static void
fixit_free(fixit * f)62 fixit_free (fixit *f)
63 {
64   free (f->fix);
65   free (f);
66 }
67 
68 
69 /* GCC and Clang follow the same pattern.
70    https://gcc.gnu.org/onlinedocs/gcc/Diagnostic-Message-Formatting-Options.html
71    https://clang.llvm.org/docs/UsersManual.html#cmdoption-fdiagnostics-parseable-fixits */
72 static void
fixit_print(fixit const * f,FILE * out)73 fixit_print (fixit const *f, FILE *out)
74 {
75   fprintf (out, "fix-it:%s:{%d:%d-%d:%d}:%s\n",
76            quotearg_n_style (1, c_quoting_style, f->location.start.file),
77            f->location.start.line, f->location.start.byte,
78            f->location.end.line, f->location.end.byte,
79            quotearg_n_style (2, c_quoting_style, f->fix));
80 }
81 
82 
83 void
fixits_register(location const * loc,char const * fix)84 fixits_register (location const *loc, char const* fix)
85 {
86   if (!fixits)
87     fixits = gl_list_create_empty (GL_ARRAY_LIST,
88                                    /* equals */ NULL,
89                                    /* hashcode */ NULL,
90                                    (gl_listelement_dispose_fn) fixit_free,
91                                    true);
92   fixit *f = fixit_new (loc, fix);
93   gl_sortedlist_add (fixits, (gl_listelement_compar_fn) fixit_cmp, f);
94   if (feature_flag & feature_fixit)
95     fixit_print (f, stderr);
96 }
97 
98 
99 bool
fixits_empty(void)100 fixits_empty (void)
101 {
102   return !fixits;
103 }
104 
105 
106 void
fixits_run(void)107 fixits_run (void)
108 {
109   if (!fixits)
110     return;
111 
112   /* This is not unlike what is done in location_caret.  */
113   uniqstr input = ((fixit *) gl_list_get_at (fixits, 0))->location.start.file;
114   /* Backup the file. */
115   char buf[256];
116   size_t len = sizeof (buf);
117   char *backup = asnprintf (buf, &len, "%s~", input);
118   if (!backup)
119     xalloc_die ();
120   if (rename (input, backup))
121     error (EXIT_FAILURE, get_errno (),
122            _("%s: cannot backup"), quotearg_colon (input));
123   FILE *in = xfopen (backup, "r");
124   FILE *out = xfopen (input, "w");
125   size_t line = 1;
126   size_t offset = 1;
127   void const *p = NULL;
128   gl_list_iterator_t iter = gl_list_iterator (fixits);
129   while (gl_list_iterator_next (&iter, &p, NULL))
130     {
131       fixit const *f = p;
132       /* Look for the correct line. */
133       while (line < f->location.start.line)
134         {
135           int c = getc (in);
136           if (c == EOF)
137             break;
138           if (c == '\n')
139             {
140               ++line;
141               offset = 1;
142             }
143           putc (c, out);
144         }
145 
146       /* Look for the right offset. */
147       bool need_eol = false;
148       while (offset < f->location.start.byte)
149         {
150           int c = getc (in);
151           if (c == EOF)
152             break;
153           ++offset;
154           if (c == '\n')
155             /* The position we are asked for is beyond the actual
156                line: pad with spaces, and remember we need a \n.  */
157             need_eol = true;
158           putc (need_eol ? ' ' : c, out);
159         }
160 
161       /* Paste the fix instead. */
162       fputs (f->fix, out);
163 
164       /* Maybe install the eol afterwards.  */
165       if (need_eol)
166         putc ('\n', out);
167 
168       /* Skip the bad input. */
169       while (line < f->location.end.line)
170         {
171           int c = getc (in);
172           if (c == EOF)
173             break;
174           if (c == '\n')
175             {
176               ++line;
177               offset = 1;
178             }
179         }
180       while (offset < f->location.end.byte)
181         {
182           int c = getc (in);
183           if (c == EOF)
184             break;
185           ++offset;
186         }
187 
188       /* If erasing the content of a full line, also remove the
189          end-of-line. */
190       if (f->fix[0] == 0 && f->location.start.byte == 1)
191         {
192           int c = getc (in);
193           if (c == EOF)
194             break;
195           else if (c == '\n')
196             {
197               ++line;
198               offset = 1;
199             }
200           else
201             ungetc (c, in);
202         }
203     }
204   /* Paste the rest of the file.  */
205   {
206     int c;
207     while ((c = getc (in)) != EOF)
208       putc (c, out);
209   }
210 
211   gl_list_iterator_free (&iter);
212   xfclose (out);
213   xfclose (in);
214   fprintf (stderr, "%s: file %s was updated (backup: %s)\n",
215            program_name, quote_n (0, input), quote_n (1, backup));
216   if (backup != buf)
217     free (backup);
218 }
219 
220 
221 /* Free the registered fixits.  */
fixits_free(void)222 void fixits_free (void)
223 {
224   if (fixits)
225     {
226       gl_list_free (fixits);
227       fixits = NULL;
228     }
229 }
230