Coverage Report

Created: 2026-02-26 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/src/format-c++-brace.c
Line
Count
Source
1
/* C++ format strings.
2
   Copyright (C) 2003-2026 Free Software Foundation, Inc.
3
4
   This program is free software: you can redistribute it and/or modify
5
   it under the terms of the GNU General Public License as published by
6
   the Free Software Foundation; either version 3 of the License, or
7
   (at your option) any later version.
8
9
   This program is distributed in the hope that it will be useful,
10
   but WITHOUT ANY WARRANTY; without even the implied warranty of
11
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
   GNU General Public License for more details.
13
14
   You should have received a copy of the GNU General Public License
15
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16
17
/* Written by Bruno Haible.  */
18
19
#include <config.h>
20
21
#include <limits.h>
22
#include <stdbool.h>
23
#include <stdlib.h>
24
#include <string.h>
25
26
#include "format.h"
27
#include "c-ctype.h"
28
#include "xalloc.h"
29
#include "xvasprintf.h"
30
#include "format-invalid.h"
31
#include "gettext.h"
32
33
0
#define _(str) gettext (str)
34
35
/* C++ format strings are specified in ISO C++ 20.
36
   Reference:
37
     - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4861.pdf
38
       § 20.20 Formatting [format]
39
       corrected in
40
       https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4950.pdf
41
       § 22.14 Formatting [format]
42
     - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/n4861.pdf
43
       § 27.12 Formatting [time.format]
44
     - https://en.cppreference.com/w/cpp/utility/format/format
45
   Implemented in GCC 13.1, usable with option '-std=c++20' or '-std=gnu++20'.
46
47
   In GNU gettext, we don't support custom format directives.  See the
48
   documentation, section "Preparing Translatable Strings".
49
   Also, we don't support time formatting, because its translation should be
50
   looked up according to the locale's LC_TIME category, where gettext() and
51
   dgettext() use the LC_MESSAGES category.  Support for time format strings
52
   may be added later, separately, with a 'c++-time-format' tag.
53
54
   There are two kinds of directives: replacement fields and escape sequences.
55
   A replacement field
56
     - starts with '{',
57
     - is optionally followed by an "argument index" specification: a nonempty
58
       digit sequence without redundant leading zeroes,
59
     - is optionally followed by ':' and
60
       - optionally a "fill-and-align" specification:
61
         an optional character other than '{' and '}', followed by one of
62
         '<', '>', '^',
63
       - optionally a "sign" specification: one of '+', '-', ' ',
64
         (but only for an [integer], [float] argument or when the type is one
65
         of 'b', 'B', 'd', 'o', 'x', 'X')
66
       - optionally: '#',
67
         (but only for an [integer], [float] argument or when the type is one
68
         of 'b', 'B', 'd', 'o', 'x', 'X')
69
       - optionally: '0',
70
         (but only for an [integer], [float] argument or when the type is one
71
         of 'b', 'B', 'd', 'o', 'x', 'X')
72
       - optionally a "width" specification:
73
         - a nonempty digit sequence with the first digit being non-zero, or
74
         - '{',
75
           then an "argument index" specification: a digit sequence without
76
           redundant leading zeroes,
77
           then '}',
78
       - optionally a "precision" specification:
79
         - '.', then
80
           - a nonempty digit sequence, or
81
           - '{',
82
             then an "argument index" specification: a digit sequence without
83
             redundant leading zeroes,
84
             then '}',
85
           (but only for a [float], [string] argument)
86
       - optionally: 'L',
87
         (but only for an [integer], [float], [character], [bool] argument)
88
       - optionally a "type" specification: one of
89
         [integer, character, bool]  'b', 'B', 'd', 'o', 'x', 'X',
90
         [float]                     'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
91
         [integer, character]        'c',
92
         [string, bool]              's',
93
         [pointer]                   'p',
94
     - is finished by '}'.
95
   An escape sequence is either '{{' or '}}'.
96
   Numbered ({n} or {n:spec}) and unnumbered ({} or {:spec}) argument
97
   specifications cannot be used in the same string.
98
99
   The translator may add *argument index* specifications everywhere, i.e.
100
   convert all unnumbered argument specifications to numbered argument
101
   specifications.  Or vice versa.
102
103
   The translator may add or remove *fill-and-align* specifications, because
104
   they are valid for all types.
105
106
   The translator may add a *sign* specification when the type is one of
107
   'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
108
   but not when it is 'c' (because for [character] arguments, "{:c}" is valid
109
   but "{:-c}" is not).
110
   The translator may remove *sign* specifications.
111
112
   The translator may add a '#' flag when the type is one of
113
   'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
114
   but not when it is 'c' (because for [character] arguments, "{:c}" is valid
115
   but "{:#c}" is not).
116
   The translator may remove a '#' flag.
117
118
   The translator may add a '0' flag when the type is one of
119
   'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
120
   but not when it is 'c' (because for [character] arguments, "{:c}" is valid
121
   but "{:0c}" is not).
122
   The translator may remove a '0' flag.
123
124
   The translator may add or remove *width* specifications, because they are
125
   valid for all types.
126
127
   The translator may add a *precision* specification when the type is one of
128
   'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G',
129
   but not when it is 's' (because for [bool] arguments, "{:s}" is valid but
130
   "{:.9s}" is not).
131
   The translator may remove *precision* specifications.
132
133
   The translator may add an 'L' flag when the type is one of
134
   'b', 'B', 'd', 'o', 'x', 'X', 'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G', 'c',
135
   but not when it is 's' (because for [string] arguments, "{:s}" is valid but
136
   "{:Ls}" is not).
137
   The translator may remove an 'L' flag.
138
 */
139
140
/* Describes the valid argument types for a given directive.  */
141
enum format_arg_type
142
{
143
  FAT_INTEGER    = 1 << 0,
144
  FAT_FLOAT      = 1 << 1,
145
  FAT_CHARACTER  = 1 << 2,
146
  FAT_STRING     = 1 << 3,
147
  FAT_BOOL       = 1 << 4,
148
  FAT_POINTER    = 1 << 5,
149
  FAT_NONE       = 0,
150
  FAT_ANY        = (FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_STRING
151
                    | FAT_BOOL | FAT_POINTER)
152
};
153
154
struct numbered_arg
155
{
156
  /* The number of the argument, 0-based.  */
157
  size_t number;
158
159
  /* The type is a bit mask that is the logical OR of the 'enum format_arg_type'
160
     values that represent each possible argument types for the argument.
161
     We use this field in order to report an error in format_check for cases like
162
       #, c++-format
163
       msgid  "{:c}"
164
       msgstr "{:-c}"
165
     because "{:c}" is valid for argument type [integer, character], whereas
166
     "{:-c}" is valid only for [integer].  */
167
  unsigned int type;
168
169
  /* The presentation is a bit mask that is the logical OR of the
170
     'enum format_arg_type' values of the directives that reference
171
     the argument.  Here the "type" specifications are mapped like this:
172
       'b', 'B', 'd', 'o', 'x', 'X'            -> FAT_INTEGER
173
       'a', 'A', 'e', 'E', 'f', 'F', 'g', 'G'  -> FAT_FLOAT
174
       'c'                                     -> FAT_CHARACTER
175
       's'                                     -> FAT_STRING
176
       'p'                                     -> FAT_POINTER
177
     The use of this presentation field is that
178
       * We want to allow the translator to change e.g. an 'o' type to an 'x'
179
         type, or an 'e' type to a 'g' type, and so on.  (This is possible
180
         for c-format strings too.)
181
       * But we do not want to allow the translator to change e.g. a 'c' type
182
         to a 'd' type:
183
           #, c++-format
184
           msgid  "{:c}"
185
           msgstr "{:d}"
186
     We use this field in order to report an error in format_check.  */
187
  unsigned int presentation;
188
};
189
190
struct spec
191
{
192
  size_t directives;
193
  size_t numbered_arg_count;
194
  struct numbered_arg *numbered;
195
};
196
197
198
static int
199
numbered_arg_compare (const void *p1, const void *p2)
200
0
{
201
0
  size_t n1 = ((const struct numbered_arg *) p1)->number;
202
0
  size_t n2 = ((const struct numbered_arg *) p2)->number;
203
204
0
  return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
205
0
}
206
207
static void *
208
format_parse (const char *format, bool translated, char *fdi,
209
              char **invalid_reason)
210
0
{
211
0
  const char *const format_start = format;
212
213
0
  struct spec spec;
214
0
  spec.directives = 0;
215
0
  spec.numbered_arg_count = 0;
216
0
  spec.numbered = NULL;
217
0
  size_t numbered_allocated = 0;
218
0
  size_t unnumbered_arg_count = 0;
219
220
0
  for (; *format != '\0';)
221
    /* Invariant: spec.numbered_arg_count == 0 || unnumbered_arg_count == 0.  */
222
0
    if (*format == '{')
223
0
      {
224
0
        FDI_SET (format, FMTDIR_START);
225
0
        format++;
226
0
        spec.directives++;
227
0
        if (*format == '{')
228
          /* An escape sequence '{{'.  */
229
0
          ;
230
0
        else
231
0
          {
232
            /* A replacement field.  */
233
0
            size_t arg_array_index;
234
235
            /* Parse arg-id.  */
236
0
            if (c_isdigit (*format))
237
0
              {
238
                /* Numbered argument.  */
239
0
                unsigned int arg_id;
240
241
0
                arg_id = (*format - '0');
242
0
                if (*format == '0')
243
0
                  format++;
244
0
                else
245
0
                  {
246
0
                    format++;
247
0
                    while (c_isdigit (*format))
248
0
                      {
249
0
                        if (arg_id >= UINT_MAX / 10)
250
0
                          {
251
0
                            *invalid_reason =
252
0
                              xasprintf (_("In the directive number %zu, the arg-id is too large."), spec.directives);
253
0
                            FDI_SET (format, FMTDIR_ERROR);
254
0
                            goto bad_format;
255
0
                          }
256
                        /* Here arg_id <= floor(UINT_MAX/10) - 1.  */
257
0
                        arg_id = arg_id * 10 + (*format - '0');
258
                        /* Here arg_id < floor(UINT_MAX/10)*10 <= UINT_MAX.  */
259
0
                        format++;
260
0
                      }
261
0
                  }
262
263
                /* Numbered and unnumbered specifications are exclusive.  */
264
0
                if (unnumbered_arg_count > 0)
265
0
                  {
266
0
                    *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
267
0
                    FDI_SET (format - 1, FMTDIR_ERROR);
268
0
                    goto bad_format;
269
0
                  }
270
271
0
                if (numbered_allocated == spec.numbered_arg_count)
272
0
                  {
273
0
                    numbered_allocated = 2 * numbered_allocated + 1;
274
0
                    spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
275
0
                  }
276
0
                arg_array_index = spec.numbered_arg_count;
277
0
                spec.numbered[spec.numbered_arg_count].number = arg_id + 1;
278
0
                spec.numbered_arg_count++;
279
0
              }
280
0
            else
281
0
              {
282
                /* Unnumbered argument.  */
283
284
                /* Numbered and unnumbered specifications are exclusive.  */
285
0
                if (spec.numbered_arg_count > 0)
286
0
                  {
287
0
                    *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
288
0
                    FDI_SET (format - 1, FMTDIR_ERROR);
289
0
                    goto bad_format;
290
0
                  }
291
292
0
                if (numbered_allocated == unnumbered_arg_count)
293
0
                  {
294
0
                    numbered_allocated = 2 * numbered_allocated + 1;
295
0
                    spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
296
0
                  }
297
0
                arg_array_index = unnumbered_arg_count;
298
0
                spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
299
0
                unnumbered_arg_count++;
300
0
              }
301
302
0
            unsigned int type = FAT_ANY;
303
0
            unsigned int presentation = 0;
304
305
0
            if (*format == ':')
306
0
              {
307
0
                format++;
308
                /* Parse format-spec.  */
309
310
                /* Parse fill-and-align.  */
311
0
                if ((*format != '\0' && *format != '{' && *format != '}')
312
0
                    && (format[1] == '<' || format[1] == '>'
313
0
                        || format[1] == '^'))
314
0
                  format += 2;
315
0
                else if (*format == '<' || *format == '>' || *format == '^')
316
0
                  format++;
317
318
                /* Parse sign.  */
319
0
                bool have_sign = false;
320
0
                if (*format == '+' || *format == '-' || *format == ' ')
321
0
                  {
322
0
                    format++;
323
0
                    have_sign = true;
324
0
                  }
325
326
                /* Parse '#' flag.  */
327
0
                bool have_hash_flag = false;
328
0
                if (*format == '#')
329
0
                  {
330
0
                    format++;
331
0
                    have_hash_flag = true;
332
0
                  }
333
334
                /* Parse '0' flag.  */
335
0
                bool have_zero_flag = false;
336
0
                if (*format == '0')
337
0
                  {
338
0
                    format++;
339
0
                    have_zero_flag = true;
340
0
                  }
341
342
                /* Parse width.  */
343
0
                if (c_isdigit (*format) && *format != '0')
344
0
                  {
345
0
                    do
346
0
                      format++;
347
0
                    while (c_isdigit (*format));
348
0
                  }
349
0
                else if (*format == '{')
350
0
                  {
351
0
                    format++;
352
0
                    if (c_isdigit (*format))
353
0
                      {
354
                        /* Numbered argument.  */
355
0
                        unsigned int width_arg_id;
356
357
0
                        width_arg_id = (*format - '0');
358
0
                        if (*format == '0')
359
0
                          format++;
360
0
                        else
361
0
                          {
362
0
                            format++;
363
0
                            while (c_isdigit (*format))
364
0
                              {
365
0
                                if (width_arg_id >= UINT_MAX / 10)
366
0
                                  {
367
0
                                    *invalid_reason =
368
0
                                      xasprintf (_("In the directive number %zu, the width's arg-id is too large."), spec.directives);
369
0
                                    FDI_SET (format, FMTDIR_ERROR);
370
0
                                    goto bad_format;
371
0
                                  }
372
                                /* Here width_arg_id <= floor(UINT_MAX/10) - 1.  */
373
0
                                width_arg_id = width_arg_id * 10 + (*format - '0');
374
                                /* Here width_arg_id < floor(UINT_MAX/10)*10 <= UINT_MAX.  */
375
0
                                format++;
376
0
                              }
377
0
                          }
378
379
                        /* Numbered and unnumbered specifications are exclusive.  */
380
0
                        if (unnumbered_arg_count > 0)
381
0
                          {
382
0
                            *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
383
0
                            FDI_SET (format - 1, FMTDIR_ERROR);
384
0
                            goto bad_format;
385
0
                          }
386
387
0
                        if (numbered_allocated == spec.numbered_arg_count)
388
0
                          {
389
0
                            numbered_allocated = 2 * numbered_allocated + 1;
390
0
                            spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
391
0
                          }
392
0
                        spec.numbered[spec.numbered_arg_count].number = width_arg_id + 1;
393
0
                        spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
394
0
                        spec.numbered[spec.numbered_arg_count].presentation = 0;
395
0
                        spec.numbered_arg_count++;
396
0
                      }
397
0
                    else
398
0
                      {
399
                        /* Unnumbered argument.  */
400
401
                        /* Numbered and unnumbered specifications are exclusive.  */
402
0
                        if (spec.numbered_arg_count > 0)
403
0
                          {
404
0
                            *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
405
0
                            FDI_SET (format - 1, FMTDIR_ERROR);
406
0
                            goto bad_format;
407
0
                          }
408
409
0
                        if (numbered_allocated == unnumbered_arg_count)
410
0
                          {
411
0
                            numbered_allocated = 2 * numbered_allocated + 1;
412
0
                            spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
413
0
                          }
414
0
                        spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
415
0
                        spec.numbered[unnumbered_arg_count].type = FAT_INTEGER;
416
0
                        spec.numbered[unnumbered_arg_count].presentation = 0;
417
0
                        unnumbered_arg_count++;
418
0
                      }
419
420
0
                    if (*format != '}')
421
0
                      {
422
0
                        *invalid_reason =
423
0
                          xasprintf (_("In the directive number %zu, the width's arg-id is not terminated through '}'."), spec.directives);
424
0
                        FDI_SET (format - 1, FMTDIR_ERROR);
425
0
                        goto bad_format;
426
0
                      }
427
0
                    format++;
428
0
                  }
429
430
                /* Parse precision.  */
431
0
                bool have_precision = false;
432
0
                if (*format == '.')
433
0
                  {
434
0
                    format++;
435
436
0
                    if (c_isdigit (*format))
437
0
                      {
438
0
                        do
439
0
                          format++;
440
0
                        while (c_isdigit (*format));
441
442
0
                        have_precision = true;
443
0
                      }
444
0
                    else if (*format == '{')
445
0
                      {
446
0
                        format++;
447
0
                        if (c_isdigit (*format))
448
0
                          {
449
                            /* Numbered argument.  */
450
0
                            unsigned int precision_arg_id;
451
452
0
                            precision_arg_id = (*format - '0');
453
0
                            if (*format == '0')
454
0
                              format++;
455
0
                            else
456
0
                              {
457
0
                                format++;
458
0
                                while (c_isdigit (*format))
459
0
                                  {
460
0
                                    if (precision_arg_id >= UINT_MAX / 10)
461
0
                                      {
462
0
                                        *invalid_reason =
463
0
                                          xasprintf (_("In the directive number %zu, the width's arg-id is too large."), spec.directives);
464
0
                                        FDI_SET (format, FMTDIR_ERROR);
465
0
                                        goto bad_format;
466
0
                                      }
467
                                    /* Here precision_arg_id <= floor(UINT_MAX/10) - 1.  */
468
0
                                    precision_arg_id = precision_arg_id * 10 + (*format - '0');
469
                                    /* Here precision_arg_id < floor(UINT_MAX/10)*10 <= UINT_MAX.  */
470
0
                                    format++;
471
0
                                  }
472
0
                              }
473
474
                            /* Numbered and unnumbered specifications are exclusive.  */
475
0
                            if (unnumbered_arg_count > 0)
476
0
                              {
477
0
                                *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
478
0
                                FDI_SET (format - 1, FMTDIR_ERROR);
479
0
                                goto bad_format;
480
0
                              }
481
482
0
                            if (numbered_allocated == spec.numbered_arg_count)
483
0
                              {
484
0
                                numbered_allocated = 2 * numbered_allocated + 1;
485
0
                                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
486
0
                              }
487
0
                            spec.numbered[spec.numbered_arg_count].number = precision_arg_id + 1;
488
0
                            spec.numbered[spec.numbered_arg_count].type = FAT_INTEGER;
489
0
                            spec.numbered[spec.numbered_arg_count].presentation = 0;
490
0
                            spec.numbered_arg_count++;
491
0
                          }
492
0
                        else
493
0
                          {
494
                            /* Unnumbered argument.  */
495
496
                            /* Numbered and unnumbered specifications are exclusive.  */
497
0
                            if (spec.numbered_arg_count > 0)
498
0
                              {
499
0
                                *invalid_reason = INVALID_MIXES_NUMBERED_UNNUMBERED ();
500
0
                                FDI_SET (format - 1, FMTDIR_ERROR);
501
0
                                goto bad_format;
502
0
                              }
503
504
0
                            if (numbered_allocated == unnumbered_arg_count)
505
0
                              {
506
0
                                numbered_allocated = 2 * numbered_allocated + 1;
507
0
                                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
508
0
                              }
509
0
                            spec.numbered[unnumbered_arg_count].number = unnumbered_arg_count + 1;
510
0
                            spec.numbered[unnumbered_arg_count].type = FAT_INTEGER;
511
0
                            spec.numbered[unnumbered_arg_count].presentation = 0;
512
0
                            unnumbered_arg_count++;
513
0
                          }
514
515
0
                        if (*format != '}')
516
0
                          {
517
0
                            *invalid_reason =
518
0
                              xasprintf (_("In the directive number %zu, the precision's arg-id is not terminated through '}'."), spec.directives);
519
0
                            FDI_SET (format - 1, FMTDIR_ERROR);
520
0
                            goto bad_format;
521
0
                          }
522
0
                        format++;
523
524
0
                        have_precision = true;
525
0
                      }
526
0
                    else
527
0
                      format--;
528
0
                  }
529
530
                /* Parse 'L' flag.  */
531
0
                bool have_L_flag = false;
532
0
                if (*format == 'L')
533
0
                  {
534
0
                    format++;
535
0
                    have_L_flag = true;
536
0
                  }
537
538
                /* Parse type.  */
539
0
                char type_spec = '\0';
540
0
                if (*format != '\0' && *format != '}')
541
0
                  {
542
0
                    type_spec = *format;
543
544
0
                    switch (type_spec)
545
0
                      {
546
0
                      case 'b': case 'B':
547
0
                      case 'd':
548
0
                      case 'o':
549
0
                      case 'x': case 'X':
550
0
                        type = FAT_INTEGER | FAT_CHARACTER | FAT_BOOL;
551
0
                        presentation = FAT_INTEGER;
552
0
                        break;
553
554
0
                      case 'a': case 'A':
555
0
                      case 'e': case 'E':
556
0
                      case 'f': case 'F':
557
0
                      case 'g': case 'G':
558
0
                        type = FAT_FLOAT;
559
0
                        presentation = FAT_FLOAT;
560
0
                        break;
561
562
0
                      case 'c':
563
0
                        type = FAT_INTEGER | FAT_CHARACTER;
564
0
                        presentation = FAT_CHARACTER;
565
0
                        break;
566
567
0
                      case 's':
568
0
                        type = FAT_STRING | FAT_BOOL;
569
0
                        presentation = FAT_STRING;
570
0
                        break;
571
572
0
                      case 'p':
573
0
                        type = FAT_POINTER;
574
0
                        presentation = FAT_POINTER;
575
0
                        break;
576
577
0
                      default:
578
0
                        *invalid_reason =
579
0
                          (c_isprint (type_spec)
580
0
                           ? xasprintf (_("In the directive number %zu, the character '%c' is not a standard type specifier."), spec.directives, type_spec)
581
0
                           : xasprintf (_("The character that terminates the directive number %zu is not a standard type specifier."), spec.directives));
582
0
                        FDI_SET (format, FMTDIR_ERROR);
583
0
                        goto bad_format;
584
0
                      }
585
586
0
                    if (have_sign && (type & (FAT_INTEGER | FAT_FLOAT)) == 0)
587
0
                      {
588
0
                        *invalid_reason =
589
0
                          xasprintf (_("In the directive number %zu, the sign specification is incompatible with the type specifier '%c'."), spec.directives, type_spec);
590
0
                        FDI_SET (format, FMTDIR_ERROR);
591
0
                        goto bad_format;
592
0
                      }
593
594
0
                    if (have_hash_flag && (type & (FAT_INTEGER | FAT_FLOAT)) == 0)
595
0
                      {
596
0
                        *invalid_reason =
597
0
                          xasprintf (_("In the directive number %zu, the '#' option is incompatible with the type specifier '%c'."), spec.directives, type_spec);
598
0
                        FDI_SET (format, FMTDIR_ERROR);
599
0
                        goto bad_format;
600
0
                      }
601
602
0
                    if (have_zero_flag && (type & (FAT_INTEGER | FAT_FLOAT)) == 0)
603
0
                      {
604
0
                        *invalid_reason =
605
0
                          xasprintf (_("In the directive number %zu, the '0' option is incompatible with the type specifier '%c'."), spec.directives, type_spec);
606
0
                        FDI_SET (format, FMTDIR_ERROR);
607
0
                        goto bad_format;
608
0
                      }
609
610
0
                    if (have_precision && (type & (FAT_FLOAT | FAT_STRING)) == 0)
611
0
                      {
612
0
                        *invalid_reason =
613
0
                          xasprintf (_("In the directive number %zu, the precision specification is incompatible with the type specifier '%c'."), spec.directives, type_spec);
614
0
                        FDI_SET (format, FMTDIR_ERROR);
615
0
                        goto bad_format;
616
0
                      }
617
618
0
                    if (have_L_flag && (type & (FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_BOOL)) == 0)
619
0
                      {
620
0
                        *invalid_reason =
621
0
                          xasprintf (_("In the directive number %zu, the 'L' option is incompatible with the type specifier '%c'."), spec.directives, type_spec);
622
0
                        FDI_SET (format, FMTDIR_ERROR);
623
0
                        goto bad_format;
624
0
                      }
625
626
0
                    format++;
627
0
                  }
628
629
0
                if (have_sign)
630
0
                  {
631
                    /* Citing ISO C++ 20:
632
                       "The sign option is only valid for arithmetic types other
633
                        than charT and bool or when an integer presentation type
634
                        is specified."  */
635
0
                    switch (type_spec)
636
0
                      {
637
0
                      case 'b': case 'B':
638
0
                      case 'd':
639
0
                      case 'o':
640
0
                      case 'x': case 'X':
641
0
                        break;
642
0
                      default:
643
                        /* No type_spec:     FAT_ANY                     -> FAT_INTEGER | FAT_FLOAT
644
                           type_spec = 'c':  FAT_INTEGER | FAT_CHARACTER -> FAT_INTEGER
645
                          */
646
0
                        type = type & (FAT_INTEGER | FAT_FLOAT);
647
0
                        break;
648
0
                      }
649
0
                  }
650
651
0
                if (have_hash_flag)
652
0
                  {
653
                    /* Citing ISO C++ 20:
654
                       "The # option ... This option is valid for arithmetic
655
                        types other than charT and bool or when an integer
656
                        presentation type is specified, and not otherwise."  */
657
0
                    switch (type_spec)
658
0
                      {
659
0
                      case 'b': case 'B':
660
0
                      case 'd':
661
0
                      case 'o':
662
0
                      case 'x': case 'X':
663
0
                        break;
664
0
                      default:
665
                        /* No type_spec:     FAT_ANY                     -> FAT_INTEGER | FAT_FLOAT
666
                           type_spec = 'c':  FAT_INTEGER | FAT_CHARACTER -> FAT_INTEGER
667
                          */
668
0
                        type = type & (FAT_INTEGER | FAT_FLOAT);
669
0
                        break;
670
0
                      }
671
0
                  }
672
673
0
                if (have_zero_flag)
674
0
                  {
675
                    /* Citing ISO C++ 20:
676
                       "A zero (0) character preceding the width field ... This
677
                        option is only valid for arithmetic types other than
678
                        charT and bool or when an integer presentation type is
679
                        specified."  */
680
0
                    switch (type_spec)
681
0
                      {
682
0
                      case 'b': case 'B':
683
0
                      case 'd':
684
0
                      case 'o':
685
0
                      case 'x': case 'X':
686
0
                        break;
687
0
                      default:
688
                        /* No type_spec:     FAT_ANY                     -> FAT_INTEGER | FAT_FLOAT
689
                           type_spec = 'c':  FAT_INTEGER | FAT_CHARACTER -> FAT_INTEGER
690
                          */
691
0
                        type = type & (FAT_INTEGER | FAT_FLOAT);
692
0
                        break;
693
0
                      }
694
0
                  }
695
696
0
                if (have_precision)
697
0
                  {
698
                    /* Citing ISO C++ 20:
699
                       "The nonnegative-integer in precision ... It can only be
700
                        used with floating-point and string types."  */
701
                    /* No type_spec:     FAT_ANY               -> FAT_FLOAT | FAT_STRING
702
                       type_spec = 's':  FAT_STRING | FAT_BOOL -> FAT_STRING
703
                      */
704
0
                    type = type & (FAT_FLOAT | FAT_STRING);
705
0
                  }
706
707
0
                if (have_L_flag)
708
0
                  {
709
                    /* Citing ISO C++ 20:
710
                       "The L option is only valid for arithmetic types"  */
711
                    /* No type_spec:     FAT_ANY               -> FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_BOOL
712
                       type_spec = 's':  FAT_STRING | FAT_BOOL -> FAT_BOOL
713
                     */
714
0
                    type = type & (FAT_INTEGER | FAT_FLOAT | FAT_CHARACTER | FAT_BOOL);
715
0
                  }
716
717
                /* If no possible type is left for the directive, e.g. in the
718
                   format string "{:.9Ls}", the format string is invalid.  */
719
0
                if (type == FAT_NONE)
720
0
                  {
721
0
                    *invalid_reason =
722
0
                      xasprintf (_("The directive number %zu, with all of its options, is not applicable to any type."), spec.directives);
723
0
                    FDI_SET (format - 1, FMTDIR_ERROR);
724
0
                    goto bad_format;
725
0
                  }
726
0
              }
727
728
0
            spec.numbered[arg_array_index].type = type;
729
0
            spec.numbered[arg_array_index].presentation = presentation;
730
731
0
            if (*format == '\0')
732
0
              {
733
0
                *invalid_reason =
734
0
                  xasprintf (_("The string ends in the middle of the directive number %zu."), spec.directives);
735
0
                FDI_SET (format - 1, FMTDIR_ERROR);
736
0
                goto bad_format;
737
0
              }
738
739
0
            if (*format != '}')
740
0
              {
741
0
                *invalid_reason =
742
0
                  xasprintf (_("The directive number %zu is not terminated through '}'."), spec.directives);
743
0
                FDI_SET (format - 1, FMTDIR_ERROR);
744
0
                goto bad_format;
745
0
              }
746
0
          }
747
748
0
        FDI_SET (format, FMTDIR_END);
749
750
0
        format++;
751
0
      }
752
0
    else if (*format == '}')
753
0
      {
754
0
        FDI_SET (format, FMTDIR_START);
755
0
        format++;
756
0
        spec.directives++;
757
0
        if (*format == '}')
758
          /* An escape sequence '}}'.  */
759
0
          ;
760
0
        else
761
0
          {
762
0
            *invalid_reason =
763
0
              (spec.directives == 0
764
0
               ? xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'."))
765
0
               : xasprintf (_("The string contains a lone '}' after directive number %zu."), spec.directives));
766
0
            FDI_SET (*format == '\0' ? format - 1 : format, FMTDIR_ERROR);
767
0
            goto bad_format;
768
0
          }
769
770
0
        FDI_SET (format, FMTDIR_END);
771
772
0
        format++;
773
0
      }
774
0
    else
775
0
      format++;
776
777
  /* Convert the unnumbered argument array to numbered arguments.  */
778
0
  if (unnumbered_arg_count > 0)
779
0
    spec.numbered_arg_count = unnumbered_arg_count;
780
  /* Sort the numbered argument array, and eliminate duplicates.  */
781
0
  else if (spec.numbered_arg_count > 1)
782
0
    {
783
0
      qsort (spec.numbered, spec.numbered_arg_count,
784
0
             sizeof (struct numbered_arg), numbered_arg_compare);
785
786
      /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
787
0
      bool err = false;
788
0
      size_t i, j;
789
0
      for (i = j = 0; i < spec.numbered_arg_count; i++)
790
0
        if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
791
0
          {
792
0
            unsigned int type1 = spec.numbered[i].type;
793
0
            unsigned int type2 = spec.numbered[j-1].type;
794
795
0
            unsigned int type_both = type1 & type2;
796
797
0
            if (type_both == FAT_NONE)
798
0
              {
799
                /* Incompatible types.  */
800
0
                if (!err)
801
0
                  *invalid_reason =
802
0
                    INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
803
0
                err = true;
804
0
              }
805
806
0
            spec.numbered[j-1].type = type_both;
807
0
            spec.numbered[j-1].presentation =
808
0
              spec.numbered[i].presentation | spec.numbered[j-1].presentation;
809
0
          }
810
0
        else
811
0
          {
812
0
            if (j < i)
813
0
              {
814
0
                spec.numbered[j].number = spec.numbered[i].number;
815
0
                spec.numbered[j].type = spec.numbered[i].type;
816
0
                spec.numbered[j].presentation = spec.numbered[i].presentation;
817
0
              }
818
0
            j++;
819
0
          }
820
0
      spec.numbered_arg_count = j;
821
0
      if (err)
822
        /* *invalid_reason has already been set above.  */
823
0
        goto bad_format;
824
0
    }
825
826
0
  struct spec *result = XMALLOC (struct spec);
827
0
  *result = spec;
828
0
  return result;
829
830
0
 bad_format:
831
0
  if (spec.numbered != NULL)
832
0
    free (spec.numbered);
833
0
  return NULL;
834
0
}
835
836
static void
837
format_free (void *descr)
838
0
{
839
0
  struct spec *spec = (struct spec *) descr;
840
841
0
  if (spec->numbered != NULL)
842
0
    free (spec->numbered);
843
0
  free (spec);
844
0
}
845
846
static int
847
format_get_number_of_directives (void *descr)
848
0
{
849
0
  struct spec *spec = (struct spec *) descr;
850
851
0
  return spec->directives;
852
0
}
853
854
/* Size of buffer needed for type_description result.  */
855
0
#define MAX_TYPE_DESCRIPTION_LEN (50 + 1)
856
/* Returns a textual description of TYPE in BUF.  */
857
static void
858
get_type_description (char buf[MAX_TYPE_DESCRIPTION_LEN], unsigned int type)
859
0
{
860
0
  char *p = buf;
861
0
  bool first = true;
862
863
0
  p = stpcpy (p, "[");
864
0
  if (type & FAT_INTEGER)
865
0
    {
866
0
      if (!first)
867
0
        p = stpcpy (p, ", ");
868
0
      p = stpcpy (p, "integer");
869
0
      first = false;
870
0
    }
871
0
  if (type & FAT_FLOAT)
872
0
    {
873
0
      if (!first)
874
0
        p = stpcpy (p, ", ");
875
0
      p = stpcpy (p, "float");
876
0
      first = false;
877
0
    }
878
0
  if (type & FAT_CHARACTER)
879
0
    {
880
0
      if (!first)
881
0
        p = stpcpy (p, ", ");
882
0
      p = stpcpy (p, "character");
883
0
      first = false;
884
0
    }
885
0
  if (type & FAT_STRING)
886
0
    {
887
0
      if (!first)
888
0
        p = stpcpy (p, ", ");
889
0
      p = stpcpy (p, "string");
890
0
      first = false;
891
0
    }
892
0
  if (type & FAT_BOOL)
893
0
    {
894
0
      if (!first)
895
0
        p = stpcpy (p, ", ");
896
0
      p = stpcpy (p, "bool");
897
0
      first = false;
898
0
    }
899
0
  if (type & FAT_POINTER)
900
0
    {
901
0
      if (!first)
902
0
        p = stpcpy (p, ", ");
903
0
      p = stpcpy (p, "pointer");
904
0
      first = false;
905
0
    }
906
0
  p = stpcpy (p, "]");
907
0
  *p++ = '\0';
908
  /* Verify that the buffer was large enough.  */
909
0
  if (p - buf > MAX_TYPE_DESCRIPTION_LEN)
910
0
    abort ();
911
0
}
912
913
static bool
914
format_check (void *msgid_descr, void *msgstr_descr, bool equality,
915
              formatstring_error_logger_t error_logger, void *error_logger_data,
916
              const char *pretty_msgid, const char *pretty_msgstr)
917
0
{
918
0
  struct spec *spec1 = (struct spec *) msgid_descr;
919
0
  struct spec *spec2 = (struct spec *) msgstr_descr;
920
0
  bool err = false;
921
922
0
  if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
923
0
    {
924
0
      size_t n1 = spec1->numbered_arg_count;
925
0
      size_t n2 = spec2->numbered_arg_count;
926
927
      /* Check that the argument numbers are the same.
928
         Both arrays are sorted.  We search for the first difference.  */
929
0
      {
930
0
        size_t i, j;
931
0
        for (i = 0, j = 0; i < n1 || j < n2; )
932
0
          {
933
0
            int cmp = (i >= n1 ? 1 :
934
0
                       j >= n2 ? -1 :
935
0
                       spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
936
0
                       spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
937
0
                       0);
938
939
0
            if (cmp > 0)
940
0
              {
941
0
                if (error_logger)
942
0
                  error_logger (error_logger_data,
943
0
                                _("a format specification for argument %zu, as in '%s', doesn't exist in '%s'"),
944
0
                                spec2->numbered[j].number, pretty_msgstr,
945
0
                                pretty_msgid);
946
0
                err = true;
947
0
                break;
948
0
              }
949
0
            else if (cmp < 0)
950
0
              {
951
0
                if (equality)
952
0
                  {
953
0
                    if (error_logger)
954
0
                      error_logger (error_logger_data,
955
0
                                    _("a format specification for argument %zu doesn't exist in '%s'"),
956
0
                                    spec1->numbered[i].number, pretty_msgstr);
957
0
                    err = true;
958
0
                    break;
959
0
                  }
960
0
                else
961
0
                  i++;
962
0
              }
963
0
            else
964
0
              j++, i++;
965
0
          }
966
0
      }
967
      /* Check that the argument types are not being restricted in the msgstr,
968
         and that the presentation does not get changed in the msgstr.  */
969
0
      if (!err)
970
0
        {
971
0
          size_t i, j;
972
0
          for (i = 0, j = 0; j < n2; )
973
0
            {
974
0
              if (spec1->numbered[i].number == spec2->numbered[j].number)
975
0
                {
976
0
                  unsigned int type_difference = spec1->numbered[i].type & ~spec2->numbered[j].type;
977
0
                  if (type_difference != 0)
978
0
                    {
979
0
                      if (error_logger)
980
0
                        {
981
0
                          char buf[MAX_TYPE_DESCRIPTION_LEN];
982
0
                          get_type_description (buf, type_difference);
983
0
                          error_logger (error_logger_data,
984
0
                                        _("The format specification for argument %zu in '%s' is applicable to the types %s, but the format specification for argument %zu in '%s' is not."),
985
0
                                        spec1->numbered[i].number, pretty_msgid, buf,
986
0
                                        spec2->numbered[j].number, pretty_msgstr);
987
0
                        }
988
0
                      err = true;
989
0
                      break;
990
0
                    }
991
0
                  unsigned int presentation_difference =
992
0
                    spec2->numbered[j].presentation & ~spec1->numbered[i].presentation;
993
0
                  if (presentation_difference != 0)
994
0
                    {
995
0
                      if (error_logger)
996
0
                        error_logger (error_logger_data,
997
0
                                      _("The format specification for argument %zu in '%s' uses a different presentation than the format specification for argument %zu in '%s'."),
998
0
                                      spec2->numbered[j].number, pretty_msgstr,
999
0
                                      spec1->numbered[i].number, pretty_msgid);
1000
0
                      err = true;
1001
0
                      break;
1002
0
                    }
1003
0
                  j++, i++;
1004
0
                }
1005
0
              else
1006
0
                i++;
1007
0
            }
1008
0
        }
1009
0
    }
1010
1011
0
  return err;
1012
0
}
1013
1014
1015
struct formatstring_parser formatstring_cplusplus_brace =
1016
{
1017
  format_parse,
1018
  format_free,
1019
  format_get_number_of_directives,
1020
  NULL,
1021
  format_check
1022
};
1023
1024
1025
#ifdef TEST
1026
1027
/* Test program: Print the argument list specification returned by
1028
   format_parse for strings read from standard input.  */
1029
1030
#include <stdio.h>
1031
1032
static void
1033
format_print (void *descr)
1034
{
1035
  struct spec *spec = (struct spec *) descr;
1036
1037
  if (spec == NULL)
1038
    {
1039
      printf ("INVALID");
1040
      return;
1041
    }
1042
1043
  printf ("(");
1044
  size_t last = 1;
1045
  for (size_t i = 0; i < spec->numbered_arg_count; i++)
1046
    {
1047
      size_t number = spec->numbered[i].number;
1048
1049
      if (i > 0)
1050
        printf (" ");
1051
      if (number < last)
1052
        abort ();
1053
      for (; last < number; last++)
1054
        printf ("_ ");
1055
      {
1056
        char buf[MAX_TYPE_DESCRIPTION_LEN];
1057
        get_type_description (buf, spec->numbered[i].type);
1058
        printf ("%s", buf);
1059
      }
1060
      last = number + 1;
1061
    }
1062
  printf (")");
1063
}
1064
1065
int
1066
main ()
1067
{
1068
  for (;;)
1069
    {
1070
      char *line = NULL;
1071
      size_t line_size = 0;
1072
      int line_len = getline (&line, &line_size, stdin);
1073
      if (line_len < 0)
1074
        break;
1075
      if (line_len > 0 && line[line_len - 1] == '\n')
1076
        line[--line_len] = '\0';
1077
1078
      char *invalid_reason = NULL;
1079
      void *descr = format_parse (line, false, NULL, &invalid_reason);
1080
1081
      format_print (descr);
1082
      printf ("\n");
1083
      if (descr == NULL)
1084
        printf ("%s\n", invalid_reason);
1085
1086
      free (invalid_reason);
1087
      free (line);
1088
    }
1089
1090
  return 0;
1091
}
1092
1093
/*
1094
 * For Emacs M-x compile
1095
 * Local Variables:
1096
 * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DTEST format-c++-brace.c ../gnulib-lib/libgettextlib.la"
1097
 * End:
1098
 */
1099
1100
#endif /* TEST */