[clang] [Parser][ObjC] Add -Wobjc-prefix-length warning option. (PR #97597)
Alastair Houghton via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 18 03:23:49 PDT 2024
https://github.com/al45tair updated https://github.com/llvm/llvm-project/pull/97597
>From c11624370bf5615c7902993052435b92ae50b41b Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Wed, 3 Jul 2024 17:00:51 +0100
Subject: [PATCH 1/8] [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 2241f8481484e..fc09603bf3e1e 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -612,6 +612,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 5ad9c1f24b7c5..7debdc0cdf75a 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -353,6 +353,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 2400b193d4d38..57344b75cebda 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3419,6 +3419,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 93e60be512aae..e3dd9c3d339cd 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 d48b26cc74132..17f7eb44db537 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6242,6 +6242,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 --------------------------------------------------------
+
+ at interface _Foo
+ at end
+
+ at interface Foo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+ at interface NSFoo
+ at end
+
+// Special case for prefix-length 2
+ at interface NSCFFoo
+ at end
+
+ at interface NSCFXFoo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+ at interface NSXFoo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+// -- Categories --------------------------------------------------------------
+
+// Categories don't trigger these warnings
+
+ at interface Foo (Bar)
+ at end
+
+ at interface NSFoo (Bar)
+ at end
+
+ at interface NSCFFoo (Bar)
+ at end
+
+ at interface NSXFoo (Bar)
+ at end
+
+// -- Protocols ---------------------------------------------------------------
+
+ at protocol _FooProtocol
+ at end
+
+ at protocol FooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
+ at protocol NSFooProtocol
+ at end
+
+// Special case for prefix-length 2
+ at protocol NSCFFooProtocol
+ at end
+
+ at protocol NSCFXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
+ at protocol NSXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
>From 7ff3ad2492e2ccb504b959ae4373afa8770c9ec0 Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Wed, 3 Jul 2024 18:14:34 +0100
Subject: [PATCH 2/8] [Parser][ObjC] Add -Wobjc-prefix warning option.
This allows you to specify a comma separated list of allowed prefixes,
as an alternative to the -Wobjc-prefix-length option.
rdar://131055157
---
clang/include/clang/Basic/DiagnosticGroups.td | 1 +
.../clang/Basic/DiagnosticParseKinds.td | 7 +++
clang/include/clang/Basic/LangOptions.def | 2 +-
clang/include/clang/Basic/LangOptions.h | 3 ++
clang/include/clang/Driver/Options.td | 10 +++-
clang/include/clang/Parse/Parser.h | 1 +
clang/lib/Driver/ToolChains/Clang.cpp | 2 +
clang/lib/Parse/ParseObjc.cpp | 50 ++++++++++++++++---
clang/test/Parser/objc-prefixes2.m | 27 ++++++++++
9 files changed, 92 insertions(+), 11 deletions(-)
create mode 100644 clang/test/Parser/objc-prefixes2.m
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index fc09603bf3e1e..f58a5af6807a6 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -612,6 +612,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 ObjCPrefix : DiagGroup<"objc-prefix">;
def ObjCPrefixLength : DiagGroup<"objc-prefix-length">;
def CompletionHandler : DiagGroup<"completion-handler">;
def CalledOnceParameter : DiagGroup<"called-once-parameter", [CompletionHandler]>;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index 7e26bd13f9c7b..ae82bdbff7870 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -570,6 +570,13 @@ def warn_objc_unprefixed_protocol_name : Warning<
"un-prefixed Objective-C protocol name">,
InGroup<ObjCPrefixLength>;
+def warn_objc_bad_class_name_prefix : Warning<
+ "Objective-C class name prefix not in permitted list">,
+ InGroup<ObjCPrefix>;
+def warn_objc_bad_protocol_name_prefix : Warning<
+ "Objective-C protocol name prefix not in permitted list">,
+ InGroup<ObjCPrefix>;
+
/// 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 7debdc0cdf75a..edad911d288f4 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -353,7 +353,7 @@ 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,
+BENIGN_VALUE_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 "
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 91f1c2f2e6239..2a525228597bb 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -499,6 +499,9 @@ class LangOptions : public LangOptionsBase {
std::string ObjCConstantStringClass;
+ /// Allowed prefixes for ObjC classes and protocols
+ std::vector<std::string> ObjCAllowedPrefixes;
+
/// The name of the handler function to be called when -ftrapv is
/// specified.
///
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 57344b75cebda..118e6848ab16d 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3419,11 +3419,17 @@ defm objc_exceptions : BoolFOption<"objc-exceptions",
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Enable Objective-C exceptions">,
NegFlag<SetFalse>>;
+def Wobjc_prefix_EQ:
+ CommaJoined<["-"], "Wobjc-prefix=">, Group<W_value_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ MetaVarName<"<prefixes>">,
+ HelpText<"Warn if Objective-C class or protocol names do not start with any prefix in a comma separated list <prefixes>. Takes precedence over -Wobjc-prefix-length">,
+ MarshallingInfoStringVector<LangOpts<"ObjCAllowedPrefixes">>;
def Wobjc_prefix_length_EQ:
- Joined<["-"], "Wobjc-prefix-length=">,
+ Joined<["-"], "Wobjc-prefix-length=">, Group<W_value_Group>,
Visibility<[ClangOption, CC1Option]>,
MetaVarName<"<N>">,
- HelpText<"Warn if Objective-C class or protocol names do not start with a prefix of the specified length">,
+ HelpText<"Warn if Objective-C class or protocol names do not start with a prefix of the specified length. -Wobjc-prefix takes precedence if set">,
MarshallingInfoInt<LangOpts<"ObjCPrefixLength">>;
defm application_extension : BoolFOption<"application-extension",
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index e3dd9c3d339cd..0c181d601cb87 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 isObjCPublicNamePrefixAllowed(StringRef name);
bool isValidObjCPublicName(StringRef name);
DeclGroupPtrTy ParseObjCAtDirectives(ParsedAttributes &DeclAttrs,
ParsedAttributes &DeclSpecAttrs);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 17f7eb44db537..356b308121d5c 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6242,6 +6242,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
}
Args.AddAllArgs(CmdArgs, options::OPT_Wsystem_headers_in_module_EQ);
+
+ Args.AddAllArgs(CmdArgs, options::OPT_Wobjc_prefix_EQ);
Args.AddLastArg(CmdArgs, options::OPT_Wobjc_prefix_length_EQ);
if (Args.hasFlag(options::OPT_pedantic, options::OPT_no_pedantic, false))
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 006328140d0eb..226b058add0ad 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -208,6 +208,13 @@ void Parser::CheckNestedObjCContexts(SourceLocation AtLoc)
/// expected to have a prefix as all names are in a single global
/// namespace.
///
+/// If the `-Wobjc-prefix=<list>` option is active, it specifies a list
+/// of permitted prefixes; classes and protocols must start with a
+/// prefix from that list. Note that in this case, we check that
+/// the name matches <prefix>(<upper-case><not-upper-case>?)?, so e.g.
+/// if you specify `-Wobjc-prefix=NS`, then `NSURL` would not be valid;
+/// you would want to specify `-Wobjc-prefix=NS,NSURL` in that case.
+///
/// 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.
@@ -229,10 +236,35 @@ void Parser::CheckNestedObjCContexts(SourceLocation AtLoc)
///
/// 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::isObjCPublicNamePrefixAllowed(StringRef name) {
+ size_t nameLen = name.size();
+
+ // If there's nothing in the list, it's allowed
+ if (getLangOpts().ObjCAllowedPrefixes.empty())
+ return true;
+
+ // Otherwise it must start with a list entry, optionally followed by
+ // an uppercase letter and then optionally by something that isn't an
+ // uppercase letter.
+ for (StringRef prefix : getLangOpts().ObjCAllowedPrefixes) {
+ size_t prefixLen = prefix.size();
+ if (nameLen >= prefixLen && name.starts_with(prefix) &&
+ (nameLen == prefixLen || isUppercase(name[prefixLen])) &&
+ (nameLen == prefixLen + 1 || !isUppercase(name[prefixLen + 1])))
+ return true;
+ }
+
+ return false;
+}
+
bool Parser::isValidObjCPublicName(StringRef name) {
size_t nameLen = name.size();
size_t requiredUpperCase = getLangOpts().ObjCPrefixLength + 1;
+ // If ObjCPrefixLength is zero, we do no further checking
+ if (requiredUpperCase == 1)
+ return true;
+
if (name.starts_with('_'))
return true;
@@ -372,10 +404,11 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
}
// 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);
+ if (!PP.getSourceManager().isInSystemHeader(nameLoc)) {
+ if (!isObjCPublicNamePrefixAllowed(nameId->getName()))
+ Diag(nameLoc, diag::warn_objc_bad_class_name_prefix);
+ else if (!isValidObjCPublicName(nameId->getName()))
+ Diag(nameLoc, diag::warn_objc_unprefixed_class_name);
}
// Parse a class interface.
@@ -2140,10 +2173,11 @@ 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 (!PP.getSourceManager().isInSystemHeader(nameLoc)) {
+ if (!isObjCPublicNamePrefixAllowed(protocolName->getName()))
+ Diag(nameLoc, diag::warn_objc_bad_protocol_name_prefix);
+ else if (!isValidObjCPublicName(protocolName->getName()))
+ Diag(nameLoc, diag::warn_objc_unprefixed_protocol_name);
}
if (TryConsumeToken(tok::semi)) { // forward declaration of one protocol.
diff --git a/clang/test/Parser/objc-prefixes2.m b/clang/test/Parser/objc-prefixes2.m
new file mode 100644
index 0000000000000..2d9ba88f65d43
--- /dev/null
+++ b/clang/test/Parser/objc-prefixes2.m
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 %s -Wobjc-prefixes=NS,NSCF,NSURL -fsyntax-only -verify
+
+// Test prefix list rules
+
+ at interface Foo // expected-warning {{Objective-C class name prefix not in permitted list}}
+ at end
+
+ at interface NSFoo
+ at end
+
+ at interface NSfoo // expected-warning {{Objective-C class name prefix not in permitted list}}
+ at end
+
+ at interface NSFFoo // expected-warning {{Objective-C class name prefix not in permitted list}}
+ at end
+
+ at interface NSCFFoo
+ at end
+
+ at interface NSURL
+ at end
+
+ at interface NSURLFoo
+ at end
+
+ at interface NSRGBColor // expected-warning {{Objective-C class name prefix not in permitted list}}
+ at end
>From 3bce47c67f44aaf4b5b321bd54bad066c4a98ccc Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Thu, 4 Jul 2024 12:18:35 +0100
Subject: [PATCH 3/8] [Parser][ObjC] Add -Wobjc-forbidden-prefixes warning
option.
Also rename `-Wobjc-prefix` to `-Wobjc-prefixes`, as that makes more sense,
and fix some capitalization issues.
Additionally, deal with `NSURL` and `NSUUID` and other similar things
in a better way (specifically, for the allow and forbidden lists, we
allow a match of the entire prefix against the name, rather than
requiring additional characters).
rdar://131055157
---
clang/include/clang/Basic/DiagnosticGroups.td | 3 +-
.../clang/Basic/DiagnosticParseKinds.td | 11 +-
clang/include/clang/Basic/LangOptions.h | 3 +
clang/include/clang/Driver/Options.td | 12 +-
clang/include/clang/Parse/Parser.h | 9 +-
clang/lib/Driver/ToolChains/Clang.cpp | 3 +-
clang/lib/Parse/ParseObjc.cpp | 110 +++++++++++-------
clang/test/Parser/objc-prefixes2.m | 11 +-
8 files changed, 109 insertions(+), 53 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index f58a5af6807a6..7f3d4b463a8df 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -612,7 +612,8 @@ 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 ObjCPrefix : DiagGroup<"objc-prefix">;
+def ObjCPrefixes : DiagGroup<"objc-prefixes">;
+def ObjCForbiddenPrefixes : DiagGroup<"objc-forbidden-prefixes">;
def ObjCPrefixLength : DiagGroup<"objc-prefix-length">;
def CompletionHandler : DiagGroup<"completion-handler">;
def CalledOnceParameter : DiagGroup<"called-once-parameter", [CompletionHandler]>;
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index ae82bdbff7870..ff0c33488ea53 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -572,10 +572,17 @@ def warn_objc_unprefixed_protocol_name : Warning<
def warn_objc_bad_class_name_prefix : Warning<
"Objective-C class name prefix not in permitted list">,
- InGroup<ObjCPrefix>;
+ InGroup<ObjCPrefixes>;
def warn_objc_bad_protocol_name_prefix : Warning<
"Objective-C protocol name prefix not in permitted list">,
- InGroup<ObjCPrefix>;
+ InGroup<ObjCPrefixes>;
+
+def warn_objc_forbidden_class_name_prefix : Warning<
+ "Objective-C class name prefix in forbidden list">,
+ InGroup<ObjCForbiddenPrefixes>;
+def warn_objc_forbidden_protocol_name_prefix : Warning<
+ "Objective-C protocol name prefix in forbidden list">,
+ InGroup<ObjCForbiddenPrefixes>;
/// Objective-C++ parser diagnostics
def err_expected_token_instead_of_objcxx_keyword : Error<
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 2a525228597bb..932bacc27d8cb 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -502,6 +502,9 @@ class LangOptions : public LangOptionsBase {
/// Allowed prefixes for ObjC classes and protocols
std::vector<std::string> ObjCAllowedPrefixes;
+ /// Forbidden prefixes for ObjC classes and protocols
+ std::vector<std::string> ObjCForbiddenPrefixes;
+
/// The name of the handler function to be called when -ftrapv is
/// specified.
///
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 118e6848ab16d..3e3238aca1266 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3419,17 +3419,23 @@ defm objc_exceptions : BoolFOption<"objc-exceptions",
PosFlag<SetTrue, [], [ClangOption, CC1Option],
"Enable Objective-C exceptions">,
NegFlag<SetFalse>>;
-def Wobjc_prefix_EQ:
- CommaJoined<["-"], "Wobjc-prefix=">, Group<W_value_Group>,
+def Wobjc_prefixes_EQ:
+ CommaJoined<["-"], "Wobjc-prefixes=">, Group<W_value_Group>,
Visibility<[ClangOption, CC1Option]>,
MetaVarName<"<prefixes>">,
HelpText<"Warn if Objective-C class or protocol names do not start with any prefix in a comma separated list <prefixes>. Takes precedence over -Wobjc-prefix-length">,
MarshallingInfoStringVector<LangOpts<"ObjCAllowedPrefixes">>;
+def Wobjc_forbidden_prefixes_EQ:
+ Joined<["-"], "Wobjc-forbidden-prefixes=">, Group<W_value_Group>,
+ Visibility<[ClangOption, CC1Option]>,
+ MetaVarName<"<prefixes>">,
+ HelpText<"Warn if Objective-C class or protocol names start with any prefix in a comma separated list <prefixes>. Takes precedence over both -Wobjc-prefixes and -Wobjc-prefix-length">,
+ MarshallingInfoStringVector<LangOpts<"ObjCForbiddenPrefixes">>;
def Wobjc_prefix_length_EQ:
Joined<["-"], "Wobjc-prefix-length=">, Group<W_value_Group>,
Visibility<[ClangOption, CC1Option]>,
MetaVarName<"<N>">,
- HelpText<"Warn if Objective-C class or protocol names do not start with a prefix of the specified length. -Wobjc-prefix takes precedence if set">,
+ 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",
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 0c181d601cb87..206124892b6bf 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1704,8 +1704,13 @@ class Parser : public CodeCompletionHandler {
// Objective-C External Declarations
void MaybeSkipAttributes(tok::ObjCKeywordKind Kind);
- bool isObjCPublicNamePrefixAllowed(StringRef name);
- bool isValidObjCPublicName(StringRef name);
+ enum ObjCPublicNameValidationResult {
+ ObjCNameUnprefixed = 0,
+ ObjCNameForbidden = -2,
+ ObjCNameNotAllowed = -1,
+ ObjCNameAllowed = 1
+ };
+ ObjCPublicNameValidationResult ValidateObjCPublicName(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 356b308121d5c..2d6bb2bf902c6 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6243,7 +6243,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddAllArgs(CmdArgs, options::OPT_Wsystem_headers_in_module_EQ);
- Args.AddAllArgs(CmdArgs, options::OPT_Wobjc_prefix_EQ);
+ Args.AddAllArgs(CmdArgs, options::OPT_Wobjc_prefixes_EQ);
+ Args.AddAllArgs(CmdArgs, options::OPT_Wobjc_forbidden_prefixes_EQ);
Args.AddLastArg(CmdArgs, options::OPT_Wobjc_prefix_length_EQ);
if (Args.hasFlag(options::OPT_pedantic, options::OPT_no_pedantic, false))
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 226b058add0ad..37795fbaadd59 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -236,55 +236,61 @@ void Parser::CheckNestedObjCContexts(SourceLocation AtLoc)
///
/// 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::isObjCPublicNamePrefixAllowed(StringRef name) {
- size_t nameLen = name.size();
- // If there's nothing in the list, it's allowed
- if (getLangOpts().ObjCAllowedPrefixes.empty())
- return true;
+static inline bool ObjCNameMatchesPrefix(StringRef Name, StringRef Prefix) {
+ size_t NameLen = Name.size();
+ size_t PrefixLen = Prefix.size();
+ return (NameLen >= PrefixLen && Name.starts_with(Prefix) &&
+ (NameLen == PrefixLen || isUppercase(Name[PrefixLen])) &&
+ (NameLen == PrefixLen + 1 || !isUppercase(Name[PrefixLen + 1])));
+}
- // Otherwise it must start with a list entry, optionally followed by
- // an uppercase letter and then optionally by something that isn't an
- // uppercase letter.
- for (StringRef prefix : getLangOpts().ObjCAllowedPrefixes) {
- size_t prefixLen = prefix.size();
- if (nameLen >= prefixLen && name.starts_with(prefix) &&
- (nameLen == prefixLen || isUppercase(name[prefixLen])) &&
- (nameLen == prefixLen + 1 || !isUppercase(name[prefixLen + 1])))
- return true;
+Parser::ObjCPublicNameValidationResult
+Parser::ValidateObjCPublicName(StringRef Name) {
+ // Check the -Wobjc-forbidden-prefixes list
+ if (!getLangOpts().ObjCForbiddenPrefixes.empty()) {
+ for (StringRef Prefix : getLangOpts().ObjCForbiddenPrefixes) {
+ if (ObjCNameMatchesPrefix(Name, Prefix))
+ return ObjCNameForbidden;
+ }
}
- return false;
-}
+ // Check the -Wobjc-prefixes list
+ if (!getLangOpts().ObjCAllowedPrefixes.empty()) {
+ for (StringRef Prefix : getLangOpts().ObjCAllowedPrefixes) {
+ if (ObjCNameMatchesPrefix(Name, Prefix))
+ return ObjCNameAllowed;
+ }
-bool Parser::isValidObjCPublicName(StringRef name) {
- size_t nameLen = name.size();
- size_t requiredUpperCase = getLangOpts().ObjCPrefixLength + 1;
+ return ObjCNameNotAllowed;
+ }
- // If ObjCPrefixLength is zero, we do no further checking
- if (requiredUpperCase == 1)
- return true;
+ // Finally, check against the -Wobjc-prefix-length setting
+ if (getLangOpts().ObjCPrefixLength) {
+ size_t NameLen = Name.size();
+ size_t RequiredUpperCase = getLangOpts().ObjCPrefixLength + 1;
- if (name.starts_with('_'))
- return true;
+ if (Name.starts_with('_'))
+ return ObjCNameAllowed;
- // 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;
+ // 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 ObjCNameAllowed;
- if (nameLen < requiredUpperCase)
- return false;
+ if (NameLen < RequiredUpperCase)
+ return ObjCNameUnprefixed;
- for (size_t n = 0; n < requiredUpperCase; ++n) {
- if (!isUppercase(name[n]))
- return false;
- }
+ for (size_t N = 0; N < RequiredUpperCase; ++N) {
+ if (!isUppercase(Name[N]))
+ return ObjCNameUnprefixed;
+ }
- if (nameLen > requiredUpperCase && isUppercase(name[requiredUpperCase]))
- return false;
+ if (NameLen > RequiredUpperCase && isUppercase(Name[RequiredUpperCase]))
+ return ObjCNameUnprefixed;
+ }
- return true;
+ return ObjCNameAllowed;
}
///
@@ -405,10 +411,19 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
// Not a category - we are declaring a class
if (!PP.getSourceManager().isInSystemHeader(nameLoc)) {
- if (!isObjCPublicNamePrefixAllowed(nameId->getName()))
- Diag(nameLoc, diag::warn_objc_bad_class_name_prefix);
- else if (!isValidObjCPublicName(nameId->getName()))
+ switch (ValidateObjCPublicName(nameId->getName())) {
+ case ObjCNameUnprefixed:
Diag(nameLoc, diag::warn_objc_unprefixed_class_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(nameLoc, diag::warn_objc_bad_class_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(nameLoc, diag::warn_objc_forbidden_class_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
}
// Parse a class interface.
@@ -2174,10 +2189,19 @@ Parser::ParseObjCAtProtocolDeclaration(SourceLocation AtLoc,
SourceLocation nameLoc = ConsumeToken();
if (!PP.getSourceManager().isInSystemHeader(nameLoc)) {
- if (!isObjCPublicNamePrefixAllowed(protocolName->getName()))
- Diag(nameLoc, diag::warn_objc_bad_protocol_name_prefix);
- else if (!isValidObjCPublicName(protocolName->getName()))
+ switch (ValidateObjCPublicName(protocolName->getName())) {
+ case ObjCNameUnprefixed:
Diag(nameLoc, diag::warn_objc_unprefixed_protocol_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(nameLoc, diag::warn_objc_bad_protocol_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(nameLoc, diag::warn_objc_forbidden_protocol_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
}
if (TryConsumeToken(tok::semi)) { // forward declaration of one protocol.
diff --git a/clang/test/Parser/objc-prefixes2.m b/clang/test/Parser/objc-prefixes2.m
index 2d9ba88f65d43..fc94b15663dcf 100644
--- a/clang/test/Parser/objc-prefixes2.m
+++ b/clang/test/Parser/objc-prefixes2.m
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 %s -Wobjc-prefixes=NS,NSCF,NSURL -fsyntax-only -verify
+// RUN: %clang_cc1 %s -Wobjc-prefixes=NS,NSCF,NSURL -Wobjc-forbidden-prefixes=XX -fsyntax-only -verify
// Test prefix list rules
@@ -25,3 +25,12 @@ @interface NSURLFoo
@interface NSRGBColor // expected-warning {{Objective-C class name prefix not in permitted list}}
@end
+
+ at protocol NSRGBColorProtocol // expected-warning {{Objective-C protocol name prefix not in permitted list}}
+ at end
+
+ at interface XXFoo // expected-warning {{Objective-C class name prefix in forbidden list}}
+ at end
+
+ at protocol XXFooProtocol // expected-warning {{Objective-C protocol name prefix in forbidden list}}
+ at end
>From 25a8eff5fb790438f7d96e59bfdca22781e032e0 Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Thu, 4 Jul 2024 14:54:45 +0100
Subject: [PATCH 4/8] [Parser][ObjC] Just add the last copy of each argument.
We probably should just add the last copy of the `-Wobjc-prefix` argument(s).
rdar://131055157
---
clang/lib/Driver/ToolChains/Clang.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 2d6bb2bf902c6..a423fdcee8de0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6243,8 +6243,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.AddAllArgs(CmdArgs, options::OPT_Wsystem_headers_in_module_EQ);
- Args.AddAllArgs(CmdArgs, options::OPT_Wobjc_prefixes_EQ);
- Args.AddAllArgs(CmdArgs, options::OPT_Wobjc_forbidden_prefixes_EQ);
+ Args.AddLastArg(CmdArgs, options::OPT_Wobjc_prefixes_EQ);
+ Args.AddLastArg(CmdArgs, options::OPT_Wobjc_forbidden_prefixes_EQ);
Args.AddLastArg(CmdArgs, options::OPT_Wobjc_prefix_length_EQ);
if (Args.hasFlag(options::OPT_pedantic, options::OPT_no_pedantic, false))
>From ae6339280594a4253c842278cdf3d1cfa807bcc3 Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Thu, 4 Jul 2024 17:03:14 +0100
Subject: [PATCH 5/8] [Parser][ObjC] Don't use the W_value_Group for
-Wobjc-prefix*
If we use `W_value_Group`, we end up adding extra arguments from
`clang::ParseDiagnosticArgs`, which results in argument round trip
failure.
rdar://131055157
---
clang/include/clang/Driver/Options.td | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 3e3238aca1266..f50a7b24fbcbe 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3420,19 +3420,19 @@ defm objc_exceptions : BoolFOption<"objc-exceptions",
"Enable Objective-C exceptions">,
NegFlag<SetFalse>>;
def Wobjc_prefixes_EQ:
- CommaJoined<["-"], "Wobjc-prefixes=">, Group<W_value_Group>,
+ CommaJoined<["-"], "Wobjc-prefixes=">,
Visibility<[ClangOption, CC1Option]>,
MetaVarName<"<prefixes>">,
HelpText<"Warn if Objective-C class or protocol names do not start with any prefix in a comma separated list <prefixes>. Takes precedence over -Wobjc-prefix-length">,
MarshallingInfoStringVector<LangOpts<"ObjCAllowedPrefixes">>;
def Wobjc_forbidden_prefixes_EQ:
- Joined<["-"], "Wobjc-forbidden-prefixes=">, Group<W_value_Group>,
+ Joined<["-"], "Wobjc-forbidden-prefixes=">,
Visibility<[ClangOption, CC1Option]>,
MetaVarName<"<prefixes>">,
HelpText<"Warn if Objective-C class or protocol names start with any prefix in a comma separated list <prefixes>. Takes precedence over both -Wobjc-prefixes and -Wobjc-prefix-length">,
MarshallingInfoStringVector<LangOpts<"ObjCForbiddenPrefixes">>;
def Wobjc_prefix_length_EQ:
- Joined<["-"], "Wobjc-prefix-length=">, Group<W_value_Group>,
+ 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">,
>From 07ea1ee1a76f8eb31a8d0d979dba3e9f13b80817 Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Thu, 4 Jul 2024 17:46:40 +0100
Subject: [PATCH 6/8] [Parser][ObjC] Fix an off-by-one bug.
Don't try to read off the end of the `StringRef` for the name in
`ObjCNameMatchesPrefix()`.
rdar://131055157
---
clang/lib/Parse/ParseObjc.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 37795fbaadd59..4d0f6b41832ef 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -241,8 +241,8 @@ static inline bool ObjCNameMatchesPrefix(StringRef Name, StringRef Prefix) {
size_t NameLen = Name.size();
size_t PrefixLen = Prefix.size();
return (NameLen >= PrefixLen && Name.starts_with(Prefix) &&
- (NameLen == PrefixLen || isUppercase(Name[PrefixLen])) &&
- (NameLen == PrefixLen + 1 || !isUppercase(Name[PrefixLen + 1])));
+ (NameLen <= PrefixLen || isUppercase(Name[PrefixLen])) &&
+ (NameLen <= PrefixLen + 1 || !isUppercase(Name[PrefixLen + 1])));
}
Parser::ObjCPublicNameValidationResult
>From 995f0f7793411e88920f6d41569ed30691aabbbc Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Tue, 16 Jul 2024 18:36:35 +0100
Subject: [PATCH 7/8] [Sema][ObjC] Move new logic from Parser to Sema.
We should really do these checks in Sema, not in the parser (it's possible
the parser might be used by something that doesn't care about these rules).
Also, add warnings for the case where a method is being added using a
category on a class that isn't one of the ones allowed by the class name
rules; in that case, it's OK to add the method, but you need to prefix
its name.
rdar://131055157
---
.../clang/Basic/DiagnosticParseKinds.td | 21 --
.../clang/Basic/DiagnosticSemaKinds.td | 31 +++
clang/include/clang/Parse/Parser.h | 7 -
clang/include/clang/Sema/SemaObjC.h | 9 +
clang/lib/Parse/ParseObjc.cpp | 122 ---------
clang/lib/Sema/SemaDeclObjC.cpp | 243 ++++++++++++++++++
clang/lib/Sema/SemaObjCProperty.cpp | 45 +++-
clang/test/Parser/objc-prefixes.m | 62 -----
clang/test/SemaObjC/prefixes.m | 129 ++++++++++
.../objc-prefixes2.m => SemaObjC/prefixes2.m} | 17 ++
10 files changed, 473 insertions(+), 213 deletions(-)
delete mode 100644 clang/test/Parser/objc-prefixes.m
create mode 100644 clang/test/SemaObjC/prefixes.m
rename clang/test/{Parser/objc-prefixes2.m => SemaObjC/prefixes2.m} (51%)
diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td
index ff0c33488ea53..12aab09f28556 100644
--- a/clang/include/clang/Basic/DiagnosticParseKinds.td
+++ b/clang/include/clang/Basic/DiagnosticParseKinds.td
@@ -563,27 +563,6 @@ 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>;
-
-def warn_objc_bad_class_name_prefix : Warning<
- "Objective-C class name prefix not in permitted list">,
- InGroup<ObjCPrefixes>;
-def warn_objc_bad_protocol_name_prefix : Warning<
- "Objective-C protocol name prefix not in permitted list">,
- InGroup<ObjCPrefixes>;
-
-def warn_objc_forbidden_class_name_prefix : Warning<
- "Objective-C class name prefix in forbidden list">,
- InGroup<ObjCForbiddenPrefixes>;
-def warn_objc_forbidden_protocol_name_prefix : Warning<
- "Objective-C protocol name prefix in forbidden list">,
- InGroup<ObjCForbiddenPrefixes>;
-
/// 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/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 52ff4b026a60e..4e0c9f0fa2d06 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1419,6 +1419,37 @@ def warn_objc_property_retain_of_block : Warning<
def warn_objc_readonly_property_has_setter : Warning<
"setter cannot be specified for a readonly property">,
InGroup<ObjCReadonlyPropertyHasSetter>;
+
+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>;
+def warn_objc_unprefixed_category_method_name : Warning<
+ "un-prefixed Objective-C method name on category">,
+ InGroup<ObjCPrefixLength>;
+
+def warn_objc_bad_class_name_prefix : Warning<
+ "Objective-C class name prefix not in permitted list">,
+ InGroup<ObjCPrefixes>;
+def warn_objc_bad_protocol_name_prefix : Warning<
+ "Objective-C protocol name prefix not in permitted list">,
+ InGroup<ObjCPrefixes>;
+def warn_objc_bad_category_method_name_prefix : Warning<
+ "Objective-C category method name prefix not in permitted list">,
+ InGroup<ObjCPrefixes>;
+
+def warn_objc_forbidden_class_name_prefix : Warning<
+ "Objective-C class name prefix in forbidden list">,
+ InGroup<ObjCForbiddenPrefixes>;
+def warn_objc_forbidden_protocol_name_prefix : Warning<
+ "Objective-C protocol name prefix in forbidden list">,
+ InGroup<ObjCForbiddenPrefixes>;
+def warn_objc_forbidden_category_method_name_prefix : Warning<
+ "Objective-C category method name prefix in forbidden list">,
+ InGroup<ObjCForbiddenPrefixes>;
+
def warn_atomic_property_rule : Warning<
"writable atomic property %0 cannot pair a synthesized %select{getter|setter}1 "
"with a user defined %select{getter|setter}2">,
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 206124892b6bf..93e60be512aae 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -1704,13 +1704,6 @@ class Parser : public CodeCompletionHandler {
// Objective-C External Declarations
void MaybeSkipAttributes(tok::ObjCKeywordKind Kind);
- enum ObjCPublicNameValidationResult {
- ObjCNameUnprefixed = 0,
- ObjCNameForbidden = -2,
- ObjCNameNotAllowed = -1,
- ObjCNameAllowed = 1
- };
- ObjCPublicNameValidationResult ValidateObjCPublicName(StringRef Name);
DeclGroupPtrTy ParseObjCAtDirectives(ParsedAttributes &DeclAttrs,
ParsedAttributes &DeclSpecAttrs);
DeclGroupPtrTy ParseObjCAtClassDeclaration(SourceLocation atLoc);
diff --git a/clang/include/clang/Sema/SemaObjC.h b/clang/include/clang/Sema/SemaObjC.h
index 07c3c1a06be16..cd9961070d8bc 100644
--- a/clang/include/clang/Sema/SemaObjC.h
+++ b/clang/include/clang/Sema/SemaObjC.h
@@ -51,6 +51,15 @@ class SemaObjC : public SemaBase {
public:
SemaObjC(Sema &S);
+ enum ObjCNameValidationResult {
+ ObjCNameUnprefixed = 0,
+ ObjCNameForbidden = -2,
+ ObjCNameNotAllowed = -1,
+ ObjCNameAllowed = 1
+ };
+ ObjCNameValidationResult ValidateObjCPublicName(StringRef Name);
+ ObjCNameValidationResult ValidateObjCForeignCategorySelector(Selector Sel);
+
ExprResult CheckObjCForCollectionOperand(SourceLocation forLoc,
Expr *collection);
StmtResult ActOnObjCForCollectionStmt(SourceLocation ForColLoc, Stmt *First,
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 4d0f6b41832ef..d2f70ce468666 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -204,95 +204,6 @@ 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=<list>` option is active, it specifies a list
-/// of permitted prefixes; classes and protocols must start with a
-/// prefix from that list. Note that in this case, we check that
-/// the name matches <prefix>(<upper-case><not-upper-case>?)?, so e.g.
-/// if you specify `-Wobjc-prefix=NS`, then `NSURL` would not be valid;
-/// you would want to specify `-Wobjc-prefix=NS,NSURL` in that case.
-///
-/// 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.
-
-static inline bool ObjCNameMatchesPrefix(StringRef Name, StringRef Prefix) {
- size_t NameLen = Name.size();
- size_t PrefixLen = Prefix.size();
- return (NameLen >= PrefixLen && Name.starts_with(Prefix) &&
- (NameLen <= PrefixLen || isUppercase(Name[PrefixLen])) &&
- (NameLen <= PrefixLen + 1 || !isUppercase(Name[PrefixLen + 1])));
-}
-
-Parser::ObjCPublicNameValidationResult
-Parser::ValidateObjCPublicName(StringRef Name) {
- // Check the -Wobjc-forbidden-prefixes list
- if (!getLangOpts().ObjCForbiddenPrefixes.empty()) {
- for (StringRef Prefix : getLangOpts().ObjCForbiddenPrefixes) {
- if (ObjCNameMatchesPrefix(Name, Prefix))
- return ObjCNameForbidden;
- }
- }
-
- // Check the -Wobjc-prefixes list
- if (!getLangOpts().ObjCAllowedPrefixes.empty()) {
- for (StringRef Prefix : getLangOpts().ObjCAllowedPrefixes) {
- if (ObjCNameMatchesPrefix(Name, Prefix))
- return ObjCNameAllowed;
- }
-
- return ObjCNameNotAllowed;
- }
-
- // Finally, check against the -Wobjc-prefix-length setting
- if (getLangOpts().ObjCPrefixLength) {
- size_t NameLen = Name.size();
- size_t RequiredUpperCase = getLangOpts().ObjCPrefixLength + 1;
-
- if (Name.starts_with('_'))
- return ObjCNameAllowed;
-
- // 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 ObjCNameAllowed;
-
- if (NameLen < RequiredUpperCase)
- return ObjCNameUnprefixed;
-
- for (size_t N = 0; N < RequiredUpperCase; ++N) {
- if (!isUppercase(Name[N]))
- return ObjCNameUnprefixed;
- }
-
- if (NameLen > RequiredUpperCase && isUppercase(Name[RequiredUpperCase]))
- return ObjCNameUnprefixed;
- }
-
- return ObjCNameAllowed;
-}
-
///
/// objc-interface:
/// objc-class-interface-attributes[opt] objc-class-interface
@@ -409,23 +320,6 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
return CategoryType;
}
- // Not a category - we are declaring a class
- if (!PP.getSourceManager().isInSystemHeader(nameLoc)) {
- switch (ValidateObjCPublicName(nameId->getName())) {
- case ObjCNameUnprefixed:
- Diag(nameLoc, diag::warn_objc_unprefixed_class_name);
- break;
- case ObjCNameNotAllowed:
- Diag(nameLoc, diag::warn_objc_bad_class_name_prefix);
- break;
- case ObjCNameForbidden:
- Diag(nameLoc, diag::warn_objc_forbidden_class_name_prefix);
- break;
- case ObjCNameAllowed:
- break;
- }
- }
-
// Parse a class interface.
IdentifierInfo *superClassId = nullptr;
SourceLocation superClassLoc;
@@ -2188,22 +2082,6 @@ Parser::ParseObjCAtProtocolDeclaration(SourceLocation AtLoc,
IdentifierInfo *protocolName = Tok.getIdentifierInfo();
SourceLocation nameLoc = ConsumeToken();
- if (!PP.getSourceManager().isInSystemHeader(nameLoc)) {
- switch (ValidateObjCPublicName(protocolName->getName())) {
- case ObjCNameUnprefixed:
- Diag(nameLoc, diag::warn_objc_unprefixed_protocol_name);
- break;
- case ObjCNameNotAllowed:
- Diag(nameLoc, diag::warn_objc_bad_protocol_name_prefix);
- break;
- case ObjCNameForbidden:
- Diag(nameLoc, diag::warn_objc_forbidden_protocol_name_prefix);
- break;
- case ObjCNameAllowed:
- break;
- }
- }
-
if (TryConsumeToken(tok::semi)) { // forward declaration of one protocol.
IdentifierLocPair ProtoInfo(protocolName, nameLoc);
return Actions.ObjC().ActOnForwardProtocolDeclaration(AtLoc, ProtoInfo,
diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp
index 807453400abdd..3bafd115059d1 100644
--- a/clang/lib/Sema/SemaDeclObjC.cpp
+++ b/clang/lib/Sema/SemaDeclObjC.cpp
@@ -33,6 +33,188 @@
using namespace clang;
+/// 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-prefixes=<list>` option is active, it specifies a list
+/// of permitted prefixes; classes and protocols must start with a
+/// prefix from that list. Note that in this case, we check that
+/// the name matches <prefix>(<upper-case><not-upper-case>?)?, so e.g.
+/// if you specify `-Wobjc-prefixes=NS`, then `NSURL` would not be valid;
+/// you would want to specify `-Wobjc-prefixes=NS,NSURL` in that case.
+///
+/// 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.
+///
+/// Underscore prefixes are stripped from the name before looking for the
+/// prefix. This is to avoid people thinking that they can put an underscore
+/// on the front "to make the name private", as it does no such thing.
+
+static inline bool ObjCNameMatchesPrefix(StringRef Name, StringRef Prefix) {
+ size_t NameLen = Name.size();
+ size_t PrefixLen = Prefix.size();
+ return (NameLen >= PrefixLen && Name.starts_with(Prefix) &&
+ (NameLen <= PrefixLen || isUppercase(Name[PrefixLen])) &&
+ (NameLen <= PrefixLen + 1 || !isUppercase(Name[PrefixLen + 1])));
+}
+
+SemaObjC::ObjCNameValidationResult
+SemaObjC::ValidateObjCPublicName(StringRef Name) {
+ // Ignore leading underscores
+ Name = Name.ltrim('_');
+
+ // Check the -Wobjc-forbidden-prefixes list
+ if (!getLangOpts().ObjCForbiddenPrefixes.empty()) {
+ for (StringRef Prefix : getLangOpts().ObjCForbiddenPrefixes) {
+ if (ObjCNameMatchesPrefix(Name, Prefix))
+ return ObjCNameForbidden;
+ }
+ }
+
+ // Check the -Wobjc-prefixes list
+ if (!getLangOpts().ObjCAllowedPrefixes.empty()) {
+ for (StringRef Prefix : getLangOpts().ObjCAllowedPrefixes) {
+ if (ObjCNameMatchesPrefix(Name, Prefix))
+ return ObjCNameAllowed;
+ }
+
+ return ObjCNameNotAllowed;
+ }
+
+ // Finally, check against the -Wobjc-prefix-length setting
+ if (getLangOpts().ObjCPrefixLength) {
+ size_t NameLen = Name.size();
+ size_t RequiredUpperCase = getLangOpts().ObjCPrefixLength + 1;
+
+ // 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 ObjCNameAllowed;
+
+ if (NameLen < RequiredUpperCase)
+ return ObjCNameUnprefixed;
+
+ for (size_t N = 0; N < RequiredUpperCase; ++N) {
+ if (!isUppercase(Name[N]))
+ return ObjCNameUnprefixed;
+ }
+
+ if (NameLen > RequiredUpperCase && isUppercase(Name[RequiredUpperCase]))
+ return ObjCNameUnprefixed;
+ }
+
+ return ObjCNameAllowed;
+}
+
+/// For categories on Objective-C classes whose names do not match an
+/// allowed prefix, method selectors must be prefixed to avoid collisions.
+///
+/// As with class and protocol names, the set of allowed prefixes is
+/// controlled by the `-Wobjc-prefixes=<list>`, `-Wobjc-prefix-length` and
+/// `-Wobjc-forbidden-prefixes=<list>` options.
+///
+/// Method name prefixes are expected to start with a valid prefix followed
+/// by a non-uppercase character. For example, with `-Wobjc-prefix-length`
+/// set to 2, the following are valid:
+///
+/// NShasAnEvenNumberOfVowels
+/// NS_consistsOnlyOfConsonants
+/// NS_findSecondToLastOccurrenceOf:inReverse:
+///
+/// but these are not:
+///
+/// :
+/// MySHA256Hash
+/// NSOhNoYouDont
+///
+/// As with classes and protocols, leading underscores are ignored when
+/// examining the selector.
+///
+/// Additionally, the prefix "set" is ignored; the reasoning behind this is
+/// that a property
+///
+/// @property int NSfoo;
+///
+/// should be allowed in a category, and that will generate the getter and
+/// setter as follows:
+///
+/// - (int)NSfoo;
+/// - (void)setNSfoo:(int)n;
+
+static inline bool ObjCSelNameMatchesPrefix(StringRef Name, StringRef Prefix) {
+ size_t NameLen = Name.size();
+ size_t PrefixLen = Prefix.size();
+ return (NameLen >= PrefixLen && Name.starts_with(Prefix) &&
+ (NameLen <= PrefixLen || !isUppercase(Name[PrefixLen])));
+}
+
+SemaObjC::ObjCNameValidationResult
+SemaObjC::ValidateObjCForeignCategorySelector(Selector Sel) {
+ StringRef Name = Sel.getNameForSlot(0).ltrim('_');
+
+ // Ignore any "set" prefix
+ Name.consume_front("set");
+
+ // Check the -Wobjc-forbidden-prefixes list
+ if (!getLangOpts().ObjCForbiddenPrefixes.empty()) {
+ for (StringRef Prefix : getLangOpts().ObjCForbiddenPrefixes) {
+ if (ObjCSelNameMatchesPrefix(Name, Prefix))
+ return ObjCNameForbidden;
+ }
+ }
+
+ // Check the -Wobjc-prefixes list
+ if (!getLangOpts().ObjCAllowedPrefixes.empty()) {
+ for (StringRef Prefix : getLangOpts().ObjCAllowedPrefixes) {
+ if (ObjCSelNameMatchesPrefix(Name, Prefix))
+ return ObjCNameAllowed;
+ }
+
+ return ObjCNameNotAllowed;
+ }
+
+ // Finally, check against the -Wobjc-prefix-length setting
+ if (getLangOpts().ObjCPrefixLength) {
+ size_t NameLen = Name.size();
+ size_t RequiredUpperCase = getLangOpts().ObjCPrefixLength;
+
+ // Special case for NSCF when prefix length is 2
+ if (RequiredUpperCase == 2 && NameLen > 4 && Name.starts_with("NSCF") &&
+ !isUppercase(Name[4]))
+ return ObjCNameAllowed;
+
+ if (NameLen < RequiredUpperCase)
+ return ObjCNameUnprefixed;
+
+ for (size_t N = 0; N < RequiredUpperCase; ++N) {
+ if (!isUppercase(Name[N]))
+ return ObjCNameUnprefixed;
+ }
+
+ if (NameLen > RequiredUpperCase && isUppercase(Name[RequiredUpperCase]))
+ return ObjCNameUnprefixed;
+ }
+
+ return ObjCNameAllowed;
+}
+
/// Check whether the given method, which must be in the 'init'
/// family, is a valid member of that family.
///
@@ -992,6 +1174,23 @@ ObjCInterfaceDecl *SemaObjC::ActOnStartClassInterface(
Diag(PrevDecl->getLocation(), diag::note_previous_definition);
}
+ // Check the name to make sure it has the required prefix.
+ if (!SemaRef.getSourceManager().isInSystemHeader(ClassLoc)) {
+ switch (ValidateObjCPublicName(ClassName->getName())) {
+ case ObjCNameUnprefixed:
+ Diag(ClassLoc, diag::warn_objc_unprefixed_class_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(ClassLoc, diag::warn_objc_bad_class_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(ClassLoc, diag::warn_objc_forbidden_class_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
+ }
+
// Create a declaration to describe this @interface.
ObjCInterfaceDecl* PrevIDecl = dyn_cast_or_null<ObjCInterfaceDecl>(PrevDecl);
@@ -1222,6 +1421,24 @@ ObjCProtocolDecl *SemaObjC::ActOnStartProtocolInterface(
bool err = false;
// FIXME: Deal with AttrList.
assert(ProtocolName && "Missing protocol identifier");
+
+ // Check the name to make sure it has the required prefix.
+ if (!SemaRef.getSourceManager().isInSystemHeader(ProtocolLoc)) {
+ switch (ValidateObjCPublicName(ProtocolName->getName())) {
+ case ObjCNameUnprefixed:
+ Diag(ProtocolLoc, diag::warn_objc_unprefixed_protocol_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(ProtocolLoc, diag::warn_objc_bad_protocol_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(ProtocolLoc, diag::warn_objc_forbidden_protocol_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
+ }
+
ObjCProtocolDecl *PrevDecl = LookupProtocol(
ProtocolName, ProtocolLoc, SemaRef.forRedeclarationInCurContext());
ObjCProtocolDecl *PDecl = nullptr;
@@ -5009,6 +5226,32 @@ Decl *SemaObjC::ActOnMethodDeclaration(
return ObjCMethod;
}
+ // If the method is in a category, check the name of the category to
+ // see if we're supposed to be using a prefix on the method name.
+ if (ObjCCategoryDecl *Cat = dyn_cast<ObjCCategoryDecl>(ClassDecl)) {
+ ObjCInterfaceDecl *Interface = Cat->getClassInterface();
+ if (Interface &&
+ !SemaRef.getSourceManager().isInSystemHeader(SelectorLocs[0]) &&
+ ValidateObjCPublicName(Interface->getName()) != ObjCNameAllowed) {
+ // The class we're adding a category to does not match our prefix
+ // warning settings; check that Sel has a prefix that does.
+ switch (ValidateObjCForeignCategorySelector(Sel)) {
+ case ObjCNameUnprefixed:
+ Diag(SelectorLocs[0], diag::warn_objc_unprefixed_category_method_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(SelectorLocs[0], diag::warn_objc_bad_category_method_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(SelectorLocs[0],
+ diag::warn_objc_forbidden_category_method_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
+ }
+ }
+
// If this Objective-C method does not have a related result type, but we
// are allowed to infer related result types, try to do so based on the
// method family.
diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp
index 031f2a6af8774..531deae333b17 100644
--- a/clang/lib/Sema/SemaObjCProperty.cpp
+++ b/clang/lib/Sema/SemaObjCProperty.cpp
@@ -188,9 +188,52 @@ Decl *SemaObjC::ActOnProperty(Scope *S, SourceLocation AtLoc,
bool isReadWrite = ((Attributes & ObjCPropertyAttribute::kind_readwrite) ||
// default is readwrite!
!(Attributes & ObjCPropertyAttribute::kind_readonly));
+ ObjCContainerDecl *ClassDecl = cast<ObjCContainerDecl>(SemaRef.CurContext);
+
+ // If the property is in a category, check the name of the category to
+ // see if we're supposed to be using a prefix on the property name.
+ if (ObjCCategoryDecl *Cat = dyn_cast<ObjCCategoryDecl>(ClassDecl)) {
+ ObjCInterfaceDecl *Interface = Cat->getClassInterface();
+ if (!SemaRef.getSourceManager().isInSystemHeader(AtLoc) &&
+ ValidateObjCPublicName(Interface->getName()) != ObjCNameAllowed) {
+ SourceLocation GetterLoc = ODS.getGetterName()
+ ? ODS.getGetterNameLoc()
+ : FD.D.getSourceRange().getBegin();
+ switch (ValidateObjCForeignCategorySelector(GetterSel)) {
+ case ObjCNameUnprefixed:
+ Diag(GetterLoc, diag::warn_objc_unprefixed_category_method_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(GetterLoc, diag::warn_objc_bad_category_method_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(GetterLoc, diag::warn_objc_forbidden_category_method_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
+
+ if (ODS.getSetterName()) {
+ SourceLocation SetterLoc = ODS.getSetterNameLoc();
+ switch (ValidateObjCForeignCategorySelector(SetterSel)) {
+ case ObjCNameUnprefixed:
+ Diag(SetterLoc, diag::warn_objc_unprefixed_category_method_name);
+ break;
+ case ObjCNameNotAllowed:
+ Diag(SetterLoc, diag::warn_objc_bad_category_method_name_prefix);
+ break;
+ case ObjCNameForbidden:
+ Diag(SetterLoc,
+ diag::warn_objc_forbidden_category_method_name_prefix);
+ break;
+ case ObjCNameAllowed:
+ break;
+ }
+ }
+ }
+ }
// Proceed with constructing the ObjCPropertyDecls.
- ObjCContainerDecl *ClassDecl = cast<ObjCContainerDecl>(SemaRef.CurContext);
ObjCPropertyDecl *Res = nullptr;
if (ObjCCategoryDecl *CDecl = dyn_cast<ObjCCategoryDecl>(ClassDecl)) {
if (CDecl->IsClassExtension()) {
diff --git a/clang/test/Parser/objc-prefixes.m b/clang/test/Parser/objc-prefixes.m
deleted file mode 100644
index 32c8448f93fbe..0000000000000
--- a/clang/test/Parser/objc-prefixes.m
+++ /dev/null
@@ -1,62 +0,0 @@
-// RUN: %clang_cc1 %s -Wobjc-prefix-length=2 -fsyntax-only -verify
-
-// Test prefix length rules for ObjC interfaces and protocols
-
-// -- Plain interfaces --------------------------------------------------------
-
- at interface _Foo
- at end
-
- at interface Foo // expected-warning {{un-prefixed Objective-C class name}}
- at end
-
- at interface NSFoo
- at end
-
-// Special case for prefix-length 2
- at interface NSCFFoo
- at end
-
- at interface NSCFXFoo // expected-warning {{un-prefixed Objective-C class name}}
- at end
-
- at interface NSXFoo // expected-warning {{un-prefixed Objective-C class name}}
- at end
-
-// -- Categories --------------------------------------------------------------
-
-// Categories don't trigger these warnings
-
- at interface Foo (Bar)
- at end
-
- at interface NSFoo (Bar)
- at end
-
- at interface NSCFFoo (Bar)
- at end
-
- at interface NSXFoo (Bar)
- at end
-
-// -- Protocols ---------------------------------------------------------------
-
- at protocol _FooProtocol
- at end
-
- at protocol FooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
- at end
-
- at protocol NSFooProtocol
- at end
-
-// Special case for prefix-length 2
- at protocol NSCFFooProtocol
- at end
-
- at protocol NSCFXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
- at end
-
- at protocol NSXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
- at end
-
diff --git a/clang/test/SemaObjC/prefixes.m b/clang/test/SemaObjC/prefixes.m
new file mode 100644
index 0000000000000..beb1ce5bd19b2
--- /dev/null
+++ b/clang/test/SemaObjC/prefixes.m
@@ -0,0 +1,129 @@
+// RUN: %clang_cc1 %s -Wobjc-prefix-length=2 -fsyntax-only -verify
+
+// Test prefix length rules for ObjC interfaces and protocols
+
+// -- Plain interfaces --------------------------------------------------------
+
+ at interface _Foo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+ at interface Foo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+ at interface NSFoo
+ at end
+
+ at interface _NSFoo
+ at end
+
+ at interface __NSFoo
+ at end
+
+// Special case for prefix-length 2
+ at interface NSCFFoo
+ at end
+
+ at interface _NSCFFoo
+ at end
+
+ at interface NSCFXFoo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+ at interface NSXFoo // expected-warning {{un-prefixed Objective-C class name}}
+ at end
+
+// -- Categories --------------------------------------------------------------
+
+// Categories don't trigger these warnings, but methods in categories that
+// aren't appropriately prefixed are required to be appropriately prefixed
+
+ at interface Foo (Bar)
+
+ at property int flibble; // expected-warning {{un-prefixed Objective-C method name on category}}
+ at property(getter=theFlibble) int NS_flibble; // expected-warning {{un-prefixed Objective-C method name on category}}
+ at property(setter=setTheFlibble:) int NS_flibble2; // expected-warning {{un-prefixed Objective-C method name on category}}
+ at property int NS_flibble3;
+
+- (void)bar; // expected-warning {{un-prefixed Objective-C method name on category}}
+- (void)NSbar;
+- (void)NS_bar;
+- (void)NSCF_bar;
+- (void)NSXbar; // expected-warning {{un-prefixed Objective-C method name on category}}
+
+ at end
+
+ at interface NSFoo (Bar)
+
+ at property int flibble;
+ at property(getter=theFlibble) int NS_flibble;
+ at property(setter=setTheFlibble:) int NS_flibble2;
+ at property int NS_flibble3;
+
+- (void)bar;
+- (void)NSbar;
+- (void)NS_bar;
+- (void)NSCF_bar;
+- (void)NSXbar;
+
+ at end
+
+ at interface NSCFFoo (Bar)
+
+ at property int flibble;
+ at property(getter=theFlibble) int NS_flibble;
+ at property(setter=setTheFlibble:) int NS_flibble2;
+ at property int NS_flibble3;
+
+- (void)bar;
+- (void)NSbar;
+- (void)NS_bar;
+- (void)NSCF_bar;
+- (void)NSXbar;
+
+ at end
+
+ at interface NSXFoo (Bar)
+
+ at property int flibble; // expected-warning {{un-prefixed Objective-C method name on category}}
+ at property(getter=theFlibble) int NS_flibble; // expected-warning {{un-prefixed Objective-C method name on category}}
+ at property(setter=setTheFlibble:) int NS_flibble2; // expected-warning {{un-prefixed Objective-C method name on category}}
+ at property int NS_flibble3;
+
+- (void)bar; // expected-warning {{un-prefixed Objective-C method name on category}}
+- (void)NSbar;
+- (void)NS_bar;
+- (void)NSCF_bar;
+- (void)NSXbar; // expected-warning {{un-prefixed Objective-C method name on category}}
+
+ at end
+
+// -- Protocols ---------------------------------------------------------------
+
+ at protocol _FooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
+ at protocol FooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
+ at protocol NSFooProtocol
+ at end
+
+ at protocol _NSFooProtocol
+ at end
+
+ at protocol __NSFooProtocol
+ at end
+
+// Special case for prefix-length 2
+ at protocol NSCFFooProtocol
+ at end
+
+ at protocol _NSCFFooProtocol
+ at end
+
+ at protocol NSCFXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
+ at protocol NSXFooProtocol // expected-warning {{un-prefixed Objective-C protocol name}}
+ at end
+
diff --git a/clang/test/Parser/objc-prefixes2.m b/clang/test/SemaObjC/prefixes2.m
similarity index 51%
rename from clang/test/Parser/objc-prefixes2.m
rename to clang/test/SemaObjC/prefixes2.m
index fc94b15663dcf..7ae4359a16cf0 100644
--- a/clang/test/Parser/objc-prefixes2.m
+++ b/clang/test/SemaObjC/prefixes2.m
@@ -34,3 +34,20 @@ @interface XXFoo // expected-warning {{Objective-C class name prefix in forbidde
@protocol XXFooProtocol // expected-warning {{Objective-C protocol name prefix in forbidden list}}
@end
+
+ at interface Foo (Bar)
+
+ at property int flibble; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+ at property(getter=theFlibble) int NS_flibble; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+ at property(setter=setTheFlibble:) int NS_flibble2; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+ at property int NS_flibble3;
+ at property int XX_flibble; // expected-warning {{Objective-C category method name prefix in forbidden list}}
+
+- (void)bar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+- (void)NSbar;
+- (void)NS_bar;
+- (void)NSCF_bar;
+- (void)NSXbar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+- (void)XXbar; // expected-warning {{Objective-C category method name prefix in forbidden list}}
+
+ at end
>From 15fd36b719e59858c1f4d7b39ac7d36eac58d575 Mon Sep 17 00:00:00 2001
From: Alastair Houghton <ahoughton at apple.com>
Date: Wed, 17 Jul 2024 15:50:08 +0100
Subject: [PATCH 8/8] [Sema][ObjC] Update the rules for category method names.
We should allow upper or lower-case versions of the prefix.
Also, we should ignore any `is` prefix, since that is occasionally
used by people.
rdar://131055157
---
clang/lib/Sema/SemaDeclObjC.cpp | 29 ++++++++++++++++++++---------
clang/test/SemaObjC/prefixes2.m | 17 +++++++++++++++++
2 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp
index 3bafd115059d1..fcbe98d96c466 100644
--- a/clang/lib/Sema/SemaDeclObjC.cpp
+++ b/clang/lib/Sema/SemaDeclObjC.cpp
@@ -130,25 +130,28 @@ SemaObjC::ValidateObjCPublicName(StringRef Name) {
/// controlled by the `-Wobjc-prefixes=<list>`, `-Wobjc-prefix-length` and
/// `-Wobjc-forbidden-prefixes=<list>` options.
///
-/// Method name prefixes are expected to start with a valid prefix followed
-/// by a non-uppercase character. For example, with `-Wobjc-prefix-length`
-/// set to 2, the following are valid:
+/// Method name prefixes are expected to start with a valid prefix, which
+/// for methods may be lowercase, followed by a character of a different
+/// case, or an underscore. For instance, the following are valid:
///
/// NShasAnEvenNumberOfVowels
/// NS_consistsOnlyOfConsonants
/// NS_findSecondToLastOccurrenceOf:inReverse:
+/// ns_stringByRemovingAllVowels
+/// nsHasAtLeastTwentyCharacters
///
/// but these are not:
///
/// :
/// MySHA256Hash
/// NSOhNoYouDont
+/// nshasAnOddNumberOfVowels
///
/// As with classes and protocols, leading underscores are ignored when
/// examining the selector.
///
-/// Additionally, the prefix "set" is ignored; the reasoning behind this is
-/// that a property
+/// Additionally, the prefixes "set" is ignored; the reasoning behind
+/// this is that a property
///
/// @property int NSfoo;
///
@@ -157,20 +160,28 @@ SemaObjC::ValidateObjCPublicName(StringRef Name) {
///
/// - (int)NSfoo;
/// - (void)setNSfoo:(int)n;
+///
+/// We also ignore the prefix "is", which is generally discouraged but
+/// very occasionally does get used.
static inline bool ObjCSelNameMatchesPrefix(StringRef Name, StringRef Prefix) {
size_t NameLen = Name.size();
size_t PrefixLen = Prefix.size();
- return (NameLen >= PrefixLen && Name.starts_with(Prefix) &&
- (NameLen <= PrefixLen || !isUppercase(Name[PrefixLen])));
+ return (NameLen >= PrefixLen && Name.starts_with_insensitive(Prefix) &&
+ (NameLen <= PrefixLen ||
+ (isUppercase(Name[0]) && !isUppercase(Name[PrefixLen])) ||
+ (isLowercase(Name[0]) && !isLowercase(Name[PrefixLen]))));
}
SemaObjC::ObjCNameValidationResult
SemaObjC::ValidateObjCForeignCategorySelector(Selector Sel) {
StringRef Name = Sel.getNameForSlot(0).ltrim('_');
- // Ignore any "set" prefix
- Name.consume_front("set");
+ // Ignore any "set" or "is" prefix
+ if (Name.size() >= 4 && Name.starts_with("set") && !isLowercase(Name[3]))
+ Name = Name.drop_front(3).ltrim('_');
+ else if (Name.size() >= 3 && Name.starts_with("is") && !isLowercase(Name[2]))
+ Name = Name.drop_front(2).ltrim('_');
// Check the -Wobjc-forbidden-prefixes list
if (!getLangOpts().ObjCForbiddenPrefixes.empty()) {
diff --git a/clang/test/SemaObjC/prefixes2.m b/clang/test/SemaObjC/prefixes2.m
index 7ae4359a16cf0..cad71de8cc60b 100644
--- a/clang/test/SemaObjC/prefixes2.m
+++ b/clang/test/SemaObjC/prefixes2.m
@@ -47,7 +47,24 @@ - (void)bar; // expected-warning {{Objective-C category method name prefix not i
- (void)NSbar;
- (void)NS_bar;
- (void)NSCF_bar;
+- (void)ns_bar;
+- (void)nsBar;
+- (void)nsbar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
- (void)NSXbar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+- (void)nsxBar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
- (void)XXbar; // expected-warning {{Objective-C category method name prefix in forbidden list}}
+// "set" prefix is ignored
+- (void)setBar:(int)x; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+- (void)set_bar:(int)x; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+- (void)setNSbar:(int)x;
+- (void)set_NSbar:(int)x;
+
+// "is" prefix is ignored
+- (int)isNSbar;
+- (int)is_NSbar;
+- (int)is_ns_bar;
+- (int)is_bar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+- (int)isBar; // expected-warning {{Objective-C category method name prefix not in permitted list}}
+
@end
More information about the cfe-commits
mailing list