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

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Thu Mar 14 09:54:24 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 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] - 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)]];



More information about the cfe-commits mailing list