microsoft / cppwinrt

C++/WinRT
MIT License
1.66k stars 239 forks source link

Hide protected and overridable members from public projections #1319

Closed sylveon closed 1 year ago

sylveon commented 1 year ago

According to the WinRT type system:

Protected interfaces

A composable class may declare zero or more of its member interfaces to be protected. A non-composable class may not declare member interfaces to be protected. Only code in a class that composes a composable class (directly or indirectly) may query for and use interfaces that the composable class declares as protected. Code from outside the composition chain may not query for, nor use, interfaces that the composable class declares as protected.

For example, if UIElement declares a protected interface IUIElementProtected, then only classes that compose UIElement—including both direct (Control) and indirect (Button) composition—may query for and use the IUIElementProtected interface.

Overridable interfaces

A composable class may declare zero or more of its member interfaces to be overridable. An overridable interface may only be queried for, and used within, a composition chain—similar to the rules about accessing protected interfaces detailed previously. However, where a protected interface may only be implemented by the class that originally declared it, overridable interfaces may be re-implemented by classes that compose the class that implemented the overridable interface.

Previously, cppwinrt allowed anyone to directly call protected/overridable members, for example this code works

StackPanel panel;
panel.ArrangeOverride({ }); // works, despite this being overridable
panel.ProtectedCursor(); // works, despite this being protected

This change makes it so that only implementation types which derive from the base have access to protected and overridable members.

This change is potentially breaking, but code that this change breaks should've never compiled to begin with (and indeed the equivalents in C++/CX as well as .NET Framework and .NET Core do not build).

In worse case, if access to protected and overridable members is desired (despite this being against the WinRT type system), one can do base.as<IBaseProtected>().ProtectedMember()

There's a potential gain to compile-time when Windows.UI.Xaml is involved, since a bunch of consume_t classes for overridable/protected members no longer get instantiated.

Fixes: #1317

github-actions[bot] commented 1 year ago

This pull request is stale because it has been open 10 days with no activity. Remove stale label or comment or this will be closed in 5 days.

sylveon commented 1 year ago

Bump

kennykerr commented 1 year ago

Unfortunately, a project maintainer is not currently available to review this pull request. Please see the contributing guide for more information. Feel free to keep the conversation going on the related issue.

jonwis commented 1 year ago

Per discussion thread, reopening

kennykerr commented 1 year ago

@jonwis - this change is going to be harder to review as it changes code gen which can't be directly reviewed just by looking at the PR even with good test coverage. I tend to take such PRs, clone the branch, and manually verify the code gen looks sound. Its tedious but the repercussions from bad code gen can be expensive if detected later on.

p.s. for windows-rs I opted to include code gen in the repo for test verification and diffing which helps to automate this.

sylveon commented 1 year ago

I thought I had forgot about protected constructors, but it turns out protected constructors are already hidden from code which doesn't compose. I added test coverage for it as I couldn't find any.

github-actions[bot] commented 1 year ago

This pull request is stale because it has been open 10 days with no activity. Remove stale label or comment or this will be closed in 5 days.

jonwis commented 1 year ago

I'll take this on Monday to compare the generated headers and some sample compiled code. I like the idea that we add some kind of "diff output" script that uses the current release version and the just-built version then your favorite directory-diffing tool to see what changed.

The source-breaking aspect has me concerned given how large the Windows OS codebase is and how widespread protected members are in WinUI3.

I know we don't like behavioral modification switches that change codegen, but this definitely is a place where it might be better to allow a "enable protected member access by namespace filter" control out.

sylveon commented 1 year ago

how widespread protected members are in WinUI3.

Most WinUI 3 code is in C#, where protected members are hidden. I'd expect the amount of C++ developers which are actively using protected members against guidance to be fairly low.

kennykerr commented 1 year ago

enable protected member access by namespace filter

There is no precedent for per-namespace alterations of this kind. I would suggest you start without an escape hatch and see how bad the breaks are in practice by performing an OS build. You may find that they are easily manageable. Once you have a good idea of what the practical implications are, add an escape hatch as needed and only if absolutely necessary. The approach C++/WinRT has used in the past is to use a preprocessor definition like WINRT_LEAN_AND_MEAN. This will be a lot simpler to implement and manage.

https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/macros

This is how I upgraded the OS from C++/WinRT 1.0 to 2.0. But again, you need to get the OS repo up to date first so that you can easily tell the delta of this change specifically. Otherwise, you will just be fighting a slew of random breaks.

sylveon commented 1 year ago

Yeah, and namespace filtering is non-trivial to implement.

jonwis commented 1 year ago

OK, https://github.com/sylveon/cppwinrt/pull/1 adds new differencing tools to compare the latest release and the under-development codegen. The changes here seem expected - mostly limited to XAML types, for instance. Will need to ingest into the OS carefully.

dmachaj commented 10 months ago

Unfortunately this does seem to be a breaking change, at least for some projects :(. I'm trying to build our code base with a private cppwinrt.exe that I built from the HEAD of master and I have some build breaks that seem to match this signature. (The project is currently on the most recent published release 2.0.230706.1 which narrowly missed this change).

sylveon commented 10 months ago

Are those breaking changes perhaps caused by said projects using protected/overridable members? :P

dmachaj commented 10 months ago

Probably. They are using something from Xaml::Controls::IControlProtected. It even has Protected in the name 🤷.

Unfortunately this usage is a pattern established in an (internal) project template so there are dozens of components that will have this same break to fix. At least this particular fix is not especially difficult.