Skip to content

Commit 2f97695

Browse files
authored
[C] Diagnose declarations hidden in C++ (#137368)
This introduces a new diagnostic, -Wc++-hidden-decl, which is grouped under -Wc++-compat, that diagnoses declarations which are valid in C but invalid in C++ due to the type being at the wrong scope. e.g., ``` struct S { struct T { int x; } t; }; struct T t; // Valid C, invalid C++ ``` This is implementing the other half of #21898
1 parent 5e4ec04 commit 2f97695

File tree

8 files changed

+137
-3
lines changed

8 files changed

+137
-3
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,18 @@ C Language Changes
150150
- Added ``-Wimplicit-void-ptr-cast``, grouped under ``-Wc++-compat``, which
151151
diagnoses implicit conversion from ``void *`` to another pointer type as
152152
being incompatible with C++. (#GH17792)
153+
- Added ``-Wc++-hidden-decl``, grouped under ``-Wc++-compat``, which diagnoses
154+
use of tag types which are visible in C but not visible in C++ due to scoping
155+
rules. e.g.,
156+
157+
.. code-block:: c
158+
159+
struct S {
160+
struct T {
161+
int x;
162+
} t;
163+
};
164+
struct T t; // Invalid C++, valid C, now diagnosed
153165
- Added ``-Wimplicit-int-enum-cast``, grouped under ``-Wc++-compat``, which
154166
diagnoses implicit conversion from integer types to an enumeration type in C,
155167
which is not compatible with C++. #GH37027

clang/include/clang/AST/DeclBase.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2239,10 +2239,14 @@ class DeclContext {
22392239
return DC && this->getPrimaryContext() == DC->getPrimaryContext();
22402240
}
22412241

2242-
/// Determine whether this declaration context encloses the
2242+
/// Determine whether this declaration context semantically encloses the
22432243
/// declaration context DC.
22442244
bool Encloses(const DeclContext *DC) const;
22452245

2246+
/// Determine whether this declaration context lexically encloses the
2247+
/// declaration context DC.
2248+
bool LexicallyEncloses(const DeclContext *DC) const;
2249+
22462250
/// Find the nearest non-closure ancestor of this context,
22472251
/// i.e. the innermost semantic parent of this context which is not
22482252
/// a closure. A context may be its own non-closure ancestor.

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,13 +156,14 @@ def BuiltinRequiresHeader : DiagGroup<"builtin-requires-header">;
156156
def C99Compat : DiagGroup<"c99-compat">;
157157
def C23Compat : DiagGroup<"c23-compat">;
158158
def : DiagGroup<"c2x-compat", [C23Compat]>;
159+
def HiddenCppDecl : DiagGroup<"c++-hidden-decl">;
159160
def DefaultConstInitUnsafe : DiagGroup<"default-const-init-unsafe">;
160161
def DefaultConstInit : DiagGroup<"default-const-init", [DefaultConstInitUnsafe]>;
161162
def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
162163
def ImplicitIntToEnumCast : DiagGroup<"implicit-int-enum-cast",
163164
[ImplicitEnumEnumCast]>;
164165
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit,
165-
ImplicitIntToEnumCast]>;
166+
ImplicitIntToEnumCast, HiddenCppDecl]>;
166167

167168
def ExternCCompat : DiagGroup<"extern-c-compat">;
168169
def KeywordCompat : DiagGroup<"keyword-compat">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,10 @@ def warn_unused_lambda_capture: Warning<"lambda capture %0 is not "
496496
"%select{used|required to be captured for this use}1">,
497497
InGroup<UnusedLambdaCapture>, DefaultIgnore;
498498

499+
def warn_decl_hidden_in_cpp : Warning<
500+
"%select{struct|union|enum}0 defined within a struct or union is not visible "
501+
"in C++">, InGroup<HiddenCppDecl>, DefaultIgnore;
502+
499503
def warn_reserved_extern_symbol: Warning<
500504
"identifier %0 is reserved because %select{"
501505
"<ERROR>|" // ReservedIdentifierStatus::NotReserved

clang/include/clang/Sema/Sema.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3523,6 +3523,7 @@ class Sema final : public SemaBase {
35233523
}
35243524

35253525
void warnOnReservedIdentifier(const NamedDecl *D);
3526+
void warnOnCTypeHiddenInCPlusPlus(const NamedDecl *D);
35263527

35273528
Decl *ActOnDeclarator(Scope *S, Declarator &D);
35283529

clang/lib/AST/DeclBase.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,18 @@ bool DeclContext::Encloses(const DeclContext *DC) const {
14221422
return getPrimaryContext()->Encloses(DC);
14231423

14241424
for (; DC; DC = DC->getParent())
1425-
if (!isa<LinkageSpecDecl>(DC) && !isa<ExportDecl>(DC) &&
1425+
if (!isa<LinkageSpecDecl, ExportDecl>(DC) &&
1426+
DC->getPrimaryContext() == this)
1427+
return true;
1428+
return false;
1429+
}
1430+
1431+
bool DeclContext::LexicallyEncloses(const DeclContext *DC) const {
1432+
if (getPrimaryContext() != this)
1433+
return getPrimaryContext()->LexicallyEncloses(DC);
1434+
1435+
for (; DC; DC = DC->getLexicalParent())
1436+
if (!isa<LinkageSpecDecl, ExportDecl>(DC) &&
14261437
DC->getPrimaryContext() == this)
14271438
return true;
14281439
return false;

clang/lib/Sema/SemaDecl.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6500,6 +6500,8 @@ NamedDecl *Sema::HandleDeclarator(Scope *S, Declarator &D,
65006500
if (!New)
65016501
return nullptr;
65026502

6503+
warnOnCTypeHiddenInCPlusPlus(New);
6504+
65036505
// If this has an identifier and is not a function template specialization,
65046506
// add it to the scope stack.
65056507
if (New->getDeclName() && AddToScope)
@@ -15213,6 +15215,32 @@ void Sema::CheckFunctionOrTemplateParamDeclarator(Scope *S, Declarator &D) {
1521315215
}
1521415216
}
1521515217

15218+
void Sema::warnOnCTypeHiddenInCPlusPlus(const NamedDecl *D) {
15219+
// This only matters in C.
15220+
if (getLangOpts().CPlusPlus)
15221+
return;
15222+
15223+
// This only matters if the declaration has a type.
15224+
const auto *VD = dyn_cast<ValueDecl>(D);
15225+
if (!VD)
15226+
return;
15227+
15228+
// Get the type, this only matters for tag types.
15229+
QualType QT = VD->getType();
15230+
const auto *TD = QT->getAsTagDecl();
15231+
if (!TD)
15232+
return;
15233+
15234+
// Check if the tag declaration is lexically declared somewhere different
15235+
// from the lexical declaration of the given object, then it will be hidden
15236+
// in C++ and we should warn on it.
15237+
if (!TD->getLexicalParent()->LexicallyEncloses(D->getLexicalDeclContext())) {
15238+
unsigned Kind = TD->isEnum() ? 2 : TD->isUnion() ? 1 : 0;
15239+
Diag(D->getLocation(), diag::warn_decl_hidden_in_cpp) << Kind;
15240+
Diag(TD->getLocation(), diag::note_declared_at);
15241+
}
15242+
}
15243+
1521615244
static void CheckExplicitObjectParameter(Sema &S, ParmVarDecl *P,
1521715245
SourceLocation ExplicitThisLoc) {
1521815246
if (!ExplicitThisLoc.isValid())
@@ -15334,6 +15362,8 @@ Decl *Sema::ActOnParamDeclarator(Scope *S, Declarator &D,
1533415362
New->setScopeInfo(S->getFunctionPrototypeDepth() - 1,
1533515363
S->getNextFunctionPrototypeIndex());
1533615364

15365+
warnOnCTypeHiddenInCPlusPlus(New);
15366+
1533715367
// Add the parameter declaration into this scope.
1533815368
S->AddDecl(New);
1533915369
if (II)
@@ -18864,6 +18894,9 @@ FieldDecl *Sema::CheckFieldDecl(DeclarationName Name, QualType T,
1886418894
if (InvalidDecl)
1886518895
NewFD->setInvalidDecl();
1886618896

18897+
if (!InvalidDecl)
18898+
warnOnCTypeHiddenInCPlusPlus(NewFD);
18899+
1886718900
if (PrevDecl && !isa<TagDecl>(PrevDecl) &&
1886818901
!PrevDecl->isPlaceholderVar(getLangOpts())) {
1886918902
Diag(Loc, diag::err_duplicate_member) << II;

clang/test/Sema/decl-hidden-in-c++.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-hidden-decl %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify -Wc++-compat %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify=good %s
4+
// RUN: %clang_cc1 -fsyntax-only -verify=cxx -x c++ -std=c++2c %s
5+
// good-no-diagnostics
6+
7+
struct A {
8+
struct B { // #b-decl
9+
int x;
10+
} bs;
11+
enum E { // #e-decl
12+
One
13+
} es;
14+
int y;
15+
};
16+
17+
struct C {
18+
struct D {
19+
struct F { // #f-decl
20+
int x;
21+
} f;
22+
} d;
23+
};
24+
25+
struct B b; // expected-warning {{struct defined within a struct or union is not visible in C++}} \
26+
expected-note@#b-decl {{declared here}} \
27+
cxx-error {{variable has incomplete type 'struct B'}} \
28+
cxx-note 3 {{forward declaration of 'B'}}
29+
enum E e; // expected-warning {{enum defined within a struct or union is not visible in C++}} \
30+
expected-note@#e-decl {{declared here}} \
31+
cxx-error {{ISO C++ forbids forward references to 'enum' types}} \
32+
cxx-error {{variable has incomplete type 'enum E'}} \
33+
cxx-note 3 {{forward declaration of 'E'}}
34+
struct F f; // expected-warning {{struct defined within a struct or union is not visible in C++}} \
35+
expected-note@#f-decl {{declared here}} \
36+
cxx-error {{variable has incomplete type 'struct F'}} \
37+
cxx-note {{forward declaration of 'F'}}
38+
39+
void func(struct B b); // expected-warning {{struct defined within a struct or union is not visible in C++}} \
40+
expected-note@#b-decl {{declared here}}
41+
void other_func(enum E e) { // expected-warning {{enum defined within a struct or union is not visible in C++}} \
42+
expected-note@#e-decl {{declared here}} \
43+
cxx-error {{variable has incomplete type 'enum E'}}
44+
struct B b; // expected-warning {{struct defined within a struct or union is not visible in C++}} \
45+
expected-note@#b-decl {{declared here}} \
46+
cxx-error {{variable has incomplete type 'struct B'}}
47+
}
48+
49+
struct X {
50+
struct B b; // expected-warning {{struct defined within a struct or union is not visible in C++}} \
51+
expected-note@#b-decl {{declared here}} \
52+
cxx-error {{field has incomplete type 'struct B'}}
53+
enum E e; // expected-warning {{enum defined within a struct or union is not visible in C++}} \
54+
expected-note@#e-decl {{declared here}} \
55+
cxx-error {{field has incomplete type 'enum E'}}
56+
};
57+
58+
struct Y {
59+
struct Z1 {
60+
int x;
61+
} zs;
62+
63+
struct Z2 {
64+
// This is fine, it is still valid C++.
65+
struct Z1 inner_zs;
66+
} more_zs;
67+
};
68+

0 commit comments

Comments
 (0)