Fix some accessibility corner case bugs.

Anonymous structs, anonymous unions, unscoped enums. You know, all of
the most important aspects of this wonderful language of ours.
This commit is contained in:
Dan Katz
2025-04-11 16:36:04 -04:00
parent 633d86746a
commit 4ffd254378
3 changed files with 89 additions and 21 deletions

View File

@@ -5628,27 +5628,25 @@ bool is_accessible(APValue &Result, ASTContext &C, MetaActions &Meta,
if (!Evaluator(RV, Args[0], true))
return true;
auto validate = [&](Decl *D) -> bool {
if (!D || !D->getDeclContext() || !isa<CXXRecordDecl>(D->getDeclContext()))
return DiagnoseReflectionKind(Diagnoser, Range, "a class member");
auto validate = [&](Decl *D, CXXRecordDecl *&NamingCls) -> bool {
auto *DC = dyn_cast<CXXRecordDecl>(D->getNonTransparentDeclContext());
if (!NamingCls)
NamingCls = DC;
if (auto *Ctx = cast<CXXRecordDecl>(D->getDeclContext());
Ctx->isBeingDefined())
if (DC && DC->isBeingDefined())
return Diagnoser(Range.getBegin(),
diag::metafn_access_query_class_being_defined)
<< Ctx << Range;
<< DC << Range;
return false;
};
switch (RV.getReflectionKind()) {
case ReflectionKind::Type: {
NamedDecl *D = findTypeDecl(RV.getReflectedType());
if (validate(D))
if (validate(D, NamingCls))
return true;
if (!NamingCls)
NamingCls = cast<CXXRecordDecl>(D->getDeclContext());
else if (!NamingCls)
return SetAndSucceed(Result, makeBool(C, true));
bool Accessible = UnconditionalAccess ||
Meta.IsAccessible(D, AccessDC, NamingCls);
@@ -5656,11 +5654,10 @@ bool is_accessible(APValue &Result, ASTContext &C, MetaActions &Meta,
}
case ReflectionKind::Declaration: {
ValueDecl *D = RV.getReflectedDecl();
if (validate(D))
if (validate(D, NamingCls))
return true;
if (!NamingCls)
NamingCls = cast<CXXRecordDecl>(D->getDeclContext());
else if (!NamingCls)
return SetAndSucceed(Result, makeBool(C, true));
bool Accessible = UnconditionalAccess ||
Meta.IsAccessible(RV.getReflectedDecl(), AccessDC,
@@ -5669,11 +5666,10 @@ bool is_accessible(APValue &Result, ASTContext &C, MetaActions &Meta,
}
case ReflectionKind::Template: {
TemplateDecl *D = RV.getReflectedTemplate().getAsTemplateDecl();
if (validate(D))
if (validate(D, NamingCls))
return true;
if (!NamingCls)
NamingCls = cast<CXXRecordDecl>(D->getDeclContext());
else if (!NamingCls)
return SetAndSucceed(Result, makeBool(C, true));
bool Accessible = UnconditionalAccess ||
Meta.IsAccessible(D, AccessDC, NamingCls);
@@ -5710,8 +5706,7 @@ bool is_accessible(APValue &Result, ASTContext &C, MetaActions &Meta,
case ReflectionKind::Namespace:
case ReflectionKind::DataMemberSpec:
case ReflectionKind::Annotation:
return DiagnoseReflectionKind(Diagnoser, Range, "a class member",
DescriptionOf(RV));
return SetAndSucceed(Result, makeBool(C, true));
}
llvm_unreachable("invalid reflection type");
}

View File

@@ -178,12 +178,34 @@ public:
bool IsAccessible(NamedDecl *Target, DeclContext *Ctx,
CXXRecordDecl *NamingCls) override {
bool Result = false;
// If 'Target' is a (possibly nested) anonymous struct/union or unscoped
// enumerator, replace it with its parent recursively until it's no longer
// such a member.
while (Target && Target->getDeclContext() &&
Target->getDeclContext() != NamingCls &&
[](DeclContext *DC) {
if (auto *RD = dyn_cast<CXXRecordDecl>(DC))
return RD->isAnonymousStructOrUnion();
else return DC->isTransparentContext();
}(Target->getDeclContext()))
if (isa<TranslationUnitDecl>(Target->getDeclContext()))
// Can happen if Target was a member of a static anonymous union at
// namespace scope.
return true;
else
Target = cast<NamedDecl>(Target->getDeclContext());
if (auto *Cls = dyn_cast_or_null<CXXRecordDecl>(Target->getDeclContext())) {
if (Cls != NamingCls &&
!S.IsDerivedFrom(SourceLocation{},
QualType(NamingCls->getTypeForDecl(), 0),
QualType(Cls->getTypeForDecl(), 0)))
return false;
else if (NamingCls->isAnonymousStructOrUnion())
// Clang's access-checking machinery isn't equipped to deal with checks
// where the "naming" class (ha!) is anonymous - can't imagine why!
return true;
DeclContext *PreviousDC = S.CurContext;
{

View File

@@ -288,4 +288,55 @@ static_assert(!is_private(std::meta::info{}));
static_assert(!is_private(^^int));
} // namespace queries
// ==============
// unscoped_enums
// ==============
namespace unscoped_enums {
class C {
enum { E };
public:
enum { F };
static constexpr auto r = ^^E;
};
static_assert(!is_accessible(C::r,
std::meta::access_context::unprivileged()));
static_assert(!is_accessible(C::r,
std::meta::access_context::unprivileged().via(^^C)));
static_assert(is_accessible(^^C::F,
std::meta::access_context::unprivileged()));
static_assert(is_accessible(^^C::F,
std::meta::access_context::unprivileged().via(^^C)));
} // namespace unscoped_enums
// ========================
// anonymous_structs_unions
// ========================
namespace anonymous_structs_unions {
class C {
struct { struct { struct { int a; }; }; };
public:
union { union { union { int b; }; }; };
static constexpr auto r_a = ^^a;
};
static_assert(is_accessible(C::r_a,
std::meta::access_context::unprivileged()));
static_assert(!is_accessible(C::r_a,
std::meta::access_context::unprivileged().via(^^C)));
static_assert(is_accessible(^^C::b,
std::meta::access_context::unprivileged()));
static_assert(is_accessible(^^C::b,
std::meta::access_context::unprivileged().via(^^C)));
static union { int a; };
static_assert(is_accessible(^^a,
std::meta::access_context::unprivileged()));
} // namespace anonymous_structs_unions
int main() { }