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