https://github.com/al45tair created https://github.com/llvm/llvm-project/pull/97597
This lets clang generate warnings automatically if it sees Objective-C names that don't have the correct prefix length. rdar://131055157 >From 02f7c5ef71b382866e02037ce82c85fa6fcbb394 Mon Sep 17 00:00:00 2001 From: Alastair Houghton <ahough...@apple.com> Date: Wed, 3 Jul 2024 17:00:51 +0100 Subject: [PATCH] [Parser][ObjC] Add -Wobjc-prefix-length warning option. This lets clang generate warnings automatically if it sees Objective-C names that don't have the correct prefix length. rdar://131055157 --- clang/include/clang/Basic/DiagnosticGroups.td | 1 + .../clang/Basic/DiagnosticParseKinds.td | 7 ++ clang/include/clang/Basic/LangOptions.def | 2 + clang/include/clang/Driver/Options.td | 7 ++ clang/include/clang/Parse/Parser.h | 1 + clang/lib/Driver/ToolChains/Clang.cpp | 1 + clang/lib/Parse/ParseObjc.cpp | 65 +++++++++++++++++++ clang/test/Parser/objc-prefixes.m | 62 ++++++++++++++++++ 8 files changed, 146 insertions(+) create mode 100644 clang/test/Parser/objc-prefixes.m diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 9431eea1f6be2..0d944434cda38 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -598,6 +598,7 @@ def ObjCPointerIntrospect : DiagGroup<"deprecated-objc-pointer-introspection", [ def ObjCMultipleMethodNames : DiagGroup<"objc-multiple-method-names">; def ObjCFlexibleArray : DiagGroup<"objc-flexible-array">; def ObjCBoxing : DiagGroup<"objc-boxing">; +def ObjCPrefixLength : DiagGroup<"objc-prefix-length">; def CompletionHandler : DiagGroup<"completion-handler">; def CalledOnceParameter : DiagGroup<"called-once-parameter", [CompletionHandler]>; def OpenCLUnsupportedRGBA: DiagGroup<"opencl-unsupported-rgba">; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 12aab09f28556..7e26bd13f9c7b 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -563,6 +563,13 @@ def err_declaration_does_not_declare_param : Error< "declaration does not declare a parameter">; def err_no_matching_param : Error<"parameter named %0 is missing">; +def warn_objc_unprefixed_class_name : Warning< + "un-prefixed Objective-C class name">, + InGroup<ObjCPrefixLength>; +def warn_objc_unprefixed_protocol_name : Warning< + "un-prefixed Objective-C protocol name">, + InGroup<ObjCPrefixLength>; + /// Objective-C++ parser diagnostics def err_expected_token_instead_of_objcxx_keyword : Error< "expected %0; %1 is a keyword in Objective-C++">; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 491759e2fcdbb..9df19b01f0b22 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -351,6 +351,8 @@ LANGOPT(ObjCAutoRefCount , 1, 0, "Objective-C automated reference counting") LANGOPT(ObjCWeakRuntime , 1, 0, "__weak support in the ARC runtime") LANGOPT(ObjCWeak , 1, 0, "Objective-C __weak in ARC and MRC files") LANGOPT(ObjCSubscriptingLegacyRuntime , 1, 0, "Subscripting support in legacy ObjectiveC runtime") +BENIGN_LANGOPT(ObjCPrefixLength, 32, 0, + "if non-zero, warn about Objective-C classes or protocols with names that do not have a prefix of the specified length.") BENIGN_LANGOPT(CompatibilityQualifiedIdBlockParamTypeChecking, 1, 0, "compatibility mode for type checking block parameters " "involving qualified id types") diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 1ede75d3782cd..829efafd03a2c 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3418,6 +3418,13 @@ defm objc_exceptions : BoolFOption<"objc-exceptions", PosFlag<SetTrue, [], [ClangOption, CC1Option], "Enable Objective-C exceptions">, NegFlag<SetFalse>>; +def Wobjc_prefix_length_EQ: + Joined<["-"], "Wobjc-prefix-length=">, + Visibility<[ClangOption, CC1Option]>, + MetaVarName<"<N>">, + HelpText<"Warn if Objective-C class or protocol names do not start with a prefix of the specified length">, + MarshallingInfoInt<LangOpts<"ObjCPrefixLength">>; + defm application_extension : BoolFOption<"application-extension", LangOpts<"AppExt">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption, CC1Option], diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 6880fa4bb0b03..0e2fdc44321b5 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1704,6 +1704,7 @@ class Parser : public CodeCompletionHandler { // Objective-C External Declarations void MaybeSkipAttributes(tok::ObjCKeywordKind Kind); + bool isValidObjCPublicName(StringRef name); DeclGroupPtrTy ParseObjCAtDirectives(ParsedAttributes &DeclAttrs, ParsedAttributes &DeclSpecAttrs); DeclGroupPtrTy ParseObjCAtClassDeclaration(SourceLocation atLoc); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 27c451c565f2b..c66914b0686c8 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6291,6 +6291,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, } Args.AddAllArgs(CmdArgs, options::OPT_Wsystem_headers_in_module_EQ); + Args.AddLastArg(CmdArgs, options::OPT_Wobjc_prefix_length_EQ); if (Args.hasFlag(options::OPT_pedantic, options::OPT_no_pedantic, false)) CmdArgs.push_back("-pedantic"); diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp index 6a2088a73c55b..006328140d0eb 100644 --- a/clang/lib/Parse/ParseObjc.cpp +++ b/clang/lib/Parse/ParseObjc.cpp @@ -204,6 +204,57 @@ void Parser::CheckNestedObjCContexts(SourceLocation AtLoc) Diag(Decl->getBeginLoc(), diag::note_objc_container_start) << (int)ock; } +/// An Objective-C public name (a class name or protocol name) is +/// expected to have a prefix as all names are in a single global +/// namespace. +/// +/// If the -Wobjc-prefix-length is set to N, the name must start +/// with N+1 capital letters, which must be followed by a character +/// that is not a capital letter. +/// +/// For instance, for N set to 2, the following are valid: +/// +/// NSString +/// NSArray +/// +/// but these are not: +/// +/// MyString +/// NSKString +/// NSnotAString +/// +/// We make a special exception for NSCF things when the prefix is set +/// to length 2, because that's an unusual special case in the implementation +/// of the Cocoa frameworks. +/// +/// Names that start with underscores are exempt from this check, but +/// are reserved for the system and should not be used by user code. +bool Parser::isValidObjCPublicName(StringRef name) { + size_t nameLen = name.size(); + size_t requiredUpperCase = getLangOpts().ObjCPrefixLength + 1; + + if (name.starts_with('_')) + return true; + + // Special case for NSCF when prefix length is 2 + if (requiredUpperCase == 3 && nameLen > 4 && name.starts_with("NSCF") && + isUppercase(name[4]) && (nameLen == 5 || !isUppercase(name[5]))) + return true; + + if (nameLen < requiredUpperCase) + return false; + + for (size_t n = 0; n < requiredUpperCase; ++n) { + if (!isUppercase(name[n])) + return false; + } + + if (nameLen > requiredUpperCase && isUppercase(name[requiredUpperCase])) + return false; + + return true; +} + /// /// objc-interface: /// objc-class-interface-attributes[opt] objc-class-interface @@ -319,6 +370,14 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc, return CategoryType; } + + // Not a category - we are declaring a class + if (getLangOpts().ObjCPrefixLength && + !PP.getSourceManager().isInSystemHeader(nameLoc) && + !isValidObjCPublicName(nameId->getName())) { + Diag(nameLoc, diag::warn_objc_unprefixed_class_name); + } + // Parse a class interface. IdentifierInfo *superClassId = nullptr; SourceLocation superClassLoc; @@ -2081,6 +2140,12 @@ Parser::ParseObjCAtProtocolDeclaration(SourceLocation AtLoc, IdentifierInfo *protocolName = Tok.getIdentifierInfo(); SourceLocation nameLoc = ConsumeToken(); + if (getLangOpts().ObjCPrefixLength && + !PP.getSourceManager().isInSystemHeader(nameLoc) && + !isValidObjCPublicName(protocolName->getName())) { + Diag(nameLoc, diag::warn_objc_unprefixed_protocol_name); + } + if (TryConsumeToken(tok::semi)) { // forward declaration of one protocol. IdentifierLocPair ProtoInfo(protocolName, nameLoc); return Actions.ObjC().ActOnForwardProtocolDeclaration(AtLoc, ProtoInfo, diff --git a/clang/test/Parser/objc-prefixes.m b/clang/test/Parser/objc-prefixes.m new file mode 100644 index 0000000000000..32c8448f93fbe --- /dev/null +++ b/clang/test/Parser/objc-prefixes.m @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 %s -Wobjc-prefix-length=2 -fsyntax-only -verify + +// Test prefix length rules for ObjC interfaces and protocols + +// -- Plain interfaces -------------------------------------------------------- + +@interface _Foo +@end + +@interface Foo // expected-warning {{un-prefixed Objective-C class name}} +@end + +@interface NSFoo +@end + +// Special case for prefix-length 2 +@interface NSCFFoo +@end + +@interface NSCFXFoo // expected-warning {{un-prefixed Objective-C class name}} +@end + +@interface NSXFoo // expected-warning {{un-prefixed Objective-C class name}} +@end + +// -- Categories -------------------------------------------------------------- + +// Categories don't trigger these warnings + +@interface Foo (Bar) +@end + +@interface NSFoo (Bar) +@end + +@interface NSCFFoo (Bar) +@end + +@interface NSXFoo (Bar) +@end + +// -- Protocols --------------------------------------------------------------- + +@protocol _FooProtocol +@end + +@protocol FooProtocol // expected-warning {{un-prefixed Objective-C protocol name}} +@end + +@protocol NSFooProtocol +@end + +// Special case for prefix-length 2 +@protocol NSCFFooProtocol +@end + +@protocol NSCFXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}} +@end + +@protocol NSXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}} +@end + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits