Debugging a defect with a shared argument
Tags: compiler, coding, leaf, debugging
Published: 2018-01-06 08:38:44

---
title: Debugging a defect with a shared argument
published: true
description: How a compiler (mis-)handles shared function arguments. 
tags: compiler, coding, leaf, debugging
canonical_url: https://mortoray.com/2018/01/06/debugging-a-defect-with-a-shared-argument/
cover_image: https://mortoray.files.wordpress.com/2018/01/gear-2291916_640.jpg
---

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](http://leaflang.org/).

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)
```

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.

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

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:

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

b = c
```

`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](https://mortoray.com/2013/01/18/divorcing-a-value-from-its-name/) for more details on that concept._

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

```c
// b = c
acquire(c)
release(b)
assign(b, c)
```

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:

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

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:

```leaf
pine(a)
```

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

```c
var a_tmp = acquire(a)
pine( a_tmp )
release( a_tmp )
```

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.

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

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? ](https://mortoray.com/2017/12/31/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](http://leaflang.org) to learn more about my programming language. You can also [follow me on Twitter](https://twitter.com/edaqa) or [subscribe to my live stream](https://www.twitch.tv/mortoray) and feel free to ask questions.