• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Format strings.
2    Copyright (C) 2001-2010, 2012-2013, 2015, 2019-2020 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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 "format.h"
24 
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 
29 #include "message.h"
30 #include "gettext.h"
31 
32 #define _(str) gettext (str)
33 
34 /* Table of all format string parsers.  */
35 struct formatstring_parser *formatstring_parsers[NFORMATS] =
36 {
37   /* format_c */                &formatstring_c,
38   /* format_objc */             &formatstring_objc,
39   /* format_python */           &formatstring_python,
40   /* format_python_brace */     &formatstring_python_brace,
41   /* format_java */             &formatstring_java,
42   /* format_java_printf */      &formatstring_java_printf,
43   /* format_csharp */           &formatstring_csharp,
44   /* format_javascript */       &formatstring_javascript,
45   /* format_scheme */           &formatstring_scheme,
46   /* format_lisp */             &formatstring_lisp,
47   /* format_elisp */            &formatstring_elisp,
48   /* format_librep */           &formatstring_librep,
49   /* format_ruby */             &formatstring_ruby,
50   /* format_sh */               &formatstring_sh,
51   /* format_awk */              &formatstring_awk,
52   /* format_lua */              &formatstring_lua,
53   /* format_pascal */           &formatstring_pascal,
54   /* format_smalltalk */        &formatstring_smalltalk,
55   /* format_qt */               &formatstring_qt,
56   /* format_qt_plural */        &formatstring_qt_plural,
57   /* format_kde */              &formatstring_kde,
58   /* format_kde_kuit */         &formatstring_kde_kuit,
59   /* format_boost */            &formatstring_boost,
60   /* format_tcl */              &formatstring_tcl,
61   /* format_perl */             &formatstring_perl,
62   /* format_perl_brace */       &formatstring_perl_brace,
63   /* format_php */              &formatstring_php,
64   /* format_gcc_internal */     &formatstring_gcc_internal,
65   /* format_gfc_internal */     &formatstring_gfc_internal,
66   /* format_ycp */              &formatstring_ycp
67 };
68 
69 /* Check whether both formats strings contain compatible format
70    specifications for format type i (0 <= i < NFORMATS).  */
71 int
check_msgid_msgstr_format_i(const char * msgid,const char * msgid_plural,const char * msgstr,size_t msgstr_len,size_t i,struct argument_range range,const struct plural_distribution * distribution,formatstring_error_logger_t error_logger)72 check_msgid_msgstr_format_i (const char *msgid, const char *msgid_plural,
73                              const char *msgstr, size_t msgstr_len,
74                              size_t i,
75                              struct argument_range range,
76                              const struct plural_distribution *distribution,
77                              formatstring_error_logger_t error_logger)
78 {
79   int seen_errors = 0;
80 
81   /* At runtime, we can assume the program passes arguments that fit well for
82      msgid.  We must signal an error if msgstr wants more arguments that msgid
83      accepts.
84      If msgstr wants fewer arguments than msgid, it wouldn't lead to a crash
85      at runtime, but we nevertheless give an error because
86      1) this situation occurs typically after the programmer has added some
87         arguments to msgid, so we must make the translator specially aware
88         of it (more than just "fuzzy"),
89      2) it is generally wrong if a translation wants to ignore arguments that
90         are used by other translations.  */
91 
92   struct formatstring_parser *parser = formatstring_parsers[i];
93   char *invalid_reason = NULL;
94   void *msgid_descr =
95     parser->parse (msgid_plural != NULL ? msgid_plural : msgid, false, NULL,
96                    &invalid_reason);
97 
98   if (msgid_descr != NULL)
99     {
100       const char *pretty_msgid =
101         (msgid_plural != NULL ? "msgid_plural" : "msgid");
102       char buf[18+1];
103       const char *pretty_msgstr = "msgstr";
104       bool has_plural_translations = (strlen (msgstr) + 1 < msgstr_len);
105       const char *p_end = msgstr + msgstr_len;
106       const char *p;
107       unsigned int j;
108 
109       for (p = msgstr, j = 0; p < p_end; p += strlen (p) + 1, j++)
110         {
111           void *msgstr_descr;
112 
113           if (msgid_plural != NULL)
114             {
115               sprintf (buf, "msgstr[%u]", j);
116               pretty_msgstr = buf;
117             }
118 
119           msgstr_descr = parser->parse (p, true, NULL, &invalid_reason);
120 
121           if (msgstr_descr != NULL)
122             {
123               /* Use strict checking (require same number of format
124                  directives on both sides) if the message has no plurals,
125                  or if msgid_plural exists but on the msgstr[] side
126                  there is only msgstr[0], or if distribution->often[j]
127                  indicates that the variant applies to infinitely many
128                  values of N and the N range is not restricted in a way
129                  that the variant applies to only one N.
130                  Use relaxed checking when there are at least two
131                  msgstr[] forms and the distribution does not give more
132                  precise information.  */
133               bool strict_checking =
134                 (msgid_plural == NULL
135                  || !has_plural_translations
136                  || (distribution != NULL
137                      && distribution->often != NULL
138                      && j < distribution->often_length
139                      && distribution->often[j]
140                      && !(has_range_p (range)
141                           && distribution->histogram (distribution,
142                                                       range.min, range.max, j)
143                              <= 1)));
144 
145               if (parser->check (msgid_descr, msgstr_descr,
146                                  strict_checking,
147                                  error_logger, pretty_msgid, pretty_msgstr))
148                 seen_errors++;
149 
150               parser->free (msgstr_descr);
151             }
152           else
153             {
154               error_logger (_("'%s' is not a valid %s format string, unlike '%s'. Reason: %s"),
155                             pretty_msgstr, format_language_pretty[i],
156                             pretty_msgid, invalid_reason);
157               seen_errors++;
158               free (invalid_reason);
159             }
160         }
161 
162       parser->free (msgid_descr);
163     }
164   else
165     free (invalid_reason);
166 
167   return seen_errors;
168 }
169 
170 /* Check whether both formats strings contain compatible format
171    specifications.
172    Return the number of errors that were seen.  */
173 int
check_msgid_msgstr_format(const char * msgid,const char * msgid_plural,const char * msgstr,size_t msgstr_len,const enum is_format is_format[NFORMATS],struct argument_range range,const struct plural_distribution * distribution,formatstring_error_logger_t error_logger)174 check_msgid_msgstr_format (const char *msgid, const char *msgid_plural,
175                            const char *msgstr, size_t msgstr_len,
176                            const enum is_format is_format[NFORMATS],
177                            struct argument_range range,
178                            const struct plural_distribution *distribution,
179                            formatstring_error_logger_t error_logger)
180 {
181   int seen_errors = 0;
182   size_t i;
183 
184   /* We check only those messages for which the msgid's is_format flag
185      is one of 'yes' or 'possible'.  We don't check msgids with is_format
186      'no' or 'impossible', to obey the programmer's order.  We don't check
187      msgids with is_format 'undecided' because that would introduce too
188      many checks, thus forcing the programmer to add "xgettext: no-c-format"
189      anywhere where a translator wishes to use a percent sign.  */
190   for (i = 0; i < NFORMATS; i++)
191     if (possible_format_p (is_format[i]))
192       seen_errors += check_msgid_msgstr_format_i (msgid, msgid_plural,
193                                                   msgstr, msgstr_len, i,
194                                                   range,
195                                                   distribution,
196                                                   error_logger);
197 
198   return seen_errors;
199 }
200