1 /* Resolving ambiguity of argument lists: Progressive parsing of an
2    argument list, keeping track of all possibilities.
3    Copyright (C) 2001-2019 Free Software Foundation, Inc.
4 
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
17 
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21 
22 /* Specification.  */
23 #include "xg-arglist-parser.h"
24 
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "error.h"
29 #include "error-progname.h"
30 #include "xalloc.h"
31 #include "xsize.h"
32 
33 #include "xgettext.h"
34 #include "xg-message.h"
35 
36 #include "gettext.h"
37 #define _(str) gettext (str)
38 
39 
40 struct arglist_parser *
arglist_parser_alloc(message_list_ty * mlp,const struct callshapes * shapes)41 arglist_parser_alloc (message_list_ty *mlp, const struct callshapes *shapes)
42 {
43   if (shapes == NULL || shapes->nshapes == 0)
44     {
45       struct arglist_parser *ap =
46         (struct arglist_parser *)
47         xmalloc (offsetof (struct arglist_parser, alternative[0]));
48 
49       ap->mlp = mlp;
50       ap->keyword = NULL;
51       ap->keyword_len = 0;
52       ap->next_is_msgctxt = false;
53       ap->nalternatives = 0;
54 
55       return ap;
56     }
57   else
58     {
59       struct arglist_parser *ap =
60         (struct arglist_parser *)
61         xmalloc (xsum (sizeof (struct arglist_parser),
62                        xtimes (shapes->nshapes - 1,
63                                sizeof (struct partial_call))));
64       size_t i;
65 
66       ap->mlp = mlp;
67       ap->keyword = shapes->keyword;
68       ap->keyword_len = shapes->keyword_len;
69       ap->next_is_msgctxt = false;
70       ap->nalternatives = shapes->nshapes;
71       for (i = 0; i < shapes->nshapes; i++)
72         {
73           ap->alternative[i].argnumc = shapes->shapes[i].argnumc;
74           ap->alternative[i].argnum1 = shapes->shapes[i].argnum1;
75           ap->alternative[i].argnum2 = shapes->shapes[i].argnum2;
76           ap->alternative[i].argnum1_glib_context =
77             shapes->shapes[i].argnum1_glib_context;
78           ap->alternative[i].argnum2_glib_context =
79             shapes->shapes[i].argnum2_glib_context;
80           ap->alternative[i].argtotal = shapes->shapes[i].argtotal;
81           ap->alternative[i].xcomments = shapes->shapes[i].xcomments;
82           ap->alternative[i].msgctxt = NULL;
83           ap->alternative[i].msgctxt_pos.file_name = NULL;
84           ap->alternative[i].msgctxt_pos.line_number = (size_t)(-1);
85           ap->alternative[i].msgid = NULL;
86           ap->alternative[i].msgid_context = null_context;
87           ap->alternative[i].msgid_pos.file_name = NULL;
88           ap->alternative[i].msgid_pos.line_number = (size_t)(-1);
89           ap->alternative[i].msgid_comment = NULL;
90           ap->alternative[i].msgid_comment_is_utf8 = false;
91           ap->alternative[i].msgid_plural = NULL;
92           ap->alternative[i].msgid_plural_context = null_context;
93           ap->alternative[i].msgid_plural_pos.file_name = NULL;
94           ap->alternative[i].msgid_plural_pos.line_number = (size_t)(-1);
95         }
96 
97       return ap;
98     }
99 }
100 
101 
102 struct arglist_parser *
arglist_parser_clone(struct arglist_parser * ap)103 arglist_parser_clone (struct arglist_parser *ap)
104 {
105   struct arglist_parser *copy =
106     (struct arglist_parser *)
107     xmalloc (xsum (sizeof (struct arglist_parser) - sizeof (struct partial_call),
108                    xtimes (ap->nalternatives, sizeof (struct partial_call))));
109   size_t i;
110 
111   copy->mlp = ap->mlp;
112   copy->keyword = ap->keyword;
113   copy->keyword_len = ap->keyword_len;
114   copy->next_is_msgctxt = ap->next_is_msgctxt;
115   copy->nalternatives = ap->nalternatives;
116   for (i = 0; i < ap->nalternatives; i++)
117     {
118       const struct partial_call *cp = &ap->alternative[i];
119       struct partial_call *ccp = &copy->alternative[i];
120 
121       ccp->argnumc = cp->argnumc;
122       ccp->argnum1 = cp->argnum1;
123       ccp->argnum2 = cp->argnum2;
124       ccp->argnum1_glib_context = cp->argnum1_glib_context;
125       ccp->argnum2_glib_context = cp->argnum2_glib_context;
126       ccp->argtotal = cp->argtotal;
127       ccp->xcomments = cp->xcomments;
128       ccp->msgctxt =
129         (cp->msgctxt != NULL ? mixed_string_clone (cp->msgctxt) : NULL);
130       ccp->msgctxt_pos = cp->msgctxt_pos;
131       ccp->msgid = (cp->msgid != NULL ? mixed_string_clone (cp->msgid) : NULL);
132       ccp->msgid_context = cp->msgid_context;
133       ccp->msgid_pos = cp->msgctxt_pos;
134       ccp->msgid_comment = add_reference (cp->msgid_comment);
135       ccp->msgid_comment_is_utf8 = cp->msgid_comment_is_utf8;
136       ccp->msgid_plural =
137         (cp->msgid_plural != NULL ? mixed_string_clone (cp->msgid_plural) : NULL);
138       ccp->msgid_plural_context = cp->msgid_plural_context;
139       ccp->msgid_plural_pos = cp->msgid_plural_pos;
140     }
141 
142   return copy;
143 }
144 
145 
146 void
arglist_parser_remember(struct arglist_parser * ap,int argnum,mixed_string_ty * string,flag_context_ty context,char * file_name,size_t line_number,refcounted_string_list_ty * comment,bool comment_is_utf8)147 arglist_parser_remember (struct arglist_parser *ap,
148                          int argnum, mixed_string_ty *string,
149                          flag_context_ty context,
150                          char *file_name, size_t line_number,
151                          refcounted_string_list_ty *comment,
152                          bool comment_is_utf8)
153 {
154   bool stored_string = false;
155   size_t nalternatives = ap->nalternatives;
156   size_t i;
157 
158   if (!(argnum > 0))
159     abort ();
160   for (i = 0; i < nalternatives; i++)
161     {
162       struct partial_call *cp = &ap->alternative[i];
163 
164       if (argnum == cp->argnumc)
165         {
166           cp->msgctxt = string;
167           cp->msgctxt_pos.file_name = file_name;
168           cp->msgctxt_pos.line_number = line_number;
169           stored_string = true;
170           /* Mark msgctxt as done.  */
171           cp->argnumc = 0;
172         }
173       else
174         {
175           if (argnum == cp->argnum1)
176             {
177               cp->msgid = string;
178               cp->msgid_context = context;
179               cp->msgid_pos.file_name = file_name;
180               cp->msgid_pos.line_number = line_number;
181               cp->msgid_comment = add_reference (comment);
182               cp->msgid_comment_is_utf8 = comment_is_utf8;
183               stored_string = true;
184               /* Mark msgid as done.  */
185               cp->argnum1 = 0;
186             }
187           if (argnum == cp->argnum2)
188             {
189               cp->msgid_plural = string;
190               cp->msgid_plural_context = context;
191               cp->msgid_plural_pos.file_name = file_name;
192               cp->msgid_plural_pos.line_number = line_number;
193               stored_string = true;
194               /* Mark msgid_plural as done.  */
195               cp->argnum2 = 0;
196             }
197         }
198     }
199   /* Note: There is a memory leak here: When string was stored but is later
200      not used by arglist_parser_done, we don't free it.  */
201   if (!stored_string)
202     mixed_string_free (string);
203 }
204 
205 
206 void
arglist_parser_remember_msgctxt(struct arglist_parser * ap,mixed_string_ty * string,flag_context_ty context,char * file_name,size_t line_number)207 arglist_parser_remember_msgctxt (struct arglist_parser *ap,
208                                  mixed_string_ty *string,
209                                  flag_context_ty context,
210                                  char *file_name, size_t line_number)
211 {
212   bool stored_string = false;
213   size_t nalternatives = ap->nalternatives;
214   size_t i;
215 
216   for (i = 0; i < nalternatives; i++)
217     {
218       struct partial_call *cp = &ap->alternative[i];
219 
220       cp->msgctxt = string;
221       cp->msgctxt_pos.file_name = file_name;
222       cp->msgctxt_pos.line_number = line_number;
223       stored_string = true;
224       /* Mark msgctxt as done.  */
225       cp->argnumc = 0;
226     }
227   /* Note: There is a memory leak here: When string was stored but is later
228      not used by arglist_parser_done, we don't free it.  */
229   if (!stored_string)
230     mixed_string_free (string);
231 }
232 
233 
234 bool
arglist_parser_decidedp(struct arglist_parser * ap,int argnum)235 arglist_parser_decidedp (struct arglist_parser *ap, int argnum)
236 {
237   size_t i;
238 
239   /* Test whether all alternatives are decided.
240      Note: A decided alternative can be complete
241        cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
242        && cp->argtotal == 0
243      or it can be failed if no literal strings were found at the specified
244      argument positions:
245        cp->argnumc <= argnum && cp->argnum1 <= argnum && cp->argnum2 <= argnum
246      or it can be failed if the number of arguments is exceeded:
247        cp->argtotal > 0 && cp->argtotal < argnum
248    */
249   for (i = 0; i < ap->nalternatives; i++)
250     {
251       struct partial_call *cp = &ap->alternative[i];
252 
253       if (!((cp->argnumc <= argnum
254              && cp->argnum1 <= argnum
255              && cp->argnum2 <= argnum)
256             || (cp->argtotal > 0 && cp->argtotal < argnum)))
257         /* cp is still undecided.  */
258         return false;
259     }
260   return true;
261 }
262 
263 
264 void
arglist_parser_done(struct arglist_parser * ap,int argnum)265 arglist_parser_done (struct arglist_parser *ap, int argnum)
266 {
267   size_t ncomplete;
268   size_t i;
269 
270   /* Determine the number of complete calls.  */
271   ncomplete = 0;
272   for (i = 0; i < ap->nalternatives; i++)
273     {
274       struct partial_call *cp = &ap->alternative[i];
275 
276       if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
277           && (cp->argtotal == 0 || cp->argtotal == argnum))
278         ncomplete++;
279     }
280 
281   if (ncomplete > 0)
282     {
283       struct partial_call *best_cp = NULL;
284       bool ambiguous = false;
285 
286       /* Find complete calls where msgctxt, msgid, msgid_plural are all
287          provided.  */
288       for (i = 0; i < ap->nalternatives; i++)
289         {
290           struct partial_call *cp = &ap->alternative[i];
291 
292           if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
293               && (cp->argtotal == 0 || cp->argtotal == argnum)
294               && cp->msgctxt != NULL
295               && cp->msgid != NULL
296               && cp->msgid_plural != NULL)
297             {
298               if (best_cp != NULL)
299                 {
300                   ambiguous = true;
301                   break;
302                 }
303               best_cp = cp;
304             }
305         }
306 
307       if (best_cp == NULL)
308         {
309           struct partial_call *best_cp1 = NULL;
310           struct partial_call *best_cp2 = NULL;
311 
312           /* Find complete calls where msgctxt, msgid are provided.  */
313           for (i = 0; i < ap->nalternatives; i++)
314             {
315               struct partial_call *cp = &ap->alternative[i];
316 
317               if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
318                   && (cp->argtotal == 0 || cp->argtotal == argnum)
319                   && cp->msgctxt != NULL
320                   && cp->msgid != NULL)
321                 {
322                   if (best_cp1 != NULL)
323                     {
324                       ambiguous = true;
325                       break;
326                     }
327                   best_cp1 = cp;
328                 }
329             }
330 
331           /* Find complete calls where msgid, msgid_plural are provided.  */
332           for (i = 0; i < ap->nalternatives; i++)
333             {
334               struct partial_call *cp = &ap->alternative[i];
335 
336               if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
337                   && (cp->argtotal == 0 || cp->argtotal == argnum)
338                   && cp->msgid != NULL
339                   && cp->msgid_plural != NULL)
340                 {
341                   if (best_cp2 != NULL)
342                     {
343                       ambiguous = true;
344                       break;
345                     }
346                   best_cp2 = cp;
347                 }
348             }
349 
350           if (best_cp1 != NULL)
351             best_cp = best_cp1;
352           if (best_cp2 != NULL)
353             {
354               if (best_cp != NULL)
355                 ambiguous = true;
356               else
357                 best_cp = best_cp2;
358             }
359         }
360 
361       if (best_cp == NULL)
362         {
363           /* Find complete calls where msgid is provided.  */
364           for (i = 0; i < ap->nalternatives; i++)
365             {
366               struct partial_call *cp = &ap->alternative[i];
367 
368               if (cp->argnumc == 0 && cp->argnum1 == 0 && cp->argnum2 == 0
369                   && (cp->argtotal == 0 || cp->argtotal == argnum)
370                   && cp->msgid != NULL)
371                 {
372                   if (best_cp != NULL)
373                     {
374                       ambiguous = true;
375                       break;
376                     }
377                   best_cp = cp;
378                 }
379             }
380         }
381 
382       if (ambiguous)
383         {
384           error_with_progname = false;
385           error_at_line (0, 0,
386                          best_cp->msgid_pos.file_name,
387                          best_cp->msgid_pos.line_number,
388                          _("ambiguous argument specification for keyword '%.*s'"),
389                          (int) ap->keyword_len, ap->keyword);
390           error_with_progname = true;
391         }
392 
393       if (best_cp != NULL)
394         {
395           /* best_cp indicates the best found complete call.
396              Now call remember_a_message.  */
397           flag_context_ty msgid_context;
398           flag_context_ty msgid_plural_context;
399           char *best_msgctxt;
400           char *best_msgid;
401           char *best_msgid_plural;
402           message_ty *mp;
403 
404           msgid_context = best_cp->msgid_context;
405           msgid_plural_context = best_cp->msgid_plural_context;
406 
407           /* Special support for the 3-argument tr operator in Qt:
408              When --qt and --keyword=tr:1,1,2c,3t are specified, add to the
409              context the information that the argument is expected to be a
410              qt-plural-format.  */
411           if (recognize_qt_formatstrings ()
412               && best_cp->msgid_plural == best_cp->msgid)
413             {
414               msgid_context.is_format3 = yes_according_to_context;
415               msgid_plural_context.is_format3 = yes_according_to_context;
416             }
417 
418           best_msgctxt =
419             (best_cp->msgctxt != NULL
420              ? mixed_string_contents_free1 (best_cp->msgctxt)
421              : NULL);
422           best_msgid =
423             (best_cp->msgid != NULL
424              ? mixed_string_contents_free1 (best_cp->msgid)
425              : NULL);
426           best_msgid_plural =
427             (best_cp->msgid_plural != NULL
428              ? /* Special support for the 3-argument tr operator in Qt.  */
429                (best_cp->msgid_plural == best_cp->msgid
430                 ? xstrdup (best_msgid)
431                 : mixed_string_contents_free1 (best_cp->msgid_plural))
432              : NULL);
433 
434           /* Split strings in the GNOME glib syntax "msgctxt|msgid".  */
435           if (best_cp->argnum1_glib_context || best_cp->argnum2_glib_context)
436             /* split_keywordspec should not allow the context to be specified
437                in two different ways.  */
438             if (best_msgctxt != NULL)
439               abort ();
440           if (best_cp->argnum1_glib_context)
441             {
442               const char *separator = strchr (best_msgid, '|');
443 
444               if (separator == NULL)
445                 {
446                   error_with_progname = false;
447                   error_at_line (0, 0,
448                                  best_cp->msgid_pos.file_name,
449                                  best_cp->msgid_pos.line_number,
450                                  _("warning: missing context for keyword '%.*s'"),
451                                  (int) ap->keyword_len, ap->keyword);
452                   error_with_progname = true;
453                 }
454               else
455                 {
456                   size_t ctxt_len = separator - best_msgid;
457                   char *ctxt = XNMALLOC (ctxt_len + 1, char);
458 
459                   memcpy (ctxt, best_msgid, ctxt_len);
460                   ctxt[ctxt_len] = '\0';
461                   best_msgctxt = ctxt;
462                   best_msgid = xstrdup (separator + 1);
463                 }
464             }
465           if (best_msgid_plural != NULL && best_cp->argnum2_glib_context)
466             {
467               const char *separator = strchr (best_msgid_plural, '|');
468 
469               if (separator == NULL)
470                 {
471                   error_with_progname = false;
472                   error_at_line (0, 0,
473                                  best_cp->msgid_plural_pos.file_name,
474                                  best_cp->msgid_plural_pos.line_number,
475                                  _("warning: missing context for plural argument of keyword '%.*s'"),
476                                  (int) ap->keyword_len, ap->keyword);
477                   error_with_progname = true;
478                 }
479               else
480                 {
481                   size_t ctxt_len = separator - best_msgid_plural;
482                   char *ctxt = XNMALLOC (ctxt_len + 1, char);
483 
484                   memcpy (ctxt, best_msgid_plural, ctxt_len);
485                   ctxt[ctxt_len] = '\0';
486                   if (best_msgctxt == NULL)
487                     best_msgctxt = ctxt;
488                   else
489                     {
490                       if (strcmp (ctxt, best_msgctxt) != 0)
491                         {
492                           error_with_progname = false;
493                           error_at_line (0, 0,
494                                          best_cp->msgid_plural_pos.file_name,
495                                          best_cp->msgid_plural_pos.line_number,
496                                          _("context mismatch between singular and plural form"));
497                           error_with_progname = true;
498                         }
499                       free (ctxt);
500                     }
501                   best_msgid_plural = xstrdup (separator + 1);
502                 }
503             }
504 
505           mp = remember_a_message (ap->mlp, best_msgctxt, best_msgid, true,
506                                    best_msgid_plural != NULL,
507                                    msgid_context,
508                                    &best_cp->msgid_pos,
509                                    NULL, best_cp->msgid_comment,
510                                    best_cp->msgid_comment_is_utf8);
511           if (mp != NULL && best_msgid_plural != NULL)
512             remember_a_message_plural (mp, best_msgid_plural, true,
513                                        msgid_plural_context,
514                                        &best_cp->msgid_plural_pos,
515                                        NULL, false);
516 
517           if (best_cp->xcomments.nitems > 0)
518             {
519               /* Add best_cp->xcomments to mp->comment_dot, unless already
520                  present.  */
521               size_t i;
522 
523               for (i = 0; i < best_cp->xcomments.nitems; i++)
524                 {
525                   const char *xcomment = best_cp->xcomments.item[i];
526                   bool found = false;
527 
528                   if (mp != NULL && mp->comment_dot != NULL)
529                     {
530                       size_t j;
531 
532                       for (j = 0; j < mp->comment_dot->nitems; j++)
533                         if (strcmp (xcomment, mp->comment_dot->item[j]) == 0)
534                           {
535                             found = true;
536                             break;
537                           }
538                     }
539                   if (!found)
540                     message_comment_dot_append (mp, xcomment);
541                 }
542             }
543         }
544     }
545   else
546     {
547       /* No complete call was parsed.  */
548       /* Note: There is a memory leak here: When there is more than one
549          alternative, the same string can be stored in multiple alternatives,
550          and it's not easy to free all strings reliably.  */
551       if (ap->nalternatives == 1)
552         {
553           if (ap->alternative[0].msgctxt != NULL)
554             free (ap->alternative[0].msgctxt);
555           if (ap->alternative[0].msgid != NULL)
556             free (ap->alternative[0].msgid);
557           if (ap->alternative[0].msgid_plural != NULL)
558             free (ap->alternative[0].msgid_plural);
559         }
560     }
561 
562   for (i = 0; i < ap->nalternatives; i++)
563     drop_reference (ap->alternative[i].msgid_comment);
564   free (ap);
565 }
566