Help with double free (segfault)

New to FreeBASIC? Post your questions here.
Post Reply
GWBASICcoder
Posts: 21
Joined: Jul 29, 2022 12:43

Help with double free (segfault)

Post by GWBASICcoder »

Hi,

Thanks very much for helping me this far with FreeBasic.
I am trying to understand the examples in the "EXTENDS ZSTRING". The following function code causes a double free segmentation
fault.

Code: Select all

function replaceB (byref s as estring) as estring
	print "replaceB entered"
	dim as string ret = s
	dim es as estring
	ret = ret & ret
	es = ret
	return es
end function

'''main
dim ess as estring = "A-"
dim ess2 as estring 
ess2 = replaceA(ess)
print "returned from replace"
print "ess2 = "; ess2
However, this code works fine:

Code: Select all

function replaceA (byref s as estring) as estring
	print "replaceA entered"
	dim as string ret = s
	ret = ret & ret
	return estring(ret)
end function
I can't seem to figure out why FB tries to free the pointer at 20443776 an extra time. See the error message below:

Code: Select all

cons with zstring called -- pz = A-
cons with zstring param -- allocated = 20443648
cons with null called
replaceB entered
cons with null called
let with string param: allocated 20443776
destructor: deallocating 20443776
let with estring param: allocated 20443776
destructor: deallocating 20443776
returned from replace
ess2 =
destructor: deallocating 20443776
free(): double free detected in tcache 2
Aborted (core dumped)
The UDT is below.

Code: Select all

#ifndef null
	#define null 0
#endif
type estring extends zstring
	public:
		declare constructor()
		declare constructor(byval pz as const zstring ptr = 0)
		declare operator cast() as string
		declare operator let(byval pz as const zstring ptr)
		declare operator let(byref es as estring)
		declare operator let(s as string)
		declare function length() as ulongint
		declare destructor()
	private:
		dim as zstring ptr p = null
end type

constructor estring()
	print "cons with null called"
	this.p = null
end constructor

constructor estring(byval pz as const zstring ptr)
	print "cons with zstring called -- pz = "; *pz
	if this.p <> null then
		print "cons with zstring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(*pz) + 1)
	print "cons with zstring param -- allocated = "; this.p
	*this.p = *pz
end constructor

operator estring.cast() as string
	return *this.p
end operator

operator estring.let(byval pz as const zstring ptr)
	if this.p <> null then
		print "let with zstring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(*pz) + 1)
	print "let with zstring param: allocated "; this.p
	*this.p = *pz
end operator

operator estring.let(byref es as estring)
	if this.p <> null then
		print "let with estring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(es.length() + 1)
	print "let with estring param: allocated "; this.p
	*this.p = *es.p
end operator

operator estring.let(s as string)
	if this.p <> null then
		print "let with string param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(s) + 1)
	print "let with string param: allocated "; this.p
	*this.p = s
end operator

destructor estring()
	if this.p <> null then
		print "destructor: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
end destructor

function estring.length() as ulongint
	return len(*this.p)
end function

fxm
Moderator
Posts: 12081
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Help with double free (segfault)

Post by fxm »

Because 'return es' calls the copy-constructor.
As the Type has no explicit copy-constructor, the Type object is implicitly default-constructed then a shallow-copy is done on all fields.
=> That is unsafe because a same allocation will be deallocated twice.

To use 'return es' safely, you must also define an explicit copy-constructor:

Code: Select all

#ifndef null
	#define null 0
#endif
type estring extends zstring
	public:
		declare constructor()
		declare constructor(byval pz as const zstring ptr = 0)
		declare constructor(byref es as estring)
		declare operator cast() as string
		declare operator let(byval pz as const zstring ptr)
		declare operator let(byref es as estring)
		declare operator let(byref s as string)
		declare function length() as ulongint
		declare destructor()
	private:
		dim as zstring ptr p = null
end type

constructor estring()
	print "cons with null called"
	this.p = null
end constructor

constructor estring(byval pz as const zstring ptr)
	print "cons with zstring called -- pz = "; *pz
	if this.p <> null then
		print "cons with zstring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(*pz) + 1)
	print "cons with zstring param -- allocated = "; this.p
	*this.p = *pz
end constructor

constructor estring(byref es as estring)
	print "cons with estring called -- es = "; es
	if this.p <> null then
		print "cons with estring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(es.length() + 1)
	print "cons with estring param -- allocated = "; this.p
	*this.p = *es.p
end constructor

operator estring.cast() as string
	return *this.p
end operator

operator estring.let(byval pz as const zstring ptr)
	if this.p <> null then
		print "let with zstring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(*pz) + 1)
	print "let with zstring param: allocated "; this.p
	*this.p = *pz
end operator

operator estring.let(byref es as estring)
	if this.p <> null then
		print "let with estring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(es.length() + 1)
	print "let with estring param: allocated "; this.p
	*this.p = *es.p
end operator

operator estring.let(byref s as string)
	if this.p <> null then
		print "let with string param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(s) + 1)
	print "let with string param: allocated "; this.p
	*this.p = s
end operator

destructor estring()
	if this.p <> null then
		print "destructor: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
end destructor

function estring.length() as ulongint
	return len(*this.p)
end function



function replaceB (byref s as estring) as estring
	print "replaceB entered"
	dim as string ret = s
	dim es as estring
	ret = ret & ret
	es = ret
	return es
end function

'''main
dim ess as estring = "A-"
dim ess2 as estring 
ess2 = replaceB(ess)
print "returned from replace"
print "ess2 = "; ess2
  • Code: Select all

    cons with zstring called -- pz = A-
    cons with zstring param -- allocated = 9179880
    cons with null called
    replaceB entered
    cons with null called
    let with string param: allocated 9179992
    cons with estring called -- es = A-A-
    cons with estring param -- allocated = 9179896
    destructor: deallocating 9179992
    let with estring param: allocated 9179912
    destructor: deallocating 9179896
    returned from replace
    ess2 = A-A-
    destructor: deallocating 9179912
    destructor: deallocating 9179880
    

Maximizing rule for a very safer code (at compile-time and run-time), but sometimes maximizing the real constraints:
- If the user needs to explicitly defines any form of constructor procedure (including any form of copy-constructor) or any form of let-operator procedure or a destructor procedure, it is strongly recommended to define together the default-constructor and the standard copy-constructor and the standard let-operator and the destructor.
(these 4 explicit procedures are explicitly defined to always overload correctly the corresponding implicit operations from compiler)
GWBASICcoder
Posts: 21
Joined: Jul 29, 2022 12:43

Re: Help with double free (segfault)

Post by GWBASICcoder »

Hey, thanks. Works now after I added the copy-cons as below:

Code: Select all

constructor estring(es as estring)
	print "cons with estring called -- *es.p = "; *es.p
	if this.p <> null then
		print "cons with estring param: deallocating "; this.p
		deallocate(this.p)
		this.p = null
	end if
	this.p = allocate(len(*es.p) + 1)
	print "cons with estring param -- allocated = "; this.p
	*this.p = *es.p
end constructor
Post Reply