Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enforce private and protected access modifiers for class member access #4248

Merged
merged 3 commits into from
Sep 10, 2024

Conversation

brymer-meneses
Copy link
Contributor

@brymer-meneses brymer-meneses commented Aug 26, 2024

Print diagnostics for invalid class member access. This doesn't take into account compound member access.

@brymer-meneses
Copy link
Contributor Author

I'm also not sure whether it's best to split the test file into multiple files for this feature.

Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm focusing for the moment on the way name lookup is done in DiagnoseInvalidMemberAccess's get_member_info versus Context::LookupQualifiedName (called by LookupMemberNameInScope).

Hash table lookup is typically going to be a little more expensive, because it's indirect. Sometimes it's unavoidable, but had you considered either implementing as part of LookupQualifiedName, or making more direct use of its results in order to avoid extra lookup?

Note, Context::LookupQualifiedName currently can't have multiple extended scopes, but it may end up having ambiguities which are non-ambiguous when accounting for access control.

That said, I think already the separate implementations create a risk of divergent name lookup results. In particular, how about a test such as:

base class A {
  fn F();
}

base class B {
  extend base: A;
  private fn F() -> i32;
}

base class C {
  extend base: B;
  fn G[self: Self]() -> () { return self.F(); }
}

Here, F() should resolve to A::F and compile successfully. The difference in return types should help identify if DiagnoseInvalidMemberAccess returns success while LookupQualifiedName continues to return B::F.

@josh11b josh11b removed their request for review August 26, 2024 18:43
@brymer-meneses brymer-meneses marked this pull request as draft August 27, 2024 21:57
@brymer-meneses
Copy link
Contributor Author

had you considered either implementing as part of LookupQualifiedName, or making more direct use of its results in order to avoid extra lookup?

access_kind is not exposed through LookupQualifiedName so I had to expose most of the logic to another function. I'm not sure if this is the right approach because of the code duplication.

@brymer-meneses brymer-meneses changed the title Initial implementation of private/protected access modifiers for member accesses Initial implementation of private/protected access modifiers for member access Aug 29, 2024
@brymer-meneses brymer-meneses marked this pull request as ready for review August 29, 2024 05:54
@brymer-meneses
Copy link
Contributor Author

Here, F() should resolve to A::F and compile successfully. The difference in return types should help identify if DiagnoseInvalidMemberAccess returns success while LookupQualifiedName continues to return B::F.

So running through the tester, I get:

base class A {
  fn F();
}

base class B {
  extend base: A;
  private fn F() -> i32;
}

base class C {
  extend base: B;
  // CHECK:STDERR: lookup_private_access.carbon:[[@LINE+3]]:37: ERROR: Cannot access inherited private field `F` of type `C` inherited from `B`.
  // CHECK:STDERR:   fn G[self: Self]() -> () { return self.F(); }
  // CHECK:STDERR:                                     ^~~~~~
  fn G[self: Self]() -> () { return self.F(); }
}

I assume it has something to do with GetEntryInLookupScope which is mostly from LookupQualifiedName

@jonmeow
Copy link
Contributor

jonmeow commented Aug 30, 2024

To be sure, what I'm trying to say is that I think this kind of access control check probably belongs in LookupQualifiedName, not separate from it. The current GetEntryInLookupScope approach is problematic because it creates a separate implementation of name lookup, which carries two problems:

  • It risks divergence from the LookupQualifiedName implementation.
    • (this is what the example code is showing)
  • It will repeat substantial work, even on valid code.
    • (In theory, we're more okay with doing extra work for invalid code, e.g. to improve diagnostics -- but valid code should be efficient)

My suggestion, to offer a little detail would be:

  • Set aside the current work for a moment. It's useful for understanding the things which will come up, but may not make sense to use directly.
  • Add an argument to LookupQualifiedName to indicate whether it should be allowing protected/private access. When allowed:
    • On the first scope, LookupQualifiedName would allow access to private members.
    • On extended scopes, LookupQualifiedName would only allow access to protected members.
    • (when not allowed, only public is allowed)
  • LookupQualifiedName could track if it had seen inaccessible names, so that instead of DiagnoseNotFound it could instead diagnose the access restriction.

There's a bit more to flesh out there, but to be sure, member_access.cpp may only need a change to indicate to LookupQualifiedName which kind of access enforcement it requires. Rather than doing separate enforcement, it would be integrated into the existing lookup code.

Let me know if there's a little more I can clarify here, sorry it took some time to sort out my thoughts here.

@brymer-meneses brymer-meneses marked this pull request as draft August 31, 2024 05:10
@brymer-meneses brymer-meneses changed the title Initial implementation of private/protected access modifiers for member access Enforceprivate/protected access modifiers for member access Sep 1, 2024
@brymer-meneses brymer-meneses marked this pull request as ready for review September 1, 2024 09:42
@brymer-meneses
Copy link
Contributor Author

brymer-meneses commented Sep 1, 2024

I think I have implemented all your suggestions. What do you think about printing a diagnostic for self.base.x? Should this PR handle that too?

@brymer-meneses brymer-meneses changed the title Enforceprivate/protected access modifiers for member access Enforceprivate/protected access modifiers for class member access Sep 1, 2024
@brymer-meneses brymer-meneses changed the title Enforceprivate/protected access modifiers for class member access Enforce private/protected access modifiers for class member access Sep 1, 2024
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes! I have a lot of comments, but I want to be clear, I do think this is a big improvement. I just tend to add a lot of comments for style things (a chunk of these, for example, are just small style things in the test file that happen to stick out to me).

I want to highlight two that are more structural:

  • Moving the diagnostic call to where DiagnoseNameNotFound is found.
    • comment here
    • I think this will shift how you track skipped calls, and also may lead to more changes to DiagnoseInvalidAccess.
  • Avoiding querying whether the current function is an instance function before determining what to do with it.

I'd suggest starting with these two comments, because they may still shift your approach a little.

toolchain/check/context.h Outdated Show resolved Hide resolved
toolchain/check/context.h Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/diagnostics/diagnostic_kind.def Outdated Show resolved Hide resolved
toolchain/sem_ir/class.h Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/member_access.cpp Outdated Show resolved Hide resolved
toolchain/check/context.h Outdated Show resolved Hide resolved
toolchain/check/member_access.cpp Outdated Show resolved Hide resolved
toolchain/check/member_access.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NB, just a few comments since it looks like you're still addressing old comments, but I wanted to point out a couple small things.

toolchain/check/member_access.cpp Outdated Show resolved Hide resolved
toolchain/check/context.h Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/member_access.cpp Show resolved Hide resolved
@brymer-meneses brymer-meneses changed the title Enforce private/protected access modifiers for class member access Enforce private and protected access modifiers for class member access Sep 8, 2024
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Just a few last things.

Since this is almost ready to merge, can you also update against trunk? Looking at GitHub, there are a few files with merge conflicts.

toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/member_access.cpp Outdated Show resolved Hide resolved
toolchain/check/testdata/class/access_modifers.carbon Outdated Show resolved Hide resolved
toolchain/check/testdata/class/inheritance_access.carbon Outdated Show resolved Hide resolved
toolchain/check/testdata/class/inheritance_access.carbon Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
toolchain/check/context.cpp Outdated Show resolved Hide resolved
@brymer-meneses brymer-meneses force-pushed the access-modifiers branch 2 times, most recently from 2550479 to b994331 Compare September 10, 2024 14:00
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks basically ready to merge, but I see one string_view use got missed. Can you please fix the argument to ClassInvalidMemberAccess?

toolchain/check/context.cpp Outdated Show resolved Hide resolved
Copy link
Contributor

@jonmeow jonmeow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This is great. I'll kick off a merge. :)

@jonmeow jonmeow added this pull request to the merge queue Sep 10, 2024
Merged via the queue into carbon-language:trunk with commit 8ac9c80 Sep 10, 2024
8 checks passed
@brymer-meneses brymer-meneses deleted the access-modifiers branch September 10, 2024 16:52
github-merge-queue bot pushed a commit that referenced this pull request Sep 11, 2024
I just realized that
#4248 should now
correctly enforce compound member access. This change adds tests for
this functionality.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants