blob: ceee61df23a1e85241ba7458a68cf3630bc3a671 [file] [log] [blame]
//===---- SemaAccess.cpp - C++ Access Control -------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file provides Sema routines for C++ access control semantics.
//
//===----------------------------------------------------------------------===//
#include "Sema.h"
#include "Lookup.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
using namespace clang;
/// SetMemberAccessSpecifier - Set the access specifier of a member.
/// Returns true on error (when the previous member decl access specifier
/// is different from the new member decl access specifier).
bool Sema::SetMemberAccessSpecifier(NamedDecl *MemberDecl,
NamedDecl *PrevMemberDecl,
AccessSpecifier LexicalAS) {
if (!PrevMemberDecl) {
// Use the lexical access specifier.
MemberDecl->setAccess(LexicalAS);
return false;
}
// C++ [class.access.spec]p3: When a member is redeclared its access
// specifier must be same as its initial declaration.
if (LexicalAS != AS_none && LexicalAS != PrevMemberDecl->getAccess()) {
Diag(MemberDecl->getLocation(),
diag::err_class_redeclared_with_different_access)
<< MemberDecl << LexicalAS;
Diag(PrevMemberDecl->getLocation(), diag::note_previous_access_declaration)
<< PrevMemberDecl << PrevMemberDecl->getAccess();
MemberDecl->setAccess(LexicalAS);
return true;
}
MemberDecl->setAccess(PrevMemberDecl->getAccess());
return false;
}
/// Find a class on the derivation path between Derived and Base that is
/// inaccessible. If @p NoPrivileges is true, special access rights (members
/// and friends) are not considered.
const CXXBaseSpecifier *Sema::FindInaccessibleBase(
QualType Derived, QualType Base, CXXBasePaths &Paths, bool NoPrivileges) {
Base = Context.getCanonicalType(Base).getUnqualifiedType();
assert(!Paths.isAmbiguous(Base) &&
"Can't check base class access if set of paths is ambiguous");
assert(Paths.isRecordingPaths() &&
"Can't check base class access without recorded paths");
const CXXBaseSpecifier *InaccessibleBase = 0;
const CXXRecordDecl *CurrentClassDecl = 0;
if (CXXMethodDecl *MD = dyn_cast_or_null<CXXMethodDecl>(getCurFunctionDecl()))
CurrentClassDecl = MD->getParent();
for (CXXBasePaths::paths_iterator Path = Paths.begin(), PathsEnd = Paths.end();
Path != PathsEnd; ++Path) {
bool FoundInaccessibleBase = false;
for (CXXBasePath::const_iterator Element = Path->begin(),
ElementEnd = Path->end(); Element != ElementEnd; ++Element) {
const CXXBaseSpecifier *Base = Element->Base;
switch (Base->getAccessSpecifier()) {
default:
assert(0 && "invalid access specifier");
case AS_public:
// Nothing to do.
break;
case AS_private:
// FIXME: Check if the current function/class is a friend.
if (NoPrivileges || CurrentClassDecl != Element->Class)
FoundInaccessibleBase = true;
break;
case AS_protected:
// FIXME: Implement
break;
}
if (FoundInaccessibleBase) {
InaccessibleBase = Base;
break;
}
}
if (!FoundInaccessibleBase) {
// We found a path to the base, our work here is done.
return 0;
}
}
assert(InaccessibleBase && "no path found, but no inaccessible base");
return InaccessibleBase;
}
/// CheckBaseClassAccess - Check that a derived class can access its base class
/// and report an error if it can't. [class.access.base]
bool Sema::CheckBaseClassAccess(QualType Derived, QualType Base,
unsigned InaccessibleBaseID,
CXXBasePaths &Paths, SourceLocation AccessLoc,
DeclarationName Name) {
if (!getLangOptions().AccessControl)
return false;
const CXXBaseSpecifier *InaccessibleBase = FindInaccessibleBase(
Derived, Base, Paths);
if (InaccessibleBase) {
Diag(AccessLoc, InaccessibleBaseID)
<< Derived << Base << Name;
AccessSpecifier AS = InaccessibleBase->getAccessSpecifierAsWritten();
// If there's no written access specifier, then the inheritance specifier
// is implicitly private.
if (AS == AS_none)
Diag(InaccessibleBase->getSourceRange().getBegin(),
diag::note_inheritance_implicitly_private_here);
else
Diag(InaccessibleBase->getSourceRange().getBegin(),
diag::note_inheritance_specifier_here) << AS;
return true;
}
return false;
}
/// Diagnose the path which caused the given declaration to become
/// inaccessible.
static void DiagnoseAccessPath(Sema &S, const LookupResult &R, NamedDecl *D,
AccessSpecifier Access) {
// Easy case: the decl's natural access determined its path access.
if (Access == D->getAccess() || D->getAccess() == AS_private) {
S.Diag(D->getLocation(), diag::note_access_natural)
<< (unsigned) (Access == AS_protected);
return;
}
// TODO: flesh this out
S.Diag(D->getLocation(), diag::note_access_constrained_by_path)
<< (unsigned) (Access == AS_protected);
}
/// Checks access to the given declaration in the current context.
///
/// \param R the means via which the access was made; must have a naming
/// class set
/// \param D the declaration accessed
/// \param Access the best access along any inheritance path from the
/// naming class to the declaration. AS_none means the path is impossible
bool Sema::CheckAccess(const LookupResult &R, NamedDecl *D,
AccessSpecifier Access) {
assert(R.getNamingClass() && "performing access check without naming class");
// If the access path is public, it's accessible everywhere.
if (Access == AS_public)
return false;
// If we're currently parsing a top-level declaration, delay
// diagnostics. This is the only case where parsing a declaration
// can actually change our effective context for the purposes of
// access control.
if (CurContext->isFileContext() && ParsingDeclDepth) {
DelayedDiagnostics.push_back(
DelayedDiagnostic::makeAccess(R.getNameLoc(), D, Access,
R.getNamingClass()));
return false;
}
return CheckEffectiveAccess(CurContext, R, D, Access);
}
/// Checks access from the given effective context.
bool Sema::CheckEffectiveAccess(DeclContext *EffectiveContext,
const LookupResult &R,
NamedDecl *D, AccessSpecifier Access) {
DeclContext *DC = EffectiveContext;
while (isa<CXXRecordDecl>(DC) &&
cast<CXXRecordDecl>(DC)->isAnonymousStructOrUnion())
DC = DC->getParent();
CXXRecordDecl *CurRecord;
if (isa<CXXRecordDecl>(DC))
CurRecord = cast<CXXRecordDecl>(DC);
else if (isa<CXXMethodDecl>(DC))
CurRecord = cast<CXXMethodDecl>(DC)->getParent();
else {
Diag(R.getNameLoc(), diag::err_access_outside_class)
<< (Access == AS_protected);
DiagnoseAccessPath(*this, R, D, Access);
return true;
}
CXXRecordDecl *NamingClass = R.getNamingClass();
while (NamingClass->isAnonymousStructOrUnion())
// This should be guaranteed by the fact that the decl has
// non-public access. If not, we should make it guaranteed!
NamingClass = cast<CXXRecordDecl>(NamingClass);
// White-list accesses from within the declaring class.
if (Access != AS_none &&
CurRecord->getCanonicalDecl() == NamingClass->getCanonicalDecl())
return false;
// Protected access.
if (Access == AS_protected) {
// FIXME: implement [class.protected]p1
if (CurRecord->isDerivedFrom(NamingClass))
return false;
// FIXME: dependent classes
}
// FIXME: friends
// Okay, it's a bad access, reject it.
CXXRecordDecl *DeclaringClass = cast<CXXRecordDecl>(D->getDeclContext());
if (Access == AS_protected) {
Diag(R.getNameLoc(), diag::err_access_protected)
<< Context.getTypeDeclType(DeclaringClass)
<< Context.getTypeDeclType(CurRecord);
DiagnoseAccessPath(*this, R, D, Access);
return true;
}
assert(Access == AS_private || Access == AS_none);
Diag(R.getNameLoc(), diag::err_access_private)
<< Context.getTypeDeclType(DeclaringClass)
<< Context.getTypeDeclType(CurRecord);
DiagnoseAccessPath(*this, R, D, Access);
return true;
}
void Sema::HandleDelayedAccessCheck(DelayedDiagnostic &DD, Decl *Ctx) {
NamedDecl *D = DD.AccessData.Decl;
// Fake up a lookup result.
LookupResult R(*this, D->getDeclName(), DD.Loc, LookupOrdinaryName);
R.suppressDiagnostics();
R.setNamingClass(DD.AccessData.NamingClass);
// Pretend we did this from the context of the newly-parsed
// declaration.
DeclContext *EffectiveContext = Ctx->getDeclContext();
if (CheckEffectiveAccess(EffectiveContext, R, D, DD.AccessData.Access))
DD.Triggered = true;
}
bool Sema::CheckUnresolvedLookupAccess(UnresolvedLookupExpr *E,
NamedDecl *D, AccessSpecifier Access) {
if (!getLangOptions().AccessControl || !E->getNamingClass())
return false;
// Fake up a lookup result.
LookupResult R(*this, E->getName(), E->getNameLoc(), LookupOrdinaryName);
R.suppressDiagnostics();
R.setNamingClass(E->getNamingClass());
R.addDecl(D, Access);
// FIXME: protected check (triggers for member-address expressions)
return CheckAccess(R, D, Access);
}
/// Perform access-control checking on a previously-unresolved member
/// access which has now been resolved to a member.
bool Sema::CheckUnresolvedMemberAccess(UnresolvedMemberExpr *E,
NamedDecl *D, AccessSpecifier Access) {
if (!getLangOptions().AccessControl)
return false;
// Fake up a lookup result.
LookupResult R(*this, E->getMemberName(), E->getMemberLoc(),
LookupOrdinaryName);
R.suppressDiagnostics();
R.setNamingClass(E->getNamingClass());
R.addDecl(D, Access);
if (CheckAccess(R, D, Access))
return true;
// FIXME: protected check
return false;
}
/// Checks access to a constructor.
bool Sema::CheckConstructorAccess(SourceLocation UseLoc,
CXXConstructorDecl *Constructor,
AccessSpecifier Access) {
if (!getLangOptions().AccessControl)
return false;
CXXRecordDecl *NamingClass = cast<CXXRecordDecl>(Constructor->getParent());
LookupResult R(*this, Constructor->getDeclName(), UseLoc, LookupOrdinaryName);
R.suppressDiagnostics();
R.setNamingClass(NamingClass);
return CheckAccess(R, Constructor, Access);
}
/// Checks access to an overloaded member operator, including
/// conversion operators.
bool Sema::CheckMemberOperatorAccess(SourceLocation OpLoc,
Expr *ObjectExpr,
NamedDecl *MemberOperator,
AccessSpecifier Access) {
if (!getLangOptions().AccessControl)
return false;
const RecordType *RT = ObjectExpr->getType()->getAs<RecordType>();
assert(RT && "found member operator but object expr not of record type");
CXXRecordDecl *NamingClass = cast<CXXRecordDecl>(RT->getDecl());
LookupResult R(*this, DeclarationName(), OpLoc, LookupOrdinaryName);
R.suppressDiagnostics();
R.setNamingClass(NamingClass);
if (CheckAccess(R, MemberOperator, Access))
return true;
// FIXME: protected check
return false;
}
/// Checks access to all the declarations in the given result set.
void Sema::CheckAccess(const LookupResult &R) {
for (LookupResult::iterator I = R.begin(), E = R.end(); I != E; ++I)
CheckAccess(R, *I, I.getAccess());
}