Before it’s Christmas again, I should blog about the nifty hack I did last Christmas, already nine months ago.
Often, I am quickly coding up test cases. Compiling and running usually goes like this (insert cheering after successful compile):
$ clang `pkg-config --libs --cflags gtk+-3.0` -o test/foo3 test/foo3.c $ ./test/foo3 (foo3:84880): GLib-GObject-WARNING **: g_object_set_valist: object class `GtkWindow' has no property named `width' (foo3:84880): GLib-GObject-WARNING **: gsignal.c:2293: signal `delet-event' is invalid for instance `0x101865000'
And insert a sigh of disappointment after witnessing the GObject warnings. I figured that with all the GObject Introspection work going on, it would be quite nice to teach the compiler about GObject Introspection. So while snow was pouring down last Christmas, I hacked up a quick and dirty clang plugin. It looks like this:
$ clang -cc1 -fcolor-diagnostics `pkg-config --cflags gtk+-3.0` -load libGObjectHelper.dylib -plugin gobject-helper test/foo3.c test/foo3.c:23:25: warning: object 'GtkWindow' has no property 'width' g_object_set (window, "width", 320, "height", 240, NULL); ^ test/foo3.c:23:39: warning: object 'GtkWindow' has no property 'height' g_object_set (window, "width", 320, "height", 240, NULL); ^ test/foo3.c:24:29: warning: object 'GtkWindow' has no signal 'delet-event' g_signal_connect (window, "delet-event", ^ 3 warnings generated.
One of the features I wanted to add before blogging, was to also use the clang-style spelling corrections. For the last warning, it would say "did you mean ‘delete-event’?" Of course, there’s still much more to do than that to make the plugin feature complete.
This plugin will not work just with GTK+ source code. Because it’s using GObject Introspection data, it should work for any GObject library for which the GObject Introspection data has been generated.
The code is not ready for public consumption, so I am not releasing it just yet. My plan is to find time in the very near feature once my critical bugs are under control (or worst case during Christmas again :) and work on bringing the plugin into shape and trying it out on some larger examples. See below why trying it on larger examples first is quite important to figure out whether this plugin will ever be useful for the general public. When that is done, I plan to dump it in a public git repository for all to enjoy.
So, how does it work?
At its very core, the plugin walks over the Abstract Syntax Tree (AST) produced by clang. For the above example we simply handle calls to g_signal_connect_data and g_object_set. In order to verify whether the given signal or property name exists, we need to know the exact type of the object we are calling this function on. This is a problem that cannot always be solved.
What the plugin can currently do to solve this problem, is to determine where exactly the object pointer is assigned in the same function. If the object pointer is a GtkWidget *, we could at least assume that the object contains all signals and properties belonging to GtkWidget objects. In the next line, it is possible that the value of a call to gtk_label_new() is assigned to the object pointer. In this case we know the object is of type GtkLabel and as a result we can catch more errors.
The above strategy works fine on simple examples. The challenge is to make this work for larger programs. It might be a reasonable assumption to say that often an object is created, properties set and signals attached in the same function. However, there might be many cases where an object is created in a separate function and only a GtkWidget * type is returned. When the function is in the same compilation unit, it will still be possible to find out the full type though.
It is already clear that the plugin will never be able to catch all errors. Still I expect it to be pretty helpful. Another case which is interesting to handle is the following:
GtkWidget *widget; if (can_edit) widget = gtk_entry_new (); else widget = gtk_label_new (); g_object_set (widget, ...
We will never be able to report all possible errors in this case. For example, if widget is assigned a GtkLabel and we use a property from GtkEntry, we cannot catch it. What we can do is tag the widget variable with both the GtkLabel and GtkEntry types and put out an error if a property or signal is used that is neither in GtkLabel nor in GtkEntry.
Why not just program in a higher-level language, then you would not need a plugin like this?
Good question.
To be continued …
ANSI to HTML conversion courtesy of a2h
I think that plugin is awesome. And shame on you for not making it available for the 3.0 transition where I had to look for all the “expose-event” and “expose_event” signal usages. :)
Also, the warnings you have are easily solvable by doing g_signal_connect (GTK_ENTRY (widget), … in the C code, which I consider a very sane solution for such issues, so I’d be very happy with this thing as is.
PS: I don’t think there’s a lot of high-level languages that catch errors like these at compile-time. Most of them don’t compile at all…
In the “if (can_edit)” example you cannot use the GTK_ENTRY() cast though, because you do not know whether the GtkWidget pointer contains a label or an entry. So the plugin will still have to do something smart here :).
It’s interesting that you bring up casts, because I had another idea for that. In a scenario like:
GtkWidget *entry = gtk_entry_new ();
gtk_tree_view_set_foo (GTK_TREE_VIEW (entry), …)
the plugin can warn about an invalid cast, because it can deduce that a GtkEntry type has been assigned to the entry pointer, which cannot be casted to a GtkTreeView.
About high-level languages, point taken for python. I was thinking about something like C# where the delegate you are trying to use needs to be defined in order for things to compile. If you make a typo in a signal/delegate name compilation will fail.
In the if (can_edit) case you could track possible types though, and if *all* possible types have a specific property or signal then it is ok.
Neat!
I’d been thinking about something similar to type-check parameters to g_object_{set,get} and g_variant_new(). Of course, the former depends on pretty much exactly the analysis you’ve implemented. Looking forward to using it!
I’m not sure I get why the can_edit example doesn’t work. If you use a property that only exists on GtkEntry, you can use g_signal_connect (GTK_ENTRY(widget), …) because if you used it on a label, that property would not exist. The only place where that fails is if there’s a property that exists on a GtkEntry and a GtkLabel, but not on the common parent class (like the text property). But in that case, your code is already pretty evil…
Another thing that’d be awesome if it worked would be using the allow-none annotation to infer whether a type can be NULL and if variables are passed to functions that can be NULL but must not be.