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