mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2025-01-15 12:39:19 +00:00
[CodeComplete] Cleanup access checking in code completion
Summary: Also fixes a crash (see the added 'accessibility-crash.cpp' test). Reviewers: ioeric, kadircet Reviewed By: kadircet Subscribers: cfe-commits Differential Revision: https://reviews.llvm.org/D55124 llvm-svn: 348135
This commit is contained in:
parent
6ef089d21c
commit
f1822ec431
@ -6065,7 +6065,8 @@ public:
|
||||
bool ForceCheck = false,
|
||||
bool ForceUnprivileged = false);
|
||||
void CheckLookupAccess(const LookupResult &R);
|
||||
bool IsSimplyAccessible(NamedDecl *decl, DeclContext *Ctx);
|
||||
bool IsSimplyAccessible(NamedDecl *Decl, CXXRecordDecl *NamingClass,
|
||||
QualType BaseType);
|
||||
bool isSpecialMemberAccessibleForDeletion(CXXMethodDecl *decl,
|
||||
AccessSpecifier access,
|
||||
QualType objectType);
|
||||
@ -10340,7 +10341,7 @@ public:
|
||||
void CodeCompleteAssignmentRHS(Scope *S, Expr *LHS);
|
||||
|
||||
void CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
|
||||
bool EnteringContext);
|
||||
bool EnteringContext, QualType BaseType);
|
||||
void CodeCompleteUsing(Scope *S);
|
||||
void CodeCompleteUsingDirective(Scope *S);
|
||||
void CodeCompleteNamespaceDecl(Scope *S);
|
||||
|
@ -235,6 +235,20 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS,
|
||||
|
||||
while (true) {
|
||||
if (HasScopeSpecifier) {
|
||||
if (Tok.is(tok::code_completion)) {
|
||||
// Code completion for a nested-name-specifier, where the code
|
||||
// completion token follows the '::'.
|
||||
Actions.CodeCompleteQualifiedId(getCurScope(), SS, EnteringContext,
|
||||
ObjectType.get());
|
||||
// Include code completion token into the range of the scope otherwise
|
||||
// when we try to annotate the scope tokens the dangling code completion
|
||||
// token will cause assertion in
|
||||
// Preprocessor::AnnotatePreviousCachedTokens.
|
||||
SS.setEndLoc(Tok.getLocation());
|
||||
cutOffParsing();
|
||||
return true;
|
||||
}
|
||||
|
||||
// C++ [basic.lookup.classref]p5:
|
||||
// If the qualified-id has the form
|
||||
//
|
||||
@ -246,19 +260,6 @@ bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS,
|
||||
// To implement this, we clear out the object type as soon as we've
|
||||
// seen a leading '::' or part of a nested-name-specifier.
|
||||
ObjectType = nullptr;
|
||||
|
||||
if (Tok.is(tok::code_completion)) {
|
||||
// Code completion for a nested-name-specifier, where the code
|
||||
// completion token follows the '::'.
|
||||
Actions.CodeCompleteQualifiedId(getCurScope(), SS, EnteringContext);
|
||||
// Include code completion token into the range of the scope otherwise
|
||||
// when we try to annotate the scope tokens the dangling code completion
|
||||
// token will cause assertion in
|
||||
// Preprocessor::AnnotatePreviousCachedTokens.
|
||||
SS.setEndLoc(Tok.getLocation());
|
||||
cutOffParsing();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// nested-name-specifier:
|
||||
|
@ -555,6 +555,9 @@ void PrintingCodeCompleteConsumer::ProcessCodeCompleteResults(
|
||||
Tags.push_back("Hidden");
|
||||
if (Results[I].InBaseClass)
|
||||
Tags.push_back("InBase");
|
||||
if (Results[I].Availability ==
|
||||
CXAvailabilityKind::CXAvailability_NotAccessible)
|
||||
Tags.push_back("Inaccessible");
|
||||
if (!Tags.empty())
|
||||
OS << " (" << llvm::join(Tags, ",") << ")";
|
||||
}
|
||||
|
@ -1877,22 +1877,33 @@ void Sema::CheckLookupAccess(const LookupResult &R) {
|
||||
/// specifiers into account, but no member access expressions and such.
|
||||
///
|
||||
/// \param Target the declaration to check if it can be accessed
|
||||
/// \param Ctx the class/context from which to start the search
|
||||
/// \param NamingClass the class in which the lookup was started.
|
||||
/// \param BaseType type of the left side of member access expression.
|
||||
/// \p BaseType and \p NamingClass are used for C++ access control.
|
||||
/// Depending on the lookup case, they should be set to the following:
|
||||
/// - lhs.target (member access without a qualifier):
|
||||
/// \p BaseType and \p NamingClass are both the type of 'lhs'.
|
||||
/// - lhs.X::target (member access with a qualifier):
|
||||
/// BaseType is the type of 'lhs', NamingClass is 'X'
|
||||
/// - X::target (qualified lookup without member access):
|
||||
/// BaseType is null, NamingClass is 'X'.
|
||||
/// - target (unqualified lookup).
|
||||
/// BaseType is null, NamingClass is the parent class of 'target'.
|
||||
/// \return true if the Target is accessible from the Class, false otherwise.
|
||||
bool Sema::IsSimplyAccessible(NamedDecl *Target, DeclContext *Ctx) {
|
||||
if (CXXRecordDecl *Class = dyn_cast<CXXRecordDecl>(Ctx)) {
|
||||
if (!Target->isCXXClassMember())
|
||||
return true;
|
||||
|
||||
bool Sema::IsSimplyAccessible(NamedDecl *Target, CXXRecordDecl *NamingClass,
|
||||
QualType BaseType) {
|
||||
// Perform the C++ accessibility checks first.
|
||||
if (Target->isCXXClassMember() && NamingClass) {
|
||||
if (!getLangOpts().CPlusPlus)
|
||||
return false;
|
||||
if (Target->getAccess() == AS_public)
|
||||
return true;
|
||||
QualType qType = Class->getTypeForDecl()->getCanonicalTypeInternal();
|
||||
// The unprivileged access is AS_none as we don't know how the member was
|
||||
// accessed, which is described by the access in DeclAccessPair.
|
||||
// `IsAccessible` will examine the actual access of Target (i.e.
|
||||
// Decl->getAccess()) when calculating the access.
|
||||
AccessTarget Entity(Context, AccessedEntity::Member, Class,
|
||||
DeclAccessPair::make(Target, AS_none), qType);
|
||||
AccessTarget Entity(Context, AccessedEntity::Member, NamingClass,
|
||||
DeclAccessPair::make(Target, AS_none), BaseType);
|
||||
EffectiveContext EC(CurContext);
|
||||
return ::IsAccessible(*this, EC, Entity) != ::AR_inaccessible;
|
||||
}
|
||||
|
@ -1278,38 +1278,53 @@ bool ResultBuilder::IsObjCIvar(const NamedDecl *ND) const {
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
/// Visible declaration consumer that adds a code-completion result
|
||||
/// for each visible declaration.
|
||||
class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
|
||||
ResultBuilder &Results;
|
||||
DeclContext *CurContext;
|
||||
DeclContext *InitialLookupCtx;
|
||||
// NamingClass and BaseType are used for access-checking. See
|
||||
// Sema::IsSimplyAccessible for details.
|
||||
CXXRecordDecl *NamingClass;
|
||||
QualType BaseType;
|
||||
std::vector<FixItHint> FixIts;
|
||||
// This is set to the record where the search starts, if this is a record
|
||||
// member completion.
|
||||
RecordDecl *MemberCompletionRecord = nullptr;
|
||||
|
||||
public:
|
||||
CodeCompletionDeclConsumer(
|
||||
ResultBuilder &Results, DeclContext *CurContext,
|
||||
std::vector<FixItHint> FixIts = std::vector<FixItHint>(),
|
||||
RecordDecl *MemberCompletionRecord = nullptr)
|
||||
: Results(Results), CurContext(CurContext), FixIts(std::move(FixIts)),
|
||||
MemberCompletionRecord(MemberCompletionRecord) {}
|
||||
ResultBuilder &Results, DeclContext *InitialLookupCtx,
|
||||
QualType BaseType = QualType(),
|
||||
std::vector<FixItHint> FixIts = std::vector<FixItHint>())
|
||||
: Results(Results), InitialLookupCtx(InitialLookupCtx),
|
||||
FixIts(std::move(FixIts)) {
|
||||
NamingClass = llvm::dyn_cast<CXXRecordDecl>(InitialLookupCtx);
|
||||
// If BaseType was not provided explicitly, emulate implicit 'this->'.
|
||||
if (BaseType.isNull()) {
|
||||
auto ThisType = Results.getSema().getCurrentThisType();
|
||||
if (!ThisType.isNull()) {
|
||||
assert(ThisType->isPointerType());
|
||||
BaseType = ThisType->getPointeeType();
|
||||
if (!NamingClass)
|
||||
NamingClass = BaseType->getAsCXXRecordDecl();
|
||||
}
|
||||
}
|
||||
this->BaseType = BaseType;
|
||||
}
|
||||
|
||||
void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx,
|
||||
bool InBaseClass) override {
|
||||
bool Accessible = true;
|
||||
if (Ctx) {
|
||||
// Set the actual accessing context (i.e. naming class) to the record
|
||||
// context where the search starts. When `InBaseClass` is true, `Ctx`
|
||||
// will be the base class, which is not the actual naming class.
|
||||
DeclContext *AccessingCtx =
|
||||
MemberCompletionRecord ? MemberCompletionRecord : Ctx;
|
||||
Accessible = Results.getSema().IsSimplyAccessible(ND, AccessingCtx);
|
||||
}
|
||||
// Naming class to use for access check. In most cases it was provided
|
||||
// explicitly (e.g. member access (lhs.foo) or qualified lookup (X::)), for
|
||||
// unqualified lookup we fallback to the \p Ctx in which we found the
|
||||
// member.
|
||||
auto *NamingClass = this->NamingClass;
|
||||
if (!NamingClass)
|
||||
NamingClass = llvm::dyn_cast_or_null<CXXRecordDecl>(Ctx);
|
||||
bool Accessible =
|
||||
Results.getSema().IsSimplyAccessible(ND, NamingClass, BaseType);
|
||||
ResultBuilder::Result Result(ND, Results.getBasePriority(ND), nullptr,
|
||||
false, Accessible, FixIts);
|
||||
Results.AddResult(Result, CurContext, Hiding, InBaseClass);
|
||||
Results.AddResult(Result, InitialLookupCtx, Hiding, InBaseClass);
|
||||
}
|
||||
|
||||
void EnteredContext(DeclContext *Ctx) override {
|
||||
@ -3699,20 +3714,15 @@ void Sema::CodeCompleteOrdinaryName(Scope *S,
|
||||
}
|
||||
|
||||
// If we are in a C++ non-static member function, check the qualifiers on
|
||||
// the member function to filter/prioritize the results list and set the
|
||||
// context to the record context so that accessibility check in base class
|
||||
// works correctly.
|
||||
RecordDecl *MemberCompletionRecord = nullptr;
|
||||
// the member function to filter/prioritize the results list.
|
||||
if (CXXMethodDecl *CurMethod = dyn_cast<CXXMethodDecl>(CurContext)) {
|
||||
if (CurMethod->isInstance()) {
|
||||
Results.setObjectTypeQualifiers(
|
||||
Qualifiers::fromCVRMask(CurMethod->getTypeQualifiers()));
|
||||
MemberCompletionRecord = CurMethod->getParent();
|
||||
}
|
||||
}
|
||||
|
||||
CodeCompletionDeclConsumer Consumer(Results, CurContext, /*FixIts=*/{},
|
||||
MemberCompletionRecord);
|
||||
CodeCompletionDeclConsumer Consumer(Results, CurContext);
|
||||
LookupVisibleDecls(S, LookupOrdinaryName, Consumer,
|
||||
CodeCompleter->includeGlobals(),
|
||||
CodeCompleter->loadExternal());
|
||||
@ -4152,8 +4162,7 @@ AddRecordMembersCompletionResults(Sema &SemaRef, ResultBuilder &Results,
|
||||
std::vector<FixItHint> FixIts;
|
||||
if (AccessOpFixIt)
|
||||
FixIts.emplace_back(AccessOpFixIt.getValue());
|
||||
CodeCompletionDeclConsumer Consumer(Results, SemaRef.CurContext,
|
||||
std::move(FixIts), RD);
|
||||
CodeCompletionDeclConsumer Consumer(Results, RD, BaseType, std::move(FixIts));
|
||||
SemaRef.LookupVisibleDecls(RD, Sema::LookupMemberName, Consumer,
|
||||
SemaRef.CodeCompleter->includeGlobals(),
|
||||
/*IncludeDependentBases=*/true,
|
||||
@ -4283,7 +4292,7 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base,
|
||||
|
||||
// Add all ivars from this class and its superclasses.
|
||||
if (Class) {
|
||||
CodeCompletionDeclConsumer Consumer(Results, CurContext);
|
||||
CodeCompletionDeclConsumer Consumer(Results, Class, BaseType);
|
||||
Results.setFilter(&ResultBuilder::IsObjCIvar);
|
||||
LookupVisibleDecls(
|
||||
Class, LookupMemberName, Consumer, CodeCompleter->includeGlobals(),
|
||||
@ -4856,7 +4865,7 @@ void Sema::CodeCompleteAssignmentRHS(Scope *S, Expr *LHS) {
|
||||
}
|
||||
|
||||
void Sema::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
|
||||
bool EnteringContext) {
|
||||
bool EnteringContext, QualType BaseType) {
|
||||
if (SS.isEmpty() || !CodeCompleter)
|
||||
return;
|
||||
|
||||
@ -4903,7 +4912,7 @@ void Sema::CodeCompleteQualifiedId(Scope *S, CXXScopeSpec &SS,
|
||||
|
||||
if (CodeCompleter->includeNamespaceLevelDecls() ||
|
||||
(!Ctx->isNamespace() && !Ctx->isTranslationUnit())) {
|
||||
CodeCompletionDeclConsumer Consumer(Results, CurContext);
|
||||
CodeCompletionDeclConsumer Consumer(Results, Ctx, BaseType);
|
||||
LookupVisibleDecls(Ctx, LookupOrdinaryName, Consumer,
|
||||
/*IncludeGlobalScope=*/true,
|
||||
/*IncludeDependentBases=*/true,
|
||||
|
23
clang/test/CodeCompletion/accessibility-crash.cpp
Normal file
23
clang/test/CodeCompletion/accessibility-crash.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
class X {
|
||||
public:
|
||||
int pub;
|
||||
protected:
|
||||
int prot;
|
||||
private:
|
||||
int priv;
|
||||
};
|
||||
|
||||
class Y : public X {
|
||||
int test() {
|
||||
[]() {
|
||||
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:13:1 %s -o - \
|
||||
// RUN: | FileCheck %s
|
||||
// CHECK: priv (InBase,Inaccessible)
|
||||
// CHECK: prot (InBase)
|
||||
// CHECK: pub (InBase)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
73
clang/test/CodeCompletion/accessibility.cpp
Normal file
73
clang/test/CodeCompletion/accessibility.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
class X {
|
||||
public:
|
||||
int pub;
|
||||
protected:
|
||||
int prot;
|
||||
private:
|
||||
int priv;
|
||||
};
|
||||
|
||||
class Unrelated {
|
||||
public:
|
||||
static int pub;
|
||||
protected:
|
||||
static int prot;
|
||||
private:
|
||||
static int priv;
|
||||
};
|
||||
|
||||
class Y : public X {
|
||||
int test() {
|
||||
this->pub = 10;
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:21:11 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=THIS %s
|
||||
// THIS: priv (InBase,Inaccessible)
|
||||
// THIS: prot (InBase)
|
||||
// THIS: pub (InBase)
|
||||
//
|
||||
// Also check implicit 'this->', i.e. complete at the start of the line.
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:21:1 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=THIS %s
|
||||
|
||||
X().pub + 10;
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:32:9 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=X-OBJ %s
|
||||
// X-OBJ: priv (Inaccessible)
|
||||
// X-OBJ: prot (Inaccessible)
|
||||
// X-OBJ: pub : [#int#]pub
|
||||
|
||||
Y().pub + 10;
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:39:9 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=Y-OBJ %s
|
||||
// Y-OBJ: priv (InBase,Inaccessible)
|
||||
// Y-OBJ: prot (InBase)
|
||||
// Y-OBJ: pub (InBase)
|
||||
|
||||
this->X::pub = 10;
|
||||
X::pub = 10;
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:46:14 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=THIS-BASE %s
|
||||
//
|
||||
// THIS-BASE: priv (Inaccessible)
|
||||
// THIS-BASE: prot : [#int#]prot
|
||||
// THIS-BASE: pub : [#int#]pub
|
||||
//
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:47:8 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=THIS-BASE %s
|
||||
|
||||
|
||||
this->Unrelated::pub = 10; // a check we don't crash in this cases.
|
||||
Y().Unrelated::pub = 10; // a check we don't crash in this cases.
|
||||
Unrelated::pub = 10;
|
||||
// RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:59:22 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=UNRELATED %s
|
||||
// UNRELATED: priv (Inaccessible)
|
||||
// UNRELATED: prot (Inaccessible)
|
||||
// UNRELATED: pub : [#int#]pub
|
||||
//
|
||||
// RUN: not %clang_cc1 -fsyntax-only -code-completion-at=%s:60:20 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=UNRELATED %s
|
||||
// RUN: not %clang_cc1 -fsyntax-only -code-completion-at=%s:61:16 %s -o - \
|
||||
// RUN: | FileCheck -check-prefix=UNRELATED %s
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user