Reproducible on Arch Linux x86_64 with Pony 0.22.6 and 0.23.0
actor Main
new create(env: Env) =>
let x = f(0)._1
match x
| 0 => None
end
fun f(n: U8): ((U8 | None), None) =>
if n == 0 then (0, None) else (None, None) end
This causes a segfault:
$ ponyc --debug
...
$ gdb ./segfault
...
(gdb) run
Starting program: /tmp/segfault/segfault
...
Thread 3 "segfault" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffe6438700 (LWP 15886)]
0x00005555555586d7 in Main_tag_create_oo (this=0x7ffff6c38400, env=0x7ffff6c37c00)
at /tmp/segfault/segfault.pony:3
3 let x = f(0)._1
Had the same error as well: #2609

Please note, this is fine if you build a non-debug version of the program.
What's interesting is this version:
actor Main
new create(env: Env) =>
let x = f(0)._1
match x
| let y: U8 => env.out.print(y.string())
end
fun f(n: U8): ((U8 | None), None) =>
if n == 0 then (0, None) else (None, None) end
results in a segfault in debug mode for me, and an illegal hardware instruction when doing a normal compile.
Pony 0.24.0, MacOS, LLVM 3.9.1
In the function f, where (0, None) is returned, the 0 is supposed to be "boxed". That is, the value is supposed to be an object with a type descriptor rather than a raw machine word number value. The overhead of a type descriptor is required in this case to distinguish the runtime type of the type union later when we want to match against it.
For some reason, the value isn't getting boxed, and the raw zero value ends up there. This means that when we go to do a runtime match and treat it like an object pointer, we're trying to dereference a NULL pointer (zero-valued pointer).
Process 12180 stopped
* thread #2, name = 'test.jemc', stop reason = signal SIGSEGV: invalid address (fault address: 0x0)
frame #0: 0x000000000040d23f test.jemc`Main_tag_create_oo(this=0x00007ffff6c89800, env=0x00007ffff6c89000) at test.pony:0
1 actor Main
2 new create(env: Env) =>
3 let x = f(0)
4 let x1 = x._1
5 match x1
6 | let y: U8 => env.out.print(y.string())
7 end
(lldb) p x
(t2_u2_U8_val_None_val_None_val) $5 = (_1 = 0x0000000000000000, _2 = 0x000000000042b630)
To prove that this zero value is coming from the zero numeric literal rather than somewhere else, I changed the numeric literal to a 7 and ran it again. Sure enough, the value of 7 ended up as the first value in our tuple.
```
Process 6656 stopped
Process 6656 launched: './test.jemc' (x86_64)
(lldb) p x
(t2_u2_U8_val_None_val_None_val) $0 = (_1 = 0x0000000000000007, _2 = 0x000000000042b630)
It seems this is related to the old "tuple of unions vs union of tuples" issue, in which from a type system perspective ((U8, None) | (None, None)) is logically identical to ((U8 | None), None). Issue #1892 indicates that this isn't implemented yet. However, at least in this case it seems to be at play.
During the reach pass, the type of function f looks like this (as expected):
(tupletype
(uniontype
(nominal (id $0) (id U8) x val x x)
(nominal (id $0) (id None) x val x x)
)
(nominal (id $0) (id None) x val x x)
)
However, the type of the if statement here that represents the body of the method, the type looks like this:
(uniontype
(tupletype
(nominal (id $0) (id None) x val ^ x)
(nominal (id $0) (id None) x val ^ x)
)
(tupletype
(nominal (id $0) (id U8) x val x x)
(nominal (id $0) (id None) x val ^ x)
)
)
This kind of explains why the value isn't getting boxed - from the perspective of the method body, it's not part of a type union.
Let's talk about this more at the next sync call.
A+ triaging @jemc
Has there been any discussion of / work on this since July? Should matching on tuples involving unions be avoided until this this fixed?
@bb010g no work that I am aware of. matching on tuples involving unions is somewhat problematic at this time.
My take: there's a general problem with the implementation of tuples in Pony. It's a regular source of bugs. We need to make a change to have tuples have a type and we can avoid the issue.
This should be fixed with the changes in #3723.
Most helpful comment
Had the same error as well: #2609
