Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Hanging object reference from trifecta of Type(), _Access(), and .Parent [SOLVED] 3

Status
Not open for further replies.

nqramjets

Programmer
Jun 20, 2012
15
US
This took two days to track down, so I figured it deserved posting for posterity.

We came upon a situation where the simple solution to our problem was to add an _Access method to an object-type property which would go dynamically retrieve the requested object for us.
In another segment of code we needed to know whether or not the parent object was hanging off of a form class so we could display an error message rather than just log it.
This code used [tt]TYPE()[/tt] as a guard because the expression tested for a Parent reference and, as you all know, [tt]VARTYPE()[/tt] blows up in that scenario.

Don't do all three of these things together!

The offending line of code in the below sample is in [tt]Chatty:Test()[/tt] and evaluates to this: [tt]TYPE("this.ioChild.Parent")[/tt].
This [tt]TYPE()[/tt] invocation causes a dangling object reference to the object returned from the [tt]ioChild_Access()[/tt] method.

The only way, it appears, to get this issue to happen is when all three of: TYPE(), _Access(), and .Parent appear in the statement.

For example, setting a local variable to the object returned from the access method and then using TYPE() to test its Parent reference works fine.
Using an actual object reference which does not have an access method works fine. (See the "Ok" case below)

But, for the love of FoxPro, DO NOT DO ALL THREE TOGETHER.

Sample code:
Code:
CLEAR

Talk("DoDemo() - Begin")
DoDemo()
Talk("DoDemo() - End")

RETURN

PROCEDURE DoDemo
	Talk("Begin")
	
	LOCAL loObj AS Object
	loObj = CREATEOBJECT("Chatty")
	loObj.Test()
	
	Talk("End")
ENDPROC

PROCEDURE Talk
	LPARAMETERS tuObj
	
	LOCAL lcPrefix AS String, lcProc AS String
	lcPrefix = REPLICATE("  ", MAX(PROGRAM(-1) - 1, 0))
	lcProc = PROGRAM(PROGRAM(-1) - 1) + "()"
	
	DO CASE
		CASE VARTYPE(tuObj) == "O"
			? lcPrefix + "Object " + tuObj.Tag + " of type " + tuObj.Class + " in " + lcProc
			
		CASE VARTYPE(tuObj) == "C"
			? lcPrefix + "Procedure " + lcProc + ": " + tuObj
		OTHERWISE
			? lcPrefix + "Procedure " + lcProc
	ENDCASE
ENDPROC

DEFINE CLASS Chatty AS Custom
	ioChild = .NULL.

	PROCEDURE Init
		this.Tag = SYS(2015)
		Talk(this)
		this.AddObject("oChild", "nested")
	ENDPROC

	PROCEDURE Destroy
		Talk(this)
	ENDPROC
	
	PROCEDURE Test
		Talk("Begin")
		
		LOCAL lcType AS String, lcMember AS String
		
		lcMember = "this.ioChild.Parent"	&& Hang
		*lcMember = "this.oChild.Parent"	&& Ok
		
		lcType = TYPE(lcMember)
		Talk("Type of [" + lcMember + "] is: " + lcType)
		
		Talk("End")
	ENDPROC

	PROCEDURE ioChild_Access
		Talk(this)
		RETURN this.oChild
	ENDPROC
ENDDEFINE

DEFINE CLASS Nested AS Custom
	PROCEDURE Init
		this.Tag = SYS(2015)
		Talk(this)
	ENDPROC

	PROCEDURE Destroy
		Talk(this)
	ENDPROC
ENDDEFINE
 
I spent a decent amount of time tracking down a similar bug and your post was instrumental in finding it! I've reworked your demo to show the situation that was causing dangling object references in my scenario.

For me it was a combination of the _Access method and storing a reference to the parent of the object reference returned from this accessor in a local variable. See the code snippet below.

Code:
CLEAR

Talk("DoDemo - Begin")
DoDemo()
Talk("DoDemo - End")

RETURN


PROCEDURE DoDemo
	Talk("Begin")
	
	LOCAL loObj AS Object
	loObj = CREATEOBJECT("Chatty")
	loObj.Test()
	
	Talk("End")
ENDPROC

PROCEDURE Talk
	LPARAMETERS tuObj
	
	LOCAL lcPrefix AS String, lcProc AS String
	lcPrefix = REPLICATE("  ", MAX(PROGRAM(-1) - 1, 0))
	lcProc = PROGRAM(PROGRAM(-1) - 1)
	
	DO CASE
		CASE VARTYPE(tuObj) == "O"
			? lcPrefix + "Object " + tuObj.Tag + " of type " + tuObj.Class + " in " + lcProc
			
		CASE VARTYPE(tuObj) == "C"
			? lcPrefix + "Procedure " + lcProc + ": " + tuObj
		OTHERWISE
			? lcPrefix + "Procedure " + lcProc
	ENDCASE
ENDPROC

DEFINE CLASS Chatty AS Custom
	ioChild = .NULL.
	
	PROCEDURE Init
		this.Tag = SYS(2015)
		Talk(this)
		this.AddObject("oChild", "Nested")
	ENDPROC

	PROCEDURE Destroy
		Talk(this)
	ENDPROC
	
	PROCEDURE Test
		Talk("Begin")
		
		LOCAL loParent AS Object
		LOCAL lcParentClass AS String
		
		*loParent = this.ioChild.Parent && Hangs
		*lcParentClass = loParent.Class
		
		lcParentClass = this.ioChild.Parent.Class
		
		Talk("End")
	ENDPROC
	
	PROCEDURE ioChild_Access
		Talk(this)
		RETURN this.oChild
	ENDPROC
ENDDEFINE

DEFINE CLASS Nested AS Custom
	PROCEDURE Init
		this.Tag = SYS(2015)
		Talk(this)
	ENDPROC

	PROCEDURE Destroy
		Talk(this)
	ENDPROC
ENDDEFINE
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top