Yesterday, I am testing nim destructor thing, and find a strange bug.
Look at the code modified from tests/destrcutor/tcustomstrings.nim
type
mystring = object
len, cap: int
data: ptr UncheckedArray[char]
var
allocCount, deallocCount: int
proc `=destroy`*(s: var mystring) =
if s.data != nil:
dealloc(s.data)
inc deallocCount
s.data = nil
s.len = 0
s.cap = 0
proc `=sink`*(a: var mystring, b: mystring) =
if a.data != nil and a.data != b.data:
dealloc(a.data)
inc deallocCount
a.len = b.len
a.cap = b.cap
a.data = b.data
proc ensure(self: var mystring; newLen: int) =
if newLen >= self.cap:
self.cap = max((self.cap * 3) shr 1, newLen)
if self.cap > 0:
if self.data == nil: inc allocCount
self.data = cast[type(self.data)](realloc(self.data, self.cap + 1))
proc create*(lit: string): mystring =
let newLen = lit.len
ensure(result, newLen)
copyMem(addr result.data[result.len], unsafeAddr lit[0], newLen + 1)
result.len = newLen
proc `=`*(a: var mystring; b: mystring) =
if a.data != nil and a.data != b.data:
echo "dealloc ", cast[int](a.data) # the a.data is not nil !!!!
dealloc(a.data)
inc deallocCount
a.data = nil
a.len = b.len
a.cap = b.cap
if b.data != nil:
a.data = cast[type(a.data)](alloc(a.cap + 1))
inc allocCount
copyMem(a.data, b.data, a.cap+1)
proc `&`*(a, b: mystring): mystring =
if result.data == nil:
echo "nil" # here we check it is nil
result = a
proc main() =
for i in 0..<1:
let b = create" to append"
let c = b & create"more here"
main()
When I compile with -d:release, it give SIGSEGV.
nil
dealloc 1791187200
SIGSEGV: Illegal storage access. (Attempt to read from nil?)
The signal is generated in dealloc(a.data) in function =, but before call this function, the a.data is nil !!!! Very weird.
I thought it is a nim compiler bug, but found it is the vc compiler bug finally.
I start a debugger to check the call to =, to find that the argument is passed wrong way. Function = is defined as __fastcall, but the c compiler generated an __stdcall to =.
Here is the c code generated by nim.
# function `=`
N_LIB_PRIVATE N_NIMCALL(void, eq__qx39cr0PtF4OjyizSYR9bNoA_2)(tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg* a, tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg b) {
{
NIM_BOOL T3_;
tyArray_Re75IspeoxXy2oCZHwcRrA T7_;
T3_ = (NIM_BOOL)0;
T3_ = !(((*a).data == NIM_NIL));
if (!(T3_)) goto LA4_;
T3_ = !(((*a).data == b.data));
LA4_: ;
if (!T3_) goto LA5_;
nimZeroMem((void*)T7_, sizeof(tyArray_Re75IspeoxXy2oCZHwcRrA));
T7_[0] = copyString(((NimStringDesc*) &TM_59a5wK6mGRY2v9bEMYO8KsFQ_6));
T7_[1] = nimIntToStr(((NI) (ptrdiff_t) ((*a).data)));
echoBinSafe(T7_, 2);
dealloc_RCjNtRnHdRYntrcE7YtwWw(((void*) ((*a).data)));
deallocCount_dV8zMfA1D9bZ5xF49boNsMlw += ((NI) 1);
(*a).data = NIM_NIL;
}
LA5_: ;
(*a).len = b.len;
(*a).cap = b.cap;
{
void* T12_;
if (!!((b.data == NIM_NIL))) goto LA10_;
T12_ = (void*)0;
T12_ = alloc_4cubgSerkjpuLcj5MXjiLw_2(((NI) ((NI)((*a).cap + ((NI) 1)))));
(*a).data = ((NIM_CHAR*) (T12_));
allocCount_LvnLtTHe5aJKq8hYDBw7tg += ((NI) 1);
copyMem_fPlwH3r9agN9aEHB6yCPMh0wsystem(((void*) ((*a).data)), ((void*) (b.data)), ((NI) ((NI)((*a).cap + ((NI) 1)))));
}
LA10_: ;
}
# function `&`
N_LIB_PRIVATE N_NIMCALL(tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg, amp__fgQgIaHVxaWqgQAbb9a3gsw)(tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg a, tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg b) {
tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg result;
nimZeroMem((void*)(&result), sizeof(tyObject_mystring_ZsWUolR2hPINlsA3QxLwCg));
{
if (!(result.data == NIM_NIL)) goto LA3_;
echoBinSafe(TM_59a5wK6mGRY2v9bEMYO8KsFQ_4, 1);
}
LA3_: ;
eq__qx39cr0PtF4OjyizSYR9bNoA_2((&result), a);
return result;
}
The problem is: the c compiler inline the & function to main and generated wrong code:
# function `=` using __fastcall, get the first argument from ecx
@eq__qx39cr0PtF4OjyizSYR9bNoA_2@16 PROC NEAR
sub esp, 8 ; 0170 _ 83. EC, 08
push esi ; 0173 _ 56
mov esi, ecx ; 0174 _ 8B. F1
mov eax, dword ptr [esi+8H] ; 0176 _ 8B. 46, 08
test eax, eax ; 0179 _ 85. C0
setne cl ; 017B _ 0F 95. C1
test cl, cl ; 017E _ 84. C9
jz ?_010 ; 0180 _ 74, 56
cmp eax, dword ptr [esp+18H] ; 0182 _ 3B. 44 24, 18
setne al ; 0186 _ 0F 95. C0
test al, al ; 0189 _ 84. C0
jz ?_010 ; 018B _ 74, 4B
xor eax, eax ; 018D _ 33. C0
mov ecx, offset _TM_59a5wK6mGRY2v9bEMYO8KsFQ_6; 018F _ B9, 00000000(d)
mov dword ptr [esp+4H], eax ; 0194 _ 89. 44 24, 04
mov dword ptr [esp+8H], eax ; 0198 _ 89. 44 24, 08
call @copyString@4 ; 019C _ E8, 00000000(rel)
mov ecx, dword ptr [esi+8H] ; 01A1 _ 8B. 4E, 08
mov dword ptr [esp+4H], eax ; 01A4 _ 89. 44 24, 04
call @nimIntToStr@4 ; 01A8 _ E8, 00000000(rel)
mov edx, 2 ; 01AD _ BA, 00000002
lea ecx, [esp+4H] ; 01B2 _ 8D. 4C 24, 04
mov dword ptr [esp+8H], eax ; 01B6 _ 89. 44 24, 08
call @echoBinSafe@8 ; 01BA _ E8, 00000000(rel)
mov eax, dword ptr [esi+8H] ; 01BF _ 8B. 46, 08
push eax ; 01C2 _ 50
call _dealloc_RCjNtRnHdRYntrcE7YtwWw ; 01C3 _ E8, 00000000(rel)
add esp, 4 ; 01C8 _ 83. C4, 04
inc dword ptr [_deallocCount_dV8zMfA1D9bZ5xF49boNsMlw]; 01CB _ FF. 05, 00000000(d)
mov dword ptr [esi+8H], 0 ; 01D1 _ C7. 46, 08, 00000000
?_010: cmp dword ptr [esp+18H], 0 ; 01D8 _ 83. 7C 24, 18, 00
mov ecx, dword ptr [esp+10H] ; 01DD _ 8B. 4C 24, 10
mov eax, dword ptr [esp+14H] ; 01E1 _ 8B. 44 24, 14
mov dword ptr [esi], ecx ; 01E5 _ 89. 0E
mov dword ptr [esi+4H], eax ; 01E7 _ 89. 46, 04
jz ?_011 ; 01EA _ 74, 26
inc eax ; 01EC _ 40
push eax ; 01ED _ 50
call _alloc_4cubgSerkjpuLcj5MXjiLw_2 ; 01EE _ E8, 00000000(rel)
mov dword ptr [esi+8H], eax ; 01F3 _ 89. 46, 08
inc dword ptr [_allocCount_LvnLtTHe5aJKq8hYDBw7tg]; 01F6 _ FF. 05, 00000000(d)
mov edx, dword ptr [esi+4H] ; 01FC _ 8B. 56, 04
mov eax, dword ptr [esp+1CH] ; 01FF _ 8B. 44 24, 1C
mov ecx, dword ptr [esi+8H] ; 0203 _ 8B. 4E, 08
inc edx ; 0206 _ 42
push edx ; 0207 _ 52
push eax ; 0208 _ 50
push ecx ; 0209 _ 51
call _memcpy ; 020A _ E8, 00000000(rel)
add esp, 16 ; 020F _ 83. C4, 10
?_011: pop esi ; 0212 _ 5E
add esp, 8 ; 0213 _ 83. C4, 08
ret 12 ; 0216 _ C2, 000C
@eq__qx39cr0PtF4OjyizSYR9bNoA_2@16 ENDP
# inlined code from `main`, look at the call to function `=`, it use `__stdcall` to push the argument
xor eax, eax ; 0395 _ 33. C0
test eax, eax ; 03AF _ 85. C0
jnz ?_016 ; 03B1 _ 75, 0D
lea edx, [eax+1H] ; 03B3 _ 8D. 50, 01
mov ecx, offset _TM_59a5wK6mGRY2v9bEMYO8KsFQ_4; 03B6 _ B9, 00000000(d)
call @echoBinSafe@8 ; 03BB _ E8, 00000000(rel)
?_016: sub esp, 12 ; 03C0 _ 83. EC, 0C
mov eax, esp ; 03C3 _ 8B. C4
mov dword ptr [eax], esi ; 03C5 _ 89. 30
mov dword ptr [eax+4H], edi ; 03C7 _ 89. 78, 04
mov dword ptr [eax+8H], ebx ; 03CA _ 89. 58, 08
lea eax, [ebp+48H] ; 03CD _ 8D. 45, 48
push eax ; 03D0 _ 50
call @eq__qx39cr0PtF4OjyizSYR9bNoA_2@16 ; 03D1 _ E8, 00000000(rel)
mov ecx, dword ptr [ebp+68H] ; 03D6 _ 8B. 4D, 68
mov esi, dword ptr [ebp+50H] ; 03D9 _ 8B. 75, 50
test ecx, ecx ; 03DC _ 85. C9
This problem only exist vc compiler 2005, 2008, 2010 x86 version with /O2 or /Ox.
We can add compiler flat /Ob1 to disable greed inline to avoid the problem.
No, the = function code is generated as __fastcall, and & function call = as __fastcall.
If the & is not inlined in function main with compiler option /Ob1 or /Ob0, then all is fine. But when the function & is inlined in function main, the code is generated as __stdcall to call the =.