1

I'm trying to write some generic debug code using the Delphi RTTI. The problem I have come across is that I'm examining the contents of a TList which only holds Pointers. Now I know from my code that these Pointers are in fact TObject references (or some descendant).

So my question is this: given a valid Pointer is there a safe way to determine if it is in fact a TObject reference?

Steve
  • 1,769
  • 2
  • 22
  • 33

1 Answers1

0

There is no safe way to determine whether valid pointer is TObject reference

You can only tell with certainty that pointer is not an object reference.

Having said that, for debugging purposes there is a way to detect that pointer might be an object reference, but you may also get false positives - memory content we are inspecting may satisfy the check by pure chance.


Every object instance also holds a pointer to its class virtual method table - VMT. It also has a pointer pointing to start of its data - offset of that pointer is defined by vmtSelfPtr constant declared in System unit.

Class references in vmtSelfPtr differ from objects as they hold reference back to self.

Above facts will tells us we are not looking at a class reference first, and then we will check whether possible object's VMT points to possible class reference.

Besides that for each pointer we will first check that it belongs to valid address space.

You can find more information about VMT here Internal Data Formats - Class Types

The code that detects whether pointer is possible object is taken from Spring4D library:

uses
  {$IFDEF MSWINDOWS}
  Windows,
  {$ENDIF }
  TypInfo;

function IsValidObject(p: PPointer): Boolean;
{$IFDEF MSWINDOWS}
var
  memInfo: TMemoryBasicInformation;
{$ENDIF}

  function IsValidAddress(address: Pointer): Boolean;
  begin
    // Must be above 64k and 4 byte aligned
    if (UIntPtr(address) > $FFFF) and (UIntPtr(address) and 3 = 0) then
    begin
{$IFDEF MSWINDOWS}
      // do we need to recheck the virtual memory?
      if (UIntPtr(memInfo.BaseAddress) > UIntPtr(address))
        or ((UIntPtr(memInfo.BaseAddress) + memInfo.RegionSize) < (UIntPtr(address) + SizeOf(Pointer))) then
      begin
        // retrieve the status for the pointer
        memInfo.RegionSize := 0;
        VirtualQuery(address, memInfo, SizeOf(memInfo));
      end;
      // check the readability of the memory address
     if (memInfo.RegionSize >= SizeOf(Pointer))
        and (memInfo.State = MEM_COMMIT)
        and (memInfo.Protect and (PAGE_READONLY or PAGE_READWRITE
          or PAGE_WRITECOPY or PAGE_EXECUTE or PAGE_EXECUTE_READ
          or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY) <> 0)
        and (memInfo.Protect and PAGE_GUARD = 0) then
{$ENDIF}
      Exit(True);
    end;
    Result := False;
  end;

begin
  Result := False;
  if Assigned(p) then
  try
{$IFDEF MSWINDOWS}
    memInfo.RegionSize := 0;
{$ENDIF}
    if IsValidAddress(p)
      // not a class pointer - they point to themselves in the vmtSelfPtr slot
      and not (IsValidAddress(PByte(p) + vmtSelfPtr)
      and (p = PPointer(PByte(p) + vmtSelfPtr)^)) then
      if IsValidAddress(p^) and IsValidAddress(PByte(p^) + vmtSelfPtr)
        // looks to be an object, it points to a valid class pointer
        and (p^ = PPointer(PByte(p^) + vmtSelfPtr)^) then
        Result := True;
  except
  end; //FI:W501
end;

And we can use that function like:

var
  o: TObject;
  p: Pointer;
  i: NativeInt;

begin
  i := 5;
  p := @i;
  o := TObject.Create;

  Writeln(IsValidObject(Pointer(o)));  // TRUE
  Writeln(IsValidObject(p));           // FALSE
end.

Note: IsValidObject should only be used on valid pointers - meaning pointers that point to valid allocated memory. You cannot detect whether object instance behind the pointer has been released or not.

If you have following code, you will still get TRUE as the result of the IsValidObject call.

  o := TObject.Create;
  o.Free;
  Writeln(IsValidObject(Pointer(o)));  // TRUE

Note: Besides for debugging purposes, IsValidObject can be safely called in release mode on any pointer you know it is either nil, class reference, or object reference. In other words you can safely use it to distinguish between class and object references.

Dalija Prasnikar
  • 27,212
  • 44
  • 82
  • 159
  • A favourite approach of mine, when I care a lot about my universe of `TThing` instances, is to have a `strict private class var FInstances: TList` which contains all living instances; hence, it is created in the `TThing` class ctor, freed in the `TThing` class dtor, and updated in the `TThing` instance ctor and instance dtor. But of course this is a `TThing`-only solution. – Andreas Rejbrand Dec 09 '22 at 21:44
  • BTW, I forgot to say that I bought your book and really liked it! :) – Andreas Rejbrand Dec 12 '22 at 09:22
  • @AndreasRejbrand Thanks! I think you mentioned that already ;) – Dalija Prasnikar Dec 12 '22 at 09:28