Page 1 of 1

COM reference leak when procedure trapped

Posted: Tue Feb 02, 2021 12:35 pm
by X512
Test code:

Code: Select all

MODULE A;
	IMPORT COM, Log := StdLog;
	
	TYPE
		Object = POINTER TO RECORD (COM.IUnknown)
		END;
	
	PROCEDURE (o: Object) RELEASE;
	BEGIN
		Log.String("Object.RELEASE"); Log.Ln;
	END RELEASE;
	
	PROCEDURE (o: Object) FINALIZE;
	BEGIN
		Log.String("Object.FINALIZE"); Log.Ln;
	END FINALIZE;
	
	PROCEDURE NewObject (): Object;
		VAR o: Object;
	BEGIN
		NEW(o);
		RETURN o;
	END NewObject;
	
	PROCEDURE Do*;
		VAR obj: COM.IUnknown; 
	BEGIN
		obj := NewObject();
		HALT(0);
	END Do;
	
END A.

A.Do
Kernel.Collect
DevDecoder.Decode A
When calling A.Do, obj.Release is not called and reference become leaked causing memory leak. Leaked objects can be confirmad in COM -> Show Interfaces menu. Leaked objects can't be collected by garbage collector.

SEH handler that call IUnknown.Release should be installed to fix issue. SEH handler code generator is already present and used for [guarded] procedures.

Note that Blackbox compiler implements RAII for IUnknown pointers, IUnknown.Release method is called when procedure returns.

Re: COM reference leak when procedure trapped

Posted: Thu Feb 04, 2021 11:55 am
by luowy
understand, but how to do?

Re: COM reference leak when procedure trapped

Posted: Sat Feb 06, 2021 8:06 am
by X512
Something like this should be generated by compiler to solve issue (code is functional):

Code: Select all

MODULE A;
	IMPORT S := SYSTEM, W := WinApi, COM, Log := StdLog;
   
	CONST
		ExceptionContinueExecution = 0;
		ExceptionContinueSearch = 1;
		ExceptionNestedException = 2;
		ExceptionCollidedUnwind = 3;
		
		EXCEPTION_UNWINDING = {1};
		EXCEPTION_EXIT_UNWIND = {2};
		EXCEPTION_STACK_INVALID = {3};
		EXCEPTION_NESTED_CALL = {4};
		EXCEPTION_TARGET_UNWIND = {5};
		EXCEPTION_COLLIDED_UNWIND = {6};
	
	TYPE
		ExcpFramePtr = POINTER TO ExcpFrame;
		ExcpFrame = EXTENSIBLE RECORD [untagged]
			link: ExcpFramePtr;
			handler: PROCEDURE [ccall] (
				excpRec: W.PtrEXCEPTION_RECORD;
				estFrame: ExcpFramePtr;
				context: W.PtrCONTEXT;
				dispCont: INTEGER
			): INTEGER;
		END;
		ReleaseHandlerPtr = POINTER TO ReleaseHandler;
		ReleaseHandler = RECORD (ExcpFrame)
			ptr: INTEGER;
		END;
	
	Object = POINTER TO RECORD (COM.IUnknown)
	END;

	PROCEDURE [code] InstallExcp (VAR e: ExcpFrame) 64H, 8BH, 0DH, 0, 0, 0, 0, 89H, 8, 64H, 0A3H, 0, 0, 0, 0;
	PROCEDURE [code] RemoveExcp (VAR e: ExcpFrame) 8BH, 0, 64H, 0A3H, 0, 0, 0, 0;

	PROCEDURE [ccall] ExcpHandler (
		excpRec: W.PtrEXCEPTION_RECORD;
		estFrame: ExcpFramePtr;
		context: W.PtrCONTEXT;
		dispCont: INTEGER
	): INTEGER;
		VAR unk: COM.IUnknown;
	BEGIN
		Log.String("ExcpHandler(");
		Log.Set(excpRec.ExceptionFlags);
		Log.String(")"); Log.Ln;
		IF excpRec.ExceptionFlags * (EXCEPTION_UNWINDING + EXCEPTION_EXIT_UNWIND) # {} THEN
			(* cleanup phase, called from W.RtlUnwind *)
			(* call ptr.Release *)
			S.PUT(S.ADR(unk), estFrame(ReleaseHandlerPtr).ptr); unk := NIL;
		ELSE
			(* final handler search phase *)
		END;
		RETURN ExceptionContinueSearch;
	END ExcpHandler;

	PROCEDURE (o: Object) RELEASE;
	BEGIN
		Log.String("Object.RELEASE"); Log.Ln;
	END RELEASE;

	PROCEDURE (o: Object) FINALIZE;
	BEGIN
		Log.String("Object.FINALIZE"); Log.Ln;
	END FINALIZE;

	PROCEDURE NewObject (): Object;
		VAR o: Object;
	BEGIN
		NEW(o);
		RETURN o;
	END NewObject;

	PROCEDURE Do*;
		VAR excp: ReleaseHandler; obj: COM.IUnknown;
	BEGIN
		excp.handler := ExcpHandler;
		excp.ptr := 0;
		InstallExcp(excp);
		obj := NewObject();
		excp.ptr := S.VAL(INTEGER, obj);
		HALT(0);
		RemoveExcp(excp);
	END Do;

END A.

A.Do
Kernel.Collect
DevDecoder.Decode A
Compiler already generate SEH handler install code for [guarded] procedures, handler is Kernel.InterfaceTrapHandler. It can be reused and new SEH handler can be created for calling IUnknown.Release for COM pointer in local variables. COM pointer list should be generated by compiler and passed to SEH handler.

Re: COM reference leak when procedure trapped

Posted: Sat Feb 27, 2021 3:16 am
by luowy
thanks X512!
I made a fixup, three files has changed: DevCPC486, DevCPV486, Kernel;
For easy testing, I build the whole FW(base on the last distribution of community), TestCom is a test file;
Feedback is welcome!