1 #include "config.h"
2 
3 #include <telepathy-glib/dbus.h>
4 #include <telepathy-glib/debug.h>
5 #include <telepathy-glib/errors.h>
6 #include <telepathy-glib/interfaces.h>
7 #include <telepathy-glib/intset.h>
8 #include <telepathy-glib/proxy-subclass.h>    /* for _invalidated etc. */
9 #include <telepathy-glib/util.h>
10 
11 #include "tests/lib/myassert.h"
12 #include "tests/lib/stub-object.h"
13 #include "tests/lib/util.h"
14 
15 /* just for convenience, since it's used a lot */
16 #define PTR(ui) GUINT_TO_POINTER(ui)
17 
18 /* state tracking */
19 static GMainLoop *mainloop;
20 static TpDBusDaemon *a;
21 static TpDBusDaemon *b;
22 static TpDBusDaemon *c;
23 static TpDBusDaemon *d;
24 static TpDBusDaemon *e;
25 static TpDBusDaemon *f;
26 static TpDBusDaemon *g;
27 static TpDBusDaemon *h;
28 static TpDBusDaemon *z;
29 static TpIntset *caught_signal;
30 static TpIntset *freed_user_data;
31 
32 enum {
33     TEST_A,
34     TEST_B,
35     TEST_C,
36     TEST_D,
37     TEST_E,
38     TEST_F,
39     TEST_G,
40     TEST_H,
41     TEST_Z = 25,
42     N_DAEMONS
43 };
44 
45 static void
h_stub_destroyed(gpointer data,GObject * stub)46 h_stub_destroyed (gpointer data,
47                   GObject *stub)
48 {
49   TpProxySignalConnection **p = data;
50 
51   tp_proxy_signal_connection_disconnect (*p);
52 }
53 
54 static void
destroy_user_data(gpointer user_data)55 destroy_user_data (gpointer user_data)
56 {
57   guint which = GPOINTER_TO_UINT (user_data);
58   g_message ("User data %c destroyed", 'A' + which);
59   MYASSERT (!tp_intset_is_member (freed_user_data, which), "");
60   tp_intset_add (freed_user_data, which);
61 }
62 
63 static void
requested_name(TpDBusDaemon * proxy,guint result,const GError * error,gpointer user_data,GObject * weak_object)64 requested_name (TpDBusDaemon *proxy,
65                 guint result,
66                 const GError *error,
67                 gpointer user_data,
68                 GObject *weak_object)
69 {
70   g_message ("RequestName raised %s",
71       (error == NULL ? "no error" : error->message));
72   /* we're on a private bus, so certainly nobody else should own this name */
73   g_assert_no_error ((GError *) error);
74   MYASSERT (result == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER, ": %u", result);
75 }
76 
77 static void
prop_changed(TpProxy * proxy,const GPtrArray * properties,gpointer user_data,GObject * weak_object)78 prop_changed (TpProxy *proxy,
79               const GPtrArray *properties,
80               gpointer user_data,
81               GObject *weak_object)
82 {
83   g_error ("prop_changed called - a signal connection which should have "
84       "failed has succeeded. Args: proxy=%p user_data=%p", proxy, user_data);
85 }
86 
87 static void
dummy_noc(TpDBusDaemon * proxy,const gchar * name,const gchar * old,const gchar * new,gpointer user_data,GObject * weak_object)88 dummy_noc (TpDBusDaemon *proxy,
89            const gchar *name,
90            const gchar *old,
91            const gchar *new,
92            gpointer user_data,
93            GObject *weak_object)
94 {
95   g_error ("dummy_noc called - a signal connection which should have "
96       "failed has succeeded. Args: proxy=%p user_data=%p", proxy, user_data);
97 }
98 
99 static void
noc(TpDBusDaemon * proxy,const gchar * name,const gchar * old,const gchar * new,gpointer user_data,GObject * weak_object)100 noc (TpDBusDaemon *proxy,
101      const gchar *name,
102      const gchar *old,
103      const gchar *new,
104      gpointer user_data,
105      GObject *weak_object)
106 {
107   guint which = GPOINTER_TO_UINT (user_data);
108   TpDBusDaemon *want_proxy = NULL;
109   GObject *want_object = NULL;
110 
111   g_message ("Caught signal (%s: %s -> %s) with proxy #%d '%c' according to "
112       "user_data", name, old, new, which, 'a' + which);
113   g_message ("Proxy is %p, weak object is %p", proxy,
114       weak_object);
115   tp_intset_add (caught_signal, which);
116 
117   switch (which)
118     {
119     case TEST_A:
120       want_proxy = a;
121       want_object = (GObject *) z;
122       break;
123     case TEST_Z:
124       want_proxy = z;
125       want_object = (GObject *) a;
126       break;
127     default:
128       g_error ("%c (%p) got the signal, which shouldn't have happened",
129           'a' + which, proxy);
130     }
131 
132   g_message ("Expecting proxy %p, weak object %p", want_proxy, want_object);
133 
134   MYASSERT (proxy == want_proxy, ": %p != %p", proxy, want_proxy);
135   MYASSERT (weak_object == want_object, ": %p != %p", weak_object,
136       want_object);
137 
138   if (tp_intset_is_member (caught_signal, TEST_A) &&
139       tp_intset_is_member (caught_signal, TEST_Z))
140     {
141       /* we've had all the signals we're going to */
142       g_main_loop_quit (mainloop);
143     }
144 }
145 
146 static void
set_freed(gpointer user_data)147 set_freed (gpointer user_data)
148 {
149   gboolean *boolptr = user_data;
150 
151   MYASSERT (*boolptr == FALSE, "");
152   *boolptr = TRUE;
153 }
154 
155 int
main(int argc,char ** argv)156 main (int argc,
157       char **argv)
158 {
159   GObject *stub;
160   GError *error_out = NULL;
161   GError err = { TP_ERROR, TP_ERROR_INVALID_ARGUMENT, "Because I said so" };
162   TpProxySignalConnection *sc;
163   gpointer tmp_obj;
164   gboolean freed = FALSE;
165 
166   tp_tests_abort_after (10);
167   tp_debug_set_flags ("all");
168 
169   freed_user_data = tp_intset_sized_new (N_DAEMONS);
170   caught_signal = tp_intset_sized_new (N_DAEMONS);
171 
172   mainloop = g_main_loop_new (NULL, FALSE);
173 
174   /* We use TpDBusDaemon because it's a convenient concrete subclass of
175    * TpProxy. */
176   g_message ("Creating proxies");
177   a = tp_tests_dbus_daemon_dup_or_die ();
178   g_message ("a=%p", a);
179   b = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
180   g_message ("b=%p", b);
181   c = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
182   g_message ("c=%p", c);
183   d = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
184   g_message ("d=%p", d);
185   e = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
186   g_message ("e=%p", e);
187   f = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
188   g_message ("f=%p", f);
189   g = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
190   g_message ("g=%p", g);
191   h = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
192   g_message ("h=%p", h);
193   z = tp_dbus_daemon_new (tp_proxy_get_dbus_connection (a));
194   g_message ("z=%p", z);
195 
196   /* a survives */
197   g_message ("Connecting signal to a");
198   tp_cli_dbus_daemon_connect_to_name_owner_changed (a, noc, PTR (TEST_A),
199       destroy_user_data, (GObject *) z, &error_out);
200   g_assert_no_error (error_out);
201 
202   /* assert that connecting to a signal on an interface we don't have fails */
203   freed = FALSE;
204   tp_cli_properties_interface_connect_to_properties_changed (a, prop_changed,
205       &freed, set_freed, NULL, &error_out);
206   MYASSERT (freed, "");
207   MYASSERT (error_out != NULL, "");
208   MYASSERT (error_out->code == TP_DBUS_ERROR_NO_INTERFACE, "");
209   g_error_free (error_out);
210   error_out = NULL;
211 
212   /* b gets its signal connection cancelled because stub is
213    * destroyed */
214   stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (),
215       NULL);
216   g_message ("Connecting signal to b");
217   tp_cli_dbus_daemon_connect_to_name_owner_changed (b, noc, PTR (TEST_B),
218       destroy_user_data, stub, &error_out);
219   g_assert_no_error (error_out);
220   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_B), "");
221   g_object_unref (stub);
222 
223   /* c gets its signal connection cancelled because it's explicitly
224    * invalidated */
225   g_message ("Connecting signal to c");
226   tp_cli_dbus_daemon_connect_to_name_owner_changed (c, noc, PTR (TEST_C),
227       destroy_user_data, NULL, &error_out);
228   g_assert_no_error (error_out);
229   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_C), "");
230   g_message ("Forcibly invalidating c");
231   tp_proxy_invalidate ((TpProxy *) c, &err);
232   /* assert that connecting to a signal on an invalid proxy fails */
233   freed = FALSE;
234   tp_cli_dbus_daemon_connect_to_name_owner_changed (c, dummy_noc, &freed,
235       set_freed, NULL, &error_out);
236   MYASSERT (freed, "");
237   MYASSERT (error_out != NULL, "");
238   g_message ("%d: %d: %s", error_out->domain, error_out->code,
239       error_out->message);
240   MYASSERT (error_out->code == err.code, "%d != %d", error_out->code,
241       err.code);
242   g_error_free (error_out);
243   error_out = NULL;
244 
245   /* d gets its signal connection cancelled because it's
246    * implicitly invalidated by being destroyed */
247   g_message ("Connecting signal to d");
248   tp_cli_dbus_daemon_connect_to_name_owner_changed (d, noc, PTR (TEST_D),
249       destroy_user_data, NULL, &error_out);
250   g_assert_no_error (error_out);
251   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_D), "");
252   g_message ("Destroying d");
253   tmp_obj = d;
254   g_object_add_weak_pointer (tmp_obj, &tmp_obj);
255   g_object_unref (d);
256   MYASSERT (tmp_obj == NULL, "");
257   d = NULL;
258 
259   /* e gets its signal connection cancelled explicitly */
260   g_message ("Connecting signal to e");
261   sc = tp_cli_dbus_daemon_connect_to_name_owner_changed (e, noc, PTR (TEST_E),
262       destroy_user_data, NULL, &error_out);
263   g_assert_no_error (error_out);
264   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_E), "");
265   g_message ("Disconnecting signal from e");
266   tp_proxy_signal_connection_disconnect (sc);
267 
268   /* f gets its signal connection cancelled because it's implicitly
269    * invalidated by its DBusGProxy being destroyed.
270    *
271    * Note that this test case exploits implementation details of dbus-glib.
272    * If it stops working after a dbus-glib upgrade, that's probably why. */
273   g_message ("Connecting signal to f");
274   tp_cli_dbus_daemon_connect_to_name_owner_changed (f, noc, PTR (TEST_F),
275       destroy_user_data, NULL, &error_out);
276   g_assert_no_error (error_out);
277   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_F), "");
278   g_message ("Forcibly disposing f's DBusGProxy to simulate name owner loss");
279   tmp_obj = tp_proxy_borrow_interface_by_id ((TpProxy *) f,
280       TP_IFACE_QUARK_DBUS_DAEMON, NULL);
281   MYASSERT (tmp_obj != NULL, "");
282   g_object_run_dispose (tmp_obj);
283   /* assert that connecting to a signal on an invalid proxy fails */
284   freed = FALSE;
285   tp_cli_dbus_daemon_connect_to_name_owner_changed (f, dummy_noc, &freed,
286       set_freed, NULL, &error_out);
287   MYASSERT (freed, "");
288   MYASSERT (error_out != NULL, "");
289   MYASSERT (error_out->code == DBUS_GERROR_NAME_HAS_NO_OWNER, "");
290   g_error_free (error_out);
291   error_out = NULL;
292 
293   /* g gets its signal connection cancelled because it's
294    * implicitly invalidated by being destroyed; unlike d, the signal
295    * connection weakly references the proxy. This is never necessary, but is
296    * an interesting corner case that should be tested. */
297   g_message ("Connecting signal to g");
298   tp_cli_dbus_daemon_connect_to_name_owner_changed (g, noc, PTR (TEST_G),
299       destroy_user_data, (GObject *) g, &error_out);
300   g_assert_no_error (error_out);
301   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_G), "");
302   g_message ("Destroying g");
303   tmp_obj = g;
304   g_object_add_weak_pointer (tmp_obj, &tmp_obj);
305   g_object_unref (g);
306   MYASSERT (tmp_obj == NULL, "");
307   g = NULL;
308 
309   /* h gets its signal connection cancelled because its weak object is
310    * destroyed, meaning there are simultaneously two reasons for it to become
311    * cancelled (fd.o#14750) */
312   stub = tp_tests_object_new_static_class (tp_tests_stub_object_get_type (),
313       NULL);
314   g_object_weak_ref (stub, h_stub_destroyed, &sc);
315   g_message ("Connecting signal to h");
316   tp_cli_dbus_daemon_connect_to_name_owner_changed (h, noc, PTR (TEST_H),
317       destroy_user_data, stub, &error_out);
318   g_assert_no_error (error_out);
319   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_H), "");
320   g_object_unref (stub);
321 
322   /* z survives; we assume that the signals are delivered in either
323    * forward or reverse order, so if both a and z have had their signal, we
324    * can stop the main loop */
325   g_message ("Connecting signal to z");
326   tp_cli_dbus_daemon_connect_to_name_owner_changed (z, noc, PTR (TEST_Z),
327       destroy_user_data, (GObject *) a, &error_out);
328   g_assert_no_error (error_out);
329 
330   /* make sure a NameOwnerChanged signal occurs */
331   g_message ("Requesting name");
332   tp_cli_dbus_daemon_call_request_name (a, -1, "com.example.NameTest",
333       0, requested_name, NULL, NULL, NULL);
334 
335   g_message ("Running main loop");
336   g_main_loop_run (mainloop);
337   g_main_loop_unref (mainloop);
338 
339   /* Now that the main loop has run, cancelled signal connections have been
340    * freed */
341   MYASSERT (tp_intset_is_member (freed_user_data, TEST_B), "");
342   MYASSERT (tp_intset_is_member (freed_user_data, TEST_C), "");
343   MYASSERT (tp_intset_is_member (freed_user_data, TEST_D), "");
344   MYASSERT (tp_intset_is_member (freed_user_data, TEST_E), "");
345   MYASSERT (tp_intset_is_member (freed_user_data, TEST_F), "");
346   MYASSERT (tp_intset_is_member (freed_user_data, TEST_G), "");
347   MYASSERT (tp_intset_is_member (freed_user_data, TEST_H), "");
348 
349   /* both A and Z are still listening for signals, so their user data is
350    * still held */
351   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_A), "");
352   MYASSERT (!tp_intset_is_member (freed_user_data, TEST_Z), "");
353 
354   g_message ("Dereferencing remaining proxies");
355   g_object_unref (a);
356   g_object_unref (b);
357   g_object_unref (c);
358   MYASSERT (d == NULL, "");
359   g_object_unref (e);
360   g_object_unref (f);
361   MYASSERT (g == NULL, "");
362   g_object_unref (z);
363 
364   /* we should already have checked each of these at least once, but just to
365    * make sure we have a systematic test that all user data is freed... */
366   MYASSERT (tp_intset_is_member (freed_user_data, TEST_A), "");
367   MYASSERT (tp_intset_is_member (freed_user_data, TEST_B), "");
368   MYASSERT (tp_intset_is_member (freed_user_data, TEST_C), "");
369   MYASSERT (tp_intset_is_member (freed_user_data, TEST_D), "");
370   MYASSERT (tp_intset_is_member (freed_user_data, TEST_E), "");
371   MYASSERT (tp_intset_is_member (freed_user_data, TEST_F), "");
372   MYASSERT (tp_intset_is_member (freed_user_data, TEST_G), "");
373   MYASSERT (tp_intset_is_member (freed_user_data, TEST_H), "");
374   MYASSERT (tp_intset_is_member (freed_user_data, TEST_Z), "");
375 
376   tp_intset_destroy (freed_user_data);
377   tp_intset_destroy (caught_signal);
378 
379   return 0;
380 }
381