philnik created this revision.
philnik added reviewers: aaron.ballman, erichkeane.
Herald added a subscriber: dschuff.
Herald added a project: All.
philnik requested review of this revision.
Herald added subscribers: cfe-commits, aheejin.
Herald added a project: clang.

This attribute is equivalent to a defaulted member equality comparison for the 
purposes of __is_trivially_equality_comparable. The difference is that it 
doesn't try to do ADL calls when instantiating a class template. We encountered 
this problem while trying to make classes in libc++ trivially equality 
comparable. It can be used to guarantee that enums are trivially equality 
comparable (which is more of a bonus than a good reason to add this attribute).


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D151625

Files:
  clang/include/clang/Basic/Attr.td
  clang/include/clang/Basic/AttrDocs.td
  clang/lib/AST/Type.cpp
  
clang/test/SemaCXX/attr-equality-operator-compares-members-lexicographically.cpp

Index: clang/test/SemaCXX/attr-equality-operator-compares-members-lexicographically.cpp
===================================================================
--- /dev/null
+++ clang/test/SemaCXX/attr-equality-operator-compares-members-lexicographically.cpp
@@ -0,0 +1,139 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++20
+
+void __attribute__((equality_operator_compares_members_lexicographically)) foo(); // expected-warning {{'equality_operator_compares_members_lexicographically' attribute only applies to classes and enums}}
+
+struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparabe1 {
+  int i;
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparabe1));
+
+struct __attribute__((equality_operator_compares_members_lexicographically)) TriviallyEqualityComparabe2 {
+  int i;
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparabe2));
+
+struct [[clang::equality_operator_compares_members_lexicographically(1)]] TestMessage {}; // expected-error {{'equality_operator_compares_members_lexicographically' attribute takes no arguments}}
+
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasPadding {
+  short i;
+  int j;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasPadding), "");
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasFloat {
+  float i;
+  int j;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasFloat), "");
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasTailPadding {
+  int i;
+  char j;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasTailPadding), "");
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableBase : NotTriviallyEqualityComparableHasTailPadding {
+  char j;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableBase), "");
+
+class [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparablePaddedOutBase {
+  int i;
+  char c;
+};
+static_assert(!__is_trivially_equality_comparable(TriviallyEqualityComparablePaddedOutBase), "");
+
+struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparablePaddedOut : TriviallyEqualityComparablePaddedOutBase {
+  char j[3];
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparablePaddedOut), "");
+
+struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparable1 {
+  char i;
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparable1));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparable2 {
+  int i;
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparable2));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableTriviallyEqualityComparableBases
+    : TriviallyEqualityComparable1, TriviallyEqualityComparable2 {
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableTriviallyEqualityComparableBases));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableBitfield {
+  int i : 1;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableBitfield));
+
+// TODO: This is trivially equality comparable
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableBitfieldFilled {
+  char i : __CHAR_BIT__;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableBitfield));
+
+union [[clang::equality_operator_compares_members_lexicographically]] U {
+  int i;
+};
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableImplicitlyDeletedOperatorByUnion {
+  U u;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableImplicitlyDeletedOperatorByUnion));
+
+struct NotTriviallyEqualityComparableExplicitlyDeleted {
+  int i;
+
+  friend bool operator==(const NotTriviallyEqualityComparableExplicitlyDeleted&, const NotTriviallyEqualityComparableExplicitlyDeleted&) = delete;
+};
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableImplicitlyDeletedOperatorByStruct {
+  NotTriviallyEqualityComparableExplicitlyDeleted u;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableImplicitlyDeletedOperatorByStruct));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasReferenceMember {
+  int& i;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasReferenceMember));
+
+enum E {
+  a,
+  b
+};
+bool operator==(E, E) { return false; }
+static_assert(!__is_trivially_equality_comparable(E));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasEnum {
+  E e;
+};
+static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasEnum));
+
+enum [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableEnum {
+  c,
+  d,
+};
+bool operator==(TriviallyEqualityComparableEnum, TriviallyEqualityComparableEnum);
+
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableEnum));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableHasTriviallyEqualityComparableEnum {
+  TriviallyEqualityComparableEnum e;
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableHasTriviallyEqualityComparableEnum));
+
+enum class [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableEnumClass {
+  e,
+  f,
+};
+bool operator==(TriviallyEqualityComparableEnumClass, TriviallyEqualityComparableEnumClass);
+
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableEnumClass));
+
+struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableHasTriviallyEqualityComparableEnumClass {
+  TriviallyEqualityComparableEnumClass e;
+};
+static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableHasTriviallyEqualityComparableEnumClass));
Index: clang/lib/AST/Type.cpp
===================================================================
--- clang/lib/AST/Type.cpp
+++ clang/lib/AST/Type.cpp
@@ -2649,7 +2649,8 @@
             Decl->isTriviallyCopyable());
   };
 
-  if (llvm::none_of(Decl->methods(), IsDefaultedOperatorEqualEqual) &&
+  if (!Decl->hasAttr<EqualityOperatorComparesMembersLexicographicallyAttr>() &&
+      llvm::none_of(Decl->methods(), IsDefaultedOperatorEqualEqual) &&
       llvm::none_of(Decl->friends(), [&](const FriendDecl *Friend) {
         if (NamedDecl *ND = Friend->getFriendDecl()) {
           return ND->isFunctionOrFunctionTemplate() &&
@@ -2667,7 +2668,11 @@
                       }) &&
          llvm::all_of(Decl->fields(), [](const FieldDecl *FD) {
            auto Type = FD->getType();
-           if (Type->isReferenceType() || Type->isEnumeralType())
+           if (Type->isReferenceType() ||
+               (Type->isEnumeralType() &&
+                !Type->getAsTagDecl()
+                     ->hasAttr<
+                         EqualityOperatorComparesMembersLexicographicallyAttr>()))
              return false;
            if (const auto *RD = Type->getAsCXXRecordDecl())
              return HasNonDeletedDefaultedEqualityComparison(RD);
@@ -2679,7 +2684,9 @@
     const ASTContext &Context) const {
   QualType CanonicalType = getCanonicalType();
   if (CanonicalType->isIncompleteType() || CanonicalType->isDependentType() ||
-      CanonicalType->isEnumeralType())
+      (CanonicalType->isEnumeralType() &&
+       !CanonicalType->getAsTagDecl()
+            ->hasAttr<EqualityOperatorComparesMembersLexicographicallyAttr>()))
     return false;
 
   if (const auto *RD = CanonicalType->getAsCXXRecordDecl()) {
Index: clang/include/clang/Basic/AttrDocs.td
===================================================================
--- clang/include/clang/Basic/AttrDocs.td
+++ clang/include/clang/Basic/AttrDocs.td
@@ -539,16 +539,16 @@
   let Category = DocCatStmt;
   let Content = [{
 If a statement is marked ``nomerge`` and contains call expressions, those call
-expressions inside the statement will not be merged during optimization. This 
+expressions inside the statement will not be merged during optimization. This
 attribute can be used to prevent the optimizer from obscuring the source
 location of certain calls. For example, it will prevent tail merging otherwise
 identical code sequences that raise an exception or terminate the program. Tail
 merging normally reduces the precision of source location information, making
 stack traces less useful for debugging. This attribute gives the user control
-over the tradeoff between code size and debug information precision. 
+over the tradeoff between code size and debug information precision.
 
-``nomerge`` attribute can also be used as function attribute to prevent all 
-calls to the specified function from merging. It has no effect on indirect 
+``nomerge`` attribute can also be used as function attribute to prevent all
+calls to the specified function from merging. It has no effect on indirect
 calls.
   }];
 }
@@ -1584,7 +1584,7 @@
 ``watchos``
   Apple's watchOS operating system. The minimum deployment target is specified by
   the ``-mwatchos-version-min=*version*`` command-line argument.
-  
+
 ``driverkit``
   Apple's DriverKit userspace kernel extensions. The minimum deployment target
   is specified as part of the triple.
@@ -3519,6 +3519,21 @@
   }];
 }
 
+def EqualityOperatorComparesMembersLexicographicallyDocs : Documentation {
+  let Category = DocCatDecl;
+  let Content = [{
+On classes, the ``equality_operator_compares_members_lexicographically``
+attribute informs clang that a call to ``operator==(const T&, const T&)`` is
+equivalent to comparing the members lexicographically (i.e. the call is
+equivalent to a call to ``bool operator==(const T&) const = default``). This is
+useful for libraries which have strict requirements on ADL uses. If possible, a
+defaulted equality comparison operator should be preferred over this attribute.
+On enums, the attribute guarantees that there are either no custom compatison
+operators defined, or any that defined are quivalent to the builtin comparison
+operator.
+  }];
+}
+
 def MSInheritanceDocs : Documentation {
   let Category = DocCatDecl;
   let Heading = "__single_inhertiance, __multiple_inheritance, __virtual_inheritance";
@@ -3930,7 +3945,7 @@
 with pointers in the C family of languages. The various nullability attributes
 indicate whether a particular pointer can be null or not, which makes APIs more
 expressive and can help static analysis tools identify bugs involving null
-pointers. Clang supports several kinds of nullability attributes: the 
+pointers. Clang supports several kinds of nullability attributes: the
 ``nonnull`` and ``returns_nonnull`` attributes indicate which function or
 method parameters and result types can never be null, while nullability type
 qualifiers indicate which pointer types can be null (``_Nullable``) or cannot
@@ -4106,7 +4121,7 @@
 The ``returns_nonnull`` attribute implies that returning a null pointer is
 undefined behavior, which the optimizer may take advantage of. The ``_Nonnull``
 type qualifier indicates that a pointer cannot be null in a more general manner
-(because it is part of the type system) and does not imply undefined behavior, 
+(because it is part of the type system) and does not imply undefined behavior,
 making it more widely applicable
 }];
 }
@@ -6062,15 +6077,15 @@
   let Content = [{
 Code can indicate CFG checks are not wanted with the ``__declspec(guard(nocf))``
 attribute. This directs the compiler to not insert any CFG checks for the entire
-function. This approach is typically used only sparingly in specific situations 
-where the programmer has manually inserted "CFG-equivalent" protection. The 
-programmer knows that they are calling through some read-only function table 
-whose address is obtained through read-only memory references and for which the 
-index is masked to the function table limit. This approach may also be applied 
-to small wrapper functions that are not inlined and that do nothing more than 
-make a call through a function pointer. Since incorrect usage of this directive 
-can compromise the security of CFG, the programmer must be very careful using 
-the directive. Typically, this usage is limited to very small functions that 
+function. This approach is typically used only sparingly in specific situations
+where the programmer has manually inserted "CFG-equivalent" protection. The
+programmer knows that they are calling through some read-only function table
+whose address is obtained through read-only memory references and for which the
+index is masked to the function table limit. This approach may also be applied
+to small wrapper functions that are not inlined and that do nothing more than
+make a call through a function pointer. Since incorrect usage of this directive
+can compromise the security of CFG, the programmer must be very careful using
+the directive. Typically, this usage is limited to very small functions that
 only call one function.
 
 `Control Flow Guard documentation <https://docs.microsoft.com/en-us/windows/win32/secbp/pe-metadata>`
@@ -6983,8 +6998,8 @@
 def WebAssemblyFuncrefDocs : Documentation {
   let Category = DocCatType;
   let Content = [{
-Clang supports the ``__funcref`` attribute for the WebAssembly target. 
-This attribute may be attached to a function pointer type, where it modifies 
+Clang supports the ``__funcref`` attribute for the WebAssembly target.
+This attribute may be attached to a function pointer type, where it modifies
 its underlying representation to be a WebAssembly ``funcref``.
   }];
 }
Index: clang/include/clang/Basic/Attr.td
===================================================================
--- clang/include/clang/Basic/Attr.td
+++ clang/include/clang/Basic/Attr.td
@@ -1628,6 +1628,14 @@
   let SimpleHandler = 1;
 }
 
+def EqualityOperatorComparesMembersLexicographically : InheritableAttr {
+  let Spellings = [Clang<"equality_operator_compares_members_lexicographically", 0>];
+  let Subjects = SubjectList<[CXXRecord, Enum]>;
+  let Documentation = [EqualityOperatorComparesMembersLexicographicallyDocs];
+  let LangOpts = [CPlusPlus];
+  let SimpleHandler = 1;
+}
+
 def MaxFieldAlignment : InheritableAttr {
   // This attribute has no spellings as it is only ever created implicitly.
   let Spellings = [];
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to