Skip to content

Commit

Permalink
[Clang][Sema] Warn when 'exclude_from_explicit_instantiation' attribu…
Browse files Browse the repository at this point in the history
…te is used on local classes and members thereof (#88777)

A local class and its members declared in a function template are
instantiated alongside the definition of that template. It therefore
does not make sense to apply the `exclude_from_explicit_instantiation`
attribute to such declarations, and this patch adds a warning to
diagnose these cases (in addition to ignoring the attribute).

(The motivation for this patch is to fix a failing test in libc++ for
#84050. In particular, line 199 in `include/__memory/uses_allocator_construction.h`
in libc++ contains the expression `this->__value_` (reduced to 
https://godbolt.org/z/KqEerKWPd) which will be looked up prior
to instantiation once #84050 lands (the lookup context is the 
current instantiation). `_LIBCPP_HIDE_FROM_ABI` includes 
`__attribute__((exclude_from_explicit_instantiation))`, which in the 
reduced example results in `Local` being instantiated with 
`Local::operator A` as its `DeclContext`)
  • Loading branch information
sdkrystian committed Apr 18, 2024
1 parent 2a4e61b commit 652ae4e
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 0 deletions.
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ Attribute Changes in Clang
This allows the ``_Nullable`` and ``_Nonnull`` family of type attributes to
apply to this class.

- Clang now warns that the ``exclude_from_explicit_instantiation`` attribute
is ignored when applied to a local class or a member thereof.

Improvements to Clang's diagnostics
-----------------------------------
- Clang now applies syntax highlighting to the code snippets it
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3669,6 +3669,9 @@ def warn_attribute_dllexport_explicit_instantiation_decl : Warning<
def warn_attribute_dllexport_explicit_instantiation_def : Warning<
"'dllexport' attribute ignored on explicit instantiation definition">,
InGroup<IgnoredAttributes>;
def warn_attribute_exclude_from_explicit_instantiation_local_class : Warning<
"%0 attribute ignored on local class%select{| member}1">,
InGroup<IgnoredAttributes>;
def warn_invalid_initializer_from_system_header : Warning<
"invalid constructor from class in system header, should not be explicit">,
InGroup<DiagGroup<"invalid-initializer-from-system-header">>;
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,21 @@ static void handleErrorAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(EA);
}

static void handleExcludeFromExplicitInstantiationAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
const auto *PD = isa<CXXRecordDecl>(D)
? cast<DeclContext>(D)
: D->getDeclContext()->getRedeclContext();
if (const auto *RD = dyn_cast<CXXRecordDecl>(PD); RD && RD->isLocalClass()) {
S.Diag(AL.getLoc(),
diag::warn_attribute_exclude_from_explicit_instantiation_local_class)
<< AL << /*IsMember=*/!isa<CXXRecordDecl>(D);
return;
}
D->addAttr(::new (S.Context)
ExcludeFromExplicitInstantiationAttr(S.Context, AL));
}

namespace {
/// Determines if a given Expr references any of the given function's
/// ParmVarDecls, or the function's implicit `this` parameter (if applicable).
Expand Down Expand Up @@ -9339,6 +9354,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_Error:
handleErrorAttr(S, D, AL);
break;
case ParsedAttr::AT_ExcludeFromExplicitInstantiation:
handleExcludeFromExplicitInstantiationAttr(S, D, AL);
break;
case ParsedAttr::AT_DiagnoseIf:
handleDiagnoseIfAttr(S, D, AL);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s

// Test that the exclude_from_explicit_instantiation attribute is ignored
// for local classes and members thereof.

#define EXCLUDE_FROM_EXPLICIT_INSTANTIATION __attribute__((exclude_from_explicit_instantiation)) // expected-note 0+{{expanded from macro}}

namespace N0 {

template<typename T>
void f() {
struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION A { // expected-warning {{attribute ignored on local class}}
// expected-note@-1 2{{in instantiation of}}
EXCLUDE_FROM_EXPLICIT_INSTANTIATION void g(T t) { // expected-warning {{attribute ignored on local class member}}
*t; // expected-error {{indirection requires pointer operand ('int' invalid)}}
}

struct EXCLUDE_FROM_EXPLICIT_INSTANTIATION B { // expected-warning {{attribute ignored on local class}}
void h(T t) {
*t; // expected-error {{indirection requires pointer operand ('int' invalid)}}
}
};
};
}

template void f<int>(); // expected-note 2{{in instantiation of}}

}

// This is a reduced example from libc++ which required that 'value'
// be prefixed with 'this->' because the definition of 'Local::operator A'
// was not instantiated when the definition of 'g' was.
namespace N1 {

struct A { };

struct B {
operator A() {
return A();
}
};

template<typename T>
auto f(T t) {
return A(t);
}

template<typename T>
auto g(T t) {
struct Local {
T value;

EXCLUDE_FROM_EXPLICIT_INSTANTIATION // expected-warning {{attribute ignored on local class member}}
operator A() {
return A(value);
}
};

return f(Local(t));
}

auto x = g(B());

}

0 comments on commit 652ae4e

Please sign in to comment.