IDE, FpDebug: evaluate more sub-items for Inspect Dialog.

This commit is contained in:
Martin 2022-06-10 15:23:57 +02:00
parent f65d3b93ee
commit 0b051af509
4 changed files with 142 additions and 63 deletions

View File

@ -11,16 +11,25 @@ uses
type type
TDbgPtrList = specialize TFPGList<TDBGPtr>;
{ TFpWatchResultConvertor } { TFpWatchResultConvertor }
TFpWatchResultConvertor = class TFpWatchResultConvertor = class
private const private const
MAX_RECURSE_LVL = 32; MAX_RECURSE_LVL = 10;
NEST_PTR_RECURSE_LVL = 6; // must be less-or-equal than MAX_RECURSE_LVL MAX_RECURSE_LVL_ARRAY = 5;
MAX_RECURSE_LVL_PTR = 8; // max depth for a chain of pointers starting at the initial value
private private
FContext: TFpDbgLocationContext; FContext: TFpDbgLocationContext;
FRecurseCnt, FRecurseInstanceCnt: integer; FExtraDepth: Boolean;
FOuterArrayIdx: integer; FRecurseCnt, FRecurseCntLow,
FRecursePointerCnt,
FRecurseInstanceCnt, FRecurseDynArray: integer;
FRecurseAddrList: TDbgPtrList;
FLastValueKind: TDbgSymbolKind;
FHasEmbeddedPointer: Boolean;
FOuterArrayIdx, FTotalArrayCnt: integer;
FRepeatCount: Integer; FRepeatCount: Integer;
protected protected
function CheckError(AnFpValue: TFpValue; AnResData: TLzDbgWatchDataIntf): boolean; function CheckError(AnFpValue: TFpValue; AnResData: TLzDbgWatchDataIntf): boolean;
@ -49,8 +58,13 @@ type
function DoWriteWatchResultData(AnFpValue: TFpValue; function DoWriteWatchResultData(AnFpValue: TFpValue;
AnResData: TLzDbgWatchDataIntf AnResData: TLzDbgWatchDataIntf
): Boolean; ): Boolean;
function DoWritePointerWatchResultData(AnFpValue: TFpValue;
AnResData: TLzDbgWatchDataIntf;
AnAddr: TDbgPtr
): Boolean;
public public
constructor Create(AContext: TFpDbgLocationContext); constructor Create(AContext: TFpDbgLocationContext);
destructor Destroy; override;
function WriteWatchResultData(AnFpValue: TFpValue; function WriteWatchResultData(AnFpValue: TFpValue;
AnResData: TLzDbgWatchDataIntf; AnResData: TLzDbgWatchDataIntf;
@ -58,6 +72,7 @@ type
): Boolean; ): Boolean;
property Context: TFpDbgLocationContext read FContext write FContext; property Context: TFpDbgLocationContext read FContext write FContext;
property ExtraDepth: Boolean read FExtraDepth write FExtraDepth;
end; end;
@ -69,6 +84,9 @@ implementation
function TFpWatchResultConvertor.CheckError(AnFpValue: TFpValue; function TFpWatchResultConvertor.CheckError(AnFpValue: TFpValue;
AnResData: TLzDbgWatchDataIntf): boolean; AnResData: TLzDbgWatchDataIntf): boolean;
begin begin
Result := AnFpValue = nil;
if Result then
exit;
Result := IsError(AnFpValue.LastError); Result := IsError(AnFpValue.LastError);
if Result then begin if Result then begin
if AnResData <> nil then if AnResData <> nil then
@ -98,14 +116,19 @@ function TFpWatchResultConvertor.PointerToResData(AnFpValue: TFpValue;
var var
DerefRes: TLzDbgWatchDataIntf; DerefRes: TLzDbgWatchDataIntf;
DerefVal: TFpValue; DerefVal: TFpValue;
addr: QWord;
begin begin
Result := True; Result := True;
AnResData.CreatePointerValue(AnFpValue.AsCardinal); addr := AnFpValue.AsCardinal;
AnResData.CreatePointerValue(addr);
AddTypeNameToResData(AnFpValue, AnResData); AddTypeNameToResData(AnFpValue, AnResData);
if CheckError(AnFpValue, AnResData) then if CheckError(AnFpValue, AnResData) then
exit; exit;
if addr = 0 then
exit;
if svfString in AnFpValue.FieldFlags then begin if svfString in AnFpValue.FieldFlags then begin
// PChar: Get zero-terminated string, rather than just one single char // PChar: Get zero-terminated string, rather than just one single char
DerefRes := AnResData.SetDerefData; DerefRes := AnResData.SetDerefData;
@ -140,21 +163,18 @@ begin
- Pascal-String type - Pascal-String type
- Any basic type (any type that has no reference or internal pointer) - Any basic type (any type that has no reference or internal pointer)
(skChar should not happen: Should be PChar above) (skChar should not happen: Should be PChar above)
* NO pointer => pointer must check NEST_PTR_RECURSE_LVL - Any other
=> pointer is NOT ALLOWED to ignore MAX_RECURSE_LVL
* NO struct, array, variant... => May include pointers or internal refs
*) *)
DerefRes := AnResData.SetDerefData; DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin if DerefRes <> nil then begin
// In case of nested pointer MAX_RECURSE_LVL may already be reached. Make an exception here, to allow one more. // In case of nested pointer MAX_RECURSE_LVL may already be reached. Make an exception here, to allow one more.
dec(FRecurseCnt); dec(FRecurseCnt);
DoWriteWatchResultData(DerefVal, DerefRes); DoWritePointerWatchResultData(DerefVal, DerefRes, addr);
inc(FRecurseCnt); inc(FRecurseCnt);
end; end;
end end
else else
if (DerefVal.Kind =skPointer) and (svfString in DerefVal.FieldFlags) then begin if (DerefVal.Kind =skPointer) and (svfString in DerefVal.FieldFlags) then begin
// PPChar / Pointer to PChar: Ignore NEST_PTR_RECURSE_LVL
DerefRes := AnResData.SetDerefData; DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin if DerefRes <> nil then begin
DerefRes.CreateString(DerefVal.AsString); DerefRes.CreateString(DerefVal.AsString);
@ -163,22 +183,17 @@ begin
end end
else else
if (DerefVal.Kind =skPointer) and (svfWideString in DerefVal.FieldFlags) then begin if (DerefVal.Kind =skPointer) and (svfWideString in DerefVal.FieldFlags) then begin
// PPWideChar / Pointer to PWideChar: Ignore NEST_PTR_RECURSE_LVL
DerefRes := AnResData.SetDerefData; DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin if DerefRes <> nil then begin
DerefRes.CreateWideString(DerefVal.AsString); DerefRes.CreateWideString(DerefVal.AsString);
AddTypeNameToResData(DerefVal, DerefRes, True); AddTypeNameToResData(DerefVal, DerefRes, True);
end; end;
end end
else else begin
if (DerefVal.Kind =skPointer) and (FRecurseCnt <= NEST_PTR_RECURSE_LVL) then begin
// Nested Pointer
DerefRes := AnResData.SetDerefData; DerefRes := AnResData.SetDerefData;
if DerefRes <> nil then begin if DerefRes <> nil then
DoWriteWatchResultData(DerefVal, DerefRes); DoWritePointerWatchResultData(DerefVal, DerefRes, addr);
end;
end; end;
// Currently do NOT deref for struct, array, ...
CheckError(DerefVal, DerefRes); CheckError(DerefVal, DerefRes);
DerefVal.ReleaseReference; DerefVal.ReleaseReference;
@ -291,8 +306,11 @@ end;
function TFpWatchResultConvertor.ArrayToResData(AnFpValue: TFpValue; function TFpWatchResultConvertor.ArrayToResData(AnFpValue: TFpValue;
AnResData: TLzDbgWatchDataIntf): Boolean; AnResData: TLzDbgWatchDataIntf): Boolean;
const
MAX_TOTAL_ARRAY_CNT = 5000;
MAX_TOTAL_ARRAY_CNT_EXTRA_DEPTH = 3500; // reset
var var
Cnt, i, RecurseInst, OuterIdx: Integer; Cnt, i, CurRecurseDynArray, OuterIdx: Integer;
LowBnd: Int64; LowBnd: Int64;
Addr: TDBGPtr; Addr: TDBGPtr;
ti: TFpSymbol; ti: TFpSymbol;
@ -300,16 +318,15 @@ var
MemberValue: TFpValue; MemberValue: TFpValue;
begin begin
Result := True; Result := True;
if FRecurseCnt > NEST_PTR_RECURSE_LVL then
exit;
Cnt := AnFpValue.MemberCount; Cnt := AnFpValue.MemberCount;
RecurseInst := FRecurseInstanceCnt; CurRecurseDynArray := FRecurseDynArray;
OuterIdx := FOuterArrayIdx; OuterIdx := FOuterArrayIdx;
if (AnFpValue.IndexTypeCount = 0) or (not AnFpValue.IndexType[0].GetValueLowBound(AnFpValue, LowBnd)) then if (AnFpValue.IndexTypeCount = 0) or (not AnFpValue.IndexType[0].GetValueLowBound(AnFpValue, LowBnd)) then
LowBnd := 0; LowBnd := 0;
Addr := 0;
ti := AnFpValue.TypeInfo; ti := AnFpValue.TypeInfo;
if (ti = nil) or (ti.Flags * [sfDynArray, sfStatArray] = []) then begin if (ti = nil) or (ti.Flags * [sfDynArray, sfStatArray] = []) then begin
EntryRes := AnResData.CreateArrayValue(datUnknown, Cnt, LowBnd); EntryRes := AnResData.CreateArrayValue(datUnknown, Cnt, LowBnd);
@ -317,7 +334,6 @@ begin
else else
if (sfDynArray in ti.Flags) or (LowBnd = 0) then begin // LowBnd = 0 => there is some bug, reporting some dyn arrays as stat. if (sfDynArray in ti.Flags) or (LowBnd = 0) then begin // LowBnd = 0 => there is some bug, reporting some dyn arrays as stat.
EntryRes := AnResData.CreateArrayValue(datDynArray, Cnt, 0); EntryRes := AnResData.CreateArrayValue(datDynArray, Cnt, 0);
Addr := 0;
if AnFpValue.FieldFlags * [svfInteger, svfCardinal] <> [] then if AnFpValue.FieldFlags * [svfInteger, svfCardinal] <> [] then
Addr := AnFpValue.AsCardinal Addr := AnFpValue.AsCardinal
else else
@ -325,7 +341,8 @@ begin
Addr := AnFpValue.DataAddress.Address; Addr := AnFpValue.DataAddress.Address;
AnResData.SetDataAddress(Addr); AnResData.SetDataAddress(Addr);
inc(FRecurseInstanceCnt); if FRecurseCnt >= 0 then
inc(FRecurseDynArray);
end end
else begin else begin
EntryRes := AnResData.CreateArrayValue(datStatArray, Cnt, LowBnd); EntryRes := AnResData.CreateArrayValue(datStatArray, Cnt, LowBnd);
@ -334,25 +351,31 @@ begin
AddTypeNameToResData(AnFpValue, AnResData); AddTypeNameToResData(AnFpValue, AnResData);
try try
if Cnt <= 0 then if (Cnt <= 0) or
(FHasEmbeddedPointer) or
(FRecurseCnt > MAX_RECURSE_LVL_ARRAY) or
( (FRecurseCnt > 0) and (FTotalArrayCnt > MAX_TOTAL_ARRAY_CNT) )
then
exit; exit;
if (Context.MemManager.MemLimits.MaxArrayLen > 0) and (Cnt > Context.MemManager.MemLimits.MaxArrayLen) then if (Context.MemManager.MemLimits.MaxArrayLen > 0) and (Cnt > Context.MemManager.MemLimits.MaxArrayLen) then
Cnt := Context.MemManager.MemLimits.MaxArrayLen; Cnt := Context.MemManager.MemLimits.MaxArrayLen;
If (FOuterArrayIdx < 0) and (FRepeatCount > 0) then Cnt := FRepeatCount If (FOuterArrayIdx < 0) and (FRecurseCnt = FRecurseCntLow) and (FRepeatCount > 0) then Cnt := FRepeatCount
else if (FRecurseCnt > 1) and (FOuterArrayIdx < 5) and (Cnt > 5) then Cnt := 5 else if (FRecurseCnt > 1) and (FOuterArrayIdx > 10) and (Cnt > 10) then Cnt := 10
else if (FRecurseCnt > 1) and (Cnt > 3) then Cnt := 3 else if (FRecurseCnt > 1) and (FOuterArrayIdx > 1) and (Cnt > 20) then Cnt := 20
else if (FRecurseCnt > 0) and (FOuterArrayIdx < 25) and (Cnt > 50) then Cnt := 50 else if (FRecurseCnt > 0) and (FOuterArrayIdx > 100) and (Cnt > 10) then Cnt := 10
else if (FRecurseCnt > 0) and (FOuterArrayIdx < 100) and (Cnt > 10) then Cnt := 10 else if (FRecurseCnt > 0) and (FOuterArrayIdx > 1) and (Cnt > 50) then Cnt := 50
else if (FRecurseCnt > 0) and (Cnt > 5) then Cnt := 5
else if (Cnt > 1000) then Cnt := 1000; else if (Cnt > 1000) then Cnt := 1000;
///////////////////// /////////////////////
// add mem read cache ?? // add mem read cache ??
// Bound types // Bound types
inc(FTotalArrayCnt, Cnt);
for i := 0 to Cnt - 1 do begin for i := 0 to Cnt - 1 do begin
if (FRecurseCnt < 0) and (FTotalArrayCnt > MAX_TOTAL_ARRAY_CNT_EXTRA_DEPTH) then
FTotalArrayCnt := MAX_TOTAL_ARRAY_CNT_EXTRA_DEPTH;
if i > FOuterArrayIdx then if i > FOuterArrayIdx then
FOuterArrayIdx := i; FOuterArrayIdx := i;
MemberValue := AnFpValue.Member[i+LowBnd]; MemberValue := AnFpValue.Member[i+LowBnd];
@ -360,12 +383,12 @@ begin
if MemberValue = nil then if MemberValue = nil then
EntryRes.CreateError('Error: Could not get member') EntryRes.CreateError('Error: Could not get member')
else else
DoWriteWatchResultData(MemberValue, EntryRes); DoWritePointerWatchResultData(MemberValue, EntryRes, Addr);
MemberValue.ReleaseReference; MemberValue.ReleaseReference;
end; end;
finally finally
FRecurseInstanceCnt := RecurseInst; FRecurseDynArray := CurRecurseDynArray;
FOuterArrayIdx := OuterIdx; FOuterArrayIdx := OuterIdx;
end end
end; end;
@ -378,7 +401,7 @@ var
vt: TLzDbgStructType; vt: TLzDbgStructType;
Cache: TFpDbgMemCacheBase; Cache: TFpDbgMemCacheBase;
AnchestorMap: TAnchestorMap; AnchestorMap: TAnchestorMap;
i, j: Integer; i, j, WasRecurseInstanceCnt: Integer;
MemberValue: TFpValue; MemberValue: TFpValue;
ti, sym: TFpSymbol; ti, sym: TFpSymbol;
ResAnch, ResField, TopAnch, UnkAnch: TLzDbgWatchDataIntf; ResAnch, ResField, TopAnch, UnkAnch: TLzDbgWatchDataIntf;
@ -413,7 +436,13 @@ begin
AnResData.CreateStructure(vt, Addr); AnResData.CreateStructure(vt, Addr);
AddTypeNameToResData(AnFpValue, AnResData); AddTypeNameToResData(AnFpValue, AnResData);
if (AnFpValue.Kind in [skClass, skInterface]) and (Addr = 0) then if (AnFpValue.Kind in [skClass, skInterface]) and
( (Addr = 0) or
(FRecurseInstanceCnt >= 1) or (FRecurseDynArray >= 2)
)
then
exit;
if FHasEmbeddedPointer then
exit; exit;
if Context.MemManager.CacheManager <> nil then if Context.MemManager.CacheManager <> nil then
@ -422,15 +451,10 @@ begin
Cache := nil; Cache := nil;
AnchestorMap := TAnchestorMap.Create; AnchestorMap := TAnchestorMap.Create;
if (AnFpValue.Kind in [skClass, skInterface]) then WasRecurseInstanceCnt := FRecurseInstanceCnt;
if (AnFpValue.Kind in [skClass, skInterface]) and (FRecurseCnt >= 0) then
inc(FRecurseInstanceCnt); inc(FRecurseInstanceCnt);
try try
if (AnFpValue.Kind in [skClass, skObject]) and (FRecurseCnt > NEST_PTR_RECURSE_LVL) then
exit;
if (AnFpValue.Kind in [skClass, skInterface]) and (FRecurseInstanceCnt >= 2) then
exit;
TopAnch := AnResData; TopAnch := AnResData;
UnkAnch := nil; UnkAnch := nil;
ti := AnFpValue.TypeInfo; ti := AnFpValue.TypeInfo;
@ -496,14 +520,13 @@ begin
end; end;
ResField := ResAnch.AddField(MbName, MBVis, []); ResField := ResAnch.AddField(MbName, MBVis, []);
if not DoWriteWatchResultData(MemberValue, ResField) then if not DoWritePointerWatchResultData(MemberValue, ResField, Addr) then
ResField.CreateError('Unknown'); ResField.CreateError('Unknown');
MemberValue.ReleaseReference; MemberValue.ReleaseReference;
end; end;
finally finally
if (AnFpValue.Kind in [skClass, skInterface]) then FRecurseInstanceCnt := WasRecurseInstanceCnt;
dec(FRecurseInstanceCnt);
AnchestorMap.Free; AnchestorMap.Free;
if Cache <> nil then if Cache <> nil then
Context.MemManager.CacheManager.RemoveCache(Cache) Context.MemManager.CacheManager.RemoveCache(Cache)
@ -563,23 +586,34 @@ function TFpWatchResultConvertor.DoWriteWatchResultData(AnFpValue: TFpValue;
var var
PrettyPrinter: TFpPascalPrettyPrinter; PrettyPrinter: TFpPascalPrettyPrinter;
s: String; s: String;
DidHaveEmbeddedPointer: Boolean;
begin begin
// FRecurseCnt should be handled by the caller // FRecurseCnt should be handled by the caller
Result := FRecurseCnt > MAX_RECURSE_LVL; Result := (FRecurseCnt > MAX_RECURSE_LVL) or (AnFpValue = nil);
if Result then if Result then
exit; exit;
Result := True; Result := False;
if AnResData = nil then
exit;
if AnFpValue = nil then begin DidHaveEmbeddedPointer := FHasEmbeddedPointer;
AnResData.CreateError('No Data'); if (FRecurseCnt <= 0) and
exit; ( (FLastValueKind = skPointer) or (FRecurseCnt=-1) ) and
(AnFpValue.Kind = skPointer) and
(FRecursePointerCnt < MAX_RECURSE_LVL_PTR)
then begin
inc(FRecursePointerCnt);
end
else begin
inc(FRecurseCnt);
if (AnFpValue.Kind = skPointer) then
FHasEmbeddedPointer := True
else
if FHasEmbeddedPointer and (FLastValueKind <> skPointer) then
exit;
// Allow only one level, after an embedded pointer (pointer nested in other data-type)
end; end;
Result := False; FLastValueKind := AnFpValue.Kind;
inc(FRecurseCnt);
try try
case AnFpValue.Kind of case AnFpValue.Kind of
skPointer: Result := PointerToResData(AnFpValue, AnResData); skPointer: Result := PointerToResData(AnFpValue, AnResData);
@ -628,26 +662,67 @@ begin
Result := True; Result := True;
end; end;
finally finally
dec(FRecurseCnt); if FRecursePointerCnt > 0 then
dec(FRecursePointerCnt)
else
dec(FRecurseCnt);
FHasEmbeddedPointer := DidHaveEmbeddedPointer;
end; end;
end; end;
function TFpWatchResultConvertor.DoWritePointerWatchResultData(
AnFpValue: TFpValue; AnResData: TLzDbgWatchDataIntf; AnAddr: TDbgPtr
): Boolean;
begin
if FRecurseAddrList.IndexOf(AnAddr) >= 0 then
exit;
if AnAddr <> 0 then
FRecurseAddrList.Add(AnAddr);
DoWriteWatchResultData(AnFpValue, AnResData);
if AnAddr <> 0 then
FRecurseAddrList.Remove(AnAddr);
end;
constructor TFpWatchResultConvertor.Create(AContext: TFpDbgLocationContext); constructor TFpWatchResultConvertor.Create(AContext: TFpDbgLocationContext);
begin begin
inherited Create; inherited Create;
FRecurseAddrList := TDbgPtrList.Create;
FContext := AContext; FContext := AContext;
end; end;
destructor TFpWatchResultConvertor.Destroy;
begin
inherited Destroy;
FRecurseAddrList.Free;
end;
function TFpWatchResultConvertor.WriteWatchResultData(AnFpValue: TFpValue; function TFpWatchResultConvertor.WriteWatchResultData(AnFpValue: TFpValue;
AnResData: TLzDbgWatchDataIntf; ARepeatCount: Integer): Boolean; AnResData: TLzDbgWatchDataIntf; ARepeatCount: Integer): Boolean;
begin begin
Result := False;
if AnResData = nil then
exit;
if AnFpValue = nil then begin
AnResData.CreateError('No Data');
exit;
end;
if CheckError(AnFpValue, AnResData) then if CheckError(AnFpValue, AnResData) then
exit; exit;
FRecurseAddrList.Clear;
FRepeatCount := ARepeatCount; FRepeatCount := ARepeatCount;
FRecurseCnt := -1; FRecurseCnt := -1;
FRecurseInstanceCnt := 0; if FExtraDepth then
FRecurseCnt := -2;
FRecurseInstanceCnt := 0;
FRecurseDynArray := 0;
FRecursePointerCnt := 0;
FRecurseCntLow := FRecurseCnt+1;
FOuterArrayIdx := -1; FOuterArrayIdx := -1;
FTotalArrayCnt := 0;
FLastValueKind := AnFpValue.Kind;
FHasEmbeddedPointer := False;
Result := DoWriteWatchResultData(AnFpValue, AnResData); Result := DoWriteWatchResultData(AnFpValue, AnResData);
end; end;

View File

@ -1025,6 +1025,7 @@ begin
(ADispFormat <> wdfMemDump) // TODO (ADispFormat <> wdfMemDump) // TODO
then begin then begin
WatchResConv := TFpWatchResultConvertor.Create(FExpressionScope.LocationContext); WatchResConv := TFpWatchResultConvertor.Create(FExpressionScope.LocationContext);
WatchResConv.ExtraDepth := defExtraDepth in FWatchValue.EvaluateFlags;
ResData := FWatchValue.ResData; ResData := FWatchValue.ResData;
Result := WatchResConv.WriteWatchResultData(ResValue, ResData, FWatchValue.RepeatCount); Result := WatchResConv.WriteWatchResultData(ResValue, ResData, FWatchValue.RepeatCount);

View File

@ -82,11 +82,14 @@ type
); );
TWatcheEvaluateFlag = TWatcheEvaluateFlag =
(defNoTypeInfo, // No Typeinfo object will be returned // for structures that means a printed value will be returned ( defClassAutoCast, // Find real class of instance, and use, instead of declared class of variable
defSimpleTypeInfo, // Returns: Kind (skSimple, skClass, ..); TypeName (but does make no attempt to avoid an alias) defAllowFunctionCall, //
defFullTypeInfo, // Get all typeinfo, resolve all anchestors defExtraDepth, // Evaluate 1 extra level of sub-elements => i.e., evaluate each nested sub-item
defClassAutoCast, // Find real class of instance, and use, instead of declared class of variable
defAllowFunctionCall//, // deprecated
defNoTypeInfo, // No Typeinfo object will be returned // for structures that means a printed value will be returned
defSimpleTypeInfo, // Returns: Kind (skSimple, skClass, ..); TypeName (but does make no attempt to avoid an alias)
defFullTypeInfo // Get all typeinfo, resolve all anchestors
// defRawMemory, // returns Array of bytes for hex dump // defRawMemory, // returns Array of bytes for hex dump
// defNoValue // Skip the value, if returning raw mem // defNoValue // Skip the value, if returning raw mem
); );

View File

@ -1372,7 +1372,7 @@ begin
if stack <> nil then if stack <> nil then
idx := stack.CurrentIndex; idx := stack.CurrentIndex;
Opts := [defFullTypeInfo]; Opts := [defExtraDepth, defFullTypeInfo];
if btnUseInstance.Down then if btnUseInstance.Down then
include(Opts, defClassAutoCast); include(Opts, defClassAutoCast);