//===---- 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/DeclFriend.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;
}

namespace {
struct EffectiveContext {
  EffectiveContext() : Record(0), Function(0) {}

  explicit EffectiveContext(DeclContext *DC) {
    if (isa<FunctionDecl>(DC)) {
      Function = cast<FunctionDecl>(DC)->getCanonicalDecl();
      DC = Function->getDeclContext();
    } else
      Function = 0;
    
    if (isa<CXXRecordDecl>(DC))
      Record = cast<CXXRecordDecl>(DC)->getCanonicalDecl();
    else
      Record = 0;
  }

  bool isClass(const CXXRecordDecl *R) const {
    return R->getCanonicalDecl() == Record;
  }

  CXXRecordDecl *Record;
  FunctionDecl *Function;
};
}

static CXXRecordDecl *FindDeclaringClass(NamedDecl *D) {
  CXXRecordDecl *DeclaringClass = cast<CXXRecordDecl>(D->getDeclContext());
  while (DeclaringClass->isAnonymousStructOrUnion())
    DeclaringClass = cast<CXXRecordDecl>(DeclaringClass->getDeclContext());
  return DeclaringClass;
}

static Sema::AccessResult GetFriendKind(Sema &S,
                                        const EffectiveContext &EC,
                                        const CXXRecordDecl *Class) {
  // A class always has access to its own members.
  if (EC.isClass(Class))
    return Sema::AR_accessible;

  // Okay, check friends.
  for (CXXRecordDecl::friend_iterator I = Class->friend_begin(),
         E = Class->friend_end(); I != E; ++I) {
    FriendDecl *Friend = *I;

    if (Type *T = Friend->getFriendType()) {
      if (EC.Record &&
          S.Context.hasSameType(QualType(T, 0),
                                S.Context.getTypeDeclType(EC.Record)))
        return Sema::AR_accessible;
    } else {
      NamedDecl *D
        = cast<NamedDecl>(Friend->getFriendDecl()->getCanonicalDecl());

      // The decl pointers in EC have been canonicalized, so pointer
      // equality is sufficient.
      if (D == EC.Function || D == EC.Record)
        return Sema::AR_accessible;
    }

    // FIXME: templates! templated contexts! dependent delay!
  }

  // That's it, give up.
  return Sema::AR_inaccessible;
}

/// Finds the best path from the naming class to the declaring class,
/// taking friend declarations into account.
///
/// \return null if friendship is dependent
static CXXBasePath *FindBestPath(Sema &S,
                                 const EffectiveContext &EC,
                                 CXXRecordDecl *Derived,
                                 CXXRecordDecl *Base,
                                 CXXBasePaths &Paths) {
  // Derive the paths to the desired base.
  bool isDerived = Derived->isDerivedFrom(Base, Paths);
  assert(isDerived && "derived class not actually derived from base");
  (void) isDerived;

  CXXBasePath *BestPath = 0;

  // Derive the friend-modified access along each path.
  for (CXXBasePaths::paths_iterator PI = Paths.begin(), PE = Paths.end();
         PI != PE; ++PI) {

    // Walk through the path backwards.
    AccessSpecifier PathAccess = AS_public;
    CXXBasePath::iterator I = PI->end(), E = PI->begin();
    while (I != E) {
      --I;

      AccessSpecifier BaseAccess = I->Base->getAccessSpecifier();
      if (BaseAccess != AS_public) {
        switch (GetFriendKind(S, EC, I->Class)) {
        case Sema::AR_inaccessible: break;
        case Sema::AR_accessible: BaseAccess = AS_public; break;
        case Sema::AR_dependent: return 0;
        case Sema::AR_delayed:
          llvm_unreachable("friend resolution is never delayed"); break;
        }
      }

      PathAccess = CXXRecordDecl::MergeAccess(BaseAccess, PathAccess);
    }

    // Note that we modify the path's Access field to the
    // friend-modified access.
    if (BestPath == 0 || PathAccess < BestPath->Access) {
      BestPath = &*PI;
      BestPath->Access = PathAccess;
    }
  }

  return BestPath;
}

/// Diagnose the path which caused the given declaration or base class
/// to become inaccessible.
static void DiagnoseAccessPath(Sema &S,
                               const EffectiveContext &EC,
                               CXXRecordDecl *NamingClass,
                               CXXRecordDecl *DeclaringClass,
                               NamedDecl *D, AccessSpecifier Access) {
  // 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)) {
    case Sema::AR_inaccessible: {
      S.Diag(D->getLocation(), diag::note_access_natural)
        << (unsigned) (Access == AS_protected)
        << /*FIXME: not implicitly*/ 0;
      return;
    }

    case Sema::AR_accessible: break;

    case Sema::AR_dependent:
    case Sema::AR_delayed:
      llvm_unreachable("dependent/delayed not allowed");
      return;
    }
  }

  CXXBasePaths Paths;
  CXXBasePath &Path = *FindBestPath(S, EC, NamingClass, DeclaringClass, Paths);

  CXXBasePath::iterator I = Path.end(), E = Path.begin();
  while (I != E) {
    --I;

    const CXXBaseSpecifier *BS = I->Base;
    AccessSpecifier BaseAccess = BS->getAccessSpecifier();

    // If this is public inheritance, or the derived class is a friend,
    // skip this step.
    if (BaseAccess == AS_public)
      continue;

    switch (GetFriendKind(S, EC, I->Class)) {
    case Sema::AR_accessible: continue;
    case Sema::AR_inaccessible: break;

    case Sema::AR_dependent:
    case Sema::AR_delayed:
      llvm_unreachable("dependent friendship, should not be diagnosing");
    }

    // Check whether this base specifier is the tighest point
    // constraining access.  We have to check against AS_private for
    // the same reasons as above.
    if (BaseAccess == AS_private || BaseAccess >= Access) {

      // We're constrained by inheritance, but we want to say
      // "declared private here" if we're diagnosing a hierarchy
      // conversion and this is the final step.
      unsigned diagnostic;
      if (D) diagnostic = diag::note_access_constrained_by_path;
      else if (I + 1 == Path.end()) diagnostic = diag::note_access_natural;
      else diagnostic = diag::note_access_constrained_by_path;

      S.Diag(BS->getSourceRange().getBegin(), diagnostic)
        << BS->getSourceRange()
        << (BaseAccess == AS_protected)
        << (BS->getAccessSpecifierAsWritten() == AS_none);
      return;
    }
  }

  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);

  if (isa<CXXConstructorDecl>(D)) {
    unsigned DiagID = (Access == AS_protected ? diag::err_access_ctor_protected
                                              : diag::err_access_ctor_private);
    S.Diag(Loc, DiagID)
      << S.Context.getTypeDeclType(DeclaringClass);
  } else {
    unsigned DiagID = (Access == AS_protected ? diag::err_access_protected
                                              : diag::err_access_private);
    S.Diag(Loc, DiagID)
      << D->getDeclName()
      << 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,
                                     Sema::AccessDiagnosticsKind ADK) {
  if (ADK == Sema::ADK_covariance) {
    S.Diag(Loc, diag::err_covariant_return_inaccessible_base)
      << S.Context.getTypeDeclType(Entity.getDerivedClass())
      << S.Context.getTypeDeclType(Entity.getBaseClass())
      << (Access == AS_protected);
  } else if (Entity.getKind() == Sema::AccessedEntity::BaseToDerivedConversion) {
    S.Diag(Loc, diag::err_downcast_from_inaccessible_base)
      << S.Context.getTypeDeclType(Entity.getDerivedClass())
      << S.Context.getTypeDeclType(Entity.getBaseClass())
      << (Access == AS_protected);
  } else {
    S.Diag(Loc, diag::err_upcast_to_inaccessible_base)
      << S.Context.getTypeDeclType(Entity.getDerivedClass())
      << S.Context.getTypeDeclType(Entity.getBaseClass())
      << (Access == AS_protected);
  }
  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,
                              Sema::AccessDiagnosticsKind ADK) {
  if (Entity.isMemberAccess())
    DiagnoseInaccessibleMember(S, Loc, EC, NamingClass, Access, Entity);
  else
    DiagnoseInaccessibleBase(S, Loc, EC, Access, Entity, ADK);
}


/// Try to elevate access using friend declarations.  This is
/// potentially quite expensive.
static void TryElevateAccess(Sema &S,
                             const EffectiveContext &EC,
                             const Sema::AccessedEntity &Entity,
                             AccessSpecifier &Access) {
  CXXRecordDecl *DeclaringClass;
  if (Entity.isMemberAccess()) {
    DeclaringClass = FindDeclaringClass(Entity.getTargetDecl());
  } else {
    DeclaringClass = Entity.getBaseClass();
  }
  CXXRecordDecl *NamingClass = Entity.getNamingClass();

  // Adjust the declaration of the referred entity.
  AccessSpecifier DeclAccess = AS_none;
  if (Entity.isMemberAccess()) {
    NamedDecl *Target = Entity.getTargetDecl();

    DeclAccess = Target->getAccess();
    if (DeclAccess != AS_public) {
      switch (GetFriendKind(S, EC, DeclaringClass)) {
      case Sema::AR_accessible: DeclAccess = AS_public; break;
      case Sema::AR_inaccessible: break;
      case Sema::AR_dependent: /* FIXME: delay dependent friendship */ return;
      case Sema::AR_delayed: llvm_unreachable("friend status is never delayed");
      }
    }

    if (DeclaringClass == NamingClass) {
      Access = DeclAccess;
      return;
    }
  }

  assert(DeclaringClass != NamingClass);

  // Append the declaration's access if applicable.
  CXXBasePaths Paths;
  CXXBasePath *Path = FindBestPath(S, EC, Entity.getNamingClass(),
                                   DeclaringClass, Paths);
  if (!Path) {
    // FIXME: delay dependent friendship
    return;
  }

  // Grab the access along the best path.
  AccessSpecifier NewAccess = Path->Access;
  if (Entity.isMemberAccess())
    NewAccess = CXXRecordDecl::MergeAccess(NewAccess, DeclAccess);
  
  assert(NewAccess <= Access && "access along best path worse than direct?");
  Access = NewAccess;
}

/// Checks access to an entity from the given effective context.
static Sema::AccessResult CheckEffectiveAccess(Sema &S,
                                               const EffectiveContext &EC,
                                               SourceLocation Loc,
                                         Sema::AccessedEntity const &Entity,
                                         Sema::AccessDiagnosticsKind ADK) {
  AccessSpecifier Access = Entity.getAccess();
  assert(Access != AS_public);

  CXXRecordDecl *NamingClass = Entity.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);

  if (!EC.Record) {
    TryElevateAccess(S, EC, Entity, Access);
    if (Access == AS_public) return Sema::AR_accessible;

    if (ADK != Sema::ADK_quiet)
      DiagnoseBadAccess(S, Loc, EC, NamingClass, Access, Entity, ADK);
    return Sema::AR_inaccessible;
  }

  // White-list accesses from within the declaring class.
  if (Access != AS_none && EC.isClass(NamingClass))
    return Sema::AR_accessible;
  
  // If the access is worse than 'protected', try to promote to it using
  // friend declarations.
  bool TriedElevation = false;
  if (Access != AS_protected) {
    TryElevateAccess(S, EC, Entity, Access);
    if (Access == AS_public) return Sema::AR_accessible;
    TriedElevation = true;
  }

  // Protected access.
  if (Access == AS_protected) {
    // FIXME: implement [class.protected]p1
    if (EC.Record->isDerivedFrom(NamingClass))
      return Sema::AR_accessible;

    // FIXME: delay dependent classes
  }

  // We're about to reject;  one last chance to promote access.
  if (!TriedElevation) {
    TryElevateAccess(S, EC, Entity, Access);
    if (Access == AS_public) return Sema::AR_accessible;
  }
    
  // Okay, that's it, reject it.
  if (ADK != Sema::ADK_quiet)
    DiagnoseBadAccess(S, Loc, EC, NamingClass, Access, Entity, ADK);
  return Sema::AR_inaccessible;
}

static Sema::AccessResult CheckAccess(Sema &S, SourceLocation Loc,
                                      const Sema::AccessedEntity &Entity,
                                      Sema::AccessDiagnosticsKind ADK
                                        = Sema::ADK_normal) {
  // If the access path is public, it's accessible everywhere.
  if (Entity.getAccess() == AS_public)
    return Sema::AR_accessible;

  // 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 (S.CurContext->isFileContext() && S.ParsingDeclDepth) {
    assert(ADK == Sema::ADK_normal && "delaying abnormal access check");
    S.DelayedDiagnostics.push_back(
        Sema::DelayedDiagnostic::makeAccess(Loc, Entity));
    return Sema::AR_delayed;
  }

  return CheckEffectiveAccess(S, EffectiveContext(S.CurContext),
                              Loc, Entity, ADK);
}

void Sema::HandleDelayedAccessCheck(DelayedDiagnostic &DD, Decl *Ctx) {
  // Pretend we did this from the context of the newly-parsed
  // declaration.
  EffectiveContext EC(Ctx->getDeclContext());

  if (CheckEffectiveAccess(*this, EC, DD.Loc, DD.AccessData, ADK_normal))
    DD.Triggered = true;
}

Sema::AccessResult Sema::CheckUnresolvedLookupAccess(UnresolvedLookupExpr *E,
                                                     NamedDecl *D,
                                                     AccessSpecifier Access) {
  if (!getLangOptions().AccessControl || !E->getNamingClass())
    return AR_accessible;

  return CheckAccess(*this, E->getNameLoc(),
                 AccessedEntity::makeMember(E->getNamingClass(), Access, D));
}

/// Perform access-control checking on a previously-unresolved member
/// access which has now been resolved to a member.
Sema::AccessResult Sema::CheckUnresolvedMemberAccess(UnresolvedMemberExpr *E,
                                                     NamedDecl *D,
                                                     AccessSpecifier Access) {
  if (!getLangOptions().AccessControl)
    return AR_accessible;

  return CheckAccess(*this, E->getMemberLoc(),
                 AccessedEntity::makeMember(E->getNamingClass(), Access, D));
}

Sema::AccessResult Sema::CheckDestructorAccess(SourceLocation Loc,
                                               const RecordType *RT) {
  if (!getLangOptions().AccessControl)
    return AR_accessible;

  CXXRecordDecl *NamingClass = cast<CXXRecordDecl>(RT->getDecl());
  CXXDestructorDecl *Dtor = NamingClass->getDestructor(Context);

  AccessSpecifier Access = Dtor->getAccess();
  if (Access == AS_public)
    return AR_accessible;

  return CheckAccess(*this, Loc,
                 AccessedEntity::makeMember(NamingClass, Access, Dtor));
}

/// Checks access to a constructor.
Sema::AccessResult Sema::CheckConstructorAccess(SourceLocation UseLoc,
                                  CXXConstructorDecl *Constructor,
                                  AccessSpecifier Access) {
  if (!getLangOptions().AccessControl)
    return AR_accessible;

  CXXRecordDecl *NamingClass = Constructor->getParent();
  return CheckAccess(*this, UseLoc,
                 AccessedEntity::makeMember(NamingClass, Access, Constructor));
}

/// Checks access to an overloaded member operator, including
/// conversion operators.
Sema::AccessResult Sema::CheckMemberOperatorAccess(SourceLocation OpLoc,
                                                   Expr *ObjectExpr,
                                                   NamedDecl *MemberOperator,
                                                   AccessSpecifier Access) {
  if (!getLangOptions().AccessControl)
    return AR_accessible;

  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());

  return CheckAccess(*this, OpLoc,
            AccessedEntity::makeMember(NamingClass, Access, MemberOperator));
}

/// Checks access for a hierarchy conversion.
///
/// \param IsBaseToDerived whether this is a base-to-derived conversion (true)
///     or a derived-to-base conversion (false)
/// \param ForceCheck true if this check should be performed even if access
///     control is disabled;  some things rely on this for semantics
/// \param ForceUnprivileged true if this check should proceed as if the
///     context had no special privileges
/// \param ADK controls the kind of diagnostics that are used
Sema::AccessResult Sema::CheckBaseClassAccess(SourceLocation AccessLoc,
                                              bool IsBaseToDerived,
                                              QualType Base,
                                              QualType Derived,
                                              const CXXBasePath &Path,
                                              bool ForceCheck,
                                              bool ForceUnprivileged,
                                              AccessDiagnosticsKind ADK) {
  if (!ForceCheck && !getLangOptions().AccessControl)
    return AR_accessible;

  if (Path.Access == AS_public)
    return AR_accessible;

  // TODO: preserve the information about which types exactly were used.
  CXXRecordDecl *BaseD, *DerivedD;
  BaseD = cast<CXXRecordDecl>(Base->getAs<RecordType>()->getDecl());
  DerivedD = cast<CXXRecordDecl>(Derived->getAs<RecordType>()->getDecl());
  AccessedEntity Entity = AccessedEntity::makeBaseClass(IsBaseToDerived,
                                                        BaseD, DerivedD,
                                                        Path.Access);

  if (ForceUnprivileged)
    return CheckEffectiveAccess(*this, EffectiveContext(),
                                AccessLoc, Entity, ADK);
  return CheckAccess(*this, AccessLoc, Entity, ADK);
}

/// Checks access to all the declarations in the given result set.
void Sema::CheckLookupAccess(const LookupResult &R) {
  assert(getLangOptions().AccessControl
         && "performing access check without access control");
  assert(R.getNamingClass() && "performing access check without naming class");

  for (LookupResult::iterator I = R.begin(), E = R.end(); I != E; ++I)
    if (I.getAccess() != AS_public)
      CheckAccess(*this, R.getNameLoc(),
                  AccessedEntity::makeMember(R.getNamingClass(),
                                             I.getAccess(), *I));
}
