From 9955f14aaf9995f6f0f7bf058ac740463003c470 Mon Sep 17 00:00:00 2001
From: Aaron Ballman
Date: Wed, 20 Apr 2022 13:26:38 -0400
Subject: [PATCH] [C2x] Disallow functions without prototypes/functions with
identifier lists
WG14 has elected to remove support for K&R C functions in C2x. The
feature was introduced into C89 already deprecated, so after this long
of a deprecation period, the committee has made an empty parameter list
mean the same thing in C as it means in C++: the function accepts no
arguments exactly as if the function were written with (void) as the
parameter list.
This patch implements WG14 N2841 No function declarators without
prototypes (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2841.htm)
and WG14 N2432 Remove support for function definitions with identifier
lists (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2432.pdf).
It also adds The -fno-knr-functions command line option to opt into
this behavior in other language modes.
Differential Revision: https://reviews.llvm.org/D123955
---
clang/docs/ReleaseNotes.rst | 7 +++++++
clang/include/clang/Basic/LangOptions.def | 1 +
clang/include/clang/Basic/LangOptions.h | 6 ++++++
clang/include/clang/Driver/Options.td | 5 +++++
clang/lib/AST/ASTContext.cpp | 9 ++++++++-
clang/lib/Parse/ParseDecl.cpp | 13 ++++++++++---
clang/lib/Sema/SemaDecl.cpp | 3 +++
clang/lib/Sema/SemaType.cpp | 13 +++++++++----
clang/lib/StaticAnalyzer/Core/MemRegion.cpp | 6 ++++--
clang/test/Driver/no-knr-functions.c | 11 +++++++++++
clang/test/Frontend/no-knr-functions.c | 11 +++++++++++
clang/test/Parser/c2x-attributes.c | 14 ++++++++++----
clang/test/Parser/c2x-func-prototype.c | 16 ++++++++++++++++
clang/test/Sema/attr-c2x.c | 2 +-
clang/test/Sema/c2x-func-prototype.c | 24 ++++++++++++++++++++++++
clang/www/c_status.html | 2 +-
16 files changed, 127 insertions(+), 16 deletions(-)
create mode 100644 clang/test/Driver/no-knr-functions.c
create mode 100644 clang/test/Frontend/no-knr-functions.c
create mode 100644 clang/test/Parser/c2x-func-prototype.c
create mode 100644 clang/test/Sema/c2x-func-prototype.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index fb81e9d..600185e 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -176,6 +176,11 @@ Non-comprehensive list of changes in this release
New Compiler Flags
------------------
+- Added the ``-fno-knr-functions`` flag to allow users to opt into the C2x
+ behavior where a function with an empty parameter list is treated as though
+ the parameter list were ``void``. There is no ``-fknr-functions`` or
+ ``-fno-no-knr-functions`` flag; this feature cannot be disabled in language
+ modes where it is required, such as C++ or C2x.
Deprecated Compiler Flags
-------------------------
@@ -239,6 +244,8 @@ C2x Feature Support
- Removed support for implicit function declarations. This was a C89 feature
that was removed in C99, but cannot be supported in C2x because it requires
support for functions without prototypes, which no longer exist in C2x.
+- Implemented `WG14 N2841 No function declarators without prototypes `_
+ and `WG14 N2432 Remove support for function definitions with identifier lists `_.
C++ Language Changes in Clang
-----------------------------
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index ba89b5a..5c62c4c 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -121,6 +121,7 @@ LANGOPT(GNUMode , 1, 1, "GNU extensions")
LANGOPT(GNUKeywords , 1, 1, "GNU keywords")
VALUE_LANGOPT(GNUCVersion , 32, 0, "GNU C compatibility version")
BENIGN_LANGOPT(ImplicitInt, 1, 0, "C89 implicit 'int'")
+LANGOPT(DisableKNRFunctions, 1, 0, "require function types to have a prototype")
LANGOPT(Digraphs , 1, 0, "digraphs")
BENIGN_LANGOPT(HexFloats , 1, 0, "C99 hexadecimal float constants")
LANGOPT(CXXOperatorNames , 1, 0, "C++ operator name keywords")
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index b0cc4c9..9d852d7 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -521,6 +521,12 @@ public:
/// as a string.
std::string getOpenCLVersionString() const;
+ /// Returns true if functions without prototypes or functions with an
+ /// identifier list (aka K&R C functions) are not allowed.
+ bool requiresStrictPrototypes() const {
+ return CPlusPlus || C2x || DisableKNRFunctions;
+ }
+
/// Check if return address signing is enabled.
bool hasSignReturnAddress() const {
return getSignReturnAddressScope() != SignReturnAddressScopeKind::None;
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 9666ac8..bb870dd 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2297,6 +2297,11 @@ defm implicit_modules : BoolFOption<"implicit-modules",
def fretain_comments_from_system_headers : Flag<["-"], "fretain-comments-from-system-headers">, Group, Flags<[CC1Option]>,
MarshallingInfoFlag>;
+def fno_knr_functions : Flag<["-"], "fno-knr-functions">, Group,
+ MarshallingInfoFlag>,
+ HelpText<"Disable support for K&R C function declarations">,
+ Flags<[CC1Option, CoreOption]>;
+
def fmudflapth : Flag<["-"], "fmudflapth">, Group;
def fmudflap : Flag<["-"], "fmudflap">, Group;
def fnested_functions : Flag<["-"], "fnested-functions">, Group;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index f6bb757..dacc4f3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4239,6 +4239,13 @@ static bool isCanonicalResultType(QualType T) {
QualType
ASTContext::getFunctionNoProtoType(QualType ResultTy,
const FunctionType::ExtInfo &Info) const {
+ // FIXME: This assertion cannot be enabled (yet) because the ObjC rewriter
+ // functionality creates a function without a prototype regardless of
+ // language mode (so it makes them even in C++). Once the rewriter has been
+ // fixed, this assertion can be enabled again.
+ //assert(!LangOpts.requiresStrictPrototypes() &&
+ // "strict prototypes are disabled");
+
// Unique functions, to guarantee there is only one function of a particular
// structure.
llvm::FoldingSetNodeID ID;
@@ -11235,7 +11242,7 @@ QualType ASTContext::GetBuiltinType(unsigned Id,
// We really shouldn't be making a no-proto type here.
- if (ArgTypes.empty() && Variadic && !getLangOpts().CPlusPlus)
+ if (ArgTypes.empty() && Variadic && !getLangOpts().requiresStrictPrototypes())
return getFunctionNoProtoType(ResType, EI);
FunctionProtoType::ExtProtoInfo EPI;
diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 233f092..2b386c8 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -6661,8 +6661,11 @@ void Parser::ParseFunctionDeclarator(Declarator &D,
else if (RequiresArg)
Diag(Tok, diag::err_argument_required_after_attribute);
- HasProto = ParamInfo.size() || getLangOpts().CPlusPlus
- || getLangOpts().OpenCL;
+ // OpenCL disallows functions without a prototype, but it doesn't enforce
+ // strict prototypes as in C2x because it allows a function definition to
+ // have an identifier list. See OpenCL 3.0 6.11/g for more details.
+ HasProto = ParamInfo.size() || getLangOpts().requiresStrictPrototypes() ||
+ getLangOpts().OpenCL;
// If we have the closing ')', eat it.
Tracker.consumeClose();
@@ -6799,7 +6802,7 @@ bool Parser::ParseRefQualifier(bool &RefQualifierIsLValueRef,
/// Note that identifier-lists are only allowed for normal declarators, not for
/// abstract-declarators.
bool Parser::isFunctionDeclaratorIdentifierList() {
- return !getLangOpts().CPlusPlus
+ return !getLangOpts().requiresStrictPrototypes()
&& Tok.is(tok::identifier)
&& !TryAltiVecVectorToken()
// K&R identifier lists can't have typedefs as identifiers, per C99
@@ -6833,6 +6836,10 @@ bool Parser::isFunctionDeclaratorIdentifierList() {
void Parser::ParseFunctionDeclaratorIdentifierList(
Declarator &D,
SmallVectorImpl &ParamInfo) {
+ // We should never reach this point in C2x or C++.
+ assert(!getLangOpts().requiresStrictPrototypes() &&
+ "Cannot parse an identifier list in C2x or C++");
+
// If there was no identifier specified for the declarator, either we are in
// an abstract-declarator, or we are in a parameter declarator which was found
// to be abstract. In abstract-declarators, identifier lists are not valid:
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 5d54621..0c8b7fc 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8822,6 +8822,9 @@ static FunctionDecl *CreateNewFunctionDecl(Sema &SemaRef, Declarator &D,
(D.getDeclSpec().isTypeRep() &&
D.getDeclSpec().getRepAsType().get()->isFunctionProtoType()) ||
(!R->getAsAdjusted() && R->isFunctionProtoType());
+ assert(
+ (HasPrototype || !SemaRef.getLangOpts().requiresStrictPrototypes()) &&
+ "Strict prototypes are required");
NewFD = FunctionDecl::Create(
SemaRef.Context, DC, D.getBeginLoc(), NameInfo, R, TInfo, SC,
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a9cb5fd..c0a2af7 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -5270,8 +5270,11 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
FunctionType::ExtInfo EI(
getCCForDeclaratorChunk(S, D, DeclType.getAttrs(), FTI, chunkIndex));
- if (!FTI.NumParams && !FTI.isVariadic && !LangOpts.CPlusPlus
- && !LangOpts.OpenCL) {
+ // OpenCL disallows functions without a prototype, but it doesn't enforce
+ // strict prototypes as in C2x because it allows a function definition to
+ // have an identifier list. See OpenCL 3.0 6.11/g for more details.
+ if (!FTI.NumParams && !FTI.isVariadic &&
+ !LangOpts.requiresStrictPrototypes() && !LangOpts.OpenCL) {
// Simple void foo(), where the incoming T is the result type.
T = Context.getFunctionNoProtoType(T, EI);
} else {
@@ -5290,8 +5293,10 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
S.Diag(FTI.Params[0].IdentLoc,
diag::err_ident_list_in_fn_declaration);
D.setInvalidType(true);
- // Recover by creating a K&R-style function type.
- T = Context.getFunctionNoProtoType(T, EI);
+ // Recover by creating a K&R-style function type, if possible.
+ T = (!LangOpts.requiresStrictPrototypes() && !LangOpts.OpenCL)
+ ? Context.getFunctionNoProtoType(T, EI)
+ : Context.IntTy;
break;
}
diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
index 6d0b5f8..18c1a65 100644
--- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
+++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
@@ -1033,8 +1033,10 @@ const VarRegion *MemRegionManager::getVarRegion(const VarDecl *D,
T = TSI->getType();
if (T.isNull())
T = getContext().VoidTy;
- if (!T->getAs())
- T = getContext().getFunctionNoProtoType(T);
+ if (!T->getAs()) {
+ FunctionProtoType::ExtProtoInfo Ext;
+ T = getContext().getFunctionType(T, None, Ext);
+ }
T = getContext().getBlockPointerType(T);
const BlockCodeRegion *BTR =
diff --git a/clang/test/Driver/no-knr-functions.c b/clang/test/Driver/no-knr-functions.c
new file mode 100644
index 0000000..99f397d
--- /dev/null
+++ b/clang/test/Driver/no-knr-functions.c
@@ -0,0 +1,11 @@
+// Ensure that we cannot disable strict prototypes, no matter how hard we try.
+
+// RUN: not %clang -fno-no-knr-functions -x c++ %s 2>&1 | FileCheck --check-prefixes=NONO %s
+// RUN: not %clang -fno-no-knr-functions -x c %s 2>&1 | FileCheck --check-prefixes=NONO %s
+// RUN: not %clang -fno-no-knr-functions -std=c89 -x c %s 2>&1 | FileCheck --check-prefixes=NONO %s
+// RUN: not %clang -fknr-functions -x c++ %s 2>&1 | FileCheck --check-prefixes=POS %s
+// RUN: not %clang -fknr-functions -x c %s 2>&1 | FileCheck --check-prefixes=POS %s
+// RUN: not %clang -fknr-functions -std=c89 -x c %s 2>&1 | FileCheck --check-prefixes=POS %s
+
+// NONO: error: unknown argument: '-fno-no-knr-functions'
+// POS: error: unknown argument: '-fknr-functions'
diff --git a/clang/test/Frontend/no-knr-functions.c b/clang/test/Frontend/no-knr-functions.c
new file mode 100644
index 0000000..2330e05
--- /dev/null
+++ b/clang/test/Frontend/no-knr-functions.c
@@ -0,0 +1,11 @@
+// Ensure that we cannot disable strict prototypes, no matter how hard we try.
+
+// RUN: not %clang_cc1 -fno-no-knr-functions -x c++ %s 2>&1 | FileCheck --check-prefixes=NONO %s
+// RUN: not %clang_cc1 -fno-no-knr-functions -x c %s 2>&1 | FileCheck --check-prefixes=NONO %s
+// RUN: not %clang_cc1 -fno-no-knr-functions -std=c89 -x c %s 2>&1 | FileCheck --check-prefixes=NONO %s
+// RUN: not %clang_cc1 -fknr-functions -x c++ %s 2>&1 | FileCheck --check-prefixes=POS %s
+// RUN: not %clang_cc1 -fknr-functions -x c %s 2>&1 | FileCheck --check-prefixes=POS %s
+// RUN: not %clang_cc1 -fknr-functions -std=c89 -x c %s 2>&1 | FileCheck --check-prefixes=POS %s
+
+// NONO: error: unknown argument: '-fno-no-knr-functions'
+// POS: error: unknown argument: '-fknr-functions'
diff --git a/clang/test/Parser/c2x-attributes.c b/clang/test/Parser/c2x-attributes.c
index 1e6e0a8..cffc6cd 100644
--- a/clang/test/Parser/c2x-attributes.c
+++ b/clang/test/Parser/c2x-attributes.c
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -fdouble-square-bracket-attributes -verify -Wno-strict-prototypes %s
-// RUN: %clang_cc1 -fsyntax-only -std=gnu2x -verify -Wno-strict-prototypes %s
+// RUN: %clang_cc1 -fsyntax-only -fdouble-square-bracket-attributes -verify=expected,notc2x -Wno-strict-prototypes %s
+// RUN: %clang_cc1 -fsyntax-only -std=gnu2x -verify=expected,c2x %s
enum [[]] E {
One [[]],
@@ -59,7 +59,10 @@ void f4(void) [[]];
void f5(int i [[]], [[]] int j, int [[]] k);
-void f6(a, b) [[]] int a; int b; { // expected-error {{an attribute list cannot appear here}}
+void f6(a, b) [[]] int a; int b; { // notc2x-error {{an attribute list cannot appear here}} \
+ c2x-warning 2 {{type specifier missing, defaults to 'int'}} \
+ c2x-error {{expected ';' after top level declarator}} \
+ c2x-error {{expected identifier or '('}}
}
// FIXME: technically, an attribute list cannot appear here, but we currently
@@ -67,7 +70,10 @@ void f6(a, b) [[]] int a; int b; { // expected-error {{an attribute list cannot
// behavior given that we *don't* want to parse it as part of the K&R parameter
// declarations. It is disallowed to avoid a parsing ambiguity we already
// handle well.
-int (*f7(a, b))(int, int) [[]] int a; int b; {
+int (*f7(a, b))(int, int) [[]] int a; int b; { // c2x-warning 2 {{type specifier missing, defaults to 'int'}} \
+ c2x-error {{expected ';' after top level declarator}} \
+ c2x-error {{expected identifier or '('}}
+
return 0;
}
diff --git a/clang/test/Parser/c2x-func-prototype.c b/clang/test/Parser/c2x-func-prototype.c
new file mode 100644
index 0000000..ff51945
--- /dev/null
+++ b/clang/test/Parser/c2x-func-prototype.c
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=c2x -std=c2x %s
+// RUN: %clang_cc1 -Wno-strict-prototypes -fsyntax-only -verify -std=c17 %s
+// expected-no-diagnostics
+
+// Functions with an identifier list are not supported in C2x.
+void ident_list(a) // c2x-error {{expected ';' after top level declarator}} \
+ c2x-warning {{type specifier missing, defaults to 'int'}}
+ int a;
+{} // c2x-error {{expected identifier or '('}}
+
+// Functions with an empty parameter list are supported as though the function
+// was declared with a parameter list of (void). Ensure they still parse.
+void no_param_decl();
+void no_param_defn() {}
+void (*var_of_type_with_no_param)();
+typedef void fn();
diff --git a/clang/test/Sema/attr-c2x.c b/clang/test/Sema/attr-c2x.c
index 980dc93..c774cc6 100644
--- a/clang/test/Sema/attr-c2x.c
+++ b/clang/test/Sema/attr-c2x.c
@@ -20,7 +20,7 @@ void context_async_okay(void *context [[clang::swift_async_context]]) [[clang::s
void context_async_okay2(void *context [[clang::swift_async_context]], void *selfType, char **selfWitnessTable) [[clang::swiftasynccall]];
[[clang::ownership_returns(foo)]] void *f1(void);
-[[clang::ownership_returns(foo)]] void *f2(); // expected-warning {{'ownership_returns' attribute only applies to non-K&R-style functions}}
+[[clang::ownership_returns(foo)]] void *f2();
[[clang::unavailable("not available - replaced")]] void foo2(void); // expected-note {{'foo2' has been explicitly marked unavailable here}}
void bar(void) {
diff --git a/clang/test/Sema/c2x-func-prototype.c b/clang/test/Sema/c2x-func-prototype.c
new file mode 100644
index 0000000..07b5d42
--- /dev/null
+++ b/clang/test/Sema/c2x-func-prototype.c
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=c2x -std=c2x %s
+// RUN: %clang_cc1 -Wno-strict-prototypes -fsyntax-only -verify -std=c17 %s
+// RUN: %clang_cc1 -fsyntax-only -fno-knr-functions -std=c99 -verify=c2x %s
+// expected-no-diagnostics
+
+void func(); // c2x-note {{'func' declared here}}
+typedef void (*fp)();
+
+void other_func(int i);
+
+void call(void) {
+ func(1, 2, 3); // c2x-error {{too many arguments to function call, expected 0, have 3}}
+ fp call_me = func;
+ call_me(1, 2, 3); // c2x-error {{too many arguments to function call, expected 0, have 3}}
+
+ fp nope = other_func; // c2x-warning {{incompatible function pointer types initializing 'fp' (aka 'void (*)(void)') with an expression of type 'void (int)'}}
+}
+
+// Ensure these function declarations do not merge in C2x.
+void redecl1(); // c2x-note {{previous declaration is here}}
+void redecl1(int i); // c2x-error {{conflicting types for 'redecl1'}}
+
+void redecl2(int i); // c2x-note {{previous declaration is here}}
+void redecl2(); // c2x-error {{conflicting types for 'redecl2'}}
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index b827d8d..e025388 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -725,7 +725,7 @@ conformance.
Remove support for function definitions with identifier lists |
N2432 |
- No |
+ Clang 15 |
--
2.7.4