Correct the calculation of access to more closely model the wording in
the standard. llvm-svn: 100155
This commit is contained in:
parent
cbcb692133
commit
d79b4d8173
|
@ -370,10 +370,6 @@ static Sema::AccessResult MatchesFriend(Sema &S,
|
|||
static Sema::AccessResult GetFriendKind(Sema &S,
|
||||
const EffectiveContext &EC,
|
||||
const CXXRecordDecl *Class) {
|
||||
// A class always has access to its own members.
|
||||
if (EC.includesClass(Class))
|
||||
return Sema::AR_accessible;
|
||||
|
||||
Sema::AccessResult OnFailure = Sema::AR_inaccessible;
|
||||
|
||||
// Okay, check friends.
|
||||
|
@ -401,9 +397,88 @@ static Sema::AccessResult GetFriendKind(Sema &S,
|
|||
return OnFailure;
|
||||
}
|
||||
|
||||
static Sema::AccessResult HasAccess(Sema &S,
|
||||
const EffectiveContext &EC,
|
||||
const CXXRecordDecl *NamingClass,
|
||||
AccessSpecifier Access) {
|
||||
assert(NamingClass->getCanonicalDecl() == NamingClass &&
|
||||
"declaration should be canonicalized before being passed here");
|
||||
|
||||
if (Access == AS_public) return Sema::AR_accessible;
|
||||
assert(Access == AS_private || Access == AS_protected);
|
||||
|
||||
for (EffectiveContext::record_iterator
|
||||
I = EC.Records.begin(), E = EC.Records.end(); I != E; ++I) {
|
||||
// All the declarations in EC have been canonicalized, so pointer
|
||||
// equality from this point on will work fine.
|
||||
const CXXRecordDecl *ECRecord = *I;
|
||||
|
||||
// [B2] and [M2]
|
||||
if (ECRecord == NamingClass)
|
||||
return Sema::AR_accessible;
|
||||
|
||||
// [B3] and [M3]
|
||||
if (Access == AS_protected &&
|
||||
ECRecord->isDerivedFrom(const_cast<CXXRecordDecl*>(NamingClass)))
|
||||
return Sema::AR_accessible;
|
||||
}
|
||||
|
||||
return GetFriendKind(S, EC, NamingClass);
|
||||
}
|
||||
|
||||
/// Finds the best path from the naming class to the declaring class,
|
||||
/// taking friend declarations into account.
|
||||
///
|
||||
/// C++0x [class.access.base]p5:
|
||||
/// A member m is accessible at the point R when named in class N if
|
||||
/// [M1] m as a member of N is public, or
|
||||
/// [M2] m as a member of N is private, and R occurs in a member or
|
||||
/// friend of class N, or
|
||||
/// [M3] m as a member of N is protected, and R occurs in a member or
|
||||
/// friend of class N, or in a member or friend of a class P
|
||||
/// derived from N, where m as a member of P is public, private,
|
||||
/// or protected, or
|
||||
/// [M4] there exists a base class B of N that is accessible at R, and
|
||||
/// m is accessible at R when named in class B.
|
||||
///
|
||||
/// C++0x [class.access.base]p4:
|
||||
/// A base class B of N is accessible at R, if
|
||||
/// [B1] an invented public member of B would be a public member of N, or
|
||||
/// [B2] R occurs in a member or friend of class N, and an invented public
|
||||
/// member of B would be a private or protected member of N, or
|
||||
/// [B3] R occurs in a member or friend of a class P derived from N, and an
|
||||
/// invented public member of B would be a private or protected member
|
||||
/// of P, or
|
||||
/// [B4] there exists a class S such that B is a base class of S accessible
|
||||
/// at R and S is a base class of N accessible at R.
|
||||
///
|
||||
/// Along a single inheritance path we can restate both of these
|
||||
/// iteratively:
|
||||
///
|
||||
/// First, we note that M1-4 are equivalent to B1-4 if the member is
|
||||
/// treated as a notional base of its declaring class with inheritance
|
||||
/// access equivalent to the member's access. Therefore we need only
|
||||
/// ask whether a class B is accessible from a class N in context R.
|
||||
///
|
||||
/// Let B_1 .. B_n be the inheritance path in question (i.e. where
|
||||
/// B_1 = N, B_n = B, and for all i, B_{i+1} is a direct base class of
|
||||
/// B_i). For i in 1..n, we will calculate ACAB(i), the access to the
|
||||
/// closest accessible base in the path:
|
||||
/// Access(a, b) = (* access on the base specifier from a to b *)
|
||||
/// Merge(a, forbidden) = forbidden
|
||||
/// Merge(a, private) = forbidden
|
||||
/// Merge(a, b) = min(a,b)
|
||||
/// Accessible(c, forbidden) = false
|
||||
/// Accessible(c, private) = (R is c) || IsFriend(c, R)
|
||||
/// Accessible(c, protected) = (R derived from c) || IsFriend(c, R)
|
||||
/// Accessible(c, public) = true
|
||||
/// ACAB(n) = public
|
||||
/// ACAB(i) =
|
||||
/// let AccessToBase = Merge(Access(B_i, B_{i+1}), ACAB(i+1)) in
|
||||
/// if Accessible(B_i, AccessToBase) then public else AccessToBase
|
||||
///
|
||||
/// B is an accessible base of N at R iff ACAB(1) = public.
|
||||
///
|
||||
/// \param FinalAccess the access of the "final step", or AS_none if
|
||||
/// there is no final step.
|
||||
/// \return null if friendship is dependent
|
||||
|
@ -445,14 +520,10 @@ static CXXBasePath *FindBestPath(Sema &S,
|
|||
}
|
||||
|
||||
AccessSpecifier BaseAccess = I->Base->getAccessSpecifier();
|
||||
if (BaseAccess != AS_public || PathAccess != AS_public) {
|
||||
switch (GetFriendKind(S, EC, I->Class)) {
|
||||
case Sema::AR_inaccessible:
|
||||
PathAccess = CXXRecordDecl::MergeAccess(BaseAccess, PathAccess);
|
||||
break;
|
||||
case Sema::AR_accessible:
|
||||
PathAccess = AS_public;
|
||||
break;
|
||||
PathAccess = std::max(PathAccess, BaseAccess);
|
||||
switch (HasAccess(S, EC, I->Class, PathAccess)) {
|
||||
case Sema::AR_inaccessible: break;
|
||||
case Sema::AR_accessible: PathAccess = AS_public; break;
|
||||
case Sema::AR_dependent:
|
||||
AnyDependent = true;
|
||||
goto Next;
|
||||
|
@ -460,7 +531,6 @@ static CXXBasePath *FindBestPath(Sema &S,
|
|||
llvm_unreachable("friend resolution is never delayed"); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we modify the path's Access field to the
|
||||
// friend-modified access.
|
||||
|
@ -491,16 +561,27 @@ static CXXBasePath *FindBestPath(Sema &S,
|
|||
/// to become inaccessible.
|
||||
static void DiagnoseAccessPath(Sema &S,
|
||||
const EffectiveContext &EC,
|
||||
CXXRecordDecl *NamingClass,
|
||||
CXXRecordDecl *DeclaringClass,
|
||||
NamedDecl *D, AccessSpecifier Access) {
|
||||
const Sema::AccessedEntity &Entity) {
|
||||
AccessSpecifier Access = Entity.getAccess();
|
||||
CXXRecordDecl *NamingClass = Entity.getNamingClass();
|
||||
NamingClass = NamingClass->getCanonicalDecl();
|
||||
|
||||
NamedDecl *D;
|
||||
CXXRecordDecl *DeclaringClass;
|
||||
if (Entity.isMemberAccess()) {
|
||||
D = Entity.getTargetDecl();
|
||||
DeclaringClass = FindDeclaringClass(D);
|
||||
} else {
|
||||
D = 0;
|
||||
DeclaringClass = Entity.getBaseClass();
|
||||
}
|
||||
DeclaringClass = DeclaringClass->getCanonicalDecl();
|
||||
|
||||
// Easy case: the decl's natural access determined its path access.
|
||||
// We have to check against AS_private here in case Access is AS_none,
|
||||
// indicating a non-public member of a private base class.
|
||||
//
|
||||
// DependentFriend should be impossible here.
|
||||
if (D && (Access == D->getAccess() || D->getAccess() == AS_private)) {
|
||||
switch (GetFriendKind(S, EC, DeclaringClass)) {
|
||||
switch (HasAccess(S, EC, DeclaringClass, D->getAccess())) {
|
||||
case Sema::AR_inaccessible: {
|
||||
S.Diag(D->getLocation(), diag::note_access_natural)
|
||||
<< (unsigned) (Access == AS_protected)
|
||||
|
@ -566,101 +647,113 @@ static void DiagnoseAccessPath(Sema &S,
|
|||
llvm_unreachable("access not apparently constrained by path");
|
||||
}
|
||||
|
||||
/// Diagnose an inaccessible class member.
|
||||
static void DiagnoseInaccessibleMember(Sema &S, SourceLocation Loc,
|
||||
const EffectiveContext &EC,
|
||||
CXXRecordDecl *NamingClass,
|
||||
AccessSpecifier Access,
|
||||
const Sema::AccessedEntity &Entity) {
|
||||
NamedDecl *D = Entity.getTargetDecl();
|
||||
CXXRecordDecl *DeclaringClass = FindDeclaringClass(D);
|
||||
|
||||
S.Diag(Loc, Entity.getDiag())
|
||||
<< (Access == AS_protected)
|
||||
<< D->getDeclName()
|
||||
<< S.Context.getTypeDeclType(NamingClass)
|
||||
<< S.Context.getTypeDeclType(DeclaringClass);
|
||||
DiagnoseAccessPath(S, EC, NamingClass, DeclaringClass, D, Access);
|
||||
}
|
||||
|
||||
/// Diagnose an inaccessible hierarchy conversion.
|
||||
static void DiagnoseInaccessibleBase(Sema &S, SourceLocation Loc,
|
||||
const EffectiveContext &EC,
|
||||
AccessSpecifier Access,
|
||||
const Sema::AccessedEntity &Entity) {
|
||||
S.Diag(Loc, Entity.getDiag())
|
||||
<< (Access == AS_protected)
|
||||
<< DeclarationName()
|
||||
<< S.Context.getTypeDeclType(Entity.getDerivedClass())
|
||||
<< S.Context.getTypeDeclType(Entity.getBaseClass());
|
||||
DiagnoseAccessPath(S, EC, Entity.getDerivedClass(),
|
||||
Entity.getBaseClass(), 0, Access);
|
||||
}
|
||||
|
||||
static void DiagnoseBadAccess(Sema &S, SourceLocation Loc,
|
||||
const EffectiveContext &EC,
|
||||
CXXRecordDecl *NamingClass,
|
||||
AccessSpecifier Access,
|
||||
const Sema::AccessedEntity &Entity) {
|
||||
if (Entity.isMemberAccess())
|
||||
DiagnoseInaccessibleMember(S, Loc, EC, NamingClass, Access, Entity);
|
||||
else
|
||||
DiagnoseInaccessibleBase(S, Loc, EC, Access, Entity);
|
||||
const CXXRecordDecl *NamingClass = Entity.getNamingClass();
|
||||
NamedDecl *D;
|
||||
const CXXRecordDecl *DeclaringClass;
|
||||
if (Entity.isMemberAccess()) {
|
||||
D = Entity.getTargetDecl();
|
||||
DeclaringClass = FindDeclaringClass(D);
|
||||
} else {
|
||||
D = 0;
|
||||
DeclaringClass = Entity.getBaseClass();
|
||||
}
|
||||
|
||||
S.Diag(Loc, Entity.getDiag())
|
||||
<< (Entity.getAccess() == AS_protected)
|
||||
<< (D ? D->getDeclName() : DeclarationName())
|
||||
<< S.Context.getTypeDeclType(NamingClass)
|
||||
<< S.Context.getTypeDeclType(DeclaringClass);
|
||||
DiagnoseAccessPath(S, EC, Entity);
|
||||
}
|
||||
|
||||
|
||||
/// Try to elevate access using friend declarations. This is
|
||||
/// potentially quite expensive.
|
||||
///
|
||||
/// \return true if elevation was dependent
|
||||
static bool TryElevateAccess(Sema &S,
|
||||
/// Determines whether the accessed entity is accessible. Public members
|
||||
/// have been weeded out by this point.
|
||||
static Sema::AccessResult IsAccessible(Sema &S,
|
||||
const EffectiveContext &EC,
|
||||
const Sema::AccessedEntity &Entity,
|
||||
AccessSpecifier &Access) {
|
||||
const Sema::AccessedEntity &Entity) {
|
||||
// Determine the actual naming class.
|
||||
CXXRecordDecl *NamingClass = Entity.getNamingClass();
|
||||
while (NamingClass->isAnonymousStructOrUnion())
|
||||
NamingClass = cast<CXXRecordDecl>(NamingClass->getParent());
|
||||
NamingClass = NamingClass->getCanonicalDecl();
|
||||
|
||||
AccessSpecifier UnprivilegedAccess = Entity.getAccess();
|
||||
assert(UnprivilegedAccess != AS_public && "public access not weeded out");
|
||||
|
||||
// Before we try to recalculate access paths, try to white-list
|
||||
// accesses which just trade in on the final step, i.e. accesses
|
||||
// which don't require [M4] or [B4]. These are by far the most
|
||||
// common forms of access.
|
||||
if (UnprivilegedAccess != AS_none) {
|
||||
switch (HasAccess(S, EC, NamingClass, UnprivilegedAccess)) {
|
||||
case Sema::AR_dependent:
|
||||
// This is actually an interesting policy decision. We don't
|
||||
// *have* to delay immediately here: we can do the full access
|
||||
// calculation in the hope that friendship on some intermediate
|
||||
// class will make the declaration accessible non-dependently.
|
||||
// But that's not cheap, and odds are very good (note: assertion
|
||||
// made without data) that the friend declaration will determine
|
||||
// access.
|
||||
return Sema::AR_dependent;
|
||||
|
||||
case Sema::AR_accessible: return Sema::AR_accessible;
|
||||
case Sema::AR_inaccessible: break;
|
||||
case Sema::AR_delayed:
|
||||
llvm_unreachable("friendship never subject to contextual delay");
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the declaring class.
|
||||
CXXRecordDecl *DeclaringClass;
|
||||
if (Entity.isMemberAccess()) {
|
||||
DeclaringClass = FindDeclaringClass(Entity.getTargetDecl());
|
||||
} else {
|
||||
DeclaringClass = Entity.getBaseClass();
|
||||
}
|
||||
CXXRecordDecl *NamingClass = Entity.getNamingClass();
|
||||
DeclaringClass = DeclaringClass->getCanonicalDecl();
|
||||
|
||||
// We lower member accesses to base accesses by pretending that the
|
||||
// member is a base class of its declaring class.
|
||||
AccessSpecifier FinalAccess;
|
||||
|
||||
// Adjust the declaration of the referred entity.
|
||||
AccessSpecifier DeclAccess = AS_public;
|
||||
if (Entity.isMemberAccess()) {
|
||||
// Determine if the declaration is accessible from EC when named
|
||||
// in its declaring class.
|
||||
NamedDecl *Target = Entity.getTargetDecl();
|
||||
|
||||
DeclAccess = Target->getAccess();
|
||||
if (DeclAccess != AS_public) {
|
||||
switch (GetFriendKind(S, EC, DeclaringClass)) {
|
||||
case Sema::AR_accessible: DeclAccess = AS_public; break;
|
||||
FinalAccess = Target->getAccess();
|
||||
switch (HasAccess(S, EC, DeclaringClass, FinalAccess)) {
|
||||
case Sema::AR_accessible: FinalAccess = AS_public; break;
|
||||
case Sema::AR_inaccessible: break;
|
||||
case Sema::AR_dependent: return true;
|
||||
case Sema::AR_dependent: return Sema::AR_dependent; // see above
|
||||
case Sema::AR_delayed: llvm_unreachable("friend status is never delayed");
|
||||
}
|
||||
}
|
||||
|
||||
if (DeclaringClass == NamingClass) {
|
||||
Access = DeclAccess;
|
||||
return false;
|
||||
}
|
||||
if (DeclaringClass == NamingClass)
|
||||
return (FinalAccess == AS_public
|
||||
? Sema::AR_accessible
|
||||
: Sema::AR_inaccessible);
|
||||
} else {
|
||||
FinalAccess = AS_public;
|
||||
}
|
||||
|
||||
assert(DeclaringClass != NamingClass);
|
||||
|
||||
// Append the declaration's access if applicable.
|
||||
CXXBasePaths Paths;
|
||||
CXXBasePath *Path = FindBestPath(S, EC, Entity.getNamingClass(),
|
||||
DeclaringClass, DeclAccess, Paths);
|
||||
CXXBasePath *Path = FindBestPath(S, EC, NamingClass, DeclaringClass,
|
||||
FinalAccess, Paths);
|
||||
if (!Path)
|
||||
return true;
|
||||
return Sema::AR_dependent;
|
||||
|
||||
// Grab the access along the best path (note that this includes the
|
||||
// final-step access).
|
||||
AccessSpecifier NewAccess = Path->Access;
|
||||
assert(NewAccess <= Access && "access along best path worse than direct?");
|
||||
Access = NewAccess;
|
||||
return false;
|
||||
assert(Path->Access <= UnprivilegedAccess &&
|
||||
"access along best path worse than direct?");
|
||||
if (Path->Access == AS_public)
|
||||
return Sema::AR_accessible;
|
||||
return Sema::AR_inaccessible;
|
||||
}
|
||||
|
||||
static void DelayAccess(Sema &S,
|
||||
|
@ -683,47 +776,37 @@ static void DelayAccess(Sema &S,
|
|||
static Sema::AccessResult CheckEffectiveAccess(Sema &S,
|
||||
const EffectiveContext &EC,
|
||||
SourceLocation Loc,
|
||||
Sema::AccessedEntity const &Entity) {
|
||||
AccessSpecifier Access = Entity.getAccess();
|
||||
assert(Access != AS_public && "called for public access!");
|
||||
const Sema::AccessedEntity &Entity) {
|
||||
assert(Entity.getAccess() != AS_public && "called for public access!");
|
||||
|
||||
// Find a non-anonymous naming class. For records with access,
|
||||
// there should always be one of these.
|
||||
CXXRecordDecl *NamingClass = Entity.getNamingClass();
|
||||
while (NamingClass->isAnonymousStructOrUnion())
|
||||
NamingClass = cast<CXXRecordDecl>(NamingClass->getParent());
|
||||
|
||||
// White-list accesses from classes with privileges equivalent to the
|
||||
// naming class --- but only if the access path isn't forbidden
|
||||
// (i.e. an access of a private member from a subclass).
|
||||
if (Access != AS_none && EC.includesClass(NamingClass))
|
||||
return Sema::AR_accessible;
|
||||
|
||||
// Try to elevate access.
|
||||
// TODO: on some code, it might be better to do the protected check
|
||||
// without trying to elevate first.
|
||||
if (TryElevateAccess(S, EC, Entity, Access)) {
|
||||
switch (IsAccessible(S, EC, Entity)) {
|
||||
case Sema::AR_dependent:
|
||||
DelayAccess(S, EC, Loc, Entity);
|
||||
return Sema::AR_dependent;
|
||||
}
|
||||
|
||||
if (Access == AS_public) return Sema::AR_accessible;
|
||||
case Sema::AR_delayed:
|
||||
llvm_unreachable("IsAccessible cannot contextually delay");
|
||||
|
||||
// Protected access.
|
||||
if (Access == AS_protected) {
|
||||
// FIXME: implement [class.protected]p1
|
||||
for (llvm::SmallVectorImpl<CXXRecordDecl*>::const_iterator
|
||||
I = EC.Records.begin(), E = EC.Records.end(); I != E; ++I)
|
||||
if ((*I)->isDerivedFrom(NamingClass))
|
||||
return Sema::AR_accessible;
|
||||
|
||||
// FIXME: delay if we can't decide class derivation yet.
|
||||
}
|
||||
|
||||
// Okay, that's it, reject it.
|
||||
case Sema::AR_inaccessible:
|
||||
if (!Entity.isQuiet())
|
||||
DiagnoseBadAccess(S, Loc, EC, NamingClass, Access, Entity);
|
||||
DiagnoseBadAccess(S, Loc, EC, Entity);
|
||||
return Sema::AR_inaccessible;
|
||||
|
||||
case Sema::AR_accessible:
|
||||
break;
|
||||
}
|
||||
|
||||
// We only consider the natural access of the declaration when
|
||||
// deciding whether to do the protected check.
|
||||
if (Entity.isMemberAccess() && Entity.getAccess() == AS_protected) {
|
||||
NamedDecl *D = Entity.getTargetDecl();
|
||||
if (isa<FieldDecl>(D) ||
|
||||
(isa<CXXMethodDecl>(D) && cast<CXXMethodDecl>(D)->isInstance())) {
|
||||
// FIXME: implement [class.protected]
|
||||
}
|
||||
}
|
||||
|
||||
return Sema::AR_accessible;
|
||||
}
|
||||
|
||||
static Sema::AccessResult CheckAccess(Sema &S, SourceLocation Loc,
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
// RUN: %clang_cc1 -faccess-control -verify %s
|
||||
|
||||
namespace test0 {
|
||||
struct A {
|
||||
static int x;
|
||||
};
|
||||
struct B : A {};
|
||||
struct C : B {};
|
||||
|
||||
int test() {
|
||||
return A::x
|
||||
+ B::x
|
||||
+ C::x;
|
||||
}
|
||||
}
|
||||
|
||||
namespace test1 {
|
||||
struct A {
|
||||
private: static int x; // expected-note 5 {{declared private here}}
|
||||
static int test() { return x; }
|
||||
};
|
||||
struct B : public A {
|
||||
static int test() { return x; } // expected-error {{private member}}
|
||||
};
|
||||
struct C : private A {
|
||||
static int test() { return x; } // expected-error {{private member}}
|
||||
};
|
||||
|
||||
struct D {
|
||||
public: static int x;
|
||||
static int test() { return x; }
|
||||
};
|
||||
struct E : private D { // expected-note{{constrained by private inheritance}}
|
||||
static int test() { return x; }
|
||||
};
|
||||
|
||||
int test() {
|
||||
return A::x // expected-error {{private member}}
|
||||
+ B::x // expected-error {{private member}}
|
||||
+ C::x // expected-error {{private member}}
|
||||
+ D::x
|
||||
+ E::x; // expected-error {{private member}}
|
||||
}
|
||||
}
|
||||
|
||||
namespace test2 {
|
||||
class A {
|
||||
protected: static int x;
|
||||
};
|
||||
|
||||
class B : private A {}; // expected-note {{private inheritance}}
|
||||
class C : private A {
|
||||
int test(B *b) {
|
||||
return b->x; // expected-error {{private member}}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: flesh out these cases
|
Loading…
Reference in New Issue