Debugger, feature request: prohibit ambiguous types in expression evaluators.
Currently using integer
/pinteger
datatypes in Evaluate/Watch windows leads to data loss very hard to detect: only two least sigificant bytes are displayed, while two most significant bytes are silently ignored. THe same would probably happen to Breakpoint Condition, memory dump go-to commands and other places the IDE Evaluator can be potentially used.
See discussion in #40171 (closed)
The root cause is that FPC has a number of different integer/pinteger types: one in barebone RTL (system unit) and another in add-on objpas unit. The debugger has no way to read user's mind and is left to pick one of the 'integer' datatypes at random (current implementation: race condition vs DWARF emitter). This is completely unexpected, and would hurt users more and more as Lazarus debugger matures and users learn to rely upon it.
More so, even if FPC RTL would ever remove this ambiguity - the same problem may happen in other situations.
Example 1: there is some buisioness domain library, that declares some type with a generic name like THandle
or TID
. And then there is one other library for some other domain, that also declares a type with the same generic name. As long as some project developed using both thos libraries - we get the same names collision.
Example 2: A project is developed in modern "Dependency inversion" fashion. Most (maybe even 100%) of the code and data structures are hidden private in implementation
section, and some shared crossbars/service locators are used to link them together. Again, because the datatypes would be unit-local there would be no point to invent descriptive type names, and they would probably coalesce on some very generic concepts like TCounter
or TRange
or something. But those types would not be private for DebugInfo, hence names collision again.
Quite confusing anf hard to recognize case would be copy-pasting a complex 'low-level' expression from the sources into Eval/Watch - just to have it silently miscalculated because Lazarus Debugger happenned to pick a different competing datatype for a typecase than one picked by the FPC compiler.
Proposal: As long as expression evaluator meets a not-qualified datatype (that is, a naked 'TypeName' instead of UnitName.TypeName
) reference it should check if the ambiguity arises.
Good example:
unit Graphics;
interface
...
type
TColor = TGraphicsColor;
TFontStyle = System.UITypes.TFontStyle;
unit GraphType;
interface
type
TGraphicsColor = System.UITypes.TColor
unit System.UITypes;
interface
Type
TColor = -$7FFFFFFF-1..$7FFFFFFF;
TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);
Here is no ambiguity what are TColor
or TFontStyle
. There is "one true declaration" for both types and other units only carry a type alias to it.
A bit different example would be:
Unit A;
interface
Type TName = ....
then
Unit B;
interface uses A;
Type TMyHandle = A.TName;
and
Unit C;
interface uses A;
Type TMyHandle = A.TName;
There is no data type TMyHandle
(only aliases) but still there is no ambiguity, bacause all the known aliases are reduced to the same type.
Bad example:
System.pas declares type integer = smallint; pinteger = ^smallint
; and ObjPas.pas carries
{$ifdef CPU16} ... {$else CPU16} Integer = longint; PInteger = ^longint;
The identifier here is used by two fundamentally different datatypes.
Or nested types within one unit:
Unit Unit1;
interface
implementation
type
TMyRec = record
private
type TCnt = word;
public
.....
end;
TMyCls = class
private
type TCnt = shortint;
public
.....
end;
What should the debugger do meating a breakpoint with a condition like TCnt(x)<0
?
The debugger's expressions engine should, by default at least, refuse to consume ambiguous datatypes and should require user to specialize them by adding unit name prefix.
it can dramatically slow down the watches window, as it needs for any non-local-unit symbol to an exhaustive search
Not quite. There is no need to do the search more than once. It can be a typical lazy-eval + cache scheme. Like properties inside VCL TPen,TBrush, etc.
The expression's ambiguity state is invalidated, if:
- types cache is invalidated
- expression text was changed
- maybe expression value was changed and now belongs to a different class type
The types/aliases cache is invalidated, if:
- a binary module (EXE, DLL, forecoming BPL, etc) was loaded, having debug-info
- a binary module, having debug-info, was unloaded
- a separate debug info data was loaded to a previously loaded binary module (at least Delphi has such a command in the Modules window, not sure about Lazarus)