1 /*
2 * Copyright © 2020 Benjamin Otte
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library 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 GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20 #include "config.h"
21
22 #include <gtk/gtk.h>
23
24 static struct
25 {
26 const char *expected;
27 const char *alternative;
28 } exceptions[] = {
29 /* keep this sorted please */
30 { "gdk_device_get_tool", "gdk_device_get_device_tool" },
31 { "gdk_display_get_input_shapes", "gdk_display_supports_input_shapes" },
32 { "gtk_constraint_guide_get_max_height", "gtk_constraint_guide_get_max_size" },
33 { "gtk_constraint_guide_get_max_width", "gtk_constraint_guide_get_max_size" },
34 { "gtk_constraint_guide_get_min_height", "gtk_constraint_guide_get_min_size" },
35 { "gtk_constraint_guide_get_min_width", "gtk_constraint_guide_get_min_size" },
36 { "gtk_constraint_guide_get_nat_height", "gtk_constraint_guide_get_nat_size" },
37 { "gtk_constraint_guide_get_nat_width", "gtk_constraint_guide_get_nat_size" },
38 { "gtk_constraint_guide_set_max_height", "gtk_constraint_guide_set_max_size" },
39 { "gtk_constraint_guide_set_max_width", "gtk_constraint_guide_set_max_size" },
40 { "gtk_constraint_guide_set_min_height", "gtk_constraint_guide_set_min_size" },
41 { "gtk_constraint_guide_set_min_width", "gtk_constraint_guide_set_min_size" },
42 { "gtk_constraint_guide_set_nat_height", "gtk_constraint_guide_set_nat_size" },
43 { "gtk_constraint_guide_set_nat_width", "gtk_constraint_guide_set_nat_size" },
44 { "gtk_tree_view_get_enable_grid_lines", "gtk_tree_view_get_grid_lines" },
45 { "gtk_tree_view_set_enable_grid_lines", "gtk_tree_view_set_grid_lines" },
46 { "gtk_widget_get_height_request", "gtk_widget_get_size_request" },
47 { "gtk_widget_get_width_request", "gtk_widget_get_size_request" },
48 { "gtk_widget_set_height_request", "gtk_widget_set_size_request" },
49 { "gtk_widget_set_width_request", "gtk_widget_set_size_request" },
50 { "gtk_window_get_default_height", "gtk_window_get_default_size" },
51 { "gtk_window_get_default_width", "gtk_window_get_default_size" },
52 { "gtk_window_set_default_height", "gtk_window_set_default_size" },
53 { "gtk_window_set_default_width", "gtk_window_set_default_size" },
54 { "gtk_window_get_display", "gtk_widget_get_display" },
55 { "gtk_window_get_focus_widget", "gtk_window_get_focus" },
56 { "gtk_window_set_focus_widget", "gtk_window_set_focus" },
57 };
58
59 static const char *type_exceptions[] = {
60 "GtkCellRenderer",
61 "GtkSettings",
62 "GtkTextTag",
63 };
64
65 static GModule *module;
66
67 static gboolean
function_exists(const char * function_name)68 function_exists (const char *function_name)
69 {
70 gpointer func;
71 guint i;
72
73 if (g_module_symbol (module, function_name, &func) && func)
74 return TRUE;
75
76 for (i = 0; i < G_N_ELEMENTS(exceptions); i++)
77 {
78 if (g_str_equal (function_name, exceptions[i].expected))
79 {
80 if (exceptions[i].alternative)
81 return function_exists (exceptions[i].alternative);
82 else
83 return TRUE;
84 }
85 }
86
87 return FALSE;
88 }
89
90 /* Keep in sync with gtkbuilder.c ! */
91 static char *
type_name_mangle(const char * name,gboolean split_first_cap)92 type_name_mangle (const char *name,
93 gboolean split_first_cap)
94 {
95 GString *symbol_name = g_string_new ("");
96 int i;
97
98 for (i = 0; name[i] != '\0'; i++)
99 {
100 /* skip if uppercase, first or previous is uppercase */
101 if ((name[i] == g_ascii_toupper (name[i]) &&
102 ((i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
103 (i == 1 && name[0] == g_ascii_toupper (name[0]) && split_first_cap))) ||
104 (i > 2 && name[i] == g_ascii_toupper (name[i]) &&
105 name[i-1] == g_ascii_toupper (name[i-1]) &&
106 name[i-2] == g_ascii_toupper (name[i-2])))
107 g_string_append_c (symbol_name, '_');
108 g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
109 }
110
111 return g_string_free (symbol_name, FALSE);
112 }
113
114 static void
property_name_mangle(GString * symbol_name,const char * name)115 property_name_mangle (GString *symbol_name,
116 const char *name)
117 {
118 guint i;
119
120 for (i = 0; name[i] != '\0'; i++)
121 {
122 if (g_ascii_isalnum (name[i]))
123 g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
124 else
125 g_string_append_c (symbol_name, '_');
126 }
127 }
128
129 const char *getters[] = { "get", "is", "ref" };
130 const char *setters[] = { "set" };
131
132 static char **
get_potential_names(GType type,gboolean get,const char * property_name)133 get_potential_names (GType type,
134 gboolean get,
135 const char *property_name)
136 {
137 GPtrArray *options;
138 char *type_name_options[2];
139 guint n_type_name_options, n_verbs;
140 const char **verbs;
141 guint i, j;
142
143 if (get)
144 {
145 verbs = getters;
146 n_verbs = G_N_ELEMENTS (getters);
147 }
148 else
149 {
150 verbs = setters;
151 n_verbs = G_N_ELEMENTS (setters);
152 }
153
154 type_name_options[0] = type_name_mangle (g_type_name (type), FALSE);
155 type_name_options[1] = type_name_mangle (g_type_name (type), TRUE);
156 if (g_str_equal (type_name_options[0], type_name_options[1]))
157 n_type_name_options = 1;
158 else
159 n_type_name_options = 2;
160
161 options = g_ptr_array_new ();
162
163 for (i = 0; i < n_type_name_options; i++)
164 {
165 for (j = 0; j < n_verbs; j++)
166 {
167 GString *str;
168
169 str = g_string_new (type_name_options[i]);
170 g_string_append_c (str, '_');
171 g_string_append (str, verbs[j]);
172 g_string_append_c (str, '_');
173 property_name_mangle (str, property_name);
174
175 g_ptr_array_add (options, g_string_free (str, FALSE));
176 }
177
178 if (g_str_has_prefix (property_name, "is-") ||
179 g_str_has_prefix (property_name, "has-") ||
180 g_str_has_prefix (property_name, "contains-"))
181 {
182 GString *str;
183
184 /* try without a verb */
185 str = g_string_new (type_name_options[i]);
186 g_string_append_c (str, '_');
187 property_name_mangle (str, property_name);
188
189 g_ptr_array_add (options, g_string_free (str, FALSE));
190 }
191 }
192
193 g_free (type_name_options[0]);
194 g_free (type_name_options[1]);
195
196 g_ptr_array_add (options, NULL);
197
198 return (char **) g_ptr_array_free (options, FALSE);
199 }
200
201 static void
check_function_name(GType type,gboolean get,const char * property_name)202 check_function_name (GType type,
203 gboolean get,
204 const char *property_name)
205 {
206 guint i;
207 char **names;
208
209 names = get_potential_names (type, get, property_name);
210
211 for (i = 0; names[i] != NULL; i++)
212 {
213 if (function_exists (names[i]))
214 {
215 g_strfreev (names);
216 return;
217 }
218 }
219
220 g_test_message ("No %s for property %s::%s", get ? "getter" : "setter", g_type_name (type), property_name);
221 for (i = 0; names[i] != NULL; i++)
222 {
223 g_test_message (" %s", names[i]);
224 }
225
226 g_test_fail ();
227
228 g_strfreev (names);
229 }
230
231 static void
check_property(GParamSpec * pspec)232 check_property (GParamSpec *pspec)
233 {
234 if (pspec->flags & G_PARAM_READABLE)
235 {
236 check_function_name (pspec->owner_type,
237 TRUE,
238 pspec->name);
239 }
240 if (pspec->flags & G_PARAM_WRITABLE &&
241 !(pspec->flags & G_PARAM_CONSTRUCT_ONLY))
242 {
243 check_function_name (pspec->owner_type,
244 FALSE,
245 pspec->name);
246 }
247 }
248
249 static void
test_accessors(gconstpointer data)250 test_accessors (gconstpointer data)
251 {
252 GType type = GPOINTER_TO_SIZE (data);
253 GObjectClass *klass;
254 GParamSpec **pspecs;
255 guint i, n_pspecs;
256
257 klass = g_type_class_ref (type);
258 pspecs = g_object_class_list_properties (klass, &n_pspecs);
259
260 for (i = 0; i < n_pspecs; ++i)
261 {
262 GParamSpec *pspec = pspecs[i];
263
264 if (pspec->owner_type != type)
265 continue;
266
267 check_property (pspec);
268 }
269
270 g_free (pspecs);
271 g_type_class_unref (klass);
272 }
273
274 static gboolean
type_is_whitelisted(GType type)275 type_is_whitelisted (GType type)
276 {
277 guint i;
278
279 if (!G_TYPE_IS_INSTANTIATABLE (type) ||
280 !g_type_is_a (type, G_TYPE_OBJECT))
281 return TRUE;
282
283 for (i = 0; i < G_N_ELEMENTS (type_exceptions); i++)
284 {
285 GType exception = g_type_from_name (type_exceptions[i]);
286
287 /* type hasn't been registered yet */
288 if (exception == G_TYPE_INVALID)
289 continue;
290
291 if (g_type_is_a (type, exception))
292 return TRUE;
293 }
294
295 return FALSE;
296 }
297
298 int
main(int argc,char ** argv)299 main (int argc, char **argv)
300 {
301 const GType *all_types;
302 guint n_types = 0, i;
303 int result;
304
305 /* These must be set before before gtk_test_init */
306 g_setenv ("GIO_USE_VFS", "local", TRUE);
307 g_setenv ("GSETTINGS_BACKEND", "memory", TRUE);
308
309 /* initialize test program */
310 gtk_test_init (&argc, &argv);
311 gtk_test_register_all_types ();
312
313 module = g_module_open (NULL, G_MODULE_BIND_LAZY);
314
315 all_types = gtk_test_list_all_types (&n_types);
316
317 for (i = 0; i < n_types; i++)
318 {
319 char *test_path;
320
321 if (type_is_whitelisted (all_types[i]))
322 continue;
323
324 test_path = g_strdup_printf ("/accessor-apis/%s", g_type_name (all_types[i]));
325
326 g_test_add_data_func (test_path, GSIZE_TO_POINTER (all_types[i]), test_accessors);
327
328 g_free (test_path);
329 }
330
331 result = g_test_run();
332
333 g_module_close (module);
334
335 return result;
336 }
337