Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do some properties go out of scope in the watch list, while others do not?

First of all, sorry for the lengthy code example, but I believe it's needed to illustrate my problem.

As a debugging help I often introduce a "DebugString"-method on my objects, which returns a concise object summary. But sometimes my objects are too complex to be represented optimally in a single string, so I use stringlists. Now, I would like to use the excellent debug visualizers in Delphi to monitor my object. The way I do this is to introduce a property with a getter that rebuilds the stringlist.

This kinda works, but for every line I trace, the property gets out of scope, so I have to click the magnifying glass in the watch window again to see the value. Why is this?

To reproduce, create a new console application:

program Project1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes;

type
  TMyClass = class
  private
    FInternalData : array[0..4] of integer;
    FDebugStringList : TStringList;
    procedure RebuildDebugStringlist;
    function GetDebugStringList: TStringList;
    function GetDebugString : string;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Scramble;
    property DebugStringList : TStringList read GetDebugStringList;
    property DebugString : string read GetDebugString;
  end;

constructor TMyClass.Create;
begin
  FDebugStringList := TStringList.Create;
end;

destructor TMyClass.Destroy;
begin
  FDebugStringList.Free;
  inherited;
end;

function TMyClass.GetDebugString: string;
var
  I : integer;
begin
  Result := 'Object state: ';
  for I := 0 to 3 do
    Result := Result + inttostr(FInternalData[I])+' ';
end;

function TMyClass.GetDebugStringList: TStringList;
begin
  RebuildDebugStringlist;
  Result := FDebugStringlist;
end;

procedure TMyClass.RebuildDebugStringlist;
var
  I : integer;
begin
  FDebugStringList.Clear;

  FDebugStringList.Add('Object state:');
  for I := 0 to 4 do
    FDebugStringList.Add(inttostr(FInternalData[I]));
end;

procedure TMyClass.Scramble;
var
  I : integer;
begin
  for I := 0 to 4 do
    FInternalData[I] := Random(100);
end;

var
  vMyObj : TMyClass;

begin
  vMyObj := TMyClass.Create;
  try
    vMyObj.Scramble;
    vMyObj.Scramble;
    vMyObj.Scramble;
  finally
    vMyObj.Free;
  end;

  readln;
end.
  1. Add watches for "vMyObj.DebugStringList" and "vMyObj.DebugString"
  2. Place a breakpoint on line 77 (the 2nd "vMyObj.Scramble"), and run.
  3. Click the magnifying glass next to the "DebugStringList" watch to get the visualizer
  4. Observe that the visualizer works nicely :)
  5. Step over the next line. The visualizer now indicates the watch is out of scope.
  6. Press the magnifying glass again to see the new state of the object.

Why does the visualizer say that the watch is out of scope? How can I fix this?

PS: I know I can write debug visualizers, but I use "DebugString" and "DebugStringList" in some automatic tests, and I would really like to use them in this easy fashion.

Update: I use Delphi XE

Update 2: Despite a good effort by Marjan Venema, I still have no solution to this problem. I've filed a report with Embarcadero (QC number 98062, please vote :-)). However, I suspect it will take some time for Embarcadero fix this problem, and seeing how I'm still interested in a workaround, I'll offer a small bounty. Never tried that before, so it will be interesting to se what happens :-)

like image 857
Svein Bringsli Avatar asked Aug 23 '11 12:08

Svein Bringsli


1 Answers

It goes out of scope because that is exactly what happens when Scramble is executed. The bug maybe in that the visualizer does not get refreshed when it comes back into scope. Haven't had a look at the TStrings visualizer yet, but a work-around is to use an untyped pointer variable to the FDebugStringList and put a watch on a typecast of that TStringList to that derefenced pointer.

var
  vMyObj : TMyClass;
  vSL: Pointer;

{$OPTIMIZATION OFF}
begin
  vMyObj := TMyClass.Create;
  try
    vSL := @(vMyObj.FDebugStringList);

and the watch on:

TStringList(vSL^)

When you now break on the second scramble, open the visualizer for vSL, you'll see the contents of FDebugStringList. When you step over that second scramble, you can see the visualizer "thinking while scramble executes and then refreshing itself as you come back to the main level".

Pitfall: you do need to make sure the untyped pointer variable doesn't get optimized away. So either make some non-trivial use of it make sure optimization is off while debugging.

Edit: Unfortunately it seems the work around shows outdated values. See comment by Svein.

Update

Disclaimer: I am no ToolsAPI expert. A cursory examination of StringListVisualizer and the ToolsAPI units shows that RefreshVisualizer is "{ Called when the data for the visualizer needs to be refreshed }". Furthermore, the string "RefreshVisualizer" is only found in the interface declaration in the ToolsAPI unit and declared and implemented in the StringListVisualizer unit. So my guess at the moment is that the debugger should call RefreshVisualizer when coming back to stop on the third Scramble call, but doesn't. Worth a QC report in my opinion. At the very least it is a "User experience shortcoming".

like image 150
Marjan Venema Avatar answered Oct 04 '22 14:10

Marjan Venema