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