Skip to content

[Clang] Disallow explicit object parameters in more contexts #89078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,8 @@ Bug Fixes to C++ Support
- Fix a crash when parsing an invalid type-requirement in a requires expression. (#GH51868)
- Fix parsing of built-in type-traits such as ``__is_pointer`` in libstdc++ headers. (#GH95598)
- Fixed failed assertion when resolving context of defaulted comparison method outside of struct. (#GH96043).
- Clang now diagnoses explicit object parameters in member pointers and other contexts where they should not appear.
Fixes (#GH85992).

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -7577,6 +7577,8 @@ def err_explicit_object_lambda_ambiguous_base : Error<
def err_explicit_object_lambda_inaccessible_base : Error<
"invalid explicit object parameter type %0 in lambda with capture; "
"the type must derive publicly from the lambda">;
def err_explicit_object_parameter_invalid: Error<
"an explicit object parameter can only appear as the first parameter of a member function">;

def err_ref_qualifier_overload : Error<
"cannot overload a member function %select{without a ref-qualifier|with "
Expand Down
34 changes: 30 additions & 4 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11258,6 +11258,34 @@ void Sema::CheckExplicitObjectMemberFunction(Declarator &D,
D.setInvalidType();
}

// Friend declarations require some care. Consider:
//
// namespace N {
// struct A{};
// int f(A);
// }
//
// struct S {
// struct T {
// int f(this T);
// };
//
// friend int T::f(this T); // Allow this.
// friend int f(this S); // But disallow this.
// friend int N::f(this A); // And disallow this.
// };
//
// Here, it seems to suffice to check whether the scope
// specifier designates a class type.
if (D.getDeclSpec().isFriendSpecified() &&
!isa_and_present<CXXRecordDecl>(
computeDeclContext(D.getCXXScopeSpec()))) {
Diag(ExplicitObjectParam->getBeginLoc(),
diag::err_explicit_object_parameter_nonmember)
<< D.getSourceRange() << /*non-member=*/2 << IsLambda;
D.setInvalidType();
}

if (IsLambda && FTI.hasMutableQualifier()) {
Diag(ExplicitObjectParam->getBeginLoc(),
diag::err_explicit_object_parameter_mutable)
Expand All @@ -11268,10 +11296,8 @@ void Sema::CheckExplicitObjectMemberFunction(Declarator &D,
return;

if (!DC || !DC->isRecord()) {
Diag(ExplicitObjectParam->getLocation(),
diag::err_explicit_object_parameter_nonmember)
<< D.getSourceRange() << /*non-member=*/2 << IsLambda;
D.setInvalidType();
assert(D.isInvalidType() && "Explicit object parameter in non-member "
"should have been diagnosed already");
return;
}

Expand Down
55 changes: 55 additions & 0 deletions clang/lib/Sema/SemaType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4762,6 +4762,61 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
// Check for auto functions and trailing return type and adjust the
// return type accordingly.
if (!D.isInvalidType()) {
auto IsClassType = [&](CXXScopeSpec &SS) {
// If there already was an problem with the scope, don’t issue another
// error about the explicit object parameter.
return SS.isInvalid() ||
isa_and_present<CXXRecordDecl>(S.computeDeclContext(SS));
};

// C++23 [dcl.fct]p6:
//
// An explicit-object-parameter-declaration is a parameter-declaration
// with a this specifier. An explicit-object-parameter-declaration shall
// appear only as the first parameter-declaration of a
// parameter-declaration-list of one of:
//
// - a declaration of a member function or member function template
// ([class.mem]), or
//
// - an explicit instantiation ([temp.explicit]) or explicit
// specialization ([temp.expl.spec]) of a templated member function,
// or
//
// - a lambda-declarator [expr.prim.lambda].
DeclaratorContext C = D.getContext();
ParmVarDecl *First =
FTI.NumParams
? dyn_cast_if_present<ParmVarDecl>(FTI.Params[0].Param)
: nullptr;

bool IsFunctionDecl = D.getInnermostNonParenChunk() == &DeclType;
if (First && First->isExplicitObjectParameter() &&
C != DeclaratorContext::LambdaExpr &&

// Either not a member or nested declarator in a member.
//
// Note that e.g. 'static' or 'friend' declarations are accepted
// here; we diagnose them later when we build the member function
// because it's easier that way.
(C != DeclaratorContext::Member || !IsFunctionDecl) &&

// Allow out-of-line definitions of member functions.
!IsClassType(D.getCXXScopeSpec())) {
if (IsFunctionDecl)
S.Diag(First->getBeginLoc(),
diag::err_explicit_object_parameter_nonmember)
<< /*non-member*/ 2 << /*function*/ 0
<< First->getSourceRange();
else
S.Diag(First->getBeginLoc(),
diag::err_explicit_object_parameter_invalid)
<< First->getSourceRange();

D.setInvalidType();
AreDeclaratorChunksValid = false;
}

// trailing-return-type is only required if we're declaring a function,
// and not, for instance, a pointer to a function.
if (D.getDeclSpec().hasAutoTypeSpec() &&
Expand Down
41 changes: 41 additions & 0 deletions clang/test/SemaCXX/cxx2b-deducing-this.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -918,3 +918,44 @@ struct C {
}
};
}

namespace GH85992 {
namespace N {
struct A {
int f(this A);
};

int f(A);
}

struct S {
int (S::*x)(this int); // expected-error {{an explicit object parameter can only appear as the first parameter of a member function}}
int (*y)(this int); // expected-error {{an explicit object parameter can only appear as the first parameter of a member function}}
int (***z)(this int); // expected-error {{an explicit object parameter can only appear as the first parameter of a member function}}

int f(this S);
int ((g))(this S);
friend int h(this S); // expected-error {{an explicit object parameter cannot appear in a non-member function}}
int h(int x, int (*)(this S)); // expected-error {{an explicit object parameter can only appear as the first parameter of a member function}}

struct T {
int f(this T);
};

friend int T::f(this T);
friend int N::A::f(this N::A);
friend int N::f(this N::A); // expected-error {{an explicit object parameter cannot appear in a non-member function}}
int friend func(this T); // expected-error {{an explicit object parameter cannot appear in a non-member function}}
};

using T = int (*)(this int); // expected-error {{an explicit object parameter can only appear as the first parameter of a member function}}
using U = int (S::*)(this int); // expected-error {{an explicit object parameter can only appear as the first parameter of a member function}}
int h(this int); // expected-error {{an explicit object parameter cannot appear in a non-member function}}

int S::f(this S) { return 1; }

namespace a {
void f();
};
void a::f(this auto) {} // expected-error {{an explicit object parameter cannot appear in a non-member function}}
}