-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SIGSEGV with object variants and RTTI #23690
Comments
!nim c |
🐧 Linux bisect by @alex65536 (contributor)devel 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
ASTstable 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
AST2.0.4 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
AST2.0.0 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
AST1.6.20 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
AST1.4.8 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
AST1.2.18 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
AST1.0.10 👎 FAILOutput
IRCompiled filesize0 bytes (0 bytes)
Stats
ASTStats
🤖 Bug found in |
!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 |
It crashed with 1.6.16 with |
Digging further into the compiler code, I noticed the following:
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:
|
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 |
You call |
Note that we're moving away from 0'd memory to the more abstract " |
fixes #23690 ```nim dest.`:state` = src.`:state` var :tmp_553651276 = dest.e1.a `=wasMoved`(dest.e1.a) dest.e1.a.kind = src.e1.a.kind case dest.e1.a.kind of 0: dest.e1.a.a = src.e1.a.a of 1: `=copy`(dest.e1.a.c, src.e1.a.c) case :tmp_553651276.kind of 0: of 1: `=destroy`(:tmp_553651276.c) ``` `dest.e1.a.kind = src.e1.a.kind` changes the discrimant but it fails to clear the memory of `dest.e1.a`. Before using hooks for copying, we need to clear the dest, e.g. `=wasMoved(dest.e1.a.c)`. ```nim dest.`:state` = src.`:state` var :tmp_553651276 = dest.e1.a `=wasMoved`(dest.e1.a) dest.e1.a.kind = src.e1.a.kind case dest.e1.a.kind of 0: `=wasMoved`(dest.e1.a.a) dest.e1.a.a = src.e1.a.a `=wasMoved`(dest.e1.a.b) of 1: `=wasMoved`(dest.e1.a.c) `=copy`(dest.e1.a.c, src.e1.a.c) case :tmp_553651276.kind of 0: of 1: `=destroy`(:tmp_553651276.c) ```
fixes #23690 ```nim dest.`:state` = src.`:state` var :tmp_553651276 = dest.e1.a `=wasMoved`(dest.e1.a) dest.e1.a.kind = src.e1.a.kind case dest.e1.a.kind of 0: dest.e1.a.a = src.e1.a.a of 1: `=copy`(dest.e1.a.c, src.e1.a.c) case :tmp_553651276.kind of 0: of 1: `=destroy`(:tmp_553651276.c) ``` `dest.e1.a.kind = src.e1.a.kind` changes the discrimant but it fails to clear the memory of `dest.e1.a`. Before using hooks for copying, we need to clear the dest, e.g. `=wasMoved(dest.e1.a.c)`. ```nim dest.`:state` = src.`:state` var :tmp_553651276 = dest.e1.a `=wasMoved`(dest.e1.a) dest.e1.a.kind = src.e1.a.kind case dest.e1.a.kind of 0: `=wasMoved`(dest.e1.a.a) dest.e1.a.a = src.e1.a.a `=wasMoved`(dest.e1.a.b) of 1: `=wasMoved`(dest.e1.a.c) `=copy`(dest.e1.a.c, src.e1.a.c) case :tmp_553651276.kind of 0: of 1: `=destroy`(:tmp_553651276.c) ``` (cherry picked from commit 262ff64)
fixes #23690 ```nim dest.`:state` = src.`:state` var :tmp_553651276 = dest.e1.a `=wasMoved`(dest.e1.a) dest.e1.a.kind = src.e1.a.kind case dest.e1.a.kind of 0: dest.e1.a.a = src.e1.a.a of 1: `=copy`(dest.e1.a.c, src.e1.a.c) case :tmp_553651276.kind of 0: of 1: `=destroy`(:tmp_553651276.c) ``` `dest.e1.a.kind = src.e1.a.kind` changes the discrimant but it fails to clear the memory of `dest.e1.a`. Before using hooks for copying, we need to clear the dest, e.g. `=wasMoved(dest.e1.a.c)`. ```nim dest.`:state` = src.`:state` var :tmp_553651276 = dest.e1.a `=wasMoved`(dest.e1.a) dest.e1.a.kind = src.e1.a.kind case dest.e1.a.kind of 0: `=wasMoved`(dest.e1.a.a) dest.e1.a.a = src.e1.a.a `=wasMoved`(dest.e1.a.b) of 1: `=wasMoved`(dest.e1.a.c) `=copy`(dest.e1.a.c, src.e1.a.c) case :tmp_553651276.kind of 0: of 1: `=destroy`(:tmp_553651276.c) ``` (cherry picked from commit 262ff64)
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 unionThe text was updated successfully, but these errors were encountered: