DEV Community

Cover image for Debugging a defect with a shared argument
edA‑qa mort‑ora‑y
edA‑qa mort‑ora‑y

Posted on • Originally published at mortoray.com

Debugging a defect with a shared argument

While writing some error handling helpers, during my algorithm stream, I encountered a defect. It's a tricky one dealing with the passing of shared arguments to functions in Leaf.

After a lot of searching I managed to reduce it to this minimal unit test.

/* EXPECT(3) */
var a : integer shared := 2

defn pine = (b : integer shared) -> {
    var c : integer shared := 3
    b = c
    trace(b)
}

pine(a)
Enter fullscreen mode Exit fullscreen mode

The function is silly, but it is sufficient to demonstrate an error. It manifests as severe sounding abort: duplicate-release-shared error.

For the curious, the example was reduced from the below error reporting function.

@export defn print = ( ofi : ⁑fail_info optional shared ) -> {
    println(["Error:"])
     while has(ofi) {
        var q = getopt(ofi)
         println(["\t",q.tag])
         ofi = q.next
     }
}
Enter fullscreen mode Exit fullscreen mode

That iterates over the tags in an error and prints them to the console. It mostly works, except for one issue on the ofi = q.next line -- it also causes that nasty duplicate-release-shared error.

It's a problem of ownership

The error is one of ownership: two code paths are freeing the same value. Consider this basic code:

var b : shared integer := 5
var c : shared integer := 6

b = c
Enter fullscreen mode Exit fullscreen mode

b and c are both shared values. These are roughly equivalent to a shared_ptr<integer> in C++, or the Integer type in Java. When we assign b = c we're not modifying values, rather changing references. You may refer to my article on divorcing a name from its value for more details on that concept.

Leaf uses reference counting; this is roughly what happens when we assign to a shared variable:

// b = c
acquire(c)
release(b)
assign(b, c)
Enter fullscreen mode Exit fullscreen mode

We release the old object in b, acquire the new one c, and then assign the reference (a memory pointer).

Take a look back at the test case again, the b = c comes from that example:

defn pine = (b : integer shared) -> {
    var c : integer shared := 3
    b = c
    trace(b)
}
Enter fullscreen mode Exit fullscreen mode

The trouble here is that b is an argument to the function, so we're only allowed to release it if we own the variable. It turns out we don't!

Consider the calling side:

pine(a)
Enter fullscreen mode Exit fullscreen mode

Reduced to pseudo-code, again based on reference counting:

var a_tmp = acquire(a)
pine( a_tmp )
release( a_tmp )
Enter fullscreen mode Exit fullscreen mode

This bit of code ensures that the reference to a is valid during the function call. Since a is a globally modifiable, we need to play it safe here. The problem is the above code calls release(a_tmp). But our pine function is also doing release(b), which happens to be the same shared object as a_tmp. Thus we've released the object twice!

Logically avoiding the issue

This issue can be avoided by shadowing all arguments locally in the function.

defn pine = ( _b : integer shared) -> {
    var b = _b
    var c : integer shared := 3
    b = c
    trace(b)
}
Enter fullscreen mode Exit fullscreen mode

The shadowing works since the first thing the function does is acquire(_b), and the eventual release(b) doesn't touch the source argument.

It feels a bit like overkill to do this for all arguments on the off-chance they might be modified. I guess the compiler could scan the code and figure it out. Or, I could just disallow modifying function arguments. That's how I arrived at my question Should function arguments be reassignable or mutable? .

For sanity in the compiler, I'll likely add a fix (local shadowing), and forbid assigned to arguments by default. Then use some magic to try and optimize away all situations where it isn't needed -- though popular opinion seems to lean towards forbidding the modification of arguments.

Be sure to visit the Leaf site to learn more about my programming language. You can also follow me on Twitter or subscribe to my live stream and feel free to ask questions.

Top comments (0)