Recent

Author Topic: Trying to check if object is Freed.  (Read 2474 times)

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Trying to check if object is Freed.
« on: April 05, 2025, 09:58:57 pm »
I know I should use FreeAndNil.

I just wandering, if a program compiled with $R+, then procedure Int_Check_Object(vmt) called on each object invocation.
It verifies that the object constructor has been called first to build the VMT of the object, otherwise it throws an Runtime error 210.
Parameters: vmt = Current value of the SELF register.

I do not want to throw 210, I want to make a function Check_Object that checks the VMT of the object same way as Int_Check_Object and returns true/false.


I found procedure fpc_check_object in generic.inc
Code: Pascal  [Select][+][-]
  1. procedure fpc_check_object(_vmt : pointer); [public,alias:'FPC_CHECK_OBJECT'];  compilerproc;
  2. begin
  3.   if (_vmt=nil) or
  4.      (pobjectvmt(_vmt)^.size=0) or
  5.      (pobjectvmt(_vmt)^.size+pobjectvmt(_vmt)^.msize<>0) then
  6.     HandleErrorAddrFrameInd(210,get_pc_addr,get_frame);
  7. end;
  8.  

But my function does not work
Code: Pascal  [Select][+][-]
  1. type
  2.   pobjectvmt=^tobjectvmt;
  3.   tobjectvmt=record
  4.     size,msize:sizeuint;
  5.     parent:{$ifdef VER3_0}pointer{$else}ppointer{$endif};
  6.   end;
  7.  
  8. function check_object(_vmt : pointer): boolean;
  9. begin
  10.   Result := not ( (_vmt=nil) or
  11.      (pobjectvmt(_vmt)^.size=0) or
  12.      (pobjectvmt(_vmt)^.size+pobjectvmt(_vmt)^.msize<>0)
  13.   );
  14. end;
  15.  
  16. procedure TForm1.Button2Click(Sender: TObject);
  17. var O: TObject; IsGood: boolean;
  18. begin
  19.   O := TObject.Create;
  20.   IsGood := check_object(O);
  21.   O.Free;
  22.   IsGood := check_object(O);
  23. end;
  24.  

What am I doing wrong?
Where can I get VMT of the object (not class)?
« Last Edit: April 05, 2025, 10:04:49 pm by mm7 »

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: Trying to check if object is Freed.
« Reply #1 on: April 05, 2025, 11:53:56 pm »
I've made it!  ;)

May be it is overkill, but I also compare the object size with its class instance size, for more reliability.

Code: Pascal  [Select][+][-]
  1. type
  2.   pobjectvmt=^tobjectvmt;
  3.   tobjectvmt=record
  4.     size,msize:SizeUInt;
  5.     parent:{$ifdef VER3_0}pointer{$else}ppointer{$endif};
  6.   end;
  7.  
  8. type
  9. TobjHdr=record vmt: pobjectvmt; end;
  10. PobjHdr=^TobjHdr;
  11.  
  12. function check_object(O: TObject; C: TClass): boolean;
  13. var _vmt : pobjectvmt; InstSz:SizeUInt;
  14. begin
  15.   InstSz := C.InstanceSize;
  16.   _vmt := PobjHdr(O)^.vmt;
  17.   Result := not ( (_vmt = nil)
  18.     or (_vmt^.size = 0)
  19.     or (_vmt^.size <> InstSz)
  20.     or (_vmt^.size <> not(_vmt^.msize)+1)
  21.   );
  22. end;
  23.  
  24. procedure TForm1.Button2Click(Sender: TObject);
  25. var O: TBitmap; IsGood: boolean;
  26. begin
  27.   O := TBitmap.Create;
  28.   IsGood := check_object(O, TBitmap);
  29.  
  30.   O.Free;
  31.  
  32.   IsGood := check_object(O, TBitmap);
  33. end;
  34.  

It also works when program compiled without $R+.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11353
  • Debugger - SynEdit - and more
    • wiki
Re: Trying to check if object is Freed.
« Reply #2 on: April 06, 2025, 12:35:01 am »
Well, it's may fail at random....

Depending on how you compile, if you do MyObject.Free  the memory will not be "cleared".
So a ghost object will still be there. And any check that you may run, may take that ghost for a living thing.


jamie

  • Hero Member
  • *****
  • Posts: 6960
Re: Trying to check if object is Freed.
« Reply #3 on: April 06, 2025, 01:10:04 am »
@mm7, as stated that will be no better.

Save yourself from yourself and use FreeAndNil
The only true wisdom is knowing you know nothing

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: Trying to check if object is Freed.
« Reply #4 on: April 06, 2025, 04:28:00 am »
Well, it's may fail at random....

Depending on how you compile, if you do MyObject.Free  the memory will not be "cleared".
So a ghost object will still be there. And any check that you may run, may take that ghost for a living thing.

Well, does it mean if a program compiled with $R+ it also can fail at random?

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1530
    • Lebeau Software
Re: Trying to check if object is Freed.
« Reply #5 on: April 06, 2025, 06:03:20 am »
Once an object is freed, all bets are off if you try to access the memory that the object occupied. You don't know what the content of the memorymis, or even if the memory is valid anymore. Trying to do any kind of analysis to determine if the object is still valid or not is undefined behavior. So just don't do it. The only safe option you have is to discard or nil any pointer you had pointing at the object.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11353
  • Debugger - SynEdit - and more
    • wiki
Re: Trying to check if object is Freed.
« Reply #6 on: April 06, 2025, 09:47:07 am »
Well, does it mean if a program compiled with $R+ it also can fail at random?

For detecting dangling pointer to freed objects: Yes.

Code: Pascal  [Select][+][-]
  1. var a: TStringList;
  2. begin
  3.   a:= TStringList.create;
  4.   a.add('abc');
  5.   a.Destroy;
  6.   if a.count = 1 then writeln(1) else writeln('?');
  7. end;

That code may run without error, and at random chance print 1 or ?.

Code: Pascal  [Select][+][-]
  1. var a,b: TStringList;
  2. begin
  3.   a:= TStringList.create;
  4.   b := a;
  5.   a.add('abc');
  6.   FreeAndNil(a);
  7.   if b.count = 1 then writeln(1) else writeln('?');
  8. end;
Same as above. FreeAndNil does not take care of the copied pointer in b. That will be dangling at this point.





$R+ is range checks? I am not sure what they activate in terms of object checking.

But fpc has various checks available:

-gh heaptrc
Afaik (needs double checking) this clears released memory, and keeps it out of circulation for a limited time => so it increases chances that the code above will crash, or that other checks will detect the error in the above code.

-CR (method checking)
wont do much on the above code. It checks that the object has the correct class if you call virtual methods.
That may or may not fail if you call a virtual method on a freed object. Basically if the memory has been altered then the class will look wrong.
Mind, this check can crash by itself. If the altered memory points to locations not owned by the app.

 $OBJECTCHECKS
https://www.freepascal.org/docs-html/prog/progsu57.html#x64-630001.2.57
Checks for nil. So you get an exception or run-time error rather than an access violation. But only if you nil'ed the variable.


$CHECKPOINTER
https://www.freepascal.org/docs-html/prog/progsu8.html#x15-140001.2.8
That has a rather good chance to detect problems. (not 100%, but usually close to it)
Beware: this is majorly slow (IIRC)
This does not check what is at the end of the pointer, but rather checks if the pointer is in the internal tables of allocated memory.  So at least it should prevent the code from ever access violate.

Mind you there is a chance that even if the memory was freed, something else has allocated memory at the same location => then the check passes, even so your pointer is dangling.
And, if that something else has content that partly looks like your object then it can fool the other checks, if they are enabled too. But that is like a very very low chance.




Outside of FPC, there is
  valgrind --tool=memcheck

That will detect freed pointer (including object) access reliably. It keeps track of every alloc and dealloc, and it does prevent re-use of memory (at least for a long time / need to check the docs).

Mind you, that it is majorly slow.


I heard, but do not know, that if you use the llvm backend of fpc, then there are similar checks avail, that also work (very/almost?) reliable. Not sure how much exactly....

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: Trying to check if object is Freed.
« Reply #7 on: April 06, 2025, 04:38:36 pm »
Well, does it mean if a program compiled with $R+ it also can fail at random?

For detecting dangling pointer to freed objects: Yes.

$R+ is range checks? I am not sure what they activate in terms of object checking.

Thank you, Martin for the comprehensive explanation.
I do not disagree with what you and others said.
However I kind of trusted the FPC checks, $R+ and others.
I got the info from this doc https://www.stack.nl/~marcov/comparch.pdf
11.4 FPC_CHECK_OBJECT

I understand how memory allocation and re-allocation works, and also concerned of if some other object of same class will be allocated in exactly same place. It will fool the check.
But again, I trusted FPC, thought it may use some magic under the hood, when $R+, to distinguish the freed object from a new one. That is why I started this exercise.

Now I compiled RTL with debug option to investigate it further.
But it does not compile. Throws error
Project2.lpr(12,3) Error: Cannot find Interfaces used by Project2, incompatible ppu=/usr/share/lazarus/3.8.0/lcl/units/x86_64-linux/gtk2/interfaces.ppu, package LCL

Should I rebuild LCL with debug as well?

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: Trying to check if object is Freed.
« Reply #8 on: April 06, 2025, 04:50:05 pm »
Martin, I used your code for test, modified it slightly.
Code: Pascal  [Select][+][-]
  1. type
  2.   pobjectvmt=^tobjectvmt;
  3.   tobjectvmt=record
  4.     size,msize:SizeUInt;
  5.     parent:{$ifdef VER3_0}pointer{$else}ppointer{$endif};
  6.   end;
  7.  
  8. type
  9. TobjHdr=record vmt: pobjectvmt; end;
  10. PobjHdr=^TobjHdr;
  11.  
  12. // Checks if an object is dangling, similarly as FPC $R+ does.
  13. function check_object(O: TObject; C: TClass): boolean;
  14. var _vmt : pobjectvmt; InstSz:SizeUInt;
  15. begin
  16.   if O <> nil then
  17.   begin
  18.     InstSz := C.InstanceSize;
  19.     _vmt := PobjHdr(O)^.vmt;
  20.     Result := not ( (_vmt = nil)
  21.       or (_vmt^.size = 0)
  22.       or (_vmt^.size <> InstSz)
  23.       or (_vmt^.size <> not(_vmt^.msize)+1)
  24.     );
  25.   end
  26.   else Result := false;
  27. end;
  28.  
  29. procedure TForm1.Button3Click(Sender: TObject);
  30. var a: TStringList; i:integer;
  31. begin
  32.   for i:=0 to 999999999 do
  33.   begin
  34.     a:= TStringList.create;
  35.     a.add('abc');
  36.     a.Destroy;
  37.     if check_object(a,TStringList) then
  38.     begin
  39.        writeln('failure at ',i);
  40.        {$R+}
  41.        writeln(a.count); // FPC R$ check
  42.        {$R-}
  43.      end;
  44.   end;
  45. end;
  46.  

I ran it for billion checks. With global range check on and off, both.
No single failure.
I agree, there is some probability of a failure.
But it is same probability that FPC range check provides.

So, my function can be used, carefully. In some cases where using the good practice FreeAndNil() / Assigned() is too convoluted. Or in addition to it.
But it does not replace the good practice.

jamie

  • Hero Member
  • *****
  • Posts: 6960
Re: Trying to check if object is Freed.
« Reply #9 on: April 06, 2025, 10:36:10 pm »
I really don't understand why you are going off the deep end on doing this?

Could it be that you are trying to determine if outside objects were freed while in use of your class?

If that being the case, the TCOMPONENT has a "notification" mechanism whereas your component will get notified if any used objects outside get released. You simply need to add your component to the list of the outside component so your component will tack its demise.

Jamie
The only true wisdom is knowing you know nothing

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11353
  • Debugger - SynEdit - and more
    • wiki
Re: Trying to check if object is Freed.
« Reply #10 on: April 06, 2025, 11:28:03 pm »
I ran it for billion checks. With global range check on and off, both.

That is because of the default mem manager overwrites part of the freed memory.

So indeed with the default mem manager (and also with heaptrc), you do not get an error for the "freed ghost". But you may get an error if a new instance was created in the meantime.

Try this (ignore the leak for b, that can be fixed):
Code: Pascal  [Select][+][-]
  1.     procedure TForm1.Button1Click(Sender: TObject);
  2.     var a,b: TStringList; i:integer;
  3.     begin
  4.       for i:=0 to 999999999 do
  5.       begin
  6.         a:= TStringList.create;
  7.         a.add('abc');
  8.         a.Destroy;
  9.         b:= TStringList.create;
  10.         if check_object(a,TStringList) then
  11.         begin
  12.            writeln('failure at ',i);
  13.            {$R+}
  14.            writeln(a.count); // FPC R$ check
  15.            {$R-}
  16.          end;
  17.       end;
  18.     end;
  19.  


Or alternatively, open the project source and as the very first uses insert
Code: Pascal  [Select][+][-]
  1. uses
  2. cmem,
  3.  




If you recompiled the RTL you have to rebuild the LCL too (it may or may not do that automatically). You can do that with or without debug info. just rebuild is needed.

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: Trying to check if object is Freed.
« Reply #11 on: April 07, 2025, 12:03:21 am »
I really don't understand why you are going off the deep end on doing this?

Could it be that you are trying to determine if outside objects were freed while in use of your class?

If that being the case, the TCOMPONENT has a "notification" mechanism whereas your component will get notified if any used objects outside get released. You simply need to add your component to the list of the outside component so your component will tack its demise.

Jamie

The app I am working on (CAD-like) creates thousands of objects: points, edges, faces, etc.
They are linked one to another all kinds of ways. When a user edits them, he can create points, split edges (that creates points), merge them back, split faces, etc ...
And tracking of these dependencies is quite convoluted.
When a face is deleted, it should delete edges that belong solely to it. When an edge is deleted it should delete its points, etc ...
Also there are global lists of them for fast search.
Using TCOMPONENT would be quite heavy, I think.

jamie

  • Hero Member
  • *****
  • Posts: 6960
Re: Trying to check if object is Freed.
« Reply #12 on: April 07, 2025, 01:41:54 am »
Yes, I've done such programs.

 But let me pass some advice on to you, you would be better off just making the simple classes and have a single instance for each type of figure, and then have a flat tree table reading the type so that it can be CASE switched and the current location in the tree table pass via a pointer or offset index to a single instance of the correct class that handles that one case.

 Creating many class instances of the same type will most likely chew up resources, where a table of the items of the needed info can be past to a single instant of the class to handle the type at hand.
The only true wisdom is knowing you know nothing

mm7

  • Full Member
  • ***
  • Posts: 222
  • PDP-11 RSX Pascal, Turbo Pascal, Delphi, Lazarus
Re: Trying to check if object is Freed.
« Reply #13 on: April 07, 2025, 04:58:07 am »
Yes, I've done such programs.

 But let me pass some advice on to you, you would be better off just making the simple classes and have a single instance for each type of figure, and then have a flat tree table reading the type so that it can be CASE switched and the current location in the tree table pass via a pointer or offset index to a single instance of the correct class that handles that one case.

 Creating many class instances of the same type will most likely chew up resources, where a table of the items of the needed info can be past to a single instant of the class to handle the type at hand.

Probably. But it is too much of re-work. Reading raw data from table will do memcopy load. Also it is almost same as having just objects of classess without tables. The object has pointer to object VMT, and then to class VMT where methods are located. So basically the object in memory is pointer to VMT + instance data (that includes pointer to class VMT). Two pointers per object data is not a big deal.

PS/ I cannot compile Lazarus. Linker fails with
Code: Text  [Select][+][-]
  1. (9015) Linking ../lazbuild
  2. /usr/bin/ld: /usr/share/lazarus/3.8.0/lcl/units/x86_64-linux/intfgraphics.o: in function `.La16':
  3. intfgraphics.pas:(.debug_info+0x8961): undefined reference to `DBG2_$SYSTEM_$$_IUNKNOWN'
  4. /usr/bin/ld: /usr/share/lazarus/3.8.0/lcl/units/x86_64-linux/intfgraphics.o: in function `.La18':
  5. intfgraphics.pas:(.debug_info+0x89f0): undefined reference to `DBG2_$SYSTEM_$$_IUNKNOWN'
  6. /usr/bin/ld: /usr/share/lazarus/3.8.0/lcl/units/x86_64-linux/graphics.o: in function `.La151':
  7. graphics.pp:(.debug_info+0x1dc5a): undefined reference to `DBG2_$SYSTEM_$$_IUNKNOWN'
  8. /usr/bin/ld: /usr/share/lazarus/3.8.0/lcl/units/x86_64-linux/graphics.o: in function `.La152':
  9. graphics.pp:(.debug_info+0x1dd1b): undefined reference to `DBG2_$SYSTEM_$$_IUNKNOWN'
  10. /usr/bin/ld: /usr/share/lazarus/3.8.0/lcl/units/x86_64-linux/imagelistcache.o: in function `.La1':
  11. imagelistcache.pas:(.debug_info+0x170): undefined reference to `DBG2_$SYSTEM_$$_IUNKNOWN'
  12. /usr/bin/ld: /usr/share/lazarus/3.8.0/components/ideintf/units/x86_64-linux/nogui/idehelpintf.o:idehelpintf.pas:(.debug_info+0xc48): more undefined references to `DBG2_$SYSTEM_$$_IUNKNOWN' follow
  13. /usr/share/lazarus/3.8.0/ide/lazbuild.lpr(1883,1) Error: (9013) Error while linking
  14. /usr/share/lazarus/3.8.0/ide/lazbuild.lpr(1883,1) Fatal: (10026) There were 1 errors compiling module, stopping
  15. Fatal: (1018) Compilation aborted
  16.  

Why it generates DBG2_$SYSTEM_$$_IUNKNOWN symbol? And other DBG2?
When I compiled FPC with debug it did not generate any DBG2.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 11353
  • Debugger - SynEdit - and more
    • wiki
Re: Trying to check if object is Freed.
« Reply #14 on: April 07, 2025, 07:07:07 am »
Why it generates DBG2_$SYSTEM_$$_IUNKNOWN symbol? And other DBG2?
When I compiled FPC with debug it did not generate any DBG2.

Afaik, that happens on Linux when you mix Dwarf2 and Dwarf3.




 

TinyPortal © 2005-2018