Skip to content

Commit 56c8d3a

Browse files
committed
[Clang] strengthen checks for 'main' function to meet [basic.start.main] p3 requirements
1 parent 1a9acd7 commit 56c8d3a

File tree

5 files changed

+91
-11
lines changed

5 files changed

+91
-11
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ Improvements to Clang's diagnostics
145145

146146
- -Wdangling-assignment-gsl is enabled by default.
147147

148+
- Clang now diagnoses the use of `main` in an `extern` context as invalid according to [basic.start.main] p3. Fixes #GH101512.
149+
148150
Improvements to Clang's time-trace
149151
----------------------------------
150152

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,9 @@ def warn_main_redefined : Warning<"variable named 'main' with external linkage "
990990
"has undefined behavior">, InGroup<Main>;
991991
def ext_main_used : Extension<
992992
"referring to 'main' within an expression is a Clang extension">, InGroup<Main>;
993+
def ext_main_invalid_linkage_specification : ExtWarn<
994+
"'main' should not be "
995+
"'extern \"%select{C|C++}0\"'">, InGroup<Main>;
993996

994997
/// parser diagnostics
995998
def ext_no_declarators : ExtWarn<"declaration does not declare anything">,

clang/lib/AST/Decl.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3292,11 +3292,9 @@ bool FunctionDecl::isImmediateFunction() const {
32923292
}
32933293

32943294
bool FunctionDecl::isMain() const {
3295-
const TranslationUnitDecl *tunit =
3296-
dyn_cast<TranslationUnitDecl>(getDeclContext()->getRedeclContext());
3297-
return tunit &&
3298-
!tunit->getASTContext().getLangOpts().Freestanding &&
3299-
isNamed(this, "main");
3295+
const DeclContext *DC = getDeclContext();
3296+
return isNamed(this, "main") && !getLangOpts().Freestanding &&
3297+
(DC->getRedeclContext()->isTranslationUnit() || isExternC());
33003298
}
33013299

33023300
bool FunctionDecl::isMSVCRTEntryPoint() const {

clang/lib/Sema/SemaDecl.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7353,6 +7353,15 @@ void emitReadOnlyPlacementAttrWarning(Sema &S, const VarDecl *VD) {
73537353
}
73547354
}
73557355

7356+
// Checks if VD is declared at global scope or with C language linkage
7357+
static bool isMainVar(DeclarationName Name, VarDecl *VD) {
7358+
return Name.getAsIdentifierInfo() &&
7359+
Name.getAsIdentifierInfo()->isStr("main") &&
7360+
!VD->getDescribedVarTemplate() &&
7361+
(VD->getDeclContext()->getRedeclContext()->isTranslationUnit() ||
7362+
VD->isExternC());
7363+
}
7364+
73567365
NamedDecl *Sema::ActOnVariableDeclarator(
73577366
Scope *S, Declarator &D, DeclContext *DC, TypeSourceInfo *TInfo,
73587367
LookupResult &Previous, MultiTemplateParamsArg TemplateParamLists,
@@ -8052,10 +8061,7 @@ NamedDecl *Sema::ActOnVariableDeclarator(
80528061
}
80538062

80548063
// Special handling of variable named 'main'.
8055-
if (Name.getAsIdentifierInfo() && Name.getAsIdentifierInfo()->isStr("main") &&
8056-
NewVD->getDeclContext()->getRedeclContext()->isTranslationUnit() &&
8057-
!getLangOpts().Freestanding && !NewVD->getDescribedVarTemplate()) {
8058-
8064+
if (!getLangOpts().Freestanding && isMainVar(Name, NewVD)) {
80598065
// C++ [basic.start.main]p3
80608066
// A program that declares a variable main at global scope is ill-formed.
80618067
if (getLangOpts().CPlusPlus)
@@ -12210,7 +12216,18 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD,
1221012216
return Redeclaration;
1221112217
}
1221212218

12213-
void Sema::CheckMain(FunctionDecl* FD, const DeclSpec& DS) {
12219+
void Sema::CheckMain(FunctionDecl *FD, const DeclSpec &DS) {
12220+
// [basic.start.main]p3
12221+
// The main function shall not be declared with a linkage-specification.
12222+
if (FD->isExternCContext() ||
12223+
(FD->isExternCXXContext() &&
12224+
FD->getDeclContext()->getRedeclContext()->isTranslationUnit())) {
12225+
Diag(FD->getLocation(), diag::ext_main_invalid_linkage_specification)
12226+
<< FD->getLanguageLinkage();
12227+
FD->setInvalidDecl();
12228+
return;
12229+
}
12230+
1221412231
// C++11 [basic.start.main]p3:
1221512232
// A program that [...] declares main to be inline, static or
1221612233
// constexpr is ill-formed.
@@ -12238,7 +12255,6 @@ void Sema::CheckMain(FunctionDecl* FD, const DeclSpec& DS) {
1223812255
<< FixItHint::CreateRemoval(DS.getConstexprSpecLoc());
1223912256
FD->setConstexprKind(ConstexprSpecKind::Unspecified);
1224012257
}
12241-
1224212258
if (getLangOpts().OpenCL) {
1224312259
Diag(FD->getLocation(), diag::err_opencl_no_main)
1224412260
<< FD->hasAttr<OpenCLKernelAttr>();

clang/test/CXX/basic/basic.start/basic.start.main/p3.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
// RUN: %clang_cc1 -fsyntax-only -verify %s -DTEST8
99
// RUN: %clang_cc1 -fsyntax-only -verify %s -DTEST9
1010
// RUN: %clang_cc1 -fsyntax-only -verify %s -DTEST10 -ffreestanding
11+
// RUN: %clang_cc1 -fsyntax-only -verify -pedantic-errors %s -DTEST11
12+
// RUN: %clang_cc1 -fsyntax-only -verify -pedantic-errors %s -DTEST12
13+
// RUN: %clang_cc1 -fsyntax-only -verify -pedantic-errors %s -DTEST13
1114

1215
#if TEST1
1316
int main; // expected-error{{main cannot be declared as global variable}}
@@ -61,6 +64,64 @@ int q(void)
6164
// expected-no-diagnostics
6265
int main;
6366

67+
#elif TEST11
68+
extern "C" {
69+
namespace Y {
70+
int main; // expected-error {{main cannot be declared as global variable}}
71+
}
72+
}
73+
namespace ns {
74+
extern "C" int main; // expected-error {{main cannot be declared as global variable}}
75+
}
76+
77+
#elif TEST12
78+
extern "C" struct A { int main(); }; // ok
79+
80+
namespace c {
81+
extern "C" void main(); // expected-error {{'main' should not be 'extern "C"'}}
82+
}
83+
84+
extern "C" {
85+
namespace Z {
86+
void main(); // expected-error {{'main' should not be 'extern "C"'}}
87+
}
88+
}
89+
90+
namespace ns {
91+
extern "C" struct A {
92+
int main; // ok
93+
};
94+
95+
extern "C" struct B {
96+
int main(); // ok
97+
};
98+
}
99+
100+
#elif TEST13
101+
extern "C++" {
102+
int main(); // expected-error {{'main' should not be 'extern "C++"'}}
103+
}
104+
105+
extern "C" {
106+
int main(); // expected-error {{'main' should not be 'extern "C"'}}
107+
}
108+
109+
extern "C" int main(); // expected-error {{'main' should not be 'extern "C"'}}
110+
extern "C++" int main(); // expected-error {{'main' should not be 'extern "C++"'}}
111+
112+
namespace ns1 {
113+
extern "C++" int main(); // ok
114+
extern "C" {
115+
extern "C++" {
116+
int main(void *); // ok
117+
}
118+
}
119+
}
120+
121+
namespace ns2 {
122+
extern "C++" void main() {} // ok
123+
}
124+
64125
#else
65126
#error Unknown Test
66127
#endif

0 commit comments

Comments
 (0)