Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Sema][ObjC] Add warning options to detect bad name prefixes. #97597

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

al45tair
Copy link
Contributor

@al45tair al45tair commented Jul 3, 2024

On Darwin, two-character prefixes are reserved for Apple, while three-character prefixes are reserved for use by third party developers. Breaking these rules in framework code can lead to symbol clashes, which can result in unexpected behaviour.

This changeset adds three warning options, all turned off by default, that cause Clang to warn the user if they breach the policy specified by the warning options. They are:

  • -Wobjc-prefix-length=<count>, which tests for a certain prefix length on every ObjC class or protocol
  • -Wobjc-prefixes=<prefix>[,<prefix>...], which requires that the prefix on every ObjC class or protocol be one from a specified list, and
  • -Wobjc-forbidden-prefixes=<prefix>[,<prefix>...], which lists prefixes that are explicitly disallowed.

These are also used to determine whether or not a prefix should be added to a category method; if the class on which the category is being declared does not match the permitted prefix settings, then the above warning options will also require a prefix on any methods declared in that category.

All of these warnings are disabled for system headers.

rdar://131055157

@al45tair al45tair force-pushed the eng/PR-131055157 branch 3 times, most recently from cb570a0 to 995f0f7 Compare July 16, 2024 17:51
Copy link

github-actions bot commented Jul 17, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

On Darwin, two-character prefixes are reserved for Apple, while three-
character prefixes are reserved for use by third party developers.
Breaking these rules in framework code can lead to symbol clashes,
which can result in unexpected behaviour.

This changeset adds three warning options, all turned off by default,
that cause Clang to warn the user if they breach the policy specified
by the warning options.  They are:

* `-Wobjc-prefix-length=<count>`, which tests for a certain prefix length
  on every ObjC class or protocol
* `-Wobjc-prefixes=<prefix>[,<prefix>...]`, which requires that the prefix
  on every ObjC class or protocol be one from a specified list, and
* `-Wobjc-forbidden-prefixes=<prefix>[,<prefix>...]`, which lists prefixes
  that are explicitly disallowed.

These are also used to determine whether or not a prefix should be added
to a category method; if the class on which the category is being declared
does not match the permitted prefix settings, then the above warning
options will *also* require a prefix on any methods declared in that
category.

All of these warnings are disabled for system headers.

rdar://131055157
@al45tair al45tair changed the title [Parser][ObjC] Add -Wobjc-prefix-length warning option. [Sema][ObjC] Add warning options to detect bad name prefixes. Jul 18, 2024
@al45tair al45tair marked this pull request as ready for review July 18, 2024 13:43
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 18, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented Jul 18, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-driver

Author: Alastair Houghton (al45tair)

Changes

On Darwin, two-character prefixes are reserved for Apple, while three-character prefixes are reserved for use by third party developers. Breaking these rules in framework code can lead to symbol clashes, which can result in unexpected behaviour.

This changeset adds three warning options, all turned off by default, that cause Clang to warn the user if they breach the policy specified by the warning options. They are:

  • -Wobjc-prefix-length=&lt;count&gt;, which tests for a certain prefix length on every ObjC class or protocol
  • -Wobjc-prefixes=&lt;prefix&gt;[,&lt;prefix&gt;...], which requires that the prefix on every ObjC class or protocol be one from a specified list, and
  • -Wobjc-forbidden-prefixes=&lt;prefix&gt;[,&lt;prefix&gt;...], which lists prefixes that are explicitly disallowed.

These are also used to determine whether or not a prefix should be added to a category method; if the class on which the category is being declared does not match the permitted prefix settings, then the above warning options will also require a prefix on any methods declared in that category.

All of these warnings are disabled for system headers.

rdar://131055157


Patch is 27.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/97597.diff

12 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+3)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+31)
  • (modified) clang/include/clang/Basic/LangOptions.def (+2)
  • (modified) clang/include/clang/Basic/LangOptions.h (+6)
  • (modified) clang/include/clang/Driver/Options.td (+19)
  • (modified) clang/include/clang/Sema/SemaObjC.h (+9)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+4)
  • (modified) clang/lib/Parse/ParseObjc.cpp (+1)
  • (modified) clang/lib/Sema/SemaDeclObjC.cpp (+254)
  • (modified) clang/lib/Sema/SemaObjCProperty.cpp (+44-1)
  • (added) clang/test/SemaObjC/prefixes.m (+129)
  • (added) clang/test/SemaObjC/prefixes2.m (+70)
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 2241f8481484e..7f3d4b463a8df 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -612,6 +612,9 @@ 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 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]>;
 def OpenCLUnsupportedRGBA: DiagGroup<"opencl-unsupported-rgba">;
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/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 5ad9c1f24b7c5..edad911d288f4 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_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 "
                "involving qualified id types")
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 91f1c2f2e6239..932bacc27d8cb 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -499,6 +499,12 @@ class LangOptions : public LangOptionsBase {
 
   std::string ObjCConstantStringClass;
 
+  /// 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 2400b193d4d38..f50a7b24fbcbe 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -3419,6 +3419,25 @@ defm objc_exceptions : BoolFOption<"objc-exceptions",
   PosFlag<SetTrue, [], [ClangOption, CC1Option],
           "Enable Objective-C exceptions">,
   NegFlag<SetFalse>>;
+def Wobjc_prefixes_EQ:
+  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=">,
+  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=">,
+  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/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/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index d48b26cc74132..a423fdcee8de0 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -6243,6 +6243,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
 
   Args.AddAllArgs(CmdArgs, options::OPT_Wsystem_headers_in_module_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))
     CmdArgs.push_back("-pedantic");
   Args.AddLastArg(CmdArgs, options::OPT_pedantic_errors);
diff --git a/clang/lib/Parse/ParseObjc.cpp b/clang/lib/Parse/ParseObjc.cpp
index 6a2088a73c55b..d2f70ce468666 100644
--- a/clang/lib/Parse/ParseObjc.cpp
+++ b/clang/lib/Parse/ParseObjc.cpp
@@ -319,6 +319,7 @@ Decl *Parser::ParseObjCAtInterfaceDeclaration(SourceLocation AtLoc,
 
     return CategoryType;
   }
+
   // Parse a class interface.
   IdentifierInfo *superClassId = nullptr;
   SourceLocation superClassLoc;
diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp
index 807453400abdd..fcbe98d96c466 100644
--- a/clang/lib/Sema/SemaDeclObjC.cpp
+++ b/clang/lib/Sema/SemaDeclObjC.cpp
@@ -33,6 +33,199 @@
 
 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, 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 prefixes "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;
+///
+/// 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_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" 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()) {
+    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 +1185,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 +1432,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 +5237,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 ObjCNameUnpre...
[truncated]

@al45tair
Copy link
Contributor Author

Failure doesn't look like anything to do with this PR.

@al45tair al45tair marked this pull request as draft July 31, 2024 07:16
@al45tair
Copy link
Contributor Author

Converting back to draft as I'm still tinkering slightly with the warning rules.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants