Test if a Pointer is a TObject instance

471 views Asked by At

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?

1

There are 1 answers

3
Dalija Prasnikar On

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.