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