Date: Mon, 11 Nov 2024 00:01:27 -0800
> On Nov 10, 2024, at 10:30 PM, Ville Voutilainen <ville.voutilainen_at_[hidden]> wrote:
>
> On Mon, 11 Nov 2024 at 08:09, Oliver Hunt via Ext <ext_at_[hidden]> wrote:
>> Entirely in agreement here - restricting access to private fields from being accessible via reflection based on source level annotations that exist to stop developers from misusing members and data that aren’t intended to be directly accessed outside of the class is good defensive practice. However limiting _reflection_ from examining those data is severely limiting to many basic uses of reflection, a canonical example is serialization, but there are numerous others.
>
> Serialization, hashing, and other use cases are attainable by defining
> an API for that use case and using generative programming techniques
> to produce the necessary opt-ins for such an API, without breaking
> through access controls.
What are the generative programming techniques you’re suggesting here?
I guess the assumption is that developers are going to require all their dependencies to implement support for whatever their traversal or introspection APIs are and that will turn around fast enough that it will be worth doing that rather than continuing to use libclang or whatever tooling they’re using.
Obviously their internal code can just be aggressive and whatever their use case is can use reflection to detect whether each type encountered has overridden the required APIs itself rather than just showing an inherited one.
>> In the context of P2719, having the ability to (transitively) introspect all fields of a type is useful for the purpose of an allocator wanting to reason about the properties of a type. Precluding access to the actual content of a type drastically limits the use of reflection for this purpose (and forces developers to rely on compiler extensions, manual annotations, manual adoption, and or external tools like libclang, etc to provide the necessary information).
>
> Perhaps we should have reflection queries for such properties, instead
> of relying on access-bypassing reflection of privates.
Maybe, but that requires knowing ahead of time what properties will matter to all possible allocators - for example you might need standard APIs to describe byte content, perform structural equivalence (for whatever equivalence the allocator cares about), padding information, exact pointer information (which may involve private types), etc. Knowing exactly what information will be needed requires knowing the environment, use case, and policies an allocator is attempting to provide, enforce, or guarantee. Allowing traversal of the type means not needing to know all of this ahead of time, and not needing to try to develop standard APIs for every possible allocator’s requirements.
For example, it is not unreasonable for an allocator to want to know whether a type being [de]allocated contains members of certain types, or instantiations of certain templates, and behave differently based on what it finds.
An alternative would be to just provide public members my default while providing a non-default path that exposes all fields and members for the use cases that need it. So discouraging blind access to non-public members, without requiring separate tooling that renders the protection irrelevant anyway, but also negates any reason for those projects to consider reflection at all (specifically: if reflection cannot, by design, do what they need today there is no reason to even toy with adopting it. If they’re able to use reflection in a non-default path, then they can adopt the reflection mechanisms, and then migrate to a better overall design over time)
—Oliver
>
> On Mon, 11 Nov 2024 at 08:09, Oliver Hunt via Ext <ext_at_[hidden]> wrote:
>> Entirely in agreement here - restricting access to private fields from being accessible via reflection based on source level annotations that exist to stop developers from misusing members and data that aren’t intended to be directly accessed outside of the class is good defensive practice. However limiting _reflection_ from examining those data is severely limiting to many basic uses of reflection, a canonical example is serialization, but there are numerous others.
>
> Serialization, hashing, and other use cases are attainable by defining
> an API for that use case and using generative programming techniques
> to produce the necessary opt-ins for such an API, without breaking
> through access controls.
What are the generative programming techniques you’re suggesting here?
I guess the assumption is that developers are going to require all their dependencies to implement support for whatever their traversal or introspection APIs are and that will turn around fast enough that it will be worth doing that rather than continuing to use libclang or whatever tooling they’re using.
Obviously their internal code can just be aggressive and whatever their use case is can use reflection to detect whether each type encountered has overridden the required APIs itself rather than just showing an inherited one.
>> In the context of P2719, having the ability to (transitively) introspect all fields of a type is useful for the purpose of an allocator wanting to reason about the properties of a type. Precluding access to the actual content of a type drastically limits the use of reflection for this purpose (and forces developers to rely on compiler extensions, manual annotations, manual adoption, and or external tools like libclang, etc to provide the necessary information).
>
> Perhaps we should have reflection queries for such properties, instead
> of relying on access-bypassing reflection of privates.
Maybe, but that requires knowing ahead of time what properties will matter to all possible allocators - for example you might need standard APIs to describe byte content, perform structural equivalence (for whatever equivalence the allocator cares about), padding information, exact pointer information (which may involve private types), etc. Knowing exactly what information will be needed requires knowing the environment, use case, and policies an allocator is attempting to provide, enforce, or guarantee. Allowing traversal of the type means not needing to know all of this ahead of time, and not needing to try to develop standard APIs for every possible allocator’s requirements.
For example, it is not unreasonable for an allocator to want to know whether a type being [de]allocated contains members of certain types, or instantiations of certain templates, and behave differently based on what it finds.
An alternative would be to just provide public members my default while providing a non-default path that exposes all fields and members for the use cases that need it. So discouraging blind access to non-public members, without requiring separate tooling that renders the protection irrelevant anyway, but also negates any reason for those projects to consider reflection at all (specifically: if reflection cannot, by design, do what they need today there is no reason to even toy with adopting it. If they’re able to use reflection in a non-default path, then they can adopt the reflection mechanisms, and then migrate to a better overall design over time)
—Oliver
Received on 2024-11-11 08:01:51