Closed alex65536 closed 2 weeks ago
!nim c
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:24
2024-06-07T03:45:24
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:25
2024-06-07T03:45:25
now
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:25
2024-06-07T03:45:25
now
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:28
2024-06-07T03:45:28
now
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:31
2024-06-07T03:45:31
now
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:33
2024-06-07T03:45:33
now
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:35
2024-06-07T03:45:35
0 bytes (0 bytes)
```cpp
```
2024-06-07T03:45:37
2024-06-07T03:45:37
now
11.4.0
14.0.0
20.3
2024-06-07T03:44:52Z
1
nim c --run -d:nimDebug -d:nimDebugDlOpen -d:ssl -d:nimDisableCertificateValidation --forceBuild:on --colors:off --verbosity:0 --hints:off --lineTrace:off --nimcache:/home/runner/work/Nim/Nim --out:/home/runner/work/Nim/Nim/temp /home/runner/work/Nim/Nim/temp.nim
:robot: Bug found in 16 minutes
bisecting 8
commits at 0
commits per second
!nim c
import std/sequtils
type
SomeObj* = object of RootObj
Item* = object
case kind*: 0..1
of 0:
a*: int
b*: SomeObj
of 1:
c*: string
ItemExt* = object
a*: Item
b*: string
proc do1(x: int): seq[(string, Item)] =
result = @[("zero", Item(kind: 1, c: "first"))]
proc do2(x: int, e: ItemExt): seq[(string, ItemExt)] =
do1(x).map(proc(v: (string, Item)): auto = (v[0], ItemExt(a: v[1], b: e.b)))
echo do2(0, ItemExt(a: Item(kind: 1, c: "second"), b: "third"))
@alex65536 wow, your insights are really helpful. I think it's mostly likely that copy
needs to zero memery the union before assignement. I'm working on it. Thank you very much!
It crashed with 1.6.16 with --mm:orc -d:useMalloc
Digging further into the compiler code, I noticed the following:
wasMoved
for the old object.wasMoved
is basically implemented here. Among other things, it calls resetLoc()
, which in our case (code) generates nimZeroMem
+ default-initialization.I am not proficient in the compiler internals at all, so everything below this text in this comment is just a wild guess from a person who has seen the compiler code today for the first time:
how destructors of old values behave when they encounter zeroed memory
Destructors are supposed to be written in such a way that 0'd memory indicates "this was moved"
Destructors are supposed to be written in such a way that 0'd memory indicates "this was moved"
OK, thank you, then zeroing is a nice idea indeed :)
And what happens if a =copy()
implementation hooked by user receives zeroed memory as a destination? Or is there anything that prevents such thing from happening?
You call =destroy
then copy the data over, there is no special logic for data that is 0'd =wasMoved
should disarm =destroy
.
Destructors are supposed to be written in such a way that 0'd memory indicates "this was moved"
Note that we're moving away from 0'd memory to the more abstract "=wasMoved
/default state".
Description
The simplest reproducer I have found by now is:
This code stably leads to segfault under my amd64 Linux machine (tested both on
2.0.4
anddevel
)Nim Version
Also reproducible on stable
2.0.4
.Current Output
Expected Output
Possible Solution
No response
Additional Information
Some extra debugging shows more details:
sequtils.map
with a loop makes the issue go away--mm:refc
doesn't reproduce the bug, but--mm:arc
and--mm:orc
doDebugging points out that
eqcopy
implementation is likely at fault. The compiled C code is as follows:It does the following:
dest_p0->a
TM__ckn2SibXCdoGNfJOD0JenA_5
into it. As far as I understand, it is the default value ofItem
, and as a default value, it haskind: 0
and, becausekind: 0
containsSomeObj
, the contents of the union are filled with some RTTI datakind
, switching the active union element ((*dest_p0).a.kind = (*src_p1).a.kind
). Note that nothing is zeroed at the moment, and all the RTTI data are still in the union