[Dwarf-discuss] Proposal: Add support for "property" with getter/setter (based on Pascal properties)

Martin lists@mfriebe.de
Thu Jun 20 11:02:40 GMT 2024


Sorry for the late reply. I've been away.

 > - What does it mean for a property to be default? I couldn't find that
 > defined in the linked FreePascal documentation.

The property can be accessed without its name being specified.

This currently exists for (array-like) indexed properties.

`   property Foo [AnIndex: TSomeType]: TPropType read GetVal; default;`

If variable Bar is of a type having above property, then instead of
   `Bar.Foo[SomeVal]`
The code/expression can read
   `Bar[SomeVal]`

Bar as on object itself does not have an index. So by giving an index it 
is clear that the expression accesses the property.

Currently this is limited to array-like properties. There are some ideas 
to extend this, but nothing concrete.
The determination, if an expression accesses a "default property" does 
not belong to the DWARF specs, it has to be done by a language specific 
expression parser.

Also right now, only one indexed property can be marked default, but 
this may get extended. Multiply properties with different index-types 
could be each default.


 > - Why is there both a DW_AT_Default_Property and
 > a DW_TAG_Property_Default?

1) DW_AT_Default_Property
Above "default property".

2) DW_TAG_Property_Default
"default value" of a property.

property Foo: integer read FFoo default 1;
// In Pascal, there is no semicolon before "default" for this.

The latter can (in Pascal) be accessed via Run-Time-Type-Information. It 
is e.g. used in streaming of objects.
The property can be omitted from the stream, if it has its default 
value. Or when reading a stream, the default value will be assigned, if 
no value is present in the stream.
The compiler only generate the RTTI. Streaming can be implemented by the 
user, but the RTL provides functionality using the "default" as described.

On 2nd thought: I don't know if (or in which form) such info exists in 
other languages. And the amount/kind of such info available may be 
subject to change. I included it, as default-value and stored (below) in 
Pascal are commonly used. But there is room to discuss them as an 
extension (and possible more generic: some form of attributes that can 
be associated with any value/type)


 > - What is the purpose of DW_TAG_Property_Stored?
 > Maybe also add an example for this.

The DW_TAG_Property_Stored is used in a similar fashion as default. But 
provide just a boolean.

It is also accessible via RTTI. It can be a constant but often is a 
function.

property Foo: integer read FFoo stored GetFooStored;

If the properties value is only meaningful when the object is in a 
specific state, then GetFooStored could return true/false depending on 
that state.

E.g. if a RGBA color has a flag "Solid", then when it is solid, it needs 
no alpha channel. The object may not want the unused alpha value to 
appear in a stream. It could then implement a stored-function for the 
Alpha property.


 > - DW_TAG_Property_Argument_List would also benefit from an example.

The current use case would be several properties using the same getter.
Each property has an "index" (not the array-like index sorry another 
naming conflict, will call this one "property-index").

```
class TMyClass
  function GetProp(AIndex: Integer): integer;
   property MyProp1: integer index 1 read GetProp;
   property MyProp2: integer index 2 read GetProp
end;

class TMyOtherClass
  function GetOtherProp(a: boolean; AIndex: Integer): integer;
   property MyProp1[a:integer]: integer index 1 read GetOtherProp;
   property MyProp2[a:integer]: integer index 2 read GetOtherProp;
end;
```

The property-index is passed as one of the arguments to the function.

In general, this may also be useful for other languages, if they pass 
multiple arguments to a getter/setter, and the order of arguments needs 
to be clarified.

For the above, it avoids a (probably) Pascal specific 
`DW_AT_pascal_property_index_value`.

The proposal has some notes on this in the "function parameters" section.

Example, the default parameters (without the property-index) for
- GetProp is (_this)
- GetOtherProp are (_this, a)
And setters
- SetProp is (_this, NewValue)
- SetOtherProp are (_this, a, NewValue)

The position for the value of 1 or 2 (for the property-index) must be 
specified.

Alternatively, it could be hard-specified in the DWARF spec, as a 
language specific order...
But then, there would be a need for `DW_AT_pascal_property_index_value`.
And other languages may need their own extra values.

The idea is, that having a property, it can be translated into a 
function/method call as follows:
- 'Foo.MyProp' = `Foo.MyPropGetter()`
- global/non-class: 'MyProp' = `MyPropGetter()`
- 'Foo.MyProp[1,2]' = `Foo.MyPropGetter(1,2)`

Any arguments not specified by the user (and not _this) will be given as 
a list of: argument-position / argument-value


For the classes above

```
   DW_TAG_Structure_type
     DW_AT_Name :  "TMyClass"
L1:
     DW_TAG_subprogram
         DW_AT_Name            :  "GetProp"
         DW_AT_Type            :  <...>
       DW_TAG_formal_parameter
           DW_AT_Name            :  "_this"
       DW_TAG_formal_parameter
           DW_AT_Name            :  "AIndex"
     DW_TAG_Property
         DW_AT_Name             :  "MyProp1"
         DW_AT_Type             :  <...>
       DW_TAG_Property_Getter
           DW_AT_Property_Forward :  reference to L1
         DW_TAG_Property_Argument_List
             DW_AT_Property_Argument_Number : 1
                          ! DW_TAG_formal_parameter at pos 1 "AIndex"
             DW_AT_Property_Argument_Value : 1    ! the index value

```
For MyProp2, DW_AT_Property_Argument_Value will be 2.

For `GetOtherProp` the `DW_AT_Property_Argument_Number` would be position 2
```
   DW_TAG_Structure_type
     DW_AT_Name :  "TMyOtherClass"
L1:
     DW_TAG_subprogram
         DW_AT_Name            :  "GetProp"
         DW_AT_Type            :  <...>
       DW_TAG_formal_parameter
           DW_AT_Name            :  "_this"
       DW_TAG_formal_parameter
           DW_AT_Name            :  "a"
       DW_TAG_formal_parameter     ! argument number 2 (zero based)
           DW_AT_Name            :  "AIndex"
     DW_TAG_Property
         DW_AT_Name             :  "MyProp1"
         DW_AT_Type             :  <...>
       DW_TAG_Property_Getter
           DW_AT_Property_Forward :  reference to L1
         DW_TAG_Property_Argument_List
             DW_AT_Property_Argument_Number : 2
                          ! DW_TAG_formal_parameter at pos 2 "AIndex"
             DW_AT_Property_Argument_Value : 1    ! the index value

```


If a setter is called, then the new value is the last argument to the 
function. If any language has a different position it can specify
DW_AT_Property_Value_Argument_Number
Which is a direct child of DW_TAG_Property_Setter (not in 
DW_TAG_Property_Argument_List)

For the hypothetical / non-Pascal
   procedure SetOtherProp(NewVal: integer; a: boolean; AIndex: Integer);

```
       DW_TAG_Property_Setter
           DW_AT_Property_Forward :  reference
           DW_AT_Property_Value_Argument_Number : 1   ! pos of "NewVal"
         DW_TAG_Property_Argument_List
             DW_AT_Property_Argument_Number : 3   ! pos of "AIndex"
             DW_AT_Property_Argument_Value : 1    ! the index value
```

The _this argument is passed according to normal function calling rules.
If the _this DW_TAG_formal_parameter (or DW_AT_Property_Argument_Number) 
has been used by any DW_AT_Property_Argument_Number, then it needs to be 
decided
- don't pass it (assume it has been set already)
- pass it in the next available param?

The arguments for the array-like index will be passed left to right in 
any DW_TAG_formal_parameter that has not been used.
E.g. with DW_AT_Property_Value_Argument_Number=1
DW_TAG_formal_parameter(0): _this
DW_TAG_formal_parameter(1): new value
DW_TAG_formal_parameter(2): array-like "a" (leftmost unused param)
DW_TAG_formal_parameter(3): property-index "AIndex"



 > - If I understand correctly, the purpose of DW_AT_Property_Forward
 > is to reference a getter or setter DW_TAG_subprogram.
 >  Is there a specific reason you decided for this representation
 >  over nesting a DW_TAG_subprogram with a DW_AT_property_getter (true) 
inside the DW_TAG_property?

Actually it can refer any other (non-property) member of an object. 
(function, member, or variable (static member).
Or for properties outside a class, it can refer a function or variable.

The proposal allows a choice of either
- DW_AT_Property_Forward
- DW_TAG_Member, DW_TAG_Variable, DW_TAG_subprogram

So, it is possible to have anonymous accessors/storage.

The forward would simple allow the debugger to identify the named member 
that provides the value, if any debugger wants to maybe show that.

IMHO, if the field/accessor is on another object then the forward seems 
more natural. But that is my personal preference.
If there will be functions on nested objects, the directly specifying 
DW_TAG_subprogram may be misleading with regards to the correct _this 
parameter (though it will work, as the param can be given by 
DW_AT_Property_Object)



 > - Why isn't the accessibility attribute
 > on the linked DW_TAG_subprogram?

They can have different visibility.

```
TMyClass = class
private
   function Getbar: boolean;
public
   property bar: boolean read Getbar;
end;
```

// or even
```
TMyClass = class
private
   function Getbar: boolean;
protected
   property bar: boolean read Getbar;
end;

TMySubClass = class(TMyClass)
public
   property bar; // only change visibility
   procedure GetBar: boolean; reintroduce;
end;
```

In TMySubClass the property "bar" still calls the inherited GetBar from 
the parent.
The GetBar isn't virtual, and so the new GetBar hides the parent GetBar 
for any code calling GetBar directly (without the property)


 > - Why is DW_AT_property_object needed?

//Example
TPoint = record x,y: integer; end;
TLine = class
private
   FStart,FStop: TPoint;
public
   property StartX: integer read FStart.X;
end;

In the DWARF description we would need to know if "X" is in FStart or FStop.

The DW_AT_Property_Forward would refer to the field "x" in the 
DW_TAG_Structure for TPoint. So it would not indicate if that is FStart 
or FStop. (since both will refer the type "TPoint")


At the moment Pascal only supports such redirects for "records" (i.e. 
structures without reference, that are inlined into the parents memory).
For such structures the structure could be found from the memory location.

But for a generic approach the DW_AT_Property_Forward   would point to 
"X" in "TPoint", and "X" would have a DW_AT_member_location. So the 
address of FStart would be needed.

Of course if DW_AT_Location pointed to the address of FStart.X then that 
provides the final location.

Currently - in Pascal - such redirects only work for fields. In future 
this may support functions in nested object (Though there are no current 
plans). A function would need a self parameter, therefore need to know 
the address of the enclosing object.

Of course the address of the object can be a DW_AT_location.
Its a matter of understanding the purpose of that location (it is not 
the value itself).
And also, while I can't think of any other location that could in future 
be referred from a DW_TAG_Property_[GS]etter, there may be a chance for 
that to happen.





On 08/06/2024 01:12, Adrian Prantl wrote:
> Hello Martin,
> 
> Thanks for submitting your proposal! In order to prepare it for discussion I would suggest to first try pick a first choice for all the design options in the proposal and then once the direction is clear, distill the normative text changes that would need to be made in the DWARF specification.
> 
> Adding a new TAG called DW_TAG_property seems like a good approach. There is nothing that's overly specific about Pascal in the proposal, so using the generic name seems like a good choice.
> 
> I had a couple of initial questions that came up while reading through the proposal:
> 
> - What does it mean for a property to be default? I couldn't find that defined in the linked FreePascal documentation.
> - Why is there both a DW_AT_Default_Property and a DW_TAG_Property_Default?
> - What is the purpose of DW_TAG_Property_Stored? Maybe also add an example for this.
> - DW_TAG_Property_Argument_List would also benefit from an example.
> 
> - If I understand correctly, the purpose of DW_AT_Property_Forward is to reference a getter or setter DW_TAG_subprogram.
>    Is there a specific reason you decided for this representation over nesting a DW_TAG_subprogram with a DW_AT_property_getter (true) inside the DW_TAG_property?
> 
> - Why isn't the accessibility attribute on the linked DW_TAG_subprogram?
> 
> After having read the last example, I think I can answer my question from above: Pascal allows to specify an access path to a (nested) field as the read accessor.
> 
> - Why is DW_AT_property_object needed? Couldn't the debugger determine the location from following the DW_AT_Property_Forward link? Related: Does this need to be a new attribute, or could this be DW_AT_location?
> 


More information about the Dwarf-discuss mailing list