Coverage Report

Created: 2026-08-17 07:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/src/format-gfc-internal.c
Line
Count
Source
1
/* GFC (GNU Fortran Compiler) internal 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 <stdbool.h>
22
#include <stdlib.h>
23
24
#include "format.h"
25
#include "attribute.h"
26
#include "c-ctype.h"
27
#include "xalloc.h"
28
#include "xvasprintf.h"
29
#include "format-invalid.h"
30
#include "gettext.h"
31
32
0
#define _(str) gettext (str)
33
34
/* GFC internal format strings consist of format directives that are specific
35
   to the GNU Fortran Compiler frontend of GCC, implemented in
36
   gcc-4.3.3/gcc/fortran/error.c (function error_print).
37
38
   A directive
39
   - starts with '%',
40
   - either is finished by '%', that needs no argument,
41
   - or is continued like this:
42
       - optionally 'm$' where m is a positive integer,
43
       - finished by a specifier
44
           - 'C', that needs no argument but uses a particular variable
45
                  (but for the purposes of 'm$' numbering it consumes an
46
                  argument nevertheless, of 'void' type),
47
           - 'L', that needs a 'locus *' argument,
48
           - 'i', 'd', that need a signed integer argument,
49
           - 'u', that needs an unsigned integer argument,
50
           - 'li', 'ld', that need a signed long integer argument,
51
           - 'lu', that needs an unsigned long integer argument,
52
           - 'c', that needs a character argument,
53
           - 's', that needs a string argument.
54
55
   Numbered ('%m$') and unnumbered argument specifications can be used in the
56
   same string. The effect of '%m$' is to set the current argument number to
57
   m. The current argument number is incremented after processing a directive.
58
59
   When numbered argument specifications are used, specifying the Nth argument
60
   requires that all the leading arguments, from the first to the (N-1)th, are
61
   specified in the format string.
62
63
   These gfc-internal format strings were removed from GCC on 2024-10-11 (for
64
   GCC 15.1.0).  */
65
66
enum format_arg_type
67
{
68
  FAT_NONE              = 0,
69
  /* Basic types */
70
  FAT_VOID              = 1,
71
  FAT_INTEGER           = 2,
72
  FAT_CHAR              = 3,
73
  FAT_STRING            = 4,
74
  FAT_LOCUS             = 5,
75
  /* Flags */
76
  FAT_UNSIGNED          = 1 << 3,
77
  FAT_SIZE_LONG         = 1 << 4,
78
  /* Bitmasks */
79
  FAT_SIZE_MASK         = FAT_SIZE_LONG
80
};
81
#ifdef __cplusplus
82
typedef int format_arg_type_t;
83
#else
84
typedef enum format_arg_type format_arg_type_t;
85
#endif
86
87
struct numbered_arg
88
{
89
  size_t number;
90
  format_arg_type_t type;
91
};
92
93
struct unnumbered_arg
94
{
95
  format_arg_type_t type;
96
};
97
98
struct spec
99
{
100
  size_t directives;
101
  size_t unnumbered_arg_count;
102
  struct unnumbered_arg *unnumbered
103
    COUNTED_BY (unnumbered_arg_count);
104
  bool uses_currentloc;
105
};
106
107
108
static int
109
numbered_arg_compare (const void *p1, const void *p2)
110
0
{
111
0
  size_t n1 = ((const struct numbered_arg *) p1)->number;
112
0
  size_t n2 = ((const struct numbered_arg *) p2)->number;
113
114
0
  return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
115
0
}
116
117
static void *
118
format_parse (const char *format, bool translated, char *fdi,
119
              char **invalid_reason)
120
0
{
121
0
  const char *const format_start = format;
122
123
0
  struct spec spec;
124
0
  spec.directives = 0;
125
0
  size_t numbered_arg_count = 0;
126
0
  size_t numbered_allocated = 0;
127
0
  struct numbered_arg *numbered = NULL;
128
0
  spec.uses_currentloc = false;
129
0
  size_t number = 1;
130
131
0
  for (; *format != '\0';)
132
0
    if (*format++ == '%')
133
0
      {
134
        /* A directive.  */
135
0
        FDI_SET (format - 1, FMTDIR_START);
136
0
        spec.directives++;
137
138
0
        if (*format != '%')
139
0
          {
140
0
            if (c_isdigit (*format))
141
0
              {
142
0
                const char *f = format;
143
0
                size_t m = 0;
144
145
0
                do
146
0
                  {
147
0
                    m = 10 * m + (*f - '0');
148
0
                    f++;
149
0
                  }
150
0
                while (c_isdigit (*f));
151
152
0
                if (*f == '$')
153
0
                  {
154
0
                    if (m == 0)
155
0
                      {
156
0
                        *invalid_reason = INVALID_ARGNO_0 (spec.directives);
157
0
                        FDI_SET (f, FMTDIR_ERROR);
158
0
                        goto bad_format;
159
0
                      }
160
0
                    number = m;
161
0
                    format = ++f;
162
0
                  }
163
0
              }
164
165
0
            format_arg_type_t type;
166
0
            if (*format == 'C')
167
0
              {
168
0
                type = FAT_VOID;
169
0
                spec.uses_currentloc = true;
170
0
              }
171
0
            else if (*format == 'L')
172
0
              type = FAT_LOCUS;
173
0
            else if (*format == 'c')
174
0
              type = FAT_CHAR;
175
0
            else if (*format == 's')
176
0
              type = FAT_STRING;
177
0
            else
178
0
              {
179
0
                format_arg_type_t size = 0;
180
181
0
                if (*format == 'l')
182
0
                  {
183
0
                    ++format;
184
0
                    size = FAT_SIZE_LONG;
185
0
                  }
186
187
0
                if (*format == 'i' || *format == 'd')
188
0
                  type = FAT_INTEGER | size;
189
0
                else if (*format == 'u')
190
0
                  type = FAT_INTEGER | FAT_UNSIGNED | size;
191
0
                else
192
0
                  {
193
0
                    if (*format == '\0')
194
0
                      {
195
0
                        *invalid_reason = INVALID_UNTERMINATED_DIRECTIVE ();
196
0
                        FDI_SET (format - 1, FMTDIR_ERROR);
197
0
                      }
198
0
                    else
199
0
                      {
200
0
                        *invalid_reason =
201
0
                          INVALID_CONVERSION_SPECIFIER (spec.directives, *format);
202
0
                        FDI_SET (format, FMTDIR_ERROR);
203
0
                      }
204
0
                    goto bad_format;
205
0
                  }
206
0
              }
207
208
0
            if (numbered_allocated == numbered_arg_count)
209
0
              {
210
0
                numbered_allocated = 2 * numbered_allocated + 1;
211
0
                numbered = (struct numbered_arg *) xrealloc (numbered, numbered_allocated * sizeof (struct numbered_arg));
212
0
              }
213
0
            numbered[numbered_arg_count].number = number;
214
0
            numbered[numbered_arg_count].type = type;
215
0
            numbered_arg_count++;
216
217
0
            number++;
218
0
          }
219
220
0
        FDI_SET (format, FMTDIR_END);
221
222
0
        format++;
223
0
      }
224
225
  /* Sort the numbered argument array, and eliminate duplicates.  */
226
0
  if (numbered_arg_count > 1)
227
0
    {
228
0
      qsort (numbered, numbered_arg_count,
229
0
             sizeof (struct numbered_arg), numbered_arg_compare);
230
231
      /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
232
0
      bool err = false;
233
0
      size_t i, j;
234
0
      for (i = j = 0; i < numbered_arg_count; i++)
235
0
        if (j > 0 && numbered[i].number == numbered[j-1].number)
236
0
          {
237
0
            format_arg_type_t type1 = numbered[i].type;
238
0
            format_arg_type_t type2 = numbered[j-1].type;
239
240
0
            format_arg_type_t type_both;
241
0
            if (type1 == type2)
242
0
              type_both = type1;
243
0
            else
244
0
              {
245
                /* Incompatible types.  */
246
0
                type_both = FAT_NONE;
247
0
                if (!err)
248
0
                  *invalid_reason =
249
0
                    INVALID_INCOMPATIBLE_ARG_TYPES (numbered[i].number);
250
0
                err = true;
251
0
              }
252
253
0
            numbered[j-1].type = type_both;
254
0
          }
255
0
        else
256
0
          {
257
0
            if (j < i)
258
0
              {
259
0
                numbered[j].number = numbered[i].number;
260
0
                numbered[j].type = numbered[i].type;
261
0
              }
262
0
            j++;
263
0
          }
264
0
      numbered_arg_count = j;
265
0
      if (err)
266
        /* *invalid_reason has already been set above.  */
267
0
        goto bad_format;
268
0
    }
269
270
  /* Verify that the format string uses all arguments up to the highest
271
     numbered one.  */
272
0
  for (size_t i = 0; i < numbered_arg_count; i++)
273
0
    if (numbered[i].number != i + 1)
274
0
      {
275
0
        *invalid_reason =
276
0
          xasprintf (_("The string refers to argument number %zu but ignores argument number %zu."), numbered[i].number, i + 1);
277
0
        goto bad_format;
278
0
      }
279
280
  /* So now the numbered arguments array is equivalent to a sequence
281
     of unnumbered arguments.  Eliminate the FAT_VOID placeholders.  */
282
0
  {
283
0
    spec.unnumbered_arg_count = 0;
284
0
    for (size_t i = 0; i < numbered_arg_count; i++)
285
0
      if (numbered[i].type != FAT_VOID)
286
0
        spec.unnumbered_arg_count++;
287
288
0
    if (spec.unnumbered_arg_count > 0)
289
0
      {
290
0
        spec.unnumbered = XNMALLOC (spec.unnumbered_arg_count, struct unnumbered_arg);
291
0
        size_t j = 0;
292
0
        for (size_t i = 0; i < numbered_arg_count; i++)
293
0
          if (numbered[i].type != FAT_VOID)
294
0
            spec.unnumbered[j++].type = numbered[i].type;
295
0
      }
296
0
    else
297
0
      spec.unnumbered = NULL;
298
0
  }
299
0
  free (numbered);
300
301
0
  struct spec *result = XMALLOC (struct spec);
302
0
  *result = spec;
303
0
  return result;
304
305
0
 bad_format:
306
0
  if (numbered != NULL)
307
0
    free (numbered);
308
0
  return NULL;
309
0
}
310
311
static void
312
format_free (void *descr)
313
0
{
314
0
  struct spec *spec = (struct spec *) descr;
315
316
0
  if (spec->unnumbered != NULL)
317
0
    free (spec->unnumbered);
318
0
  free (spec);
319
0
}
320
321
static int
322
format_get_number_of_directives (void *descr)
323
0
{
324
0
  struct spec *spec = (struct spec *) descr;
325
326
0
  return spec->directives;
327
0
}
328
329
static bool
330
format_check (void *msgid_descr, void *msgstr_descr, bool equality,
331
              formatstring_error_logger_t error_logger, void *error_logger_data,
332
              const char *pretty_msgid, const char *pretty_msgstr)
333
0
{
334
0
  struct spec *spec1 = (struct spec *) msgid_descr;
335
0
  struct spec *spec2 = (struct spec *) msgstr_descr;
336
0
  bool err = false;
337
338
  /* Check the argument types are the same.  */
339
0
  if (equality
340
0
      ? spec1->unnumbered_arg_count != spec2->unnumbered_arg_count
341
0
      : spec1->unnumbered_arg_count < spec2->unnumbered_arg_count)
342
0
    {
343
0
      if (error_logger)
344
0
        error_logger (error_logger_data,
345
0
                      _("number of format specifications in '%s' and '%s' does not match"),
346
0
                      pretty_msgid, pretty_msgstr);
347
0
      err = true;
348
0
    }
349
0
  else
350
0
    for (size_t i = 0; i < spec2->unnumbered_arg_count; i++)
351
0
      if (spec1->unnumbered[i].type != spec2->unnumbered[i].type)
352
0
        {
353
0
          if (error_logger)
354
0
            error_logger (error_logger_data,
355
0
                          _("format specifications in '%s' and '%s' for argument %zu are not the same"),
356
0
                          pretty_msgid, pretty_msgstr, i + 1);
357
0
          err = true;
358
0
        }
359
360
  /* Check that the use of currentloc is the same.  */
361
0
  if (spec1->uses_currentloc != spec2->uses_currentloc)
362
0
    {
363
0
      if (error_logger)
364
0
        {
365
0
          if (spec1->uses_currentloc)
366
0
            error_logger (error_logger_data,
367
0
                          _("'%s' uses %%C but '%s' doesn't"),
368
0
                          pretty_msgid, pretty_msgstr);
369
0
          else
370
0
            error_logger (error_logger_data,
371
0
                          _("'%s' does not use %%C but '%s' uses %%C"),
372
0
                          pretty_msgid, pretty_msgstr);
373
0
        }
374
0
      err = true;
375
0
    }
376
377
0
  return err;
378
0
}
379
380
381
struct formatstring_parser formatstring_gfc_internal =
382
{
383
  format_parse,
384
  format_free,
385
  format_get_number_of_directives,
386
  NULL,
387
  format_check
388
};
389
390
391
#ifdef TEST
392
393
/* Test program: Print the argument list specification returned by
394
   format_parse for strings read from standard input.  */
395
396
#include <stdio.h>
397
398
static void
399
format_print (void *descr)
400
{
401
  struct spec *spec = (struct spec *) descr;
402
403
  if (spec == NULL)
404
    {
405
      printf ("INVALID");
406
      return;
407
    }
408
409
  printf ("(");
410
  for (size_t i = 0; i < spec->unnumbered_arg_count; i++)
411
    {
412
      if (i > 0)
413
        printf (" ");
414
      if (spec->unnumbered[i].type & FAT_UNSIGNED)
415
        printf ("[unsigned]");
416
      switch (spec->unnumbered[i].type & FAT_SIZE_MASK)
417
        {
418
        case 0:
419
          break;
420
        case FAT_SIZE_LONG:
421
          printf ("[long]");
422
          break;
423
        default:
424
          abort ();
425
        }
426
      switch (spec->unnumbered[i].type & ~(FAT_UNSIGNED | FAT_SIZE_MASK))
427
        {
428
        case FAT_INTEGER:
429
          printf ("i");
430
          break;
431
        case FAT_CHAR:
432
          printf ("c");
433
          break;
434
        case FAT_STRING:
435
          printf ("s");
436
          break;
437
        case FAT_LOCUS:
438
          printf ("L");
439
          break;
440
        default:
441
          abort ();
442
        }
443
    }
444
  printf (")");
445
  if (spec->uses_currentloc)
446
    printf (" C");
447
}
448
449
int
450
main ()
451
{
452
  for (;;)
453
    {
454
      char *line = NULL;
455
      size_t line_size = 0;
456
      int line_len = getline (&line, &line_size, stdin);
457
      if (line_len < 0)
458
        break;
459
      if (line_len > 0 && line[line_len - 1] == '\n')
460
        line[--line_len] = '\0';
461
462
      char *invalid_reason = NULL;
463
      void *descr = format_parse (line, false, NULL, &invalid_reason);
464
465
      format_print (descr);
466
      printf ("\n");
467
      if (descr == NULL)
468
        printf ("%s\n", invalid_reason);
469
470
      free (invalid_reason);
471
      free (line);
472
    }
473
474
  return 0;
475
}
476
477
/*
478
 * For Emacs M-x compile
479
 * Local Variables:
480
 * 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-gfc-internal.c ../gnulib-lib/libgettextlib.la"
481
 * End:
482
 */
483
484
#endif /* TEST */