Question

After experimenting with Vala and inspecting the generated C source code i came up with the following Vala code:

class Foo : GLib.Object {
    public string baz;
}

class Main : GLib.Object {
    public static Foo foo;

    public static void bar(Foo f) {
        foo = null;
        f.baz = "Hi";
    }

    public static int main(string[] args) {
        foo = new Foo();
        bar(foo);
        return 0;
    }
}

Inspecting the generated C code i realized that the Vala compiler didn't insert a reference count (RC) increment for foo when passing it to bar. So as far as i understand it, the first line in bar will decrement the RC of foo to 0, which in turn should free up foo, effectively making the passed variable f a dangling pointer, which is then accessed in the second line of bar. However, the program executes without problems, so i'm not sure whether i am missing something here or it works just by pure coincidence. Here the generated C code for reference:

void main_bar (Foo* f) {
    Foo* _tmp0_;
    gchar* _tmp1_;
    g_return_if_fail (f != NULL);
    _g_object_unref0 (main_foo);
    main_foo = NULL;
    _tmp0_ = f;
    _tmp1_ = g_strdup ("Hi");
    _g_free0 (_tmp0_->baz);
    _tmp0_->baz = _tmp1_;
}

gint main_main (gchar** args, int args_length1) {
    gint result = 0;
    Foo* _tmp0_;
    Foo* _tmp1_;
    _tmp0_ = foo_new ();
    _g_object_unref0 (main_foo);
    main_foo = _tmp0_;
    _tmp1_ = main_foo;
    main_bar (_tmp1_);
    result = 0;
    return result;
}
Was it helpful?

Solution

This is correct behaviour. Only owned references are counted. Parameters are unowned, unless explicitly specified otherwise. So f in bar, is never reference counted because the caller is responsible for maintaining the reference count. Variable storage locations (class fields, stack variables, global variables) are all owned.

So, let's examine main and bar separately:

main creates an instance of Foo, which needs to be put somewhere. It puts it in the global variable foo, which owns it. There is now a single reference on the object created via foo. We then call bar, which takes a parameter foo. We know that foo already references the object and that passing it as a parameter does not require us to increment the reference unless the parameter is owned. Therefore, we simply pass the pointer foo to bar.

bar takes a parameter of type Foo called f which is does not own. It assigns null to a completely unrelated global variable called foo, this decrements the object's reference count, cleaning it up as necessary. It then does an assignment to a field in f.

To have this work “correctly” the compiler would have to 1) understand that foo and f are the same, even though you can call bar with any parameter, 2) know that decrementing the reference count on foo is slightly different from decrementing it to zero in some cases. That's just much too complex for any compiler not capable of solving the halting problem.

To make your code work as expected you have two options:

  1. Assign your new object both to the global variable foo and a stack variable which you pass to bar. You now have ensured that the variable remains live through the call to bar.

  2. Make bar take owned Foo f instead of Foo f. This will cause the caller to increment the reference on foo before passing it and bar to decrement the reference when it's done.

In short, it's the caller's responsibility to make sure that a variable remains live when it passes it to a method for the life-span of that method. You can imagine that gets a little more complicated when that method is async.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top