[clang] nolock/noalloc attributes (PR #84983)

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Tue Apr 2 09:04:58 PDT 2024


https://github.com/dougsonos updated https://github.com/llvm/llvm-project/pull/84983

>From 0619e30a3a4be724185406fad2413c786e2acff1 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 11 Mar 2024 17:19:01 -0700
Subject: [PATCH 01/18] Initial squash/cleanup

---
 clang/include/clang/AST/Decl.h                |    9 +
 clang/include/clang/AST/Type.h                |  297 +++-
 clang/include/clang/AST/TypeProperties.td     |    5 +
 clang/include/clang/Basic/Attr.td             |   23 +
 clang/include/clang/Basic/AttrDocs.td         |   17 +
 clang/include/clang/Basic/DiagnosticGroups.td |    4 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   95 ++
 clang/include/clang/Sema/Sema.h               |   16 +
 clang/lib/AST/ASTContext.cpp                  |   31 +-
 clang/lib/AST/Decl.cpp                        |   85 +
 clang/lib/AST/Type.cpp                        |  275 +++
 clang/lib/AST/TypePrinter.cpp                 |   14 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 1491 +++++++++++++++++
 clang/lib/Sema/Sema.cpp                       |   24 +
 clang/lib/Sema/SemaDecl.cpp                   |   89 +
 clang/lib/Sema/SemaDeclAttr.cpp               |   28 +
 clang/lib/Sema/SemaDeclCXX.cpp                |   20 +
 clang/lib/Sema/SemaExpr.cpp                   |    4 +
 clang/lib/Sema/SemaExprCXX.cpp                |    8 +-
 clang/lib/Sema/SemaLambda.cpp                 |    5 +
 clang/lib/Sema/SemaOverload.cpp               |   27 +
 clang/lib/Sema/SemaType.cpp                   |  130 +-
 clang/test/Sema/attr-nolock.cpp               |   78 +
 clang/test/Sema/attr-nolock2.cpp              |   88 +
 clang/test/Sema/attr-nolock3.cpp              |  144 ++
 25 files changed, 2997 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/Sema/attr-nolock.cpp
 create mode 100644 clang/test/Sema/attr-nolock2.cpp
 create mode 100644 clang/test/Sema/attr-nolock3.cpp

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index a5879591f4c659..0460f30ce8a8b4 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,6 +3008,13 @@ class FunctionDecl : public DeclaratorDecl,
   /// computed and stored.
   unsigned getODRHash() const;
 
+  FunctionEffectSet getFunctionEffects() const {
+    const auto *FPT = getType()->getAs<FunctionProtoType>();
+    if (FPT)
+      return FPT->getFunctionEffects();
+    return {};
+  }
+
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
   static bool classofKind(Kind K) {
@@ -4633,6 +4640,8 @@ class BlockDecl : public Decl, public DeclContext {
 
   SourceRange getSourceRange() const override LLVM_READONLY;
 
+  FunctionEffectSet getFunctionEffects() const;
+
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
   static bool classofKind(Kind K) { return K == Block; }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 1942b0e67f65a3..41c60a6e221d4e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4047,8 +4047,12 @@ class FunctionType : public Type {
     LLVM_PREFERRED_TYPE(bool)
     unsigned HasArmTypeAttributes : 1;
 
+    LLVM_PREFERRED_TYPE(bool)
+    unsigned HasFunctionEffects : 1;
+
     FunctionTypeExtraBitfields()
-        : NumExceptionType(0), HasArmTypeAttributes(false) {}
+        : NumExceptionType(0), HasArmTypeAttributes(false),
+          HasFunctionEffects(false) {}
   };
 
   /// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number
@@ -4181,6 +4185,131 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
   }
 };
 
+class FunctionEffect;
+class FunctionEffectSet;
+
+// It is the user's responsibility to keep this in set form: elements are
+// ordered and unique.
+// We could hide the mutating methods which are capable of breaking the
+// invariant, but they're needed and safe when used with STL set algorithms.
+class MutableFunctionEffectSet : public SmallVector<const FunctionEffect *, 4> {
+public:
+  using SmallVector::insert;
+  using SmallVector::SmallVector;
+
+  /// Maintains order/uniquenesss.
+  void insert(const FunctionEffect *effect);
+
+  MutableFunctionEffectSet &operator|=(FunctionEffectSet rhs);
+};
+
+class FunctionEffectSet {
+public:
+  // These sets will tend to be very small (1 element), so represent them as
+  // sorted vectors, which are compatible with the STL set algorithms. Using an
+  // array or vector also means the elements are contiguous, keeping iterators
+  // simple.
+
+private:
+  // 'Uniqued' refers to the set itself being uniqued. Storage is allocated
+  // separately. Use ArrayRef for its iterators. Subclass so as to be able to
+  // compare (it seems ArrayRef would silently convert itself to a vector for
+  // comparison?!).
+  class UniquedAndSortedFX : public llvm::ArrayRef<const FunctionEffect *> {
+  public:
+    using Base = llvm::ArrayRef<const FunctionEffect *>;
+
+    UniquedAndSortedFX(Base Array) : Base(Array) {}
+    UniquedAndSortedFX(const FunctionEffect **Ptr, size_t Len)
+        : Base(ptr, len) {}
+
+    bool operator<(const UniquedAndSortedFX &rhs) const;
+  };
+
+  // Could have used a TinyPtrVector if it were unique-able.
+  // Empty set has a null Impl.
+  llvm::PointerUnion<const FunctionEffect *, const UniquedAndSortedFX *> Impl;
+
+  explicit FunctionEffectSet(const FunctionEffect *Single) : Impl(Single) {}
+  explicit FunctionEffectSet(const UniquedAndSortedFX *Multi) : Impl(Multi) {}
+
+public:
+  using Differences =
+      SmallVector<std::pair<const FunctionEffect *, bool /*added*/>>;
+
+  FunctionEffectSet() : Impl(nullptr) {}
+
+  void *getOpaqueValue() const { return Impl.getOpaqueValue(); }
+
+  explicit operator bool() const { return !empty(); }
+  bool empty() const {
+    if (Impl.isNull())
+      return true;
+    if (const UniquedAndSortedFX *Vec =
+            dyn_cast_if_present<const UniquedAndSortedFX *>(Impl))
+      return Vec->empty();
+    return false;
+  }
+  size_t size() const {
+    if (empty())
+      return 0;
+    if (isa<const FunctionEffect *>(Impl))
+      return 1;
+    return cast<const UniquedAndSortedFX *>(Impl)->size();
+  }
+
+  using iterator = const FunctionEffect *const *;
+
+  iterator begin() const {
+    if (isa<const FunctionEffect *>(Impl))
+      return Impl.getAddrOfPtr1();
+    return cast<const UniquedAndSortedFX *>(Impl)->begin();
+  }
+
+  iterator end() const {
+    if (isa<const FunctionEffect *>(Impl))
+      return begin() + (Impl.isNull() ? 0 : 1);
+    return cast<const UniquedAndSortedFX *>(Impl)->end();
+  }
+
+  ArrayRef<const FunctionEffect *> items() const { return {begin(), end()}; }
+
+  // Since iterators are non-trivial and sets are very often empty,
+  // encourage short-circuiting loops for the empty set.
+  // void for_each(llvm::function_ref<void(const FunctionEffect*)> func) const;
+
+  bool operator==(const FunctionEffectSet &other) const {
+    return Impl == other.Impl;
+  }
+  bool operator!=(const FunctionEffectSet &other) const {
+    return Impl != other.Impl;
+  }
+  bool operator<(const FunctionEffectSet &other) const;
+
+  void dump(llvm::raw_ostream &OS) const;
+
+  /// Factory functions: return instances with uniqued implementations.
+  static FunctionEffectSet create(const FunctionEffect &single) {
+    return FunctionEffectSet{&single};
+  }
+  static FunctionEffectSet create(llvm::ArrayRef<const FunctionEffect *> items);
+
+  /// Union. Caller should check for incompatible effects.
+  FunctionEffectSet operator|(const FunctionEffectSet &rhs) const;
+  /// Intersection.
+  MutableFunctionEffectSet operator&(const FunctionEffectSet &rhs) const;
+  /// Difference.
+  MutableFunctionEffectSet operator-(const FunctionEffectSet &rhs) const;
+
+  /// Caller should short-circuit by checking for equality first.
+  static Differences differences(const FunctionEffectSet &Old,
+                                 const FunctionEffectSet &New);
+
+  /// Extract the effects from a Type if it is a BlockType or FunctionProtoType,
+  /// or pointer to one.
+  static FunctionEffectSet get(const Type &TyRef);
+};
+
 /// Represents a prototype with parameter type info, e.g.
 /// 'int foo(int)' or 'int foo(void)'.  'void' is represented as having no
 /// parameters, not as having a single void parameter. Such a type can have
@@ -4195,7 +4324,8 @@ class FunctionProtoType final
           FunctionProtoType, QualType, SourceLocation,
           FunctionType::FunctionTypeExtraBitfields,
           FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
-          Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, Qualifiers> {
+          Expr *, FunctionDecl *, FunctionType::ExtParameterInfo,
+          FunctionEffectSet, Qualifiers> {
   friend class ASTContext; // ASTContext creates these.
   friend TrailingObjects;
 
@@ -4226,6 +4356,9 @@ class FunctionProtoType final
   //   an ExtParameterInfo for each of the parameters. Present if and
   //   only if hasExtParameterInfos() is true.
   //
+  // * Optionally, a FunctionEffectSet. Present if and only if
+  //   hasFunctionEffects() is true.
+  //
   // * Optionally a Qualifiers object to represent extra qualifiers that can't
   //   be represented by FunctionTypeBitfields.FastTypeQuals. Present if and only
   //   if hasExtQualifiers() is true.
@@ -4284,6 +4417,7 @@ class FunctionProtoType final
     ExceptionSpecInfo ExceptionSpec;
     const ExtParameterInfo *ExtParameterInfos = nullptr;
     SourceLocation EllipsisLoc;
+    FunctionEffectSet FunctionEffects;
 
     ExtProtoInfo()
         : Variadic(false), HasTrailingReturn(false),
@@ -4301,7 +4435,8 @@ class FunctionProtoType final
 
     bool requiresFunctionProtoTypeExtraBitfields() const {
       return ExceptionSpec.Type == EST_Dynamic ||
-             requiresFunctionProtoTypeArmAttributes();
+             requiresFunctionProtoTypeArmAttributes() ||
+             FunctionEffects;
     }
 
     bool requiresFunctionProtoTypeArmAttributes() const {
@@ -4349,6 +4484,10 @@ class FunctionProtoType final
     return hasExtParameterInfos() ? getNumParams() : 0;
   }
 
+  unsigned numTrailingObjects(OverloadToken<FunctionEffectSet>) const {
+    return hasFunctionEffects();
+  }
+
   /// Determine whether there are any argument types that
   /// contain an unexpanded parameter pack.
   static bool containsAnyUnexpandedParameterPack(const QualType *ArgArray,
@@ -4450,6 +4589,7 @@ class FunctionProtoType final
     EPI.RefQualifier = getRefQualifier();
     EPI.ExtParameterInfos = getExtParameterInfosOrNull();
     EPI.AArch64SMEAttributes = getAArch64SMEAttributes();
+    EPI.FunctionEffects = getFunctionEffects();
     return EPI;
   }
 
@@ -4661,6 +4801,18 @@ class FunctionProtoType final
     return false;
   }
 
+  bool hasFunctionEffects() const {
+    if (!hasExtraBitfields())
+      return false;
+    return getTrailingObjects<FunctionTypeExtraBitfields>()->HasFunctionEffects;
+  }
+
+  FunctionEffectSet getFunctionEffects() const {
+    if (hasFunctionEffects())
+      return *getTrailingObjects<FunctionEffectSet>();
+    return {};
+  }
+
   bool isSugared() const { return false; }
   QualType desugar() const { return QualType(this, 0); }
 
@@ -7754,6 +7906,145 @@ QualType DecayedType::getPointeeType() const {
 void FixedPointValueToString(SmallVectorImpl<char> &Str, llvm::APSInt Val,
                              unsigned Scale);
 
+// ------------------------------------------------------------------------------
+
+// TODO: Should FunctionEffect be located elsewhere, where Decl is not
+// forward-declared?
+class Decl;
+class CXXMethodDecl;
+
+/// Represents an abstract function effect.
+class FunctionEffect {
+public:
+  enum EffectType {
+    kGeneric,
+    kNoLockTrue,
+    kNoAllocTrue,
+  };
+
+  /// Flags describing behaviors of the effect.
+  using Flags = unsigned;
+  enum FlagBit : unsigned {
+    // Some effects require verification, e.g. nolock(true); others might not?
+    // (no example yet)
+    kRequiresVerification = 0x1,
+
+    // Does this effect want to verify all function calls originating in
+    // functions having this effect?
+    kVerifyCalls = 0x2,
+
+    // Can verification inspect callees' implementations? (e.g. nolock: yes,
+    // tcb+types: no)
+    kInferrableOnCallees = 0x4,
+
+    // Language constructs which effects can diagnose as disallowed.
+    kExcludeThrow = 0x8,
+    kExcludeCatch = 0x10,
+    kExcludeObjCMessageSend = 0x20,
+    kExcludeStaticLocalVars = 0x40,
+    kExcludeThreadLocalVars = 0x80
+  };
+
+private:
+  const EffectType Type_;
+  const Flags Flags_;
+  const char *Name;
+
+public:
+  using CalleeDeclOrType =
+      llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
+
+  FunctionEffect(EffectType T, Flags F, const char *Name)
+      : Type_{T}, Flags_{F}, Name{Name} {}
+  virtual ~FunctionEffect();
+
+  /// The type of the effect.
+  EffectType type() const { return Type_; }
+
+  /// Flags describing behaviors of the effect.
+  Flags getFlags() const { return Flags_; }
+
+  /// The description printed in diagnostics, e.g. 'nolock'.
+  StringRef name() const { return Name; }
+
+  /// The description used by TypePrinter, e.g. __attribute__((clang_nolock))
+  virtual std::string attribute() const = 0;
+
+  /// Return true if adding or removing the effect as part of a type conversion
+  /// should generate a diagnostic.
+  virtual bool diagnoseConversion(bool adding, QualType OldType,
+                                  FunctionEffectSet OldFX, QualType NewType,
+                                  FunctionEffectSet NewFX) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  virtual bool diagnoseRedeclaration(bool adding,
+                                     const FunctionDecl &OldFunction,
+                                     FunctionEffectSet OldFX,
+                                     const FunctionDecl &NewFunction,
+                                     FunctionEffectSet NewFX) const;
+
+  /// Return true if adding or removing the effect in a C++ virtual method 
+  /// override should generate a diagnostic.
+  virtual bool diagnoseMethodOverride(bool adding,
+                                      const CXXMethodDecl &OldMethod,
+                                      FunctionEffectSet OldFX,
+                                      const CXXMethodDecl &NewMethod,
+                                      FunctionEffectSet NewFX) const;
+
+  /// Return true if the effect is allowed to be inferred on the specified Decl
+  /// (may be a FunctionDecl or BlockDecl). Only used if the effect has
+  /// kInferrableOnCallees flag set. Example: This allows nolock(false) to 
+  /// prevent inference for the function.
+  virtual bool canInferOnDecl(const Decl *Caller,
+                              FunctionEffectSet CallerFX) const;
+
+  // Called if kVerifyCalls flag is set; return false for success. When true is
+  // returned for a direct call, then the kInferrableOnCallees flag may trigger
+  // inference rather than an immediate diagnostic. Caller should be assumed to
+  // have the effect (it may not have it explicitly when inferring).
+  virtual bool diagnoseFunctionCall(bool direct, const Decl *Caller,
+                                    FunctionEffectSet CallerFX,
+                                    CalleeDeclOrType Callee,
+                                    FunctionEffectSet CalleeFX) const;
+};
+
+/// FunctionEffect subclass for nolock and noalloc (whose behaviors are close
+/// to identical).
+class NoLockNoAllocEffect : public FunctionEffect {
+  bool isNoLock() const { return type() == kNoLockTrue; }
+
+public:
+  static const NoLockNoAllocEffect &nolock_instance();
+  static const NoLockNoAllocEffect &noalloc_instance();
+
+  NoLockNoAllocEffect(EffectType ty, const char *name);
+  ~NoLockNoAllocEffect() override;
+
+  std::string attribute() const override;
+
+  bool diagnoseConversion(bool adding, QualType OldType,
+                          FunctionEffectSet OldFX, QualType NewType,
+                          FunctionEffectSet NewFX) const override;
+
+  bool diagnoseRedeclaration(bool adding, const FunctionDecl &OldFunction,
+                             FunctionEffectSet OldFX,
+                             const FunctionDecl &NewFunction,
+                             FunctionEffectSet NewFX) const override;
+
+  bool diagnoseMethodOverride(bool adding, const CXXMethodDecl &OldMethod,
+                              FunctionEffectSet OldFX,
+                              const CXXMethodDecl &NewMethod,
+                              FunctionEffectSet NewFX) const override;
+
+  bool canInferOnDecl(const Decl *Caller,
+                      FunctionEffectSet CallerFX) const override;
+
+  bool diagnoseFunctionCall(bool direct, const Decl *Caller,
+                            FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
+                            FunctionEffectSet CalleeFX) const override;
+};
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_TYPE_H
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 0ba172a4035fdb..1d5d8f696977e7 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -326,6 +326,10 @@ let Class = FunctionProtoType in {
   def : Property<"AArch64SMEAttributes", UInt32> {
     let Read = [{ node->getAArch64SMEAttributes() }];
   }
+  /* TODO: How to serialize FunctionEffect / FunctionEffectSet?
+  def : Property<"functionEffects", FunctionEffectSet> {
+    let Read = [{ node->getFunctionEffects() }];
+  }*/
 
   def : Creator<[{
     auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
@@ -342,6 +346,7 @@ let Class = FunctionProtoType in {
     epi.ExtParameterInfos =
       extParameterInfo.empty() ? nullptr : extParameterInfo.data();
     epi.AArch64SMEAttributes = AArch64SMEAttributes;
+    //epi.FunctionEffects = functionEffects;
     return ctx.getFunctionType(returnType, parameters, epi);
   }]>;
 }
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index fd7970d0451acd..78bbe5185741b4 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1402,6 +1402,29 @@ def CXX11NoReturn : InheritableAttr {
   let Documentation = [CXX11NoReturnDocs];
 }
 
+def NoLock : DeclOrTypeAttr {
+  let Spellings = [CXX11<"clang", "nolock">,
+                   C2x<"clang", "nolock">,
+                   GNU<"clang_nolock">];
+
+  // Subjects - not needed?
+  //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
+  let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
+  let HasCustomParsing = 0;
+  let Documentation = [NoLockNoAllocDocs];
+}
+
+def NoAlloc : DeclOrTypeAttr {
+  let Spellings = [CXX11<"clang", "noalloc">,
+                   C2x<"clang", "noalloc">,
+                   GNU<"clang_noalloc">];
+  // Subjects - not needed?
+  //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
+  let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
+  let HasCustomParsing = 0;
+  let Documentation = [NoLockNoAllocDocs];
+}
+
 // Similar to CUDA, OpenCL attributes do not receive a [[]] spelling because
 // the specification does not expose them with one currently.
 def OpenCLKernel : InheritableAttr {
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 2c07cd09b0d5b7..bb897c708ebbea 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7973,3 +7973,20 @@ requirement:
   }
   }];
 }
+
+def NoLockNoAllocDocs : Documentation {
+  let Category = DocCatType;
+  let Content = [{
+The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks, 
+function pointers, lambdas, and member functions. The attributes identify code
+which must not allocate memory or lock, and the compiler uses the attributes to
+verify these requirements.
+
+Like ``noexcept``, ``nolock`` and ``noalloc`` have an optional argument, a
+compile-time constant boolean expression. By default, the argument is true, so
+``[[clang::nolock(true)]]`` is equivalent to ``[[clang::nolock]]``, and declares
+the function type as never locking.
+
+TODO: how much of the RFC to include here? Make it a separate page?
+  }];
+}
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 3f14167d6b8469..0d6e9f0289f6bc 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1507,3 +1507,7 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
 // Warnings and fixes to support the "safe buffers" programming model.
 def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
 def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>;
+
+// Warnings and notes related to the function effects system underlying
+// the nolock and noalloc attributes.
+def FunctionEffects : DiagGroup<"function-effects">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index c54105507753eb..5777f7dfbbcad1 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10778,6 +10778,101 @@ def warn_imp_cast_drops_unaligned : Warning<
   "implicit cast from type %0 to type %1 drops __unaligned qualifier">,
   InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
 
+def warn_func_effect_allocates : Warning<
+  "'%0' function '%1' must not allocate or deallocate memory">,
+  InGroup<FunctionEffects>;
+
+def note_func_effect_allocates : Note<
+  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
+
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function '%1' must not throw or catch exceptions">,
+  InGroup<FunctionEffects>;
+
+def note_func_effect_throws_or_catches : Note<
+  "'%1' cannot be inferred '%0' because it throws or catches exceptions">;
+
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function '%1' must not have static locals">,
+  InGroup<FunctionEffects>;
+
+def note_func_effect_has_static_local : Note<
+  "'%1' cannot be inferred '%0' because it has a static local">;
+
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function '%1' must not use thread-local variables">,
+  InGroup<FunctionEffects>;
+
+def note_func_effect_uses_thread_local : Note<
+  "'%1' cannot be inferred '%0' because it uses a thread-local variable">;
+
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function '%1' must not access an ObjC method or property">,
+  InGroup<FunctionEffects>;
+
+def note_func_effect_calls_objc : Note<
+  "'%1' cannot be inferred '%0' because it accesses an ObjC method or property">;
+
+def warn_func_effect_calls_disallowed_func : Warning<
+  "'%0' function '%1' must not call non-'%0' function '%2'">,
+  InGroup<FunctionEffects>;
+
+// UNTESTED
+def warn_func_effect_calls_disallowed_expr : Warning<
+  "'%0' function '%1' must not call non-'%0' expression">,
+  InGroup<FunctionEffects>;
+
+def note_func_effect_calls_disallowed_func : Note<
+  "'%1' cannot be inferred '%0' because it calls non-'%0' function '%2'">;
+
+def note_func_effect_call_extern : Note<
+  "'%1' cannot be inferred '%0' because it has no definition in this translation unit">;
+
+def note_func_effect_call_not_inferrable : Note<
+  "'%1' does not permit inference of '%0'">;
+
+def note_func_effect_call_virtual : Note<
+  "'%1' cannot be inferred '%0' because it is virtual">;
+
+def note_func_effect_call_func_ptr : Note<
+  "'%1' cannot be inferred '%0' because it is a function pointer">;
+
+// TODO: Not currently being generated
+def warn_perf_annotation_implies_noexcept : Warning<
+  "'%0' function should be declared noexcept">,
+  InGroup<PerfAnnotationImpliesNoexcept>;
+
+// TODO: Not currently being generated
+def warn_func_effect_false_on_type : Warning<
+  "only functions/methods/blocks may be declared %0(false)">,
+  InGroup<FunctionEffects>;
+
+// TODO: can the usual template expansion notes be used?
+def note_func_effect_from_template : Note<
+  "in template expansion here">;
+
+// TODO: Needs to be tested
+def warn_incompatible_func_effects : Warning<
+  "attributes '%0' and '%1' are incompatible">,
+  InGroup<FunctionEffects>;
+
+// spoofing nolock/noalloc
+def warn_invalid_add_func_effects : Warning<
+  "attribute '%0' should not be added via type conversion">,
+  InGroup<FunctionEffects>;
+
+def warn_invalid_remove_func_effects : Warning<
+  "attribute '%0' should not be removed via type conversion">,
+  InGroup<FunctionEffects>;
+
+def warn_mismatched_func_effect_override : Warning<
+  "attribute '%0' on overriding function does not match base version">,
+  InGroup<FunctionEffects>;
+
+def warn_mismatched_func_effect_redeclaration : Warning<
+  "attribute '%0' on function does not match previous declaration">,
+  InGroup<FunctionEffects>;
+
 } // end of sema category
 
 let CategoryName = "API Notes Issue" in {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 267c79cc057cba..2772f882a5c09d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -721,6 +721,12 @@ class Sema final {
   /// Load weak undeclared identifiers from the external source.
   void LoadExternalWeakUndeclaredIdentifiers();
 
+  /// All functions/lambdas/blocks which have bodies and which have a non-empty
+  /// FunctionEffectSet to be verified.
+  SmallVector<const Decl *> DeclsWithUnverifiedEffects;
+  /// The union of all effects present on DeclsWithUnverifiedEffects.
+  MutableFunctionEffectSet AllEffectsToVerify;
+
   /// Determine if VD, which must be a variable or function, is an external
   /// symbol that nonetheless can't be referenced from outside this translation
   /// unit because its type has no linkage and it's not extern "C".
@@ -940,6 +946,10 @@ class Sema final {
   /// Warn when implicitly casting 0 to nullptr.
   void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
 
+  /// Warn when implicitly changing function effects.
+  void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
+                                        SourceLocation Loc);
+
   bool makeUnavailableInSystemHeader(SourceLocation loc,
                                      UnavailableAttr::ImplicitReason reason);
 
@@ -3132,6 +3142,9 @@ class Sema final {
                               QualType T, TypeSourceInfo *TSInfo,
                               StorageClass SC);
 
+  /// Potentially add a FunctionDecl or BlockDecl to DeclsWithUnverifiedEffects.
+  void CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX);
+
   // Contexts where using non-trivial C union types can be disallowed. This is
   // passed to err_non_trivial_c_union_in_invalid_context.
   enum NonTrivialCUnionContext {
@@ -3739,6 +3752,9 @@ class Sema final {
                                       StringRef &Str,
                                       SourceLocation *ArgLocation = nullptr);
 
+  bool checkBoolExprArgumentAttr(const ParsedAttr &Attr, unsigned ArgNum,
+                                 bool &Value);
+
   /// Determine if type T is a valid subject for a nonnull and similar
   /// attributes. By default, we look through references (the behavior used by
   /// nonnull), but if the second parameter is true, then we treat a reference
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 5a8fae76a43a4d..43fccb117a897d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4502,11 +4502,13 @@ QualType ASTContext::getFunctionTypeInternal(
   size_t Size = FunctionProtoType::totalSizeToAlloc<
       QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
       FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
-      Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers>(
+      Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, 
+      FunctionEffectSet, Qualifiers>(
       NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
       EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
       ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
       EPI.ExtParameterInfos ? NumArgs : 0,
+      EPI.FunctionEffects ? 1 : 0,
       EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0);
 
   auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType));
@@ -10435,6 +10437,15 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
     allRTypes = false;
 
   FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn);
+  FunctionEffectSet FromFX, ToFX;
+  std::optional<FunctionEffectSet> MergedFX;
+
+  if (lproto) ToFX = lproto->getFunctionEffects();
+  if (rproto) FromFX = rproto->getFunctionEffects();
+  if (ToFX != FromFX) {
+    // We want the intersection of the effects...
+    MergedFX = FunctionEffectSet::create(FromFX & ToFX);
+  }
 
   if (lproto && rproto) { // two C99 style function prototypes
     assert((AllowCXX ||
@@ -10487,13 +10498,18 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
         allRTypes = false;
     }
 
-    if (allLTypes) return lhs;
-    if (allRTypes) return rhs;
+    if (!MergedFX) { // effects changed so we can't return either side unaltered
+      if (allLTypes) return lhs;
+      if (allRTypes) return rhs;
+    }
 
     FunctionProtoType::ExtProtoInfo EPI = lproto->getExtProtoInfo();
     EPI.ExtInfo = einfo;
     EPI.ExtParameterInfos =
         newParamInfos.empty() ? nullptr : newParamInfos.data();
+    if (MergedFX) {
+      EPI.FunctionEffects = *MergedFX;
+    }
     return getFunctionType(retType, types, EPI);
   }
 
@@ -10526,11 +10542,16 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
         return {};
     }
 
-    if (allLTypes) return lhs;
-    if (allRTypes) return rhs;
+    if (!MergedFX) { // effects changed so we can't return either side unaltered
+      if (allLTypes) return lhs;
+      if (allRTypes) return rhs;
+    }
 
     FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
     EPI.ExtInfo = einfo;
+    if (MergedFX) {
+      EPI.FunctionEffects = *MergedFX;
+    }
     return getFunctionType(retType, proto->getParamTypes(), EPI);
   }
 
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 8626f04012f7d4..596f4ea2e67176 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3517,6 +3517,61 @@ bool FunctionDecl::isMemberLikeConstrainedFriend() const {
   return FriendConstraintRefersToEnclosingTemplate();
 }
 
+static void examine(const Decl& D)
+{
+  PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
+  PP.TerseOutput = 1;
+  D.print(llvm::outs(), PP);
+}
+
+#if TEMP_DISABLE
+// This constant controls the default policy.
+constexpr static bool kDefaultCanInferPerfAnnotation = true;
+
+static bool declCanInferPerfAnnotation(const Decl &D, QualType QT) {
+  // llvm::outs() << "declCanInferPerfAnnotation " << &D << "\n";
+  // examine(D);
+
+  // nolock(false) or noalloc(false) disables inference.
+  if (QT->disallowPerfAnnotationInference()) {
+    // llvm::outs() << "  disallowed by QT\n";
+    return false;
+  }
+  if (auto *IA = D.getAttr<PerformanceInferredAttr>()) {
+    // llvm::outs() << "  decl has attr " << IA->getCanInfer() << "\n";
+    return IA->getCanInfer();
+  }
+  if (auto *Method = dyn_cast<CXXMethodDecl>(&D)) {
+    auto *Class = Method->getParent();
+    if (auto *IA = Class->getAttr<PerformanceInferredAttr>()) {
+      // llvm::outs() << "  class has attr " << IA->getCanInfer() << "\n";
+      return IA->getCanInfer();
+    }
+  }
+
+  // for (const DeclContext *DC = D.getDeclContext(); DC != nullptr; DC = DC->getParent()) {
+  //   if (auto *Decl2 = dyn_cast<Decl>(DC)) {
+  //     examine(*Decl2);
+  //     if (auto *IA = Decl2->getAttr<PerformanceInferredAttr>()) {
+  //       llvm::outs() << "  decl2 has attr " << IA->getCanInfer() << "\n";
+  //       return IA->getCanInfer();
+  //     }
+  //   }
+  // }
+
+  // llvm::outs() << "  result: false\n";
+  return kDefaultCanInferPerfAnnotation;
+}
+
+PerfAnnotation FunctionDecl::getPerfAnnotation() const {
+  return getType()->getPerfAnnotation();
+}
+
+bool FunctionDecl::canInferPerfAnnotation() const {
+  return declCanInferPerfAnnotation(*this, getType());
+}
+#endif // TEMP_DISABLE
+
 MultiVersionKind FunctionDecl::getMultiVersionKind() const {
   if (hasAttr<TargetAttr>())
     return MultiVersionKind::Target;
@@ -5226,6 +5281,36 @@ SourceRange BlockDecl::getSourceRange() const {
   return SourceRange(getLocation(), Body ? Body->getEndLoc() : getLocation());
 }
 
+FunctionEffectSet BlockDecl::getFunctionEffects() const {
+  if (auto* TSI = getSignatureAsWritten()) {
+    if (auto* FPT = TSI->getType()->getAs<FunctionProtoType>()) {
+      return FPT->getFunctionEffects();
+    }
+  }
+  return {};
+}
+
+#if TEMP_DISABLE
+PerfAnnotation BlockDecl::getPerfAnnotation() const
+{
+  if (auto* TSI = getSignatureAsWritten()) {
+    return TSI->getType()->getPerfAnnotation();
+  }
+  return PerfAnnotation::None;
+}
+#endif // TEMP_DISABLE
+
+#if 0
+// unused
+bool BlockDecl::canInferPerfAnnotation() const
+{
+  if (auto* TSI = getSignatureAsWritten()) {
+    return declCanInferPerfAnnotation(*this, TSI->getType());
+  }
+  return kDefaultCanInferPerfAnnotation;
+}
+#endif
+
 //===----------------------------------------------------------------------===//
 // Other Decl Allocation/Deallocation Method Implementations
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 22666184c56ccf..ce209be2c3a079 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3583,6 +3583,13 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
     auto &EllipsisLoc = *getTrailingObjects<SourceLocation>();
     EllipsisLoc = epi.EllipsisLoc;
   }
+
+  if (epi.FunctionEffects) {
+    auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
+    ExtraBits.HasFunctionEffects = true;
+
+    *getTrailingObjects<FunctionEffectSet>() = epi.FunctionEffects;
+  }
 }
 
 bool FunctionProtoType::hasDependentExceptionSpec() const {
@@ -3666,11 +3673,14 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
   // Finally we have a trailing return type flag (bool)
   // combined with AArch64 SME Attributes, to save space:
   //      int
+  // Then add the FunctionEffects
   //
   // There is no ambiguity between the consumed arguments and an empty EH
   // spec because of the leading 'bool' which unambiguously indicates
   // whether the following bool is the EH spec or part of the arguments.
 
+  ID.AddPointer(epi.FunctionEffects.getOpaqueValue()); // TODO: Where???
+
   ID.AddPointer(Result.getAsOpaquePtr());
   for (unsigned i = 0; i != NumParams; ++i)
     ID.AddPointer(ArgTys[i].getAsOpaquePtr());
@@ -3684,6 +3694,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
   ID.AddInteger(unsigned(epi.Variadic) +
                 (epi.RefQualifier << 1) +
                 (epi.ExceptionSpec.Type << 3));
+
   ID.Add(epi.TypeQuals);
   if (epi.ExceptionSpec.Type == EST_Dynamic) {
     for (QualType Ex : epi.ExceptionSpec.Exceptions)
@@ -4912,3 +4923,267 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
   Profile(ID, Context, getDeducedType(), getKeyword(), isDependentType(),
           getTypeConstraintConcept(), getTypeConstraintArguments());
 }
+
+
+FunctionEffect::~FunctionEffect() = default;
+
+bool FunctionEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
+    QualType NewType, FunctionEffectSet NewFX) const
+{
+  return false;
+}
+
+bool FunctionEffect::diagnoseRedeclaration(bool adding,
+    const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
+    const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const { return false; }
+
+bool FunctionEffect::diagnoseMethodOverride(bool adding,
+    const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
+    const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const { return false; }
+
+bool FunctionEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
+{
+  return false;
+}
+
+bool FunctionEffect::diagnoseFunctionCall(bool direct, 
+    const Decl* Caller, FunctionEffectSet CallerFX,
+    CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
+{
+  return false;
+}
+
+const NoLockNoAllocEffect& NoLockNoAllocEffect::nolock_instance()
+{
+  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  return global;
+}
+
+const NoLockNoAllocEffect& NoLockNoAllocEffect::noalloc_instance()
+{
+  static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
+  return global;
+}
+
+// TODO: Separate flags for noalloc
+NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType ty, const char* name)
+  : FunctionEffect{ ty, kRequiresVerification | kVerifyCalls | kInferrableOnCallees | kExcludeThrow | kExcludeCatch
+    | kExcludeObjCMessageSend | kExcludeStaticLocalVars | kExcludeThreadLocalVars, name }
+{
+}
+
+NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
+
+std::string NoLockNoAllocEffect::attribute() const
+{
+  return std::string{ "__attribute__((clang_" } + name().str() + "))";
+}
+
+bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
+    QualType NewType, FunctionEffectSet NewFX) const
+{
+  // noalloc can't be added (spoofed) during a conversion, unless we have nolock
+  if (adding) {
+    if (!isNoLock()) {
+      for (const auto* effect : OldFX) {
+        if (effect->type() == kNoLockTrue)
+          return false;
+      }
+    }
+    // nolock can't be added (spoofed) during a conversion.
+    return true;
+  }
+  return false;
+}
+
+bool NoLockNoAllocEffect::diagnoseRedeclaration(bool adding,
+  const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
+  const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const
+{
+  // nolock/noalloc can't be removed in a redeclaration
+  // adding -> false, removing -> true (diagnose)
+  return !adding;
+}
+
+bool NoLockNoAllocEffect::diagnoseMethodOverride(bool adding,
+  const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
+  const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const
+{
+  // nolock/noalloc can't be removed from an override
+  return !adding;
+}
+
+bool NoLockNoAllocEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
+{
+  // Does the Decl have nolock(false) / noalloc(false) ?
+  QualType QT;
+  if (isa<BlockDecl>(Caller)) {
+    const auto* TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
+    QT = TSI->getType();
+  } else if (isa<ValueDecl>(Caller)) {
+    QT = cast<ValueDecl>(Caller)->getType();
+  } else {
+    return false;
+  }
+  if (QT->hasAttr(isNoLock() ? attr::Kind::NoLock : attr::Kind::NoAlloc)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool NoLockNoAllocEffect::diagnoseFunctionCall(bool direct, 
+    const Decl* Caller, FunctionEffectSet CallerFX,
+    CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
+{
+  const EffectType callerType = type();
+  for (const auto* effect : CalleeFX) {
+    const EffectType ty = effect->type();
+    if (ty == callerType || (callerType == kNoAllocTrue && ty == kNoLockTrue)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// =====
+
+void MutableFunctionEffectSet::insert(const FunctionEffect* effect)
+{
+  auto iter = std::lower_bound(begin(), end(), effect);
+  if (*iter != effect) {
+    insert(iter, effect);
+  }
+}
+
+MutableFunctionEffectSet& MutableFunctionEffectSet::operator|=(FunctionEffectSet rhs)
+{
+  // TODO: For large rhs sets, use set_union or a custom insert-in-place
+  for (const auto* effect : rhs) {
+    insert(effect);
+  }
+  return *this;
+}
+
+// This could be simpler if there were a simple set container that could be queried by
+// ArrayRef but which stored something else. Possibly a DenseMap with void values?
+FunctionEffectSet FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect*> items)
+{
+  if (items.empty()) {
+    return FunctionEffectSet{};
+  }
+  if (items.size() == 1) {
+    return FunctionEffectSet{ items[0] };
+  }
+
+  UniquedAndSortedFX newSet{ items }; // just copies the ArrayRef
+
+  // SmallSet only has contains(), so it provides no way to obtain the uniqued value.
+  static std::set<UniquedAndSortedFX> uniquedFXSets;
+
+  // See if we already have this set.
+  const auto iter = uniquedFXSets.find(newSet);
+  if (iter != uniquedFXSets.end()) {
+    return FunctionEffectSet{ &*iter };
+  }
+
+  // Copy the incoming array to permanent storage.
+  auto* storage = new const FunctionEffect*[items.size()];
+  std::copy(items.begin(), items.end(), storage);
+
+  // Make a new wrapper and insert it into the set.
+  newSet = UniquedAndSortedFX{ storage, items.size() };
+  auto [insiter, good] = uniquedFXSets.insert(newSet);
+  return FunctionEffectSet{ &*insiter };
+}
+
+FunctionEffectSet FunctionEffectSet::operator|(const FunctionEffectSet& rhs) const
+{
+  const FunctionEffectSet& lhs = *this;
+  if (lhs.empty()) {
+    return rhs;
+  }
+  if (rhs.empty()) {
+    return lhs;
+  }
+  // Optimize the case where the two sets are identical
+  if (lhs == rhs) {
+    return lhs;
+  }
+
+  MutableFunctionEffectSet vec;
+  vec.reserve(lhs.size() + rhs.size());
+  std::set_union(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+  // The result of a set operation is an ordered/unique set.
+  return FunctionEffectSet::create(vec);
+}
+
+MutableFunctionEffectSet FunctionEffectSet::operator&(const FunctionEffectSet& rhs) const
+{
+  const FunctionEffectSet& lhs = *this;
+  if (lhs.empty() || rhs.empty()) {
+    return {};
+  }
+
+  MutableFunctionEffectSet vec;
+  std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+  // The result of a set operation is an ordered/unique set.
+  return vec;
+}
+
+// TODO: inline?
+FunctionEffectSet FunctionEffectSet::get(const Type& TyRef)
+{
+  const Type* Ty = &TyRef;      
+  if (Ty->isPointerType())
+    Ty = Ty->getPointeeType().getTypePtr();
+  if (const auto* FPT = Ty->getAs<FunctionProtoType>())
+    return FPT->getFunctionEffects();
+  return {};
+}
+
+FunctionEffectSet::Differences FunctionEffectSet::differences(
+    const FunctionEffectSet& Old, const FunctionEffectSet& New)
+{
+  // TODO: Could be a one-pass algorithm.
+  Differences result;
+  for (const auto* effect : (New - Old)) {
+    result.emplace_back(effect, true);
+  }
+  for (const auto* effect : (Old - New)) {
+    result.emplace_back(effect, false);
+  }
+  return result;
+}
+
+MutableFunctionEffectSet FunctionEffectSet::operator-(const FunctionEffectSet& rhs) const
+{
+  const FunctionEffectSet& lhs = *this;
+  MutableFunctionEffectSet result;
+
+  std::set_difference(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(result));
+  return result;
+}
+
+bool FunctionEffectSet::operator<(const FunctionEffectSet& rhs) const
+{
+  const FunctionEffectSet& lhs = *this;
+  return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+}
+
+bool FunctionEffectSet::UniquedAndSortedFX::operator<(const UniquedAndSortedFX& rhs) const
+{
+  return this < &rhs;
+}
+
+void FunctionEffectSet::dump(llvm::raw_ostream &OS) const
+{
+  OS << "FX{";
+  bool first = true;
+  for (const auto* effect : *this) {
+    if (!first) OS << ", ";
+    else first = false;
+    OS << effect->name();
+  }
+  OS << "}";
+}
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 7dcc4348f8e036..560f2faeb9e503 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -984,8 +984,15 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
     OS << " &&";
     break;
   }
+
   T->printExceptionSpecification(OS, Policy);
 
+  if (auto effects = T->getFunctionEffects()) {
+    for (const auto* effect : effects) {
+      OS << " " << effect->attribute();
+    }
+  }
+
   if (T->hasTrailingReturn()) {
     OS << " -> ";
     print(T->getReturnType(), OS, StringRef());
@@ -1904,6 +1911,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
   case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
   case attr::IntelOclBicc: OS << "inteloclbicc"; break;
+
   case attr::PreserveMost:
     OS << "preserve_most";
     break;
@@ -1930,6 +1938,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   // Nothing to print for this attribute.
   case attr::HLSLParamModifier:
     break;
+  case attr::NoLock:
+    OS << "clang_nolock(false)";
+    break;
+  case attr::NoAlloc:
+    OS << "clang_noalloc(false)";
+    break;
   }
   OS << "))";
 }
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 6992ba9ad9a756..a0d813fa4d3d14 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2380,6 +2380,1494 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// =============================================================================
+
+#define FX_ANALYZER_VERIFY_DECL_LIST 1
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  // DeclExternWithoutConstraint, // TODO: not used?
+  DeclWithoutConstraintOrInference,
+  //DeclVirtualWithoutConstraint,
+  //DeclFuncPtrWithoutConstraint,
+
+  CallsUnsafeDecl,
+  CallsDisallowedExpr,
+};
+
+struct Diagnostic {
+  const FunctionEffect* Effect = nullptr;
+  const Decl* Callee = nullptr; // only valid for Calls*
+  SourceLocation Loc{};
+  DiagnosticID ID{ DiagnosticID::None };
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc, const Decl* Callee = nullptr)
+    : Effect{ Effect }, Callee{ Callee }, Loc{ Loc }, ID{ ID }
+  {
+  }
+};
+
+enum class SpecialFuncType : uint8_t {
+  None, OperatorNew, OperatorDelete
+};
+enum class CallType {
+  Unknown, Function, Virtual, Block
+  // unknown: probably function pointer
+};
+
+// Return whether the function CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl* FD)
+{
+  if (!(FD->hasBody() || FD->isInlined())) {
+    // externally defined; we couldn't verify if we wanted to.
+    return false;
+  }
+  if (FD->isTrivial()) {
+    // Otherwise `struct x { int a; };` would have an unverifiable default
+    // constructor.
+    return true;
+  }
+  return true;
+}
+
+// Transitory, more extended information about a callable, which can be a function,
+// block, function pointer...
+struct CallableInfo {
+  const Decl* CDecl;
+  mutable std::optional<std::string> MaybeName; // mutable because built on demand in const method
+  SpecialFuncType FuncType{ SpecialFuncType::None };
+  FunctionEffectSet Effects;
+  CallType CType{ CallType::Unknown };
+  // bool IsAllowListed{ false };
+
+  CallableInfo(const Decl& CD, SpecialFuncType FT = SpecialFuncType::None)
+    : CDecl{ &CD }, FuncType{ FT }
+  {
+    //llvm::errs() << "CallableInfo " << name() << "\n";
+
+    if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
+      assert(FD->getCanonicalDecl() == FD);
+      // Use the function's definition, if any.
+      if (auto* Def = FD->getDefinition()) {
+        CDecl = FD = Def;
+      }
+      CType = CallType::Function;
+      if (auto* Method = dyn_cast<CXXMethodDecl>(FD)) {
+        if (Method->isVirtual()) {
+          CType = CallType::Virtual;
+        }
+      }
+      Effects = FD->getFunctionEffects();
+
+      // TODO: Generalize via noreturn??? but that would cover exceptions too.
+      // if (name() == "__assert_rtn") {
+      //   // big hack because it's hard to get the attribute to stick on it
+      //   // through a redeclaration, not sure why.
+      //   IsAllowListed = true;
+      // }
+    } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+      CType = CallType::Block;
+      Effects = BD->getFunctionEffects();
+    } else if (auto* VD = dyn_cast<ValueDecl>(CDecl)) {
+      // ValueDecl is function, enum, or variable, so just look at the type.
+      Effects = FunctionEffectSet::get(*VD->getType());
+    }
+  }
+
+  bool isDirectCall() const {
+    return CType == CallType::Function || CType == CallType::Block;
+  }
+
+  bool isVerifiable() const
+  {
+    switch (CType) {
+    case CallType::Unknown:
+    case CallType::Virtual:
+      break;
+    case CallType::Block:
+      return true;
+    case CallType::Function:
+      return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+    }
+    return false;
+  }
+
+  /// Generate a name for logging.
+  std::string name(Sema& sema) const
+  {
+    if (!MaybeName) {
+      std::string Name;
+      llvm::raw_string_ostream OS(Name);
+
+      if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
+        FD->getNameForDiagnostic(OS, sema.getPrintingPolicy(),
+                                    /*Qualified=*/true);
+      } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+        OS << "(block " << BD->getBlockManglingNumber() << ")";
+      } else if (auto* VD = dyn_cast<NamedDecl>(CDecl)) {
+        VD->printQualifiedName(OS);
+      }
+      MaybeName = Name;
+    }
+    return *MaybeName;
+  }
+};
+
+// ----------
+// Map effects to single diagnostics.
+class EffectToDiagnosticMap {
+  // Since we currently only have a tiny number of effects (typically no more than 1),
+  // use a sorted SmallVector.
+  using Element = std::pair<const FunctionEffect*, Diagnostic>;
+  using ImplVec = llvm::SmallVector<Element>;
+  std::unique_ptr<ImplVec> Impl;
+public:
+  Diagnostic& getOrInsertDefault(const FunctionEffect* key)
+  {
+    if (Impl == nullptr) {
+      Impl = std::make_unique<llvm::SmallVector<Element>>();
+      auto& item = Impl->emplace_back();
+      item.first = key;
+      return item.second;
+    }
+    Element elem{ key, {} };
+    auto iter = _find(elem);
+    if (iter != Impl->end() && iter->first == key) {
+      return iter->second;
+    }
+    iter = Impl->insert(iter, elem);
+    return iter->second;
+  }
+
+  const Diagnostic* lookup(const FunctionEffect* key)
+  {
+    if (Impl == nullptr) {
+      return nullptr;
+    }
+    Element elem{ key, {} };
+    auto iter = _find(elem);
+    if (iter != Impl->end() && iter->first == key) {
+      return &iter->second;
+    }
+    return nullptr;
+  }
+
+  size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+  ImplVec::iterator _find(const Element& elem)
+  {
+    return std::lower_bound(Impl->begin(), Impl->end(), elem, [](const Element& lhs, const Element& rhs) {
+      return lhs.first < rhs.first;
+    });
+  }
+};
+
+// ----------
+// State pertaining to a function whose AST is walked. Since there are potentially a large
+// number of these objects, it needs care about size.
+class PendingFunctionAnalysis {
+  // Current size: 5 pointers
+  friend class CompleteFunctionAnalysis;
+
+  struct DirectCall {
+    const Decl* Callee;
+    SourceLocation CallLoc;
+  };
+
+public:
+  // We always have two disjoint sets of effects to verify:
+  // 1. Effects declared explicitly by this function.
+  // 2. All other inferrable effects needing verification.
+  FunctionEffectSet DeclaredVerifiableEffects;
+  FunctionEffectSet FXToInfer;
+
+private:
+  // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr to optimize
+  // size for the case of 0 diagnostics.
+  std::unique_ptr<SmallVector<Diagnostic>> DiagnosticsForExplicitFX;
+
+  // Potential diagnostics pertaining to other, non-explicit, inferrable effects.
+  EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+  std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
+
+public:
+  PendingFunctionAnalysis(const CallableInfo& cinfo, FunctionEffectSet AllInferrableEffectsToVerify)
+  {
+    MutableFunctionEffectSet fx;
+    for (const auto* effect : cinfo.Effects) {
+      if (effect->getFlags() & FunctionEffect::kRequiresVerification) {
+        fx.insert(effect);
+      }
+    }
+    DeclaredVerifiableEffects = FunctionEffectSet::create(fx);
+
+    // Check for effects we are not allowed to infer
+    fx.clear();
+    for (const auto* effect : AllInferrableEffectsToVerify) {
+      if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
+        fx.insert(effect);
+      } else {
+        // Add a diagnostic for this effect if a caller were to
+        // try to infer it.
+        auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
+        diag = Diagnostic{ effect, DiagnosticID::DeclWithoutConstraintOrInference,
+          cinfo.CDecl->getLocation() };
+      }
+    }
+    // fx is now the set of inferrable effects which are not prohibited
+    FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) - DeclaredVerifiableEffects);
+  }
+
+  void checkAddDiagnostic(bool inferring, const Diagnostic& NewDiag)
+  {
+    if (!inferring) {
+      if (DiagnosticsForExplicitFX == nullptr) {
+        DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
+      }
+      DiagnosticsForExplicitFX->push_back(NewDiag);
+    } else {
+      auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
+      if (diag.ID == DiagnosticID::None) {
+        diag = NewDiag;
+      }
+    }
+  }
+
+  void addUnverifiedDirectCall(const Decl* D, SourceLocation CallLoc)
+  {
+    if (UnverifiedDirectCalls == nullptr) {
+      UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
+    }
+    UnverifiedDirectCalls->emplace_back(DirectCall{ D, CallLoc });
+  }
+
+  // Analysis is complete when there are no unverified direct calls.
+  bool isComplete() const
+  {
+    return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
+  }
+
+  const Diagnostic* diagnosticForInferrableEffect(const FunctionEffect* effect)
+  {
+    return InferrableEffectToFirstDiagnostic.lookup(effect);
+  }
+
+  const SmallVector<DirectCall>& unverifiedCalls() const
+  {
+    assert(!isComplete());
+    return *UnverifiedDirectCalls;
+  }
+
+  SmallVector<Diagnostic>* getDiagnosticsForExplicitFX() const
+  {
+    return DiagnosticsForExplicitFX.get();
+  }
+
+  void dump(llvm::raw_ostream& OS) const
+  {
+    OS << "Pending: Declared ";
+    DeclaredVerifiableEffects.dump(OS);
+    OS << ", " << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0) << " diags; ";
+    OS << " Infer ";
+    FXToInfer.dump(OS);
+    OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+  }
+};
+
+// ----------
+class CompleteFunctionAnalysis {
+  // Current size: 2 pointers
+public:
+  // Has effects which are both the declared ones -- not to be inferred -- plus ones which
+  // have been successfully inferred. These are all considered "verified" for the purposes
+  // of callers; any issue with verifying declared effects has already been reported and
+  // is not the problem of any caller.
+  FunctionEffectSet VerifiedEffects;
+
+private:
+  // This is used to generate notes about failed inference.
+  EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+public:
+  CompleteFunctionAnalysis(PendingFunctionAnalysis& pending, FunctionEffectSet funcFX, FunctionEffectSet AllInferrableEffectsToVerify)
+  {
+    MutableFunctionEffectSet verified;
+    verified |= funcFX;
+    for (const auto* effect : AllInferrableEffectsToVerify) {
+      if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
+        verified.insert(effect);
+      }
+    }
+    VerifiedEffects = FunctionEffectSet::create(verified);
+
+    InferrableEffectToFirstDiagnostic = std::move(pending.InferrableEffectToFirstDiagnostic);
+  }
+
+  const Diagnostic* firstDiagnosticForEffect(const FunctionEffect* effect)
+  {
+    // TODO: is this correct?
+    return InferrableEffectToFirstDiagnostic.lookup(effect);
+  }
+
+  void dump(llvm::raw_ostream& OS) const
+  {
+    OS << "Complete: Verified ";
+    VerifiedEffects.dump(OS);
+    OS << "; Infer ";
+    OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+  }
+};
+
+/*
+	TODO: nolock and noalloc imply noexcept
+        if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
+          if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
+              != clang::CT_Cannot) {
+            S.Diag(Callable->getBeginLoc(), 
+              diag::warn_perf_annotation_implies_noexcept)
+              << getPerfAnnotationSpelling(CInfo.PerfAnnot);
+          }
+        }
+*/
+
+// ==========
+class Analyzer {
+  constexpr static int kDebugLogLevel = 3;
+
+  // --
+  Sema& mSema;
+
+  // used from Sema:
+  //  SmallVector<const Decl *> DeclsWithUnverifiedEffects
+
+  // Subset of Sema.AllEffectsToVerify
+  FunctionEffectSet AllInferrableEffectsToVerify;
+
+  using FuncAnalysisPtr = llvm::PointerUnion<PendingFunctionAnalysis*, CompleteFunctionAnalysis*>;
+
+  // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger 
+  // than complete state, so use different objects to represent them.
+  // The state pointers are owned by the container.
+  struct AnalysisMap : public llvm::DenseMap<const Decl*, FuncAnalysisPtr> {
+  
+    ~AnalysisMap();
+
+    // use lookup()
+
+    CompleteFunctionAnalysis* completedAnalysisForDecl(const Decl* D) const
+    {
+      if (auto AP = lookup(D)) {
+        if (isa<CompleteFunctionAnalysis*>(AP)) {
+          return AP.get<CompleteFunctionAnalysis*>();
+        }
+      }
+      return nullptr;
+    }
+
+    void dump(Sema& S, llvm::raw_ostream& OS)
+    {
+      OS << "AnalysisMap:\n";
+      for (const auto& item : *this) {
+        CallableInfo CI{ *item.first };
+        const auto AP = item.second;
+        OS << item.first << " " << CI.name(S) << " : ";
+        if (AP.isNull()) {
+          OS << "null\n";
+        } else if (isa<CompleteFunctionAnalysis*>(AP)) {
+          auto* CFA = AP.get<CompleteFunctionAnalysis*>();
+          OS << CFA << " ";
+          CFA->dump(OS);
+        } else if (isa<PendingFunctionAnalysis*>(AP)) {
+          auto* PFA = AP.get<PendingFunctionAnalysis*>();
+          OS << PFA << " ";
+          PFA->dump(OS);
+        } else llvm_unreachable("never");
+      }
+    }
+  };
+  AnalysisMap DeclAnalysis;
+
+public:
+  Analyzer(Sema& S)
+    : mSema{ S }
+  {
+  }
+
+  void run(const TranslationUnitDecl& TU)
+  {
+#if FX_ANALYZER_VERIFY_DECL_LIST
+    verifyRootDecls(TU);
+#endif
+    // Gather all of the effects to be verified to see what operations need to be checked,
+    // and to see which ones are inferrable.
+    {
+      MutableFunctionEffectSet inferrableEffects;
+      for (const FunctionEffect* effect : mSema.AllEffectsToVerify) {
+        const auto Flags = effect->getFlags();
+        if (Flags & FunctionEffect::kInferrableOnCallees) {
+          inferrableEffects.insert(effect);
+        }
+      }
+      AllInferrableEffectsToVerify = FunctionEffectSet::create(inferrableEffects);
+      llvm::outs() << "AllInferrableEffectsToVerify: ";
+      AllInferrableEffectsToVerify.dump(llvm::outs());
+      llvm::outs() << "\n";
+    }
+
+    SmallVector<const Decl*>& verifyQueue = mSema.DeclsWithUnverifiedEffects;
+
+    // It's useful to use DeclsWithUnverifiedEffects as a stack for a
+    // depth-first traversal rather than have a secondary container. But first,
+    // reverse it, so Decls are verified in the order they are declared.
+    std::reverse(verifyQueue.begin(), verifyQueue.end());
+
+    while (!verifyQueue.empty()) {
+      const Decl* D = verifyQueue.back();
+      if (auto AP = DeclAnalysis.lookup(D)) {
+          if (isa<CompleteFunctionAnalysis*>(AP)) {
+            // already done
+            verifyQueue.pop_back();
+            continue;
+          }
+          if (isa<PendingFunctionAnalysis*>(AP)) {
+            // All children have been traversed; finish analysis.
+            auto* pending = AP.get<PendingFunctionAnalysis*>();
+            finishPendingAnalysis(D, pending);
+            verifyQueue.pop_back();
+            continue;
+          }
+          llvm_unreachable("shouldn't happen");
+      }
+
+      auto* pending = verifyDecl(D);
+      if (pending == nullptr) {
+        // completed now
+        verifyQueue.pop_back();
+        continue;
+      }
+
+      for (const auto& call : pending->unverifiedCalls()) {
+        // This lookup could be optimized out if the results could have been saved
+        // from followCall when we traversed the caller's AST. It would however
+        // make the check for recursion more complex.
+        auto AP = DeclAnalysis.lookup(call.Callee);
+        if (AP.isNull()) {
+          verifyQueue.push_back(call.Callee);
+          continue;
+        }
+        if (isa<PendingFunctionAnalysis*>(AP)) {
+          // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
+          __builtin_trap();
+        }
+        llvm_unreachable("shouldn't happen");
+      }
+    }
+  }
+
+private:
+  // Verify a single Decl. Return the pending structure if that was the result, else null.
+  // This method must not recurse.
+  PendingFunctionAnalysis* verifyDecl(const Decl *D)
+  {
+    const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
+    if (FD != nullptr) {
+      // Currently, built-in functions are always considered safe.
+      if (FD->getBuiltinID() != 0) {
+        return nullptr;
+      }
+      // If it doesn't have a body, then we have to rely on the declaration.
+
+#warning FIXME
+/*      if (!functionIsVerifiable(mSema, FD)) {
+        // const PerfAnnotation PA = FD->getPerfAnnotation();
+        // if (PA != PerfAnnotation::NoLock) {
+        //   Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
+        // }
+        return;
+      }*/
+    }
+    CallableInfo CInfo{ *D };
+
+    // Build a PendingFunctionAnalysis on the stack. If it turns out to be complete,
+    // we'll have avoided a heap allocation; if it's incomplete, it's a fairly
+    // trivial move to a heap-allocated object.
+    PendingFunctionAnalysis FAnalysis{ CInfo, AllInferrableEffectsToVerify };
+
+    llvm::outs() << "\nVerifying " << CInfo.name(mSema) << " ";
+    FAnalysis.dump(llvm::outs());
+
+    FunctionBodyASTVisitor Visitor{ *this, FAnalysis, CInfo };
+
+    Visitor.run();
+    if (FAnalysis.isComplete()) {
+      completeAnalysis(CInfo, FAnalysis);
+      return nullptr;
+    }
+    // Copy the pending analysis to the heap and save it in the map.
+    auto* pendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
+    DeclAnalysis[D] = pendingPtr;
+    llvm::outs() << "inserted pending " << pendingPtr << "\n";
+    DeclAnalysis.dump(mSema, llvm::outs());
+    return pendingPtr;
+  }
+
+  // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis and inserted
+  // in the container.
+  void completeAnalysis(const CallableInfo& CInfo, PendingFunctionAnalysis& pending)
+  {
+    if (auto* diags = pending.getDiagnosticsForExplicitFX()) {
+      emitDiagnostics(*diags, CInfo, mSema);
+    }
+    auto* completePtr = new CompleteFunctionAnalysis(pending,
+      CInfo.Effects, AllInferrableEffectsToVerify);
+    DeclAnalysis[CInfo.CDecl] = completePtr;
+    llvm::outs() << "inserted complete " << completePtr << "\n";
+    DeclAnalysis.dump(mSema, llvm::outs());
+  }
+
+  // Called after all direct calls requiring inference have been found -- or not.
+  // Generally replicates FunctionBodyASTVisitor::followCall() but without the
+  // possibility of inference.
+  void finishPendingAnalysis(const Decl* D, PendingFunctionAnalysis* pending)
+  {
+      CallableInfo Caller{ *D };
+      for (const auto& call : pending->unverifiedCalls()) {
+        CallableInfo Callee{ *call.Callee };
+        followCall(Caller, *pending, Callee, call.CallLoc, /*assertNoFurtherInference=*/true);
+      }
+      completeAnalysis(Caller, *pending);
+      delete pending;
+      llvm::outs() << "destroyed pending " << pending << "\n";
+  }
+
+  // Here we have a call to a Decl, either explicitly via a CallExpr or some
+  // other AST construct. CallableInfo pertains to the callee.
+  void followCall(const CallableInfo& Caller, PendingFunctionAnalysis &PFA,
+    const CallableInfo& Callee, SourceLocation CallLoc,
+    bool assertNoFurtherInference)
+  {
+    const bool DirectCall = Callee.isDirectCall();
+    FunctionEffectSet CalleeEffects = Callee.Effects;
+    bool isInferencePossible = DirectCall;
+
+    if (DirectCall) {
+      if (auto* CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+        CalleeEffects = CFA->VerifiedEffects;
+        isInferencePossible = false; // we've already traversed it
+      }
+    }
+    if (assertNoFurtherInference) {
+      assert(!isInferencePossible);
+    }
+    if (!Callee.isVerifiable()) {
+      isInferencePossible = false;
+    }
+    llvm::outs() << "followCall from " << Caller.name(mSema) << " to " << Callee.name(mSema) 
+      << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+    CalleeEffects.dump(llvm::outs());
+    llvm::outs() << "\n";
+    puts("");
+
+    auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
+      const auto flags = effect->getFlags();
+      if (flags & FunctionEffect::kVerifyCalls) {
+        const bool diagnose = effect->diagnoseFunctionCall(DirectCall, Caller.CDecl, Caller.Effects,
+          Callee.CDecl, CalleeEffects);
+        if (diagnose) {
+          // If inference is not allowed, or the target is indirect (virtual method/function ptr?),
+          // generate a diagnostic now.
+          if (!isInferencePossible || !(flags & FunctionEffect::kInferrableOnCallees)) {
+            if (Callee.FuncType == SpecialFuncType::None) {
+              PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::CallsUnsafeDecl, CallLoc, Callee.CDecl });
+            } else {
+              PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::AllocatesMemory, CallLoc });
+            }
+          } else {
+            // Inference is allowed and necessary; defer it.
+            PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+          }
+        }
+      }
+    };
+
+    for (auto* effect : PFA.DeclaredVerifiableEffects) {
+      check1Effect(effect, false);
+    }
+
+    for (auto* effect : PFA.FXToInfer) {
+      check1Effect(effect, true);
+    }
+  }
+
+  // Should only be called when determined to be complete.
+  void emitDiagnostics(SmallVector<Diagnostic>& Diags, const CallableInfo& CInfo, Sema& S)
+  {
+#define UNTESTED __builtin_trap();
+#define TESTED
+    const SourceManager& SM = S.getSourceManager();
+    std::sort(Diags.begin(), Diags.end(), [&SM](const Diagnostic& lhs, const Diagnostic& rhs) {
+      return SM.isBeforeInTranslationUnit(lhs.Loc, rhs.Loc);
+    });
+
+    const auto TopFuncName = CInfo.name(S);
+
+    // TODO: Can we get better template instantiation notes?
+    auto checkAddTemplateNote = [&](const Decl* D) {
+      if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+        while (FD != nullptr && FD->isTemplateInstantiation()) {
+          S.Diag(FD->getPointOfInstantiation(), diag::note_func_effect_from_template);
+          FD = FD->getTemplateInstantiationPattern();
+        }
+      }
+    };
+
+    // Top-level diagnostics are warnings.
+    for (const auto& Diag : Diags) {
+      StringRef effectName = Diag.Effect->name();
+      switch (Diag.ID) {
+      case DiagnosticID::None:
+      //case DiagnosticID::DeclExternWithoutConstraint:
+      case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen here
+      //case DiagnosticID::DeclVirtualWithoutConstraint:
+      //case DiagnosticID::DeclFuncPtrWithoutConstraint:
+        llvm_unreachable("Unexpected diagnostic kind");
+        break;
+      case DiagnosticID::AllocatesMemory:
+        S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName << TopFuncName;
+        checkAddTemplateNote(CInfo.CDecl);
+              TESTED
+        break;
+      case DiagnosticID::Throws:
+      case DiagnosticID::Catches:
+        S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) << effectName << TopFuncName;
+        checkAddTemplateNote(CInfo.CDecl);
+              TESTED
+        break;
+      case DiagnosticID::HasStaticLocal:
+        S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName << TopFuncName;
+        checkAddTemplateNote(CInfo.CDecl);
+              TESTED
+        break;
+      case DiagnosticID::AccessesThreadLocal:
+        S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) << effectName << TopFuncName;
+        checkAddTemplateNote(CInfo.CDecl);
+              TESTED
+        break;
+      case DiagnosticID::CallsObjC:
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName << TopFuncName;
+        checkAddTemplateNote(CInfo.CDecl);
+              TESTED
+        break;
+      case DiagnosticID::CallsDisallowedExpr:
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr) << effectName << TopFuncName;
+        checkAddTemplateNote(CInfo.CDecl);
+              UNTESTED
+        break;
+
+      case DiagnosticID::CallsUnsafeDecl:
+        {
+          CallableInfo CalleeInfo{ *Diag.Callee };
+          auto CalleeName = CalleeInfo.name(S);
+
+          S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func) << effectName << TopFuncName << CalleeName;
+          checkAddTemplateNote(CInfo.CDecl);
+
+          // Emit notes explaining the transitive chain of inferences: Why isn't the callee safe?
+          for (const auto* Callee = Diag.Callee; Callee != nullptr; ) {
+            std::optional<CallableInfo> MaybeNextCallee;
+            auto* completed = DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+            if (completed == nullptr) {
+              // No result - could be
+              // - non-inline
+              // - virtual
+              if (CalleeInfo.CType == CallType::Virtual) {
+                S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) << effectName << CalleeName;
+                TESTED
+              } else if (CalleeInfo.CType == CallType::Unknown) {
+                S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
+                TESTED
+              } else {
+                S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) << effectName << CalleeName;
+                TESTED
+              }
+              break;
+            }
+            const auto* pDiag2 = completed->firstDiagnosticForEffect(Diag.Effect);
+            if (pDiag2 == nullptr) {
+              break;
+            }
+
+            const auto& Diag2 = *pDiag2;
+            switch (Diag2.ID) {
+            case DiagnosticID::None:
+              llvm_unreachable("Unexpected diagnostic kind");
+              break;
+            // case DiagnosticID::DeclExternWithoutConstraint:
+            //   S.Diag(Diag2.Loc, diag::note_func_effect_call_extern) << effectName << CalleeName;
+            //   break;
+            case DiagnosticID::DeclWithoutConstraintOrInference:
+              S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable) << effectName << CalleeName;
+              TESTED
+              break;
+            // case DiagnosticID::DeclVirtualWithoutConstraint:
+            //   S.Diag(Diag2.Loc, diag::note_func_effect_call_virtual) << effectName << CalleeName;
+            //   break;
+            //case DiagnosticID::DeclFuncPtrWithoutConstraint:
+            case DiagnosticID::CallsDisallowedExpr:
+              S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
+              UNTESTED
+              break;
+            case DiagnosticID::AllocatesMemory:
+              S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName << CalleeName;
+              TESTED
+              break;
+            case DiagnosticID::Throws:
+            case DiagnosticID::Catches:
+              S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) << effectName << CalleeName;
+              TESTED
+              break;
+            case DiagnosticID::HasStaticLocal:
+              S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) << effectName << CalleeName;
+              UNTESTED
+              break;
+            case DiagnosticID::AccessesThreadLocal:
+              S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) << effectName << CalleeName;
+              UNTESTED
+              break;
+            case DiagnosticID::CallsObjC:
+              S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName << CalleeName;
+              UNTESTED
+              break;
+            case DiagnosticID::CallsUnsafeDecl:
+              MaybeNextCallee.emplace(*Diag2.Callee);
+              S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func) << effectName << CalleeName << MaybeNextCallee->name(S);
+              TESTED
+              break;
+            }
+            checkAddTemplateNote(Callee);
+            Callee = Diag2.Callee;
+            if (MaybeNextCallee) {
+              CalleeInfo = *MaybeNextCallee;
+              CalleeName = CalleeInfo.name(S);
+            }
+          }
+        }
+        break;
+      }
+    }
+  }
+
+  // ----------
+  // This AST visitor is used to traverse the body of a function during effect verification.
+  // This happens in 2 distinct situations:
+  // [1] The function has declared effects which need to be validated.
+  // [2] The function has not explicitly declared an effect in question, and is being
+  //    checked for implicit conformance.
+  // When we are verifying explicit conformance [1] we should generate all diagnostics.
+  // When we are inferring conformance [2] we will need to save enough diagnostics
+  // to provide a note to explain why inference failed; just the first violation per
+  // FunctionEffect.
+  //
+  // Populates a provided FunctionAnalysis object.
+  //
+  // TODO: Currently we create a new RecursiveASTVisitor for every function analysis.
+  // Is it so lightweight that this is OK? It would appear so.
+  struct FunctionBodyASTVisitor : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+    constexpr static bool Stop = false;
+    constexpr static bool Proceed = true;
+    
+    Analyzer &Outer;
+    PendingFunctionAnalysis &CurrentFunction;
+    CallableInfo& CurrentCaller;
+
+    FunctionBodyASTVisitor(Analyzer &outer, PendingFunctionAnalysis &CurrentFunction,
+      CallableInfo& CurrentCaller)
+      : Outer{ outer }, CurrentFunction{ CurrentFunction }, CurrentCaller{ CurrentCaller }
+    {
+    }
+
+    // -- Entry point --
+    void run()
+    {
+      // The target function itself may have some implicit code paths beyond the body:
+      // member and base constructors and destructors.
+      if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
+        if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+          for (const CXXCtorInitializer* Initer : Ctor->inits()) {
+            if (Expr* Init = Initer->getInit()) {
+              VisitStmt(Init);
+            }
+          }
+        } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+          followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+        } else if (!FD->isTrivial() && FD->isDefaulted()) {
+          // needed? maybe not
+        }
+      }
+      // else could be BlockDecl
+
+      // Do an AST traversal of the function/block body
+      TraverseDecl(const_cast<Decl*>(CurrentCaller.CDecl));
+    }
+
+    // -- Methods implementing common logic --
+
+    // Only effects whose flags include the specified flag receive a potential diagnostic.
+    // TODO: Consider bypasses for these loops by precomputing the flags we care about?
+    void addDiagnostic(FunctionEffect::FlagBit Flag, DiagnosticID D, SourceLocation Loc,
+      const Decl* Callee = nullptr)
+    {
+      // If there are ANY declared verifiable effects holding the flag, store just one diagnostic.
+      for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
+        if (effect->getFlags() & Flag) {
+          addDiagnosticInner(/*inferring=*/false, effect, D, Loc, Callee);
+          break;
+        }
+      }
+      // For each inferred-but-not-verifiable effect holding the flag, store a diagnostic,
+      // if we don't already have a diagnostic for that effect.
+      for (auto* effect : CurrentFunction.FXToInfer) {
+        if (effect->getFlags() & Flag) {
+          addDiagnosticInner(/*inferring=*/true, effect, D, Loc, Callee);
+        }
+      }
+    }
+
+    void addDiagnosticInner(bool inferring, const FunctionEffect* effect, DiagnosticID D,
+      SourceLocation Loc, const Decl* Callee = nullptr)
+    {
+      Diagnostic NewDiag{ effect, D, Loc, Callee };
+      CurrentFunction.checkAddDiagnostic(inferring, NewDiag);
+    }
+
+    // Here we have a call to a Decl, either explicitly via a CallExpr or some
+    // other AST construct. CallableInfo pertains to the callee.
+    void followCall(const CallableInfo& CI, SourceLocation CallLoc)
+    {
+      Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, /*assertNoFurtherInference=*/false);
+    }
+
+    void checkIndirectCall(CallExpr* Call, Expr* CalleeExpr)
+    {
+      const auto CalleeType = CalleeExpr->getType();
+      auto *FPT = CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+
+      auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
+        if (effect->getFlags() & FunctionEffect::kVerifyCalls) {
+          if (FPT == nullptr || effect->diagnoseFunctionCall(/*direct=*/false, 
+            CurrentCaller.CDecl, CurrentCaller.Effects, FPT, FPT->getFunctionEffects())) {
+            addDiagnosticInner(inferring, effect, DiagnosticID::CallsDisallowedExpr, Call->getBeginLoc());
+          }
+        }
+      };
+
+      for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
+        check1Effect(effect, false);
+      }
+
+      for (auto* effect : CurrentFunction.FXToInfer) {
+        check1Effect(effect, true);
+      }
+    }
+
+    // This destructor's body should be followed by the caller, but here we follow
+    // the field and base destructors.
+    void followDestructor(const CXXRecordDecl* Rec, const CXXDestructorDecl* Dtor)
+    {
+      for (const FieldDecl* Field : Rec->fields()) {
+        followTypeDtor(Field->getType());
+      }
+
+      if (const auto* Class = dyn_cast<CXXRecordDecl>(Rec)) {
+        for (const CXXBaseSpecifier& Base : Class->bases()) {
+          followTypeDtor(Base.getType());
+        }
+        for (const CXXBaseSpecifier& Base : Class->vbases()) {
+          followTypeDtor(Base.getType());
+        }
+      }
+    }
+
+    void followTypeDtor(QualType QT)
+    {
+      const Type* Ty = QT.getTypePtr();
+      while (Ty->isArrayType()) {
+        const ArrayType* Arr = Ty->getAsArrayTypeUnsafe();
+        QT = Arr->getElementType();
+        Ty = QT.getTypePtr();
+      }
+
+      if (Ty->isRecordType()) {
+        if (const CXXRecordDecl* Class = Ty->getAsCXXRecordDecl()) {
+          if (auto* Dtor = Class->getDestructor()) {
+            CallableInfo CI{ *Dtor };
+            followCall(CI, Dtor->getLocation());
+          }
+        }
+        return;
+      }
+      if (Ty->isScalarType() || Ty->isReferenceType() || Ty->isAtomicType()) {
+        return;
+      }
+
+      llvm::errs() << "warning: " << QT << ": unknown special functions\n";
+    }
+
+    // -- Methods for use of RecursiveASTVisitor --
+
+    bool shouldVisitImplicitCode() const { return true; }
+
+    bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+    bool VisitCXXThrowExpr(CXXThrowExpr* Throw)
+    {
+      addDiagnostic(FunctionEffect::kExcludeThrow, DiagnosticID::Throws, Throw->getThrowLoc());
+      return Proceed;
+    }
+
+    bool VisitCXXCatchStmt(CXXCatchStmt* Catch)
+    {
+      addDiagnostic(FunctionEffect::kExcludeCatch, DiagnosticID::Catches, Catch->getCatchLoc());
+      return Proceed;
+    }
+
+    bool VisitObjCMessageExpr(ObjCMessageExpr* Msg)
+    {
+      addDiagnostic(FunctionEffect::kExcludeObjCMessageSend, DiagnosticID::CallsObjC, Msg->getBeginLoc());
+      return Proceed;
+    }
+
+    bool VisitCallExpr(CallExpr* Call)
+    {
+      if constexpr (kDebugLogLevel > 2) {
+        llvm::errs() << "VisitCallExpr : " << Call->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+      }
+
+      /*if ((AllFlagsOfFXToVerify & FunctionEffect::kVerifyCalls) == 0u) {
+        return Proceed;
+      }*/
+
+      Expr* CalleeExpr = Call->getCallee();
+      if (const Decl* Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+        CallableInfo CI{ *Callee };
+        followCall(CI, Call->getBeginLoc());
+        return Proceed;
+      }
+
+      if (isa<CXXPseudoDestructorExpr>(CalleeExpr)) {
+        // just destroying a scalar, fine.
+        return Proceed;
+      }
+
+      checkIndirectCall(Call, CalleeExpr);
+
+      // No Decl, just an Expr. It could be a cast or something; we could dig
+      // into it and look for a DeclRefExpr. But for now it should suffice
+      // to look at the type of the Expr. Well, unless we're in a template :-/
+
+      //llvm::errs() << "CalleeExpr ";
+      //CalleeExpr->dumpColor();
+      //llvm::errs() << Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ": warning: null callee\n";
+
+      return Proceed;
+    }
+
+    bool VisitVarDecl(VarDecl* Var)
+    {
+      if constexpr (kDebugLogLevel > 2) {
+        llvm::errs() << "VisitVarDecl : " << Var->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+      }
+
+      if (Var->isStaticLocal()) {
+        addDiagnostic(FunctionEffect::kExcludeStaticLocalVars, DiagnosticID::HasStaticLocal, Var->getLocation());
+      }
+
+      const QualType::DestructionKind DK = Var->needsDestruction(Outer.mSema.getASTContext());
+      if (DK == QualType::DK_cxx_destructor) {
+        QualType QT = Var->getType();
+        if (const auto* ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+          if (const auto* CxxRec = dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+            if (const auto* Dtor = CxxRec->getDestructor()) {
+              CallableInfo CI{ *Dtor };
+              followCall(CI, Var->getLocation());
+            }
+          }
+        }
+      }
+      return Proceed;
+    }
+
+    bool VisitCXXNewExpr(CXXNewExpr* New)
+    {
+      // BUG? It seems incorrect that RecursiveASTVisitor does not
+      // visit the call to operator new.
+      if (auto* FD = New->getOperatorNew()) {
+        CallableInfo CI{ *FD, SpecialFuncType::OperatorNew };
+        followCall(CI, New->getBeginLoc());
+      }
+
+      // It's a bit excessive to check operator delete here, since it's
+      // just a fallback for operator new followed by a failed constructor.
+      // We could check it via New->getOperatorDelete().
+
+      // It DOES however visit the called constructor
+      return Proceed;
+    }
+
+    bool VisitCXXDeleteExpr(CXXDeleteExpr* Delete)
+    {
+      // BUG? It seems incorrect that RecursiveASTVisitor does not
+      // visit the call to operator delete.
+      if (auto* FD = Delete->getOperatorDelete()) {
+        CallableInfo CI{ *FD, SpecialFuncType::OperatorDelete };
+        followCall(CI, Delete->getBeginLoc());
+      }
+
+      // It DOES however visit the called destructor
+
+      return Proceed;
+    }
+
+    bool VisitCXXConstructExpr(CXXConstructExpr* Construct)
+    {
+      if constexpr (kDebugLogLevel > 2) {
+        llvm::errs() << "VisitCXXConstructExpr : " << 
+          Construct->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+      }
+
+      // BUG? It seems incorrect that RecursiveASTVisitor does not
+      // visit the call to the constructor.
+      const CXXConstructorDecl* Ctor = Construct->getConstructor();
+      CallableInfo CI{ *Ctor };
+      followCall(CI, Construct->getLocation());
+
+      return Proceed;
+    }
+
+    bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr* DEI)
+    {
+      if (auto* Expr = DEI->getExpr()) {
+        TraverseStmt(Expr);
+      }
+      return Proceed;
+    }
+
+    bool TraverseLambdaExpr(LambdaExpr* Lambda)
+    {
+      // We override this so as the be able to skip traversal of the lambda's
+      // body. We have to explicitly traverse the captures.
+      for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) {
+        if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
+          Lambda->capture_init_begin()[I]) == Stop) return Stop;
+      }
+
+      return Proceed;
+    }
+
+    bool TraverseBlockExpr(BlockExpr* /*unused*/)
+    {
+      // TODO: are the capture expressions (ctor call?) safe?
+      return Proceed;
+    }
+
+    bool VisitDeclRefExpr(const DeclRefExpr *E)
+    {
+      const ValueDecl* Val = E->getDecl();
+      if (isa<VarDecl>(Val)) {
+        const VarDecl* Var = cast<VarDecl>(Val);
+        const auto TLSK = Var->getTLSKind();
+        if (TLSK != VarDecl::TLS_None) {
+          // At least on macOS, thread-local variables are initialized on
+          // first access.
+          addDiagnostic(FunctionEffect::kExcludeThreadLocalVars,
+            DiagnosticID::AccessesThreadLocal, E->getLocation());
+        }
+      }
+      return Proceed;
+    }
+
+
+    // Unevaluated contexts: need to skip
+    // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
+
+    // bool TraverseTypeLoc(TypeLoc /*unused*/)
+    // {
+    //   // This is a big blunt hammer so that we don't reach __invoke()'s call to declval().
+    //   // Is it correct?
+    //   return Proceed;
+    // }
+
+    bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+      return TraverseStmt(Node->getResultExpr());
+    }
+    bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) { return Proceed; }
+
+    bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
+
+    bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; }
+
+    bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; }
+
+    bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
+  };
+
+#if FX_ANALYZER_VERIFY_DECL_LIST
+  // Sema has accumulated DeclsWithUnverifiedEffects. As a debug check, do our
+  // own AST traversal and see what we find.
+
+  using MatchFinder = ast_matchers::MatchFinder;
+  static constexpr StringRef Tag_Callable = "Callable";
+
+  // -----
+  // Called for every callable in the translation unit.
+  struct CallableFinderCallback : MatchFinder::MatchCallback {
+    Sema& mSema;
+
+    CallableFinderCallback(Sema& S) : mSema{ S } {}
+
+    void run(const MatchFinder::MatchResult &Result) override {
+      if (auto* Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
+        if (const auto FX = functionEffectsForDecl(Callable)) {
+          // Reuse this filtering method in Sema
+          mSema.CheckAddCallableWithEffects(Callable, FX);
+        }
+      }
+    }
+
+    static FunctionEffectSet functionEffectsForDecl(const Decl *D) {
+      if (auto* FD = D->getAsFunction()) {
+        return FD->getFunctionEffects();
+      }
+      if (auto* BD = dyn_cast<BlockDecl>(D)) {
+        return BD->getFunctionEffects(); 
+      }
+      return {};
+    }
+
+    static void get(Sema &S,const TranslationUnitDecl& TU)
+    {
+      MatchFinder CallableFinder;
+      CallableFinderCallback Callback{ S };
+
+      using namespace clang::ast_matchers;
+
+      CallableFinder.addMatcher(
+          decl(forEachDescendant(decl(anyOf(
+              functionDecl(hasBody(anything())).bind(Tag_Callable),
+              //objcMethodDecl(isDefinition()).bind(Tag_Callable), // no, always unsafe
+              blockDecl().bind(Tag_Callable))))),
+          &Callback);
+      // Matching LambdaExpr this way [a] doesn't seem to work (need to check for Stmt?)
+      // and [b] doesn't seem necessary, since the anonymous function is reached via the above.
+      // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))), &Callback);
+
+      CallableFinder.match(TU, TU.getASTContext());
+    }
+  };
+
+  void verifyRootDecls(const TranslationUnitDecl& TU) const
+  {
+    // If this weren't debug code, it would be good to find a way to move/swap
+    // instead of copying.
+    SmallVector<const Decl *> decls = mSema.DeclsWithUnverifiedEffects;
+    mSema.DeclsWithUnverifiedEffects.clear();
+
+    CallableFinderCallback::get(mSema, TU);
+
+    /*if (decls.size() != mSema.DeclsWithUnverifiedEffects.size())*/ {
+      llvm::errs() << "\nFXAnalysis: mSema gathered " << decls.size()
+        << " Decls; second AST pass found " << mSema.DeclsWithUnverifiedEffects.size() << "\n";
+    }
+  }
+#endif
+
+};
+
+Analyzer::AnalysisMap::~AnalysisMap()
+{
+  for (const auto& item : *this) {
+    auto ptr = item.second;
+    if (isa<PendingFunctionAnalysis*>(ptr)) {
+      delete ptr.get<PendingFunctionAnalysis*>();
+    } else {
+      delete ptr.get<CompleteFunctionAnalysis*>();
+    }
+  }
+}
+
+} // namespace FXAnalysis
+
+#if TEMP_DISABLED
+#if 0
+      // OLD OLD OLD from followCall
+      if (CI.PerfAnnot != PerfAnnotation::None) {
+        // Even if the callable turns out to be unsafe, if it is declared safe,
+        // don't create a chain reaction by propagating the error to its callers.
+        if constexpr (kDebugLogLevel > 1) {
+          llvm::errs() << Outer.mDebugIndent << "followCall: safe: " << int(CI.CType) << " " << CI.name() << "\n";
+        }
+
+        if (CI.isVerifiable()) {
+          // The top-level search for callables isn't finding all template instantiations?
+          // Since we have a function body here, possibly verify now.
+          Outer.determineCallableSafety(CI, /*EmitDiags=*/true);
+        } else {
+          Outer.markWithoutFollowing(CI, DiagnosticID::None);
+        }
+
+        return;
+      }
+
+      if constexpr (kDebugLogLevel > 1) {
+        llvm::errs() << Outer.mDebugIndent << "followCall type " << int(CI.CType) << " " << CI.name() << "\n";
+      }
+
+      if (!CI.isDirectCall()) {
+        // Function pointer or virtual method
+        Outer.markWithoutFollowing(CI, CI.CType == CallType::Virtual ?
+          DiagnosticID::DeclVirtualWithoutConstraint : DiagnosticID::DeclFuncPtrWithoutConstraint);
+        return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
+      }
+
+      // Here we have a direct call. Are we allowed to infer? If not, emit a diagnostic.
+      if (!CI.isInferrable()) {
+        Outer.markWithoutFollowing(CI, DiagnosticID::DeclWithoutConstraintOrInference);
+        return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
+      }
+
+      const DeclInfo& DI = Outer.determineCallableSafety(CI);
+      if (DI.hasDiagnostic()) {
+        if (CI.FuncType == SpecialFuncType::None) {
+          return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
+        }
+        return addDiagnostic(DiagnosticID::AllocatesMemory, CallLoc);
+      }
+#endif
+
+
+struct PerfConstraintAnalyzer {
+  // Move all no_locks functions (both explicit and implicitly via call chain)
+  // into a separate code segment?
+  //constexpr static bool kCreateNoLocksSection = true;
+
+  // If non-null, the unsafe callable.
+  using FollowResult = const FunctionDecl*;
+
+  using MatchFinder = ast_matchers::MatchFinder;
+
+
+  // Information recorded about every visited function/block.
+  struct DeclInfo {
+    explicit DeclInfo(PerfAnnotation PA) {} //: DeclPerfAnnot{ PA } {}
+
+    bool hasDiagnostic() const { return Diag.ID != DiagnosticID::None; }
+    const Diagnostic& diagnostic() const { assert(hasDiagnostic()); return Diag; }
+    void setDiagnostic(const Diagnostic& e)
+    {
+        Diag = e;
+    }
+
+    // Scanning: prevent recursion
+    bool isScanning() const { return Scanning; }
+    void setScanning(bool b) { Scanning = b; }
+
+    Diagnostic Diag;
+
+    // This reflects the declared annotation, not the inferred one.
+    //PerfAnnotation DeclPerfAnnot{};
+
+    // Used to prevent infinite recursion.
+    bool Scanning = false;
+  };
+
+  // This contains an entry for every visited callable. Caution: determineCallableSafety
+  // assumes that an iterator remains valid across calls which may mutate the container.
+  using InferenceMap = std::map<const Decl*, DeclInfo>;
+
+  // --
+
+
+  // -----
+
+  Sema& S;
+  InferenceMap mInferenceMap;
+  std::string mDebugIndent;
+
+  // -----
+
+  PerfConstraintAnalyzer(Sema& S)
+    : S{ S }
+  {
+  }
+
+  // Variant of determineCallableSafety where we already know we can't follow
+  // because it is virtual or a function pointer etc. Or, we don't need to follow
+  // it because we know it's OK but we want to have it in the inference table.
+  void markWithoutFollowing(const CallableInfo& CInfo, DiagnosticID Diag)
+  {
+    auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
+    if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
+      return;
+    }
+    iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
+    if (Diag != DiagnosticID::None) {
+      DeclInfo& Result = iter->second;
+      Result.setDiagnostic({ Diag, CInfo.CDecl->getLocation(), nullptr });
+    }
+  }
+
+  void maybeDiagnose(DeclInfo& Result, const Diagnostic& Diag)
+  {
+    Result.setDiagnostic(Diag);
+  }
+
+  // Returns a populated map entry for the given CallableInfo's Decl.
+  // Caller should take special care to avoid recursion when Scanning is true.
+  // EmitDiags should only be true when called from the top-level traversal
+  // of all attributed functions in the TranslationUnit.
+  const DeclInfo& determineCallableSafety(const CallableInfo& CInfo, bool EmitDiags = false)
+  {
+    // If we have already visited this callable, return the previous result.
+    auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
+    if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
+      return iter->second;
+    }
+
+    if constexpr (kDebugLogLevel) {
+      llvm::errs() << mDebugIndent << "determineCallableSafety -> " << CInfo.name() << "\n";
+    }
+
+    iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
+    DeclInfo& Result = iter->second;
+
+    std::optional<PerfConstraintASTVisitor> MaybeVisitor; // built on demand
+    auto getVisitor = [&]() -> PerfConstraintASTVisitor& {
+      if (!MaybeVisitor) {
+        MaybeVisitor.emplace(*this, /*StopOnFirstDiag=*/!EmitDiags);
+      }
+      return *MaybeVisitor;
+    };
+
+    const FunctionDecl *FD = dyn_cast<FunctionDecl>(CInfo.CDecl);
+    if (FD != nullptr) {
+      // Currently, built-in functions are always considered safe.
+      if (FD->getBuiltinID() != 0) {
+        return Result;
+      }
+      // If it doesn't have a body, then we have to rely on the declaration.
+      if (!functionIsVerifiable(S, FD)) {
+        const PerfAnnotation PA = FD->getPerfAnnotation();
+        if (PA != PerfAnnotation::NoLock) {
+          Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
+        }
+        return Result;
+      }
+
+      if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+        for (const CXXCtorInitializer* Initer : Ctor->inits()) {
+          if (Expr* Init = Initer->getInit()) {
+            Result.setScanning(true);
+            getVisitor().VisitStmt(Init);
+          }
+        }
+      } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+        if (auto* UnsafeCallee = followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor)) {
+          //const auto PA = UnsafeCallee->getType()->isNoLock() ? PerfAnnotation::True : PerfAnnotation::False;
+          getVisitor().addDiagnostic(DiagnosticID::CallsUnsafeDecl, FD->getLocation(), UnsafeCallee);
+        }
+      } else if (!FD->isTrivial() && FD->isDefaulted()) {
+        // needed? maybe not
+      }
+    }
+
+    if constexpr (kDebugLogLevel) {
+      mDebugIndent += "    ";
+    }
+
+    Result.setScanning(true);
+
+    auto& Visitor = getVisitor();
+    Visitor.TraverseDecl(const_cast<Decl*>(CInfo.CDecl));
+
+    const auto& Diagnostics = Visitor.Diagnostics;
+
+    Result.setScanning(false);
+
+    if constexpr (kDebugLogLevel) {
+      mDebugIndent = mDebugIndent.substr(0, mDebugIndent.size() - 4);
+      llvm::errs() << mDebugIndent << "determineCallableSafety <- " << CInfo.name() << " : " << Diagnostics.size() << " violations\n";
+      //EmitDiags = true; // TEMPORARY
+    }
+
+    if (!Diagnostics.empty()) {
+      if (EmitDiags) {
+        emitDiagnostics(Diagnostics, CInfo);
+      }
+      Result.setDiagnostic(Diagnostics.front());
+    }
+
+    return Result;
+  }
+
+  void emitDiagnostics(const std::vector<Diagnostic>& Diags,
+    const CallableInfo& CInfo)
+  {
+  }
+
+  // Top-level entry point. Search the entire TU for functions to verify.
+  // Ways to optimize:
+  // - are the diagnostics enabled? if not, bail
+  // - did Sema see any tagged functions with bodies? if not, bail
+  void run(const TranslationUnitDecl& TU)
+  {
+
+    /*if constexpr (kCreateNoLocksSection) {
+      createNoLocksSection();
+    }*/
+  }
+
+  /*void createNoLocksSection()
+  {
+    const char* kSectionName = "__TEXT,__nolock,regular,pure_instructions";
+    if (auto err = S.isValidSectionSpecifier(kSectionName)) {
+      // real diagnostic?
+      llvm::errs() << "error: invalid section name " << kSectionName << " (" << err << ")\n";
+      return;
+    }
+
+    auto& MapEntry = S.getASTContext().SemaAnalysisGeneratedSections[kSectionName];
+    MapEntry.reserve(mInferenceMap.size());
+    for (auto& item : mInferenceMap) {
+      MapEntry.emplace_back(item.first);
+      if constexpr (kDebugLogLevel > 1) {
+        if (auto* F = dyn_cast<FunctionDecl>(item.first)) {
+          CallableInfo CI{ *F, S };
+          llvm::errs() << "nolock: " << CI.name() << "\n";
+        }
+      }
+    }
+  }*/
+};
+#endif // TEMP_DISABLED
+
+// =============================================================================
+
+
 //===----------------------------------------------------------------------===//
 // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
 //  warnings on a function, method, or block.
@@ -2534,6 +4022,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
                        SourceLocation())) {
     CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
   }
+
+  // TODO: skip this if the warning isn't enabled.
+  FXAnalysis::Analyzer{ S }.run(*TU);
 }
 
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 720d5fd5f0428d..6022e4a838e8aa 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -585,6 +585,27 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
   Diag(Loc, diag::warn_nullability_lost) << SrcType << DstType;
 }
 
+// Generate diagnostics when adding or removing effects in a type conversion.
+void Sema::diagnoseFunctionEffectConversion(QualType DstType,
+                                          QualType SrcType,
+                                          SourceLocation Loc)
+{
+  llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> " << DstType << "\n";
+  const auto SrcFX = FunctionEffectSet::get(*SrcType);
+  const auto DstFX = FunctionEffectSet::get(*DstType);
+  if (SrcFX != DstFX) {
+    const auto diffs = FunctionEffectSet::differences(SrcFX, DstFX);
+    for (const auto& item : diffs) {
+      const FunctionEffect* effect = item.first;
+      const bool adding = item.second;
+      if (effect->diagnoseConversion(adding,
+        SrcType, SrcFX, DstType, DstFX)) {
+        Diag(Loc, adding ? diag::warn_invalid_add_func_effects : diag::warn_invalid_remove_func_effects) << effect->name();
+      }
+    }
+  }
+}
+
 void Sema::diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E) {
   // nullptr only exists from C++11 on, so don't warn on its absence earlier.
   if (!getLangOpts().CPlusPlus11)
@@ -661,6 +682,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
 
   diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
   diagnoseZeroToNullptrConversion(Kind, E);
+  if (!isCast(CCK) && !E->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+    diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
+  }
 
   QualType ExprTy = Context.getCanonicalType(E->getType());
   QualType TypeTy = Context.getCanonicalType(Ty);
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1f4a041e88dfff..77fdffef6382e0 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3922,6 +3922,40 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
     return true;
   }
 
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();  
+  if (OldFX != NewFX) {
+    const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
+    for (const auto& item : diffs) {
+      const FunctionEffect* effect = item.first;
+      const bool adding = item.second;
+      if (effect->diagnoseRedeclaration(adding, *Old, OldFX, *New, NewFX)) {
+        Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << effect->name();
+        Diag(Old->getLocation(), diag::note_previous_declaration);
+      }
+    }
+
+    const auto MergedFX = OldFX | NewFX;
+
+    // Having diagnosed any problems, prevent further errors by applying the merged set of effects
+    // to both declarations.
+    auto applyMergedFX = [&](FunctionDecl* FD) {
+      const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
+      FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+      EPI.FunctionEffects = MergedFX;
+      QualType ModQT = Context.getFunctionType(FD->getReturnType(),
+                                                  FPT->getParamTypes(), EPI);
+
+      FD->setType(ModQT);
+    };
+
+    applyMergedFX(Old);
+    applyMergedFX(New);
+
+    OldQType = Old->getType();
+    NewQType = New->getType();
+  }
+
   if (getLangOpts().CPlusPlus) {
     OldQType = Context.getCanonicalType(Old->getType());
     NewQType = Context.getCanonicalType(New->getType());
@@ -11100,6 +11134,49 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
   return nullptr;
 }
 
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
+{
+  if (!D->hasBody()) {
+    if (const auto *FD = D->getAsFunction()) {
+      if (!FD->willHaveBody()) {
+        return;
+      }
+    }
+  }
+
+  if (Diags.getIgnoreAllWarnings() ||
+      (Diags.getSuppressSystemWarnings() &&
+        SourceMgr.isInSystemHeader(D->getLocation())))
+    return;
+
+  if (hasUncompilableErrorOccurred())
+    return;
+
+  // For code in dependent contexts, we'll do this at instantiation time
+  // (??? This was copied from something else in AnalysisBasedWarnings ???)
+  if (cast<DeclContext>(D)->isDependentContext()) {
+    return;
+  }
+
+  // Filter out declarations that the FunctionEffect analysis should skip
+  // and not verify. (??? Is this the optimal order in which to test ???)
+  bool effectsNeedVerification = false;
+  for (const auto *Effect : FX) {
+    if (Effect->getFlags() & FunctionEffect::kRequiresVerification) {
+      AllEffectsToVerify.insert(Effect);
+      effectsNeedVerification = true;
+    }
+  }
+  if (!effectsNeedVerification) {
+    return;
+  }
+
+  // Record the declaration for later analysis.
+  DeclsWithUnverifiedEffects.push_back(D);
+}
+
 /// Determines if we can perform a correct type check for \p D as a
 /// redeclaration of \p PrevDecl. If not, we can generally still perform a
 /// best-effort check.
@@ -15712,6 +15789,12 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
   else
     FD = cast<FunctionDecl>(D);
 
+  auto *Canon = FD->getCanonicalDecl();
+
+  // llvm::outs() << "** ActOnStartOfFunctionDef " << FD->getName() << 
+  //   " " << FD << " " << Canon << " " << FD->getType() << "\n";
+  // getNameForDiagnostic
+
   // Do not push if it is a lambda because one is already pushed when building
   // the lambda in ActOnStartOfLambdaDefinition().
   if (!isLambdaCallOperator(FD))
@@ -15912,6 +15995,12 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
       getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
     Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
 
+  const auto FX = FD->getCanonicalDecl()->getFunctionEffects();
+  llvm::outs() << "^^ " << FX.size() << " effects\n";
+  if (FX) {
+    CheckAddCallableWithEffects(FD, FX);
+  }
+
   return D;
 }
 
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index c00120b59d396e..12f7869441c9be 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -395,6 +395,34 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
   return checkStringLiteralArgumentAttr(AL, ArgExpr, Str, ArgLocation);
 }
 
+/// Check if the argument \p ArgNum of \p Attr is a compile-time constant
+/// integer (boolean) expression. If not, emit an error and return false.
+bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
+                                    bool &Value)
+{
+  if (AL.isInvalid()) {
+    return false;
+  }
+  Expr* ArgExpr = AL.getArgAsExpr(ArgNum);
+  SourceLocation errorLoc{ AL.getLoc() };
+
+  if (AL.isArgIdent(ArgNum)) {
+    IdentifierLoc * IL = AL.getArgAsIdent(ArgNum);
+    errorLoc = IL->Loc;
+  } else if (ArgExpr != nullptr) {
+    auto maybeVal = ArgExpr->getIntegerConstantExpr(Context, &errorLoc);
+    if (maybeVal) {
+      Value = maybeVal->getBoolValue();
+      return true;
+    }
+  }
+
+  AL.setInvalid();
+  Diag(errorLoc, diag::err_attribute_argument_n_type)
+    << AL << ArgNum << AANT_ArgumentConstantExpr;
+  return false;
+}
+
 /// Applies the given attribute to the Decl without performing any
 /// additional semantic checking.
 template <typename AttrType>
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index e258a4f7c89415..dac5d5f3e5c1e6 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18321,6 +18321,26 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
     return true;
   }
 
+  // Virtual overrides must have the same or stronger performance annotation.
+  const auto OldFX = Old->getFunctionEffects();
+  const auto NewFX = New->getFunctionEffects();
+
+  if (OldFX != NewFX) {
+    const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
+    bool AnyDiags = false;
+
+    for (const auto& item : diffs) {
+      const FunctionEffect* effect = item.first;
+      const bool adding = item.second;
+      if (effect->diagnoseMethodOverride(adding, *Old, OldFX, *New, NewFX)) {
+        Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << effect->name();
+        Diag(Old->getLocation(), diag::note_overridden_virtual_function);
+        AnyDiags = true;
+      }
+    }
+    if (AnyDiags) return true;
+  }
+
   CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv();
 
   // If the calling conventions match, everything is fine
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 93f82e68ab6440..643f8e7f444884 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -17126,6 +17126,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
   BlockDecl *BD = BSI->TheDecl;
 
+  if (const auto FX = BD->getFunctionEffects()) {
+    CheckAddCallableWithEffects(BD, FX);
+  }
+
   if (BSI->HasImplicitReturnType)
     deduceClosureReturnType(*BSI);
 
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index c34a40fa7c81ac..50007d4e1de119 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4945,10 +4945,16 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
 
   // If this conversion sequence succeeded and involved implicitly converting a
   // _Nullable type to a _Nonnull one, complain.
-  if (!isCast(CCK))
+  if (!isCast(CCK)) {
     diagnoseNullableToNonnullConversion(ToType, InitialFromType,
                                         From->getBeginLoc());
 
+    // TODO: This generates a redundant diagnostic for:
+    // void (^nl_block0)() NOLOCK = ^(){};
+    // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/))
+    //   diagnoseFunctionEffectConversion(ToType, InitialFromType,
+    //                                         From->getBeginLoc());
+  }
   return From;
 }
 
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 5b95bae567b721..0e1a0f8dfdbabc 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1888,6 +1888,11 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
 ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
   LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
   ActOnFinishFunctionBody(LSI.CallOperator, Body);
+
+  if (const auto FX = LSI.CallOperator->getFunctionEffects()) {
+    CheckAddCallableWithEffects(LSI.CallOperator, FX);
+  }
+
   return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
 }
 
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index b0c693f078efe2..8f1563e7884429 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1786,6 +1786,9 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
 /// type.
 bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
                                 QualType &ResultTy) {
+
+  llvm::outs() << "IsFunctionConversion " << FromType << " " << ToType << "\n";
+
   if (Context.hasSameUnqualifiedType(FromType, ToType))
     return false;
 
@@ -1866,6 +1869,30 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
       FromFn = QT->getAs<FunctionType>();
       Changed = true;
     }
+
+#if 1
+    // NOTE (TEMP): this works for C++ to allow dropping effects.
+    // For plain C, however, this creates an error when dropping effects!
+
+    // Transparently add/drop effects; here we are concerned with
+    // language rules/canonicalization. Adding/dropping effects is a warning.
+    auto FromFX = FromFPT->getFunctionEffects();
+    auto ToFX = ToFPT->getFunctionEffects();
+    if (FromFX != ToFX) {
+      llvm::outs() << "IsFunctionConversion effects change " << FromType << " -> " << ToType << "\n";
+
+      //const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
+      // TODO: diagnose conflicts
+
+      FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
+      ExtInfo.FunctionEffects = ToFX;
+      QualType QT = Context.getFunctionType(FromFPT->getReturnType(),
+                                            FromFPT->getParamTypes(), ExtInfo);
+      FromFn = QT->getAs<FunctionType>();
+      llvm::outs() << "  produced " << QT << "\n";
+      Changed = true;
+    }
+#endif
   }
 
   if (!Changed)
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 3148299f6467af..2bc6b5b3880a8e 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -144,6 +144,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
 #define FUNCTION_TYPE_ATTRS_CASELIST                                           \
   case ParsedAttr::AT_NSReturnsRetained:                                       \
   case ParsedAttr::AT_NoReturn:                                                \
+  case ParsedAttr::AT_NoLock:                                                  \
+  case ParsedAttr::AT_NoAlloc:                                                 \
   case ParsedAttr::AT_Regparm:                                                 \
   case ParsedAttr::AT_CmseNSCall:                                              \
   case ParsedAttr::AT_ArmStreaming:                                            \
@@ -209,10 +211,18 @@ namespace {
     /// validating that noderef was used on a pointer or array.
     bool parsedNoDeref;
 
+    // Flags to diagnose illegal permutations of nolock(cond) and noalloc(cond).
+    // Manual logic for finding previous attributes would be more complex,
+    // unless we transoformed nolock/noalloc(false) into distinct separate
+    // attributes from the ones which are parsed.
+    unsigned char parsedNolock : 2;
+    unsigned char parsedNoalloc : 2;
+
   public:
     TypeProcessingState(Sema &sema, Declarator &declarator)
         : sema(sema), declarator(declarator),
-          chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false) {}
+          chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
+          parsedNolock(0), parsedNoalloc(0) {}
 
     Sema &getSema() const {
       return sema;
@@ -339,6 +349,11 @@ namespace {
 
     bool didParseNoDeref() const { return parsedNoDeref; }
 
+    void setParsedNolock(unsigned char v) { parsedNolock = v; }
+    unsigned char getParsedNolock() const { return parsedNolock; }
+    void setParsedNoalloc(unsigned char v) { parsedNoalloc = v; }
+    unsigned char getParsedNoalloc() const { return parsedNoalloc; }
+
     ~TypeProcessingState() {
       if (savedAttrs.empty())
         return;
@@ -7926,6 +7941,114 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
   llvm_unreachable("unexpected attribute kind!");
 }
 
+static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
+  ParsedAttr &attr, QualType &type, FunctionTypeUnwrapper& unwrapped)
+{
+  // Values of nolockState / noallocState
+  enum {
+    kNotSeen   = 0,
+    kSeenFalse = 1,
+    kSeenTrue  = 2
+  };
+
+  const bool isNoLock = attr.getKind() == ParsedAttr::AT_NoLock;
+  Sema &S = state.getSema();
+
+  // Delay if this is not a function type.
+  if (!unwrapped.isFunctionType())
+    return false;
+
+  // Require FunctionProtoType
+  auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
+  if (FPT == nullptr) {
+    // TODO: special diagnostic?
+    return false;
+  }
+
+  // Parse the conditional expression, if any
+  bool Cond = true; // default
+  if (attr.getNumArgs() > 0) {
+    if (!S.checkBoolExprArgumentAttr(attr, 0, Cond)) {
+      attr.setInvalid();
+      return false;
+    }
+  }
+
+  FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+
+  auto incompatible = [&](StringRef attrTrue, StringRef attrFalse) {
+    Sema &S = state.getSema();
+    S.Diag(attr.getLoc(), diag::err_attributes_are_not_compatible)
+        << attrTrue << attrFalse
+        << false;
+    // we don't necessarily have the location of the previous attribute,
+    // so no note.
+    attr.setInvalid();
+    return true;
+  };
+
+  // check nolock(true) against nolock(false), and same for noalloc
+  const unsigned newState = Cond ? kSeenTrue : kSeenFalse;
+  const unsigned oppositeNewState = Cond ? kSeenFalse : kSeenTrue;
+  if (isNoLock) {
+    if (state.getParsedNolock() == oppositeNewState) {
+      return incompatible("nolock(true)", "nolock(false)");
+    }
+    // also check nolock(true) against noalloc(false)
+    if (Cond && state.getParsedNoalloc() == kSeenFalse) {
+      return incompatible("nolock(true)", "noalloc(false)");
+    }
+    state.setParsedNolock(newState);
+  } else {
+    if (state.getParsedNoalloc() == oppositeNewState) {
+      return incompatible("noalloc(true)", "noalloc(false)");
+    }
+    // also check nolock(true) against noalloc(false)
+    if (state.getParsedNolock() == kSeenTrue) {
+      if (!Cond) {
+        return incompatible("nolock(true)", "noalloc(false)");
+      }
+      // Ignore noalloc(true) since we already have nolock(true).
+      return true;
+    }
+    state.setParsedNoalloc(newState);
+  }
+  
+  if (!Cond) {
+    // nolock(false) and noalloc(false) are represented as sugar, with AttributedType
+    Attr *A = nullptr;
+    if (isNoLock) {
+      A = NoLockAttr::Create(S.Context, false);
+    } else {
+      A = NoAllocAttr::Create(S.Context, false);
+    }
+    type = state.getAttributedType(A, type, type);
+    return true;
+  }
+
+  const FunctionEffect* Effect = nullptr;
+  if (isNoLock) {
+    Effect = &NoLockNoAllocEffect::nolock_instance();
+  } else {
+    Effect = &NoLockNoAllocEffect::noalloc_instance();
+  }
+
+  MutableFunctionEffectSet newEffectSet{ Effect };
+  if (EPI.FunctionEffects) {
+    // Preserve all previous effects - except noalloc, when we are adding nolock
+    for (const auto* effect : EPI.FunctionEffects) {
+      if (!(isNoLock && effect->type() == FunctionEffect::kNoAllocTrue))
+        newEffectSet.insert(effect);
+    }
+  }
+
+  EPI.FunctionEffects = FunctionEffectSet::create(newEffectSet);
+  QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
+                                              FPT->getParamTypes(), EPI);
+  type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
+  return true;
+}
+
 static bool checkMutualExclusion(TypeProcessingState &state,
                                  const FunctionProtoType::ExtProtoInfo &EPI,
                                  ParsedAttr &Attr,
@@ -8239,6 +8362,11 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
     return true;
   }
 
+  if (attr.getKind() == ParsedAttr::AT_NoLock
+   || attr.getKind() == ParsedAttr::AT_NoAlloc) {
+    return handleNoLockNoAllocTypeAttr(state, attr, type, unwrapped);
+  }
+
   // Delay if the type didn't work out to a function.
   if (!unwrapped.isFunctionType()) return false;
 
diff --git a/clang/test/Sema/attr-nolock.cpp b/clang/test/Sema/attr-nolock.cpp
new file mode 100644
index 00000000000000..3f9938a2e54601
--- /dev/null
+++ b/clang/test/Sema/attr-nolock.cpp
@@ -0,0 +1,78 @@
+// RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
+// expected-no-diagnostics
+
+// Make sure that the attribute gets parsed and attached to the correct AST elements.
+// Update 1 Mar 2024
+
+#pragma clang diagnostic ignored "-Wunused-variable"
+
+// =========================================================================================
+// Square brackets, true
+
+#define NOLOCK [[clang::nolock]]
+
+// On the type of the FunctionDecl
+void nl_function() NOLOCK;
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() NOLOCK;
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
+
+// Check alternate attribute type and placement
+__attribute__((clang_nolock)) void (*nl_func_b)(void);
+// CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nolock))'
+
+// On the type of the ParmVarDecl of a function parameter
+static void nlReceiver(void (*nl_func)() NOLOCK);
+// CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nolock))'
+
+// As an AttributedType within the nested types of a typedef
+typedef void (*nl_fp_type)() NOLOCK;
+// CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nolock))'
+using nl_fp_talias = void (*)() NOLOCK;
+// CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nolock))'
+
+// From a typedef or typealias, on a VarDecl
+nl_fp_type nl_fp_var1;
+// CHECK: VarDecl {{.*}} nl_fp_var1 'nl_fp_type':'void (*)() __attribute__((clang_nolock))'
+nl_fp_talias nl_fp_var2;
+// CHECK: VarDecl {{.*}} nl_fp_var2 'nl_fp_talias':'void (*)() __attribute__((clang_nolock))'
+
+// On type of a FieldDecl
+struct Struct {
+	void (*nl_func_field)() NOLOCK;
+// CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nolock))'
+};
+
+// noalloc should be subsumed into nolock
+void nl1() [[clang::nolock]] [[clang::noalloc]];
+// CHECK: FunctionDecl {{.*}} nl1 'void () __attribute__((clang_nolock))'
+
+void nl2() [[clang::noalloc]] [[clang::nolock]];
+// CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nolock))'
+
+// --- Blocks ---
+
+// On the type of the VarDecl holding a BlockDecl
+void (^nl_block1)() NOLOCK = ^() NOLOCK {};
+// CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nolock))'
+
+int (^nl_block2)() NOLOCK = ^() NOLOCK { return 0; };
+// CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nolock))'
+
+// The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute
+static void blockCaller() { nl_block1(); }
+// CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
+
+// $$$ TODO: There are still some loose ends in all the methods of the lambda
+auto nl_lambda = []() NOLOCK {};
+
+// =========================================================================================
+// Square brackets, false
+
+void nl_func_false() [[clang::nolock(false)]];
+// CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
+
+// TODO: Duplicate the above for noalloc
+// TODO: Duplicate the above for GNU-style attribute?
diff --git a/clang/test/Sema/attr-nolock2.cpp b/clang/test/Sema/attr-nolock2.cpp
new file mode 100644
index 00000000000000..9c494dcdd581db
--- /dev/null
+++ b/clang/test/Sema/attr-nolock2.cpp
@@ -0,0 +1,88 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c2x %s
+
+// TODO: There's a problem with diagnosing type conversions in plain C.
+
+#pragma clang diagnostic error "-Wstrict-prototypes"
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+#if 1 // TEMP_DISABLE
+
+// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
+// Check invalid combinations of nolock/noalloc attributes
+void nl_true_false_1(void) [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+void nl_true_false_2(void) [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+
+void na_true_false_1(void) [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+void na_true_false_2(void) [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+
+void nl_true_na_true_1(void) [[clang::nolock]] [[clang::noalloc]];
+void nl_true_na_true_2(void) [[clang::noalloc]] [[clang::nolock]];
+
+void nl_true_na_false_1(void) [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+void nl_true_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+
+void nl_false_na_true_1(void) [[clang::nolock(false)]] [[clang::noalloc]];
+void nl_false_na_true_2(void) [[clang::noalloc]] [[clang::nolock(false)]];
+
+void nl_false_na_false_1(void) [[clang::nolock(false)]] [[clang::noalloc(false)]];
+void nl_false_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock(false)]];
+
+// --- TYPE CONVERSIONS ---
+
+void unannotated(void);
+void nolock(void) [[clang::nolock]];
+void noalloc(void) [[clang::noalloc]];
+void type_conversions(void)
+{
+	// It's fine to remove a performance constraint.
+	void (*fp_plain)(void);
+
+	fp_plain = unannotated;
+	fp_plain = nolock;
+	fp_plain = noalloc;
+
+	// Adding/spoofing nolock is unsafe.
+	void (*fp_nolock)(void) [[clang::nolock]];
+	fp_nolock = nolock;
+	fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+	fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+
+	// Adding/spoofing noalloc is unsafe.
+	void (*fp_noalloc)(void) [[clang::noalloc]];
+	fp_noalloc = noalloc;
+	fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
+	fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
+}
+
+// --- VIRTUAL METHODS ---
+#ifdef __cplusplus
+struct Base {
+	virtual void f1();
+	virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
+	virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
+};
+
+struct Derived : public Base {
+	void f1() [[clang::nolock]] override;
+	void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
+	void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
+};
+#endif // __cplusplus
+
+// --- REDECLARATIONS ---
+
+int f2(void);
+// redeclaration with a stronger constraint is OK.
+int f2(void) [[clang::nolock]]; // expected-note {{previous declaration is here}}
+int f2(void) { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+
+int f3(void);
+// redeclaration with a stronger constraint is OK.
+int f3(void) [[clang::noalloc]]; // expected-note {{previous declaration is here}}
+int f3(void) { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
+
+#endif // TEMP_DISABLE
diff --git a/clang/test/Sema/attr-nolock3.cpp b/clang/test/Sema/attr-nolock3.cpp
new file mode 100644
index 00000000000000..cfe52da16447c1
--- /dev/null
+++ b/clang/test/Sema/attr-nolock3.cpp
@@ -0,0 +1,144 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+// --- CONSTRAINTS ---
+#if 0 // TEMP_DISABLE
+
+void nl1() [[clang::nolock]]
+{
+	auto* pInt = new int; // expected-warning {{'nolock' function 'nl1' must not allocate or deallocate memory}}
+}
+
+void nl2() [[clang::nolock]]
+{
+	static int global; // expected-warning {{'nolock' function 'nl2' must not have static locals}}
+}
+
+void nl3() [[clang::nolock]]
+{
+	try {
+		throw 42; // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+	}
+	catch (...) { // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+	}
+}
+
+void nl4_inline() {}
+void nl4_not_inline(); // expected-note {{'nl4_not_inline' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+
+void nl4() [[clang::nolock]]
+{
+	nl4_inline(); // OK
+	nl4_not_inline(); // expected-warning {{'nolock' function 'nl4' must not call non-'nolock' function 'nl4_not_inline'}}
+}
+
+
+struct HasVirtual {
+	virtual void unsafe(); // expected-note {{'HasVirtual::unsafe' cannot be inferred 'nolock' because it is virtual}}
+};
+
+void nl5() [[clang::nolock]]
+{
+ 	HasVirtual hv;
+ 	hv.unsafe(); // expected-warning {{'nolock' function 'nl5' must not call non-'nolock' function 'HasVirtual::unsafe'}}
+}
+
+void nl6_unsafe(); // expected-note {{'nl6_unsafe' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl6_transitively_unsafe()
+{
+	nl6_unsafe(); // expected-note {{'nl6_transitively_unsafe' cannot be inferred 'nolock' because it calls non-'nolock' function 'nl6_unsafe'}}
+}
+
+void nl6() [[clang::nolock]]
+{
+	nl6_transitively_unsafe(); // expected-warning {{'nolock' function 'nl6' must not call non-'nolock' function 'nl6_transitively_unsafe'}}
+}
+
+thread_local int tl_var{ 42 };
+
+bool tl_test() [[clang::nolock]]
+{
+	return tl_var > 0; // expected-warning {{'nolock' function 'tl_test' must not use thread-local variables}}
+}
+
+void nl7()
+{
+	// Make sure we verify blocks
+	auto blk = ^() [[clang::nolock]] {
+		throw 42; // expected-warning {{'nolock' function '(block 0)' must not throw or catch exceptions}}
+	};
+}
+
+void nl8()
+{
+	// Make sure we verify lambdas
+	auto lambda = []() [[clang::nolock]] {
+		throw 42; // expected-warning {{'nolock' function 'nl8()::(anonymous class)::operator()' must not throw or catch exceptions}}
+	};
+}
+
+// Make sure template expansions are found and verified.
+	template <typename T>
+	struct Adder {
+		static T add_explicit(T x, T y) [[clang::nolock]]
+		{
+			return x + y; // expected-warning {{'nolock' function 'Adder<Stringy>::add_explicit' must not call non-'nolock' function 'operator+'}}
+		}
+		static T add_implicit(T x, T y)
+		{
+			return x + y; // expected-note {{'Adder<Stringy2>::add_implicit' cannot be inferred 'nolock' because it calls non-'nolock' function 'operator+'}}
+		}
+	};
+
+	struct Stringy {
+		friend Stringy operator+(const Stringy& x, const Stringy& y)
+		{
+			// Do something inferably unsafe
+			auto* z = new char[42]; // expected-note {{'operator+' cannot be inferred 'nolock' because it allocates/deallocates memory}}
+			return {};
+		}
+	};
+
+	struct Stringy2 {
+		friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
+		{
+			// Do something inferably unsafe
+			throw 42; // expected-note {{'operator+' cannot be inferred 'nolock' because it throws or catches exceptions}}
+		}
+	};
+
+void nl9() [[clang::nolock]]
+{
+	Adder<int>::add_explicit(1, 2);
+	Adder<int>::add_implicit(1, 2);
+
+	Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
+	Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function 'nl9' must not call non-'nolock' function 'Adder<Stringy2>::add_implicit'}} \
+		expected-note {{in template expansion here}}
+}
+
+void nl10(
+	void (*fp1)(), // expected-note {{'fp1' cannot be inferred 'nolock' because it is a function pointer}}
+	void (*fp2)() [[clang::nolock]]
+	) [[clang::nolock]]
+{
+	fp1(); // expected-warning {{'nolock' function 'nl10' must not call non-'nolock' function 'fp1'}}
+	fp2();
+}
+
+#endif // TEMP_DISABLE
+
+// --- PLAYGROUND ---
+
+void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{'nl11_no_inference' does not permit inference of 'nolock'}}
+{
+}
+
+void nl11() [[clang::nolock]]
+{
+	nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
+}
+

>From 2a1b0d2d99c9bc4595dfc98f250bf43d23a28093 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 12 Mar 2024 13:22:58 -0700
Subject: [PATCH 02/18] Cleanup, mostly code style

---
 clang/include/clang/AST/Type.h                |   24 +-
 clang/include/clang/Basic/Attr.td             |    4 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |    6 +-
 clang/lib/AST/Decl.cpp                        |   80 +-
 clang/lib/AST/Type.cpp                        |  287 ++--
 clang/lib/AST/TypePrinter.cpp                 |    8 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 1316 +++++++----------
 clang/lib/Sema/Sema.cpp                       |   23 +-
 clang/lib/Sema/SemaDecl.cpp                   |   31 +-
 clang/lib/Sema/SemaDeclAttr.cpp               |   16 +-
 clang/lib/Sema/SemaDeclCXX.cpp                |   17 +-
 clang/lib/Sema/SemaType.cpp                   |   44 +-
 clang/test/Sema/attr-nolock-wip.cpp           |   19 +
 clang/test/Sema/attr-nolock2.cpp              |    4 -
 clang/test/Sema/attr-nolock3.cpp              |    7 +-
 15 files changed, 782 insertions(+), 1104 deletions(-)
 create mode 100644 clang/test/Sema/attr-nolock-wip.cpp

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 41c60a6e221d4e..de21561ce5dcad 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4221,7 +4221,7 @@ class FunctionEffectSet {
 
     UniquedAndSortedFX(Base Array) : Base(Array) {}
     UniquedAndSortedFX(const FunctionEffect **Ptr, size_t Len)
-        : Base(ptr, len) {}
+        : Base(Ptr, Len) {}
 
     bool operator<(const UniquedAndSortedFX &rhs) const;
   };
@@ -7955,14 +7955,14 @@ class FunctionEffect {
       llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
 
   FunctionEffect(EffectType T, Flags F, const char *Name)
-      : Type_{T}, Flags_{F}, Name{Name} {}
+      : Type_(T), Flags_(F), Name(Name) {}
   virtual ~FunctionEffect();
 
   /// The type of the effect.
   EffectType type() const { return Type_; }
 
   /// Flags describing behaviors of the effect.
-  Flags getFlags() const { return Flags_; }
+  Flags flags() const { return Flags_; }
 
   /// The description printed in diagnostics, e.g. 'nolock'.
   StringRef name() const { return Name; }
@@ -7972,13 +7972,13 @@ class FunctionEffect {
 
   /// Return true if adding or removing the effect as part of a type conversion
   /// should generate a diagnostic.
-  virtual bool diagnoseConversion(bool adding, QualType OldType,
+  virtual bool diagnoseConversion(bool Adding, QualType OldType,
                                   FunctionEffectSet OldFX, QualType NewType,
                                   FunctionEffectSet NewFX) const;
 
   /// Return true if adding or removing the effect in a redeclaration should
   /// generate a diagnostic.
-  virtual bool diagnoseRedeclaration(bool adding,
+  virtual bool diagnoseRedeclaration(bool Adding,
                                      const FunctionDecl &OldFunction,
                                      FunctionEffectSet OldFX,
                                      const FunctionDecl &NewFunction,
@@ -7986,7 +7986,7 @@ class FunctionEffect {
 
   /// Return true if adding or removing the effect in a C++ virtual method 
   /// override should generate a diagnostic.
-  virtual bool diagnoseMethodOverride(bool adding,
+  virtual bool diagnoseMethodOverride(bool Adding,
                                       const CXXMethodDecl &OldMethod,
                                       FunctionEffectSet OldFX,
                                       const CXXMethodDecl &NewMethod,
@@ -8003,7 +8003,7 @@ class FunctionEffect {
   // returned for a direct call, then the kInferrableOnCallees flag may trigger
   // inference rather than an immediate diagnostic. Caller should be assumed to
   // have the effect (it may not have it explicitly when inferring).
-  virtual bool diagnoseFunctionCall(bool direct, const Decl *Caller,
+  virtual bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
                                     FunctionEffectSet CallerFX,
                                     CalleeDeclOrType Callee,
                                     FunctionEffectSet CalleeFX) const;
@@ -8018,21 +8018,21 @@ class NoLockNoAllocEffect : public FunctionEffect {
   static const NoLockNoAllocEffect &nolock_instance();
   static const NoLockNoAllocEffect &noalloc_instance();
 
-  NoLockNoAllocEffect(EffectType ty, const char *name);
+  NoLockNoAllocEffect(EffectType Type, const char *Name);
   ~NoLockNoAllocEffect() override;
 
   std::string attribute() const override;
 
-  bool diagnoseConversion(bool adding, QualType OldType,
+  bool diagnoseConversion(bool Adding, QualType OldType,
                           FunctionEffectSet OldFX, QualType NewType,
                           FunctionEffectSet NewFX) const override;
 
-  bool diagnoseRedeclaration(bool adding, const FunctionDecl &OldFunction,
+  bool diagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
                              FunctionEffectSet OldFX,
                              const FunctionDecl &NewFunction,
                              FunctionEffectSet NewFX) const override;
 
-  bool diagnoseMethodOverride(bool adding, const CXXMethodDecl &OldMethod,
+  bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
                               FunctionEffectSet OldFX,
                               const CXXMethodDecl &NewMethod,
                               FunctionEffectSet NewFX) const override;
@@ -8040,7 +8040,7 @@ class NoLockNoAllocEffect : public FunctionEffect {
   bool canInferOnDecl(const Decl *Caller,
                       FunctionEffectSet CallerFX) const override;
 
-  bool diagnoseFunctionCall(bool direct, const Decl *Caller,
+  bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
                             FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
                             FunctionEffectSet CalleeFX) const override;
 };
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 78bbe5185741b4..4033b9efb86f39 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1404,7 +1404,7 @@ def CXX11NoReturn : InheritableAttr {
 
 def NoLock : DeclOrTypeAttr {
   let Spellings = [CXX11<"clang", "nolock">,
-                   C2x<"clang", "nolock">,
+                   C23<"clang", "nolock">,
                    GNU<"clang_nolock">];
 
   // Subjects - not needed?
@@ -1416,7 +1416,7 @@ def NoLock : DeclOrTypeAttr {
 
 def NoAlloc : DeclOrTypeAttr {
   let Spellings = [CXX11<"clang", "noalloc">,
-                   C2x<"clang", "noalloc">,
+                   C23<"clang", "noalloc">,
                    GNU<"clang_noalloc">];
   // Subjects - not needed?
   //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5777f7dfbbcad1..df553a47a8dbe7 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10838,9 +10838,9 @@ def note_func_effect_call_func_ptr : Note<
   "'%1' cannot be inferred '%0' because it is a function pointer">;
 
 // TODO: Not currently being generated
-def warn_perf_annotation_implies_noexcept : Warning<
-  "'%0' function should be declared noexcept">,
-  InGroup<PerfAnnotationImpliesNoexcept>;
+// def warn_perf_annotation_implies_noexcept : Warning<
+//  "'%0' function should be declared noexcept">,
+//  InGroup<PerfAnnotationImpliesNoexcept>;
 
 // TODO: Not currently being generated
 def warn_func_effect_false_on_type : Warning<
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 596f4ea2e67176..502c978d5b78e4 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -3517,61 +3517,6 @@ bool FunctionDecl::isMemberLikeConstrainedFriend() const {
   return FriendConstraintRefersToEnclosingTemplate();
 }
 
-static void examine(const Decl& D)
-{
-  PrintingPolicy PP = D.getASTContext().getPrintingPolicy();
-  PP.TerseOutput = 1;
-  D.print(llvm::outs(), PP);
-}
-
-#if TEMP_DISABLE
-// This constant controls the default policy.
-constexpr static bool kDefaultCanInferPerfAnnotation = true;
-
-static bool declCanInferPerfAnnotation(const Decl &D, QualType QT) {
-  // llvm::outs() << "declCanInferPerfAnnotation " << &D << "\n";
-  // examine(D);
-
-  // nolock(false) or noalloc(false) disables inference.
-  if (QT->disallowPerfAnnotationInference()) {
-    // llvm::outs() << "  disallowed by QT\n";
-    return false;
-  }
-  if (auto *IA = D.getAttr<PerformanceInferredAttr>()) {
-    // llvm::outs() << "  decl has attr " << IA->getCanInfer() << "\n";
-    return IA->getCanInfer();
-  }
-  if (auto *Method = dyn_cast<CXXMethodDecl>(&D)) {
-    auto *Class = Method->getParent();
-    if (auto *IA = Class->getAttr<PerformanceInferredAttr>()) {
-      // llvm::outs() << "  class has attr " << IA->getCanInfer() << "\n";
-      return IA->getCanInfer();
-    }
-  }
-
-  // for (const DeclContext *DC = D.getDeclContext(); DC != nullptr; DC = DC->getParent()) {
-  //   if (auto *Decl2 = dyn_cast<Decl>(DC)) {
-  //     examine(*Decl2);
-  //     if (auto *IA = Decl2->getAttr<PerformanceInferredAttr>()) {
-  //       llvm::outs() << "  decl2 has attr " << IA->getCanInfer() << "\n";
-  //       return IA->getCanInfer();
-  //     }
-  //   }
-  // }
-
-  // llvm::outs() << "  result: false\n";
-  return kDefaultCanInferPerfAnnotation;
-}
-
-PerfAnnotation FunctionDecl::getPerfAnnotation() const {
-  return getType()->getPerfAnnotation();
-}
-
-bool FunctionDecl::canInferPerfAnnotation() const {
-  return declCanInferPerfAnnotation(*this, getType());
-}
-#endif // TEMP_DISABLE
-
 MultiVersionKind FunctionDecl::getMultiVersionKind() const {
   if (hasAttr<TargetAttr>())
     return MultiVersionKind::Target;
@@ -5282,35 +5227,14 @@ SourceRange BlockDecl::getSourceRange() const {
 }
 
 FunctionEffectSet BlockDecl::getFunctionEffects() const {
-  if (auto* TSI = getSignatureAsWritten()) {
-    if (auto* FPT = TSI->getType()->getAs<FunctionProtoType>()) {
+  if (auto *TSI = getSignatureAsWritten()) {
+    if (auto *FPT = TSI->getType()->getAs<FunctionProtoType>()) {
       return FPT->getFunctionEffects();
     }
   }
   return {};
 }
 
-#if TEMP_DISABLE
-PerfAnnotation BlockDecl::getPerfAnnotation() const
-{
-  if (auto* TSI = getSignatureAsWritten()) {
-    return TSI->getType()->getPerfAnnotation();
-  }
-  return PerfAnnotation::None;
-}
-#endif // TEMP_DISABLE
-
-#if 0
-// unused
-bool BlockDecl::canInferPerfAnnotation() const
-{
-  if (auto* TSI = getSignatureAsWritten()) {
-    return declCanInferPerfAnnotation(*this, TSI->getType());
-  }
-  return kDefaultCanInferPerfAnnotation;
-}
-#endif
-
 //===----------------------------------------------------------------------===//
 // Other Decl Allocation/Deallocation Method Implementations
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index ce209be2c3a079..998360db3c5dd0 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3694,7 +3694,6 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
   ID.AddInteger(unsigned(epi.Variadic) +
                 (epi.RefQualifier << 1) +
                 (epi.ExceptionSpec.Type << 3));
-
   ID.Add(epi.TypeQuals);
   if (epi.ExceptionSpec.Type == EST_Dynamic) {
     for (QualType Ex : epi.ExceptionSpec.Exceptions)
@@ -4924,69 +4923,77 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
           getTypeConstraintConcept(), getTypeConstraintArguments());
 }
 
-
 FunctionEffect::~FunctionEffect() = default;
 
-bool FunctionEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
-    QualType NewType, FunctionEffectSet NewFX) const
-{
+bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
+                                        FunctionEffectSet OldFX,
+                                        QualType NewType,
+                                        FunctionEffectSet NewFX) const {
   return false;
 }
 
-bool FunctionEffect::diagnoseRedeclaration(bool adding,
-    const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
-    const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const { return false; }
+bool FunctionEffect::diagnoseRedeclaration(bool Adding,
+                                           const FunctionDecl &OldFunction,
+                                           FunctionEffectSet OldFX,
+                                           const FunctionDecl &NewFunction,
+                                           FunctionEffectSet NewFX) const {
+  return false;
+}
 
-bool FunctionEffect::diagnoseMethodOverride(bool adding,
-    const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
-    const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const { return false; }
+bool FunctionEffect::diagnoseMethodOverride(bool Adding,
+                                            const CXXMethodDecl &OldMethod,
+                                            FunctionEffectSet OldFX,
+                                            const CXXMethodDecl &NewMethod,
+                                            FunctionEffectSet NewFX) const {
+  return false;
+}
 
-bool FunctionEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
-{
+bool FunctionEffect::canInferOnDecl(const Decl *Caller,
+                                    FunctionEffectSet CallerFX) const {
   return false;
 }
 
-bool FunctionEffect::diagnoseFunctionCall(bool direct, 
-    const Decl* Caller, FunctionEffectSet CallerFX,
-    CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
-{
+bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
+                                          FunctionEffectSet CallerFX,
+                                          CalleeDeclOrType Callee,
+                                          FunctionEffectSet CalleeFX) const {
   return false;
 }
 
-const NoLockNoAllocEffect& NoLockNoAllocEffect::nolock_instance()
-{
+const NoLockNoAllocEffect &NoLockNoAllocEffect::nolock_instance() {
   static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
   return global;
 }
 
-const NoLockNoAllocEffect& NoLockNoAllocEffect::noalloc_instance()
-{
+const NoLockNoAllocEffect &NoLockNoAllocEffect::noalloc_instance() {
   static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
   return global;
 }
 
 // TODO: Separate flags for noalloc
-NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType ty, const char* name)
-  : FunctionEffect{ ty, kRequiresVerification | kVerifyCalls | kInferrableOnCallees | kExcludeThrow | kExcludeCatch
-    | kExcludeObjCMessageSend | kExcludeStaticLocalVars | kExcludeThreadLocalVars, name }
-{
-}
+NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType Ty, const char *Name)
+    : FunctionEffect(Ty,
+                     kRequiresVerification | kVerifyCalls |
+                         kInferrableOnCallees | kExcludeThrow | kExcludeCatch |
+                         kExcludeObjCMessageSend | kExcludeStaticLocalVars |
+                         kExcludeThreadLocalVars,
+                     Name) {}
 
 NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
 
-std::string NoLockNoAllocEffect::attribute() const
-{
-  return std::string{ "__attribute__((clang_" } + name().str() + "))";
+std::string NoLockNoAllocEffect::attribute() const {
+  return std::string{"__attribute__((clang_"} + name().str() + "))";
 }
 
-bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType, FunctionEffectSet OldFX,
-    QualType NewType, FunctionEffectSet NewFX) const
-{
+bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType,
+                                             FunctionEffectSet OldFX,
+                                             QualType NewType,
+                                             FunctionEffectSet NewFX) const {
   // noalloc can't be added (spoofed) during a conversion, unless we have nolock
   if (adding) {
     if (!isNoLock()) {
-      for (const auto* effect : OldFX) {
-        if (effect->type() == kNoLockTrue)
+      for (const auto *Effect : OldFX) {
+        if (Effect->type() == kNoLockTrue)
           return false;
       }
     }
@@ -4996,29 +5003,29 @@ bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType, Func
   return false;
 }
 
-bool NoLockNoAllocEffect::diagnoseRedeclaration(bool adding,
-  const FunctionDecl& OldFunction, FunctionEffectSet OldFX,
-  const FunctionDecl& NewFunction, FunctionEffectSet NewFX) const
-{
+bool NoLockNoAllocEffect::diagnoseRedeclaration(bool Adding,
+                                                const FunctionDecl &OldFunction,
+                                                FunctionEffectSet OldFX,
+                                                const FunctionDecl &NewFunction,
+                                                FunctionEffectSet NewFX) const {
   // nolock/noalloc can't be removed in a redeclaration
   // adding -> false, removing -> true (diagnose)
-  return !adding;
+  return !Adding;
 }
 
-bool NoLockNoAllocEffect::diagnoseMethodOverride(bool adding,
-  const CXXMethodDecl& OldMethod, FunctionEffectSet OldFX,
-  const CXXMethodDecl& NewMethod, FunctionEffectSet NewFX) const
-{
+bool NoLockNoAllocEffect::diagnoseMethodOverride(
+    bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
+    const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
   // nolock/noalloc can't be removed from an override
-  return !adding;
+  return !Adding;
 }
 
-bool NoLockNoAllocEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet CallerFX) const
-{
+bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
+                                         FunctionEffectSet CallerFX) const {
   // Does the Decl have nolock(false) / noalloc(false) ?
   QualType QT;
   if (isa<BlockDecl>(Caller)) {
-    const auto* TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
+    const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
     QT = TSI->getType();
   } else if (isa<ValueDecl>(Caller)) {
     QT = cast<ValueDecl>(Caller)->getType();
@@ -5032,14 +5039,15 @@ bool NoLockNoAllocEffect::canInferOnDecl(const Decl* Caller, FunctionEffectSet C
   return true;
 }
 
-bool NoLockNoAllocEffect::diagnoseFunctionCall(bool direct, 
-    const Decl* Caller, FunctionEffectSet CallerFX,
-    CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const
-{
-  const EffectType callerType = type();
-  for (const auto* effect : CalleeFX) {
-    const EffectType ty = effect->type();
-    if (ty == callerType || (callerType == kNoAllocTrue && ty == kNoLockTrue)) {
+// TODO: Notice that we don't care about some of the parameters. Is the
+// interface overly general?
+bool NoLockNoAllocEffect::diagnoseFunctionCall(
+    bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
+    CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
+  const EffectType CallerType = type();
+  for (const auto *Effect : CalleeFX) {
+    const EffectType ET = Effect->type();
+    if (ET == CallerType || (CallerType == kNoAllocTrue && ET == kNoLockTrue)) {
       return false;
     }
   }
@@ -5048,142 +5056,145 @@ bool NoLockNoAllocEffect::diagnoseFunctionCall(bool direct,
 
 // =====
 
-void MutableFunctionEffectSet::insert(const FunctionEffect* effect)
-{
-  auto iter = std::lower_bound(begin(), end(), effect);
-  if (*iter != effect) {
-    insert(iter, effect);
+void MutableFunctionEffectSet::insert(const FunctionEffect *Effect) {
+  auto Iter = std::lower_bound(begin(), end(), Effect);
+  if (*Iter != Effect) {
+    insert(Iter, Effect);
   }
 }
 
-MutableFunctionEffectSet& MutableFunctionEffectSet::operator|=(FunctionEffectSet rhs)
-{
-  // TODO: For large rhs sets, use set_union or a custom insert-in-place
-  for (const auto* effect : rhs) {
-    insert(effect);
+MutableFunctionEffectSet &
+MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
+  // TODO: For large RHS sets, use set_union or a custom insert-in-place
+  for (const auto *Effect : RHS) {
+    insert(Effect);
   }
   return *this;
 }
 
-// This could be simpler if there were a simple set container that could be queried by
-// ArrayRef but which stored something else. Possibly a DenseMap with void values?
-FunctionEffectSet FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect*> items)
-{
-  if (items.empty()) {
+// This could be simpler if there were a simple set container that could be
+// queried by ArrayRef but which stored something else. Possibly a DenseMap with
+// void values?
+FunctionEffectSet
+FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
+  if (Items.empty()) {
     return FunctionEffectSet{};
   }
-  if (items.size() == 1) {
-    return FunctionEffectSet{ items[0] };
+  if (Items.size() == 1) {
+    return FunctionEffectSet{Items[0]};
   }
 
-  UniquedAndSortedFX newSet{ items }; // just copies the ArrayRef
+  UniquedAndSortedFX NewSet(Items); // just copies the ArrayRef
 
-  // SmallSet only has contains(), so it provides no way to obtain the uniqued value.
+  // SmallSet only has contains(), so it provides no way to obtain the uniqued
+  // value.
   static std::set<UniquedAndSortedFX> uniquedFXSets;
 
   // See if we already have this set.
-  const auto iter = uniquedFXSets.find(newSet);
-  if (iter != uniquedFXSets.end()) {
-    return FunctionEffectSet{ &*iter };
+  const auto Iter = uniquedFXSets.find(NewSet);
+  if (Iter != uniquedFXSets.end()) {
+    return FunctionEffectSet{&*Iter};
   }
 
   // Copy the incoming array to permanent storage.
-  auto* storage = new const FunctionEffect*[items.size()];
-  std::copy(items.begin(), items.end(), storage);
+  auto *Storage = new const FunctionEffect *[Items.size()];
+  std::copy(Items.begin(), Items.end(), Storage);
 
   // Make a new wrapper and insert it into the set.
-  newSet = UniquedAndSortedFX{ storage, items.size() };
-  auto [insiter, good] = uniquedFXSets.insert(newSet);
-  return FunctionEffectSet{ &*insiter };
+  NewSet = UniquedAndSortedFX(Storage, Items.size());
+  auto [InsIter, _] = uniquedFXSets.insert(NewSet);
+  return FunctionEffectSet(&*InsIter);
 }
 
-FunctionEffectSet FunctionEffectSet::operator|(const FunctionEffectSet& rhs) const
-{
-  const FunctionEffectSet& lhs = *this;
-  if (lhs.empty()) {
-    return rhs;
-  }
-  if (rhs.empty()) {
-    return lhs;
-  }
+FunctionEffectSet
+FunctionEffectSet::operator|(const FunctionEffectSet &RHS) const {
+  // Optimize for one of the two sets being empty
+  const FunctionEffectSet &LHS = *this;
+  if (LHS.empty())
+    return RHS;
+  if (RHS.empty())
+    return LHS;
+
   // Optimize the case where the two sets are identical
-  if (lhs == rhs) {
-    return lhs;
-  }
+  if (LHS == RHS)
+    return LHS;
 
-  MutableFunctionEffectSet vec;
-  vec.reserve(lhs.size() + rhs.size());
-  std::set_union(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+  MutableFunctionEffectSet Vec;
+  Vec.reserve(LHS.size() + RHS.size());
+  std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+                 std::back_inserter(Vec));
   // The result of a set operation is an ordered/unique set.
-  return FunctionEffectSet::create(vec);
+  return FunctionEffectSet::create(Vec);
 }
 
-MutableFunctionEffectSet FunctionEffectSet::operator&(const FunctionEffectSet& rhs) const
-{
-  const FunctionEffectSet& lhs = *this;
-  if (lhs.empty() || rhs.empty()) {
+MutableFunctionEffectSet
+FunctionEffectSet::operator&(const FunctionEffectSet &RHS) const {
+  const FunctionEffectSet &LHS = *this;
+  if (LHS.empty() || RHS.empty()) {
     return {};
   }
 
-  MutableFunctionEffectSet vec;
-  std::set_intersection(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(vec));
+  MutableFunctionEffectSet Vec;
+  std::set_intersection(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+                        std::back_inserter(Vec));
   // The result of a set operation is an ordered/unique set.
-  return vec;
+  return Vec;
 }
 
 // TODO: inline?
-FunctionEffectSet FunctionEffectSet::get(const Type& TyRef)
-{
-  const Type* Ty = &TyRef;      
+FunctionEffectSet FunctionEffectSet::get(const Type &TyRef) {
+  const Type *Ty = &TyRef;
   if (Ty->isPointerType())
     Ty = Ty->getPointeeType().getTypePtr();
-  if (const auto* FPT = Ty->getAs<FunctionProtoType>())
+  if (const auto *FPT = Ty->getAs<FunctionProtoType>())
     return FPT->getFunctionEffects();
   return {};
 }
 
-FunctionEffectSet::Differences FunctionEffectSet::differences(
-    const FunctionEffectSet& Old, const FunctionEffectSet& New)
-{
+FunctionEffectSet::Differences
+FunctionEffectSet::differences(const FunctionEffectSet &Old,
+                               const FunctionEffectSet &New) {
   // TODO: Could be a one-pass algorithm.
-  Differences result;
-  for (const auto* effect : (New - Old)) {
-    result.emplace_back(effect, true);
+  Differences Result;
+  for (const auto *Effect : (New - Old)) {
+    Result.emplace_back(Effect, true);
   }
-  for (const auto* effect : (Old - New)) {
-    result.emplace_back(effect, false);
+  for (const auto *Effect : (Old - New)) {
+    Result.emplace_back(Effect, false);
   }
-  return result;
+  return Result;
 }
 
-MutableFunctionEffectSet FunctionEffectSet::operator-(const FunctionEffectSet& rhs) const
-{
-  const FunctionEffectSet& lhs = *this;
-  MutableFunctionEffectSet result;
+MutableFunctionEffectSet
+FunctionEffectSet::operator-(const FunctionEffectSet &RHS) const {
+  const FunctionEffectSet &LHS = *this;
+  MutableFunctionEffectSet Result;
 
-  std::set_difference(lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), std::back_inserter(result));
-  return result;
+  std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+                      std::back_inserter(Result));
+  return Result;
 }
 
-bool FunctionEffectSet::operator<(const FunctionEffectSet& rhs) const
-{
-  const FunctionEffectSet& lhs = *this;
-  return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+bool FunctionEffectSet::operator<(const FunctionEffectSet &RHS) const {
+  const FunctionEffectSet &LHS = *this;
+  return std::lexicographical_compare(LHS.begin(), LHS.end(), RHS.begin(),
+                                      RHS.end());
 }
 
-bool FunctionEffectSet::UniquedAndSortedFX::operator<(const UniquedAndSortedFX& rhs) const
-{
-  return this < &rhs;
+bool FunctionEffectSet::UniquedAndSortedFX::operator<(
+    const UniquedAndSortedFX &RHS) const {
+  return this < &RHS;
 }
 
-void FunctionEffectSet::dump(llvm::raw_ostream &OS) const
-{
+void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
   OS << "FX{";
-  bool first = true;
-  for (const auto* effect : *this) {
-    if (!first) OS << ", ";
-    else first = false;
-    OS << effect->name();
+  bool First = true;
+  for (const auto *Effect : *this) {
+    if (!First)
+      OS << ", ";
+    else
+      First = false;
+    OS << Effect->name();
   }
   OS << "}";
 }
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 560f2faeb9e503..252a5dfbc952b0 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -984,12 +984,11 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
     OS << " &&";
     break;
   }
-
   T->printExceptionSpecification(OS, Policy);
 
-  if (auto effects = T->getFunctionEffects()) {
-    for (const auto* effect : effects) {
-      OS << " " << effect->attribute();
+  if (const FunctionEffectSet FX = T->getFunctionEffects()) {
+    for (const auto *Effect : FX) {
+      OS << " " << Effect->attribute();
     }
   }
 
@@ -1911,7 +1910,6 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::AArch64SVEPcs: OS << "aarch64_sve_pcs"; break;
   case attr::AMDGPUKernelCall: OS << "amdgpu_kernel"; break;
   case attr::IntelOclBicc: OS << "inteloclbicc"; break;
-
   case attr::PreserveMost:
     OS << "preserve_most";
     break;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index a0d813fa4d3d14..69417b10049e97 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -27,6 +27,8 @@
 #include "clang/AST/StmtObjC.h"
 #include "clang/AST/StmtVisitor.h"
 #include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
 #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
 #include "clang/Analysis/Analyses/CalledOnceCheck.h"
 #include "clang/Analysis/Analyses/Consumed.h"
@@ -2382,6 +2384,7 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 
 // =============================================================================
 
+// Temporary debugging option
 #define FX_ANALYZER_VERIFY_DECL_LIST 1
 
 namespace FXAnalysis {
@@ -2396,41 +2399,37 @@ enum class DiagnosticID : uint8_t {
   AccessesThreadLocal,
 
   // These only apply to callees, where the analysis stops at the Decl
-  // DeclExternWithoutConstraint, // TODO: not used?
   DeclWithoutConstraintOrInference,
-  //DeclVirtualWithoutConstraint,
-  //DeclFuncPtrWithoutConstraint,
 
   CallsUnsafeDecl,
   CallsDisallowedExpr,
 };
 
 struct Diagnostic {
-  const FunctionEffect* Effect = nullptr;
-  const Decl* Callee = nullptr; // only valid for Calls*
-  SourceLocation Loc{};
-  DiagnosticID ID{ DiagnosticID::None };
+  const FunctionEffect *Effect = nullptr;
+  const Decl *Callee = nullptr; // only valid for Calls*
+  SourceLocation Loc;
+  DiagnosticID ID = DiagnosticID::None;
 
   Diagnostic() = default;
 
-  Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc, const Decl* Callee = nullptr)
-    : Effect{ Effect }, Callee{ Callee }, Loc{ Loc }, ID{ ID }
-  {
-  }
+  Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+             const Decl *Callee = nullptr)
+      : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
 };
 
-enum class SpecialFuncType : uint8_t {
-  None, OperatorNew, OperatorDelete
-};
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
 enum class CallType {
-  Unknown, Function, Virtual, Block
+  Unknown,
+  Function,
+  Virtual,
+  Block
   // unknown: probably function pointer
 };
 
 // Return whether the function CAN be verified.
 // The question of whether it SHOULD be verified is independent.
-static bool functionIsVerifiable(const FunctionDecl* FD)
-{
+static bool functionIsVerifiable(const FunctionDecl *FD) {
   if (!(FD->hasBody() || FD->isInlined())) {
     // externally defined; we couldn't verify if we wanted to.
     return false;
@@ -2443,45 +2442,37 @@ static bool functionIsVerifiable(const FunctionDecl* FD)
   return true;
 }
 
-// Transitory, more extended information about a callable, which can be a function,
-// block, function pointer...
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer...
 struct CallableInfo {
-  const Decl* CDecl;
-  mutable std::optional<std::string> MaybeName; // mutable because built on demand in const method
-  SpecialFuncType FuncType{ SpecialFuncType::None };
+  const Decl *CDecl;
+  mutable std::optional<std::string>
+      MaybeName; // mutable because built on demand in const method
+  SpecialFuncType FuncType = SpecialFuncType::None;
   FunctionEffectSet Effects;
-  CallType CType{ CallType::Unknown };
-  // bool IsAllowListed{ false };
+  CallType CType = CallType::Unknown;
 
-  CallableInfo(const Decl& CD, SpecialFuncType FT = SpecialFuncType::None)
-    : CDecl{ &CD }, FuncType{ FT }
-  {
-    //llvm::errs() << "CallableInfo " << name() << "\n";
+  CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+      : CDecl(&CD), FuncType(FT) {
+    // llvm::errs() << "CallableInfo " << name() << "\n";
 
-    if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
+    if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
       assert(FD->getCanonicalDecl() == FD);
       // Use the function's definition, if any.
-      if (auto* Def = FD->getDefinition()) {
+      if (auto *Def = FD->getDefinition()) {
         CDecl = FD = Def;
       }
       CType = CallType::Function;
-      if (auto* Method = dyn_cast<CXXMethodDecl>(FD)) {
+      if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
         if (Method->isVirtual()) {
           CType = CallType::Virtual;
         }
       }
       Effects = FD->getFunctionEffects();
-
-      // TODO: Generalize via noreturn??? but that would cover exceptions too.
-      // if (name() == "__assert_rtn") {
-      //   // big hack because it's hard to get the attribute to stick on it
-      //   // through a redeclaration, not sure why.
-      //   IsAllowListed = true;
-      // }
-    } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+    } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
       CType = CallType::Block;
       Effects = BD->getFunctionEffects();
-    } else if (auto* VD = dyn_cast<ValueDecl>(CDecl)) {
+    } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
       // ValueDecl is function, enum, or variable, so just look at the type.
       Effects = FunctionEffectSet::get(*VD->getType());
     }
@@ -2491,8 +2482,7 @@ struct CallableInfo {
     return CType == CallType::Function || CType == CallType::Block;
   }
 
-  bool isVerifiable() const
-  {
+  bool isVerifiable() const {
     switch (CType) {
     case CallType::Unknown:
     case CallType::Virtual:
@@ -2505,19 +2495,18 @@ struct CallableInfo {
     return false;
   }
 
-  /// Generate a name for logging.
-  std::string name(Sema& sema) const
-  {
+  /// Generate a name for logging and diagnostics.
+  std::string name(Sema &Sem) const {
     if (!MaybeName) {
       std::string Name;
       llvm::raw_string_ostream OS(Name);
 
-      if (auto* FD = dyn_cast<FunctionDecl>(CDecl)) {
-        FD->getNameForDiagnostic(OS, sema.getPrintingPolicy(),
-                                    /*Qualified=*/true);
-      } else if (auto* BD = dyn_cast<BlockDecl>(CDecl)) {
+      if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+        FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+                                 /*Qualified=*/true);
+      } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
         OS << "(block " << BD->getBlockManglingNumber() << ")";
-      } else if (auto* VD = dyn_cast<NamedDecl>(CDecl)) {
+      } else if (auto *VD = dyn_cast<NamedDecl>(CDecl)) {
         VD->printQualifiedName(OS);
       }
       MaybeName = Name;
@@ -2529,35 +2518,34 @@ struct CallableInfo {
 // ----------
 // Map effects to single diagnostics.
 class EffectToDiagnosticMap {
-  // Since we currently only have a tiny number of effects (typically no more than 1),
-  // use a sorted SmallVector.
-  using Element = std::pair<const FunctionEffect*, Diagnostic>;
+  // Since we currently only have a tiny number of effects (typically no more
+  // than 1), use a sorted SmallVector.
+  using Element = std::pair<const FunctionEffect *, Diagnostic>;
   using ImplVec = llvm::SmallVector<Element>;
   std::unique_ptr<ImplVec> Impl;
+
 public:
-  Diagnostic& getOrInsertDefault(const FunctionEffect* key)
-  {
+  Diagnostic &getOrInsertDefault(const FunctionEffect *Key) {
     if (Impl == nullptr) {
       Impl = std::make_unique<llvm::SmallVector<Element>>();
-      auto& item = Impl->emplace_back();
-      item.first = key;
-      return item.second;
+      auto &Item = Impl->emplace_back();
+      Item.first = Key;
+      return Item.second;
     }
-    Element elem{ key, {} };
-    auto iter = _find(elem);
-    if (iter != Impl->end() && iter->first == key) {
-      return iter->second;
+    Element Elem(Key, {});
+    auto Iter = _find(Elem);
+    if (Iter != Impl->end() && Iter->first == Key) {
+      return Iter->second;
     }
-    iter = Impl->insert(iter, elem);
-    return iter->second;
+    Iter = Impl->insert(Iter, Elem);
+    return Iter->second;
   }
 
-  const Diagnostic* lookup(const FunctionEffect* key)
-  {
+  const Diagnostic *lookup(const FunctionEffect *key) {
     if (Impl == nullptr) {
       return nullptr;
     }
-    Element elem{ key, {} };
+    Element elem(key, {});
     auto iter = _find(elem);
     if (iter != Impl->end() && iter->first == key) {
       return &iter->second;
@@ -2568,23 +2556,23 @@ class EffectToDiagnosticMap {
   size_t size() const { return Impl ? Impl->size() : 0; }
 
 private:
-  ImplVec::iterator _find(const Element& elem)
-  {
-    return std::lower_bound(Impl->begin(), Impl->end(), elem, [](const Element& lhs, const Element& rhs) {
-      return lhs.first < rhs.first;
-    });
+  ImplVec::iterator _find(const Element &elem) {
+    return std::lower_bound(Impl->begin(), Impl->end(), elem,
+                            [](const Element &lhs, const Element &rhs) {
+                              return lhs.first < rhs.first;
+                            });
   }
 };
 
 // ----------
-// State pertaining to a function whose AST is walked. Since there are potentially a large
-// number of these objects, it needs care about size.
+// State pertaining to a function whose AST is walked. Since there are
+// potentially a large number of these objects, it needs care about size.
 class PendingFunctionAnalysis {
   // Current size: 5 pointers
   friend class CompleteFunctionAnalysis;
 
   struct DirectCall {
-    const Decl* Callee;
+    const Decl *Callee;
     SourceLocation CallLoc;
   };
 
@@ -2596,21 +2584,22 @@ class PendingFunctionAnalysis {
   FunctionEffectSet FXToInfer;
 
 private:
-  // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr to optimize
-  // size for the case of 0 diagnostics.
+  // Diagnostics pertaining to the function's explicit effects. Use a unique_ptr
+  // to optimize size for the case of 0 diagnostics.
   std::unique_ptr<SmallVector<Diagnostic>> DiagnosticsForExplicitFX;
 
-  // Potential diagnostics pertaining to other, non-explicit, inferrable effects.
+  // Potential diagnostics pertaining to other, non-explicit, inferrable
+  // effects.
   EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
 
   std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
 
 public:
-  PendingFunctionAnalysis(const CallableInfo& cinfo, FunctionEffectSet AllInferrableEffectsToVerify)
-  {
+  PendingFunctionAnalysis(const CallableInfo &cinfo,
+                          FunctionEffectSet AllInferrableEffectsToVerify) {
     MutableFunctionEffectSet fx;
-    for (const auto* effect : cinfo.Effects) {
-      if (effect->getFlags() & FunctionEffect::kRequiresVerification) {
+    for (const auto *effect : cinfo.Effects) {
+      if (effect->flags() & FunctionEffect::kRequiresVerification) {
         fx.insert(effect);
       }
     }
@@ -2618,71 +2607,73 @@ class PendingFunctionAnalysis {
 
     // Check for effects we are not allowed to infer
     fx.clear();
-    for (const auto* effect : AllInferrableEffectsToVerify) {
+    for (const auto *effect : AllInferrableEffectsToVerify) {
       if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
         fx.insert(effect);
       } else {
         // Add a diagnostic for this effect if a caller were to
         // try to infer it.
-        auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
-        diag = Diagnostic{ effect, DiagnosticID::DeclWithoutConstraintOrInference,
-          cinfo.CDecl->getLocation() };
+        auto &diag =
+            InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
+        diag =
+            Diagnostic(effect, DiagnosticID::DeclWithoutConstraintOrInference,
+                       cinfo.CDecl->getLocation());
       }
     }
     // fx is now the set of inferrable effects which are not prohibited
-    FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) - DeclaredVerifiableEffects);
+    FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) -
+                                          DeclaredVerifiableEffects);
   }
 
-  void checkAddDiagnostic(bool inferring, const Diagnostic& NewDiag)
-  {
-    if (!inferring) {
+  // Hide the way that diagnostics for explicitly required effects vs. inferred
+  // ones are handled differently.
+  void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+    if (!Inferring) {
       if (DiagnosticsForExplicitFX == nullptr) {
         DiagnosticsForExplicitFX = std::make_unique<SmallVector<Diagnostic>>();
       }
       DiagnosticsForExplicitFX->push_back(NewDiag);
     } else {
-      auto& diag = InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
-      if (diag.ID == DiagnosticID::None) {
-        diag = NewDiag;
+      auto &Diag =
+          InferrableEffectToFirstDiagnostic.getOrInsertDefault(NewDiag.Effect);
+      if (Diag.ID == DiagnosticID::None) {
+        Diag = NewDiag;
       }
     }
   }
 
-  void addUnverifiedDirectCall(const Decl* D, SourceLocation CallLoc)
-  {
+  void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
     if (UnverifiedDirectCalls == nullptr) {
       UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
     }
-    UnverifiedDirectCalls->emplace_back(DirectCall{ D, CallLoc });
+    UnverifiedDirectCalls->emplace_back(DirectCall{D, CallLoc});
   }
 
   // Analysis is complete when there are no unverified direct calls.
-  bool isComplete() const
-  {
+  bool isComplete() const {
     return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
   }
 
-  const Diagnostic* diagnosticForInferrableEffect(const FunctionEffect* effect)
-  {
+  const Diagnostic *
+  diagnosticForInferrableEffect(const FunctionEffect *effect) {
     return InferrableEffectToFirstDiagnostic.lookup(effect);
   }
 
-  const SmallVector<DirectCall>& unverifiedCalls() const
-  {
+  const SmallVector<DirectCall> &unverifiedCalls() const {
     assert(!isComplete());
     return *UnverifiedDirectCalls;
   }
 
-  SmallVector<Diagnostic>* getDiagnosticsForExplicitFX() const
-  {
+  SmallVector<Diagnostic> *getDiagnosticsForExplicitFX() const {
     return DiagnosticsForExplicitFX.get();
   }
 
-  void dump(llvm::raw_ostream& OS) const
-  {
+  void dump(llvm::raw_ostream &OS) const {
     OS << "Pending: Declared ";
     DeclaredVerifiableEffects.dump(OS);
-    OS << ", " << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0) << " diags; ";
+    OS << ", "
+       << (DiagnosticsForExplicitFX ? DiagnosticsForExplicitFX->size() : 0)
+       << " diags; ";
     OS << " Infer ";
     FXToInfer.dump(OS);
     OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\n";
@@ -2693,10 +2684,10 @@ class PendingFunctionAnalysis {
 class CompleteFunctionAnalysis {
   // Current size: 2 pointers
 public:
-  // Has effects which are both the declared ones -- not to be inferred -- plus ones which
-  // have been successfully inferred. These are all considered "verified" for the purposes
-  // of callers; any issue with verifying declared effects has already been reported and
-  // is not the problem of any caller.
+  // Has effects which are both the declared ones -- not to be inferred -- plus
+  // ones which have been successfully inferred. These are all considered
+  // "verified" for the purposes of callers; any issue with verifying declared
+  // effects has already been reported and is not the problem of any caller.
   FunctionEffectSet VerifiedEffects;
 
 private:
@@ -2704,28 +2695,28 @@ class CompleteFunctionAnalysis {
   EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
 
 public:
-  CompleteFunctionAnalysis(PendingFunctionAnalysis& pending, FunctionEffectSet funcFX, FunctionEffectSet AllInferrableEffectsToVerify)
-  {
+  CompleteFunctionAnalysis(PendingFunctionAnalysis &pending,
+                           FunctionEffectSet funcFX,
+                           FunctionEffectSet AllInferrableEffectsToVerify) {
     MutableFunctionEffectSet verified;
     verified |= funcFX;
-    for (const auto* effect : AllInferrableEffectsToVerify) {
+    for (const auto *effect : AllInferrableEffectsToVerify) {
       if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
         verified.insert(effect);
       }
     }
     VerifiedEffects = FunctionEffectSet::create(verified);
 
-    InferrableEffectToFirstDiagnostic = std::move(pending.InferrableEffectToFirstDiagnostic);
+    InferrableEffectToFirstDiagnostic =
+        std::move(pending.InferrableEffectToFirstDiagnostic);
   }
 
-  const Diagnostic* firstDiagnosticForEffect(const FunctionEffect* effect)
-  {
+  const Diagnostic *firstDiagnosticForEffect(const FunctionEffect *effect) {
     // TODO: is this correct?
     return InferrableEffectToFirstDiagnostic.lookup(effect);
   }
 
-  void dump(llvm::raw_ostream& OS) const
-  {
+  void dump(llvm::raw_ostream &OS) const {
     OS << "Complete: Verified ";
     VerifiedEffects.dump(OS);
     OS << "; Infer ";
@@ -2734,11 +2725,11 @@ class CompleteFunctionAnalysis {
 };
 
 /*
-	TODO: nolock and noalloc imply noexcept
+        TODO: nolock and noalloc imply noexcept
         if (auto* Method = dyn_cast<CXXMethodDecl>(CInfo.CDecl)) {
           if (Method->getType()->castAs<FunctionProtoType>()->canThrow()
               != clang::CT_Cannot) {
-            S.Diag(Callable->getBeginLoc(), 
+            S.Diag(Callable->getBeginLoc(),
               diag::warn_perf_annotation_implies_noexcept)
               << getPerfAnnotationSpelling(CInfo.PerfAnnot);
           }
@@ -2750,7 +2741,7 @@ class Analyzer {
   constexpr static int kDebugLogLevel = 3;
 
   // --
-  Sema& mSema;
+  Sema &Sem;
 
   // used from Sema:
   //  SmallVector<const Decl *> DeclsWithUnverifiedEffects
@@ -2758,78 +2749,76 @@ class Analyzer {
   // Subset of Sema.AllEffectsToVerify
   FunctionEffectSet AllInferrableEffectsToVerify;
 
-  using FuncAnalysisPtr = llvm::PointerUnion<PendingFunctionAnalysis*, CompleteFunctionAnalysis*>;
+  using FuncAnalysisPtr =
+      llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
 
-  // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger 
+  // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
   // than complete state, so use different objects to represent them.
   // The state pointers are owned by the container.
-  struct AnalysisMap : public llvm::DenseMap<const Decl*, FuncAnalysisPtr> {
-  
+  struct AnalysisMap : public llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+
     ~AnalysisMap();
 
     // use lookup()
 
-    CompleteFunctionAnalysis* completedAnalysisForDecl(const Decl* D) const
-    {
+    /// Shortcut for the case where we only care about completed analysis.
+    CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
       if (auto AP = lookup(D)) {
-        if (isa<CompleteFunctionAnalysis*>(AP)) {
-          return AP.get<CompleteFunctionAnalysis*>();
+        if (isa<CompleteFunctionAnalysis *>(AP)) {
+          return AP.get<CompleteFunctionAnalysis *>();
         }
       }
       return nullptr;
     }
 
-    void dump(Sema& S, llvm::raw_ostream& OS)
-    {
+    void dump(Sema &S, llvm::raw_ostream &OS) {
       OS << "AnalysisMap:\n";
-      for (const auto& item : *this) {
-        CallableInfo CI{ *item.first };
+      for (const auto &item : *this) {
+        CallableInfo CI(*item.first);
         const auto AP = item.second;
         OS << item.first << " " << CI.name(S) << " : ";
         if (AP.isNull()) {
           OS << "null\n";
-        } else if (isa<CompleteFunctionAnalysis*>(AP)) {
-          auto* CFA = AP.get<CompleteFunctionAnalysis*>();
+        } else if (isa<CompleteFunctionAnalysis *>(AP)) {
+          auto *CFA = AP.get<CompleteFunctionAnalysis *>();
           OS << CFA << " ";
           CFA->dump(OS);
-        } else if (isa<PendingFunctionAnalysis*>(AP)) {
-          auto* PFA = AP.get<PendingFunctionAnalysis*>();
+        } else if (isa<PendingFunctionAnalysis *>(AP)) {
+          auto *PFA = AP.get<PendingFunctionAnalysis *>();
           OS << PFA << " ";
           PFA->dump(OS);
-        } else llvm_unreachable("never");
+        } else
+          llvm_unreachable("never");
       }
     }
   };
   AnalysisMap DeclAnalysis;
 
 public:
-  Analyzer(Sema& S)
-    : mSema{ S }
-  {
-  }
+  Analyzer(Sema &S) : Sem(S) {}
 
-  void run(const TranslationUnitDecl& TU)
-  {
+  void run(const TranslationUnitDecl &TU) {
 #if FX_ANALYZER_VERIFY_DECL_LIST
     verifyRootDecls(TU);
 #endif
-    // Gather all of the effects to be verified to see what operations need to be checked,
-    // and to see which ones are inferrable.
+    // Gather all of the effects to be verified to see what operations need to
+    // be checked, and to see which ones are inferrable.
     {
       MutableFunctionEffectSet inferrableEffects;
-      for (const FunctionEffect* effect : mSema.AllEffectsToVerify) {
-        const auto Flags = effect->getFlags();
+      for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
+        const auto Flags = effect->flags();
         if (Flags & FunctionEffect::kInferrableOnCallees) {
           inferrableEffects.insert(effect);
         }
       }
-      AllInferrableEffectsToVerify = FunctionEffectSet::create(inferrableEffects);
+      AllInferrableEffectsToVerify =
+          FunctionEffectSet::create(inferrableEffects);
       llvm::outs() << "AllInferrableEffectsToVerify: ";
       AllInferrableEffectsToVerify.dump(llvm::outs());
       llvm::outs() << "\n";
     }
 
-    SmallVector<const Decl*>& verifyQueue = mSema.DeclsWithUnverifiedEffects;
+    SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
 
     // It's useful to use DeclsWithUnverifiedEffects as a stack for a
     // depth-first traversal rather than have a secondary container. But first,
@@ -2837,40 +2826,40 @@ class Analyzer {
     std::reverse(verifyQueue.begin(), verifyQueue.end());
 
     while (!verifyQueue.empty()) {
-      const Decl* D = verifyQueue.back();
+      const Decl *D = verifyQueue.back();
       if (auto AP = DeclAnalysis.lookup(D)) {
-          if (isa<CompleteFunctionAnalysis*>(AP)) {
-            // already done
-            verifyQueue.pop_back();
-            continue;
-          }
-          if (isa<PendingFunctionAnalysis*>(AP)) {
-            // All children have been traversed; finish analysis.
-            auto* pending = AP.get<PendingFunctionAnalysis*>();
-            finishPendingAnalysis(D, pending);
-            verifyQueue.pop_back();
-            continue;
-          }
-          llvm_unreachable("shouldn't happen");
+        if (isa<CompleteFunctionAnalysis *>(AP)) {
+          // already done
+          verifyQueue.pop_back();
+          continue;
+        }
+        if (isa<PendingFunctionAnalysis *>(AP)) {
+          // All children have been traversed; finish analysis.
+          auto *pending = AP.get<PendingFunctionAnalysis *>();
+          finishPendingAnalysis(D, pending);
+          verifyQueue.pop_back();
+          continue;
+        }
+        llvm_unreachable("shouldn't happen");
       }
 
-      auto* pending = verifyDecl(D);
-      if (pending == nullptr) {
+      auto *Pending = verifyDecl(D);
+      if (Pending == nullptr) {
         // completed now
         verifyQueue.pop_back();
         continue;
       }
 
-      for (const auto& call : pending->unverifiedCalls()) {
-        // This lookup could be optimized out if the results could have been saved
-        // from followCall when we traversed the caller's AST. It would however
-        // make the check for recursion more complex.
-        auto AP = DeclAnalysis.lookup(call.Callee);
+      for (const auto &Call : Pending->unverifiedCalls()) {
+        // This lookup could be optimized out if the results could have been
+        // saved from followCall when we traversed the caller's AST. It would
+        // however make the check for recursion more complex.
+        auto AP = DeclAnalysis.lookup(Call.Callee);
         if (AP.isNull()) {
-          verifyQueue.push_back(call.Callee);
+          verifyQueue.push_back(Call.Callee);
           continue;
         }
-        if (isa<PendingFunctionAnalysis*>(AP)) {
+        if (isa<PendingFunctionAnalysis *>(AP)) {
           // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
           __builtin_trap();
         }
@@ -2880,38 +2869,28 @@ class Analyzer {
   }
 
 private:
-  // Verify a single Decl. Return the pending structure if that was the result, else null.
-  // This method must not recurse.
-  PendingFunctionAnalysis* verifyDecl(const Decl *D)
-  {
+  // Verify a single Decl. Return the pending structure if that was the result,
+  // else null. This method must not recurse.
+  PendingFunctionAnalysis *verifyDecl(const Decl *D) {
+    // TODO: Is this in the right place?
     const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
     if (FD != nullptr) {
       // Currently, built-in functions are always considered safe.
       if (FD->getBuiltinID() != 0) {
         return nullptr;
       }
-      // If it doesn't have a body, then we have to rely on the declaration.
-
-#warning FIXME
-/*      if (!functionIsVerifiable(mSema, FD)) {
-        // const PerfAnnotation PA = FD->getPerfAnnotation();
-        // if (PA != PerfAnnotation::NoLock) {
-        //   Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
-        // }
-        return;
-      }*/
     }
-    CallableInfo CInfo{ *D };
+    CallableInfo CInfo(*D);
 
-    // Build a PendingFunctionAnalysis on the stack. If it turns out to be complete,
-    // we'll have avoided a heap allocation; if it's incomplete, it's a fairly
-    // trivial move to a heap-allocated object.
-    PendingFunctionAnalysis FAnalysis{ CInfo, AllInferrableEffectsToVerify };
+    // Build a PendingFunctionAnalysis on the stack. If it turns out to be
+    // complete, we'll have avoided a heap allocation; if it's incomplete, it's
+    // a fairly trivial move to a heap-allocated object.
+    PendingFunctionAnalysis FAnalysis(CInfo, AllInferrableEffectsToVerify);
 
-    llvm::outs() << "\nVerifying " << CInfo.name(mSema) << " ";
+    llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
     FAnalysis.dump(llvm::outs());
 
-    FunctionBodyASTVisitor Visitor{ *this, FAnalysis, CInfo };
+    FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
 
     Visitor.run();
     if (FAnalysis.isComplete()) {
@@ -2919,83 +2898,88 @@ class Analyzer {
       return nullptr;
     }
     // Copy the pending analysis to the heap and save it in the map.
-    auto* pendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
-    DeclAnalysis[D] = pendingPtr;
-    llvm::outs() << "inserted pending " << pendingPtr << "\n";
-    DeclAnalysis.dump(mSema, llvm::outs());
-    return pendingPtr;
-  }
-
-  // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis and inserted
-  // in the container.
-  void completeAnalysis(const CallableInfo& CInfo, PendingFunctionAnalysis& pending)
-  {
-    if (auto* diags = pending.getDiagnosticsForExplicitFX()) {
-      emitDiagnostics(*diags, CInfo, mSema);
-    }
-    auto* completePtr = new CompleteFunctionAnalysis(pending,
-      CInfo.Effects, AllInferrableEffectsToVerify);
-    DeclAnalysis[CInfo.CDecl] = completePtr;
-    llvm::outs() << "inserted complete " << completePtr << "\n";
-    DeclAnalysis.dump(mSema, llvm::outs());
-  }
-
-  // Called after all direct calls requiring inference have been found -- or not.
-  // Generally replicates FunctionBodyASTVisitor::followCall() but without the
-  // possibility of inference.
-  void finishPendingAnalysis(const Decl* D, PendingFunctionAnalysis* pending)
-  {
-      CallableInfo Caller{ *D };
-      for (const auto& call : pending->unverifiedCalls()) {
-        CallableInfo Callee{ *call.Callee };
-        followCall(Caller, *pending, Callee, call.CallLoc, /*assertNoFurtherInference=*/true);
-      }
-      completeAnalysis(Caller, *pending);
-      delete pending;
-      llvm::outs() << "destroyed pending " << pending << "\n";
+    auto *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
+    DeclAnalysis[D] = PendingPtr;
+    llvm::outs() << "inserted pending " << PendingPtr << "\n";
+    DeclAnalysis.dump(Sem, llvm::outs());
+    return PendingPtr;
+  }
+
+  // Consume PendingFunctionAnalysis, transformed to CompleteFunctionAnalysis
+  // and inserted in the container.
+  void completeAnalysis(const CallableInfo &CInfo,
+                        PendingFunctionAnalysis &Pending) {
+    if (auto *Diags = Pending.getDiagnosticsForExplicitFX()) {
+      emitDiagnostics(*Diags, CInfo, Sem);
+    }
+    auto *CompletePtr = new CompleteFunctionAnalysis(
+        Pending, CInfo.Effects, AllInferrableEffectsToVerify);
+    DeclAnalysis[CInfo.CDecl] = CompletePtr;
+    llvm::outs() << "inserted complete " << CompletePtr << "\n";
+    DeclAnalysis.dump(Sem, llvm::outs());
+  }
+
+  // Called after all direct calls requiring inference have been found -- or
+  // not. Generally replicates FunctionBodyASTVisitor::followCall() but without
+  // the possibility of inference.
+  void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
+    CallableInfo Caller(*D);
+    for (const auto &Call : Pending->unverifiedCalls()) {
+      CallableInfo Callee(*Call.Callee);
+      followCall(Caller, *Pending, Callee, Call.CallLoc,
+                 /*AssertNoFurtherInference=*/true);
+    }
+    completeAnalysis(Caller, *Pending);
+    delete Pending;
+    llvm::outs() << "destroyed pending " << Pending << "\n";
   }
 
   // Here we have a call to a Decl, either explicitly via a CallExpr or some
   // other AST construct. CallableInfo pertains to the callee.
-  void followCall(const CallableInfo& Caller, PendingFunctionAnalysis &PFA,
-    const CallableInfo& Callee, SourceLocation CallLoc,
-    bool assertNoFurtherInference)
-  {
+  void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
+                  const CallableInfo &Callee, SourceLocation CallLoc,
+                  bool AssertNoFurtherInference) {
     const bool DirectCall = Callee.isDirectCall();
     FunctionEffectSet CalleeEffects = Callee.Effects;
-    bool isInferencePossible = DirectCall;
+    bool IsInferencePossible = DirectCall;
 
     if (DirectCall) {
-      if (auto* CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+      if (auto *CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
         CalleeEffects = CFA->VerifiedEffects;
-        isInferencePossible = false; // we've already traversed it
+        IsInferencePossible = false; // we've already traversed it
       }
     }
-    if (assertNoFurtherInference) {
-      assert(!isInferencePossible);
+    if (AssertNoFurtherInference) {
+      assert(!IsInferencePossible);
     }
     if (!Callee.isVerifiable()) {
-      isInferencePossible = false;
+      IsInferencePossible = false;
     }
-    llvm::outs() << "followCall from " << Caller.name(mSema) << " to " << Callee.name(mSema) 
-      << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+    llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
+                 << Callee.name(Sem)
+                 << "; verifiable: " << Callee.isVerifiable() << "; callee ";
     CalleeEffects.dump(llvm::outs());
     llvm::outs() << "\n";
     puts("");
 
-    auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
-      const auto flags = effect->getFlags();
-      if (flags & FunctionEffect::kVerifyCalls) {
-        const bool diagnose = effect->diagnoseFunctionCall(DirectCall, Caller.CDecl, Caller.Effects,
-          Callee.CDecl, CalleeEffects);
+    auto check1Effect = [&](const FunctionEffect *Effect, bool Inferring) {
+      const auto Flags = Effect->flags();
+      if (Flags & FunctionEffect::kVerifyCalls) {
+        const bool diagnose = Effect->diagnoseFunctionCall(
+            DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
+            CalleeEffects);
         if (diagnose) {
-          // If inference is not allowed, or the target is indirect (virtual method/function ptr?),
-          // generate a diagnostic now.
-          if (!isInferencePossible || !(flags & FunctionEffect::kInferrableOnCallees)) {
+          // If inference is not allowed, or the target is indirect (virtual
+          // method/function ptr?), generate a diagnostic now.
+          if (!IsInferencePossible ||
+              !(Flags & FunctionEffect::kInferrableOnCallees)) {
             if (Callee.FuncType == SpecialFuncType::None) {
-              PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::CallsUnsafeDecl, CallLoc, Callee.CDecl });
+              PFA.checkAddDiagnostic(Inferring,
+                                     {Effect, DiagnosticID::CallsUnsafeDecl,
+                                      CallLoc, Callee.CDecl});
             } else {
-              PFA.checkAddDiagnostic(inferring, { effect, DiagnosticID::AllocatesMemory, CallLoc });
+              PFA.checkAddDiagnostic(
+                  Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
             }
           } else {
             // Inference is allowed and necessary; defer it.
@@ -3005,215 +2989,225 @@ class Analyzer {
       }
     };
 
-    for (auto* effect : PFA.DeclaredVerifiableEffects) {
-      check1Effect(effect, false);
+    for (auto *Effect : PFA.DeclaredVerifiableEffects) {
+      check1Effect(Effect, false);
     }
 
-    for (auto* effect : PFA.FXToInfer) {
-      check1Effect(effect, true);
+    for (auto *Effect : PFA.FXToInfer) {
+      check1Effect(Effect, true);
     }
   }
 
   // Should only be called when determined to be complete.
-  void emitDiagnostics(SmallVector<Diagnostic>& Diags, const CallableInfo& CInfo, Sema& S)
-  {
+  void emitDiagnostics(SmallVector<Diagnostic> &Diags,
+                       const CallableInfo &CInfo, Sema &S) {
 #define UNTESTED __builtin_trap();
 #define TESTED
-    const SourceManager& SM = S.getSourceManager();
-    std::sort(Diags.begin(), Diags.end(), [&SM](const Diagnostic& lhs, const Diagnostic& rhs) {
-      return SM.isBeforeInTranslationUnit(lhs.Loc, rhs.Loc);
-    });
+    const SourceManager &SM = S.getSourceManager();
+    std::sort(Diags.begin(), Diags.end(),
+              [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+                return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+              });
 
     const auto TopFuncName = CInfo.name(S);
 
     // TODO: Can we get better template instantiation notes?
-    auto checkAddTemplateNote = [&](const Decl* D) {
+    auto checkAddTemplateNote = [&](const Decl *D) {
       if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
         while (FD != nullptr && FD->isTemplateInstantiation()) {
-          S.Diag(FD->getPointOfInstantiation(), diag::note_func_effect_from_template);
+          S.Diag(FD->getPointOfInstantiation(),
+                 diag::note_func_effect_from_template);
           FD = FD->getTemplateInstantiationPattern();
         }
       }
     };
 
     // Top-level diagnostics are warnings.
-    for (const auto& Diag : Diags) {
+    for (const auto &Diag : Diags) {
       StringRef effectName = Diag.Effect->name();
       switch (Diag.ID) {
       case DiagnosticID::None:
-      //case DiagnosticID::DeclExternWithoutConstraint:
-      case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen here
-      //case DiagnosticID::DeclVirtualWithoutConstraint:
-      //case DiagnosticID::DeclFuncPtrWithoutConstraint:
+      case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen
+                                                           // here
         llvm_unreachable("Unexpected diagnostic kind");
         break;
       case DiagnosticID::AllocatesMemory:
-        S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName << TopFuncName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
+            << effectName << TopFuncName;
         checkAddTemplateNote(CInfo.CDecl);
-              TESTED
+        TESTED
         break;
       case DiagnosticID::Throws:
       case DiagnosticID::Catches:
-        S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches) << effectName << TopFuncName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
+            << effectName << TopFuncName;
         checkAddTemplateNote(CInfo.CDecl);
-              TESTED
+        TESTED
         break;
       case DiagnosticID::HasStaticLocal:
-        S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName << TopFuncName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local)
+            << effectName << TopFuncName;
         checkAddTemplateNote(CInfo.CDecl);
-              TESTED
+        TESTED
         break;
       case DiagnosticID::AccessesThreadLocal:
-        S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local) << effectName << TopFuncName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
+            << effectName << TopFuncName;
         checkAddTemplateNote(CInfo.CDecl);
-              TESTED
+        TESTED
         break;
       case DiagnosticID::CallsObjC:
-        S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName << TopFuncName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc)
+            << effectName << TopFuncName;
         checkAddTemplateNote(CInfo.CDecl);
-              TESTED
+        TESTED
         break;
       case DiagnosticID::CallsDisallowedExpr:
-        S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr) << effectName << TopFuncName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
+            << effectName << TopFuncName;
         checkAddTemplateNote(CInfo.CDecl);
-              UNTESTED
+        UNTESTED
         break;
 
-      case DiagnosticID::CallsUnsafeDecl:
-        {
-          CallableInfo CalleeInfo{ *Diag.Callee };
-          auto CalleeName = CalleeInfo.name(S);
-
-          S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func) << effectName << TopFuncName << CalleeName;
-          checkAddTemplateNote(CInfo.CDecl);
-
-          // Emit notes explaining the transitive chain of inferences: Why isn't the callee safe?
-          for (const auto* Callee = Diag.Callee; Callee != nullptr; ) {
-            std::optional<CallableInfo> MaybeNextCallee;
-            auto* completed = DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
-            if (completed == nullptr) {
-              // No result - could be
-              // - non-inline
-              // - virtual
-              if (CalleeInfo.CType == CallType::Virtual) {
-                S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual) << effectName << CalleeName;
-                TESTED
-              } else if (CalleeInfo.CType == CallType::Unknown) {
-                S.Diag(Callee->getLocation(), diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
-                TESTED
-              } else {
-                S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern) << effectName << CalleeName;
-                TESTED
-              }
-              break;
-            }
-            const auto* pDiag2 = completed->firstDiagnosticForEffect(Diag.Effect);
-            if (pDiag2 == nullptr) {
-              break;
-            }
+      case DiagnosticID::CallsUnsafeDecl: {
+        CallableInfo CalleeInfo{*Diag.Callee};
+        auto CalleeName = CalleeInfo.name(S);
 
-            const auto& Diag2 = *pDiag2;
-            switch (Diag2.ID) {
-            case DiagnosticID::None:
-              llvm_unreachable("Unexpected diagnostic kind");
-              break;
-            // case DiagnosticID::DeclExternWithoutConstraint:
-            //   S.Diag(Diag2.Loc, diag::note_func_effect_call_extern) << effectName << CalleeName;
-            //   break;
-            case DiagnosticID::DeclWithoutConstraintOrInference:
-              S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable) << effectName << CalleeName;
-              TESTED
-              break;
-            // case DiagnosticID::DeclVirtualWithoutConstraint:
-            //   S.Diag(Diag2.Loc, diag::note_func_effect_call_virtual) << effectName << CalleeName;
-            //   break;
-            //case DiagnosticID::DeclFuncPtrWithoutConstraint:
-            case DiagnosticID::CallsDisallowedExpr:
-              S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr) << effectName << CalleeName;
-              UNTESTED
-              break;
-            case DiagnosticID::AllocatesMemory:
-              S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName << CalleeName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
+            << effectName << TopFuncName << CalleeName;
+        checkAddTemplateNote(CInfo.CDecl);
+
+        // Emit notes explaining the transitive chain of inferences: Why isn't
+        // the callee safe?
+        for (const auto *Callee = Diag.Callee; Callee != nullptr;) {
+          std::optional<CallableInfo> MaybeNextCallee;
+          auto *Completed =
+              DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+          if (Completed == nullptr) {
+            // No result - could be
+            // - non-inline
+            // - virtual
+            if (CalleeInfo.CType == CallType::Virtual) {
+              S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
+                  << effectName << CalleeName;
               TESTED
-              break;
-            case DiagnosticID::Throws:
-            case DiagnosticID::Catches:
-              S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches) << effectName << CalleeName;
+            } else if (CalleeInfo.CType == CallType::Unknown) {
+              S.Diag(Callee->getLocation(),
+                     diag::note_func_effect_call_func_ptr)
+                  << effectName << CalleeName;
               TESTED
-              break;
-            case DiagnosticID::HasStaticLocal:
-              S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local) << effectName << CalleeName;
-              UNTESTED
-              break;
-            case DiagnosticID::AccessesThreadLocal:
-              S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local) << effectName << CalleeName;
-              UNTESTED
-              break;
-            case DiagnosticID::CallsObjC:
-              S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName << CalleeName;
-              UNTESTED
-              break;
-            case DiagnosticID::CallsUnsafeDecl:
-              MaybeNextCallee.emplace(*Diag2.Callee);
-              S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func) << effectName << CalleeName << MaybeNextCallee->name(S);
+            } else {
+              S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
+                  << effectName << CalleeName;
               TESTED
-              break;
-            }
-            checkAddTemplateNote(Callee);
-            Callee = Diag2.Callee;
-            if (MaybeNextCallee) {
-              CalleeInfo = *MaybeNextCallee;
-              CalleeName = CalleeInfo.name(S);
             }
+            break;
+          }
+          const auto *pDiag2 = Completed->firstDiagnosticForEffect(Diag.Effect);
+          if (pDiag2 == nullptr) {
+            break;
+          }
+
+          const auto &Diag2 = *pDiag2;
+          switch (Diag2.ID) {
+          case DiagnosticID::None:
+            llvm_unreachable("Unexpected diagnostic kind");
+            break;
+          case DiagnosticID::DeclWithoutConstraintOrInference:
+            S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
+                << effectName << CalleeName;
+            TESTED
+            break;
+          case DiagnosticID::CallsDisallowedExpr:
+            S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+                << effectName << CalleeName;
+            UNTESTED
+            break;
+          case DiagnosticID::AllocatesMemory:
+            S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
+                << effectName << CalleeName;
+            TESTED
+            break;
+          case DiagnosticID::Throws:
+          case DiagnosticID::Catches:
+            S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
+                << effectName << CalleeName;
+            TESTED
+            break;
+          case DiagnosticID::HasStaticLocal:
+            S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+                << effectName << CalleeName;
+            UNTESTED
+            break;
+          case DiagnosticID::AccessesThreadLocal:
+            S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+                << effectName << CalleeName;
+            UNTESTED
+            break;
+          case DiagnosticID::CallsObjC:
+            S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc)
+                << effectName << CalleeName;
+            UNTESTED
+            break;
+          case DiagnosticID::CallsUnsafeDecl:
+            MaybeNextCallee.emplace(*Diag2.Callee);
+            S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
+                << effectName << CalleeName << MaybeNextCallee->name(S);
+            TESTED
+            break;
+          }
+          checkAddTemplateNote(Callee);
+          Callee = Diag2.Callee;
+          if (MaybeNextCallee) {
+            CalleeInfo = *MaybeNextCallee;
+            CalleeName = CalleeInfo.name(S);
           }
         }
-        break;
+      } break;
       }
     }
   }
 
   // ----------
-  // This AST visitor is used to traverse the body of a function during effect verification.
-  // This happens in 2 distinct situations:
-  // [1] The function has declared effects which need to be validated.
-  // [2] The function has not explicitly declared an effect in question, and is being
-  //    checked for implicit conformance.
-  // When we are verifying explicit conformance [1] we should generate all diagnostics.
-  // When we are inferring conformance [2] we will need to save enough diagnostics
-  // to provide a note to explain why inference failed; just the first violation per
-  // FunctionEffect.
+  // This AST visitor is used to traverse the body of a function during effect
+  // verification. This happens in 2 situations:
+  //  [1] The function has declared effects which need to be validated.
+  //  [2] The function has not explicitly declared an effect in question, and is
+  //      being checked for implicit conformance.
   //
-  // Populates a provided FunctionAnalysis object.
+  // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
+  // all diagnostic output.
   //
-  // TODO: Currently we create a new RecursiveASTVisitor for every function analysis.
-  // Is it so lightweight that this is OK? It would appear so.
-  struct FunctionBodyASTVisitor : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+  // TODO: Currently we create a new RecursiveASTVisitor for every function
+  // analysis. Is it so lightweight that this is OK? It would appear so.
+  struct FunctionBodyASTVisitor
+      : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
     constexpr static bool Stop = false;
     constexpr static bool Proceed = true;
-    
+
     Analyzer &Outer;
     PendingFunctionAnalysis &CurrentFunction;
-    CallableInfo& CurrentCaller;
+    CallableInfo &CurrentCaller;
 
-    FunctionBodyASTVisitor(Analyzer &outer, PendingFunctionAnalysis &CurrentFunction,
-      CallableInfo& CurrentCaller)
-      : Outer{ outer }, CurrentFunction{ CurrentFunction }, CurrentCaller{ CurrentCaller }
-    {
-    }
+    FunctionBodyASTVisitor(Analyzer &outer,
+                           PendingFunctionAnalysis &CurrentFunction,
+                           CallableInfo &CurrentCaller)
+        : Outer(outer), CurrentFunction(CurrentFunction),
+          CurrentCaller(CurrentCaller) {}
 
     // -- Entry point --
     void run()
     {
-      // The target function itself may have some implicit code paths beyond the body:
-      // member and base constructors and destructors.
+      // The target function itself may have some implicit code paths beyond the
+      // body: member and base constructors and destructors. Visit these first.
       if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
-        if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
-          for (const CXXCtorInitializer* Initer : Ctor->inits()) {
-            if (Expr* Init = Initer->getInit()) {
+        if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+          for (const CXXCtorInitializer *Initer : Ctor->inits()) {
+            if (Expr *Init = Initer->getInit()) {
               VisitStmt(Init);
             }
           }
-        } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
+        } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
           followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
         } else if (!FD->isTrivial() && FD->isDefaulted()) {
           // needed? maybe not
@@ -3222,100 +3216,105 @@ class Analyzer {
       // else could be BlockDecl
 
       // Do an AST traversal of the function/block body
-      TraverseDecl(const_cast<Decl*>(CurrentCaller.CDecl));
+      TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
     }
 
     // -- Methods implementing common logic --
 
-    // Only effects whose flags include the specified flag receive a potential diagnostic.
-    // TODO: Consider bypasses for these loops by precomputing the flags we care about?
-    void addDiagnostic(FunctionEffect::FlagBit Flag, DiagnosticID D, SourceLocation Loc,
-      const Decl* Callee = nullptr)
+    // Handle a language construct forbidden by some effects. Only effects whose
+    // flags include the specified flag receive a diagnostic. \p Flag describes
+    // the construct.
+    void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+                       SourceLocation Loc, const Decl *Callee = nullptr)
     {
-      // If there are ANY declared verifiable effects holding the flag, store just one diagnostic.
-      for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
-        if (effect->getFlags() & Flag) {
-          addDiagnosticInner(/*inferring=*/false, effect, D, Loc, Callee);
+      // If there are ANY declared verifiable effects holding the flag, store
+      // just one diagnostic.
+      for (auto *Effect : CurrentFunction.DeclaredVerifiableEffects) {
+        if (Effect->flags() & Flag) {
+          addDiagnosticInner(/*inferring=*/false, Effect, D, Loc, Callee);
           break;
         }
       }
-      // For each inferred-but-not-verifiable effect holding the flag, store a diagnostic,
-      // if we don't already have a diagnostic for that effect.
-      for (auto* effect : CurrentFunction.FXToInfer) {
-        if (effect->getFlags() & Flag) {
-          addDiagnosticInner(/*inferring=*/true, effect, D, Loc, Callee);
+      // For each inferred-but-not-verifiable effect holding the flag, store a
+      // diagnostic, if we don't already have a diagnostic for that effect.
+      for (auto *Effect : CurrentFunction.FXToInfer) {
+        if (Effect->flags() & Flag) {
+          addDiagnosticInner(/*inferring=*/true, Effect, D, Loc, Callee);
         }
       }
     }
 
-    void addDiagnosticInner(bool inferring, const FunctionEffect* effect, DiagnosticID D,
-      SourceLocation Loc, const Decl* Callee = nullptr)
-    {
-      Diagnostic NewDiag{ effect, D, Loc, Callee };
-      CurrentFunction.checkAddDiagnostic(inferring, NewDiag);
+    void addDiagnosticInner(bool Inferring, const FunctionEffect *Effect,
+                            DiagnosticID D, SourceLocation Loc,
+                            const Decl *Callee = nullptr) {
+      CurrentFunction.checkAddDiagnostic(Inferring, 
+        Diagnostic(Effect, D, Loc, Callee));
     }
 
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
     // other AST construct. CallableInfo pertains to the callee.
-    void followCall(const CallableInfo& CI, SourceLocation CallLoc)
-    {
-      Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc, /*assertNoFurtherInference=*/false);
+    void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+      Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+                       /*AssertNoFurtherInference=*/false);
     }
 
-    void checkIndirectCall(CallExpr* Call, Expr* CalleeExpr)
-    {
+    void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
       const auto CalleeType = CalleeExpr->getType();
-      auto *FPT = CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
-
-      auto check1Effect = [&](const FunctionEffect* effect, bool inferring) {
-        if (effect->getFlags() & FunctionEffect::kVerifyCalls) {
-          if (FPT == nullptr || effect->diagnoseFunctionCall(/*direct=*/false, 
-            CurrentCaller.CDecl, CurrentCaller.Effects, FPT, FPT->getFunctionEffects())) {
-            addDiagnosticInner(inferring, effect, DiagnosticID::CallsDisallowedExpr, Call->getBeginLoc());
+      auto *FPT =
+          CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+
+      auto check1Effect = [&](const FunctionEffect *effect, bool inferring) {
+        if (effect->flags() & FunctionEffect::kVerifyCalls) {
+          if (FPT == nullptr ||
+              effect->diagnoseFunctionCall(
+                  /*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
+                  FPT, FPT->getFunctionEffects())) {
+            addDiagnosticInner(inferring, effect,
+                               DiagnosticID::CallsDisallowedExpr,
+                               Call->getBeginLoc());
           }
         }
       };
 
-      for (auto* effect : CurrentFunction.DeclaredVerifiableEffects) {
+      for (auto *effect : CurrentFunction.DeclaredVerifiableEffects) {
         check1Effect(effect, false);
       }
 
-      for (auto* effect : CurrentFunction.FXToInfer) {
+      for (auto *effect : CurrentFunction.FXToInfer) {
         check1Effect(effect, true);
       }
     }
 
-    // This destructor's body should be followed by the caller, but here we follow
-    // the field and base destructors.
-    void followDestructor(const CXXRecordDecl* Rec, const CXXDestructorDecl* Dtor)
-    {
-      for (const FieldDecl* Field : Rec->fields()) {
+    // This destructor's body should be followed by the caller, but here we
+    // follow the field and base destructors.
+    void followDestructor(const CXXRecordDecl *Rec,
+                          const CXXDestructorDecl *Dtor) {
+      for (const FieldDecl *Field : Rec->fields()) {
         followTypeDtor(Field->getType());
       }
 
-      if (const auto* Class = dyn_cast<CXXRecordDecl>(Rec)) {
-        for (const CXXBaseSpecifier& Base : Class->bases()) {
+      if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+        for (const CXXBaseSpecifier &Base : Class->bases()) {
           followTypeDtor(Base.getType());
         }
-        for (const CXXBaseSpecifier& Base : Class->vbases()) {
+        for (const CXXBaseSpecifier &Base : Class->vbases()) {
           followTypeDtor(Base.getType());
         }
       }
     }
 
-    void followTypeDtor(QualType QT)
-    {
-      const Type* Ty = QT.getTypePtr();
+    void followTypeDtor(QualType QT) {
+      const Type *Ty = QT.getTypePtr();
       while (Ty->isArrayType()) {
-        const ArrayType* Arr = Ty->getAsArrayTypeUnsafe();
+        const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
         QT = Arr->getElementType();
         Ty = QT.getTypePtr();
       }
 
       if (Ty->isRecordType()) {
-        if (const CXXRecordDecl* Class = Ty->getAsCXXRecordDecl()) {
-          if (auto* Dtor = Class->getDestructor()) {
-            CallableInfo CI{ *Dtor };
+        if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
+          if (auto *Dtor = Class->getDestructor()) {
+            CallableInfo CI{*Dtor};
             followCall(CI, Dtor->getLocation());
           }
         }
@@ -3334,37 +3333,34 @@ class Analyzer {
 
     bool shouldWalkTypesOfTypeLocs() const { return false; }
 
-    bool VisitCXXThrowExpr(CXXThrowExpr* Throw)
-    {
-      addDiagnostic(FunctionEffect::kExcludeThrow, DiagnosticID::Throws, Throw->getThrowLoc());
+    bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+      diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow, DiagnosticID::Throws,
+                                Throw->getThrowLoc());
       return Proceed;
     }
 
-    bool VisitCXXCatchStmt(CXXCatchStmt* Catch)
-    {
-      addDiagnostic(FunctionEffect::kExcludeCatch, DiagnosticID::Catches, Catch->getCatchLoc());
+    bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch, DiagnosticID::Catches,
+                                Catch->getCatchLoc());
       return Proceed;
     }
 
-    bool VisitObjCMessageExpr(ObjCMessageExpr* Msg)
-    {
-      addDiagnostic(FunctionEffect::kExcludeObjCMessageSend, DiagnosticID::CallsObjC, Msg->getBeginLoc());
+    bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+      diagnoseLanguageConstruct(FunctionEffect::kExcludeObjCMessageSend,
+                                DiagnosticID::CallsObjC, Msg->getBeginLoc());
       return Proceed;
     }
 
-    bool VisitCallExpr(CallExpr* Call)
-    {
+    bool VisitCallExpr(CallExpr *Call) {
       if constexpr (kDebugLogLevel > 2) {
-        llvm::errs() << "VisitCallExpr : " << Call->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+        llvm::errs() << "VisitCallExpr : "
+                     << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+                     << "\n";
       }
 
-      /*if ((AllFlagsOfFXToVerify & FunctionEffect::kVerifyCalls) == 0u) {
-        return Proceed;
-      }*/
-
-      Expr* CalleeExpr = Call->getCallee();
-      if (const Decl* Callee = CalleeExpr->getReferencedDeclOfCallee()) {
-        CallableInfo CI{ *Callee };
+      Expr *CalleeExpr = Call->getCallee();
+      if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+        CallableInfo CI(*Callee);
         followCall(CI, Call->getBeginLoc());
         return Proceed;
       }
@@ -3380,30 +3376,36 @@ class Analyzer {
       // into it and look for a DeclRefExpr. But for now it should suffice
       // to look at the type of the Expr. Well, unless we're in a template :-/
 
-      //llvm::errs() << "CalleeExpr ";
-      //CalleeExpr->dumpColor();
-      //llvm::errs() << Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ": warning: null callee\n";
+      // llvm::errs() << "CalleeExpr ";
+      // CalleeExpr->dumpColor();
+      // llvm::errs() <<
+      // Call->getBeginLoc().printToString(Outer.S.getSourceManager()) << ":
+      // warning: null callee\n";
 
       return Proceed;
     }
 
-    bool VisitVarDecl(VarDecl* Var)
-    {
+    bool VisitVarDecl(VarDecl *Var) {
       if constexpr (kDebugLogLevel > 2) {
-        llvm::errs() << "VisitVarDecl : " << Var->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+        llvm::errs() << "VisitVarDecl : "
+                     << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+                     << "\n";
       }
 
       if (Var->isStaticLocal()) {
-        addDiagnostic(FunctionEffect::kExcludeStaticLocalVars, DiagnosticID::HasStaticLocal, Var->getLocation());
+        diagnoseLanguageConstruct(FunctionEffect::kExcludeStaticLocalVars,
+                                  DiagnosticID::HasStaticLocal, Var->getLocation());
       }
 
-      const QualType::DestructionKind DK = Var->needsDestruction(Outer.mSema.getASTContext());
+      const QualType::DestructionKind DK =
+          Var->needsDestruction(Outer.Sem.getASTContext());
       if (DK == QualType::DK_cxx_destructor) {
         QualType QT = Var->getType();
-        if (const auto* ClsType = QT.getTypePtr()->getAs<RecordType>()) {
-          if (const auto* CxxRec = dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
-            if (const auto* Dtor = CxxRec->getDestructor()) {
-              CallableInfo CI{ *Dtor };
+        if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+          if (const auto *CxxRec =
+                  dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+            if (const auto *Dtor = CxxRec->getDestructor()) {
+              CallableInfo CI(*Dtor);
               followCall(CI, Var->getLocation());
             }
           }
@@ -3412,12 +3414,11 @@ class Analyzer {
       return Proceed;
     }
 
-    bool VisitCXXNewExpr(CXXNewExpr* New)
-    {
+    bool VisitCXXNewExpr(CXXNewExpr *New) {
       // BUG? It seems incorrect that RecursiveASTVisitor does not
       // visit the call to operator new.
-      if (auto* FD = New->getOperatorNew()) {
-        CallableInfo CI{ *FD, SpecialFuncType::OperatorNew };
+      if (auto *FD = New->getOperatorNew()) {
+        CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
         followCall(CI, New->getBeginLoc());
       }
 
@@ -3429,12 +3430,11 @@ class Analyzer {
       return Proceed;
     }
 
-    bool VisitCXXDeleteExpr(CXXDeleteExpr* Delete)
-    {
+    bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
       // BUG? It seems incorrect that RecursiveASTVisitor does not
       // visit the call to operator delete.
-      if (auto* FD = Delete->getOperatorDelete()) {
-        CallableInfo CI{ *FD, SpecialFuncType::OperatorDelete };
+      if (auto *FD = Delete->getOperatorDelete()) {
+        CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
         followCall(CI, Delete->getBeginLoc());
       }
 
@@ -3443,71 +3443,69 @@ class Analyzer {
       return Proceed;
     }
 
-    bool VisitCXXConstructExpr(CXXConstructExpr* Construct)
-    {
+    bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
       if constexpr (kDebugLogLevel > 2) {
-        llvm::errs() << "VisitCXXConstructExpr : " << 
-          Construct->getBeginLoc().printToString(Outer.mSema.SourceMgr) << "\n";
+        llvm::errs() << "VisitCXXConstructExpr : "
+                     << Construct->getBeginLoc().printToString(
+                            Outer.Sem.SourceMgr)
+                     << "\n";
       }
 
       // BUG? It seems incorrect that RecursiveASTVisitor does not
       // visit the call to the constructor.
-      const CXXConstructorDecl* Ctor = Construct->getConstructor();
-      CallableInfo CI{ *Ctor };
+      const CXXConstructorDecl *Ctor = Construct->getConstructor();
+      CallableInfo CI(*Ctor);
       followCall(CI, Construct->getLocation());
 
       return Proceed;
     }
 
-    bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr* DEI)
-    {
-      if (auto* Expr = DEI->getExpr()) {
+    bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
+      if (auto *Expr = DEI->getExpr()) {
         TraverseStmt(Expr);
       }
       return Proceed;
     }
 
-    bool TraverseLambdaExpr(LambdaExpr* Lambda)
-    {
+    bool TraverseLambdaExpr(LambdaExpr *Lambda) {
       // We override this so as the be able to skip traversal of the lambda's
       // body. We have to explicitly traverse the captures.
       for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I) {
         if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
-          Lambda->capture_init_begin()[I]) == Stop) return Stop;
+                                  Lambda->capture_init_begin()[I]) == Stop)
+          return Stop;
       }
 
       return Proceed;
     }
 
-    bool TraverseBlockExpr(BlockExpr* /*unused*/)
-    {
+    bool TraverseBlockExpr(BlockExpr * /*unused*/) {
       // TODO: are the capture expressions (ctor call?) safe?
       return Proceed;
     }
 
-    bool VisitDeclRefExpr(const DeclRefExpr *E)
-    {
-      const ValueDecl* Val = E->getDecl();
+    bool VisitDeclRefExpr(const DeclRefExpr *E) {
+      const ValueDecl *Val = E->getDecl();
       if (isa<VarDecl>(Val)) {
-        const VarDecl* Var = cast<VarDecl>(Val);
+        const VarDecl *Var = cast<VarDecl>(Val);
         const auto TLSK = Var->getTLSKind();
         if (TLSK != VarDecl::TLS_None) {
           // At least on macOS, thread-local variables are initialized on
           // first access.
-          addDiagnostic(FunctionEffect::kExcludeThreadLocalVars,
-            DiagnosticID::AccessesThreadLocal, E->getLocation());
+          diagnoseLanguageConstruct(FunctionEffect::kExcludeThreadLocalVars,
+                                    DiagnosticID::AccessesThreadLocal, E->getLocation());
         }
       }
       return Proceed;
     }
 
-
     // Unevaluated contexts: need to skip
     // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
 
     // bool TraverseTypeLoc(TypeLoc /*unused*/)
     // {
-    //   // This is a big blunt hammer so that we don't reach __invoke()'s call to declval().
+    //   // This is a big blunt hammer so that we don't reach __invoke()'s call
+    //   to declval().
     //   // Is it correct?
     //   return Proceed;
     // }
@@ -3515,7 +3513,9 @@ class Analyzer {
     bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
       return TraverseStmt(Node->getResultExpr());
     }
-    bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) { return Proceed; }
+    bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+      return Proceed;
+    }
 
     bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
 
@@ -3536,338 +3536,84 @@ class Analyzer {
   // -----
   // Called for every callable in the translation unit.
   struct CallableFinderCallback : MatchFinder::MatchCallback {
-    Sema& mSema;
+    Sema &Sem;
 
-    CallableFinderCallback(Sema& S) : mSema{ S } {}
+    CallableFinderCallback(Sema &S) : Sem(S) {}
 
     void run(const MatchFinder::MatchResult &Result) override {
-      if (auto* Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
+      if (auto *Callable = Result.Nodes.getNodeAs<Decl>(Tag_Callable)) {
         if (const auto FX = functionEffectsForDecl(Callable)) {
           // Reuse this filtering method in Sema
-          mSema.CheckAddCallableWithEffects(Callable, FX);
+          Sem.CheckAddCallableWithEffects(Callable, FX);
         }
       }
     }
 
     static FunctionEffectSet functionEffectsForDecl(const Decl *D) {
-      if (auto* FD = D->getAsFunction()) {
+      if (auto *FD = D->getAsFunction()) {
         return FD->getFunctionEffects();
       }
-      if (auto* BD = dyn_cast<BlockDecl>(D)) {
-        return BD->getFunctionEffects(); 
+      if (auto *BD = dyn_cast<BlockDecl>(D)) {
+        return BD->getFunctionEffects();
       }
       return {};
     }
 
-    static void get(Sema &S,const TranslationUnitDecl& TU)
-    {
+    static void get(Sema &S, const TranslationUnitDecl &TU) {
       MatchFinder CallableFinder;
-      CallableFinderCallback Callback{ S };
+      CallableFinderCallback Callback(S);
 
       using namespace clang::ast_matchers;
 
       CallableFinder.addMatcher(
-          decl(forEachDescendant(decl(anyOf(
-              functionDecl(hasBody(anything())).bind(Tag_Callable),
-              //objcMethodDecl(isDefinition()).bind(Tag_Callable), // no, always unsafe
-              blockDecl().bind(Tag_Callable))))),
+          decl(forEachDescendant(
+              decl(anyOf(functionDecl(hasBody(anything())).bind(Tag_Callable),
+                         // objcMethodDecl(isDefinition()).bind(Tag_Callable),
+                         // // no, always unsafe
+                         blockDecl().bind(Tag_Callable))))),
           &Callback);
-      // Matching LambdaExpr this way [a] doesn't seem to work (need to check for Stmt?)
-      // and [b] doesn't seem necessary, since the anonymous function is reached via the above.
-      // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))), &Callback);
+      // Matching LambdaExpr this way [a] doesn't seem to work (need to check
+      // for Stmt?) and [b] doesn't seem necessary, since the anonymous function
+      // is reached via the above.
+      // CallableFinder.addMatcher(stmt(forEachDescendant(stmt(lambdaExpr().bind(Tag_Callable)))),
+      // &Callback);
 
       CallableFinder.match(TU, TU.getASTContext());
     }
   };
 
-  void verifyRootDecls(const TranslationUnitDecl& TU) const
-  {
+  void verifyRootDecls(const TranslationUnitDecl &TU) const {
     // If this weren't debug code, it would be good to find a way to move/swap
     // instead of copying.
-    SmallVector<const Decl *> decls = mSema.DeclsWithUnverifiedEffects;
-    mSema.DeclsWithUnverifiedEffects.clear();
+    SmallVector<const Decl *> decls = Sem.DeclsWithUnverifiedEffects;
+    Sem.DeclsWithUnverifiedEffects.clear();
 
-    CallableFinderCallback::get(mSema, TU);
+    CallableFinderCallback::get(Sem, TU);
 
-    /*if (decls.size() != mSema.DeclsWithUnverifiedEffects.size())*/ {
-      llvm::errs() << "\nFXAnalysis: mSema gathered " << decls.size()
-        << " Decls; second AST pass found " << mSema.DeclsWithUnverifiedEffects.size() << "\n";
+    /*if (decls.size() != Sem.DeclsWithUnverifiedEffects.size())*/ {
+      llvm::errs() << "\nFXAnalysis: Sem gathered " << decls.size()
+                   << " Decls; second AST pass found "
+                   << Sem.DeclsWithUnverifiedEffects.size() << "\n";
     }
   }
 #endif
-
 };
 
-Analyzer::AnalysisMap::~AnalysisMap()
-{
-  for (const auto& item : *this) {
-    auto ptr = item.second;
-    if (isa<PendingFunctionAnalysis*>(ptr)) {
-      delete ptr.get<PendingFunctionAnalysis*>();
+Analyzer::AnalysisMap::~AnalysisMap() {
+  for (const auto &Item : *this) {
+    auto AP = Item.second;
+    if (isa<PendingFunctionAnalysis *>(AP)) {
+      delete AP.get<PendingFunctionAnalysis *>();
     } else {
-      delete ptr.get<CompleteFunctionAnalysis*>();
+      delete AP.get<CompleteFunctionAnalysis *>();
     }
   }
 }
 
 } // namespace FXAnalysis
 
-#if TEMP_DISABLED
-#if 0
-      // OLD OLD OLD from followCall
-      if (CI.PerfAnnot != PerfAnnotation::None) {
-        // Even if the callable turns out to be unsafe, if it is declared safe,
-        // don't create a chain reaction by propagating the error to its callers.
-        if constexpr (kDebugLogLevel > 1) {
-          llvm::errs() << Outer.mDebugIndent << "followCall: safe: " << int(CI.CType) << " " << CI.name() << "\n";
-        }
-
-        if (CI.isVerifiable()) {
-          // The top-level search for callables isn't finding all template instantiations?
-          // Since we have a function body here, possibly verify now.
-          Outer.determineCallableSafety(CI, /*EmitDiags=*/true);
-        } else {
-          Outer.markWithoutFollowing(CI, DiagnosticID::None);
-        }
-
-        return;
-      }
-
-      if constexpr (kDebugLogLevel > 1) {
-        llvm::errs() << Outer.mDebugIndent << "followCall type " << int(CI.CType) << " " << CI.name() << "\n";
-      }
-
-      if (!CI.isDirectCall()) {
-        // Function pointer or virtual method
-        Outer.markWithoutFollowing(CI, CI.CType == CallType::Virtual ?
-          DiagnosticID::DeclVirtualWithoutConstraint : DiagnosticID::DeclFuncPtrWithoutConstraint);
-        return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
-      }
-
-      // Here we have a direct call. Are we allowed to infer? If not, emit a diagnostic.
-      if (!CI.isInferrable()) {
-        Outer.markWithoutFollowing(CI, DiagnosticID::DeclWithoutConstraintOrInference);
-        return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
-      }
-
-      const DeclInfo& DI = Outer.determineCallableSafety(CI);
-      if (DI.hasDiagnostic()) {
-        if (CI.FuncType == SpecialFuncType::None) {
-          return addDiagnostic(DiagnosticID::CallsUnsafeDecl, CallLoc, CI.CDecl);
-        }
-        return addDiagnostic(DiagnosticID::AllocatesMemory, CallLoc);
-      }
-#endif
-
-
-struct PerfConstraintAnalyzer {
-  // Move all no_locks functions (both explicit and implicitly via call chain)
-  // into a separate code segment?
-  //constexpr static bool kCreateNoLocksSection = true;
-
-  // If non-null, the unsafe callable.
-  using FollowResult = const FunctionDecl*;
-
-  using MatchFinder = ast_matchers::MatchFinder;
-
-
-  // Information recorded about every visited function/block.
-  struct DeclInfo {
-    explicit DeclInfo(PerfAnnotation PA) {} //: DeclPerfAnnot{ PA } {}
-
-    bool hasDiagnostic() const { return Diag.ID != DiagnosticID::None; }
-    const Diagnostic& diagnostic() const { assert(hasDiagnostic()); return Diag; }
-    void setDiagnostic(const Diagnostic& e)
-    {
-        Diag = e;
-    }
-
-    // Scanning: prevent recursion
-    bool isScanning() const { return Scanning; }
-    void setScanning(bool b) { Scanning = b; }
-
-    Diagnostic Diag;
-
-    // This reflects the declared annotation, not the inferred one.
-    //PerfAnnotation DeclPerfAnnot{};
-
-    // Used to prevent infinite recursion.
-    bool Scanning = false;
-  };
-
-  // This contains an entry for every visited callable. Caution: determineCallableSafety
-  // assumes that an iterator remains valid across calls which may mutate the container.
-  using InferenceMap = std::map<const Decl*, DeclInfo>;
-
-  // --
-
-
-  // -----
-
-  Sema& S;
-  InferenceMap mInferenceMap;
-  std::string mDebugIndent;
-
-  // -----
-
-  PerfConstraintAnalyzer(Sema& S)
-    : S{ S }
-  {
-  }
-
-  // Variant of determineCallableSafety where we already know we can't follow
-  // because it is virtual or a function pointer etc. Or, we don't need to follow
-  // it because we know it's OK but we want to have it in the inference table.
-  void markWithoutFollowing(const CallableInfo& CInfo, DiagnosticID Diag)
-  {
-    auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
-    if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
-      return;
-    }
-    iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
-    if (Diag != DiagnosticID::None) {
-      DeclInfo& Result = iter->second;
-      Result.setDiagnostic({ Diag, CInfo.CDecl->getLocation(), nullptr });
-    }
-  }
-
-  void maybeDiagnose(DeclInfo& Result, const Diagnostic& Diag)
-  {
-    Result.setDiagnostic(Diag);
-  }
-
-  // Returns a populated map entry for the given CallableInfo's Decl.
-  // Caller should take special care to avoid recursion when Scanning is true.
-  // EmitDiags should only be true when called from the top-level traversal
-  // of all attributed functions in the TranslationUnit.
-  const DeclInfo& determineCallableSafety(const CallableInfo& CInfo, bool EmitDiags = false)
-  {
-    // If we have already visited this callable, return the previous result.
-    auto iter = mInferenceMap.lower_bound(CInfo.CDecl);
-    if (iter != mInferenceMap.end() && iter->first == CInfo.CDecl) {
-      return iter->second;
-    }
-
-    if constexpr (kDebugLogLevel) {
-      llvm::errs() << mDebugIndent << "determineCallableSafety -> " << CInfo.name() << "\n";
-    }
-
-    iter = mInferenceMap.insert(iter, { CInfo.CDecl, DeclInfo{ CInfo.PerfAnnot } });
-    DeclInfo& Result = iter->second;
-
-    std::optional<PerfConstraintASTVisitor> MaybeVisitor; // built on demand
-    auto getVisitor = [&]() -> PerfConstraintASTVisitor& {
-      if (!MaybeVisitor) {
-        MaybeVisitor.emplace(*this, /*StopOnFirstDiag=*/!EmitDiags);
-      }
-      return *MaybeVisitor;
-    };
-
-    const FunctionDecl *FD = dyn_cast<FunctionDecl>(CInfo.CDecl);
-    if (FD != nullptr) {
-      // Currently, built-in functions are always considered safe.
-      if (FD->getBuiltinID() != 0) {
-        return Result;
-      }
-      // If it doesn't have a body, then we have to rely on the declaration.
-      if (!functionIsVerifiable(S, FD)) {
-        const PerfAnnotation PA = FD->getPerfAnnotation();
-        if (PA != PerfAnnotation::NoLock) {
-          Result.setDiagnostic({ DiagnosticID::DeclExternWithoutConstraint, FD->getLocation(), nullptr });
-        }
-        return Result;
-      }
-
-      if (auto* Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
-        for (const CXXCtorInitializer* Initer : Ctor->inits()) {
-          if (Expr* Init = Initer->getInit()) {
-            Result.setScanning(true);
-            getVisitor().VisitStmt(Init);
-          }
-        }
-      } else if (auto* Dtor = dyn_cast<CXXDestructorDecl>(FD)) {
-        if (auto* UnsafeCallee = followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor)) {
-          //const auto PA = UnsafeCallee->getType()->isNoLock() ? PerfAnnotation::True : PerfAnnotation::False;
-          getVisitor().addDiagnostic(DiagnosticID::CallsUnsafeDecl, FD->getLocation(), UnsafeCallee);
-        }
-      } else if (!FD->isTrivial() && FD->isDefaulted()) {
-        // needed? maybe not
-      }
-    }
-
-    if constexpr (kDebugLogLevel) {
-      mDebugIndent += "    ";
-    }
-
-    Result.setScanning(true);
-
-    auto& Visitor = getVisitor();
-    Visitor.TraverseDecl(const_cast<Decl*>(CInfo.CDecl));
-
-    const auto& Diagnostics = Visitor.Diagnostics;
-
-    Result.setScanning(false);
-
-    if constexpr (kDebugLogLevel) {
-      mDebugIndent = mDebugIndent.substr(0, mDebugIndent.size() - 4);
-      llvm::errs() << mDebugIndent << "determineCallableSafety <- " << CInfo.name() << " : " << Diagnostics.size() << " violations\n";
-      //EmitDiags = true; // TEMPORARY
-    }
-
-    if (!Diagnostics.empty()) {
-      if (EmitDiags) {
-        emitDiagnostics(Diagnostics, CInfo);
-      }
-      Result.setDiagnostic(Diagnostics.front());
-    }
-
-    return Result;
-  }
-
-  void emitDiagnostics(const std::vector<Diagnostic>& Diags,
-    const CallableInfo& CInfo)
-  {
-  }
-
-  // Top-level entry point. Search the entire TU for functions to verify.
-  // Ways to optimize:
-  // - are the diagnostics enabled? if not, bail
-  // - did Sema see any tagged functions with bodies? if not, bail
-  void run(const TranslationUnitDecl& TU)
-  {
-
-    /*if constexpr (kCreateNoLocksSection) {
-      createNoLocksSection();
-    }*/
-  }
-
-  /*void createNoLocksSection()
-  {
-    const char* kSectionName = "__TEXT,__nolock,regular,pure_instructions";
-    if (auto err = S.isValidSectionSpecifier(kSectionName)) {
-      // real diagnostic?
-      llvm::errs() << "error: invalid section name " << kSectionName << " (" << err << ")\n";
-      return;
-    }
-
-    auto& MapEntry = S.getASTContext().SemaAnalysisGeneratedSections[kSectionName];
-    MapEntry.reserve(mInferenceMap.size());
-    for (auto& item : mInferenceMap) {
-      MapEntry.emplace_back(item.first);
-      if constexpr (kDebugLogLevel > 1) {
-        if (auto* F = dyn_cast<FunctionDecl>(item.first)) {
-          CallableInfo CI{ *F, S };
-          llvm::errs() << "nolock: " << CI.name() << "\n";
-        }
-      }
-    }
-  }*/
-};
-#endif // TEMP_DISABLED
-
 // =============================================================================
 
-
 //===----------------------------------------------------------------------===//
 // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
 //  warnings on a function, method, or block.
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 6022e4a838e8aa..39d9eeac43429e 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -586,21 +586,20 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
 }
 
 // Generate diagnostics when adding or removing effects in a type conversion.
-void Sema::diagnoseFunctionEffectConversion(QualType DstType,
-                                          QualType SrcType,
-                                          SourceLocation Loc)
-{
-  llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> " << DstType << "\n";
+void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
+                                            SourceLocation Loc) {
+  llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> "
+               << DstType << "\n";
   const auto SrcFX = FunctionEffectSet::get(*SrcType);
   const auto DstFX = FunctionEffectSet::get(*DstType);
   if (SrcFX != DstFX) {
-    const auto diffs = FunctionEffectSet::differences(SrcFX, DstFX);
-    for (const auto& item : diffs) {
-      const FunctionEffect* effect = item.first;
-      const bool adding = item.second;
-      if (effect->diagnoseConversion(adding,
-        SrcType, SrcFX, DstType, DstFX)) {
-        Diag(Loc, adding ? diag::warn_invalid_add_func_effects : diag::warn_invalid_remove_func_effects) << effect->name();
+    for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
+      const FunctionEffect *Effect = Item.first;
+      const bool Adding = Item.second;
+      if (Effect->diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
+        Diag(Loc, Adding ? diag::warn_invalid_add_func_effects
+                         : diag::warn_invalid_remove_func_effects)
+            << Effect->name();
       }
     }
   }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 77fdffef6382e0..f437db5661b00d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3925,12 +3925,12 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
   const auto OldFX = Old->getFunctionEffects();
   const auto NewFX = New->getFunctionEffects();  
   if (OldFX != NewFX) {
-    const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
-    for (const auto& item : diffs) {
-      const FunctionEffect* effect = item.first;
-      const bool adding = item.second;
-      if (effect->diagnoseRedeclaration(adding, *Old, OldFX, *New, NewFX)) {
-        Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << effect->name();
+    const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
+    for (const auto& Item : Diffs) {
+      const FunctionEffect* Effect = Item.first;
+      const bool Adding = Item.second;
+      if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+        Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << Effect->name();
         Diag(Old->getLocation(), diag::note_previous_declaration);
       }
     }
@@ -11162,14 +11162,14 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
 
   // Filter out declarations that the FunctionEffect analysis should skip
   // and not verify. (??? Is this the optimal order in which to test ???)
-  bool effectsNeedVerification = false;
+  bool FXNeedVerification = false;
   for (const auto *Effect : FX) {
-    if (Effect->getFlags() & FunctionEffect::kRequiresVerification) {
+    if (Effect->flags() & FunctionEffect::kRequiresVerification) {
       AllEffectsToVerify.insert(Effect);
-      effectsNeedVerification = true;
+      FXNeedVerification = true;
     }
   }
-  if (!effectsNeedVerification) {
+  if (!FXNeedVerification) {
     return;
   }
 
@@ -15789,12 +15789,6 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
   else
     FD = cast<FunctionDecl>(D);
 
-  auto *Canon = FD->getCanonicalDecl();
-
-  // llvm::outs() << "** ActOnStartOfFunctionDef " << FD->getName() << 
-  //   " " << FD << " " << Canon << " " << FD->getType() << "\n";
-  // getNameForDiagnostic
-
   // Do not push if it is a lambda because one is already pushed when building
   // the lambda in ActOnStartOfLambdaDefinition().
   if (!isLambdaCallOperator(FD))
@@ -15995,9 +15989,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
       getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
     Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
 
-  const auto FX = FD->getCanonicalDecl()->getFunctionEffects();
-  llvm::outs() << "^^ " << FX.size() << " effects\n";
-  if (FX) {
+  // TODO: does this really need to be getCanonicalDecl()?
+  if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
     CheckAddCallableWithEffects(FD, FX);
   }
 
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 12f7869441c9be..fd27ccd5c1dd1d 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -403,22 +403,22 @@ bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
   if (AL.isInvalid()) {
     return false;
   }
-  Expr* ArgExpr = AL.getArgAsExpr(ArgNum);
-  SourceLocation errorLoc{ AL.getLoc() };
+  Expr *ArgExpr = AL.getArgAsExpr(ArgNum);
+  SourceLocation ErrorLoc(AL.getLoc());
 
   if (AL.isArgIdent(ArgNum)) {
-    IdentifierLoc * IL = AL.getArgAsIdent(ArgNum);
-    errorLoc = IL->Loc;
+    IdentifierLoc *IL = AL.getArgAsIdent(ArgNum);
+    ErrorLoc = IL->Loc;
   } else if (ArgExpr != nullptr) {
-    auto maybeVal = ArgExpr->getIntegerConstantExpr(Context, &errorLoc);
-    if (maybeVal) {
-      Value = maybeVal->getBoolValue();
+    if (const std::optional<llvm::APSInt> MaybeVal =
+        ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
+      Value = MaybeVal->getBoolValue();
       return true;
     }
   }
 
   AL.setInvalid();
-  Diag(errorLoc, diag::err_attribute_argument_n_type)
+  Diag(ErrorLoc, diag::err_attribute_argument_n_type)
     << AL << ArgNum << AANT_ArgumentConstantExpr;
   return false;
 }
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index dac5d5f3e5c1e6..7533d3f9054a27 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18321,24 +18321,25 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
     return true;
   }
 
-  // Virtual overrides must have the same or stronger performance annotation.
+  // Virtual overrides: check for matching effects.
   const auto OldFX = Old->getFunctionEffects();
   const auto NewFX = New->getFunctionEffects();
 
   if (OldFX != NewFX) {
-    const auto diffs = FunctionEffectSet::differences(OldFX, NewFX);
+    const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
     bool AnyDiags = false;
 
-    for (const auto& item : diffs) {
-      const FunctionEffect* effect = item.first;
-      const bool adding = item.second;
-      if (effect->diagnoseMethodOverride(adding, *Old, OldFX, *New, NewFX)) {
-        Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << effect->name();
+    for (const auto& Item : Diffs) {
+      const FunctionEffect* Effect = Item.first;
+      const bool Adding = Item.second;
+      if (Effect->diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+        Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << Effect->name();
         Diag(Old->getLocation(), diag::note_overridden_virtual_function);
         AnyDiags = true;
       }
     }
-    if (AnyDiags) return true;
+    if (AnyDiags)
+      return true;
   }
 
   CallingConv NewCC = NewFT->getCallConv(), OldCC = OldFT->getCallConv();
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 2bc6b5b3880a8e..c4f87be129fdca 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -213,7 +213,7 @@ namespace {
 
     // Flags to diagnose illegal permutations of nolock(cond) and noalloc(cond).
     // Manual logic for finding previous attributes would be more complex,
-    // unless we transoformed nolock/noalloc(false) into distinct separate
+    // unless we transformed nolock/noalloc(false) into distinct separate
     // attributes from the ones which are parsed.
     unsigned char parsedNolock : 2;
     unsigned char parsedNoalloc : 2;
@@ -7942,16 +7942,12 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
 }
 
 static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
-  ParsedAttr &attr, QualType &type, FunctionTypeUnwrapper& unwrapped)
-{
+                                        ParsedAttr &PAttr, QualType &type,
+                                        FunctionTypeUnwrapper &unwrapped) {
   // Values of nolockState / noallocState
-  enum {
-    kNotSeen   = 0,
-    kSeenFalse = 1,
-    kSeenTrue  = 2
-  };
+  enum { kNotSeen = 0, kSeenFalse = 1, kSeenTrue = 2 };
 
-  const bool isNoLock = attr.getKind() == ParsedAttr::AT_NoLock;
+  const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
   Sema &S = state.getSema();
 
   // Delay if this is not a function type.
@@ -7967,9 +7963,9 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
 
   // Parse the conditional expression, if any
   bool Cond = true; // default
-  if (attr.getNumArgs() > 0) {
-    if (!S.checkBoolExprArgumentAttr(attr, 0, Cond)) {
-      attr.setInvalid();
+  if (PAttr.getNumArgs() > 0) {
+    if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
+      PAttr.setInvalid();
       return false;
     }
   }
@@ -7978,12 +7974,11 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
 
   auto incompatible = [&](StringRef attrTrue, StringRef attrFalse) {
     Sema &S = state.getSema();
-    S.Diag(attr.getLoc(), diag::err_attributes_are_not_compatible)
-        << attrTrue << attrFalse
-        << false;
+    S.Diag(PAttr.getLoc(), diag::err_attributes_are_not_compatible)
+        << attrTrue << attrFalse << false;
     // we don't necessarily have the location of the previous attribute,
     // so no note.
-    attr.setInvalid();
+    PAttr.setInvalid();
     return true;
   };
 
@@ -8013,9 +8008,10 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
     }
     state.setParsedNoalloc(newState);
   }
-  
+
   if (!Cond) {
-    // nolock(false) and noalloc(false) are represented as sugar, with AttributedType
+    // nolock(false) and noalloc(false) are represented as sugar, with
+    // AttributedType
     Attr *A = nullptr;
     if (isNoLock) {
       A = NoLockAttr::Create(S.Context, false);
@@ -8026,17 +8022,17 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
     return true;
   }
 
-  const FunctionEffect* Effect = nullptr;
+  const FunctionEffect *Effect = nullptr;
   if (isNoLock) {
     Effect = &NoLockNoAllocEffect::nolock_instance();
   } else {
     Effect = &NoLockNoAllocEffect::noalloc_instance();
   }
 
-  MutableFunctionEffectSet newEffectSet{ Effect };
+  MutableFunctionEffectSet newEffectSet{Effect};
   if (EPI.FunctionEffects) {
     // Preserve all previous effects - except noalloc, when we are adding nolock
-    for (const auto* effect : EPI.FunctionEffects) {
+    for (const auto *effect : EPI.FunctionEffects) {
       if (!(isNoLock && effect->type() == FunctionEffect::kNoAllocTrue))
         newEffectSet.insert(effect);
     }
@@ -8044,7 +8040,7 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
 
   EPI.FunctionEffects = FunctionEffectSet::create(newEffectSet);
   QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
-                                              FPT->getParamTypes(), EPI);
+                                               FPT->getParamTypes(), EPI);
   type = unwrapped.wrap(S, newtype->getAs<FunctionType>());
   return true;
 }
@@ -8362,8 +8358,8 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr,
     return true;
   }
 
-  if (attr.getKind() == ParsedAttr::AT_NoLock
-   || attr.getKind() == ParsedAttr::AT_NoAlloc) {
+  if (attr.getKind() == ParsedAttr::AT_NoLock ||
+      attr.getKind() == ParsedAttr::AT_NoAlloc) {
     return handleNoLockNoAllocTypeAttr(state, attr, type, unwrapped);
   }
 
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
new file mode 100644
index 00000000000000..a63750f5ca18ef
--- /dev/null
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -0,0 +1,19 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+void unannotated(void);
+void nolock(void) [[clang::nolock]];
+void noalloc(void) [[clang::noalloc]];
+void type_conversions(void)
+{
+	// It's fine to remove a performance constraint.
+	void (*fp_plain)(void);
+
+	fp_plain = unannotated;
+	fp_plain = nolock;
+	fp_plain = noalloc;
+}
diff --git a/clang/test/Sema/attr-nolock2.cpp b/clang/test/Sema/attr-nolock2.cpp
index 9c494dcdd581db..d2a35453b08bb7 100644
--- a/clang/test/Sema/attr-nolock2.cpp
+++ b/clang/test/Sema/attr-nolock2.cpp
@@ -9,8 +9,6 @@
 #error "the 'nolock' attribute is not available"
 #endif
 
-#if 1 // TEMP_DISABLE
-
 // --- ATTRIBUTE SYNTAX: COMBINATIONS ---
 // Check invalid combinations of nolock/noalloc attributes
 void nl_true_false_1(void) [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
@@ -84,5 +82,3 @@ int f3(void);
 // redeclaration with a stronger constraint is OK.
 int f3(void) [[clang::noalloc]]; // expected-note {{previous declaration is here}}
 int f3(void) { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
-
-#endif // TEMP_DISABLE
diff --git a/clang/test/Sema/attr-nolock3.cpp b/clang/test/Sema/attr-nolock3.cpp
index cfe52da16447c1..65d610f796f87f 100644
--- a/clang/test/Sema/attr-nolock3.cpp
+++ b/clang/test/Sema/attr-nolock3.cpp
@@ -5,7 +5,6 @@
 #endif
 
 // --- CONSTRAINTS ---
-#if 0 // TEMP_DISABLE
 
 void nl1() [[clang::nolock]]
 {
@@ -129,10 +128,7 @@ void nl10(
 	fp2();
 }
 
-#endif // TEMP_DISABLE
-
-// --- PLAYGROUND ---
-
+// Interactions with nolock(false)
 void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{'nl11_no_inference' does not permit inference of 'nolock'}}
 {
 }
@@ -141,4 +137,3 @@ void nl11() [[clang::nolock]]
 {
 	nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
 }
-

>From 8aaeeb27ef50557ed56839db7956c1b6e69ba318 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 12 Mar 2024 14:36:23 -0700
Subject: [PATCH 03/18] squash me

---
 clang/lib/AST/Type.cpp              |  4 ++--
 clang/lib/Sema/Sema.cpp             |  3 ++-
 clang/lib/Sema/SemaOverload.cpp     |  5 +++--
 clang/test/Sema/attr-nolock-wip.cpp | 11 +++++++++--
 4 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 998360db3c5dd0..8cc4bc42210f99 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4985,12 +4985,12 @@ std::string NoLockNoAllocEffect::attribute() const {
   return std::string{"__attribute__((clang_"} + name().str() + "))";
 }
 
-bool NoLockNoAllocEffect::diagnoseConversion(bool adding, QualType OldType,
+bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
                                              FunctionEffectSet OldFX,
                                              QualType NewType,
                                              FunctionEffectSet NewFX) const {
   // noalloc can't be added (spoofed) during a conversion, unless we have nolock
-  if (adding) {
+  if (Adding) {
     if (!isNoLock()) {
       for (const auto *Effect : OldFX) {
         if (Effect->type() == kNoLockTrue)
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 39d9eeac43429e..d3b9b048230f9c 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -588,7 +588,7 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
 // Generate diagnostics when adding or removing effects in a type conversion.
 void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                             SourceLocation Loc) {
-  llvm::outs() << "diagnoseFunctionEffectConversion " << SrcType << " -> "
+  llvm::outs() << "diagnoseFunctionEffectConversion: " << SrcType << " -> "
                << DstType << "\n";
   const auto SrcFX = FunctionEffectSet::get(*SrcType);
   const auto DstFX = FunctionEffectSet::get(*DstType);
@@ -682,6 +682,7 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
   diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
   diagnoseZeroToNullptrConversion(Kind, E);
   if (!isCast(CCK) && !E->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+    llvm::outs() << "Sema::ImpCastExprToType\n";
     diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
   }
 
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 8f1563e7884429..80a2e6a6456d0a 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1787,7 +1787,7 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
 bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
                                 QualType &ResultTy) {
 
-  llvm::outs() << "IsFunctionConversion " << FromType << " " << ToType << "\n";
+  llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType << "\n";
 
   if (Context.hasSameUnqualifiedType(FromType, ToType))
     return false;
@@ -1827,6 +1827,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
     if (TyClass != Type::FunctionProto && TyClass != Type::FunctionNoProto)
       return false;
   }
+  llvm::outs() << "  didn't exit early\n";
 
   const auto *FromFn = cast<FunctionType>(CanFrom);
   FunctionType::ExtInfo FromEInfo = FromFn->getExtInfo();
@@ -1879,7 +1880,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
     auto FromFX = FromFPT->getFunctionEffects();
     auto ToFX = ToFPT->getFunctionEffects();
     if (FromFX != ToFX) {
-      llvm::outs() << "IsFunctionConversion effects change " << FromType << " -> " << ToType << "\n";
+      llvm::outs() << "  effects change: " << FromType << " -> " << ToType << "\n";
 
       //const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
       // TODO: diagnose conflicts
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index a63750f5ca18ef..c30d0f42e37aee 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -8,12 +8,19 @@
 void unannotated(void);
 void nolock(void) [[clang::nolock]];
 void noalloc(void) [[clang::noalloc]];
+
+
+void callthis(void (*fp)(void));
+
+
 void type_conversions(void)
 {
+// 	callthis(nolock);
+
 	// It's fine to remove a performance constraint.
 	void (*fp_plain)(void);
 
-	fp_plain = unannotated;
+// 	fp_plain = unannotated;
 	fp_plain = nolock;
-	fp_plain = noalloc;
+// 	fp_plain = noalloc;
 }

>From eb204dff79817667c65f09bbd38ece245f50b99e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 12 Mar 2024 14:41:16 -0700
Subject: [PATCH 04/18] apply the in-tree clang-format (which seems subtly
 different from my system's local one)

---
 clang/include/clang/AST/Type.h           | 11 +++++-----
 clang/lib/AST/ASTContext.cpp             | 23 ++++++++++++--------
 clang/lib/Sema/AnalysisBasedWarnings.cpp | 27 ++++++++++++------------
 clang/lib/Sema/Sema.cpp                  |  3 ++-
 clang/lib/Sema/SemaDecl.cpp              | 23 ++++++++++----------
 clang/lib/Sema/SemaDeclAttr.cpp          |  7 +++---
 clang/lib/Sema/SemaDeclCXX.cpp           |  7 +++---
 clang/lib/Sema/SemaExprCXX.cpp           |  3 ++-
 clang/lib/Sema/SemaOverload.cpp          | 10 +++++----
 9 files changed, 62 insertions(+), 52 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index de21561ce5dcad..779c72ab63648d 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4360,8 +4360,8 @@ class FunctionProtoType final
   //   hasFunctionEffects() is true.
   //
   // * Optionally a Qualifiers object to represent extra qualifiers that can't
-  //   be represented by FunctionTypeBitfields.FastTypeQuals. Present if and only
-  //   if hasExtQualifiers() is true.
+  //   be represented by FunctionTypeBitfields.FastTypeQuals. Present if and
+  //   only if hasExtQualifiers() is true.
   //
   // The optional FunctionTypeExtraBitfields has to be before the data
   // related to the exception specification since it contains the number
@@ -4435,8 +4435,7 @@ class FunctionProtoType final
 
     bool requiresFunctionProtoTypeExtraBitfields() const {
       return ExceptionSpec.Type == EST_Dynamic ||
-             requiresFunctionProtoTypeArmAttributes() ||
-             FunctionEffects;
+             requiresFunctionProtoTypeArmAttributes() || FunctionEffects;
     }
 
     bool requiresFunctionProtoTypeArmAttributes() const {
@@ -7984,7 +7983,7 @@ class FunctionEffect {
                                      const FunctionDecl &NewFunction,
                                      FunctionEffectSet NewFX) const;
 
-  /// Return true if adding or removing the effect in a C++ virtual method 
+  /// Return true if adding or removing the effect in a C++ virtual method
   /// override should generate a diagnostic.
   virtual bool diagnoseMethodOverride(bool Adding,
                                       const CXXMethodDecl &OldMethod,
@@ -7994,7 +7993,7 @@ class FunctionEffect {
 
   /// Return true if the effect is allowed to be inferred on the specified Decl
   /// (may be a FunctionDecl or BlockDecl). Only used if the effect has
-  /// kInferrableOnCallees flag set. Example: This allows nolock(false) to 
+  /// kInferrableOnCallees flag set. Example: This allows nolock(false) to
   /// prevent inference for the function.
   virtual bool canInferOnDecl(const Decl *Caller,
                               FunctionEffectSet CallerFX) const;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 43fccb117a897d..68f2ac3e1329f8 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -4502,13 +4502,12 @@ QualType ASTContext::getFunctionTypeInternal(
   size_t Size = FunctionProtoType::totalSizeToAlloc<
       QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
       FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
-      Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, 
+      Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo,
       FunctionEffectSet, Qualifiers>(
       NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
       EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
       ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
-      EPI.ExtParameterInfos ? NumArgs : 0,
-      EPI.FunctionEffects ? 1 : 0,
+      EPI.ExtParameterInfos ? NumArgs : 0, EPI.FunctionEffects ? 1 : 0,
       EPI.TypeQuals.hasNonFastQualifiers() ? 1 : 0);
 
   auto *FTP = (FunctionProtoType *)Allocate(Size, alignof(FunctionProtoType));
@@ -10440,8 +10439,10 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
   FunctionEffectSet FromFX, ToFX;
   std::optional<FunctionEffectSet> MergedFX;
 
-  if (lproto) ToFX = lproto->getFunctionEffects();
-  if (rproto) FromFX = rproto->getFunctionEffects();
+  if (lproto)
+    ToFX = lproto->getFunctionEffects();
+  if (rproto)
+    FromFX = rproto->getFunctionEffects();
   if (ToFX != FromFX) {
     // We want the intersection of the effects...
     MergedFX = FunctionEffectSet::create(FromFX & ToFX);
@@ -10499,8 +10500,10 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
     }
 
     if (!MergedFX) { // effects changed so we can't return either side unaltered
-      if (allLTypes) return lhs;
-      if (allRTypes) return rhs;
+      if (allLTypes)
+        return lhs;
+      if (allRTypes)
+        return rhs;
     }
 
     FunctionProtoType::ExtProtoInfo EPI = lproto->getExtProtoInfo();
@@ -10543,8 +10546,10 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
     }
 
     if (!MergedFX) { // effects changed so we can't return either side unaltered
-      if (allLTypes) return lhs;
-      if (allRTypes) return rhs;
+      if (allLTypes)
+        return lhs;
+      if (allRTypes)
+        return rhs;
     }
 
     FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 69417b10049e97..bdc3aa906541be 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3196,8 +3196,7 @@ class Analyzer {
           CurrentCaller(CurrentCaller) {}
 
     // -- Entry point --
-    void run()
-    {
+    void run() {
       // The target function itself may have some implicit code paths beyond the
       // body: member and base constructors and destructors. Visit these first.
       if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
@@ -3225,8 +3224,8 @@ class Analyzer {
     // flags include the specified flag receive a diagnostic. \p Flag describes
     // the construct.
     void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
-                       SourceLocation Loc, const Decl *Callee = nullptr)
-    {
+                                   SourceLocation Loc,
+                                   const Decl *Callee = nullptr) {
       // If there are ANY declared verifiable effects holding the flag, store
       // just one diagnostic.
       for (auto *Effect : CurrentFunction.DeclaredVerifiableEffects) {
@@ -3247,8 +3246,8 @@ class Analyzer {
     void addDiagnosticInner(bool Inferring, const FunctionEffect *Effect,
                             DiagnosticID D, SourceLocation Loc,
                             const Decl *Callee = nullptr) {
-      CurrentFunction.checkAddDiagnostic(Inferring, 
-        Diagnostic(Effect, D, Loc, Callee));
+      CurrentFunction.checkAddDiagnostic(Inferring,
+                                         Diagnostic(Effect, D, Loc, Callee));
     }
 
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -3334,14 +3333,14 @@ class Analyzer {
     bool shouldWalkTypesOfTypeLocs() const { return false; }
 
     bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
-      diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow, DiagnosticID::Throws,
-                                Throw->getThrowLoc());
+      diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow,
+                                DiagnosticID::Throws, Throw->getThrowLoc());
       return Proceed;
     }
 
     bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
-      diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch, DiagnosticID::Catches,
-                                Catch->getCatchLoc());
+      diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch,
+                                DiagnosticID::Catches, Catch->getCatchLoc());
       return Proceed;
     }
 
@@ -3394,7 +3393,8 @@ class Analyzer {
 
       if (Var->isStaticLocal()) {
         diagnoseLanguageConstruct(FunctionEffect::kExcludeStaticLocalVars,
-                                  DiagnosticID::HasStaticLocal, Var->getLocation());
+                                  DiagnosticID::HasStaticLocal,
+                                  Var->getLocation());
       }
 
       const QualType::DestructionKind DK =
@@ -3493,7 +3493,8 @@ class Analyzer {
           // At least on macOS, thread-local variables are initialized on
           // first access.
           diagnoseLanguageConstruct(FunctionEffect::kExcludeThreadLocalVars,
-                                    DiagnosticID::AccessesThreadLocal, E->getLocation());
+                                    DiagnosticID::AccessesThreadLocal,
+                                    E->getLocation());
         }
       }
       return Proceed;
@@ -3770,7 +3771,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
   }
 
   // TODO: skip this if the warning isn't enabled.
-  FXAnalysis::Analyzer{ S }.run(*TU);
+  FXAnalysis::Analyzer{S}.run(*TU);
 }
 
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index d3b9b048230f9c..b120ed22246961 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -681,7 +681,8 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
 
   diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
   diagnoseZeroToNullptrConversion(Kind, E);
-  if (!isCast(CCK) && !E->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/)) {
+  if (!isCast(CCK) && !E->isNullPointerConstant(
+                          Context, Expr::NPC_NeverValueDependent /* ???*/)) {
     llvm::outs() << "Sema::ImpCastExprToType\n";
     diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
   }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index f437db5661b00d..d407018adfde60 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3923,28 +3923,30 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
   }
 
   const auto OldFX = Old->getFunctionEffects();
-  const auto NewFX = New->getFunctionEffects();  
+  const auto NewFX = New->getFunctionEffects();
   if (OldFX != NewFX) {
     const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
-    for (const auto& Item : Diffs) {
-      const FunctionEffect* Effect = Item.first;
+    for (const auto &Item : Diffs) {
+      const FunctionEffect *Effect = Item.first;
       const bool Adding = Item.second;
       if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
-        Diag(New->getLocation(), diag::warn_mismatched_func_effect_redeclaration) << Effect->name();
+        Diag(New->getLocation(),
+             diag::warn_mismatched_func_effect_redeclaration)
+            << Effect->name();
         Diag(Old->getLocation(), diag::note_previous_declaration);
       }
     }
 
     const auto MergedFX = OldFX | NewFX;
 
-    // Having diagnosed any problems, prevent further errors by applying the merged set of effects
-    // to both declarations.
-    auto applyMergedFX = [&](FunctionDecl* FD) {
+    // Having diagnosed any problems, prevent further errors by applying the
+    // merged set of effects to both declarations.
+    auto applyMergedFX = [&](FunctionDecl *FD) {
       const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
       FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
       EPI.FunctionEffects = MergedFX;
       QualType ModQT = Context.getFunctionType(FD->getReturnType(),
-                                                  FPT->getParamTypes(), EPI);
+                                               FPT->getParamTypes(), EPI);
 
       FD->setType(ModQT);
     };
@@ -11136,8 +11138,7 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
 
 // Should only be called when getFunctionEffects() returns a non-empty set.
 // Decl should be a FunctionDecl or BlockDecl.
-void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
-{
+void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
   if (!D->hasBody()) {
     if (const auto *FD = D->getAsFunction()) {
       if (!FD->willHaveBody()) {
@@ -11148,7 +11149,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX)
 
   if (Diags.getIgnoreAllWarnings() ||
       (Diags.getSuppressSystemWarnings() &&
-        SourceMgr.isInSystemHeader(D->getLocation())))
+       SourceMgr.isInSystemHeader(D->getLocation())))
     return;
 
   if (hasUncompilableErrorOccurred())
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index fd27ccd5c1dd1d..7805922c66b57a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -398,8 +398,7 @@ bool Sema::checkStringLiteralArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
 /// Check if the argument \p ArgNum of \p Attr is a compile-time constant
 /// integer (boolean) expression. If not, emit an error and return false.
 bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
-                                    bool &Value)
-{
+                                     bool &Value) {
   if (AL.isInvalid()) {
     return false;
   }
@@ -411,7 +410,7 @@ bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
     ErrorLoc = IL->Loc;
   } else if (ArgExpr != nullptr) {
     if (const std::optional<llvm::APSInt> MaybeVal =
-        ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
+            ArgExpr->getIntegerConstantExpr(Context, &ErrorLoc)) {
       Value = MaybeVal->getBoolValue();
       return true;
     }
@@ -419,7 +418,7 @@ bool Sema::checkBoolExprArgumentAttr(const ParsedAttr &AL, unsigned ArgNum,
 
   AL.setInvalid();
   Diag(ErrorLoc, diag::err_attribute_argument_n_type)
-    << AL << ArgNum << AANT_ArgumentConstantExpr;
+      << AL << ArgNum << AANT_ArgumentConstantExpr;
   return false;
 }
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 7533d3f9054a27..2f9a4a14155fb4 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18329,11 +18329,12 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
     const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
     bool AnyDiags = false;
 
-    for (const auto& Item : Diffs) {
-      const FunctionEffect* Effect = Item.first;
+    for (const auto &Item : Diffs) {
+      const FunctionEffect *Effect = Item.first;
       const bool Adding = Item.second;
       if (Effect->diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
-        Diag(New->getLocation(), diag::warn_mismatched_func_effect_override) << Effect->name();
+        Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
+            << Effect->name();
         Diag(Old->getLocation(), diag::note_overridden_virtual_function);
         AnyDiags = true;
       }
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 50007d4e1de119..e698139e55a239 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -4951,7 +4951,8 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
 
     // TODO: This generates a redundant diagnostic for:
     // void (^nl_block0)() NOLOCK = ^(){};
-    // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent /* ???*/))
+    // if (!From->isNullPointerConstant(Context, Expr::NPC_NeverValueDependent
+    // /* ???*/))
     //   diagnoseFunctionEffectConversion(ToType, InitialFromType,
     //                                         From->getBeginLoc());
   }
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 80a2e6a6456d0a..e34aa7e970d5dc 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1787,7 +1787,8 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
 bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
                                 QualType &ResultTy) {
 
-  llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType << "\n";
+  llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType
+               << "\n";
 
   if (Context.hasSameUnqualifiedType(FromType, ToType))
     return false;
@@ -1880,10 +1881,11 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
     auto FromFX = FromFPT->getFunctionEffects();
     auto ToFX = ToFPT->getFunctionEffects();
     if (FromFX != ToFX) {
-      llvm::outs() << "  effects change: " << FromType << " -> " << ToType << "\n";
+      llvm::outs() << "  effects change: " << FromType << " -> " << ToType
+                   << "\n";
 
-      //const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
-      // TODO: diagnose conflicts
+      // const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
+      //  TODO: diagnose conflicts
 
       FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
       ExtInfo.FunctionEffects = ToFX;

>From 3b0277bedd5391a17012b69135ac1a26c0a236a6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 13 Mar 2024 08:08:52 -0700
Subject: [PATCH 05/18] Address most of the low-hanging fruit in the initial
 review feedback.

---
 clang/include/clang/AST/Type.h                | 44 +++++----
 clang/include/clang/Basic/Attr.td             | 10 +--
 clang/lib/AST/Type.cpp                        | 41 +++++----
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 46 +++++-----
 clang/lib/Sema/SemaDecl.cpp                   |  9 +-
 clang/lib/Sema/SemaType.cpp                   | 44 +++++----
 ...olock3.cpp => attr-nolock-constraints.cpp} |  2 +
 ...ttr-nolock.cpp => attr-nolock-parsing.cpp} | 47 +++++++---
 clang/test/Sema/attr-nolock-sema.cpp          | 89 +++++++++++++++++++
 clang/test/Sema/attr-nolock-wip.cpp           | 12 +--
 clang/test/Sema/attr-nolock2.cpp              | 84 -----------------
 11 files changed, 228 insertions(+), 200 deletions(-)
 rename clang/test/Sema/{attr-nolock3.cpp => attr-nolock-constraints.cpp} (96%)
 rename clang/test/Sema/{attr-nolock.cpp => attr-nolock-parsing.cpp} (67%)
 create mode 100644 clang/test/Sema/attr-nolock-sema.cpp
 delete mode 100644 clang/test/Sema/attr-nolock2.cpp

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 779c72ab63648d..63b78d75089f9e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4274,10 +4274,6 @@ class FunctionEffectSet {
 
   ArrayRef<const FunctionEffect *> items() const { return {begin(), end()}; }
 
-  // Since iterators are non-trivial and sets are very often empty,
-  // encourage short-circuiting loops for the empty set.
-  // void for_each(llvm::function_ref<void(const FunctionEffect*)> func) const;
-
   bool operator==(const FunctionEffectSet &other) const {
     return Impl == other.Impl;
   }
@@ -7915,37 +7911,39 @@ class CXXMethodDecl;
 /// Represents an abstract function effect.
 class FunctionEffect {
 public:
-  enum EffectType {
-    kGeneric,
-    kNoLockTrue,
-    kNoAllocTrue,
+  enum class Type : unsigned char {
+    NoLockTrue,
+    NoAllocTrue,
   };
 
   /// Flags describing behaviors of the effect.
+  // (Why not a struct with bitfields? There's one function that would like to
+  // test a caller-specified bit. There are some potential optimizations that
+  // would OR together the bits of multiple effects.)
   using Flags = unsigned;
   enum FlagBit : unsigned {
     // Some effects require verification, e.g. nolock(true); others might not?
-    // (no example yet)
-    kRequiresVerification = 0x1,
+    // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
+    FE_RequiresVerification = 0x1,
 
     // Does this effect want to verify all function calls originating in
-    // functions having this effect?
-    kVerifyCalls = 0x2,
+    // functions having this effect? TODO: maybe always true, vestigial.
+    FE_VerifyCalls = 0x2,
 
     // Can verification inspect callees' implementations? (e.g. nolock: yes,
     // tcb+types: no)
-    kInferrableOnCallees = 0x4,
+    FE_InferrableOnCallees = 0x4,
 
     // Language constructs which effects can diagnose as disallowed.
-    kExcludeThrow = 0x8,
-    kExcludeCatch = 0x10,
-    kExcludeObjCMessageSend = 0x20,
-    kExcludeStaticLocalVars = 0x40,
-    kExcludeThreadLocalVars = 0x80
+    FE_ExcludeThrow = 0x8,
+    FE_ExcludeCatch = 0x10,
+    FE_ExcludeObjCMessageSend = 0x20,
+    FE_ExcludeStaticLocalVars = 0x40,
+    FE_ExcludeThreadLocalVars = 0x80
   };
 
 private:
-  const EffectType Type_;
+  const Type Type_;
   const Flags Flags_;
   const char *Name;
 
@@ -7953,12 +7951,12 @@ class FunctionEffect {
   using CalleeDeclOrType =
       llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
 
-  FunctionEffect(EffectType T, Flags F, const char *Name)
+  FunctionEffect(Type T, Flags F, const char *Name)
       : Type_(T), Flags_(F), Name(Name) {}
   virtual ~FunctionEffect();
 
   /// The type of the effect.
-  EffectType type() const { return Type_; }
+  Type type() const { return Type_; }
 
   /// Flags describing behaviors of the effect.
   Flags flags() const { return Flags_; }
@@ -8011,13 +8009,13 @@ class FunctionEffect {
 /// FunctionEffect subclass for nolock and noalloc (whose behaviors are close
 /// to identical).
 class NoLockNoAllocEffect : public FunctionEffect {
-  bool isNoLock() const { return type() == kNoLockTrue; }
+  bool isNoLock() const { return type() == Type::NoLockTrue; }
 
 public:
   static const NoLockNoAllocEffect &nolock_instance();
   static const NoLockNoAllocEffect &noalloc_instance();
 
-  NoLockNoAllocEffect(EffectType Type, const char *Name);
+  NoLockNoAllocEffect(Type Type, const char *Name);
   ~NoLockNoAllocEffect() override;
 
   std::string attribute() const override;
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4033b9efb86f39..ccc8a58176c8d6 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1402,26 +1402,20 @@ def CXX11NoReturn : InheritableAttr {
   let Documentation = [CXX11NoReturnDocs];
 }
 
-def NoLock : DeclOrTypeAttr {
+def NoLock : TypeAttr {
   let Spellings = [CXX11<"clang", "nolock">,
                    C23<"clang", "nolock">,
                    GNU<"clang_nolock">];
 
-  // Subjects - not needed?
-  //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
   let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
-  let HasCustomParsing = 0;
   let Documentation = [NoLockNoAllocDocs];
 }
 
-def NoAlloc : DeclOrTypeAttr {
+def NoAlloc : TypeAttr {
   let Spellings = [CXX11<"clang", "noalloc">,
                    C23<"clang", "noalloc">,
                    GNU<"clang_noalloc">];
-  // Subjects - not needed?
-  //let Subjects = SubjectList<[FunctionLike, Block, TypedefName], ErrorDiag>;
   let Args = [DefaultBoolArgument<"Cond", /*default*/1>];
-  let HasCustomParsing = 0;
   let Documentation = [NoLockNoAllocDocs];
 }
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 8cc4bc42210f99..0afc61ff6de487 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -3662,7 +3662,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
   // Note that valid type pointers are never ambiguous with anything else.
   //
   // The encoding grammar begins:
-  //      type type* bool int bool
+  //      effects type type* bool int bool
   // If that final bool is true, then there is a section for the EH spec:
   //      bool type*
   // This is followed by an optional "consumed argument" section of the
@@ -3673,13 +3673,12 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
   // Finally we have a trailing return type flag (bool)
   // combined with AArch64 SME Attributes, to save space:
   //      int
-  // Then add the FunctionEffects
   //
   // There is no ambiguity between the consumed arguments and an empty EH
   // spec because of the leading 'bool' which unambiguously indicates
   // whether the following bool is the EH spec or part of the arguments.
 
-  ID.AddPointer(epi.FunctionEffects.getOpaqueValue()); // TODO: Where???
+  ID.AddPointer(epi.FunctionEffects.getOpaqueValue());
 
   ID.AddPointer(Result.getAsOpaquePtr());
   for (unsigned i = 0; i != NumParams; ++i)
@@ -4961,22 +4960,22 @@ bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
 }
 
 const NoLockNoAllocEffect &NoLockNoAllocEffect::nolock_instance() {
-  static NoLockNoAllocEffect global(kNoLockTrue, "nolock");
+  static NoLockNoAllocEffect global(Type::NoLockTrue, "nolock");
   return global;
 }
 
 const NoLockNoAllocEffect &NoLockNoAllocEffect::noalloc_instance() {
-  static NoLockNoAllocEffect global(kNoAllocTrue, "noalloc");
+  static NoLockNoAllocEffect global(Type::NoAllocTrue, "noalloc");
   return global;
 }
 
 // TODO: Separate flags for noalloc
-NoLockNoAllocEffect::NoLockNoAllocEffect(EffectType Ty, const char *Name)
+NoLockNoAllocEffect::NoLockNoAllocEffect(Type Ty, const char *Name)
     : FunctionEffect(Ty,
-                     kRequiresVerification | kVerifyCalls |
-                         kInferrableOnCallees | kExcludeThrow | kExcludeCatch |
-                         kExcludeObjCMessageSend | kExcludeStaticLocalVars |
-                         kExcludeThreadLocalVars,
+                     FE_RequiresVerification | FE_VerifyCalls |
+                         FE_InferrableOnCallees | FE_ExcludeThrow |
+                         FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
+                         FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars,
                      Name) {}
 
 NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
@@ -4993,7 +4992,7 @@ bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
   if (Adding) {
     if (!isNoLock()) {
       for (const auto *Effect : OldFX) {
-        if (Effect->type() == kNoLockTrue)
+        if (Effect->type() == Type::NoLockTrue)
           return false;
       }
     }
@@ -5044,10 +5043,11 @@ bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
 bool NoLockNoAllocEffect::diagnoseFunctionCall(
     bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
     CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
-  const EffectType CallerType = type();
+  const Type CallerType = type();
   for (const auto *Effect : CalleeFX) {
-    const EffectType ET = Effect->type();
-    if (ET == CallerType || (CallerType == kNoAllocTrue && ET == kNoLockTrue)) {
+    const Type ET = Effect->type();
+    if (ET == CallerType ||
+        (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
       return false;
     }
   }
@@ -5088,11 +5088,16 @@ FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
 
   // SmallSet only has contains(), so it provides no way to obtain the uniqued
   // value.
-  static std::set<UniquedAndSortedFX> uniquedFXSets;
+  // TODO: Put this in the ASTContext
+  // TODO: Try making this a DenseSet? Requires more methods on the members.
+  // static llvm::DenseSet<UniquedAndSortedFX> UniquedFXSets;
+  // Punt on this until we revisit FunctionFX.
+
+  static std::set<UniquedAndSortedFX> UniquedFXSets;
 
   // See if we already have this set.
-  const auto Iter = uniquedFXSets.find(NewSet);
-  if (Iter != uniquedFXSets.end()) {
+  const auto Iter = UniquedFXSets.find(NewSet);
+  if (Iter != UniquedFXSets.end()) {
     return FunctionEffectSet{&*Iter};
   }
 
@@ -5102,7 +5107,7 @@ FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
 
   // Make a new wrapper and insert it into the set.
   NewSet = UniquedAndSortedFX(Storage, Items.size());
-  auto [InsIter, _] = uniquedFXSets.insert(NewSet);
+  auto [InsIter, _] = UniquedFXSets.insert(NewSet);
   return FunctionEffectSet(&*InsIter);
 }
 
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index bdc3aa906541be..cf9b1244e81bfd 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2519,9 +2519,10 @@ struct CallableInfo {
 // Map effects to single diagnostics.
 class EffectToDiagnosticMap {
   // Since we currently only have a tiny number of effects (typically no more
-  // than 1), use a sorted SmallVector.
+  // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
+  // is often empty, use a unique_ptr to the SmallVector.
   using Element = std::pair<const FunctionEffect *, Diagnostic>;
-  using ImplVec = llvm::SmallVector<Element>;
+  using ImplVec = llvm::SmallVector<Element, 1>;
   std::unique_ptr<ImplVec> Impl;
 
 public:
@@ -2533,7 +2534,7 @@ class EffectToDiagnosticMap {
       return Item.second;
     }
     Element Elem(Key, {});
-    auto Iter = _find(Elem);
+    auto *Iter = _find(Elem);
     if (Iter != Impl->end() && Iter->first == Key) {
       return Iter->second;
     }
@@ -2546,7 +2547,7 @@ class EffectToDiagnosticMap {
       return nullptr;
     }
     Element elem(key, {});
-    auto iter = _find(elem);
+    auto *iter = _find(elem);
     if (iter != Impl->end() && iter->first == key) {
       return &iter->second;
     }
@@ -2599,7 +2600,7 @@ class PendingFunctionAnalysis {
                           FunctionEffectSet AllInferrableEffectsToVerify) {
     MutableFunctionEffectSet fx;
     for (const auto *effect : cinfo.Effects) {
-      if (effect->flags() & FunctionEffect::kRequiresVerification) {
+      if (effect->flags() & FunctionEffect::FE_RequiresVerification) {
         fx.insert(effect);
       }
     }
@@ -2807,7 +2808,7 @@ class Analyzer {
       MutableFunctionEffectSet inferrableEffects;
       for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
         const auto Flags = effect->flags();
-        if (Flags & FunctionEffect::kInferrableOnCallees) {
+        if (Flags & FunctionEffect::FE_InferrableOnCallees) {
           inferrableEffects.insert(effect);
         }
       }
@@ -2820,7 +2821,7 @@ class Analyzer {
 
     SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
 
-    // It's useful to use DeclsWithUnverifiedEffects as a stack for a
+    // It's helpful to use DeclsWithUnverifiedEffects as a stack for a
     // depth-first traversal rather than have a secondary container. But first,
     // reverse it, so Decls are verified in the order they are declared.
     std::reverse(verifyQueue.begin(), verifyQueue.end());
@@ -2840,7 +2841,7 @@ class Analyzer {
           verifyQueue.pop_back();
           continue;
         }
-        llvm_unreachable("shouldn't happen");
+        llvm_unreachable("unexpected DeclAnalysis item");
       }
 
       auto *Pending = verifyDecl(D);
@@ -2861,9 +2862,10 @@ class Analyzer {
         }
         if (isa<PendingFunctionAnalysis *>(AP)) {
           // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
+          // TODO
           __builtin_trap();
         }
-        llvm_unreachable("shouldn't happen");
+        llvm_unreachable("unexpected DeclAnalysis item");
       }
     }
   }
@@ -2964,7 +2966,7 @@ class Analyzer {
 
     auto check1Effect = [&](const FunctionEffect *Effect, bool Inferring) {
       const auto Flags = Effect->flags();
-      if (Flags & FunctionEffect::kVerifyCalls) {
+      if (Flags & FunctionEffect::FE_VerifyCalls) {
         const bool diagnose = Effect->diagnoseFunctionCall(
             DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
             CalleeEffects);
@@ -2972,7 +2974,7 @@ class Analyzer {
           // If inference is not allowed, or the target is indirect (virtual
           // method/function ptr?), generate a diagnostic now.
           if (!IsInferencePossible ||
-              !(Flags & FunctionEffect::kInferrableOnCallees)) {
+              !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
             if (Callee.FuncType == SpecialFuncType::None) {
               PFA.checkAddDiagnostic(Inferring,
                                      {Effect, DiagnosticID::CallsUnsafeDecl,
@@ -3263,7 +3265,7 @@ class Analyzer {
           CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
 
       auto check1Effect = [&](const FunctionEffect *effect, bool inferring) {
-        if (effect->flags() & FunctionEffect::kVerifyCalls) {
+        if (effect->flags() & FunctionEffect::FE_VerifyCalls) {
           if (FPT == nullptr ||
               effect->diagnoseFunctionCall(
                   /*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
@@ -3333,19 +3335,19 @@ class Analyzer {
     bool shouldWalkTypesOfTypeLocs() const { return false; }
 
     bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
-      diagnoseLanguageConstruct(FunctionEffect::kExcludeThrow,
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
                                 DiagnosticID::Throws, Throw->getThrowLoc());
       return Proceed;
     }
 
     bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
-      diagnoseLanguageConstruct(FunctionEffect::kExcludeCatch,
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
                                 DiagnosticID::Catches, Catch->getCatchLoc());
       return Proceed;
     }
 
     bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
-      diagnoseLanguageConstruct(FunctionEffect::kExcludeObjCMessageSend,
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
                                 DiagnosticID::CallsObjC, Msg->getBeginLoc());
       return Proceed;
     }
@@ -3392,7 +3394,7 @@ class Analyzer {
       }
 
       if (Var->isStaticLocal()) {
-        diagnoseLanguageConstruct(FunctionEffect::kExcludeStaticLocalVars,
+        diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
                                   DiagnosticID::HasStaticLocal,
                                   Var->getLocation());
       }
@@ -3491,8 +3493,8 @@ class Analyzer {
         const auto TLSK = Var->getTLSKind();
         if (TLSK != VarDecl::TLS_None) {
           // At least on macOS, thread-local variables are initialized on
-          // first access.
-          diagnoseLanguageConstruct(FunctionEffect::kExcludeThreadLocalVars,
+          // first access, including a heap allocation.
+          diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
                                     DiagnosticID::AccessesThreadLocal,
                                     E->getLocation());
         }
@@ -3503,14 +3505,6 @@ class Analyzer {
     // Unevaluated contexts: need to skip
     // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
 
-    // bool TraverseTypeLoc(TypeLoc /*unused*/)
-    // {
-    //   // This is a big blunt hammer so that we don't reach __invoke()'s call
-    //   to declval().
-    //   // Is it correct?
-    //   return Proceed;
-    // }
-
     bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
       return TraverseStmt(Node->getResultExpr());
     }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d407018adfde60..c9488431d43436 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11155,17 +11155,21 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
   if (hasUncompilableErrorOccurred())
     return;
 
+#if 0
+// TODO: Does anything break if we don't do this?
+
   // For code in dependent contexts, we'll do this at instantiation time
   // (??? This was copied from something else in AnalysisBasedWarnings ???)
   if (cast<DeclContext>(D)->isDependentContext()) {
     return;
   }
+#endif
 
   // Filter out declarations that the FunctionEffect analysis should skip
-  // and not verify. (??? Is this the optimal order in which to test ???)
+  // and not verify.
   bool FXNeedVerification = false;
   for (const auto *Effect : FX) {
-    if (Effect->flags() & FunctionEffect::kRequiresVerification) {
+    if (Effect->flags() & FunctionEffect::FE_RequiresVerification) {
       AllEffectsToVerify.insert(Effect);
       FXNeedVerification = true;
     }
@@ -15990,7 +15994,6 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
       getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
     Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
 
-  // TODO: does this really need to be getCanonicalDecl()?
   if (const auto FX = FD->getCanonicalDecl()->getFunctionEffects()) {
     CheckAddCallableWithEffects(FD, FX);
   }
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index c4f87be129fdca..a7849f42d38c13 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -172,6 +172,8 @@ static void diagnoseBadTypeAttribute(Sema &S, const ParsedAttr &attr,
   case ParsedAttr::AT_TypeNullableResult:                                      \
   case ParsedAttr::AT_TypeNullUnspecified
 
+enum class BoolAttrState : uint8_t { Unseen, False, True };
+
 namespace {
   /// An object which stores processing state for the entire
   /// GetTypeForDeclarator process.
@@ -215,14 +217,15 @@ namespace {
     // Manual logic for finding previous attributes would be more complex,
     // unless we transformed nolock/noalloc(false) into distinct separate
     // attributes from the ones which are parsed.
-    unsigned char parsedNolock : 2;
-    unsigned char parsedNoalloc : 2;
+    BoolAttrState parsedNolock : 2;
+    BoolAttrState parsedNoalloc : 2;
 
   public:
     TypeProcessingState(Sema &sema, Declarator &declarator)
         : sema(sema), declarator(declarator),
           chunkIndex(declarator.getNumTypeObjects()), parsedNoDeref(false),
-          parsedNolock(0), parsedNoalloc(0) {}
+          parsedNolock(BoolAttrState::Unseen),
+          parsedNoalloc(BoolAttrState::Unseen) {}
 
     Sema &getSema() const {
       return sema;
@@ -349,10 +352,10 @@ namespace {
 
     bool didParseNoDeref() const { return parsedNoDeref; }
 
-    void setParsedNolock(unsigned char v) { parsedNolock = v; }
-    unsigned char getParsedNolock() const { return parsedNolock; }
-    void setParsedNoalloc(unsigned char v) { parsedNoalloc = v; }
-    unsigned char getParsedNoalloc() const { return parsedNoalloc; }
+    void setParsedNolock(BoolAttrState v) { parsedNolock = v; }
+    BoolAttrState getParsedNolock() const { return parsedNolock; }
+    void setParsedNoalloc(BoolAttrState v) { parsedNoalloc = v; }
+    BoolAttrState getParsedNoalloc() const { return parsedNoalloc; }
 
     ~TypeProcessingState() {
       if (savedAttrs.empty())
@@ -7944,16 +7947,13 @@ static Attr *getCCTypeAttr(ASTContext &Ctx, ParsedAttr &Attr) {
 static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
                                         ParsedAttr &PAttr, QualType &type,
                                         FunctionTypeUnwrapper &unwrapped) {
-  // Values of nolockState / noallocState
-  enum { kNotSeen = 0, kSeenFalse = 1, kSeenTrue = 2 };
-
-  const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
-  Sema &S = state.getSema();
-
   // Delay if this is not a function type.
   if (!unwrapped.isFunctionType())
     return false;
 
+  const bool isNoLock = PAttr.getKind() == ParsedAttr::AT_NoLock;
+  Sema &S = state.getSema();
+
   // Require FunctionProtoType
   auto *FPT = unwrapped.get()->getAs<FunctionProtoType>();
   if (FPT == nullptr) {
@@ -7962,6 +7962,8 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
   }
 
   // Parse the conditional expression, if any
+  // TODO: There's a better way to do this. See PR feedback.
+  // TODO: Handle a type-dependent expression.
   bool Cond = true; // default
   if (PAttr.getNumArgs() > 0) {
     if (!S.checkBoolExprArgumentAttr(PAttr, 0, Cond)) {
@@ -7983,14 +7985,16 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
   };
 
   // check nolock(true) against nolock(false), and same for noalloc
-  const unsigned newState = Cond ? kSeenTrue : kSeenFalse;
-  const unsigned oppositeNewState = Cond ? kSeenFalse : kSeenTrue;
+  const BoolAttrState newState =
+      Cond ? BoolAttrState::True : BoolAttrState::False;
+  const BoolAttrState oppositeNewState =
+      Cond ? BoolAttrState::False : BoolAttrState::True;
   if (isNoLock) {
     if (state.getParsedNolock() == oppositeNewState) {
       return incompatible("nolock(true)", "nolock(false)");
     }
     // also check nolock(true) against noalloc(false)
-    if (Cond && state.getParsedNoalloc() == kSeenFalse) {
+    if (Cond && state.getParsedNoalloc() == BoolAttrState::False) {
       return incompatible("nolock(true)", "noalloc(false)");
     }
     state.setParsedNolock(newState);
@@ -7999,7 +8003,7 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
       return incompatible("noalloc(true)", "noalloc(false)");
     }
     // also check nolock(true) against noalloc(false)
-    if (state.getParsedNolock() == kSeenTrue) {
+    if (state.getParsedNolock() == BoolAttrState::True) {
       if (!Cond) {
         return incompatible("nolock(true)", "noalloc(false)");
       }
@@ -8022,6 +8026,8 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
     return true;
   }
 
+  // nolock(true) and noalloc(true) are represented as FunctionEffects, in a
+  // FunctionEffectSet attached to a FunctionProtoType.
   const FunctionEffect *Effect = nullptr;
   if (isNoLock) {
     Effect = &NoLockNoAllocEffect::nolock_instance();
@@ -8031,9 +8037,9 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
 
   MutableFunctionEffectSet newEffectSet{Effect};
   if (EPI.FunctionEffects) {
-    // Preserve all previous effects - except noalloc, when we are adding nolock
+    // Preserve any previous effects - except noalloc, when we are adding nolock
     for (const auto *effect : EPI.FunctionEffects) {
-      if (!(isNoLock && effect->type() == FunctionEffect::kNoAllocTrue))
+      if (!(isNoLock && effect->type() == FunctionEffect::Type::NoAllocTrue))
         newEffectSet.insert(effect);
     }
   }
diff --git a/clang/test/Sema/attr-nolock3.cpp b/clang/test/Sema/attr-nolock-constraints.cpp
similarity index 96%
rename from clang/test/Sema/attr-nolock3.cpp
rename to clang/test/Sema/attr-nolock-constraints.cpp
index 65d610f796f87f..801f971b410865 100644
--- a/clang/test/Sema/attr-nolock3.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.cpp
@@ -1,4 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
+// the AnalysisBasedWarnings pass from running at all.
 
 #if !__has_attribute(clang_nolock)
 #error "the 'nolock' attribute is not available"
diff --git a/clang/test/Sema/attr-nolock.cpp b/clang/test/Sema/attr-nolock-parsing.cpp
similarity index 67%
rename from clang/test/Sema/attr-nolock.cpp
rename to clang/test/Sema/attr-nolock-parsing.cpp
index 3f9938a2e54601..6fac57645a1ae2 100644
--- a/clang/test/Sema/attr-nolock.cpp
+++ b/clang/test/Sema/attr-nolock-parsing.cpp
@@ -1,36 +1,34 @@
 // RUN: %clang_cc1 %s -ast-dump -fblocks | FileCheck %s
-// expected-no-diagnostics
 
 // Make sure that the attribute gets parsed and attached to the correct AST elements.
-// Update 1 Mar 2024
 
 #pragma clang diagnostic ignored "-Wunused-variable"
 
 // =========================================================================================
 // Square brackets, true
 
-#define NOLOCK [[clang::nolock]]
+namespace square_brackets {
 
 // On the type of the FunctionDecl
-void nl_function() NOLOCK;
+void nl_function() [[clang::nolock]];
 // CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
 
 // On the type of the VarDecl holding a function pointer
-void (*nl_func_a)() NOLOCK;
+void (*nl_func_a)() [[clang::nolock]];
 // CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
 
 // Check alternate attribute type and placement
-__attribute__((clang_nolock)) void (*nl_func_b)(void);
+__attribute__((clang_nolock)) void (*nl_func_b)();
 // CHECK: VarDecl {{.*}} nl_func_b 'void (*)() __attribute__((clang_nolock))'
 
 // On the type of the ParmVarDecl of a function parameter
-static void nlReceiver(void (*nl_func)() NOLOCK);
+static void nlReceiver(void (*nl_func)() [[clang::nolock]]);
 // CHECK: ParmVarDecl {{.*}} nl_func 'void (*)() __attribute__((clang_nolock))'
 
 // As an AttributedType within the nested types of a typedef
-typedef void (*nl_fp_type)() NOLOCK;
+typedef void (*nl_fp_type)() [[clang::nolock]];
 // CHECK: TypedefDecl {{.*}} nl_fp_type 'void (*)() __attribute__((clang_nolock))'
-using nl_fp_talias = void (*)() NOLOCK;
+using nl_fp_talias = void (*)() [[clang::nolock]];
 // CHECK: TypeAliasDecl {{.*}} nl_fp_talias 'void (*)() __attribute__((clang_nolock))'
 
 // From a typedef or typealias, on a VarDecl
@@ -41,7 +39,7 @@ nl_fp_talias nl_fp_var2;
 
 // On type of a FieldDecl
 struct Struct {
-	void (*nl_func_field)() NOLOCK;
+	void (*nl_func_field)() [[clang::nolock]];
 // CHECK: FieldDecl {{.*}} nl_func_field 'void (*)() __attribute__((clang_nolock))'
 };
 
@@ -55,10 +53,10 @@ void nl2() [[clang::noalloc]] [[clang::nolock]];
 // --- Blocks ---
 
 // On the type of the VarDecl holding a BlockDecl
-void (^nl_block1)() NOLOCK = ^() NOLOCK {};
+void (^nl_block1)() [[clang::nolock]] = ^() [[clang::nolock]] {};
 // CHECK: VarDecl {{.*}} nl_block1 'void (^)() __attribute__((clang_nolock))'
 
-int (^nl_block2)() NOLOCK = ^() NOLOCK { return 0; };
+int (^nl_block2)() [[clang::nolock]] = ^() [[clang::nolock]] { return 0; };
 // CHECK: VarDecl {{.*}} nl_block2 'int (^)() __attribute__((clang_nolock))'
 
 // The operand of the CallExpr is an ImplicitCastExpr of a DeclRefExpr -> nl_block which hold the attribute
@@ -66,7 +64,7 @@ static void blockCaller() { nl_block1(); }
 // CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
 
 // $$$ TODO: There are still some loose ends in all the methods of the lambda
-auto nl_lambda = []() NOLOCK {};
+auto nl_lambda = []() [[clang::nolock]] {};
 
 // =========================================================================================
 // Square brackets, false
@@ -74,5 +72,28 @@ auto nl_lambda = []() NOLOCK {};
 void nl_func_false() [[clang::nolock(false)]];
 // CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
 
+} // namespace square_brackets
+
+// =========================================================================================
+// GNU-style attribute, true
+
+namespace gnu_style {
+
+// On the type of the FunctionDecl
+void nl_function() __attribute__((clang_nolock));
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+
+// Alternate placement on the FunctionDecl
+__attribute__((clang_nolock)) void nl_function();
+// CHECK: FunctionDecl {{.*}} nl_function 'void () __attribute__((clang_nolock))'
+
+// On the type of the VarDecl holding a function pointer
+void (*nl_func_a)() __attribute__((clang_nolock));
+// CHECK: VarDecl {{.*}} nl_func_a 'void (*)() __attribute__((clang_nolock))'
+
+
+
+} // namespace gnu_style
+
 // TODO: Duplicate the above for noalloc
 // TODO: Duplicate the above for GNU-style attribute?
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
new file mode 100644
index 00000000000000..68b056bec1bdfd
--- /dev/null
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -0,0 +1,89 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
+
+// TODO: There's a problem with diagnosing type conversions in plain C.
+
+#if !__has_attribute(clang_nolock)
+#error "the 'nolock' attribute is not available"
+#endif
+
+// --- ATTRIBUTE SYNTAX: SUBJECTS ---
+
+int nl_var [[clang::nolock]]; // expected-warning {{'nolock' only applies to function types; type here is 'int'}}
+struct nl_struct {} [[clang::nolock]]; // expected-warning {{attribute 'nolock' is ignored, place it after "struct" to apply attribute to type declaration}}
+struct [[clang::nolock]] nl_struct2 {}; // expected-error {{'nolock' attribute cannot be applied to a declaration}}
+
+// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
+// Check invalid combinations of nolock/noalloc attributes
+
+void nl_true_false_1() [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+void nl_true_false_2() [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
+
+void na_true_false_1() [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+void na_true_false_2() [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
+
+void nl_true_na_true_1() [[clang::nolock]] [[clang::noalloc]];
+void nl_true_na_true_2() [[clang::noalloc]] [[clang::nolock]];
+
+void nl_true_na_false_1() [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+void nl_true_na_false_2() [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
+
+void nl_false_na_true_1() [[clang::nolock(false)]] [[clang::noalloc]];
+void nl_false_na_true_2() [[clang::noalloc]] [[clang::nolock(false)]];
+
+void nl_false_na_false_1() [[clang::nolock(false)]] [[clang::noalloc(false)]];
+void nl_false_na_false_2() [[clang::noalloc(false)]] [[clang::nolock(false)]];
+
+// --- TYPE CONVERSIONS ---
+
+void unannotated();
+void nolock() [[clang::nolock]];
+void noalloc() [[clang::noalloc]];
+void type_conversions()
+{
+	// It's fine to remove a performance constraint.
+	void (*fp_plain)();
+
+	fp_plain = unannotated;
+	fp_plain = nolock;
+	fp_plain = noalloc;
+
+	// Adding/spoofing nolock is unsafe.
+	void (*fp_nolock)() [[clang::nolock]];
+	fp_nolock = nolock;
+	fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+	fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+
+	// Adding/spoofing noalloc is unsafe.
+	void (*fp_noalloc)() [[clang::noalloc]];
+	fp_noalloc = noalloc;
+	fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
+	fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
+}
+
+// --- VIRTUAL METHODS ---
+#ifdef __cplusplus
+struct Base {
+	virtual void f1();
+	virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
+	virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
+};
+
+struct Derived : public Base {
+	void f1() [[clang::nolock]] override;
+	void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
+	void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
+};
+#endif // __cplusplus
+
+// --- REDECLARATIONS ---
+
+int f2();
+// redeclaration with a stronger constraint is OK.
+int f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
+int f2() { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+
+int f3();
+// redeclaration with a stronger constraint is OK.
+int f3() [[clang::noalloc]]; // expected-note {{previous declaration is here}}
+int f3() { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index c30d0f42e37aee..4afd9ed10582ad 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -5,20 +5,20 @@
 #error "the 'nolock' attribute is not available"
 #endif
 
-void unannotated(void);
-void nolock(void) [[clang::nolock]];
-void noalloc(void) [[clang::noalloc]];
+void unannotated();
+void nolock() [[clang::nolock]];
+void noalloc() [[clang::noalloc]];
 
 
-void callthis(void (*fp)(void));
+void callthis(void (*fp)());
 
 
-void type_conversions(void)
+void type_conversions()
 {
 // 	callthis(nolock);
 
 	// It's fine to remove a performance constraint.
-	void (*fp_plain)(void);
+	void (*fp_plain)();
 
 // 	fp_plain = unannotated;
 	fp_plain = nolock;
diff --git a/clang/test/Sema/attr-nolock2.cpp b/clang/test/Sema/attr-nolock2.cpp
deleted file mode 100644
index d2a35453b08bb7..00000000000000
--- a/clang/test/Sema/attr-nolock2.cpp
+++ /dev/null
@@ -1,84 +0,0 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
-// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c2x %s
-
-// TODO: There's a problem with diagnosing type conversions in plain C.
-
-#pragma clang diagnostic error "-Wstrict-prototypes"
-
-#if !__has_attribute(clang_nolock)
-#error "the 'nolock' attribute is not available"
-#endif
-
-// --- ATTRIBUTE SYNTAX: COMBINATIONS ---
-// Check invalid combinations of nolock/noalloc attributes
-void nl_true_false_1(void) [[clang::nolock(true)]] [[clang::nolock(false)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
-void nl_true_false_2(void) [[clang::nolock(false)]] [[clang::nolock(true)]]; // expected-error {{nolock(true) and nolock(false) attributes are not compatible}}
-
-void na_true_false_1(void) [[clang::noalloc(true)]] [[clang::noalloc(false)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
-void na_true_false_2(void) [[clang::noalloc(false)]] [[clang::noalloc(true)]]; // expected-error {{noalloc(true) and noalloc(false) attributes are not compatible}}
-
-void nl_true_na_true_1(void) [[clang::nolock]] [[clang::noalloc]];
-void nl_true_na_true_2(void) [[clang::noalloc]] [[clang::nolock]];
-
-void nl_true_na_false_1(void) [[clang::nolock]] [[clang::noalloc(false)]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
-void nl_true_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock]]; // expected-error {{nolock(true) and noalloc(false) attributes are not compatible}}
-
-void nl_false_na_true_1(void) [[clang::nolock(false)]] [[clang::noalloc]];
-void nl_false_na_true_2(void) [[clang::noalloc]] [[clang::nolock(false)]];
-
-void nl_false_na_false_1(void) [[clang::nolock(false)]] [[clang::noalloc(false)]];
-void nl_false_na_false_2(void) [[clang::noalloc(false)]] [[clang::nolock(false)]];
-
-// --- TYPE CONVERSIONS ---
-
-void unannotated(void);
-void nolock(void) [[clang::nolock]];
-void noalloc(void) [[clang::noalloc]];
-void type_conversions(void)
-{
-	// It's fine to remove a performance constraint.
-	void (*fp_plain)(void);
-
-	fp_plain = unannotated;
-	fp_plain = nolock;
-	fp_plain = noalloc;
-
-	// Adding/spoofing nolock is unsafe.
-	void (*fp_nolock)(void) [[clang::nolock]];
-	fp_nolock = nolock;
-	fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-	fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-
-	// Adding/spoofing noalloc is unsafe.
-	void (*fp_noalloc)(void) [[clang::noalloc]];
-	fp_noalloc = noalloc;
-	fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
-	fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
-}
-
-// --- VIRTUAL METHODS ---
-#ifdef __cplusplus
-struct Base {
-	virtual void f1();
-	virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
-	virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
-};
-
-struct Derived : public Base {
-	void f1() [[clang::nolock]] override;
-	void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
-	void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
-};
-#endif // __cplusplus
-
-// --- REDECLARATIONS ---
-
-int f2(void);
-// redeclaration with a stronger constraint is OK.
-int f2(void) [[clang::nolock]]; // expected-note {{previous declaration is here}}
-int f2(void) { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
-
-int f3(void);
-// redeclaration with a stronger constraint is OK.
-int f3(void) [[clang::noalloc]]; // expected-note {{previous declaration is here}}
-int f3(void) { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}

>From 4c6486ff1281471eea66811fa2b6aa941373c879 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Thu, 14 Mar 2024 07:14:32 -0700
Subject: [PATCH 06/18] Update clang/include/clang/AST/Type.h

Co-authored-by: Shafik Yaghmour <shafik.yaghmour at intel.com>
---
 clang/include/clang/AST/Type.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 63b78d75089f9e..79738729ee3f39 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4235,7 +4235,7 @@ class FunctionEffectSet {
 
 public:
   using Differences =
-      SmallVector<std::pair<const FunctionEffect *, bool /*added*/>>;
+      SmallVector<std::pair<const FunctionEffect *, /*added=*/bool>>;
 
   FunctionEffectSet() : Impl(nullptr) {}
 

>From e6b1e3557db00bd09ae3b4d2791ae06b3f870719 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 13 Mar 2024 15:47:06 -0700
Subject: [PATCH 07/18] - Sema::CheckAddCallableWithEffects: really do skip
 functions in dependent contexts. - Tweaking tests - things that came up in
 review.

---
 clang/lib/Sema/SemaDecl.cpp                   |  7 +--
 ...ock-parsing.cpp => attr-nolock-syntax.cpp} | 15 +++++-
 clang/test/Sema/attr-nolock-wip.cpp           | 52 +++++++++++++++++++
 3 files changed, 67 insertions(+), 7 deletions(-)
 rename clang/test/Sema/{attr-nolock-parsing.cpp => attr-nolock-syntax.cpp} (88%)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index c9488431d43436..d860804102dfc9 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11155,15 +11155,12 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
   if (hasUncompilableErrorOccurred())
     return;
 
-#if 0
-// TODO: Does anything break if we don't do this?
-
   // For code in dependent contexts, we'll do this at instantiation time
-  // (??? This was copied from something else in AnalysisBasedWarnings ???)
+  // Without this check, we would analyze the function based on placeholder
+  // template parameters, and potentially generate spurious diagnostics.
   if (cast<DeclContext>(D)->isDependentContext()) {
     return;
   }
-#endif
 
   // Filter out declarations that the FunctionEffect analysis should skip
   // and not verify.
diff --git a/clang/test/Sema/attr-nolock-parsing.cpp b/clang/test/Sema/attr-nolock-syntax.cpp
similarity index 88%
rename from clang/test/Sema/attr-nolock-parsing.cpp
rename to clang/test/Sema/attr-nolock-syntax.cpp
index 6fac57645a1ae2..2c0d71ba93ddf0 100644
--- a/clang/test/Sema/attr-nolock-parsing.cpp
+++ b/clang/test/Sema/attr-nolock-syntax.cpp
@@ -50,6 +50,9 @@ void nl1() [[clang::nolock]] [[clang::noalloc]];
 void nl2() [[clang::noalloc]] [[clang::nolock]];
 // CHECK: FunctionDecl {{.*}} nl2 'void () __attribute__((clang_nolock))'
 
+decltype(nl1) nl3;
+// CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nolock))'
+
 // --- Blocks ---
 
 // On the type of the VarDecl holding a BlockDecl
@@ -63,8 +66,11 @@ int (^nl_block2)() [[clang::nolock]] = ^() [[clang::nolock]] { return 0; };
 static void blockCaller() { nl_block1(); }
 // CHECK: DeclRefExpr {{.*}} 'nl_block1' 'void (^)() __attribute__((clang_nolock))'
 
-// $$$ TODO: There are still some loose ends in all the methods of the lambda
+// --- Lambdas ---
+
+// On the operator() of a lambda's CXXMethodDecl
 auto nl_lambda = []() [[clang::nolock]] {};
+// CHECK: CXXMethodDecl {{.*}} operator() 'void () const __attribute__((clang_nolock))' inline
 
 // =========================================================================================
 // Square brackets, false
@@ -72,11 +78,17 @@ auto nl_lambda = []() [[clang::nolock]] {};
 void nl_func_false() [[clang::nolock(false)]];
 // CHECK: FunctionDecl {{.*}} nl_func_false 'void () __attribute__((clang_nolock(false)))'
 
+// TODO: This exposes a bug where a type attribute is lost when inferring a lambda's
+// return type.
+auto nl_lambda_false = []() [[clang::nolock(false)]] {};
+
 } // namespace square_brackets
 
 // =========================================================================================
 // GNU-style attribute, true
 
+// TODO: Duplicate more of the above for GNU-style attribute
+
 namespace gnu_style {
 
 // On the type of the FunctionDecl
@@ -96,4 +108,3 @@ void (*nl_func_a)() __attribute__((clang_nolock));
 } // namespace gnu_style
 
 // TODO: Duplicate the above for noalloc
-// TODO: Duplicate the above for GNU-style attribute?
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 4afd9ed10582ad..f73fbbec70f701 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -5,6 +5,9 @@
 #error "the 'nolock' attribute is not available"
 #endif
 
+// ============================================================================
+
+#if 0 // C function type problems
 void unannotated();
 void nolock() [[clang::nolock]];
 void noalloc() [[clang::noalloc]];
@@ -24,3 +27,52 @@ void type_conversions()
 	fp_plain = nolock;
 // 	fp_plain = noalloc;
 }
+#endif
+
+// ============================================================================
+
+#if 0
+// https://github.com/llvm/llvm-project/pull/84983#issuecomment-1994978033
+
+// the bug where AttributedType sugar gets lost on lambdas (when the "inferred" return type gets
+// converted to a concrete one) happens here and the nolock(false) attribute is lost from h.
+
+template <class T>
+void f(T a) [[clang::nolock]] { a(); }
+
+void m()
+{
+	auto g = []() [[clang::nolock]] {
+	};
+	
+	auto h = []() [[clang::nolock(false)]] {
+	};
+
+	f(g);
+	f(h);
+}
+#endif
+
+template <class _Tp, _Tp __v>
+struct integral_constant
+{
+  static constexpr const _Tp      value = __v;
+  typedef _Tp               value_type;
+  typedef integral_constant type;
+  constexpr operator value_type() const noexcept {return value;}
+  constexpr value_type operator ()() const noexcept {return value;}
+};
+
+template <class _Tp, _Tp __v>
+const _Tp integral_constant<_Tp, __v>::value;
+
+typedef integral_constant<bool, true>  true_type;
+typedef integral_constant<bool, false> false_type;
+
+template <typename T>
+struct is_nolock;
+
+
+
+void g() [[clang::nolock]];
+void h() [[clang::nolock(false)]];

>From fc67743117c637496728099a4cb89803d2c30ff3 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 15 Mar 2024 09:01:52 -0700
Subject: [PATCH 08/18] ASTContext::mergeFunctionTypes doesn't seem to need to
 do anything (?)

---
 clang/lib/AST/ASTContext.cpp | 35 ++++++-----------------------------
 1 file changed, 6 insertions(+), 29 deletions(-)

diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 68f2ac3e1329f8..3e2142469babd5 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -10436,17 +10436,6 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
     allRTypes = false;
 
   FunctionType::ExtInfo einfo = lbaseInfo.withNoReturn(NoReturn);
-  FunctionEffectSet FromFX, ToFX;
-  std::optional<FunctionEffectSet> MergedFX;
-
-  if (lproto)
-    ToFX = lproto->getFunctionEffects();
-  if (rproto)
-    FromFX = rproto->getFunctionEffects();
-  if (ToFX != FromFX) {
-    // We want the intersection of the effects...
-    MergedFX = FunctionEffectSet::create(FromFX & ToFX);
-  }
 
   if (lproto && rproto) { // two C99 style function prototypes
     assert((AllowCXX ||
@@ -10463,6 +10452,8 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
     if (lproto->getMethodQuals() != rproto->getMethodQuals())
       return {};
 
+    // TODO: (nolock) Does anything need to be done with FunctionEffects?
+
     SmallVector<FunctionProtoType::ExtParameterInfo, 4> newParamInfos;
     bool canUseLeft, canUseRight;
     if (!mergeExtParameterInfo(lproto, rproto, canUseLeft, canUseRight,
@@ -10499,20 +10490,13 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
         allRTypes = false;
     }
 
-    if (!MergedFX) { // effects changed so we can't return either side unaltered
-      if (allLTypes)
-        return lhs;
-      if (allRTypes)
-        return rhs;
-    }
+    if (allLTypes) return lhs;
+    if (allRTypes) return rhs;
 
     FunctionProtoType::ExtProtoInfo EPI = lproto->getExtProtoInfo();
     EPI.ExtInfo = einfo;
     EPI.ExtParameterInfos =
         newParamInfos.empty() ? nullptr : newParamInfos.data();
-    if (MergedFX) {
-      EPI.FunctionEffects = *MergedFX;
-    }
     return getFunctionType(retType, types, EPI);
   }
 
@@ -10545,18 +10529,11 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
         return {};
     }
 
-    if (!MergedFX) { // effects changed so we can't return either side unaltered
-      if (allLTypes)
-        return lhs;
-      if (allRTypes)
-        return rhs;
-    }
+    if (allLTypes) return lhs;
+    if (allRTypes) return rhs;
 
     FunctionProtoType::ExtProtoInfo EPI = proto->getExtProtoInfo();
     EPI.ExtInfo = einfo;
-    if (MergedFX) {
-      EPI.FunctionEffects = *MergedFX;
-    }
     return getFunctionType(retType, proto->getParamTypes(), EPI);
   }
 

>From a63812551c65e588e5a15a86576da2a9ad179e7f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 15 Mar 2024 09:07:20 -0700
Subject: [PATCH 09/18] Fix function pointer type conversion in C by making
 IsFunctionConversion() not change the type for C. Add comment about #85415.

---
 clang/lib/Sema/Sema.cpp              |  3 --
 clang/lib/Sema/SemaExpr.cpp          |  2 ++
 clang/lib/Sema/SemaOverload.cpp      | 45 +++++++++++-----------------
 clang/test/Sema/attr-nolock-sema.cpp |  4 +--
 clang/test/Sema/attr-nolock-wip.cpp  | 31 ++++++++++++-------
 5 files changed, 42 insertions(+), 43 deletions(-)

diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index b120ed22246961..8e96522542427a 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -588,8 +588,6 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
 // Generate diagnostics when adding or removing effects in a type conversion.
 void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                             SourceLocation Loc) {
-  llvm::outs() << "diagnoseFunctionEffectConversion: " << SrcType << " -> "
-               << DstType << "\n";
   const auto SrcFX = FunctionEffectSet::get(*SrcType);
   const auto DstFX = FunctionEffectSet::get(*DstType);
   if (SrcFX != DstFX) {
@@ -683,7 +681,6 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
   diagnoseZeroToNullptrConversion(Kind, E);
   if (!isCast(CCK) && !E->isNullPointerConstant(
                           Context, Expr::NPC_NeverValueDependent /* ???*/)) {
-    llvm::outs() << "Sema::ImpCastExprToType\n";
     diagnoseFunctionEffectConversion(Ty, E->getType(), E->getBeginLoc());
   }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 643f8e7f444884..90932a532bebff 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -9986,6 +9986,8 @@ checkPointerTypesForAssignment(Sema &S, QualType LHSType, QualType RHSType,
       return Sema::IncompatibleFunctionPointer;
     return Sema::IncompatiblePointer;
   }
+  // #85415: This call to IsFunctionConversion appears to have inverted
+  // arguments: ltrans -> From, rtrans -> To
   if (!S.getLangOpts().CPlusPlus &&
       S.IsFunctionConversion(ltrans, rtrans, ltrans))
     return Sema::IncompatibleFunctionPointer;
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index e34aa7e970d5dc..0163969e9bf50d 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1787,9 +1787,6 @@ ExprResult Sema::PerformImplicitConversion(Expr *From, QualType ToType,
 bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
                                 QualType &ResultTy) {
 
-  llvm::outs() << "IsFunctionConversion: " << FromType << " -> " << ToType
-               << "\n";
-
   if (Context.hasSameUnqualifiedType(FromType, ToType))
     return false;
 
@@ -1828,7 +1825,6 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
     if (TyClass != Type::FunctionProto && TyClass != Type::FunctionNoProto)
       return false;
   }
-  llvm::outs() << "  didn't exit early\n";
 
   const auto *FromFn = cast<FunctionType>(CanFrom);
   FunctionType::ExtInfo FromEInfo = FromFn->getExtInfo();
@@ -1872,30 +1868,25 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
       Changed = true;
     }
 
-#if 1
-    // NOTE (TEMP): this works for C++ to allow dropping effects.
-    // For plain C, however, this creates an error when dropping effects!
-
-    // Transparently add/drop effects; here we are concerned with
-    // language rules/canonicalization. Adding/dropping effects is a warning.
-    auto FromFX = FromFPT->getFunctionEffects();
-    auto ToFX = ToFPT->getFunctionEffects();
-    if (FromFX != ToFX) {
-      llvm::outs() << "  effects change: " << FromType << " -> " << ToType
-                   << "\n";
-
-      // const auto MergedFX = FunctionEffectSet::getIntersection(FromFX, ToFX);
-      //  TODO: diagnose conflicts
-
-      FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
-      ExtInfo.FunctionEffects = ToFX;
-      QualType QT = Context.getFunctionType(FromFPT->getReturnType(),
-                                            FromFPT->getParamTypes(), ExtInfo);
-      FromFn = QT->getAs<FunctionType>();
-      llvm::outs() << "  produced " << QT << "\n";
-      Changed = true;
+    if (getLangOpts().CPlusPlus) {
+      // TODO:
+      // For C, when called from checkPointerTypesForAssignment,
+      // we need not to change the type, or else even an innocuous cast
+      // like dropping effects will fail.
+
+      // Transparently add/drop effects; here we are concerned with
+      // language rules/canonicalization. Adding/dropping effects is a warning.
+      const auto FromFX = FromFPT->getFunctionEffects();
+      const auto ToFX = ToFPT->getFunctionEffects();
+      if (FromFX != ToFX) {
+        FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo();
+        ExtInfo.FunctionEffects = ToFX;
+        QualType QT = Context.getFunctionType(
+            FromFPT->getReturnType(), FromFPT->getParamTypes(), ExtInfo);
+        FromFn = QT->getAs<FunctionType>();
+        Changed = true;
+      }
     }
-#endif
   }
 
   if (!Changed)
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 68b056bec1bdfd..99f6242e4747c9 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -1,7 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
-// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
-
-// TODO: There's a problem with diagnosing type conversions in plain C.
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
 
 #if !__has_attribute(clang_nolock)
 #error "the 'nolock' attribute is not available"
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index f73fbbec70f701..16ff464fb8aa08 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -7,25 +7,32 @@
 
 // ============================================================================
 
-#if 0 // C function type problems
+#if 1 // C function type problems
 void unannotated();
 void nolock() [[clang::nolock]];
 void noalloc() [[clang::noalloc]];
 
 
-void callthis(void (*fp)());
-
-
 void type_conversions()
 {
-// 	callthis(nolock);
-
 	// It's fine to remove a performance constraint.
 	void (*fp_plain)();
 
-// 	fp_plain = unannotated;
+	fp_plain = unannotated;
 	fp_plain = nolock;
-// 	fp_plain = noalloc;
+	fp_plain = noalloc;
+
+	// Adding/spoofing nolock is unsafe.
+	void (*fp_nolock)() [[clang::nolock]];
+	fp_nolock = nolock;
+	fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+	fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
+
+	// Adding/spoofing noalloc is unsafe.
+	void (*fp_noalloc)() [[clang::noalloc]];
+	fp_noalloc = noalloc;
+	fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
+	fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
 }
 #endif
 
@@ -53,6 +60,10 @@ void m()
 }
 #endif
 
+// ============================================================================
+
+#if 0
+// some messing around with type traits
 template <class _Tp, _Tp __v>
 struct integral_constant
 {
@@ -72,7 +83,7 @@ typedef integral_constant<bool, false> false_type;
 template <typename T>
 struct is_nolock;
 
-
-
 void g() [[clang::nolock]];
 void h() [[clang::nolock(false)]];
+#endif
+

>From c2ba529ffb574659ee13e6b95ee709fd53966916 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 16 Mar 2024 06:07:58 -0700
Subject: [PATCH 10/18] De-virtualize FunctionEffect; it's now a value type.
 Prepare for the uniquing table to be in ASTContext.

---
 clang/include/clang/AST/Type.h           | 372 ++++++++++-------------
 clang/lib/AST/Type.cpp                   | 306 +++++++++++--------
 clang/lib/AST/TypePrinter.cpp            |   4 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp |  87 +++---
 clang/lib/Sema/Sema.cpp                  |   6 +-
 clang/lib/Sema/SemaDecl.cpp              |  12 +-
 clang/lib/Sema/SemaDeclCXX.cpp           |   6 +-
 clang/lib/Sema/SemaType.cpp              |  19 +-
 8 files changed, 394 insertions(+), 418 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 79738729ee3f39..1d277d2ef06b19 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4185,117 +4185,198 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
   }
 };
 
-class FunctionEffect;
+// ------------------------------------------------------------------------------
+
+// TODO: Should FunctionEffect be located elsewhere, where Decl is not
+// forward-declared?
+class Decl;
+class CXXMethodDecl;
 class FunctionEffectSet;
 
+/// Represents an abstract function effect.
+class FunctionEffect {
+public:
+  enum class Type : unsigned char {
+    None = 0,
+    NoLockTrue,
+    NoAllocTrue,
+  };
+
+  /// Flags describing behaviors of the effect.
+  // (Why not a struct with bitfields? There's one function that would like to
+  // test a caller-specified bit. There are some potential optimizations that
+  // would OR together the bits of multiple effects.)
+  using Flags = unsigned;
+  enum FlagBit : unsigned {
+    // Some effects require verification, e.g. nolock(true); others might not?
+    // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
+    FE_RequiresVerification = 0x1,
+
+    // Does this effect want to verify all function calls originating in
+    // functions having this effect? TODO: maybe always true, vestigial.
+    FE_VerifyCalls = 0x2,
+
+    // Can verification inspect callees' implementations? (e.g. nolock: yes,
+    // tcb+types: no)
+    FE_InferrableOnCallees = 0x4,
+
+    // Language constructs which effects can diagnose as disallowed.
+    FE_ExcludeThrow = 0x8,
+    FE_ExcludeCatch = 0x10,
+    FE_ExcludeObjCMessageSend = 0x20,
+    FE_ExcludeStaticLocalVars = 0x40,
+    FE_ExcludeThreadLocalVars = 0x80
+  };
+
+private:
+  // For uniqueness, currently only Type_ is significant.
+
+  Type Type_ : 2;   // Expands when there are more types
+  Flags Flags_ : 8; // A constant function of Type but cached here.
+
+  // Expansion: for hypothetical TCB+types, there could be one type for TCB,
+  // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
+  // be considered for uniqueness.
+  unsigned Unused : 22;
+
+public:
+  using CalleeDeclOrType =
+      llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
+
+  FunctionEffect() : Type_(Type::None), Flags_(0), Unused(0) {}
+
+  explicit FunctionEffect(Type T);
+
+  /// The type of the effect.
+  Type type() const { return Type_; }
+
+  /// Flags describing behaviors of the effect.
+  Flags flags() const { return Flags_; }
+
+  /// The description printed in diagnostics, e.g. 'nolock'.
+  StringRef name() const;
+
+  /// A serializable, hashable representation.
+  uint32_t opaqueRepr() const { return unsigned(Type_) | (Flags_ << 2u); }
+
+  /// Return true if adding or removing the effect as part of a type conversion
+  /// should generate a diagnostic.
+  bool diagnoseConversion(bool Adding, QualType OldType,
+                          FunctionEffectSet OldFX, QualType NewType,
+                          FunctionEffectSet NewFX) const;
+
+  /// Return true if adding or removing the effect in a redeclaration should
+  /// generate a diagnostic.
+  bool diagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
+                             FunctionEffectSet OldFX,
+                             const FunctionDecl &NewFunction,
+                             FunctionEffectSet NewFX) const;
+
+  /// Return true if adding or removing the effect in a C++ virtual method
+  /// override should generate a diagnostic.
+  bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
+                              FunctionEffectSet OldFX,
+                              const CXXMethodDecl &NewMethod,
+                              FunctionEffectSet NewFX) const;
+
+  /// Return true if the effect is allowed to be inferred on the specified Decl
+  /// (may be a FunctionDecl or BlockDecl). Only used if the effect has
+  /// FE_InferrableOnCallees flag set. Example: This allows nolock(false) to
+  /// prevent inference for the function.
+  bool canInferOnDecl(const Decl *Caller, FunctionEffectSet CallerFX) const;
+
+  // Called if FE_VerifyCalls flag is set; return false for success. When true
+  // is returned for a direct call, then the FE_InferrableOnCallees flag may
+  // trigger inference rather than an immediate diagnostic. Caller should be
+  // assumed to have the effect (it may not have it explicitly when inferring).
+  bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
+                            FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
+                            FunctionEffectSet CalleeFX) const;
+
+  friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+    return LHS.Type_ == RHS.Type_;
+  }
+  friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+    return LHS.Type_ != RHS.Type_;
+  }
+  friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+    return LHS.Type_ < RHS.Type_;
+  }
+};
+
 // It is the user's responsibility to keep this in set form: elements are
 // ordered and unique.
 // We could hide the mutating methods which are capable of breaking the
 // invariant, but they're needed and safe when used with STL set algorithms.
-class MutableFunctionEffectSet : public SmallVector<const FunctionEffect *, 4> {
+class MutableFunctionEffectSet : public SmallVector<FunctionEffect, 4> {
 public:
   using SmallVector::insert;
   using SmallVector::SmallVector;
 
+  MutableFunctionEffectSet(const FunctionEffect &Effect);
+  using Base = SmallVector<FunctionEffect, 4>;
+
   /// Maintains order/uniquenesss.
-  void insert(const FunctionEffect *effect);
+  void insert(const FunctionEffect &Effect);
+
+  MutableFunctionEffectSet &operator|=(FunctionEffectSet RHS);
 
-  MutableFunctionEffectSet &operator|=(FunctionEffectSet rhs);
+  operator llvm::ArrayRef<const FunctionEffect>() const {
+    return {data(), size()};
+  }
 };
 
+/// A constant, uniqued set of FunctionEffect instances.
+// These sets will tend to be very small (0-1 elements), so represent them as
+// sorted spans, which are compatible with the STL set algorithms.
 class FunctionEffectSet {
-public:
-  // These sets will tend to be very small (1 element), so represent them as
-  // sorted vectors, which are compatible with the STL set algorithms. Using an
-  // array or vector also means the elements are contiguous, keeping iterators
-  // simple.
-
 private:
-  // 'Uniqued' refers to the set itself being uniqued. Storage is allocated
-  // separately. Use ArrayRef for its iterators. Subclass so as to be able to
-  // compare (it seems ArrayRef would silently convert itself to a vector for
-  // comparison?!).
-  class UniquedAndSortedFX : public llvm::ArrayRef<const FunctionEffect *> {
-  public:
-    using Base = llvm::ArrayRef<const FunctionEffect *>;
-
-    UniquedAndSortedFX(Base Array) : Base(Array) {}
-    UniquedAndSortedFX(const FunctionEffect **Ptr, size_t Len)
-        : Base(Ptr, Len) {}
-
-    bool operator<(const UniquedAndSortedFX &rhs) const;
-  };
-
-  // Could have used a TinyPtrVector if it were unique-able.
-  // Empty set has a null Impl.
-  llvm::PointerUnion<const FunctionEffect *, const UniquedAndSortedFX *> Impl;
+  explicit FunctionEffectSet(llvm::ArrayRef<const FunctionEffect> Array)
+      : Impl(Array) {}
 
-  explicit FunctionEffectSet(const FunctionEffect *Single) : Impl(Single) {}
-  explicit FunctionEffectSet(const UniquedAndSortedFX *Multi) : Impl(Multi) {}
+  // Points to a separately allocated array, uniqued.
+  llvm::ArrayRef<const FunctionEffect> Impl;
 
 public:
-  using Differences =
-      SmallVector<std::pair<const FunctionEffect *, /*added=*/bool>>;
+  using Differences = SmallVector<std::pair<FunctionEffect, /*added=*/bool>>;
 
-  FunctionEffectSet() : Impl(nullptr) {}
+  FunctionEffectSet() = default;
 
-  void *getOpaqueValue() const { return Impl.getOpaqueValue(); }
+  const void *getOpaqueValue() const { return Impl.data(); }
 
   explicit operator bool() const { return !empty(); }
-  bool empty() const {
-    if (Impl.isNull())
-      return true;
-    if (const UniquedAndSortedFX *Vec =
-            dyn_cast_if_present<const UniquedAndSortedFX *>(Impl))
-      return Vec->empty();
-    return false;
-  }
-  size_t size() const {
-    if (empty())
-      return 0;
-    if (isa<const FunctionEffect *>(Impl))
-      return 1;
-    return cast<const UniquedAndSortedFX *>(Impl)->size();
-  }
+  bool empty() const { return Impl.empty(); }
+  size_t size() const { return Impl.size(); }
 
-  using iterator = const FunctionEffect *const *;
+  using iterator = const FunctionEffect *;
 
-  iterator begin() const {
-    if (isa<const FunctionEffect *>(Impl))
-      return Impl.getAddrOfPtr1();
-    return cast<const UniquedAndSortedFX *>(Impl)->begin();
-  }
+  iterator begin() const { return Impl.begin(); }
 
-  iterator end() const {
-    if (isa<const FunctionEffect *>(Impl))
-      return begin() + (Impl.isNull() ? 0 : 1);
-    return cast<const UniquedAndSortedFX *>(Impl)->end();
-  }
+  iterator end() const { return Impl.end(); }
 
-  ArrayRef<const FunctionEffect *> items() const { return {begin(), end()}; }
+  ArrayRef<const FunctionEffect> items() const { return Impl; }
 
-  bool operator==(const FunctionEffectSet &other) const {
-    return Impl == other.Impl;
+  bool operator==(const FunctionEffectSet &RHS) const {
+    return Impl.data() == RHS.Impl.data();
   }
-  bool operator!=(const FunctionEffectSet &other) const {
-    return Impl != other.Impl;
+  bool operator!=(const FunctionEffectSet &RHS) const {
+    return Impl.data() != RHS.Impl.data();
   }
-  bool operator<(const FunctionEffectSet &other) const;
+  bool operator<(const FunctionEffectSet &RHS) const;
 
   void dump(llvm::raw_ostream &OS) const;
 
-  /// Factory functions: return instances with uniqued implementations.
-  static FunctionEffectSet create(const FunctionEffect &single) {
-    return FunctionEffectSet{&single};
-  }
-  static FunctionEffectSet create(llvm::ArrayRef<const FunctionEffect *> items);
+  /// Factory function: returns instances with uniqued implementations.
+  static FunctionEffectSet create(ASTContext &C,
+                                  const MutableFunctionEffectSet &FX);
 
-  /// Union. Caller should check for incompatible effects.
-  FunctionEffectSet operator|(const FunctionEffectSet &rhs) const;
   /// Intersection.
-  MutableFunctionEffectSet operator&(const FunctionEffectSet &rhs) const;
+  MutableFunctionEffectSet operator&(const FunctionEffectSet &RHS) const;
   /// Difference.
-  MutableFunctionEffectSet operator-(const FunctionEffectSet &rhs) const;
+  MutableFunctionEffectSet operator-(const FunctionEffectSet &RHS) const;
+
+  static FunctionEffectSet getUnion(ASTContext &C, const FunctionEffectSet &LHS,
+                                    const FunctionEffectSet &RHS);
 
   /// Caller should short-circuit by checking for equality first.
   static Differences differences(const FunctionEffectSet &Old,
@@ -7901,147 +7982,6 @@ QualType DecayedType::getPointeeType() const {
 void FixedPointValueToString(SmallVectorImpl<char> &Str, llvm::APSInt Val,
                              unsigned Scale);
 
-// ------------------------------------------------------------------------------
-
-// TODO: Should FunctionEffect be located elsewhere, where Decl is not
-// forward-declared?
-class Decl;
-class CXXMethodDecl;
-
-/// Represents an abstract function effect.
-class FunctionEffect {
-public:
-  enum class Type : unsigned char {
-    NoLockTrue,
-    NoAllocTrue,
-  };
-
-  /// Flags describing behaviors of the effect.
-  // (Why not a struct with bitfields? There's one function that would like to
-  // test a caller-specified bit. There are some potential optimizations that
-  // would OR together the bits of multiple effects.)
-  using Flags = unsigned;
-  enum FlagBit : unsigned {
-    // Some effects require verification, e.g. nolock(true); others might not?
-    // (no example yet; TODO: maybe always true, vestigial from nolock(false)).
-    FE_RequiresVerification = 0x1,
-
-    // Does this effect want to verify all function calls originating in
-    // functions having this effect? TODO: maybe always true, vestigial.
-    FE_VerifyCalls = 0x2,
-
-    // Can verification inspect callees' implementations? (e.g. nolock: yes,
-    // tcb+types: no)
-    FE_InferrableOnCallees = 0x4,
-
-    // Language constructs which effects can diagnose as disallowed.
-    FE_ExcludeThrow = 0x8,
-    FE_ExcludeCatch = 0x10,
-    FE_ExcludeObjCMessageSend = 0x20,
-    FE_ExcludeStaticLocalVars = 0x40,
-    FE_ExcludeThreadLocalVars = 0x80
-  };
-
-private:
-  const Type Type_;
-  const Flags Flags_;
-  const char *Name;
-
-public:
-  using CalleeDeclOrType =
-      llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
-
-  FunctionEffect(Type T, Flags F, const char *Name)
-      : Type_(T), Flags_(F), Name(Name) {}
-  virtual ~FunctionEffect();
-
-  /// The type of the effect.
-  Type type() const { return Type_; }
-
-  /// Flags describing behaviors of the effect.
-  Flags flags() const { return Flags_; }
-
-  /// The description printed in diagnostics, e.g. 'nolock'.
-  StringRef name() const { return Name; }
-
-  /// The description used by TypePrinter, e.g. __attribute__((clang_nolock))
-  virtual std::string attribute() const = 0;
-
-  /// Return true if adding or removing the effect as part of a type conversion
-  /// should generate a diagnostic.
-  virtual bool diagnoseConversion(bool Adding, QualType OldType,
-                                  FunctionEffectSet OldFX, QualType NewType,
-                                  FunctionEffectSet NewFX) const;
-
-  /// Return true if adding or removing the effect in a redeclaration should
-  /// generate a diagnostic.
-  virtual bool diagnoseRedeclaration(bool Adding,
-                                     const FunctionDecl &OldFunction,
-                                     FunctionEffectSet OldFX,
-                                     const FunctionDecl &NewFunction,
-                                     FunctionEffectSet NewFX) const;
-
-  /// Return true if adding or removing the effect in a C++ virtual method
-  /// override should generate a diagnostic.
-  virtual bool diagnoseMethodOverride(bool Adding,
-                                      const CXXMethodDecl &OldMethod,
-                                      FunctionEffectSet OldFX,
-                                      const CXXMethodDecl &NewMethod,
-                                      FunctionEffectSet NewFX) const;
-
-  /// Return true if the effect is allowed to be inferred on the specified Decl
-  /// (may be a FunctionDecl or BlockDecl). Only used if the effect has
-  /// kInferrableOnCallees flag set. Example: This allows nolock(false) to
-  /// prevent inference for the function.
-  virtual bool canInferOnDecl(const Decl *Caller,
-                              FunctionEffectSet CallerFX) const;
-
-  // Called if kVerifyCalls flag is set; return false for success. When true is
-  // returned for a direct call, then the kInferrableOnCallees flag may trigger
-  // inference rather than an immediate diagnostic. Caller should be assumed to
-  // have the effect (it may not have it explicitly when inferring).
-  virtual bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
-                                    FunctionEffectSet CallerFX,
-                                    CalleeDeclOrType Callee,
-                                    FunctionEffectSet CalleeFX) const;
-};
-
-/// FunctionEffect subclass for nolock and noalloc (whose behaviors are close
-/// to identical).
-class NoLockNoAllocEffect : public FunctionEffect {
-  bool isNoLock() const { return type() == Type::NoLockTrue; }
-
-public:
-  static const NoLockNoAllocEffect &nolock_instance();
-  static const NoLockNoAllocEffect &noalloc_instance();
-
-  NoLockNoAllocEffect(Type Type, const char *Name);
-  ~NoLockNoAllocEffect() override;
-
-  std::string attribute() const override;
-
-  bool diagnoseConversion(bool Adding, QualType OldType,
-                          FunctionEffectSet OldFX, QualType NewType,
-                          FunctionEffectSet NewFX) const override;
-
-  bool diagnoseRedeclaration(bool Adding, const FunctionDecl &OldFunction,
-                             FunctionEffectSet OldFX,
-                             const FunctionDecl &NewFunction,
-                             FunctionEffectSet NewFX) const override;
-
-  bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
-                              FunctionEffectSet OldFX,
-                              const CXXMethodDecl &NewMethod,
-                              FunctionEffectSet NewFX) const override;
-
-  bool canInferOnDecl(const Decl *Caller,
-                      FunctionEffectSet CallerFX) const override;
-
-  bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
-                            FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
-                            FunctionEffectSet CalleeFX) const override;
-};
-
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_TYPE_H
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 0afc61ff6de487..8a9922594e9f98 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4922,12 +4922,58 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
           getTypeConstraintConcept(), getTypeConstraintArguments());
 }
 
-FunctionEffect::~FunctionEffect() = default;
+FunctionEffect::FunctionEffect(Type T) : Type_(T), Flags_(0), Unused(0) {
+  switch (T) {
+  case Type::NoLockTrue:
+    Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
+             FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
+             FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars;
+    break;
+
+  case Type::NoAllocTrue:
+    // Same as NoLockTrue, except without FE_ExcludeStaticLocalVars
+    Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
+             FE_ExcludeThrow | FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
+             FE_ExcludeThreadLocalVars;
+    break;
+  default:
+    break;
+  }
+}
+
+StringRef FunctionEffect::name() const {
+  switch (Type_) {
+  default:
+    return "";
+  case Type::NoLockTrue:
+    return "nolock";
+  case Type::NoAllocTrue:
+    return "noalloc";
+  }
+}
 
 bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
                                         FunctionEffectSet OldFX,
                                         QualType NewType,
                                         FunctionEffectSet NewFX) const {
+
+  switch (Type_) {
+  case Type::NoAllocTrue:
+    // noalloc can't be added (spoofed) during a conversion, unless we have
+    // nolock
+    if (Adding) {
+      for (const auto &Effect : OldFX) {
+        if (Effect.type() == Type::NoLockTrue)
+          return false;
+      }
+    }
+    [[fallthrough]];
+  case Type::NoLockTrue:
+    // nolock can't be added (spoofed) during a conversion
+    return Adding;
+  default:
+    break;
+  }
   return false;
 }
 
@@ -4936,6 +4982,15 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
                                            FunctionEffectSet OldFX,
                                            const FunctionDecl &NewFunction,
                                            FunctionEffectSet NewFX) const {
+  switch (Type_) {
+  case Type::NoAllocTrue:
+  case Type::NoLockTrue:
+    // nolock/noalloc can't be removed in a redeclaration
+    // adding -> false, removing -> true (diagnose)
+    return !Adding;
+  default:
+    break;
+  }
   return false;
 }
 
@@ -4944,120 +4999,82 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
                                             FunctionEffectSet OldFX,
                                             const CXXMethodDecl &NewMethod,
                                             FunctionEffectSet NewFX) const {
+  switch (Type_) {
+  case Type::NoAllocTrue:
+  case Type::NoLockTrue:
+    // nolock/noalloc can't be removed from an override
+    // adding -> false, removing -> true (diagnose)
+    return !Adding;
+  default:
+    break;
+  }
   return false;
 }
 
 bool FunctionEffect::canInferOnDecl(const Decl *Caller,
                                     FunctionEffectSet CallerFX) const {
+  switch (Type_) {
+  case Type::NoAllocTrue:
+  case Type::NoLockTrue: {
+    // Does the Decl have nolock(false) / noalloc(false) ?
+    QualType QT;
+    if (isa<BlockDecl>(Caller)) {
+      const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
+      QT = TSI->getType();
+    } else if (isa<ValueDecl>(Caller)) {
+      QT = cast<ValueDecl>(Caller)->getType();
+    } else {
+      return false;
+    }
+    if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
+                                               : attr::Kind::NoAlloc)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  default:
+    break;
+  }
   return false;
 }
 
+// TODO: Notice that we don't care about some of the parameters. Is the
+// interface overly general?
 bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
                                           FunctionEffectSet CallerFX,
                                           CalleeDeclOrType Callee,
                                           FunctionEffectSet CalleeFX) const {
-  return false;
-}
-
-const NoLockNoAllocEffect &NoLockNoAllocEffect::nolock_instance() {
-  static NoLockNoAllocEffect global(Type::NoLockTrue, "nolock");
-  return global;
-}
-
-const NoLockNoAllocEffect &NoLockNoAllocEffect::noalloc_instance() {
-  static NoLockNoAllocEffect global(Type::NoAllocTrue, "noalloc");
-  return global;
-}
-
-// TODO: Separate flags for noalloc
-NoLockNoAllocEffect::NoLockNoAllocEffect(Type Ty, const char *Name)
-    : FunctionEffect(Ty,
-                     FE_RequiresVerification | FE_VerifyCalls |
-                         FE_InferrableOnCallees | FE_ExcludeThrow |
-                         FE_ExcludeCatch | FE_ExcludeObjCMessageSend |
-                         FE_ExcludeStaticLocalVars | FE_ExcludeThreadLocalVars,
-                     Name) {}
-
-NoLockNoAllocEffect::~NoLockNoAllocEffect() = default;
-
-std::string NoLockNoAllocEffect::attribute() const {
-  return std::string{"__attribute__((clang_"} + name().str() + "))";
-}
-
-bool NoLockNoAllocEffect::diagnoseConversion(bool Adding, QualType OldType,
-                                             FunctionEffectSet OldFX,
-                                             QualType NewType,
-                                             FunctionEffectSet NewFX) const {
-  // noalloc can't be added (spoofed) during a conversion, unless we have nolock
-  if (Adding) {
-    if (!isNoLock()) {
-      for (const auto *Effect : OldFX) {
-        if (Effect->type() == Type::NoLockTrue)
-          return false;
+  switch (Type_) {
+  case Type::NoAllocTrue:
+  case Type::NoLockTrue: {
+    const Type CallerType = type();
+    for (const auto &Effect : CalleeFX) {
+      const Type ET = Effect.type();
+      // Does callee have same or stronger constraint?
+      if (ET == CallerType ||
+          (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
+        return false; // no diagnostic
       }
     }
-    // nolock can't be added (spoofed) during a conversion.
-    return true;
+    return true; // warning
+  }
+  default:
+    break;
   }
   return false;
 }
 
-bool NoLockNoAllocEffect::diagnoseRedeclaration(bool Adding,
-                                                const FunctionDecl &OldFunction,
-                                                FunctionEffectSet OldFX,
-                                                const FunctionDecl &NewFunction,
-                                                FunctionEffectSet NewFX) const {
-  // nolock/noalloc can't be removed in a redeclaration
-  // adding -> false, removing -> true (diagnose)
-  return !Adding;
-}
-
-bool NoLockNoAllocEffect::diagnoseMethodOverride(
-    bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
-    const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
-  // nolock/noalloc can't be removed from an override
-  return !Adding;
-}
-
-bool NoLockNoAllocEffect::canInferOnDecl(const Decl *Caller,
-                                         FunctionEffectSet CallerFX) const {
-  // Does the Decl have nolock(false) / noalloc(false) ?
-  QualType QT;
-  if (isa<BlockDecl>(Caller)) {
-    const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
-    QT = TSI->getType();
-  } else if (isa<ValueDecl>(Caller)) {
-    QT = cast<ValueDecl>(Caller)->getType();
-  } else {
-    return false;
-  }
-  if (QT->hasAttr(isNoLock() ? attr::Kind::NoLock : attr::Kind::NoAlloc)) {
-    return false;
-  }
-
-  return true;
-}
+// =====
 
-// TODO: Notice that we don't care about some of the parameters. Is the
-// interface overly general?
-bool NoLockNoAllocEffect::diagnoseFunctionCall(
-    bool Direct, const Decl *Caller, FunctionEffectSet CallerFX,
-    CalleeDeclOrType Callee, FunctionEffectSet CalleeFX) const {
-  const Type CallerType = type();
-  for (const auto *Effect : CalleeFX) {
-    const Type ET = Effect->type();
-    if (ET == CallerType ||
-        (CallerType == Type::NoAllocTrue && ET == Type::NoLockTrue)) {
-      return false;
-    }
-  }
-  return true;
+MutableFunctionEffectSet::MutableFunctionEffectSet(
+    const FunctionEffect &effect) {
+  push_back(effect);
 }
 
-// =====
-
-void MutableFunctionEffectSet::insert(const FunctionEffect *Effect) {
-  auto Iter = std::lower_bound(begin(), end(), Effect);
+void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
+  const auto &Iter = std::lower_bound(begin(), end(), Effect);
   if (*Iter != Effect) {
     insert(Iter, Effect);
   }
@@ -5066,55 +5083,81 @@ void MutableFunctionEffectSet::insert(const FunctionEffect *Effect) {
 MutableFunctionEffectSet &
 MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
   // TODO: For large RHS sets, use set_union or a custom insert-in-place
-  for (const auto *Effect : RHS) {
+  for (const auto &Effect : RHS) {
     insert(Effect);
   }
   return *this;
 }
 
-// This could be simpler if there were a simple set container that could be
-// queried by ArrayRef but which stored something else. Possibly a DenseMap with
-// void values?
-FunctionEffectSet
-FunctionEffectSet::create(llvm::ArrayRef<const FunctionEffect *> Items) {
-  if (Items.empty()) {
-    return FunctionEffectSet{};
+using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
+
+llvm::hash_code hash_value(const FunctionEffect &Effect) {
+  return Effect.opaqueRepr();
+}
+
+namespace llvm {
+template <> struct DenseMapInfo<FunctionEffectSpan> {
+  static FunctionEffectSpan getEmptyKey() {
+    return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
+  }
+  static FunctionEffectSpan getTombstoneKey() {
+    return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
   }
-  if (Items.size() == 1) {
-    return FunctionEffectSet{Items[0]};
+  static unsigned getHashValue(const FunctionEffectSpan &Val) {
+    hash_code hash1 = hash_value(Val.size());
+    // Treat the FunctionEffects as a span of integers
+    const FunctionEffect *Begin = Val.begin();
+    const FunctionEffect *End = Val.end();
+    hash_code hash2 =
+        hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
+                           reinterpret_cast<const uint32_t *>(End));
+    return hash_combine(hash1, hash2);
+  }
+  static bool isEqual(const FunctionEffectSpan &LHS,
+                      const FunctionEffectSpan &RHS) {
+    if (LHS.size() != RHS.size()) {
+      return false;
+    }
+    // distinguish empty from tombstone
+    if (LHS.size() == 0) {
+      return LHS.data() == RHS.data();
+    }
+    return std::equal(LHS.begin(), LHS.end(), RHS.begin());
   }
+};
+} // namespace llvm
 
-  UniquedAndSortedFX NewSet(Items); // just copies the ArrayRef
+// The ASTContext
+FunctionEffectSet
+FunctionEffectSet::create(ASTContext &C, const MutableFunctionEffectSet &FX) {
+  if (FX.empty()) {
+    return {};
+  }
 
-  // SmallSet only has contains(), so it provides no way to obtain the uniqued
-  // value.
   // TODO: Put this in the ASTContext
-  // TODO: Try making this a DenseSet? Requires more methods on the members.
-  // static llvm::DenseSet<UniquedAndSortedFX> UniquedFXSets;
-  // Punt on this until we revisit FunctionFX.
+  // TODO: And destroy the memory
+  static llvm::DenseSet<FunctionEffectSpan> UniquedFXSets;
 
-  static std::set<UniquedAndSortedFX> UniquedFXSets;
-
-  // See if we already have this set.
-  const auto Iter = UniquedFXSets.find(NewSet);
+  // Do we already have the incoming set?
+  const auto Iter = UniquedFXSets.find_as(FX);
   if (Iter != UniquedFXSets.end()) {
-    return FunctionEffectSet{&*Iter};
+    return FunctionEffectSet(*Iter);
   }
 
   // Copy the incoming array to permanent storage.
-  auto *Storage = new const FunctionEffect *[Items.size()];
-  std::copy(Items.begin(), Items.end(), Storage);
+  FunctionEffect *Storage = new FunctionEffect[FX.size()];
+  std::copy(FX.begin(), FX.end(), Storage);
 
   // Make a new wrapper and insert it into the set.
-  NewSet = UniquedAndSortedFX(Storage, Items.size());
-  auto [InsIter, _] = UniquedFXSets.insert(NewSet);
-  return FunctionEffectSet(&*InsIter);
+  auto [InsIter, _] =
+      UniquedFXSets.insert(FunctionEffectSpan(Storage, FX.size()));
+  return FunctionEffectSet(*InsIter);
 }
 
-FunctionEffectSet
-FunctionEffectSet::operator|(const FunctionEffectSet &RHS) const {
+FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
+                                              const FunctionEffectSet &LHS,
+                                              const FunctionEffectSet &RHS) {
   // Optimize for one of the two sets being empty
-  const FunctionEffectSet &LHS = *this;
   if (LHS.empty())
     return RHS;
   if (RHS.empty())
@@ -5129,7 +5172,7 @@ FunctionEffectSet::operator|(const FunctionEffectSet &RHS) const {
   std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
                  std::back_inserter(Vec));
   // The result of a set operation is an ordered/unique set.
-  return FunctionEffectSet::create(Vec);
+  return FunctionEffectSet::create(C, Vec);
 }
 
 MutableFunctionEffectSet
@@ -5161,10 +5204,10 @@ FunctionEffectSet::differences(const FunctionEffectSet &Old,
                                const FunctionEffectSet &New) {
   // TODO: Could be a one-pass algorithm.
   Differences Result;
-  for (const auto *Effect : (New - Old)) {
+  for (const auto &Effect : (New - Old)) {
     Result.emplace_back(Effect, true);
   }
-  for (const auto *Effect : (Old - New)) {
+  for (const auto &Effect : (Old - New)) {
     Result.emplace_back(Effect, false);
   }
   return Result;
@@ -5186,20 +5229,15 @@ bool FunctionEffectSet::operator<(const FunctionEffectSet &RHS) const {
                                       RHS.end());
 }
 
-bool FunctionEffectSet::UniquedAndSortedFX::operator<(
-    const UniquedAndSortedFX &RHS) const {
-  return this < &RHS;
-}
-
 void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
-  OS << "FX{";
+  OS << "Effects{";
   bool First = true;
-  for (const auto *Effect : *this) {
+  for (const auto &Effect : *this) {
     if (!First)
       OS << ", ";
     else
       First = false;
-    OS << Effect->name();
+    OS << Effect.name();
   }
   OS << "}";
 }
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 252a5dfbc952b0..1f539616f04c2a 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -987,8 +987,8 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
   T->printExceptionSpecification(OS, Policy);
 
   if (const FunctionEffectSet FX = T->getFunctionEffects()) {
-    for (const auto *Effect : FX) {
-      OS << " " << Effect->attribute();
+    for (const auto &Effect : FX) {
+      OS << " __attribute__((clang_" << Effect.name() << "))";
     }
   }
 
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index cf9b1244e81bfd..ce7b91d4a5e59e 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2406,14 +2406,14 @@ enum class DiagnosticID : uint8_t {
 };
 
 struct Diagnostic {
-  const FunctionEffect *Effect = nullptr;
+  FunctionEffect Effect;
   const Decl *Callee = nullptr; // only valid for Calls*
   SourceLocation Loc;
   DiagnosticID ID = DiagnosticID::None;
 
   Diagnostic() = default;
 
-  Diagnostic(const FunctionEffect *Effect, DiagnosticID ID, SourceLocation Loc,
+  Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
              const Decl *Callee = nullptr)
       : Effect(Effect), Callee(Callee), Loc(Loc), ID(ID) {}
 };
@@ -2521,12 +2521,12 @@ class EffectToDiagnosticMap {
   // Since we currently only have a tiny number of effects (typically no more
   // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
   // is often empty, use a unique_ptr to the SmallVector.
-  using Element = std::pair<const FunctionEffect *, Diagnostic>;
+  using Element = std::pair<FunctionEffect, Diagnostic>;
   using ImplVec = llvm::SmallVector<Element, 1>;
   std::unique_ptr<ImplVec> Impl;
 
 public:
-  Diagnostic &getOrInsertDefault(const FunctionEffect *Key) {
+  Diagnostic &getOrInsertDefault(FunctionEffect Key) {
     if (Impl == nullptr) {
       Impl = std::make_unique<llvm::SmallVector<Element>>();
       auto &Item = Impl->emplace_back();
@@ -2542,7 +2542,7 @@ class EffectToDiagnosticMap {
     return Iter->second;
   }
 
-  const Diagnostic *lookup(const FunctionEffect *key) {
+  const Diagnostic *lookup(FunctionEffect key) {
     if (Impl == nullptr) {
       return nullptr;
     }
@@ -2596,20 +2596,20 @@ class PendingFunctionAnalysis {
   std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
 
 public:
-  PendingFunctionAnalysis(const CallableInfo &cinfo,
+  PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &cinfo,
                           FunctionEffectSet AllInferrableEffectsToVerify) {
     MutableFunctionEffectSet fx;
-    for (const auto *effect : cinfo.Effects) {
-      if (effect->flags() & FunctionEffect::FE_RequiresVerification) {
+    for (const auto &effect : cinfo.Effects) {
+      if (effect.flags() & FunctionEffect::FE_RequiresVerification) {
         fx.insert(effect);
       }
     }
-    DeclaredVerifiableEffects = FunctionEffectSet::create(fx);
+    DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, fx);
 
     // Check for effects we are not allowed to infer
     fx.clear();
-    for (const auto *effect : AllInferrableEffectsToVerify) {
-      if (effect->canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
+    for (const auto &effect : AllInferrableEffectsToVerify) {
+      if (effect.canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
         fx.insert(effect);
       } else {
         // Add a diagnostic for this effect if a caller were to
@@ -2622,8 +2622,8 @@ class PendingFunctionAnalysis {
       }
     }
     // fx is now the set of inferrable effects which are not prohibited
-    FXToInfer = FunctionEffectSet::create(FunctionEffectSet::create(fx) -
-                                          DeclaredVerifiableEffects);
+    FXToInfer = FunctionEffectSet::create(
+        Ctx, FunctionEffectSet::create(Ctx, fx) - DeclaredVerifiableEffects);
   }
 
   // Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2655,8 +2655,7 @@ class PendingFunctionAnalysis {
     return UnverifiedDirectCalls == nullptr || UnverifiedDirectCalls->empty();
   }
 
-  const Diagnostic *
-  diagnosticForInferrableEffect(const FunctionEffect *effect) {
+  const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
     return InferrableEffectToFirstDiagnostic.lookup(effect);
   }
 
@@ -2696,23 +2695,23 @@ class CompleteFunctionAnalysis {
   EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
 
 public:
-  CompleteFunctionAnalysis(PendingFunctionAnalysis &pending,
+  CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &pending,
                            FunctionEffectSet funcFX,
                            FunctionEffectSet AllInferrableEffectsToVerify) {
     MutableFunctionEffectSet verified;
     verified |= funcFX;
-    for (const auto *effect : AllInferrableEffectsToVerify) {
+    for (const auto &effect : AllInferrableEffectsToVerify) {
       if (pending.diagnosticForInferrableEffect(effect) == nullptr) {
         verified.insert(effect);
       }
     }
-    VerifiedEffects = FunctionEffectSet::create(verified);
+    VerifiedEffects = FunctionEffectSet::create(Ctx, verified);
 
     InferrableEffectToFirstDiagnostic =
         std::move(pending.InferrableEffectToFirstDiagnostic);
   }
 
-  const Diagnostic *firstDiagnosticForEffect(const FunctionEffect *effect) {
+  const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &effect) {
     // TODO: is this correct?
     return InferrableEffectToFirstDiagnostic.lookup(effect);
   }
@@ -2806,14 +2805,14 @@ class Analyzer {
     // be checked, and to see which ones are inferrable.
     {
       MutableFunctionEffectSet inferrableEffects;
-      for (const FunctionEffect *effect : Sem.AllEffectsToVerify) {
-        const auto Flags = effect->flags();
+      for (const FunctionEffect &effect : Sem.AllEffectsToVerify) {
+        const auto Flags = effect.flags();
         if (Flags & FunctionEffect::FE_InferrableOnCallees) {
           inferrableEffects.insert(effect);
         }
       }
       AllInferrableEffectsToVerify =
-          FunctionEffectSet::create(inferrableEffects);
+          FunctionEffectSet::create(Sem.getASTContext(), inferrableEffects);
       llvm::outs() << "AllInferrableEffectsToVerify: ";
       AllInferrableEffectsToVerify.dump(llvm::outs());
       llvm::outs() << "\n";
@@ -2887,7 +2886,8 @@ class Analyzer {
     // Build a PendingFunctionAnalysis on the stack. If it turns out to be
     // complete, we'll have avoided a heap allocation; if it's incomplete, it's
     // a fairly trivial move to a heap-allocated object.
-    PendingFunctionAnalysis FAnalysis(CInfo, AllInferrableEffectsToVerify);
+    PendingFunctionAnalysis FAnalysis(Sem.getASTContext(), CInfo,
+                                      AllInferrableEffectsToVerify);
 
     llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
     FAnalysis.dump(llvm::outs());
@@ -2915,7 +2915,8 @@ class Analyzer {
       emitDiagnostics(*Diags, CInfo, Sem);
     }
     auto *CompletePtr = new CompleteFunctionAnalysis(
-        Pending, CInfo.Effects, AllInferrableEffectsToVerify);
+        Sem.getASTContext(), Pending, CInfo.Effects,
+        AllInferrableEffectsToVerify);
     DeclAnalysis[CInfo.CDecl] = CompletePtr;
     llvm::outs() << "inserted complete " << CompletePtr << "\n";
     DeclAnalysis.dump(Sem, llvm::outs());
@@ -2964,10 +2965,10 @@ class Analyzer {
     llvm::outs() << "\n";
     puts("");
 
-    auto check1Effect = [&](const FunctionEffect *Effect, bool Inferring) {
-      const auto Flags = Effect->flags();
+    auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+      const auto Flags = Effect.flags();
       if (Flags & FunctionEffect::FE_VerifyCalls) {
-        const bool diagnose = Effect->diagnoseFunctionCall(
+        const bool diagnose = Effect.diagnoseFunctionCall(
             DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
             CalleeEffects);
         if (diagnose) {
@@ -2991,11 +2992,11 @@ class Analyzer {
       }
     };
 
-    for (auto *Effect : PFA.DeclaredVerifiableEffects) {
+    for (const auto &Effect : PFA.DeclaredVerifiableEffects) {
       check1Effect(Effect, false);
     }
 
-    for (auto *Effect : PFA.FXToInfer) {
+    for (const auto &Effect : PFA.FXToInfer) {
       check1Effect(Effect, true);
     }
   }
@@ -3026,7 +3027,7 @@ class Analyzer {
 
     // Top-level diagnostics are warnings.
     for (const auto &Diag : Diags) {
-      StringRef effectName = Diag.Effect->name();
+      StringRef effectName = Diag.Effect.name();
       switch (Diag.ID) {
       case DiagnosticID::None:
       case DiagnosticID::DeclWithoutConstraintOrInference: // shouldn't happen
@@ -3230,22 +3231,22 @@ class Analyzer {
                                    const Decl *Callee = nullptr) {
       // If there are ANY declared verifiable effects holding the flag, store
       // just one diagnostic.
-      for (auto *Effect : CurrentFunction.DeclaredVerifiableEffects) {
-        if (Effect->flags() & Flag) {
+      for (const auto &Effect : CurrentFunction.DeclaredVerifiableEffects) {
+        if (Effect.flags() & Flag) {
           addDiagnosticInner(/*inferring=*/false, Effect, D, Loc, Callee);
           break;
         }
       }
       // For each inferred-but-not-verifiable effect holding the flag, store a
       // diagnostic, if we don't already have a diagnostic for that effect.
-      for (auto *Effect : CurrentFunction.FXToInfer) {
-        if (Effect->flags() & Flag) {
+      for (const auto &Effect : CurrentFunction.FXToInfer) {
+        if (Effect.flags() & Flag) {
           addDiagnosticInner(/*inferring=*/true, Effect, D, Loc, Callee);
         }
       }
     }
 
-    void addDiagnosticInner(bool Inferring, const FunctionEffect *Effect,
+    void addDiagnosticInner(bool Inferring, const FunctionEffect &Effect,
                             DiagnosticID D, SourceLocation Loc,
                             const Decl *Callee = nullptr) {
       CurrentFunction.checkAddDiagnostic(Inferring,
@@ -3264,25 +3265,25 @@ class Analyzer {
       auto *FPT =
           CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
 
-      auto check1Effect = [&](const FunctionEffect *effect, bool inferring) {
-        if (effect->flags() & FunctionEffect::FE_VerifyCalls) {
+      auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+        if (Effect.flags() & FunctionEffect::FE_VerifyCalls) {
           if (FPT == nullptr ||
-              effect->diagnoseFunctionCall(
+              Effect.diagnoseFunctionCall(
                   /*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
                   FPT, FPT->getFunctionEffects())) {
-            addDiagnosticInner(inferring, effect,
+            addDiagnosticInner(Inferring, Effect,
                                DiagnosticID::CallsDisallowedExpr,
                                Call->getBeginLoc());
           }
         }
       };
 
-      for (auto *effect : CurrentFunction.DeclaredVerifiableEffects) {
-        check1Effect(effect, false);
+      for (const auto &Effect : CurrentFunction.DeclaredVerifiableEffects) {
+        check1Effect(Effect, false);
       }
 
-      for (auto *effect : CurrentFunction.FXToInfer) {
-        check1Effect(effect, true);
+      for (const auto &Effect : CurrentFunction.FXToInfer) {
+        check1Effect(Effect, true);
       }
     }
 
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 8e96522542427a..05b771939feb13 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -592,12 +592,12 @@ void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
   const auto DstFX = FunctionEffectSet::get(*DstType);
   if (SrcFX != DstFX) {
     for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
-      const FunctionEffect *Effect = Item.first;
+      const FunctionEffect &Effect = Item.first;
       const bool Adding = Item.second;
-      if (Effect->diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
+      if (Effect.diagnoseConversion(Adding, SrcType, SrcFX, DstType, DstFX)) {
         Diag(Loc, Adding ? diag::warn_invalid_add_func_effects
                          : diag::warn_invalid_remove_func_effects)
-            << Effect->name();
+            << Effect.name();
       }
     }
   }
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index d860804102dfc9..1f167467cad6d1 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3927,17 +3927,17 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
   if (OldFX != NewFX) {
     const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
     for (const auto &Item : Diffs) {
-      const FunctionEffect *Effect = Item.first;
+      const FunctionEffect &Effect = Item.first;
       const bool Adding = Item.second;
-      if (Effect->diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
+      if (Effect.diagnoseRedeclaration(Adding, *Old, OldFX, *New, NewFX)) {
         Diag(New->getLocation(),
              diag::warn_mismatched_func_effect_redeclaration)
-            << Effect->name();
+            << Effect.name();
         Diag(Old->getLocation(), diag::note_previous_declaration);
       }
     }
 
-    const auto MergedFX = OldFX | NewFX;
+    const auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
 
     // Having diagnosed any problems, prevent further errors by applying the
     // merged set of effects to both declarations.
@@ -11165,8 +11165,8 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
   // Filter out declarations that the FunctionEffect analysis should skip
   // and not verify.
   bool FXNeedVerification = false;
-  for (const auto *Effect : FX) {
-    if (Effect->flags() & FunctionEffect::FE_RequiresVerification) {
+  for (const auto &Effect : FX) {
+    if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
       AllEffectsToVerify.insert(Effect);
       FXNeedVerification = true;
     }
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2f9a4a14155fb4..2c163ce4c14b03 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18330,11 +18330,11 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
     bool AnyDiags = false;
 
     for (const auto &Item : Diffs) {
-      const FunctionEffect *Effect = Item.first;
+      const FunctionEffect &Effect = Item.first;
       const bool Adding = Item.second;
-      if (Effect->diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+      if (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
         Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
-            << Effect->name();
+            << Effect.name();
         Diag(Old->getLocation(), diag::note_overridden_virtual_function);
         AnyDiags = true;
       }
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index a7849f42d38c13..0fcafeb0172fb4 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8028,23 +8028,20 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
 
   // nolock(true) and noalloc(true) are represented as FunctionEffects, in a
   // FunctionEffectSet attached to a FunctionProtoType.
-  const FunctionEffect *Effect = nullptr;
-  if (isNoLock) {
-    Effect = &NoLockNoAllocEffect::nolock_instance();
-  } else {
-    Effect = &NoLockNoAllocEffect::noalloc_instance();
-  }
+  const FunctionEffect NewEffect(isNoLock ? FunctionEffect::Type::NoLockTrue
+                                          : FunctionEffect::Type::NoAllocTrue);
 
-  MutableFunctionEffectSet newEffectSet{Effect};
+  MutableFunctionEffectSet NewFX(NewEffect);
   if (EPI.FunctionEffects) {
     // Preserve any previous effects - except noalloc, when we are adding nolock
-    for (const auto *effect : EPI.FunctionEffects) {
-      if (!(isNoLock && effect->type() == FunctionEffect::Type::NoAllocTrue))
-        newEffectSet.insert(effect);
+    for (const auto &Effect : EPI.FunctionEffects) {
+      if (!(isNoLock && Effect.type() == FunctionEffect::Type::NoAllocTrue))
+        NewFX.insert(Effect);
     }
   }
 
-  EPI.FunctionEffects = FunctionEffectSet::create(newEffectSet);
+  EPI.FunctionEffects =
+      FunctionEffectSet::create(state.getSema().getASTContext(), NewFX);
   QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
                                                FPT->getParamTypes(), EPI);
   type = unwrapped.wrap(S, newtype->getAs<FunctionType>());

>From 2a5c14fe4ed70b5aea3b808fbe55ffb7d8f8f8eb Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 16 Mar 2024 10:19:25 -0700
Subject: [PATCH 11/18] Remove unused parameters from FunctionEffect interface.

---
 clang/include/clang/AST/Type.h           | 30 +++++++++---------
 clang/lib/AST/Type.cpp                   | 35 +++++++--------------
 clang/lib/Sema/AnalysisBasedWarnings.cpp | 39 +++++++++++++-----------
 3 files changed, 48 insertions(+), 56 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 1d277d2ef06b19..4da97c88a2257d 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4192,11 +4192,12 @@ class FunctionNoProtoType : public FunctionType, public llvm::FoldingSetNode {
 class Decl;
 class CXXMethodDecl;
 class FunctionEffectSet;
+class TypeSourceInfo;
 
 /// Represents an abstract function effect.
 class FunctionEffect {
 public:
-  enum class Type : unsigned char {
+  enum class Type {
     None = 0,
     NoLockTrue,
     NoAllocTrue,
@@ -4231,24 +4232,24 @@ class FunctionEffect {
 private:
   // For uniqueness, currently only Type_ is significant.
 
-  Type Type_ : 2;   // Expands when there are more types
+  LLVM_PREFERRED_TYPE(Type)
+  unsigned Type_ : 2;
   Flags Flags_ : 8; // A constant function of Type but cached here.
 
-  // Expansion: for hypothetical TCB+types, there could be one type for TCB,
+  // Expansion: for hypothetical TCB+types, there could be one Type for TCB,
   // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
   // be considered for uniqueness.
-  unsigned Unused : 22;
 
 public:
   using CalleeDeclOrType =
       llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
 
-  FunctionEffect() : Type_(Type::None), Flags_(0), Unused(0) {}
+  FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0) {}
 
   explicit FunctionEffect(Type T);
 
   /// The type of the effect.
-  Type type() const { return Type_; }
+  Type type() const { return Type(Type_); }
 
   /// Flags describing behaviors of the effect.
   Flags flags() const { return Flags_; }
@@ -4257,7 +4258,7 @@ class FunctionEffect {
   StringRef name() const;
 
   /// A serializable, hashable representation.
-  uint32_t opaqueRepr() const { return unsigned(Type_) | (Flags_ << 2u); }
+  uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
 
   /// Return true if adding or removing the effect as part of a type conversion
   /// should generate a diagnostic.
@@ -4279,19 +4280,18 @@ class FunctionEffect {
                               const CXXMethodDecl &NewMethod,
                               FunctionEffectSet NewFX) const;
 
-  /// Return true if the effect is allowed to be inferred on the specified Decl
-  /// (may be a FunctionDecl or BlockDecl). Only used if the effect has
-  /// FE_InferrableOnCallees flag set. Example: This allows nolock(false) to
-  /// prevent inference for the function.
-  bool canInferOnDecl(const Decl *Caller, FunctionEffectSet CallerFX) const;
+  /// Return true if the effect is allowed to be inferred on a Decl of the
+  /// specified type (generally a FunctionProtoType but TypeSourceInfo is
+  /// provided so any AttributedType sugar can be examined). Only used if the
+  /// effect has FE_InferrableOnCallees flag set. Example: This allows
+  /// nolock(false) to prevent inference for the function.
+  bool canInferOnFunction(const TypeSourceInfo &FType) const;
 
   // Called if FE_VerifyCalls flag is set; return false for success. When true
   // is returned for a direct call, then the FE_InferrableOnCallees flag may
   // trigger inference rather than an immediate diagnostic. Caller should be
   // assumed to have the effect (it may not have it explicitly when inferring).
-  bool diagnoseFunctionCall(bool Direct, const Decl *Caller,
-                            FunctionEffectSet CallerFX, CalleeDeclOrType Callee,
-                            FunctionEffectSet CalleeFX) const;
+  bool diagnoseFunctionCall(bool Direct, FunctionEffectSet CalleeFX) const;
 
   friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
     return LHS.Type_ == RHS.Type_;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 8a9922594e9f98..e459833bb05150 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4922,7 +4922,7 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
           getTypeConstraintConcept(), getTypeConstraintArguments());
 }
 
-FunctionEffect::FunctionEffect(Type T) : Type_(T), Flags_(0), Unused(0) {
+FunctionEffect::FunctionEffect(Type T) : Type_(unsigned(T)), Flags_(0) {
   switch (T) {
   case Type::NoLockTrue:
     Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
@@ -4942,7 +4942,7 @@ FunctionEffect::FunctionEffect(Type T) : Type_(T), Flags_(0), Unused(0) {
 }
 
 StringRef FunctionEffect::name() const {
-  switch (Type_) {
+  switch (type()) {
   default:
     return "";
   case Type::NoLockTrue:
@@ -4957,7 +4957,7 @@ bool FunctionEffect::diagnoseConversion(bool Adding, QualType OldType,
                                         QualType NewType,
                                         FunctionEffectSet NewFX) const {
 
-  switch (Type_) {
+  switch (type()) {
   case Type::NoAllocTrue:
     // noalloc can't be added (spoofed) during a conversion, unless we have
     // nolock
@@ -4982,7 +4982,7 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
                                            FunctionEffectSet OldFX,
                                            const FunctionDecl &NewFunction,
                                            FunctionEffectSet NewFX) const {
-  switch (Type_) {
+  switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue:
     // nolock/noalloc can't be removed in a redeclaration
@@ -4999,7 +4999,7 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
                                             FunctionEffectSet OldFX,
                                             const CXXMethodDecl &NewMethod,
                                             FunctionEffectSet NewFX) const {
-  switch (Type_) {
+  switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue:
     // nolock/noalloc can't be removed from an override
@@ -5011,21 +5011,12 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
   return false;
 }
 
-bool FunctionEffect::canInferOnDecl(const Decl *Caller,
-                                    FunctionEffectSet CallerFX) const {
-  switch (Type_) {
+bool FunctionEffect::canInferOnFunction(const TypeSourceInfo &FType) const {
+  switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue: {
-    // Does the Decl have nolock(false) / noalloc(false) ?
-    QualType QT;
-    if (isa<BlockDecl>(Caller)) {
-      const auto *TSI = cast<BlockDecl>(Caller)->getSignatureAsWritten();
-      QT = TSI->getType();
-    } else if (isa<ValueDecl>(Caller)) {
-      QT = cast<ValueDecl>(Caller)->getType();
-    } else {
-      return false;
-    }
+    // Does the sugar have nolock(false) / noalloc(false) ?
+    QualType QT = FType.getType();
     if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
                                                : attr::Kind::NoAlloc)) {
       return false;
@@ -5040,13 +5031,9 @@ bool FunctionEffect::canInferOnDecl(const Decl *Caller,
   return false;
 }
 
-// TODO: Notice that we don't care about some of the parameters. Is the
-// interface overly general?
-bool FunctionEffect::diagnoseFunctionCall(bool Direct, const Decl *Caller,
-                                          FunctionEffectSet CallerFX,
-                                          CalleeDeclOrType Callee,
+bool FunctionEffect::diagnoseFunctionCall(bool Direct,
                                           FunctionEffectSet CalleeFX) const {
-  switch (Type_) {
+  switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue: {
     const Type CallerType = type();
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index ce7b91d4a5e59e..68d8133254fb03 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2596,21 +2596,28 @@ class PendingFunctionAnalysis {
   std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
 
 public:
-  PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &cinfo,
+  PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &CInfo,
                           FunctionEffectSet AllInferrableEffectsToVerify) {
-    MutableFunctionEffectSet fx;
-    for (const auto &effect : cinfo.Effects) {
-      if (effect.flags() & FunctionEffect::FE_RequiresVerification) {
-        fx.insert(effect);
+    MutableFunctionEffectSet FX;
+    for (const auto &Effect : CInfo.Effects) {
+      if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
+        FX.insert(Effect);
       }
     }
-    DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, fx);
+    DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, FX);
 
     // Check for effects we are not allowed to infer
-    fx.clear();
+    FX.clear();
+    TypeSourceInfo *TSI = nullptr;
+    if (const auto *DD = dyn_cast<DeclaratorDecl>(CInfo.CDecl)) {
+      TSI = DD->getTypeSourceInfo();
+    } else if (const auto *BD = dyn_cast<BlockDecl>(CInfo.CDecl)) {
+      TSI = BD->getSignatureAsWritten();
+    }
+
     for (const auto &effect : AllInferrableEffectsToVerify) {
-      if (effect.canInferOnDecl(cinfo.CDecl, cinfo.Effects)) {
-        fx.insert(effect);
+      if (TSI && effect.canInferOnFunction(*TSI)) {
+        FX.insert(effect);
       } else {
         // Add a diagnostic for this effect if a caller were to
         // try to infer it.
@@ -2618,12 +2625,12 @@ class PendingFunctionAnalysis {
             InferrableEffectToFirstDiagnostic.getOrInsertDefault(effect);
         diag =
             Diagnostic(effect, DiagnosticID::DeclWithoutConstraintOrInference,
-                       cinfo.CDecl->getLocation());
+                       CInfo.CDecl->getLocation());
       }
     }
-    // fx is now the set of inferrable effects which are not prohibited
+    // FX is now the set of inferrable effects which are not prohibited
     FXToInfer = FunctionEffectSet::create(
-        Ctx, FunctionEffectSet::create(Ctx, fx) - DeclaredVerifiableEffects);
+        Ctx, FunctionEffectSet::create(Ctx, FX) - DeclaredVerifiableEffects);
   }
 
   // Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2968,9 +2975,8 @@ class Analyzer {
     auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
       const auto Flags = Effect.flags();
       if (Flags & FunctionEffect::FE_VerifyCalls) {
-        const bool diagnose = Effect.diagnoseFunctionCall(
-            DirectCall, Caller.CDecl, Caller.Effects, Callee.CDecl,
-            CalleeEffects);
+        const bool diagnose =
+            Effect.diagnoseFunctionCall(DirectCall, CalleeEffects);
         if (diagnose) {
           // If inference is not allowed, or the target is indirect (virtual
           // method/function ptr?), generate a diagnostic now.
@@ -3269,8 +3275,7 @@ class Analyzer {
         if (Effect.flags() & FunctionEffect::FE_VerifyCalls) {
           if (FPT == nullptr ||
               Effect.diagnoseFunctionCall(
-                  /*direct=*/false, CurrentCaller.CDecl, CurrentCaller.Effects,
-                  FPT, FPT->getFunctionEffects())) {
+                  /*direct=*/false, FPT->getFunctionEffects())) {
             addDiagnosticInner(Inferring, Effect,
                                DiagnosticID::CallsDisallowedExpr,
                                Call->getBeginLoc());

>From ad39599ae7670b9f696ca711a13dc3ed341b7fdc Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 16 Mar 2024 15:57:48 -0700
Subject: [PATCH 12/18] Don't merge effects back to Old on redeclaration

---
 clang/lib/Sema/SemaDecl.cpp         |  7 +++++--
 clang/test/Sema/attr-nolock-wip.cpp | 30 -----------------------------
 2 files changed, 5 insertions(+), 32 deletions(-)

diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 1f167467cad6d1..a9d256f3484704 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3951,10 +3951,13 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
       FD->setType(ModQT);
     };
 
-    applyMergedFX(Old);
+    // TODO: We used to apply the merged set of effects to the old decl.
+    // Now we don't.
+
+    // applyMergedFX(Old);
     applyMergedFX(New);
 
-    OldQType = Old->getType();
+    // OldQType = Old->getType();
     NewQType = New->getType();
   }
 
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 16ff464fb8aa08..7fbe75e15e9c4f 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -1,5 +1,4 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -verify %s
-// R UN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
 
 #if !__has_attribute(clang_nolock)
 #error "the 'nolock' attribute is not available"
@@ -7,35 +6,6 @@
 
 // ============================================================================
 
-#if 1 // C function type problems
-void unannotated();
-void nolock() [[clang::nolock]];
-void noalloc() [[clang::noalloc]];
-
-
-void type_conversions()
-{
-	// It's fine to remove a performance constraint.
-	void (*fp_plain)();
-
-	fp_plain = unannotated;
-	fp_plain = nolock;
-	fp_plain = noalloc;
-
-	// Adding/spoofing nolock is unsafe.
-	void (*fp_nolock)() [[clang::nolock]];
-	fp_nolock = nolock;
-	fp_nolock = unannotated; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-	fp_nolock = noalloc; // expected-warning {{attribute 'nolock' should not be added via type conversion}}
-
-	// Adding/spoofing noalloc is unsafe.
-	void (*fp_noalloc)() [[clang::noalloc]];
-	fp_noalloc = noalloc;
-	fp_noalloc = nolock; // no warning because nolock includes noalloc fp_noalloc = unannotated;
-	fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
-}
-#endif
-
 // ============================================================================
 
 #if 0

>From 513c88ca9e2b9b87c269ffa6a3f716daeda49cf0 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 17 Mar 2024 10:46:26 -0700
Subject: [PATCH 13/18] Fix: canInferOnFunction can't always get a
 TypeSourceInfo. Fix test involving redeclarations. Turn off debug logging
 spew.

---
 clang/include/clang/AST/Type.h           | 10 ++--
 clang/lib/AST/Type.cpp                   |  3 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp | 67 +++++++++++++++---------
 clang/test/Sema/attr-nolock-sema.cpp     | 17 ++++--
 4 files changed, 61 insertions(+), 36 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 4da97c88a2257d..2b559b1cc4372c 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4282,10 +4282,12 @@ class FunctionEffect {
 
   /// Return true if the effect is allowed to be inferred on a Decl of the
   /// specified type (generally a FunctionProtoType but TypeSourceInfo is
-  /// provided so any AttributedType sugar can be examined). Only used if the
-  /// effect has FE_InferrableOnCallees flag set. Example: This allows
-  /// nolock(false) to prevent inference for the function.
-  bool canInferOnFunction(const TypeSourceInfo &FType) const;
+  /// provided so any AttributedType sugar can be examined). TSI can be null
+  /// on an implicit function like a default constructor.
+  ///
+  /// This is only used if the effect has FE_InferrableOnCallees flag set. 
+  /// Example: This allows nolock(false) to prevent inference for the function.
+  bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
 
   // Called if FE_VerifyCalls flag is set; return false for success. When true
   // is returned for a direct call, then the FE_InferrableOnCallees flag may
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index e459833bb05150..98983cc192d759 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5011,12 +5011,11 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
   return false;
 }
 
-bool FunctionEffect::canInferOnFunction(const TypeSourceInfo &FType) const {
+bool FunctionEffect::canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const {
   switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue: {
     // Does the sugar have nolock(false) / noalloc(false) ?
-    QualType QT = FType.getType();
     if (QT->hasAttr(type() == Type::NoLockTrue ? attr::Kind::NoLock
                                                : attr::Kind::NoAlloc)) {
       return false;
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 68d8133254fb03..74b6c7e5c99781 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2451,6 +2451,7 @@ struct CallableInfo {
   SpecialFuncType FuncType = SpecialFuncType::None;
   FunctionEffectSet Effects;
   CallType CType = CallType::Unknown;
+  QualType QT;
 
   CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
       : CDecl(&CD), FuncType(FT) {
@@ -2468,13 +2469,16 @@ struct CallableInfo {
           CType = CallType::Virtual;
         }
       }
+      QT = FD->getType();
       Effects = FD->getFunctionEffects();
     } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
       CType = CallType::Block;
+      QT = BD->getSignatureAsWritten()->getType();
       Effects = BD->getFunctionEffects();
     } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
       // ValueDecl is function, enum, or variable, so just look at the type.
-      Effects = FunctionEffectSet::get(*VD->getType());
+      QT = VD->getType();
+      Effects = FunctionEffectSet::get(*QT);
     }
   }
 
@@ -2596,8 +2600,9 @@ class PendingFunctionAnalysis {
   std::unique_ptr<SmallVector<DirectCall>> UnverifiedDirectCalls;
 
 public:
-  PendingFunctionAnalysis(ASTContext &Ctx, const CallableInfo &CInfo,
+  PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
                           FunctionEffectSet AllInferrableEffectsToVerify) {
+    ASTContext &Ctx = Sem.getASTContext();
     MutableFunctionEffectSet FX;
     for (const auto &Effect : CInfo.Effects) {
       if (Effect.flags() & FunctionEffect::FE_RequiresVerification) {
@@ -2614,9 +2619,11 @@ class PendingFunctionAnalysis {
     } else if (const auto *BD = dyn_cast<BlockDecl>(CInfo.CDecl)) {
       TSI = BD->getSignatureAsWritten();
     }
+    // N.B. TSI can be null for things like an implicit constructor (despite
+    // having a valid QualifiedType).
 
     for (const auto &effect : AllInferrableEffectsToVerify) {
-      if (TSI && effect.canInferOnFunction(*TSI)) {
+      if (effect.canInferOnFunction(CInfo.QT, TSI)) {
         FX.insert(effect);
       } else {
         // Add a diagnostic for this effect if a caller were to
@@ -2745,7 +2752,7 @@ class CompleteFunctionAnalysis {
 
 // ==========
 class Analyzer {
-  constexpr static int kDebugLogLevel = 3;
+  constexpr static int DebugLogLevel = 0;
 
   // --
   Sema &Sem;
@@ -2820,9 +2827,11 @@ class Analyzer {
       }
       AllInferrableEffectsToVerify =
           FunctionEffectSet::create(Sem.getASTContext(), inferrableEffects);
-      llvm::outs() << "AllInferrableEffectsToVerify: ";
-      AllInferrableEffectsToVerify.dump(llvm::outs());
-      llvm::outs() << "\n";
+      if constexpr (DebugLogLevel > 0) {
+        llvm::outs() << "AllInferrableEffectsToVerify: ";
+        AllInferrableEffectsToVerify.dump(llvm::outs());
+        llvm::outs() << "\n";
+      }
     }
 
     SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
@@ -2893,11 +2902,13 @@ class Analyzer {
     // Build a PendingFunctionAnalysis on the stack. If it turns out to be
     // complete, we'll have avoided a heap allocation; if it's incomplete, it's
     // a fairly trivial move to a heap-allocated object.
-    PendingFunctionAnalysis FAnalysis(Sem.getASTContext(), CInfo,
+    PendingFunctionAnalysis FAnalysis(Sem, CInfo,
                                       AllInferrableEffectsToVerify);
 
-    llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
-    FAnalysis.dump(llvm::outs());
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
+      FAnalysis.dump(llvm::outs());
+    }
 
     FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
 
@@ -2909,8 +2920,10 @@ class Analyzer {
     // Copy the pending analysis to the heap and save it in the map.
     auto *PendingPtr = new PendingFunctionAnalysis(std::move(FAnalysis));
     DeclAnalysis[D] = PendingPtr;
-    llvm::outs() << "inserted pending " << PendingPtr << "\n";
-    DeclAnalysis.dump(Sem, llvm::outs());
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "inserted pending " << PendingPtr << "\n";
+      DeclAnalysis.dump(Sem, llvm::outs());
+    }
     return PendingPtr;
   }
 
@@ -2925,8 +2938,10 @@ class Analyzer {
         Sem.getASTContext(), Pending, CInfo.Effects,
         AllInferrableEffectsToVerify);
     DeclAnalysis[CInfo.CDecl] = CompletePtr;
-    llvm::outs() << "inserted complete " << CompletePtr << "\n";
-    DeclAnalysis.dump(Sem, llvm::outs());
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "inserted complete " << CompletePtr << "\n";
+      DeclAnalysis.dump(Sem, llvm::outs());
+    }
   }
 
   // Called after all direct calls requiring inference have been found -- or
@@ -2941,7 +2956,6 @@ class Analyzer {
     }
     completeAnalysis(Caller, *Pending);
     delete Pending;
-    llvm::outs() << "destroyed pending " << Pending << "\n";
   }
 
   // Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -2965,12 +2979,13 @@ class Analyzer {
     if (!Callee.isVerifiable()) {
       IsInferencePossible = false;
     }
-    llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
-                 << Callee.name(Sem)
-                 << "; verifiable: " << Callee.isVerifiable() << "; callee ";
-    CalleeEffects.dump(llvm::outs());
-    llvm::outs() << "\n";
-    puts("");
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
+                  << Callee.name(Sem)
+                  << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+      CalleeEffects.dump(llvm::outs());
+      llvm::outs() << "\n";
+    }
 
     auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
       const auto Flags = Effect.flags();
@@ -3359,7 +3374,7 @@ class Analyzer {
     }
 
     bool VisitCallExpr(CallExpr *Call) {
-      if constexpr (kDebugLogLevel > 2) {
+      if constexpr (DebugLogLevel > 2) {
         llvm::errs() << "VisitCallExpr : "
                      << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
                      << "\n";
@@ -3393,7 +3408,7 @@ class Analyzer {
     }
 
     bool VisitVarDecl(VarDecl *Var) {
-      if constexpr (kDebugLogLevel > 2) {
+      if constexpr (DebugLogLevel > 2) {
         llvm::errs() << "VisitVarDecl : "
                      << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
                      << "\n";
@@ -3452,7 +3467,7 @@ class Analyzer {
     }
 
     bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
-      if constexpr (kDebugLogLevel > 2) {
+      if constexpr (DebugLogLevel > 2) {
         llvm::errs() << "VisitCXXConstructExpr : "
                      << Construct->getBeginLoc().printToString(
                             Outer.Sem.SourceMgr)
@@ -3591,8 +3606,8 @@ class Analyzer {
 
     CallableFinderCallback::get(Sem, TU);
 
-    /*if (decls.size() != Sem.DeclsWithUnverifiedEffects.size())*/ {
-      llvm::errs() << "\nFXAnalysis: Sem gathered " << decls.size()
+    if constexpr (DebugLogLevel > 0) {
+      llvm::errs() << "\nFXAnalysis: Sema gathered " << decls.size()
                    << " Decls; second AST pass found "
                    << Sem.DeclsWithUnverifiedEffects.size() << "\n";
     }
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 99f6242e4747c9..b0b720792d36a4 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -75,13 +75,22 @@ struct Derived : public Base {
 #endif // __cplusplus
 
 // --- REDECLARATIONS ---
+// TODO: These are now errors in C++, but not in C
 
+#ifdef __cplusplus
+int f2(int); // expected-note {{previous declaration is here}}
+int f2(int) [[clang::nolock]]; // expected-error {{conflicting types for 'f2'}}
+#endif // __cplusplus
+
+
+#if 0
 int f2();
 // redeclaration with a stronger constraint is OK.
-int f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
-int f2() { return 42; } // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+int f2() [[clang::nolock]]; // e xpected-note {{previous declaration is here}}
+int f2() { return 42; } // e xpected-warning {{attribute 'nolock' on function does not match previous declaration}}
 
 int f3();
 // redeclaration with a stronger constraint is OK.
-int f3() [[clang::noalloc]]; // expected-note {{previous declaration is here}}
-int f3() { return 42; } // expected-warning {{attribute 'noalloc' on function does not match previous declaration}}
+int f3() [[clang::noalloc]]; // e xpected-note {{previous declaration is here}}
+int f3() { return 42; } // e xpected-warning {{attribute 'noalloc' on function does not match previous declaration}}
+#endif

>From 13254292d0e3e97d4ee16989d50fa0dacbcb8800 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 19 Mar 2024 08:45:14 -0700
Subject: [PATCH 14/18] Cleanup of redeclarations (Sema::MergeFunctionDecl) so
 that the type checks never fail. FunctionDecl::getFunctionEffects() now
 returns the union of effects present across all redeclarations.

---
 clang/include/clang/AST/Decl.h              |  7 +---
 clang/include/clang/AST/Type.h              |  2 +-
 clang/lib/AST/Decl.cpp                      | 12 ++++++
 clang/lib/AST/Type.cpp                      |  3 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp    | 14 ++++---
 clang/lib/Sema/SemaDecl.cpp                 | 42 ++++++++++-----------
 clang/test/Sema/attr-nolock-constraints.cpp |  7 ++++
 clang/test/Sema/attr-nolock-sema.cpp        | 26 +++++++++++--
 clang/test/Sema/attr-nolock-wip.cpp         |  4 ++
 9 files changed, 78 insertions(+), 39 deletions(-)

diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 0460f30ce8a8b4..acff3dee7051dc 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -3008,12 +3008,7 @@ class FunctionDecl : public DeclaratorDecl,
   /// computed and stored.
   unsigned getODRHash() const;
 
-  FunctionEffectSet getFunctionEffects() const {
-    const auto *FPT = getType()->getAs<FunctionProtoType>();
-    if (FPT)
-      return FPT->getFunctionEffects();
-    return {};
-  }
+  FunctionEffectSet getFunctionEffects() const;
 
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 2b559b1cc4372c..298029cef7195f 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4285,7 +4285,7 @@ class FunctionEffect {
   /// provided so any AttributedType sugar can be examined). TSI can be null
   /// on an implicit function like a default constructor.
   ///
-  /// This is only used if the effect has FE_InferrableOnCallees flag set. 
+  /// This is only used if the effect has FE_InferrableOnCallees flag set.
   /// Example: This allows nolock(false) to prevent inference for the function.
   bool canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const;
 
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 502c978d5b78e4..84c950bdafc1c3 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4502,6 +4502,18 @@ unsigned FunctionDecl::getODRHash() {
   return ODRHash;
 }
 
+// Effects may differ between redeclarations, so collect all effects from
+// all redeclarations.
+FunctionEffectSet FunctionDecl::getFunctionEffects() const {
+  MutableFunctionEffectSet FX;
+  for (FunctionDecl *FD : redecls()) {
+    if (const auto *FPT = FD->getType()->getAs<FunctionProtoType>()) {
+      FX |= FPT->getFunctionEffects();
+    }
+  }
+  return FunctionEffectSet::create(getASTContext(), FX);
+}
+
 //===----------------------------------------------------------------------===//
 // FieldDecl Implementation
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 98983cc192d759..bea2272c0cb5f9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5011,7 +5011,8 @@ bool FunctionEffect::diagnoseMethodOverride(bool Adding,
   return false;
 }
 
-bool FunctionEffect::canInferOnFunction(QualType QT, const TypeSourceInfo *FType) const {
+bool FunctionEffect::canInferOnFunction(QualType QT,
+                                        const TypeSourceInfo *FType) const {
   switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue: {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 74b6c7e5c99781..318e87eb3c57b9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2458,10 +2458,13 @@ struct CallableInfo {
     // llvm::errs() << "CallableInfo " << name() << "\n";
 
     if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
-      assert(FD->getCanonicalDecl() == FD);
       // Use the function's definition, if any.
       if (auto *Def = FD->getDefinition()) {
         CDecl = FD = Def;
+        // is the definition always canonical?
+        assert(FD->getCanonicalDecl() == FD);
+      } else {
+        FD = FD->getCanonicalDecl();
       }
       CType = CallType::Function;
       if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
@@ -2902,8 +2905,7 @@ class Analyzer {
     // Build a PendingFunctionAnalysis on the stack. If it turns out to be
     // complete, we'll have avoided a heap allocation; if it's incomplete, it's
     // a fairly trivial move to a heap-allocated object.
-    PendingFunctionAnalysis FAnalysis(Sem, CInfo,
-                                      AllInferrableEffectsToVerify);
+    PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
 
     if constexpr (DebugLogLevel > 0) {
       llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
@@ -2981,8 +2983,8 @@ class Analyzer {
     }
     if constexpr (DebugLogLevel > 0) {
       llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
-                  << Callee.name(Sem)
-                  << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+                   << Callee.name(Sem)
+                   << "; verifiable: " << Callee.isVerifiable() << "; callee ";
       CalleeEffects.dump(llvm::outs());
       llvm::outs() << "\n";
     }
@@ -3161,7 +3163,7 @@ class Analyzer {
           case DiagnosticID::HasStaticLocal:
             S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
                 << effectName << CalleeName;
-            UNTESTED
+            TESTED
             break;
           case DiagnosticID::AccessesThreadLocal:
             S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a9d256f3484704..5dc92ea98554a6 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3924,6 +3924,7 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
 
   const auto OldFX = Old->getFunctionEffects();
   const auto NewFX = New->getFunctionEffects();
+  QualType OldQTypeForComparison = OldQType;
   if (OldFX != NewFX) {
     const auto Diffs = FunctionEffectSet::differences(OldFX, NewFX);
     for (const auto &Item : Diffs) {
@@ -3936,29 +3937,29 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
         Diag(Old->getLocation(), diag::note_previous_declaration);
       }
     }
+    // Following a warning, we could skip merging effects from the previous
+    // declaration, but that would trigger an additional "conflicting types"
+    // error.
+    if (const auto *NewFPT = NewQType->getAs<FunctionProtoType>()) {
+      auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
 
-    const auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
-
-    // Having diagnosed any problems, prevent further errors by applying the
-    // merged set of effects to both declarations.
-    auto applyMergedFX = [&](FunctionDecl *FD) {
-      const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
-      FunctionProtoType::ExtProtoInfo EPI = FPT->getExtProtoInfo();
+      FunctionProtoType::ExtProtoInfo EPI = NewFPT->getExtProtoInfo();
       EPI.FunctionEffects = MergedFX;
-      QualType ModQT = Context.getFunctionType(FD->getReturnType(),
-                                               FPT->getParamTypes(), EPI);
-
-      FD->setType(ModQT);
-    };
+      QualType ModQT = Context.getFunctionType(NewFPT->getReturnType(),
+                                               NewFPT->getParamTypes(), EPI);
 
-    // TODO: We used to apply the merged set of effects to the old decl.
-    // Now we don't.
+      New->setType(ModQT);
+      NewQType = New->getType();
 
-    // applyMergedFX(Old);
-    applyMergedFX(New);
-
-    // OldQType = Old->getType();
-    NewQType = New->getType();
+      // Revise OldQTForComparison to include the merged effects,
+      // so as not to fail due to differences later.
+      if (const auto *OldFPT = OldQType->getAs<FunctionProtoType>()) {
+        EPI = OldFPT->getExtProtoInfo();
+        EPI.FunctionEffects = MergedFX;
+        OldQTypeForComparison = Context.getFunctionType(
+            OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
+      }
+    }
   }
 
   if (getLangOpts().CPlusPlus) {
@@ -4125,9 +4126,8 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
     // We also want to respect all the extended bits except noreturn.
 
     // noreturn should now match unless the old type info didn't have it.
-    QualType OldQTypeForComparison = OldQType;
     if (!OldTypeInfo.getNoReturn() && NewTypeInfo.getNoReturn()) {
-      auto *OldType = OldQType->castAs<FunctionProtoType>();
+      auto *OldType = OldQTypeForComparison->castAs<FunctionProtoType>();
       const FunctionType *OldTypeForComparison
         = Context.adjustFunctionType(OldType, OldTypeInfo.withNoReturn(true));
       OldQTypeForComparison = QualType(OldTypeForComparison, 0);
diff --git a/clang/test/Sema/attr-nolock-constraints.cpp b/clang/test/Sema/attr-nolock-constraints.cpp
index 801f971b410865..5d1efe931a1f91 100644
--- a/clang/test/Sema/attr-nolock-constraints.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.cpp
@@ -139,3 +139,10 @@ void nl11() [[clang::nolock]]
 {
 	nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
 }
+
+// Verify that when attached to a redeclaration, the attribute successfully attaches.
+void nl12() {
+	static int x; // expected-warning {{'nolock' function 'nl12' must not have static locals}}
+}
+void nl12() [[clang::nolock]];
+void nl13() [[clang::nolock]] { nl12(); }
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index b0b720792d36a4..28542be0a2ecd0 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -75,13 +75,31 @@ struct Derived : public Base {
 #endif // __cplusplus
 
 // --- REDECLARATIONS ---
-// TODO: These are now errors in C++, but not in C
 
 #ifdef __cplusplus
-int f2(int); // expected-note {{previous declaration is here}}
-int f2(int) [[clang::nolock]]; // expected-error {{conflicting types for 'f2'}}
-#endif // __cplusplus
+// In C++, the third declaration gets seen as a redeclaration of the second.
+void f2();
+void f2() [[clang::nolock]]; // expected-note {{previous declaration is here}}
+void f2(); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+#else
+// In C, the third declaration is redeclaration of the first (?).
+void f2();
+void f2() [[clang::nolock]];
+void f2();
+#endif
+// Note: we verify that the attribute is actually seen during the constraints tests.
+
+
+// Ensure that the redeclaration's attribute is seen and diagnosed correctly.
 
+// void f2() {
+// 	static int x;
+// }
+// void f2() [[clang::nolock]];
+// 
+// void f3() [[clang::nolock]] {
+// 	f2();
+// }
 
 #if 0
 int f2();
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index 7fbe75e15e9c4f..ab8cf57d69ed0c 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -6,6 +6,10 @@
 
 // ============================================================================
 
+void f2(int);
+void f2(int) [[clang::nolock]]; // expected-note {{previous declaration is here}}
+void f2(int); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+
 // ============================================================================
 
 #if 0

>From 5ac74293f4d7e610b60ac8f3bf45438cab840acf Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 19 Mar 2024 13:36:05 -0700
Subject: [PATCH 15/18] FunctionEffectSet::create() is now a method of
 ASTContext; the global uniquing set is now a member of ASTContext.

---
 clang/include/clang/AST/ASTContext.h     | 18 +++++++
 clang/include/clang/AST/Type.h           |  6 +--
 clang/lib/AST/ASTContext.cpp             | 68 ++++++++++++++++++++++++
 clang/lib/AST/Decl.cpp                   |  2 +-
 clang/lib/AST/Type.cpp                   | 67 +----------------------
 clang/lib/Sema/AnalysisBasedWarnings.cpp | 10 ++--
 clang/lib/Sema/SemaType.cpp              |  2 +-
 7 files changed, 96 insertions(+), 77 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index ff6b64c7f72d57..8e57d40922440d 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -69,6 +69,8 @@ class DiagnosticsEngine;
 class DynTypedNodeList;
 class Expr;
 enum class FloatModeKind;
+class FunctionEffect;
+class FunctionEffectSet;
 class GlobalDecl;
 class IdentifierTable;
 class LangOptions;
@@ -459,6 +461,16 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// This is the top-level (C++20) Named module we are building.
   Module *CurrentCXXNamedModule = nullptr;
 
+  class FunctionEffectSetUniquing {
+    llvm::DenseSet<llvm::ArrayRef<const FunctionEffect>> Set;
+
+  public:
+    FunctionEffectSet getUniqued(llvm::ArrayRef<const FunctionEffect> FX);
+
+    ~FunctionEffectSetUniquing();
+  };
+  FunctionEffectSetUniquing UniquedFunctionEffectSet;
+
   static constexpr unsigned ConstantArrayTypesLog2InitSize = 8;
   static constexpr unsigned GeneralTypesLog2InitSize = 9;
   static constexpr unsigned FunctionProtoTypesLog2InitSize = 12;
@@ -1065,6 +1077,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// Get module under construction, nullptr if this is not a C++20 module.
   Module *getCurrentNamedModule() const { return CurrentCXXNamedModule; }
 
+  /// Get or create a uniqued, immutable FunctionEffectSet.
+  FunctionEffectSet
+  getUniquedFunctionEffectSet(llvm::ArrayRef<const FunctionEffect> FX) {
+    return UniquedFunctionEffectSet.getUniqued(FX);
+  }
+
   TranslationUnitDecl *getTranslationUnitDecl() const {
     return TUDecl->getMostRecentDecl();
   }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 298029cef7195f..508736ebe4ea05 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4333,6 +4333,8 @@ class MutableFunctionEffectSet : public SmallVector<FunctionEffect, 4> {
 // sorted spans, which are compatible with the STL set algorithms.
 class FunctionEffectSet {
 private:
+  friend class ASTContext; // so it can call the private constructor
+
   explicit FunctionEffectSet(llvm::ArrayRef<const FunctionEffect> Array)
       : Impl(Array) {}
 
@@ -4368,10 +4370,6 @@ class FunctionEffectSet {
 
   void dump(llvm::raw_ostream &OS) const;
 
-  /// Factory function: returns instances with uniqued implementations.
-  static FunctionEffectSet create(ASTContext &C,
-                                  const MutableFunctionEffectSet &FX);
-
   /// Intersection.
   MutableFunctionEffectSet operator&(const FunctionEffectSet &RHS) const;
   /// Difference.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 3e2142469babd5..6dd511be275c35 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13702,3 +13702,71 @@ StringRef ASTContext::getCUIDHash() const {
   CUIDHash = llvm::utohexstr(llvm::MD5Hash(LangOpts.CUID), /*LowerCase=*/true);
   return CUIDHash;
 }
+
+using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
+
+llvm::hash_code hash_value(const FunctionEffect &Effect) {
+  return Effect.opaqueRepr();
+}
+
+namespace llvm {
+template <> struct DenseMapInfo<FunctionEffectSpan> {
+  static FunctionEffectSpan getEmptyKey() {
+    return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
+  }
+  static FunctionEffectSpan getTombstoneKey() {
+    return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
+  }
+  static unsigned getHashValue(const FunctionEffectSpan &Val) {
+    hash_code hash1 = hash_value(Val.size());
+    // Treat the FunctionEffects as a span of integers
+    const FunctionEffect *Begin = Val.begin();
+    const FunctionEffect *End = Val.end();
+    hash_code hash2 =
+        hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
+                           reinterpret_cast<const uint32_t *>(End));
+    return hash_combine(hash1, hash2);
+  }
+  static bool isEqual(const FunctionEffectSpan &LHS,
+                      const FunctionEffectSpan &RHS) {
+    if (LHS.size() != RHS.size()) {
+      return false;
+    }
+    // distinguish empty from tombstone
+    if (LHS.size() == 0) {
+      return LHS.data() == RHS.data();
+    }
+    return std::equal(LHS.begin(), LHS.end(), RHS.begin());
+  }
+};
+} // namespace llvm
+
+FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
+    llvm::ArrayRef<const FunctionEffect> FX) {
+  if (FX.empty()) {
+    return {};
+  }
+
+  // Do we already have the incoming set?
+  const auto Iter = Set.find_as(FX);
+  if (Iter != Set.end()) {
+    return FunctionEffectSet(*Iter);
+  }
+
+  // Copy the incoming array to permanent storage.
+  FunctionEffect *Storage = new FunctionEffect[FX.size()];
+  std::copy(FX.begin(), FX.end(), Storage);
+
+  // Make a new wrapper and insert it into the set.
+  llvm::ArrayRef<const FunctionEffect> Arr(Storage, FX.size());
+  auto [InsIter, _] = Set.insert(Arr);
+  return FunctionEffectSet(*InsIter);
+}
+
+ASTContext::FunctionEffectSetUniquing::~FunctionEffectSetUniquing() {
+  for (const auto &ArrRef : Set) {
+    const FunctionEffect *ptrToFX = ArrRef.data();
+    delete[] ptrToFX;
+  }
+  Set.clear();
+}
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 84c950bdafc1c3..981b61931d2538 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4511,7 +4511,7 @@ FunctionEffectSet FunctionDecl::getFunctionEffects() const {
       FX |= FPT->getFunctionEffects();
     }
   }
-  return FunctionEffectSet::create(getASTContext(), FX);
+  return getASTContext().getUniquedFunctionEffectSet(FX);
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index bea2272c0cb5f9..06f9c0190bad6f 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5076,71 +5076,6 @@ MutableFunctionEffectSet::operator|=(FunctionEffectSet RHS) {
   return *this;
 }
 
-using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
-
-llvm::hash_code hash_value(const FunctionEffect &Effect) {
-  return Effect.opaqueRepr();
-}
-
-namespace llvm {
-template <> struct DenseMapInfo<FunctionEffectSpan> {
-  static FunctionEffectSpan getEmptyKey() {
-    return {static_cast<const FunctionEffect *>(nullptr), size_t(0)};
-  }
-  static FunctionEffectSpan getTombstoneKey() {
-    return {reinterpret_cast<const FunctionEffect *>(intptr_t(-1)), size_t(0)};
-  }
-  static unsigned getHashValue(const FunctionEffectSpan &Val) {
-    hash_code hash1 = hash_value(Val.size());
-    // Treat the FunctionEffects as a span of integers
-    const FunctionEffect *Begin = Val.begin();
-    const FunctionEffect *End = Val.end();
-    hash_code hash2 =
-        hash_combine_range(reinterpret_cast<const uint32_t *>(Begin),
-                           reinterpret_cast<const uint32_t *>(End));
-    return hash_combine(hash1, hash2);
-  }
-  static bool isEqual(const FunctionEffectSpan &LHS,
-                      const FunctionEffectSpan &RHS) {
-    if (LHS.size() != RHS.size()) {
-      return false;
-    }
-    // distinguish empty from tombstone
-    if (LHS.size() == 0) {
-      return LHS.data() == RHS.data();
-    }
-    return std::equal(LHS.begin(), LHS.end(), RHS.begin());
-  }
-};
-} // namespace llvm
-
-// The ASTContext
-FunctionEffectSet
-FunctionEffectSet::create(ASTContext &C, const MutableFunctionEffectSet &FX) {
-  if (FX.empty()) {
-    return {};
-  }
-
-  // TODO: Put this in the ASTContext
-  // TODO: And destroy the memory
-  static llvm::DenseSet<FunctionEffectSpan> UniquedFXSets;
-
-  // Do we already have the incoming set?
-  const auto Iter = UniquedFXSets.find_as(FX);
-  if (Iter != UniquedFXSets.end()) {
-    return FunctionEffectSet(*Iter);
-  }
-
-  // Copy the incoming array to permanent storage.
-  FunctionEffect *Storage = new FunctionEffect[FX.size()];
-  std::copy(FX.begin(), FX.end(), Storage);
-
-  // Make a new wrapper and insert it into the set.
-  auto [InsIter, _] =
-      UniquedFXSets.insert(FunctionEffectSpan(Storage, FX.size()));
-  return FunctionEffectSet(*InsIter);
-}
-
 FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
                                               const FunctionEffectSet &LHS,
                                               const FunctionEffectSet &RHS) {
@@ -5159,7 +5094,7 @@ FunctionEffectSet FunctionEffectSet::getUnion(ASTContext &C,
   std::set_union(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
                  std::back_inserter(Vec));
   // The result of a set operation is an ordered/unique set.
-  return FunctionEffectSet::create(C, Vec);
+  return C.getUniquedFunctionEffectSet(Vec);
 }
 
 MutableFunctionEffectSet
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 318e87eb3c57b9..47202980232bc6 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2612,7 +2612,7 @@ class PendingFunctionAnalysis {
         FX.insert(Effect);
       }
     }
-    DeclaredVerifiableEffects = FunctionEffectSet::create(Ctx, FX);
+    DeclaredVerifiableEffects = Ctx.getUniquedFunctionEffectSet(FX);
 
     // Check for effects we are not allowed to infer
     FX.clear();
@@ -2639,8 +2639,8 @@ class PendingFunctionAnalysis {
       }
     }
     // FX is now the set of inferrable effects which are not prohibited
-    FXToInfer = FunctionEffectSet::create(
-        Ctx, FunctionEffectSet::create(Ctx, FX) - DeclaredVerifiableEffects);
+    FXToInfer = Ctx.getUniquedFunctionEffectSet(
+        Ctx.getUniquedFunctionEffectSet(FX) - DeclaredVerifiableEffects);
   }
 
   // Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -2722,7 +2722,7 @@ class CompleteFunctionAnalysis {
         verified.insert(effect);
       }
     }
-    VerifiedEffects = FunctionEffectSet::create(Ctx, verified);
+    VerifiedEffects = Ctx.getUniquedFunctionEffectSet(verified);
 
     InferrableEffectToFirstDiagnostic =
         std::move(pending.InferrableEffectToFirstDiagnostic);
@@ -2829,7 +2829,7 @@ class Analyzer {
         }
       }
       AllInferrableEffectsToVerify =
-          FunctionEffectSet::create(Sem.getASTContext(), inferrableEffects);
+          Sem.getASTContext().getUniquedFunctionEffectSet(inferrableEffects);
       if constexpr (DebugLogLevel > 0) {
         llvm::outs() << "AllInferrableEffectsToVerify: ";
         AllInferrableEffectsToVerify.dump(llvm::outs());
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 0fcafeb0172fb4..174ce62b4f5d9c 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8041,7 +8041,7 @@ static bool handleNoLockNoAllocTypeAttr(TypeProcessingState &state,
   }
 
   EPI.FunctionEffects =
-      FunctionEffectSet::create(state.getSema().getASTContext(), NewFX);
+      state.getSema().getASTContext().getUniquedFunctionEffectSet(NewFX);
   QualType newtype = S.Context.getFunctionType(FPT->getReturnType(),
                                                FPT->getParamTypes(), EPI);
   type = unwrapped.wrap(S, newtype->getAs<FunctionType>());

>From 81b86dffabad166f37e93e8bec833b103b762c84 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 19 Mar 2024 14:08:05 -0700
Subject: [PATCH 16/18] - Serialize FunctionEffectSet as part of a
 FunctionProtoType. - Rename Sema's DeclsWithUnverifiedEffects ->
 DeclsWithEffectsToVerify. Read and write it in ASTReader/ASTWriter. How to
 test this? - AttrDocs.td - remove trailing whitespace. - Fix hash_value for
 FunctionEffect. - FunctionDecl::getFunctionEffects() can use
 getMostRecentDecl() instead of doing a set operation.

---
 clang/include/clang/AST/ASTContext.h          |  4 ++++
 clang/include/clang/AST/Type.h                |  5 ++++
 clang/include/clang/AST/TypeProperties.td     |  9 ++++----
 clang/include/clang/Basic/AttrDocs.td         |  2 +-
 clang/include/clang/Sema/Sema.h               |  6 ++---
 .../include/clang/Serialization/ASTBitCodes.h |  4 ++++
 clang/include/clang/Serialization/ASTReader.h |  3 +++
 clang/include/clang/Serialization/ASTWriter.h |  1 +
 clang/lib/AST/ASTContext.cpp                  |  9 +++++++-
 clang/lib/AST/Decl.cpp                        | 13 ++++-------
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 14 +++++------
 clang/lib/Sema/SemaDecl.cpp                   |  2 +-
 clang/lib/Serialization/ASTReader.cpp         | 23 +++++++++++++++++++
 clang/lib/Serialization/ASTWriter.cpp         | 12 ++++++++++
 14 files changed, 81 insertions(+), 26 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 8e57d40922440d..95bd2a901e9be9 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1083,6 +1083,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
     return UniquedFunctionEffectSet.getUniqued(FX);
   }
 
+  /// Get or create a uniqued, immutable FunctionEffectSet from a serialized
+  /// span of uint32_t's.
+  FunctionEffectSet getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX);
+
   TranslationUnitDecl *getTranslationUnitDecl() const {
     return TUDecl->getMostRecentDecl();
   }
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 508736ebe4ea05..ca4660edb2f56f 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4347,6 +4347,11 @@ class FunctionEffectSet {
   FunctionEffectSet() = default;
 
   const void *getOpaqueValue() const { return Impl.data(); }
+  llvm::ArrayRef<uint32_t> serializable() const {
+    static_assert(sizeof(FunctionEffect) == sizeof(uint32_t));
+    const uint32_t *ptr = reinterpret_cast<const uint32_t *>(Impl.data());
+    return {const_cast<uint32_t *>(ptr), Impl.size()};
+  }
 
   explicit operator bool() const { return !empty(); }
   bool empty() const { return Impl.empty(); }
diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td
index 1d5d8f696977e7..3121598761e44f 100644
--- a/clang/include/clang/AST/TypeProperties.td
+++ b/clang/include/clang/AST/TypeProperties.td
@@ -326,10 +326,9 @@ let Class = FunctionProtoType in {
   def : Property<"AArch64SMEAttributes", UInt32> {
     let Read = [{ node->getAArch64SMEAttributes() }];
   }
-  /* TODO: How to serialize FunctionEffect / FunctionEffectSet?
-  def : Property<"functionEffects", FunctionEffectSet> {
-    let Read = [{ node->getFunctionEffects() }];
-  }*/
+  def : Property<"functionEffects", Array<UInt32>> {
+    let Read = [{ node->getFunctionEffects().serializable() }];
+  }
 
   def : Creator<[{
     auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
@@ -346,7 +345,7 @@ let Class = FunctionProtoType in {
     epi.ExtParameterInfos =
       extParameterInfo.empty() ? nullptr : extParameterInfo.data();
     epi.AArch64SMEAttributes = AArch64SMEAttributes;
-    //epi.FunctionEffects = functionEffects;
+    epi.FunctionEffects = ctx.getUniquedFunctionEffectSet(functionEffects);
     return ctx.getFunctionType(returnType, parameters, epi);
   }]>;
 }
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index bb897c708ebbea..71a35f63ef106a 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7977,7 +7977,7 @@ requirement:
 def NoLockNoAllocDocs : Documentation {
   let Category = DocCatType;
   let Content = [{
-The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks, 
+The ``nolock`` and ``noalloc`` attributes can be attached to functions, blocks,
 function pointers, lambdas, and member functions. The attributes identify code
 which must not allocate memory or lock, and the compiler uses the attributes to
 verify these requirements.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 2772f882a5c09d..b341e2361a5558 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -723,8 +723,8 @@ class Sema final {
 
   /// All functions/lambdas/blocks which have bodies and which have a non-empty
   /// FunctionEffectSet to be verified.
-  SmallVector<const Decl *> DeclsWithUnverifiedEffects;
-  /// The union of all effects present on DeclsWithUnverifiedEffects.
+  SmallVector<const Decl *> DeclsWithEffectsToVerify;
+  /// The union of all effects present on DeclsWithEffectsToVerify.
   MutableFunctionEffectSet AllEffectsToVerify;
 
   /// Determine if VD, which must be a variable or function, is an external
@@ -3142,7 +3142,7 @@ class Sema final {
                               QualType T, TypeSourceInfo *TSInfo,
                               StorageClass SC);
 
-  /// Potentially add a FunctionDecl or BlockDecl to DeclsWithUnverifiedEffects.
+  /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
   void CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX);
 
   // Contexts where using non-trivial C union types can be disallowed. This is
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index f31efa5117f0d1..a2d620280e14ad 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -698,6 +698,10 @@ enum ASTRecordTypes {
   /// Record code for an unterminated \#pragma clang assume_nonnull begin
   /// recorded in a preamble.
   PP_ASSUME_NONNULL_LOC = 67,
+
+  /// Record code for Sema's vector of functions/blocks with effects to
+  /// be verified.
+  DECLS_WITH_EFFECTS_TO_VERIFY = 68
 };
 
 /// Record types used within a source manager block.
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 370d8037a4da17..c34a7482b9a688 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -942,6 +942,9 @@ class ASTReader
   /// Sema tracks these to emit deferred diags.
   llvm::SmallSetVector<serialization::DeclID, 4> DeclsToCheckForDeferredDiags;
 
+  /// The IDs of all decls with function effects to be checked.
+  SmallVector<serialization::DeclID> DeclsWithEffectsToVerify;
+
 private:
   struct ImportedSubmodule {
     serialization::SubmoduleID ID;
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index e5db486a71a490..56e309e557d1ac 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -554,6 +554,7 @@ class ASTWriter : public ASTDeserializationListener,
   void WriteMSPointersToMembersPragmaOptions(Sema &SemaRef);
   void WritePackPragmaOptions(Sema &SemaRef);
   void WriteFloatControlPragmaOptions(Sema &SemaRef);
+  void WriteDeclsWithEffectsToVerify(Sema &SemaRef);
   void WriteModuleFileExtension(Sema &SemaRef,
                                 ModuleFileExtensionWriter &Writer);
 
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 6dd511be275c35..cf6a1882aea5ab 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -13706,7 +13706,7 @@ StringRef ASTContext::getCUIDHash() const {
 using FunctionEffectSpan = llvm::ArrayRef<const FunctionEffect>;
 
 llvm::hash_code hash_value(const FunctionEffect &Effect) {
-  return Effect.opaqueRepr();
+  return llvm::hash_value(Effect.opaqueRepr());
 }
 
 namespace llvm {
@@ -13741,6 +13741,13 @@ template <> struct DenseMapInfo<FunctionEffectSpan> {
 };
 } // namespace llvm
 
+FunctionEffectSet
+ASTContext::getUniquedFunctionEffectSet(llvm::ArrayRef<uint32_t> FX) {
+  static_assert(sizeof(FunctionEffect) == sizeof(uint32_t));
+  const auto *ptr = reinterpret_cast<const FunctionEffect *>(FX.data());
+  return UniquedFunctionEffectSet.getUniqued({ptr, FX.size()});
+}
+
 FunctionEffectSet ASTContext::FunctionEffectSetUniquing::getUniqued(
     llvm::ArrayRef<const FunctionEffect> FX) {
   if (FX.empty()) {
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 981b61931d2538..827d57e07d6037 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4502,16 +4502,13 @@ unsigned FunctionDecl::getODRHash() {
   return ODRHash;
 }
 
-// Effects may differ between redeclarations, so collect all effects from
-// all redeclarations.
+// Effects may differ between declarations, but they should be propagated from old
+// to new on any redeclaration, so it suffices to look at getMostRecentDecl().
 FunctionEffectSet FunctionDecl::getFunctionEffects() const {
-  MutableFunctionEffectSet FX;
-  for (FunctionDecl *FD : redecls()) {
-    if (const auto *FPT = FD->getType()->getAs<FunctionProtoType>()) {
-      FX |= FPT->getFunctionEffects();
-    }
+  if (const auto *FPT = getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
+    return FPT->getFunctionEffects();
   }
-  return getASTContext().getUniquedFunctionEffectSet(FX);
+  return {};
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 47202980232bc6..5903b792bfe1e8 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2761,7 +2761,7 @@ class Analyzer {
   Sema &Sem;
 
   // used from Sema:
-  //  SmallVector<const Decl *> DeclsWithUnverifiedEffects
+  //  SmallVector<const Decl *> DeclsWithEffectsToVerify
 
   // Subset of Sema.AllEffectsToVerify
   FunctionEffectSet AllInferrableEffectsToVerify;
@@ -2837,9 +2837,9 @@ class Analyzer {
       }
     }
 
-    SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithUnverifiedEffects;
+    SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithEffectsToVerify;
 
-    // It's helpful to use DeclsWithUnverifiedEffects as a stack for a
+    // It's helpful to use DeclsWithEffectsToVerify as a stack for a
     // depth-first traversal rather than have a secondary container. But first,
     // reverse it, so Decls are verified in the order they are declared.
     std::reverse(verifyQueue.begin(), verifyQueue.end());
@@ -3545,7 +3545,7 @@ class Analyzer {
   };
 
 #if FX_ANALYZER_VERIFY_DECL_LIST
-  // Sema has accumulated DeclsWithUnverifiedEffects. As a debug check, do our
+  // Sema has accumulated DeclsWithEffectsToVerify. As a debug check, do our
   // own AST traversal and see what we find.
 
   using MatchFinder = ast_matchers::MatchFinder;
@@ -3603,15 +3603,15 @@ class Analyzer {
   void verifyRootDecls(const TranslationUnitDecl &TU) const {
     // If this weren't debug code, it would be good to find a way to move/swap
     // instead of copying.
-    SmallVector<const Decl *> decls = Sem.DeclsWithUnverifiedEffects;
-    Sem.DeclsWithUnverifiedEffects.clear();
+    SmallVector<const Decl *> decls = Sem.DeclsWithEffectsToVerify;
+    Sem.DeclsWithEffectsToVerify.clear();
 
     CallableFinderCallback::get(Sem, TU);
 
     if constexpr (DebugLogLevel > 0) {
       llvm::errs() << "\nFXAnalysis: Sema gathered " << decls.size()
                    << " Decls; second AST pass found "
-                   << Sem.DeclsWithUnverifiedEffects.size() << "\n";
+                   << Sem.DeclsWithEffectsToVerify.size() << "\n";
     }
   }
 #endif
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 5dc92ea98554a6..aecd9b3f52a625 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -11179,7 +11179,7 @@ void Sema::CheckAddCallableWithEffects(const Decl *D, FunctionEffectSet FX) {
   }
 
   // Record the declaration for later analysis.
-  DeclsWithUnverifiedEffects.push_back(D);
+  DeclsWithEffectsToVerify.push_back(D);
 }
 
 /// Determines if we can perform a correct type check for \p D as a
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index ede9f6e93469b7..3810c1d75cff0c 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3838,6 +3838,11 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
       FPPragmaOptions.swap(Record);
       break;
 
+    case DECLS_WITH_EFFECTS_TO_VERIFY:
+      for (unsigned I = 0, N = Record.size(); I != N; ++I)
+        DeclsWithEffectsToVerify.push_back(getGlobalDeclID(F, Record[I]));
+      break;
+
     case OPENCL_EXTENSIONS:
       for (unsigned I = 0, E = Record.size(); I != E; ) {
         auto Name = ReadString(Record, I);
@@ -8208,6 +8213,24 @@ void ASTReader::InitializeSema(Sema &S) {
         NewOverrides.applyOverrides(SemaObj->getLangOpts());
   }
 
+  if (!DeclsWithEffectsToVerify.empty()) {
+    for (uint64_t ID : DeclsWithEffectsToVerify) {
+      Decl *D = GetDecl(ID);
+      SemaObj->DeclsWithEffectsToVerify.push_back(D);
+
+      FunctionEffectSet FX;
+      if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+        FX = FD->getFunctionEffects();
+      } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+        FX = BD->getFunctionEffects();
+      }
+      if (FX) {
+        SemaObj->AllEffectsToVerify |= FX;
+      }
+    }
+    DeclsWithEffectsToVerify.clear();
+  }
+
   SemaObj->OpenCLFeatures = OpenCLExtensions;
 
   UpdateSema();
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 3653d94c6e0739..731a58ee785703 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4429,6 +4429,17 @@ void ASTWriter::WriteFloatControlPragmaOptions(Sema &SemaRef) {
   Stream.EmitRecord(FLOAT_CONTROL_PRAGMA_OPTIONS, Record);
 }
 
+/// Write Sema's collected list of declarations with unverified effects.
+void ASTWriter::WriteDeclsWithEffectsToVerify(Sema &SemaRef) {
+  if (SemaRef.DeclsWithEffectsToVerify.empty())
+    return;
+  RecordData Record;
+  for (const auto *D : SemaRef.DeclsWithEffectsToVerify) {
+    AddDeclRef(D, Record);
+  }
+  Stream.EmitRecord(DECLS_WITH_EFFECTS_TO_VERIFY, Record);
+}
+
 void ASTWriter::WriteModuleFileExtension(Sema &SemaRef,
                                          ModuleFileExtensionWriter &Writer) {
   // Enter the extension block.
@@ -5287,6 +5298,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
   }
   WritePackPragmaOptions(SemaRef);
   WriteFloatControlPragmaOptions(SemaRef);
+  WriteDeclsWithEffectsToVerify(SemaRef);
 
   // Some simple statistics
   RecordData::value_type Record[] = {

>From 8414186c5d6819c805ba521f06ab73e0d6b085e9 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 20 Mar 2024 12:02:13 -0700
Subject: [PATCH 17/18] Don't repeat function names in diagnostics.

---
 .../clang/Basic/DiagnosticSemaKinds.td        | 34 ++++++-------
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 39 +++++++--------
 clang/test/Sema/attr-nolock-constraints.cpp   | 48 +++++++++----------
 3 files changed, 59 insertions(+), 62 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index df553a47a8dbe7..f50a572fe7f5e2 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10779,63 +10779,63 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
 
 def warn_func_effect_allocates : Warning<
-  "'%0' function '%1' must not allocate or deallocate memory">,
+  "'%0' function must not allocate or deallocate memory">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_allocates : Note<
-  "'%1' cannot be inferred '%0' because it allocates/deallocates memory">;
+  "function cannot be inferred '%0' because it allocates/deallocates memory">;
 
 def warn_func_effect_throws_or_catches : Warning<
-  "'%0' function '%1' must not throw or catch exceptions">,
+  "'%0' function must not throw or catch exceptions">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_throws_or_catches : Note<
-  "'%1' cannot be inferred '%0' because it throws or catches exceptions">;
+  "function cannot be inferred '%0' because it throws or catches exceptions">;
 
 def warn_func_effect_has_static_local : Warning<
-  "'%0' function '%1' must not have static locals">,
+  "'%0' function must not have static locals">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_has_static_local : Note<
-  "'%1' cannot be inferred '%0' because it has a static local">;
+  "function cannot be inferred '%0' because it has a static local">;
 
 def warn_func_effect_uses_thread_local : Warning<
-  "'%0' function '%1' must not use thread-local variables">,
+  "'%0' function must not use thread-local variables">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_uses_thread_local : Note<
-  "'%1' cannot be inferred '%0' because it uses a thread-local variable">;
+  "function cannot be inferred '%0' because it uses a thread-local variable">;
 
 def warn_func_effect_calls_objc : Warning<
-  "'%0' function '%1' must not access an ObjC method or property">,
+  "'%0' function must not access an ObjC method or property">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_calls_objc : Note<
-  "'%1' cannot be inferred '%0' because it accesses an ObjC method or property">;
+  "function cannot be inferred '%0' because it accesses an ObjC method or property">;
 
 def warn_func_effect_calls_disallowed_func : Warning<
-  "'%0' function '%1' must not call non-'%0' function '%2'">,
+  "'%0' function must not call non-'%0' function">,
   InGroup<FunctionEffects>;
 
 // UNTESTED
 def warn_func_effect_calls_disallowed_expr : Warning<
-  "'%0' function '%1' must not call non-'%0' expression">,
+  "'%0' function must not call non-'%0' expression">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_calls_disallowed_func : Note<
-  "'%1' cannot be inferred '%0' because it calls non-'%0' function '%2'">;
+  "function cannot be inferred '%0' because it calls non-'%0' function">;
 
 def note_func_effect_call_extern : Note<
-  "'%1' cannot be inferred '%0' because it has no definition in this translation unit">;
+  "function cannot be inferred '%0' because it has no definition in this translation unit">;
 
 def note_func_effect_call_not_inferrable : Note<
-  "'%1' does not permit inference of '%0'">;
+  "function does not permit inference of '%0'">;
 
 def note_func_effect_call_virtual : Note<
-  "'%1' cannot be inferred '%0' because it is virtual">;
+  "virtual method cannot be inferred '%0'">;
 
 def note_func_effect_call_func_ptr : Note<
-  "'%1' cannot be inferred '%0' because it is a function pointer">;
+  "function pointer cannot be inferred '%0'">;
 
 // TODO: Not currently being generated
 // def warn_perf_annotation_implies_noexcept : Warning<
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 5903b792bfe1e8..0f6de7c83e4907 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3035,7 +3035,6 @@ class Analyzer {
                 return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
               });
 
-    const auto TopFuncName = CInfo.name(S);
 
     // TODO: Can we get better template instantiation notes?
     auto checkAddTemplateNote = [&](const Decl *D) {
@@ -3059,48 +3058,47 @@ class Analyzer {
         break;
       case DiagnosticID::AllocatesMemory:
         S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
-            << effectName << TopFuncName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         TESTED
         break;
       case DiagnosticID::Throws:
       case DiagnosticID::Catches:
         S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
-            << effectName << TopFuncName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         TESTED
         break;
       case DiagnosticID::HasStaticLocal:
         S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local)
-            << effectName << TopFuncName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         TESTED
         break;
       case DiagnosticID::AccessesThreadLocal:
         S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
-            << effectName << TopFuncName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         TESTED
         break;
       case DiagnosticID::CallsObjC:
         S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc)
-            << effectName << TopFuncName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         TESTED
         break;
       case DiagnosticID::CallsDisallowedExpr:
         S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
-            << effectName << TopFuncName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         UNTESTED
         break;
 
       case DiagnosticID::CallsUnsafeDecl: {
         CallableInfo CalleeInfo{*Diag.Callee};
-        auto CalleeName = CalleeInfo.name(S);
 
         S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
-            << effectName << TopFuncName << CalleeName;
+            << effectName;
         checkAddTemplateNote(CInfo.CDecl);
 
         // Emit notes explaining the transitive chain of inferences: Why isn't
@@ -3115,16 +3113,16 @@ class Analyzer {
             // - virtual
             if (CalleeInfo.CType == CallType::Virtual) {
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
-                  << effectName << CalleeName;
+                  << effectName;
               TESTED
             } else if (CalleeInfo.CType == CallType::Unknown) {
               S.Diag(Callee->getLocation(),
                      diag::note_func_effect_call_func_ptr)
-                  << effectName << CalleeName;
+                  << effectName;
               TESTED
             } else {
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
-                  << effectName << CalleeName;
+                  << effectName;
               TESTED
             }
             break;
@@ -3141,44 +3139,44 @@ class Analyzer {
             break;
           case DiagnosticID::DeclWithoutConstraintOrInference:
             S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
-                << effectName << CalleeName;
+                << effectName;
             TESTED
             break;
           case DiagnosticID::CallsDisallowedExpr:
             S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
-                << effectName << CalleeName;
+                << effectName;
             UNTESTED
             break;
           case DiagnosticID::AllocatesMemory:
             S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
-                << effectName << CalleeName;
+                << effectName;
             TESTED
             break;
           case DiagnosticID::Throws:
           case DiagnosticID::Catches:
             S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
-                << effectName << CalleeName;
+                << effectName;
             TESTED
             break;
           case DiagnosticID::HasStaticLocal:
             S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
-                << effectName << CalleeName;
+                << effectName;
             TESTED
             break;
           case DiagnosticID::AccessesThreadLocal:
             S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
-                << effectName << CalleeName;
+                << effectName;
             UNTESTED
             break;
           case DiagnosticID::CallsObjC:
             S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc)
-                << effectName << CalleeName;
+                << effectName;
             UNTESTED
             break;
           case DiagnosticID::CallsUnsafeDecl:
             MaybeNextCallee.emplace(*Diag2.Callee);
             S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
-                << effectName << CalleeName << MaybeNextCallee->name(S);
+                << effectName;
             TESTED
             break;
           }
@@ -3186,7 +3184,6 @@ class Analyzer {
           Callee = Diag2.Callee;
           if (MaybeNextCallee) {
             CalleeInfo = *MaybeNextCallee;
-            CalleeName = CalleeInfo.name(S);
           }
         }
       } break;
diff --git a/clang/test/Sema/attr-nolock-constraints.cpp b/clang/test/Sema/attr-nolock-constraints.cpp
index 5d1efe931a1f91..d2d2ddbf04a2a8 100644
--- a/clang/test/Sema/attr-nolock-constraints.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.cpp
@@ -10,66 +10,66 @@
 
 void nl1() [[clang::nolock]]
 {
-	auto* pInt = new int; // expected-warning {{'nolock' function 'nl1' must not allocate or deallocate memory}}
+	auto* pInt = new int; // expected-warning {{'nolock' function must not allocate or deallocate memory}}
 }
 
 void nl2() [[clang::nolock]]
 {
-	static int global; // expected-warning {{'nolock' function 'nl2' must not have static locals}}
+	static int global; // expected-warning {{'nolock' function must not have static locals}}
 }
 
 void nl3() [[clang::nolock]]
 {
 	try {
-		throw 42; // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+		throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
 	}
-	catch (...) { // expected-warning {{'nolock' function 'nl3' must not throw or catch exceptions}}
+	catch (...) { // expected-warning {{'nolock' function must not throw or catch exceptions}}
 	}
 }
 
 void nl4_inline() {}
-void nl4_not_inline(); // expected-note {{'nl4_not_inline' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl4_not_inline(); // expected-note {{function cannot be inferred 'nolock' because it has no definition in this translation unit}}
 
 void nl4() [[clang::nolock]]
 {
 	nl4_inline(); // OK
-	nl4_not_inline(); // expected-warning {{'nolock' function 'nl4' must not call non-'nolock' function 'nl4_not_inline'}}
+	nl4_not_inline(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
 }
 
 
 struct HasVirtual {
-	virtual void unsafe(); // expected-note {{'HasVirtual::unsafe' cannot be inferred 'nolock' because it is virtual}}
+	virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nolock'}}
 };
 
 void nl5() [[clang::nolock]]
 {
  	HasVirtual hv;
- 	hv.unsafe(); // expected-warning {{'nolock' function 'nl5' must not call non-'nolock' function 'HasVirtual::unsafe'}}
+ 	hv.unsafe(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
 }
 
-void nl6_unsafe(); // expected-note {{'nl6_unsafe' cannot be inferred 'nolock' because it has no definition in this translation unit}}
+void nl6_unsafe(); // expected-note {{function cannot be inferred 'nolock' because it has no definition in this translation unit}}
 void nl6_transitively_unsafe()
 {
-	nl6_unsafe(); // expected-note {{'nl6_transitively_unsafe' cannot be inferred 'nolock' because it calls non-'nolock' function 'nl6_unsafe'}}
+	nl6_unsafe(); // expected-note {{function cannot be inferred 'nolock' because it calls non-'nolock' function}}
 }
 
 void nl6() [[clang::nolock]]
 {
-	nl6_transitively_unsafe(); // expected-warning {{'nolock' function 'nl6' must not call non-'nolock' function 'nl6_transitively_unsafe'}}
+	nl6_transitively_unsafe(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
 }
 
 thread_local int tl_var{ 42 };
 
 bool tl_test() [[clang::nolock]]
 {
-	return tl_var > 0; // expected-warning {{'nolock' function 'tl_test' must not use thread-local variables}}
+	return tl_var > 0; // expected-warning {{'nolock' function must not use thread-local variables}}
 }
 
 void nl7()
 {
 	// Make sure we verify blocks
 	auto blk = ^() [[clang::nolock]] {
-		throw 42; // expected-warning {{'nolock' function '(block 0)' must not throw or catch exceptions}}
+		throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
 	};
 }
 
@@ -77,7 +77,7 @@ void nl8()
 {
 	// Make sure we verify lambdas
 	auto lambda = []() [[clang::nolock]] {
-		throw 42; // expected-warning {{'nolock' function 'nl8()::(anonymous class)::operator()' must not throw or catch exceptions}}
+		throw 42; // expected-warning {{'nolock' function must not throw or catch exceptions}}
 	};
 }
 
@@ -86,11 +86,11 @@ void nl8()
 	struct Adder {
 		static T add_explicit(T x, T y) [[clang::nolock]]
 		{
-			return x + y; // expected-warning {{'nolock' function 'Adder<Stringy>::add_explicit' must not call non-'nolock' function 'operator+'}}
+			return x + y; // expected-warning {{'nolock' function must not call non-'nolock' function}}
 		}
 		static T add_implicit(T x, T y)
 		{
-			return x + y; // expected-note {{'Adder<Stringy2>::add_implicit' cannot be inferred 'nolock' because it calls non-'nolock' function 'operator+'}}
+			return x + y; // expected-note {{function cannot be inferred 'nolock' because it calls non-'nolock' function}}
 		}
 	};
 
@@ -98,7 +98,7 @@ void nl8()
 		friend Stringy operator+(const Stringy& x, const Stringy& y)
 		{
 			// Do something inferably unsafe
-			auto* z = new char[42]; // expected-note {{'operator+' cannot be inferred 'nolock' because it allocates/deallocates memory}}
+			auto* z = new char[42]; // expected-note {{function cannot be inferred 'nolock' because it allocates/deallocates memory}}
 			return {};
 		}
 	};
@@ -107,7 +107,7 @@ void nl8()
 		friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
 		{
 			// Do something inferably unsafe
-			throw 42; // expected-note {{'operator+' cannot be inferred 'nolock' because it throws or catches exceptions}}
+			throw 42; // expected-note {{function cannot be inferred 'nolock' because it throws or catches exceptions}}
 		}
 	};
 
@@ -117,32 +117,32 @@ void nl9() [[clang::nolock]]
 	Adder<int>::add_implicit(1, 2);
 
 	Adder<Stringy>::add_explicit({}, {}); // expected-note {{in template expansion here}}
-	Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function 'nl9' must not call non-'nolock' function 'Adder<Stringy2>::add_implicit'}} \
+	Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{'nolock' function must not call non-'nolock' function}} \
 		expected-note {{in template expansion here}}
 }
 
 void nl10(
-	void (*fp1)(), // expected-note {{'fp1' cannot be inferred 'nolock' because it is a function pointer}}
+	void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nolock'}}
 	void (*fp2)() [[clang::nolock]]
 	) [[clang::nolock]]
 {
-	fp1(); // expected-warning {{'nolock' function 'nl10' must not call non-'nolock' function 'fp1'}}
+	fp1(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
 	fp2();
 }
 
 // Interactions with nolock(false)
-void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{'nl11_no_inference' does not permit inference of 'nolock'}}
+void nl11_no_inference() [[clang::nolock(false)]] // expected-note {{function does not permit inference of 'nolock'}}
 {
 }
 
 void nl11() [[clang::nolock]]
 {
-	nl11_no_inference(); // expected-warning {{'nolock' function 'nl11' must not call non-'nolock' function 'nl11_no_inference'}}
+	nl11_no_inference(); // expected-warning {{'nolock' function must not call non-'nolock' function}}
 }
 
 // Verify that when attached to a redeclaration, the attribute successfully attaches.
 void nl12() {
-	static int x; // expected-warning {{'nolock' function 'nl12' must not have static locals}}
+	static int x; // expected-warning {{'nolock' function must not have static locals}}
 }
 void nl12() [[clang::nolock]];
 void nl13() [[clang::nolock]] { nl12(); }

>From 62b125efb4ded8981c2e0ba4bf338f0169adc203 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 20 Mar 2024 08:52:06 -0700
Subject: [PATCH 18/18] Functionality: - Effects can allow themselves to be
 propagated from virtual methods in base class to overridden methods in
 derived classes.

Fixes:
- In constraint analysis, map must be keyed with canonical decls; but decls as analyzed should be definitions wherever possible.
- FunctionEffect needs explicit padding
- Tests in Objective-C
- Don't trap on recursion; ignore recursive calls when completing function analysis.
- Handle pointers to member function, expressions of type BlockPointerType
- followCall() needs to combine declared and inferred effects of callee.
- FunctionEffectSet::get() receives a QualType instead of Type& now. It knows how to peel off references now.
- MutableFunctionEffectSet::insert fix: wasn't handling Iter == end() correctly
- Sema::IsFunctionConversion: fix: wasn't correctly removing nolock after removing noexcept
- Remove debug macros marking untested cases
---
 clang/include/clang/AST/Type.h                |  32 +++-
 .../clang/Basic/DiagnosticSemaKinds.td        |  10 +-
 clang/lib/AST/Decl.cpp                        |   8 +-
 clang/lib/AST/Type.cpp                        |  38 ++--
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 173 +++++++++++-------
 clang/lib/Sema/Sema.cpp                       |   4 +-
 clang/lib/Sema/SemaDeclCXX.cpp                |  21 ++-
 clang/lib/Sema/SemaOverload.cpp               |   2 +
 ...traints.cpp => attr-nolock-constraints.mm} |  42 +++++
 clang/test/Sema/attr-nolock-sema.cpp          |  57 +++---
 clang/test/Sema/attr-nolock-syntax.cpp        |   9 +
 clang/test/Sema/attr-nolock-wip.cpp           |  80 ++++++--
 12 files changed, 329 insertions(+), 147 deletions(-)
 rename clang/test/Sema/{attr-nolock-constraints.cpp => attr-nolock-constraints.mm} (82%)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index ca4660edb2f56f..3d7ef5f951ee1d 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4197,6 +4197,7 @@ class TypeSourceInfo;
 /// Represents an abstract function effect.
 class FunctionEffect {
 public:
+  /// Identifies the particular type of effect.
   enum class Type {
     None = 0,
     NoLockTrue,
@@ -4229,6 +4230,14 @@ class FunctionEffect {
     FE_ExcludeThreadLocalVars = 0x80
   };
 
+  /// Describes the result of effects differing between a base class's virtual
+  /// method and an overriding method in a subclass.
+  enum class OverrideResult {
+    Ignore,
+    Warn,
+    Propagate // Base method's effects are merged with those of the override.
+  };
+
 private:
   // For uniqueness, currently only Type_ is significant.
 
@@ -4240,11 +4249,15 @@ class FunctionEffect {
   // then ~16(?) bits "Subtype" to map to a specific named TCB. Subtype would
   // be considered for uniqueness.
 
+  // Since this struct is serialized as if it were a uint32_t, it's important
+  // to pad and explicitly zero the extra bits.
+  [[maybe_unused]] unsigned Padding : 22;
+
 public:
   using CalleeDeclOrType =
       llvm::PointerUnion<const Decl *, const FunctionProtoType *>;
 
-  FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0) {}
+  FunctionEffect() : Type_(unsigned(Type::None)), Flags_(0), Padding(0) {}
 
   explicit FunctionEffect(Type T);
 
@@ -4257,7 +4270,7 @@ class FunctionEffect {
   /// The description printed in diagnostics, e.g. 'nolock'.
   StringRef name() const;
 
-  /// A serializable, hashable representation.
+  /// A hashable representation.
   uint32_t opaqueRepr() const { return Type_ | (Flags_ << 2u); }
 
   /// Return true if adding or removing the effect as part of a type conversion
@@ -4275,10 +4288,11 @@ class FunctionEffect {
 
   /// Return true if adding or removing the effect in a C++ virtual method
   /// override should generate a diagnostic.
-  bool diagnoseMethodOverride(bool Adding, const CXXMethodDecl &OldMethod,
-                              FunctionEffectSet OldFX,
-                              const CXXMethodDecl &NewMethod,
-                              FunctionEffectSet NewFX) const;
+  OverrideResult diagnoseMethodOverride(bool Adding,
+                                        const CXXMethodDecl &OldMethod,
+                                        FunctionEffectSet OldFX,
+                                        const CXXMethodDecl &NewMethod,
+                                        FunctionEffectSet NewFX) const;
 
   /// Return true if the effect is allowed to be inferred on a Decl of the
   /// specified type (generally a FunctionProtoType but TypeSourceInfo is
@@ -4387,9 +4401,9 @@ class FunctionEffectSet {
   static Differences differences(const FunctionEffectSet &Old,
                                  const FunctionEffectSet &New);
 
-  /// Extract the effects from a Type if it is a BlockType or FunctionProtoType,
-  /// or pointer to one.
-  static FunctionEffectSet get(const Type &TyRef);
+  /// Extract the effects from a Type if it is a function, block, member
+  /// function pointer, reference or pointer to one.
+  static FunctionEffectSet get(QualType QT);
 };
 
 /// Represents a prototype with parameter type info, e.g.
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f50a572fe7f5e2..e841fdb4fd511a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10814,16 +10814,15 @@ def note_func_effect_calls_objc : Note<
   "function cannot be inferred '%0' because it accesses an ObjC method or property">;
 
 def warn_func_effect_calls_disallowed_func : Warning<
-  "'%0' function must not call non-'%0' function">,
+  "'%0' function must not call non-'%0' function '%1'">,
   InGroup<FunctionEffects>;
 
-// UNTESTED
 def warn_func_effect_calls_disallowed_expr : Warning<
   "'%0' function must not call non-'%0' expression">,
   InGroup<FunctionEffects>;
 
 def note_func_effect_calls_disallowed_func : Note<
-  "function cannot be inferred '%0' because it calls non-'%0' function">;
+  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
 
 def note_func_effect_call_extern : Note<
   "function cannot be inferred '%0' because it has no definition in this translation unit">;
@@ -10851,11 +10850,6 @@ def warn_func_effect_false_on_type : Warning<
 def note_func_effect_from_template : Note<
   "in template expansion here">;
 
-// TODO: Needs to be tested
-def warn_incompatible_func_effects : Warning<
-  "attributes '%0' and '%1' are incompatible">,
-  InGroup<FunctionEffects>;
-
 // spoofing nolock/noalloc
 def warn_invalid_add_func_effects : Warning<
   "attribute '%0' should not be added via type conversion">,
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 827d57e07d6037..e4171d2a3e0a8a 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -4502,10 +4502,12 @@ unsigned FunctionDecl::getODRHash() {
   return ODRHash;
 }
 
-// Effects may differ between declarations, but they should be propagated from old
-// to new on any redeclaration, so it suffices to look at getMostRecentDecl().
+// Effects may differ between declarations, but they should be propagated from
+// old to new on any redeclaration, so it suffices to look at
+// getMostRecentDecl().
 FunctionEffectSet FunctionDecl::getFunctionEffects() const {
-  if (const auto *FPT = getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
+  if (const auto *FPT =
+          getMostRecentDecl()->getType()->getAs<FunctionProtoType>()) {
     return FPT->getFunctionEffects();
   }
   return {};
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 06f9c0190bad6f..515971d650f592 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -4922,7 +4922,8 @@ void AutoType::Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) {
           getTypeConstraintConcept(), getTypeConstraintArguments());
 }
 
-FunctionEffect::FunctionEffect(Type T) : Type_(unsigned(T)), Flags_(0) {
+FunctionEffect::FunctionEffect(Type T)
+    : Type_(unsigned(T)), Flags_(0), Padding(0) {
   switch (T) {
   case Type::NoLockTrue:
     Flags_ = FE_RequiresVerification | FE_VerifyCalls | FE_InferrableOnCallees |
@@ -4994,21 +4995,19 @@ bool FunctionEffect::diagnoseRedeclaration(bool Adding,
   return false;
 }
 
-bool FunctionEffect::diagnoseMethodOverride(bool Adding,
-                                            const CXXMethodDecl &OldMethod,
-                                            FunctionEffectSet OldFX,
-                                            const CXXMethodDecl &NewMethod,
-                                            FunctionEffectSet NewFX) const {
+FunctionEffect::OverrideResult FunctionEffect::diagnoseMethodOverride(
+    bool Adding, const CXXMethodDecl &OldMethod, FunctionEffectSet OldFX,
+    const CXXMethodDecl &NewMethod, FunctionEffectSet NewFX) const {
   switch (type()) {
   case Type::NoAllocTrue:
   case Type::NoLockTrue:
     // nolock/noalloc can't be removed from an override
     // adding -> false, removing -> true (diagnose)
-    return !Adding;
+    return Adding ? OverrideResult::Ignore : OverrideResult::Propagate;
   default:
     break;
   }
-  return false;
+  return OverrideResult::Ignore;
 }
 
 bool FunctionEffect::canInferOnFunction(QualType QT,
@@ -5062,7 +5061,7 @@ MutableFunctionEffectSet::MutableFunctionEffectSet(
 
 void MutableFunctionEffectSet::insert(const FunctionEffect &Effect) {
   const auto &Iter = std::lower_bound(begin(), end(), Effect);
-  if (*Iter != Effect) {
+  if (Iter == end() || *Iter != Effect) {
     insert(Iter, Effect);
   }
 }
@@ -5112,12 +5111,23 @@ FunctionEffectSet::operator&(const FunctionEffectSet &RHS) const {
 }
 
 // TODO: inline?
-FunctionEffectSet FunctionEffectSet::get(const Type &TyRef) {
-  const Type *Ty = &TyRef;
-  if (Ty->isPointerType())
-    Ty = Ty->getPointeeType().getTypePtr();
-  if (const auto *FPT = Ty->getAs<FunctionProtoType>())
+FunctionEffectSet FunctionEffectSet::get(QualType QT) {
+  if (QT->isReferenceType())
+    QT = QT.getNonReferenceType();
+  if (QT->isPointerType())
+    QT = QT->getPointeeType();
+
+  if (const auto *BT = QT->getAs<BlockPointerType>()) {
+    QT = BT->getPointeeType();
+  } else if (const auto *MP = QT->getAs<MemberPointerType>()) {
+    if (MP->isMemberFunctionPointer()) {
+      QT = MP->getPointeeType();
+    }
+  }
+
+  if (const auto *FPT = QT->getAs<FunctionProtoType>())
     return FPT->getFunctionEffects();
+
   return {};
 }
 
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0f6de7c83e4907..10228715ea1bb9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2389,6 +2389,12 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 
 namespace FXAnalysis {
 
+#define tmp_assert(x)                                                          \
+  do {                                                                         \
+    if (!(x))                                                                  \
+      __builtin_trap();                                                        \
+  } while (0)
+
 enum class DiagnosticID : uint8_t {
   None = 0, // sentinel for an empty Diagnostic
   Throws,
@@ -2445,6 +2451,7 @@ static bool functionIsVerifiable(const FunctionDecl *FD) {
 // Transitory, more extended information about a callable, which can be a
 // function, block, function pointer...
 struct CallableInfo {
+  // CDecl holds the function's definition, if any.
   const Decl *CDecl;
   mutable std::optional<std::string>
       MaybeName; // mutable because built on demand in const method
@@ -2461,10 +2468,6 @@ struct CallableInfo {
       // Use the function's definition, if any.
       if (auto *Def = FD->getDefinition()) {
         CDecl = FD = Def;
-        // is the definition always canonical?
-        assert(FD->getCanonicalDecl() == FD);
-      } else {
-        FD = FD->getCanonicalDecl();
       }
       CType = CallType::Function;
       if (auto *Method = dyn_cast<CXXMethodDecl>(FD)) {
@@ -2481,7 +2484,7 @@ struct CallableInfo {
     } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
       // ValueDecl is function, enum, or variable, so just look at the type.
       QT = VD->getType();
-      Effects = FunctionEffectSet::get(*QT);
+      Effects = FunctionEffectSet::get(QT);
     }
   }
 
@@ -2580,8 +2583,19 @@ class PendingFunctionAnalysis {
   friend class CompleteFunctionAnalysis;
 
   struct DirectCall {
-    const Decl *Callee;
+    // Pack a Decl* and a bool indicating whether the call was detected
+    // to be recursive. Not all recursive calls are detected, just enough
+    // to break cycles.
+    llvm::PointerIntPair<const Decl *, 1> CalleeAndRecursed;
     SourceLocation CallLoc;
+
+    DirectCall(const Decl *D, SourceLocation CallLoc)
+        : CalleeAndRecursed(D), CallLoc(CallLoc) {}
+
+    const Decl *callee() const { return CalleeAndRecursed.getPointer(); }
+    bool recursed() const { return CalleeAndRecursed.getInt(); }
+
+    void setRecursed() { CalleeAndRecursed.setInt(1); }
   };
 
 public:
@@ -2664,7 +2678,7 @@ class PendingFunctionAnalysis {
     if (UnverifiedDirectCalls == nullptr) {
       UnverifiedDirectCalls = std::make_unique<SmallVector<DirectCall>>();
     }
-    UnverifiedDirectCalls->emplace_back(DirectCall{D, CallLoc});
+    UnverifiedDirectCalls->emplace_back(D, CallLoc);
   }
 
   // Analysis is complete when there are no unverified direct calls.
@@ -2676,8 +2690,8 @@ class PendingFunctionAnalysis {
     return InferrableEffectToFirstDiagnostic.lookup(effect);
   }
 
-  const SmallVector<DirectCall> &unverifiedCalls() const {
-    assert(!isComplete());
+  SmallVector<DirectCall> &unverifiedCalls() const {
+    tmp_assert(!isComplete());
     return *UnverifiedDirectCalls;
   }
 
@@ -2685,7 +2699,8 @@ class PendingFunctionAnalysis {
     return DiagnosticsForExplicitFX.get();
   }
 
-  void dump(llvm::raw_ostream &OS) const {
+  // If Sema is supplied, prints names of unverified direct calls
+  void dump(llvm::raw_ostream &OS, Sema *SemPtr = nullptr) const {
     OS << "Pending: Declared ";
     DeclaredVerifiableEffects.dump(OS);
     OS << ", "
@@ -2693,7 +2708,15 @@ class PendingFunctionAnalysis {
        << " diags; ";
     OS << " Infer ";
     FXToInfer.dump(OS);
-    OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+    OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+    if (SemPtr && UnverifiedDirectCalls) {
+      OS << "; Calls: ";
+      for (const auto &Call : *UnverifiedDirectCalls) {
+        CallableInfo CI(*Call.callee());
+        OS << " " << CI.name(*SemPtr);
+      }
+    }
+    OS << "\n";
   }
 };
 
@@ -2753,15 +2776,23 @@ class CompleteFunctionAnalysis {
         }
 */
 
+const Decl *CanonicalFunctionDecl(const Decl *D) {
+  if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+    FD = FD->getCanonicalDecl();
+    tmp_assert(FD != nullptr);
+    return FD;
+  }
+  return D;
+}
+
 // ==========
 class Analyzer {
   constexpr static int DebugLogLevel = 0;
-
   // --
   Sema &Sem;
 
   // used from Sema:
-  //  SmallVector<const Decl *> DeclsWithEffectsToVerify
+  //  SmallVector<const Decl *> CallablesWithEffectsToVerify
 
   // Subset of Sema.AllEffectsToVerify
   FunctionEffectSet AllInferrableEffectsToVerify;
@@ -2772,11 +2803,22 @@ class Analyzer {
   // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
   // than complete state, so use different objects to represent them.
   // The state pointers are owned by the container.
-  struct AnalysisMap : public llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+  class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+    using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
 
+  public:
     ~AnalysisMap();
 
-    // use lookup()
+    // Use non-public inheritance in order to maintain the invariant
+    // that lookups and insertions are via the canonical Decls.
+
+    FuncAnalysisPtr lookup(const Decl *Key) const {
+      return Base::lookup(CanonicalFunctionDecl(Key));
+    }
+
+    FuncAnalysisPtr &operator[](const Decl *Key) {
+      return Base::operator[](CanonicalFunctionDecl(Key));
+    }
 
     /// Shortcut for the case where we only care about completed analysis.
     CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
@@ -2789,7 +2831,7 @@ class Analyzer {
     }
 
     void dump(Sema &S, llvm::raw_ostream &OS) {
-      OS << "AnalysisMap:\n";
+      OS << "\nAnalysisMap:\n";
       for (const auto &item : *this) {
         CallableInfo CI(*item.first);
         const auto AP = item.second;
@@ -2807,6 +2849,7 @@ class Analyzer {
         } else
           llvm_unreachable("never");
       }
+      OS << "---\n";
     }
   };
   AnalysisMap DeclAnalysis;
@@ -2839,7 +2882,7 @@ class Analyzer {
 
     SmallVector<const Decl *> &verifyQueue = Sem.DeclsWithEffectsToVerify;
 
-    // It's helpful to use DeclsWithEffectsToVerify as a stack for a
+    // It's helpful to use CallablesWithEffectsToVerify as a stack for a
     // depth-first traversal rather than have a secondary container. But first,
     // reverse it, so Decls are verified in the order they are declared.
     std::reverse(verifyQueue.begin(), verifyQueue.end());
@@ -2869,19 +2912,21 @@ class Analyzer {
         continue;
       }
 
-      for (const auto &Call : Pending->unverifiedCalls()) {
+      for (auto &Call : Pending->unverifiedCalls()) {
         // This lookup could be optimized out if the results could have been
         // saved from followCall when we traversed the caller's AST. It would
         // however make the check for recursion more complex.
-        auto AP = DeclAnalysis.lookup(Call.Callee);
+        auto AP = DeclAnalysis.lookup(Call.callee());
         if (AP.isNull()) {
-          verifyQueue.push_back(Call.Callee);
+          verifyQueue.push_back(Call.callee());
           continue;
         }
         if (isa<PendingFunctionAnalysis *>(AP)) {
-          // $$$$$$$$$$$$$$$$$$$$$$$ recursion $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
-          // TODO
-          __builtin_trap();
+          // This indicates recursion (not necessarily direct). For the
+          // purposes of effect analysis, we can just ignore it since
+          // no effects forbid recursion.
+          Call.setRecursed();
+          continue;
         }
         llvm_unreachable("unexpected DeclAnalysis item");
       }
@@ -2892,13 +2937,8 @@ class Analyzer {
   // Verify a single Decl. Return the pending structure if that was the result,
   // else null. This method must not recurse.
   PendingFunctionAnalysis *verifyDecl(const Decl *D) {
-    // TODO: Is this in the right place?
-    const FunctionDecl *FD = dyn_cast<FunctionDecl>(D);
-    if (FD != nullptr) {
-      // Currently, built-in functions are always considered safe.
-      if (FD->getBuiltinID() != 0) {
-        return nullptr;
-      }
+    if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+      tmp_assert(FD->getBuiltinID() == 0);
     }
     CallableInfo CInfo(*D);
 
@@ -2951,8 +2991,16 @@ class Analyzer {
   // the possibility of inference.
   void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
     CallableInfo Caller(*D);
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+      Pending->dump(llvm::outs(), &Sem);
+      llvm::outs() << "\n";
+    }
     for (const auto &Call : Pending->unverifiedCalls()) {
-      CallableInfo Callee(*Call.Callee);
+      if (Call.recursed())
+        continue;
+
+      CallableInfo Callee(*Call.callee());
       followCall(Caller, *Pending, Callee, Call.CallLoc,
                  /*AssertNoFurtherInference=*/true);
     }
@@ -2966,17 +3014,22 @@ class Analyzer {
                   const CallableInfo &Callee, SourceLocation CallLoc,
                   bool AssertNoFurtherInference) {
     const bool DirectCall = Callee.isDirectCall();
+
+    // These will be its declared effects.
     FunctionEffectSet CalleeEffects = Callee.Effects;
+
     bool IsInferencePossible = DirectCall;
 
     if (DirectCall) {
       if (auto *CFA = DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
-        CalleeEffects = CFA->VerifiedEffects;
+        // Combine declared effects with those which may have been inferred.
+        CalleeEffects = FunctionEffectSet::getUnion(
+            Sem.getASTContext(), CalleeEffects, CFA->VerifiedEffects);
         IsInferencePossible = false; // we've already traversed it
       }
     }
     if (AssertNoFurtherInference) {
-      assert(!IsInferencePossible);
+      tmp_assert(!IsInferencePossible);
     }
     if (!Callee.isVerifiable()) {
       IsInferencePossible = false;
@@ -2987,6 +3040,12 @@ class Analyzer {
                    << "; verifiable: " << Callee.isVerifiable() << "; callee ";
       CalleeEffects.dump(llvm::outs());
       llvm::outs() << "\n";
+      llvm::outs() << "  callee " << Callee.CDecl << " canonical "
+                   << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+      for (auto *D : Callee.CDecl->redecls()) {
+        llvm::outs() << " " << D;
+      }
+      llvm::outs() << "\n";
     }
 
     auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
@@ -3027,15 +3086,12 @@ class Analyzer {
   // Should only be called when determined to be complete.
   void emitDiagnostics(SmallVector<Diagnostic> &Diags,
                        const CallableInfo &CInfo, Sema &S) {
-#define UNTESTED __builtin_trap();
-#define TESTED
     const SourceManager &SM = S.getSourceManager();
     std::sort(Diags.begin(), Diags.end(),
               [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
                 return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
               });
 
-
     // TODO: Can we get better template instantiation notes?
     auto checkAddTemplateNote = [&](const Decl *D) {
       if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
@@ -3057,48 +3113,40 @@ class Analyzer {
         llvm_unreachable("Unexpected diagnostic kind");
         break;
       case DiagnosticID::AllocatesMemory:
-        S.Diag(Diag.Loc, diag::warn_func_effect_allocates)
-            << effectName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
         checkAddTemplateNote(CInfo.CDecl);
-        TESTED
         break;
       case DiagnosticID::Throws:
       case DiagnosticID::Catches:
         S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
             << effectName;
         checkAddTemplateNote(CInfo.CDecl);
-        TESTED
         break;
       case DiagnosticID::HasStaticLocal:
-        S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local)
-            << effectName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
         checkAddTemplateNote(CInfo.CDecl);
-        TESTED
         break;
       case DiagnosticID::AccessesThreadLocal:
         S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
             << effectName;
         checkAddTemplateNote(CInfo.CDecl);
-        TESTED
         break;
       case DiagnosticID::CallsObjC:
-        S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc)
-            << effectName;
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
         checkAddTemplateNote(CInfo.CDecl);
-        TESTED
         break;
       case DiagnosticID::CallsDisallowedExpr:
         S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_expr)
             << effectName;
         checkAddTemplateNote(CInfo.CDecl);
-        UNTESTED
         break;
 
       case DiagnosticID::CallsUnsafeDecl: {
         CallableInfo CalleeInfo{*Diag.Callee};
+        auto CalleeName = CalleeInfo.name(S);
 
         S.Diag(Diag.Loc, diag::warn_func_effect_calls_disallowed_func)
-            << effectName;
+            << effectName << CalleeName;
         checkAddTemplateNote(CInfo.CDecl);
 
         // Emit notes explaining the transitive chain of inferences: Why isn't
@@ -3114,16 +3162,13 @@ class Analyzer {
             if (CalleeInfo.CType == CallType::Virtual) {
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
                   << effectName;
-              TESTED
             } else if (CalleeInfo.CType == CallType::Unknown) {
               S.Diag(Callee->getLocation(),
                      diag::note_func_effect_call_func_ptr)
                   << effectName;
-              TESTED
             } else {
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
                   << effectName;
-              TESTED
             }
             break;
           }
@@ -3140,50 +3185,41 @@ class Analyzer {
           case DiagnosticID::DeclWithoutConstraintOrInference:
             S.Diag(Diag2.Loc, diag::note_func_effect_call_not_inferrable)
                 << effectName;
-            TESTED
             break;
           case DiagnosticID::CallsDisallowedExpr:
             S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
                 << effectName;
-            UNTESTED
             break;
           case DiagnosticID::AllocatesMemory:
-            S.Diag(Diag2.Loc, diag::note_func_effect_allocates)
-                << effectName;
-            TESTED
+            S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
             break;
           case DiagnosticID::Throws:
           case DiagnosticID::Catches:
             S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
                 << effectName;
-            TESTED
             break;
           case DiagnosticID::HasStaticLocal:
             S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
                 << effectName;
-            TESTED
             break;
           case DiagnosticID::AccessesThreadLocal:
             S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
                 << effectName;
-            UNTESTED
             break;
           case DiagnosticID::CallsObjC:
-            S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc)
-                << effectName;
-            UNTESTED
+            S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
             break;
           case DiagnosticID::CallsUnsafeDecl:
             MaybeNextCallee.emplace(*Diag2.Callee);
             S.Diag(Diag2.Loc, diag::note_func_effect_calls_disallowed_func)
-                << effectName;
-            TESTED
+                << effectName << MaybeNextCallee->name(S);
             break;
           }
           checkAddTemplateNote(Callee);
           Callee = Diag2.Callee;
           if (MaybeNextCallee) {
             CalleeInfo = *MaybeNextCallee;
+            CalleeName = CalleeInfo.name(S);
           }
         }
       } break;
@@ -3276,6 +3312,11 @@ class Analyzer {
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
     // other AST construct. CallableInfo pertains to the callee.
     void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+      if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl)) {
+        // Currently, built-in functions are always considered safe.
+        if (FD->getBuiltinID() != 0)
+          return;
+      }
       Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
                        /*AssertNoFurtherInference=*/false);
     }
@@ -3542,7 +3583,7 @@ class Analyzer {
   };
 
 #if FX_ANALYZER_VERIFY_DECL_LIST
-  // Sema has accumulated DeclsWithEffectsToVerify. As a debug check, do our
+  // Sema has accumulated CallablesWithEffectsToVerify. As a debug check, do our
   // own AST traversal and see what we find.
 
   using MatchFinder = ast_matchers::MatchFinder;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 05b771939feb13..874243495ea39b 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -588,8 +588,8 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
 // Generate diagnostics when adding or removing effects in a type conversion.
 void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                             SourceLocation Loc) {
-  const auto SrcFX = FunctionEffectSet::get(*SrcType);
-  const auto DstFX = FunctionEffectSet::get(*DstType);
+  const auto SrcFX = FunctionEffectSet::get(SrcType);
+  const auto DstFX = FunctionEffectSet::get(DstType);
   if (SrcFX != DstFX) {
     for (const auto &Item : FunctionEffectSet::differences(SrcFX, DstFX)) {
       const FunctionEffect &Effect = Item.first;
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 2c163ce4c14b03..dfc442e646d169 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18332,11 +18332,30 @@ bool Sema::CheckOverridingFunctionAttributes(const CXXMethodDecl *New,
     for (const auto &Item : Diffs) {
       const FunctionEffect &Effect = Item.first;
       const bool Adding = Item.second;
-      if (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+      switch (Effect.diagnoseMethodOverride(Adding, *Old, OldFX, *New, NewFX)) {
+      case FunctionEffect::OverrideResult::Ignore:
+        break;
+      case FunctionEffect::OverrideResult::Warn:
         Diag(New->getLocation(), diag::warn_mismatched_func_effect_override)
             << Effect.name();
         Diag(Old->getLocation(), diag::note_overridden_virtual_function);
+        // TODO: It would be nice to have a FIXIT here!
         AnyDiags = true;
+        break;
+      case FunctionEffect::OverrideResult::Propagate: {
+        auto MergedFX = FunctionEffectSet::getUnion(Context, OldFX, NewFX);
+
+        FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
+        EPI.FunctionEffects = MergedFX;
+        QualType ModQT = Context.getFunctionType(NewFT->getReturnType(),
+                                                 NewFT->getParamTypes(), EPI);
+
+        // TODO: It's ugly to be mutating the incoming const method. It is
+        // mutable in the calling function, though. There is also the
+        // possibility here that we are discarding some other sort of sugar on
+        // the method's type.
+        const_cast<CXXMethodDecl *>(New)->setType(ModQT);
+      } break;
       }
     }
     if (AnyDiags)
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 0163969e9bf50d..e26d1ef9d2bb51 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -1873,6 +1873,8 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
       // For C, when called from checkPointerTypesForAssignment,
       // we need not to change the type, or else even an innocuous cast
       // like dropping effects will fail.
+      FromFPT =
+          dyn_cast<FunctionProtoType>(FromFn); // in case FromFn changed above
 
       // Transparently add/drop effects; here we are concerned with
       // language rules/canonicalization. Adding/dropping effects is a warning.
diff --git a/clang/test/Sema/attr-nolock-constraints.cpp b/clang/test/Sema/attr-nolock-constraints.mm
similarity index 82%
rename from clang/test/Sema/attr-nolock-constraints.cpp
rename to clang/test/Sema/attr-nolock-constraints.mm
index d2d2ddbf04a2a8..e52b50eac519f4 100644
--- a/clang/test/Sema/attr-nolock-constraints.cpp
+++ b/clang/test/Sema/attr-nolock-constraints.mm
@@ -146,3 +146,45 @@ void nl12() {
 }
 void nl12() [[clang::nolock]];
 void nl13() [[clang::nolock]] { nl12(); }
+
+// Objective-C
+ at interface OCClass
+- (void)method;
+ at end
+
+void nl14(OCClass *oc) [[clang::nolock]] {
+	[oc method]; // expected-warning {{'nolock' function must not access an ObjC method or property}}
+}
+void nl15(OCClass *oc) {
+	[oc method]; // expected-note {{function cannot be inferred 'nolock' because it accesses an ObjC method or property}}
+}
+void nl16(OCClass *oc) [[clang::nolock]] {
+	nl15(oc); // expected-warning {{'nolock' function must not call non-'nolock' function 'nl15'}}
+}
+
+// C++ member function pointers
+struct PTMFTester {
+	typedef void (PTMFTester::*ConvertFunction)() [[clang::nolock]];
+
+	void convert() [[clang::nolock]];
+
+	ConvertFunction mConvertFunc;
+};
+
+void PTMFTester::convert() [[clang::nolock]]
+{
+	(this->*mConvertFunc)();
+}
+
+// Block variables
+void nl17(void (^blk)() [[clang::nolock]]) [[clang::nolock]] {
+	blk();
+}
+
+// References to blocks
+void nl18(void (^block)() [[clang::nolock]]) [[clang::nolock]]
+{
+	auto &ref = block;
+	ref();
+}
+
diff --git a/clang/test/Sema/attr-nolock-sema.cpp b/clang/test/Sema/attr-nolock-sema.cpp
index 28542be0a2ecd0..98a11067305d56 100644
--- a/clang/test/Sema/attr-nolock-sema.cpp
+++ b/clang/test/Sema/attr-nolock-sema.cpp
@@ -59,18 +59,37 @@ void type_conversions()
 	fp_noalloc = unannotated; // expected-warning {{attribute 'noalloc' should not be added via type conversion}}
 }
 
+#ifdef __cplusplus
+// There was a bug: noexcept and nolock could be individually removed in conversion, but not both	
+void type_conversions_2()
+{
+	auto receives_fp = [](void (*fp)()) {
+	};
+	
+	auto ne = +[]() noexcept {};
+	auto nl = +[]() [[clang::nolock]] {};
+	auto nl_ne = +[]() noexcept [[clang::nolock]] {};
+	
+	receives_fp(ne);
+	receives_fp(nl);
+	receives_fp(nl_ne);
+}
+#endif
+
 // --- VIRTUAL METHODS ---
+// Attributes propagate to overridden methods, so no diagnostics.
+// Check this in the syntax tests too.
 #ifdef __cplusplus
 struct Base {
 	virtual void f1();
-	virtual void nolock() noexcept [[clang::nolock]]; // expected-note {{overridden virtual function is here}}
-	virtual void noalloc() noexcept [[clang::noalloc]]; // expected-note {{overridden virtual function is here}}
+	virtual void nolock() noexcept [[clang::nolock]];
+	virtual void noalloc() noexcept [[clang::noalloc]];
 };
 
 struct Derived : public Base {
 	void f1() [[clang::nolock]] override;
-	void nolock() noexcept override; // expected-warning {{attribute 'nolock' on overriding function does not match base version}}
-	void noalloc() noexcept override; // expected-warning {{attribute 'noalloc' on overriding function does not match base version}}
+	void nolock() noexcept override;
+	void noalloc() noexcept override;
 };
 #endif // __cplusplus
 
@@ -89,26 +108,10 @@ void f2();
 #endif
 // Note: we verify that the attribute is actually seen during the constraints tests.
 
-
-// Ensure that the redeclaration's attribute is seen and diagnosed correctly.
-
-// void f2() {
-// 	static int x;
-// }
-// void f2() [[clang::nolock]];
-// 
-// void f3() [[clang::nolock]] {
-// 	f2();
-// }
-
-#if 0
-int f2();
-// redeclaration with a stronger constraint is OK.
-int f2() [[clang::nolock]]; // e xpected-note {{previous declaration is here}}
-int f2() { return 42; } // e xpected-warning {{attribute 'nolock' on function does not match previous declaration}}
-
-int f3();
-// redeclaration with a stronger constraint is OK.
-int f3() [[clang::noalloc]]; // e xpected-note {{previous declaration is here}}
-int f3() { return 42; } // e xpected-warning {{attribute 'noalloc' on function does not match previous declaration}}
-#endif
+// --- OVERLOADS ---
+#ifdef __cplusplus
+struct S {
+	void foo(); // expected-note {{previous declaration is here}}
+	void foo(); // expected-error {{class member cannot be redeclared}}
+};
+#endif // __cplusplus
diff --git a/clang/test/Sema/attr-nolock-syntax.cpp b/clang/test/Sema/attr-nolock-syntax.cpp
index 2c0d71ba93ddf0..8deb3e88cc7d25 100644
--- a/clang/test/Sema/attr-nolock-syntax.cpp
+++ b/clang/test/Sema/attr-nolock-syntax.cpp
@@ -53,6 +53,15 @@ void nl2() [[clang::noalloc]] [[clang::nolock]];
 decltype(nl1) nl3;
 // CHECK: FunctionDecl {{.*}} nl3 'decltype(nl1)':'void () __attribute__((clang_nolock))'
 
+// Attribute propagates from base class virtual method to overrides.
+struct Base {
+	virtual void nl_method() [[clang::nolock]];
+};
+struct Derived : public Base {
+	void nl_method() override;
+	// CHECK: CXXMethodDecl {{.*}} nl_method 'void () __attribute__((clang_nolock))'
+};
+
 // --- Blocks ---
 
 // On the type of the VarDecl holding a BlockDecl
diff --git a/clang/test/Sema/attr-nolock-wip.cpp b/clang/test/Sema/attr-nolock-wip.cpp
index ab8cf57d69ed0c..914c4d09764ee8 100644
--- a/clang/test/Sema/attr-nolock-wip.cpp
+++ b/clang/test/Sema/attr-nolock-wip.cpp
@@ -4,36 +4,82 @@
 #error "the 'nolock' attribute is not available"
 #endif
 
-// ============================================================================
 
-void f2(int);
-void f2(int) [[clang::nolock]]; // expected-note {{previous declaration is here}}
-void f2(int); // expected-warning {{attribute 'nolock' on function does not match previous declaration}}
+// The diagnostic for inference not following a non-inline method should override
+// the one for a virtual method.
 
-// ============================================================================
+struct HasVirtual {
+	virtual ~HasVirtual() = default;
+	virtual void method();
+};
+
+void nl999(HasVirtual& x) [[clang::nolock]] {
+	x.method();
+}
 
-#if 0
-// https://github.com/llvm/llvm-project/pull/84983#issuecomment-1994978033
 
-// the bug where AttributedType sugar gets lost on lambdas (when the "inferred" return type gets
-// converted to a concrete one) happens here and the nolock(false) attribute is lost from h.
 
-template <class T>
-void f(T a) [[clang::nolock]] { a(); }
+#if 0
+	using nl_sugar = int (*)(int) [[clang::nolock]];
+
+	void receives_fp_nl(nl_sugar fp) {
+	}
+	
+	int callback(int) noexcept [[clang::nolock]];
 
-void m()
+void type_conversions_2()
 {
-	auto g = []() [[clang::nolock]] {
+	auto receives_fp = [](void (*fp)()) {
 	};
 	
-	auto h = []() [[clang::nolock(false)]] {
-	};
+	//auto receives_fp_nl = [](void (*fp)() [[clang::nolock]]) {
+	//};
 
-	f(g);
-	f(h);
+	auto ne = +[]() noexcept {};
+	auto nl = +[]() [[clang::nolock]] {};
+	//auto nl_ne = +[](int x) noexcept [[clang::nolock]] -> int  { return x; };
+	
+	receives_fp(ne);
+	receives_fp(nl);
+// 	receives_fp(nl_ne);
+	
+	receives_fp_nl(callback);
 }
 #endif
 
+#if 0
+struct S {
+	void foo();
+	// void foo() noexcept; // error, redeclaration
+	// void foo() [[clang::nolock]]; // error, redeclaration
+	
+	using FP = void (*)();
+	using FPNE = void (*)() noexcept;
+	using FPNL = void (*)() [[clang::nolock]];
+	
+	void bar(FP x);
+	void bar(FPNE x);	// This is a distinct overload
+	void bar(FPNL x);	// This is a distinct overload
+};
+#endif
+
+// ============================================================================
+
+#if 0
+#define RT_UNSAFE_BEGIN(reason)                                   \
+	_Pragma("clang diagnostic push")                                 \
+	_Pragma("clang diagnostic ignored \"-Wunknown-warning-option\"") \
+	_Pragma("clang diagnostic ignored \"-Wfunction-effects\"")
+
+#define RT_UNSAFE_END \
+	_Pragma("clang diagnostic pop")
+
+#define RT_UNSAFE(...)  \
+	RT_UNSAFE_BEGIN("") \
+	__VA_ARGS__            \
+	RT_UNSAFE_END
+#endif
+
 // ============================================================================
 
 #if 0



More information about the cfe-commits mailing list