Page 1 of 1

COM reference leak when procedure trapped

PostPosted: 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

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

Re: COM reference leak when procedure trapped

PostPosted: 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

PostPosted: 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!