[clang] 7fe43ad - [Clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (#99656)

via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 2 17:14:55 PDT 2024


Author: Doug Wyatt
Date: 2024-10-03T02:14:51+02:00
New Revision: 7fe43ada28c31a9e9d82a76650d987a8b209755e

URL: https://github.com/llvm/llvm-project/commit/7fe43ada28c31a9e9d82a76650d987a8b209755e
DIFF: https://github.com/llvm/llvm-project/commit/7fe43ada28c31a9e9d82a76650d987a8b209755e.diff

LOG: [Clang] nonblocking/nonallocating attributes: 2nd pass caller/callee analysis (#99656)

- In Sema, when encountering Decls with function effects needing
verification, add them to a vector, DeclsWithEffectsToVerify.
- Update AST serialization to include DeclsWithEffectsToVerify.
- In AnalysisBasedWarnings, use DeclsWithEffectsToVerify as a work
queue, verifying functions with declared effects, and inferring (when
permitted and necessary) whether their callees have effects.

---------

Co-authored-by: Doug Wyatt <dwyatt at apple.com>
Co-authored-by: Sirraide <aeternalmail at gmail.com>
Co-authored-by: Erich Keane <ekeane at nvidia.com>

Added: 
    clang/lib/Sema/SemaFunctionEffects.cpp
    clang/test/Sema/attr-nonblocking-constraints-ms.cpp
    clang/test/Sema/attr-nonblocking-constraints.c
    clang/test/Sema/attr-nonblocking-constraints.cpp
    clang/test/SemaObjCXX/attr-nonblocking-constraints.mm

Modified: 
    clang/docs/ReleaseNotes.rst
    clang/include/clang/AST/Type.h
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticGroups.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/include/clang/Sema/Sema.h
    clang/include/clang/Serialization/ASTBitCodes.h
    clang/include/clang/Serialization/ASTReader.h
    clang/include/clang/Serialization/ASTWriter.h
    clang/lib/AST/Type.cpp
    clang/lib/Sema/CMakeLists.txt
    clang/lib/Sema/Sema.cpp
    clang/lib/Sema/SemaDecl.cpp
    clang/lib/Sema/SemaDeclCXX.cpp
    clang/lib/Sema/SemaExpr.cpp
    clang/lib/Sema/SemaLambda.cpp
    clang/lib/Serialization/ASTReader.cpp
    clang/lib/Serialization/ASTWriter.cpp
    clang/test/Headers/Inputs/include/setjmp.h
    clang/test/Misc/warning-wall.c
    clang/test/Sema/attr-nonblocking-sema.cpp
    clang/test/Sema/attr-nonblocking-syntax.cpp

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b7d287fdf4cc61..bb759cede22ff9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -632,6 +632,11 @@ New features
   if class of allocation and deallocation function mismatches.
   `Documentation <https://clang.llvm.org/docs/analyzer/checkers.html#unix-mismatcheddeallocator-c-c>`__.
 
+- Function effects, e.g. the ``nonblocking`` and ``nonallocating`` "performance constraint" 
+  attributes, are now verified. For example, for functions declared with the ``nonblocking`` 
+  attribute, the compiler can generate warnings about the use of any language features, or calls to
+  other functions, which may block.
+
 Crash and bug fixes
 ^^^^^^^^^^^^^^^^^^^
 

diff  --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 7126940058baed..8ff04cf89a6b91 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -49,6 +49,7 @@
 #include "llvm/Support/PointerLikeTypeTraits.h"
 #include "llvm/Support/TrailingObjects.h"
 #include "llvm/Support/type_traits.h"
+#include <bitset>
 #include <cassert>
 #include <cstddef>
 #include <cstdint>
@@ -119,6 +120,8 @@ class EnumDecl;
 class Expr;
 class ExtQualsTypeCommonBase;
 class FunctionDecl;
+class FunctionEffectsRef;
+class FunctionEffectKindSet;
 class FunctionEffectSet;
 class IdentifierInfo;
 class NamedDecl;
@@ -4712,12 +4715,13 @@ class FunctionEffect {
 public:
   /// Identifies the particular effect.
   enum class Kind : uint8_t {
-    None = 0,
-    NonBlocking = 1,
-    NonAllocating = 2,
-    Blocking = 3,
-    Allocating = 4
+    NonBlocking,
+    NonAllocating,
+    Blocking,
+    Allocating,
+    Last = Allocating
   };
+  constexpr static size_t KindCount = static_cast<size_t>(Kind::Last) + 1;
 
   /// Flags describing some behaviors of the effect.
   using Flags = unsigned;
@@ -4743,8 +4747,6 @@ class FunctionEffect {
   // be considered for uniqueness.
 
 public:
-  FunctionEffect() : FKind(Kind::None) {}
-
   explicit FunctionEffect(Kind K) : FKind(K) {}
 
   /// The kind of the effect.
@@ -4773,8 +4775,6 @@ class FunctionEffect {
     case Kind::Blocking:
     case Kind::Allocating:
       return 0;
-    case Kind::None:
-      break;
     }
     llvm_unreachable("unknown effect kind");
   }
@@ -4782,26 +4782,36 @@ class FunctionEffect {
   /// The description printed in diagnostics, e.g. 'nonblocking'.
   StringRef name() const;
 
-  /// Return true if the effect is allowed to be inferred on the callee,
-  /// which is either a FunctionDecl or BlockDecl.
+  friend raw_ostream &operator<<(raw_ostream &OS,
+                                 const FunctionEffect &Effect) {
+    OS << Effect.name();
+    return OS;
+  }
+
+  /// Determine whether the effect is allowed to be inferred on the callee,
+  /// which is either a FunctionDecl or BlockDecl. If the returned optional
+  /// is empty, inference is permitted; otherwise it holds the effect which
+  /// blocked inference.
   /// Example: This allows nonblocking(false) to prevent inference for the
   /// function.
-  bool canInferOnFunction(const Decl &Callee) const;
+  std::optional<FunctionEffect>
+  effectProhibitingInference(const Decl &Callee,
+                             FunctionEffectKindSet CalleeFX) const;
 
   // Return false for success. When true is returned for a direct call, then the
   // FE_InferrableOnCallees flag may trigger inference rather than an immediate
   // diagnostic. Caller should be assumed to have the effect (it may not have it
   // explicitly when inferring).
   bool shouldDiagnoseFunctionCall(bool Direct,
-                                  ArrayRef<FunctionEffect> CalleeFX) const;
+                                  FunctionEffectKindSet CalleeFX) const;
 
-  friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+  friend bool operator==(FunctionEffect LHS, FunctionEffect RHS) {
     return LHS.FKind == RHS.FKind;
   }
-  friend bool operator!=(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+  friend bool operator!=(FunctionEffect LHS, FunctionEffect RHS) {
     return !(LHS == RHS);
   }
-  friend bool operator<(const FunctionEffect &LHS, const FunctionEffect &RHS) {
+  friend bool operator<(FunctionEffect LHS, FunctionEffect RHS) {
     return LHS.FKind < RHS.FKind;
   }
 };
@@ -4829,13 +4839,14 @@ struct FunctionEffectWithCondition {
   FunctionEffect Effect;
   EffectConditionExpr Cond;
 
-  FunctionEffectWithCondition() = default;
-  FunctionEffectWithCondition(const FunctionEffect &E,
-                              const EffectConditionExpr &C)
+  FunctionEffectWithCondition(FunctionEffect E, const EffectConditionExpr &C)
       : Effect(E), Cond(C) {}
 
   /// Return a textual description of the effect, and its condition, if any.
   std::string description() const;
+
+  friend raw_ostream &operator<<(raw_ostream &OS,
+                                 const FunctionEffectWithCondition &CFE);
 };
 
 /// Support iteration in parallel through a pair of FunctionEffect and
@@ -4940,6 +4951,85 @@ class FunctionEffectsRef {
   void dump(llvm::raw_ostream &OS) const;
 };
 
+/// A mutable set of FunctionEffect::Kind.
+class FunctionEffectKindSet {
+  // For now this only needs to be a bitmap.
+  constexpr static size_t EndBitPos = FunctionEffect::KindCount;
+  using KindBitsT = std::bitset<EndBitPos>;
+
+  KindBitsT KindBits{};
+
+  explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
+
+  // Functions to translate between an effect kind, starting at 1, and a
+  // position in the bitset.
+
+  constexpr static size_t kindToPos(FunctionEffect::Kind K) {
+    return static_cast<size_t>(K);
+  }
+
+  constexpr static FunctionEffect::Kind posToKind(size_t Pos) {
+    return static_cast<FunctionEffect::Kind>(Pos);
+  }
+
+  // Iterates through the bits which are set.
+  class iterator {
+    const FunctionEffectKindSet *Outer = nullptr;
+    size_t Idx = 0;
+
+    // If Idx does not reference a set bit, advance it until it does,
+    // or until it reaches EndBitPos.
+    void advanceToNextSetBit() {
+      while (Idx < EndBitPos && !Outer->KindBits.test(Idx))
+        ++Idx;
+    }
+
+  public:
+    iterator();
+    iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
+      advanceToNextSetBit();
+    }
+    bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
+    bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }
+
+    iterator operator++() {
+      ++Idx;
+      advanceToNextSetBit();
+      return *this;
+    }
+
+    FunctionEffect operator*() const {
+      assert(Idx < EndBitPos && "Dereference of end iterator");
+      return FunctionEffect(posToKind(Idx));
+    }
+  };
+
+public:
+  FunctionEffectKindSet() = default;
+  explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }
+
+  iterator begin() const { return iterator(*this, 0); }
+  iterator end() const { return iterator(*this, EndBitPos); }
+
+  void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); }
+  void insert(FunctionEffectsRef FX) {
+    for (FunctionEffect Item : FX.effects())
+      insert(Item);
+  }
+  void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
+
+  bool empty() const { return KindBits.none(); }
+  bool contains(const FunctionEffect::Kind EK) const {
+    return KindBits.test(kindToPos(EK));
+  }
+  void dump(llvm::raw_ostream &OS) const;
+
+  static FunctionEffectKindSet 
diff erence(FunctionEffectKindSet LHS,
+                                          FunctionEffectKindSet RHS) {
+    return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits);
+  }
+};
+
 /// A mutable set of FunctionEffects and possibly conditions attached to them.
 /// Used to compare and merge effects on declarations.
 ///

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 53d88482698f00..b1512e22ee2dd4 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8482,9 +8482,9 @@ compiler warnings:
 - A redeclaration of a ``nonblocking`` or ``nonallocating`` function must also be declared with
   the same attribute (or a stronger one). A redeclaration may add an attribute.
 
-The warnings are controlled by ``-Wfunction-effects``, which is enabled by default.
+The warnings are controlled by ``-Wfunction-effects``, which is disabled by default.
 
-In a future commit, the compiler will diagnose function calls from ``nonblocking`` and ``nonallocating``
+The compiler also diagnoses function calls from ``nonblocking`` and ``nonallocating``
 functions to other functions which lack the appropriate attribute.
   }];
 }

diff  --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 7d81bdf827ea0c..41e719d4d57816 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1125,6 +1125,11 @@ def ThreadSafety : DiagGroup<"thread-safety",
 def ThreadSafetyVerbose : DiagGroup<"thread-safety-verbose">;
 def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">;
 
+// Warnings and notes related to the function effects system which underlies
+// the nonblocking and nonallocating attributes.
+def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;
+
 // Uniqueness Analysis warnings
 def Consumed       : DiagGroup<"consumed">;
 
@@ -1133,7 +1138,7 @@ def Consumed       : DiagGroup<"consumed">;
 // DefaultIgnore in addition to putting it here.
 def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool,
                             MisleadingIndentation, PackedNonPod,
-                            VLACxxExtension]>;
+                            VLACxxExtension, PerfConstraintImpliesNoexcept]>;
 
 // Warnings that should be in clang-cl /w4.
 def : DiagGroup<"CL4", [All, Extra]>;
@@ -1566,10 +1571,6 @@ def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">
 def UnsafeBufferUsageInLibcCall : DiagGroup<"unsafe-buffer-usage-in-libc-call">;
 def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer, UnsafeBufferUsageInLibcCall]>;
 
-// Warnings and notes related to the function effects system underlying
-// the nonblocking and nonallocating attributes.
-def FunctionEffects : DiagGroup<"function-effects">;
-
 // Warnings and notes InstallAPI verification.
 def InstallAPIViolation : DiagGroup<"installapi-violation">;
 

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index dae357eb2d370e..aa393f2859ed1d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10972,19 +10972,68 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
 
 // Function effects
+def warn_func_effect_violation : Warning<
+  "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+  "with '%1' attribute "
+  "must not %select{allocate or deallocate memory|throw or catch exceptions|"
+  "have static local variables|use thread-local variables|access ObjC methods or properties}2">,
+  InGroup<FunctionEffects>, DefaultIgnore;
+def note_func_effect_violation : Note<
+  "%select{function|constructor|destructor|lambda|block|member initializer}0 "
+  "cannot be inferred '%1' because it "
+  "%select{allocates or deallocates memory|throws or catches exceptions|"
+  "has a static local variable|uses a thread-local variable|"
+  "accesses an ObjC method or property}2">;
+def warn_func_effect_calls_func_without_effect : Warning<
+  "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+  "with '%1' attribute "
+  "must not call non-'%1' "
+  "%select{function|constructor|destructor|lambda|block}2 "
+  "'%3'">,
+  InGroup<FunctionEffects>, DefaultIgnore;
+def note_func_effect_calls_func_without_effect : Note<
+  "%select{function|constructor|destructor|lambda|block|member initializer}0 "
+  "cannot be inferred '%1' because it calls non-'%1' "
+  "%select{function|constructor|destructor|lambda|block}2 "
+  "'%3'">;
+def warn_func_effect_calls_expr_without_effect : Warning<
+  "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+  "with '%1' attribute "
+  "must not call non-'%1' expression">,
+  InGroup<FunctionEffects>, DefaultIgnore;
+def note_func_effect_call_extern : Note<
+  "declaration cannot be inferred '%0' because it has no definition in this translation unit">;
+def note_func_effect_call_disallows_inference : Note<
+  "%select{function|constructor|destructor|lambda|block}0 "
+  "does not permit inference of '%1' because it is declared '%2'">;
+def note_func_effect_call_indirect : Note<
+  "%select{virtual method|function pointer}0 cannot be inferred '%1'">;
+def warn_perf_constraint_implies_noexcept : Warning<
+  "%select{function|constructor|destructor|lambda|block}0 "
+  "with '%1' attribute should be declared noexcept">,
+  InGroup<PerfConstraintImpliesNoexcept>, DefaultIgnore;
+
+// FIXME: It would be nice if we could provide fuller template expansion notes.
+def note_func_effect_from_template : Note<
+  "in template expansion here">;
+def note_func_effect_in_constructor : Note<
+  "in%select{| implicit}0 constructor here">;
+def note_in_evaluating_default_argument : Note<
+  "in evaluating default argument here">;
+
 // spoofing nonblocking/nonallocating
 def warn_invalid_add_func_effects : Warning<
   "attribute '%0' should not be added via type conversion">,
-  InGroup<FunctionEffects>;
+  InGroup<FunctionEffects>, DefaultIgnore;
 def warn_mismatched_func_effect_override : Warning<
   "attribute '%0' on overriding function does not match base declaration">,
-  InGroup<FunctionEffects>;
+  InGroup<FunctionEffects>, DefaultIgnore;
 def warn_mismatched_func_effect_redeclaration : Warning<
   "attribute '%0' on function does not match previous declaration">,
-  InGroup<FunctionEffects>;
+  InGroup<FunctionEffects>, DefaultIgnore;
 def warn_conflicting_func_effects : Warning<
   "effects conflict when merging declarations; kept '%0', discarded '%1'">,
-  InGroup<FunctionEffects>;
+  InGroup<FunctionEffects>, DefaultIgnore;
 def err_func_with_effects_no_prototype : Error<
   "'%0' function must have a prototype">;
 

diff  --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 3d9f12d45d646e..bede971ce0191b 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -457,55 +457,6 @@ enum class FunctionEffectMode : uint8_t {
   Dependent // effect(expr) where expr is dependent.
 };
 
-struct FunctionEffectDiff {
-  enum class Kind { Added, Removed, ConditionMismatch };
-
-  FunctionEffect::Kind EffectKind;
-  Kind DiffKind;
-  FunctionEffectWithCondition Old; // invalid when Added.
-  FunctionEffectWithCondition New; // invalid when Removed.
-
-  StringRef effectName() const {
-    if (Old.Effect.kind() != FunctionEffect::Kind::None)
-      return Old.Effect.name();
-    return New.Effect.name();
-  }
-
-  /// Describes the result of effects 
diff ering between a base class's virtual
-  /// method and an overriding method in a subclass.
-  enum class OverrideResult {
-    NoAction,
-    Warn,
-    Merge // Merge missing effect from base to derived.
-  };
-
-  /// Return true if adding or removing the effect as part of a type conversion
-  /// should generate a diagnostic.
-  bool shouldDiagnoseConversion(QualType SrcType,
-                                const FunctionEffectsRef &SrcFX,
-                                QualType DstType,
-                                const FunctionEffectsRef &DstFX) const;
-
-  /// Return true if adding or removing the effect in a redeclaration should
-  /// generate a diagnostic.
-  bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction,
-                                   const FunctionEffectsRef &OldFX,
-                                   const FunctionDecl &NewFunction,
-                                   const FunctionEffectsRef &NewFX) const;
-
-  /// Return true if adding or removing the effect in a C++ virtual method
-  /// override should generate a diagnostic.
-  OverrideResult shouldDiagnoseMethodOverride(
-      const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
-      const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const;
-};
-
-struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
-  /// Caller should short-circuit by checking for equality first.
-  FunctionEffectDifferences(const FunctionEffectsRef &Old,
-                            const FunctionEffectsRef &New);
-};
-
 /// Sema - This implements semantic analysis and AST building for C.
 /// \nosubgrouping
 class Sema final : public SemaBase {
@@ -546,6 +497,7 @@ class Sema final : public SemaBase {
   // 32. Constraints and Concepts (SemaConcept.cpp)
   // 33. Types (SemaType.cpp)
   // 34. FixIt Helpers (SemaFixItUtils.cpp)
+  // 35. Function Effects (SemaFunctionEffects.cpp)
 
   /// \name Semantic Analysis
   /// Implementations are in Sema.cpp
@@ -851,30 +803,10 @@ class Sema final : public SemaBase {
   /// Warn when implicitly casting 0 to nullptr.
   void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
 
-  // ----- function effects ---
-
   /// Warn when implicitly changing function effects.
   void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                         SourceLocation Loc);
 
-  /// Warn and return true if adding an effect to a set would create a conflict.
-  bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
-                                         const FunctionEffectWithCondition &EC,
-                                         SourceLocation NewAttrLoc);
-
-  // Report a failure to merge function effects between declarations due to a
-  // conflict.
-  void
-  diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs,
-                                       SourceLocation NewLoc,
-                                       SourceLocation OldLoc);
-
-  /// Try to parse the conditional expression attached to an effect attribute
-  /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
-  /// optional on error.
-  std::optional<FunctionEffectMode>
-  ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName);
-
   /// makeUnavailableInSystemHeader - There is an error in the current
   /// context.  If we're still in a system header, and we can plausibly
   /// make the relevant declaration unavailable instead of erroring, do
@@ -15062,6 +14994,12 @@ class Sema final : public SemaBase {
     return hasAcceptableDefinition(D, &Hidden, Kind);
   }
 
+  /// Try to parse the conditional expression attached to an effect attribute
+  /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
+  /// optional on error.
+  std::optional<FunctionEffectMode>
+  ActOnEffectExpression(Expr *CondExpr, StringRef AttributeName);
+
 private:
   /// The implementation of RequireCompleteType
   bool RequireCompleteTypeImpl(SourceLocation Loc, QualType T,
@@ -15092,6 +15030,108 @@ class Sema final : public SemaBase {
   std::string getFixItZeroLiteralForType(QualType T, SourceLocation Loc) const;
 
   ///@}
+
+  //
+  //
+  // -------------------------------------------------------------------------
+  //
+  //
+
+  /// \name Function Effects
+  /// Implementations are in SemaFunctionEffects.cpp
+  ///@{
+public:
+  struct FunctionEffectDiff {
+    enum class Kind { Added, Removed, ConditionMismatch };
+
+    FunctionEffect::Kind EffectKind;
+    Kind DiffKind;
+    std::optional<FunctionEffectWithCondition>
+        Old; // Invalid when 'Kind' is 'Added'.
+    std::optional<FunctionEffectWithCondition>
+        New; // Invalid when 'Kind' is 'Removed'.
+
+    StringRef effectName() const {
+      if (Old)
+        return Old.value().Effect.name();
+      return New.value().Effect.name();
+    }
+
+    /// Describes the result of effects 
diff ering between a base class's virtual
+    /// method and an overriding method in a subclass.
+    enum class OverrideResult {
+      NoAction,
+      Warn,
+      Merge // Merge missing effect from base to derived.
+    };
+
+    /// Return true if adding or removing the effect as part of a type
+    /// conversion should generate a diagnostic.
+    bool shouldDiagnoseConversion(QualType SrcType,
+                                  const FunctionEffectsRef &SrcFX,
+                                  QualType DstType,
+                                  const FunctionEffectsRef &DstFX) const;
+
+    /// Return true if adding or removing the effect in a redeclaration should
+    /// generate a diagnostic.
+    bool shouldDiagnoseRedeclaration(const FunctionDecl &OldFunction,
+                                     const FunctionEffectsRef &OldFX,
+                                     const FunctionDecl &NewFunction,
+                                     const FunctionEffectsRef &NewFX) const;
+
+    /// Return true if adding or removing the effect in a C++ virtual method
+    /// override should generate a diagnostic.
+    OverrideResult shouldDiagnoseMethodOverride(
+        const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
+        const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const;
+  };
+
+  struct FunctionEffectDiffVector : public SmallVector<FunctionEffectDiff> {
+    /// Caller should short-circuit by checking for equality first.
+    FunctionEffectDiffVector(const FunctionEffectsRef &Old,
+                             const FunctionEffectsRef &New);
+  };
+
+  /// All functions/lambdas/blocks which have bodies and which have a non-empty
+  /// FunctionEffectsRef to be verified.
+  SmallVector<const Decl *> DeclsWithEffectsToVerify;
+
+  /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
+  /// are all null.
+  FunctionEffectKindSet AllEffectsToVerify;
+
+public:
+  /// Warn and return true if adding a function effect to a set would create a
+  /// conflict.
+  bool diagnoseConflictingFunctionEffect(const FunctionEffectsRef &FX,
+                                         const FunctionEffectWithCondition &EC,
+                                         SourceLocation NewAttrLoc);
+
+  // Report a failure to merge function effects between declarations due to a
+  // conflict.
+  void
+  diagnoseFunctionEffectMergeConflicts(const FunctionEffectSet::Conflicts &Errs,
+                                       SourceLocation NewLoc,
+                                       SourceLocation OldLoc);
+
+  /// Inline checks from the start of maybeAddDeclWithEffects, to
+  /// minimize performance impact on code not using effects.
+  template <class FuncOrBlockDecl>
+  void maybeAddDeclWithEffects(FuncOrBlockDecl *D) {
+    if (Context.hasAnyFunctionEffects())
+      if (FunctionEffectsRef FX = D->getFunctionEffects(); !FX.empty())
+        maybeAddDeclWithEffects(D, FX);
+  }
+
+  /// Potentially add a FunctionDecl or BlockDecl to DeclsWithEffectsToVerify.
+  void maybeAddDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+  /// Unconditionally add a Decl to DeclsWithEfffectsToVerify.
+  void addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX);
+
+  void performFunctionEffectAnalysis(TranslationUnitDecl *TU);
+
+  ///@}
 };
 
 DeductionFailureInfo

diff  --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index bde19a09d6ae07..1af1f4a10db290 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -730,6 +730,10 @@ enum ASTRecordTypes {
   /// canonical declaration for the lambda class from the same module as
   /// enclosing function.
   FUNCTION_DECL_TO_LAMBDAS_MAP = 71,
+
+  /// Record code for Sema's vector of functions/blocks with effects to
+  /// be verified.
+  DECLS_WITH_EFFECTS_TO_VERIFY = 72,
 };
 
 /// Record types used within a source manager block.

diff  --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index c1843218a4b8b1..aa88560b259a3b 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -984,6 +984,9 @@ class ASTReader
   /// Sema tracks these to emit deferred diags.
   llvm::SmallSetVector<GlobalDeclID, 4> DeclsToCheckForDeferredDiags;
 
+  /// The IDs of all decls with function effects to be checked.
+  SmallVector<GlobalDeclID> DeclsWithEffectsToVerify;
+
 private:
   struct ImportedSubmodule {
     serialization::SubmoduleID ID;

diff  --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index e21d41c8673143..198dd01b8d07a0 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -604,6 +604,7 @@ class ASTWriter : public ASTDeserializationListener,
   void WriteMSPointersToMembersPragmaOptions(Sema &SemaRef);
   void WritePackPragmaOptions(Sema &SemaRef);
   void WriteFloatControlPragmaOptions(Sema &SemaRef);
+  void WriteDeclsWithEffectsToVerify(Sema &SemaRef);
   void WriteModuleFileExtension(Sema &SemaRef,
                                 ModuleFileExtensionWriter &Writer);
 

diff  --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index c703e43f12a9a6..6f4958801cfe82 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5117,8 +5117,6 @@ FunctionEffect::Kind FunctionEffect::oppositeKind() const {
     return Kind::Allocating;
   case Kind::Allocating:
     return Kind::NonAllocating;
-  case Kind::None:
-    return Kind::None;
   }
   llvm_unreachable("unknown effect kind");
 }
@@ -5133,53 +5131,41 @@ StringRef FunctionEffect::name() const {
     return "blocking";
   case Kind::Allocating:
     return "allocating";
-  case Kind::None:
-    return "(none)";
   }
   llvm_unreachable("unknown effect kind");
 }
 
-bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
+std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
+    const Decl &Callee, FunctionEffectKindSet CalleeFX) const {
   switch (kind()) {
   case Kind::NonAllocating:
   case Kind::NonBlocking: {
-    FunctionEffectsRef CalleeFX;
-    if (auto *FD = Callee.getAsFunction())
-      CalleeFX = FD->getFunctionEffects();
-    else if (auto *BD = dyn_cast<BlockDecl>(&Callee))
-      CalleeFX = BD->getFunctionEffects();
-    else
-      return false;
-    for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
+    for (FunctionEffect Effect : CalleeFX) {
       // nonblocking/nonallocating cannot call allocating.
-      if (CalleeEC.Effect.kind() == Kind::Allocating)
-        return false;
+      if (Effect.kind() == Kind::Allocating)
+        return Effect;
       // nonblocking cannot call blocking.
-      if (kind() == Kind::NonBlocking &&
-          CalleeEC.Effect.kind() == Kind::Blocking)
-        return false;
+      if (kind() == Kind::NonBlocking && Effect.kind() == Kind::Blocking)
+        return Effect;
     }
-    return true;
+    return std::nullopt;
   }
 
   case Kind::Allocating:
   case Kind::Blocking:
-    return false;
-
-  case Kind::None:
-    assert(0 && "canInferOnFunction with None");
+    assert(0 && "effectProhibitingInference with non-inferable effect kind");
     break;
   }
   llvm_unreachable("unknown effect kind");
 }
 
 bool FunctionEffect::shouldDiagnoseFunctionCall(
-    bool Direct, ArrayRef<FunctionEffect> CalleeFX) const {
+    bool Direct, FunctionEffectKindSet CalleeFX) const {
   switch (kind()) {
   case Kind::NonAllocating:
   case Kind::NonBlocking: {
     const Kind CallerKind = kind();
-    for (const auto &Effect : CalleeFX) {
+    for (FunctionEffect Effect : CalleeFX) {
       const Kind EK = Effect.kind();
       // Does callee have same or stronger constraint?
       if (EK == CallerKind ||
@@ -5192,9 +5178,6 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
   case Kind::Allocating:
   case Kind::Blocking:
     return false;
-  case Kind::None:
-    assert(0 && "shouldDiagnoseFunctionCall with None");
-    break;
   }
   llvm_unreachable("unknown effect kind");
 }
@@ -5300,21 +5283,24 @@ FunctionEffectSet FunctionEffectSet::getUnion(FunctionEffectsRef LHS,
   return Combined;
 }
 
+namespace clang {
+
+raw_ostream &operator<<(raw_ostream &OS,
+                        const FunctionEffectWithCondition &CFE) {
+  OS << CFE.Effect.name();
+  if (Expr *E = CFE.Cond.getCondition()) {
+    OS << '(';
+    E->dump();
+    OS << ')';
+  }
+  return OS;
+}
+
+} // namespace clang
+
 LLVM_DUMP_METHOD void FunctionEffectsRef::dump(llvm::raw_ostream &OS) const {
   OS << "Effects{";
-  bool First = true;
-  for (const auto &CFE : *this) {
-    if (!First)
-      OS << ", ";
-    else
-      First = false;
-    OS << CFE.Effect.name();
-    if (Expr *E = CFE.Cond.getCondition()) {
-      OS << '(';
-      E->dump();
-      OS << ')';
-    }
-  }
+  llvm::interleaveComma(*this, OS);
   OS << "}";
 }
 
@@ -5322,6 +5308,12 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
   FunctionEffectsRef(*this).dump(OS);
 }
 
+LLVM_DUMP_METHOD void FunctionEffectKindSet::dump(llvm::raw_ostream &OS) const {
+  OS << "Effects{";
+  llvm::interleaveComma(*this, OS);
+  OS << "}";
+}
+
 FunctionEffectsRef
 FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX,
                            ArrayRef<EffectConditionExpr> Conds) {

diff  --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 2cee4f5ef6e99c..719c3a9312ec15 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -55,6 +55,7 @@ add_clang_library(clangSema
   SemaExprMember.cpp
   SemaExprObjC.cpp
   SemaFixItUtils.cpp
+  SemaFunctionEffects.cpp
   SemaHLSL.cpp
   SemaHexagon.cpp
   SemaInit.cpp

diff  --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 4be7dfbc293927..f05760428458b1 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -643,7 +643,7 @@ void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
   const auto SrcFX = FunctionEffectsRef::get(SrcType);
   const auto DstFX = FunctionEffectsRef::get(DstType);
   if (SrcFX != DstFX) {
-    for (const auto &Diff : FunctionEffectDifferences(SrcFX, DstFX)) {
+    for (const auto &Diff : FunctionEffectDiffVector(SrcFX, DstFX)) {
       if (Diff.shouldDiagnoseConversion(SrcType, SrcFX, DstType, DstFX))
         Diag(Loc, diag::warn_invalid_add_func_effects) << Diff.effectName();
     }
@@ -1529,6 +1529,9 @@ void Sema::ActOnEndOfTranslationUnit() {
 
   AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl());
 
+  if (Context.hasAnyFunctionEffects())
+    performFunctionEffectAnalysis(Context.getTranslationUnitDecl());
+
   // Check we've noticed that we're no longer parsing the initializer for every
   // variable. If we miss cases, then at best we have a performance issue and
   // at worst a rejects-valid bug.
@@ -2774,156 +2777,3 @@ bool Sema::isDeclaratorFunctionLike(Declarator &D) {
   });
   return Result;
 }
-
-FunctionEffectDifferences::FunctionEffectDifferences(
-    const FunctionEffectsRef &Old, const FunctionEffectsRef &New) {
-
-  FunctionEffectsRef::iterator POld = Old.begin();
-  FunctionEffectsRef::iterator OldEnd = Old.end();
-  FunctionEffectsRef::iterator PNew = New.begin();
-  FunctionEffectsRef::iterator NewEnd = New.end();
-
-  while (true) {
-    int cmp = 0;
-    if (POld == OldEnd) {
-      if (PNew == NewEnd)
-        break;
-      cmp = 1;
-    } else if (PNew == NewEnd)
-      cmp = -1;
-    else {
-      FunctionEffectWithCondition Old = *POld;
-      FunctionEffectWithCondition New = *PNew;
-      if (Old.Effect.kind() < New.Effect.kind())
-        cmp = -1;
-      else if (New.Effect.kind() < Old.Effect.kind())
-        cmp = 1;
-      else {
-        cmp = 0;
-        if (Old.Cond.getCondition() != New.Cond.getCondition()) {
-          // FIXME: Cases where the expressions are equivalent but
-          // don't have the same identity.
-          push_back(FunctionEffectDiff{
-              Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
-              Old, New});
-        }
-      }
-    }
-
-    if (cmp < 0) {
-      // removal
-      FunctionEffectWithCondition Old = *POld;
-      push_back(FunctionEffectDiff{
-          Old.Effect.kind(), FunctionEffectDiff::Kind::Removed, Old, {}});
-      ++POld;
-    } else if (cmp > 0) {
-      // addition
-      FunctionEffectWithCondition New = *PNew;
-      push_back(FunctionEffectDiff{
-          New.Effect.kind(), FunctionEffectDiff::Kind::Added, {}, New});
-      ++PNew;
-    } else {
-      ++POld;
-      ++PNew;
-    }
-  }
-}
-
-bool FunctionEffectDiff::shouldDiagnoseConversion(
-    QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType,
-    const FunctionEffectsRef &DstFX) const {
-
-  switch (EffectKind) {
-  case FunctionEffect::Kind::NonAllocating:
-    // nonallocating can't be added (spoofed) during a conversion, unless we
-    // have nonblocking.
-    if (DiffKind == Kind::Added) {
-      for (const auto &CFE : SrcFX) {
-        if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
-          return false;
-      }
-    }
-    [[fallthrough]];
-  case FunctionEffect::Kind::NonBlocking:
-    // nonblocking can't be added (spoofed) during a conversion.
-    switch (DiffKind) {
-    case Kind::Added:
-      return true;
-    case Kind::Removed:
-      return false;
-    case Kind::ConditionMismatch:
-      // FIXME: Condition mismatches are too coarse right now -- expressions
-      // which are equivalent but don't have the same identity are detected as
-      // mismatches. We're going to diagnose those anyhow until expression
-      // matching is better.
-      return true;
-    }
-    llvm_unreachable("Unhandled FunctionEffectDiff::Kind enum");
-  case FunctionEffect::Kind::Blocking:
-  case FunctionEffect::Kind::Allocating:
-    return false;
-  case FunctionEffect::Kind::None:
-    break;
-  }
-  llvm_unreachable("unknown effect kind");
-}
-
-bool FunctionEffectDiff::shouldDiagnoseRedeclaration(
-    const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
-    const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
-  switch (EffectKind) {
-  case FunctionEffect::Kind::NonAllocating:
-  case FunctionEffect::Kind::NonBlocking:
-    // nonblocking/nonallocating can't be removed in a redeclaration.
-    switch (DiffKind) {
-    case Kind::Added:
-      return false; // No diagnostic.
-    case Kind::Removed:
-      return true; // Issue diagnostic.
-    case Kind::ConditionMismatch:
-      // All these forms of mismatches are diagnosed.
-      return true;
-    }
-    llvm_unreachable("Unhandled FunctionEffectDiff::Kind enum");
-  case FunctionEffect::Kind::Blocking:
-  case FunctionEffect::Kind::Allocating:
-    return false;
-  case FunctionEffect::Kind::None:
-    break;
-  }
-  llvm_unreachable("unknown effect kind");
-}
-
-FunctionEffectDiff::OverrideResult
-FunctionEffectDiff::shouldDiagnoseMethodOverride(
-    const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
-    const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
-  switch (EffectKind) {
-  case FunctionEffect::Kind::NonAllocating:
-  case FunctionEffect::Kind::NonBlocking:
-    switch (DiffKind) {
-
-    // If added on an override, that's fine and not diagnosed.
-    case Kind::Added:
-      return OverrideResult::NoAction;
-
-    // If missing from an override (removed), propagate from base to derived.
-    case Kind::Removed:
-      return OverrideResult::Merge;
-
-    // If there's a mismatch involving the effect's polarity or condition,
-    // issue a warning.
-    case Kind::ConditionMismatch:
-      return OverrideResult::Warn;
-    }
-    llvm_unreachable("Unhandled FunctionEffectDiff::Kind enum");
-
-  case FunctionEffect::Kind::Blocking:
-  case FunctionEffect::Kind::Allocating:
-    return OverrideResult::NoAction;
-
-  case FunctionEffect::Kind::None:
-    break;
-  }
-  llvm_unreachable("unknown effect kind");
-}

diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index add28b370bcfcc..2bf610746bc317 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3803,7 +3803,7 @@ 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 = FunctionEffectDifferences(OldFX, NewFX);
+      const auto Diffs = FunctionEffectDiffVector(OldFX, NewFX);
       for (const auto &Diff : Diffs) {
         if (Diff.shouldDiagnoseRedeclaration(*Old, OldFX, *New, NewFX)) {
           Diag(New->getLocation(),
@@ -3839,6 +3839,11 @@ bool Sema::MergeFunctionDecl(FunctionDecl *New, NamedDecl *&OldD, Scope *S,
           OldQTypeForComparison = Context.getFunctionType(
               OldFPT->getReturnType(), OldFPT->getParamTypes(), EPI);
         }
+        if (OldFX.empty()) {
+          // A redeclaration may add the attribute to a previously seen function
+          // body which needs to be verified.
+          maybeAddDeclWithEffects(Old, MergedFX);
+        }
       }
     }
   }
@@ -15677,6 +15682,8 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
       getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
     Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
 
+  maybeAddDeclWithEffects(FD);
+
   return D;
 }
 
@@ -20303,59 +20310,3 @@ bool Sema::shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee) {
   return LangOpts.CUDA && !LangOpts.CUDAIsDevice &&
          CUDA().IdentifyTarget(Callee) == CUDAFunctionTarget::Global;
 }
-
-void Sema::diagnoseFunctionEffectMergeConflicts(
-    const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc,
-    SourceLocation OldLoc) {
-  for (const FunctionEffectSet::Conflict &Conflict : Errs) {
-    Diag(NewLoc, diag::warn_conflicting_func_effects)
-        << Conflict.Kept.description() << Conflict.Rejected.description();
-    Diag(OldLoc, diag::note_previous_declaration);
-  }
-}
-
-bool Sema::diagnoseConflictingFunctionEffect(
-    const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC,
-    SourceLocation NewAttrLoc) {
-  // If the new effect has a condition, we can't detect conflicts until the
-  // condition is resolved.
-  if (NewEC.Cond.getCondition() != nullptr)
-    return false;
-
-  // Diagnose the new attribute as incompatible with a previous one.
-  auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
-    Diag(NewAttrLoc, diag::err_attributes_are_not_compatible)
-        << ("'" + NewEC.description() + "'")
-        << ("'" + PrevEC.description() + "'") << false;
-    // We don't necessarily have the location of the previous attribute,
-    // so no note.
-    return true;
-  };
-
-  // Compare against previous attributes.
-  FunctionEffect::Kind NewKind = NewEC.Effect.kind();
-
-  for (const FunctionEffectWithCondition &PrevEC : FX) {
-    // Again, can't check yet when the effect is conditional.
-    if (PrevEC.Cond.getCondition() != nullptr)
-      continue;
-
-    FunctionEffect::Kind PrevKind = PrevEC.Effect.kind();
-    // Note that we allow PrevKind == NewKind; it's redundant and ignored.
-
-    if (PrevEC.Effect.oppositeKind() == NewKind)
-      return Incompatible(PrevEC);
-
-    // A new allocating is incompatible with a previous nonblocking.
-    if (PrevKind == FunctionEffect::Kind::NonBlocking &&
-        NewKind == FunctionEffect::Kind::Allocating)
-      return Incompatible(PrevEC);
-
-    // A new nonblocking is incompatible with a previous allocating.
-    if (PrevKind == FunctionEffect::Kind::Allocating &&
-        NewKind == FunctionEffect::Kind::NonBlocking)
-      return Incompatible(PrevEC);
-  }
-
-  return false;
-}

diff  --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index d8cdfcf8c6ec05..9cb2ed02a3f764 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18181,7 +18181,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
 
     if (OldFX != NewFXOrig) {
       FunctionEffectSet NewFX(NewFXOrig);
-      const auto Diffs = FunctionEffectDifferences(OldFX, NewFX);
+      const auto Diffs = FunctionEffectDiffVector(OldFX, NewFX);
       FunctionEffectSet::Conflicts Errs;
       for (const auto &Diff : Diffs) {
         switch (Diff.shouldDiagnoseMethodOverride(*Old, OldFX, *New, NewFX)) {
@@ -18194,7 +18194,7 @@ bool Sema::CheckOverridingFunctionAttributes(CXXMethodDecl *New,
               << Old->getReturnTypeSourceRange();
           break;
         case FunctionEffectDiff::OverrideResult::Merge: {
-          NewFX.insert(Diff.Old, Errs);
+          NewFX.insert(Diff.Old.value(), Errs);
           const auto *NewFT = New->getType()->castAs<FunctionProtoType>();
           FunctionProtoType::ExtProtoInfo EPI = NewFT->getExtProtoInfo();
           EPI.FunctionEffects = FunctionEffectsRef(NewFX);

diff  --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2db9d1fc69ed1e..ae7bcedfb28c73 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16212,6 +16212,8 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
   BlockDecl *BD = BSI->TheDecl;
 
+  maybeAddDeclWithEffects(BD);
+
   if (BSI->HasImplicitReturnType)
     deduceClosureReturnType(*BSI);
 

diff  --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
new file mode 100644
index 00000000000000..0fb18d207a50ba
--- /dev/null
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -0,0 +1,1577 @@
+//=== SemaFunctionEffects.cpp - Sema handling of function effects ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements Sema handling of function effects.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Sema/SemaInternal.h"
+
+#define DEBUG_TYPE "effectanalysis"
+
+using namespace clang;
+
+namespace {
+
+enum class ViolationID : uint8_t {
+  None = 0, // Sentinel for an empty Violation.
+  // These first 5 map to a %select{} in one of several FunctionEffects
+  // diagnostics, e.g. warn_func_effect_violation.
+  BaseDiagnosticIndex,
+  AllocatesMemory = BaseDiagnosticIndex,
+  ThrowsOrCatchesExceptions,
+  HasStaticLocalVariable,
+  AccessesThreadLocalVariable,
+  AccessesObjCMethodOrProperty,
+
+  // These only apply to callees, where the analysis stops at the Decl.
+  DeclDisallowsInference,
+
+  // These both apply to indirect calls. The 
diff erence is that sometimes
+  // we have an actual Decl (generally a variable) which is the function
+  // pointer being called, and sometimes, typically due to a cast, we only
+  // have an expression.
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Information about the AST context in which a violation was found, so
+// that diagnostics can point to the correct source.
+class ViolationSite {
+public:
+  enum class Kind : uint8_t {
+    Default, // Function body.
+    MemberInitializer,
+    DefaultArgExpr
+  };
+
+private:
+  llvm::PointerIntPair<CXXDefaultArgExpr *, 2, Kind> Impl;
+
+public:
+  ViolationSite() = default;
+
+  explicit ViolationSite(CXXDefaultArgExpr *E)
+      : Impl(E, Kind::DefaultArgExpr) {}
+
+  Kind kind() const { return static_cast<Kind>(Impl.getInt()); }
+  CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); }
+
+  void setKind(Kind K) { Impl.setPointerAndInt(nullptr, K); }
+};
+
+// Represents a violation of the rules, potentially for the entire duration of
+// the analysis phase, in order to refer to it when explaining why a caller has
+// been made unsafe by a callee. Can be transformed into either a Diagnostic
+// (warning or a note), depending on whether the violation pertains to a
+// function failing to be verifed as holding an effect vs. a function failing to
+// be inferred as holding that effect.
+struct Violation {
+  FunctionEffect Effect;
+  std::optional<FunctionEffect>
+      CalleeEffectPreventingInference; // Only for certain IDs; can be nullopt.
+  ViolationID ID = ViolationID::None;
+  ViolationSite Site;
+  SourceLocation Loc;
+  const Decl *Callee =
+      nullptr; // Only valid for ViolationIDs Calls{Decl,Expr}WithoutEffect.
+
+  Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS,
+            SourceLocation Loc, const Decl *Callee = nullptr,
+            std::optional<FunctionEffect> CalleeEffect = std::nullopt)
+      : Effect(Effect), CalleeEffectPreventingInference(CalleeEffect), ID(ID),
+        Site(VS), Loc(Loc), Callee(Callee) {}
+
+  unsigned diagnosticSelectIndex() const {
+    return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex);
+  }
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallableType : uint8_t {
+  // Unknown: probably function pointer.
+  Unknown,
+  Function,
+  Virtual,
+  Block
+};
+
+// Return whether a function's effects CAN be verified.
+// The question of whether it SHOULD be verified is independent.
+static bool functionIsVerifiable(const FunctionDecl *FD) {
+  if (FD->isTrivial()) {
+    // Otherwise `struct x { int a; };` would have an unverifiable default
+    // constructor.
+    return true;
+  }
+  return FD->hasBody();
+}
+
+static bool isNoexcept(const FunctionDecl *FD) {
+  const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
+  return FPT && (FPT->isNothrow() || FD->hasAttr<NoThrowAttr>());
+}
+
+// This list is probably incomplete.
+// FIXME: Investigate:
+// __builtin_eh_return?
+// __builtin_allow_runtime_check?
+// __builtin_unwind_init and other similar things that sound exception-related.
+// va_copy?
+// coroutines?
+static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
+  FunctionEffectKindSet Result;
+
+  switch (BuiltinID) {
+  case 0:  // Not builtin.
+  default: // By default, builtins have no known effects.
+    break;
+
+  // These allocate/deallocate heap memory.
+  case Builtin::ID::BI__builtin_calloc:
+  case Builtin::ID::BI__builtin_malloc:
+  case Builtin::ID::BI__builtin_realloc:
+  case Builtin::ID::BI__builtin_free:
+  case Builtin::ID::BI__builtin_operator_delete:
+  case Builtin::ID::BI__builtin_operator_new:
+  case Builtin::ID::BIaligned_alloc:
+  case Builtin::ID::BIcalloc:
+  case Builtin::ID::BImalloc:
+  case Builtin::ID::BImemalign:
+  case Builtin::ID::BIrealloc:
+  case Builtin::ID::BIfree:
+
+  case Builtin::ID::BIfopen:
+  case Builtin::ID::BIpthread_create:
+  case Builtin::ID::BI_Block_object_dispose:
+    Result.insert(FunctionEffect(FunctionEffect::Kind::Allocating));
+    break;
+
+  // These block in some other way than allocating memory.
+  // longjmp() and friends are presumed unsafe because they are the moral
+  // equivalent of throwing a C++ exception, which is unsafe.
+  case Builtin::ID::BIlongjmp:
+  case Builtin::ID::BI_longjmp:
+  case Builtin::ID::BIsiglongjmp:
+  case Builtin::ID::BI__builtin_longjmp:
+  case Builtin::ID::BIobjc_exception_throw:
+
+  // Objective-C runtime.
+  case Builtin::ID::BIobjc_msgSend:
+  case Builtin::ID::BIobjc_msgSend_fpret:
+  case Builtin::ID::BIobjc_msgSend_fp2ret:
+  case Builtin::ID::BIobjc_msgSend_stret:
+  case Builtin::ID::BIobjc_msgSendSuper:
+  case Builtin::ID::BIobjc_getClass:
+  case Builtin::ID::BIobjc_getMetaClass:
+  case Builtin::ID::BIobjc_enumerationMutation:
+  case Builtin::ID::BIobjc_assign_ivar:
+  case Builtin::ID::BIobjc_assign_global:
+  case Builtin::ID::BIobjc_sync_enter:
+  case Builtin::ID::BIobjc_sync_exit:
+  case Builtin::ID::BINSLog:
+  case Builtin::ID::BINSLogv:
+
+  // stdio.h
+  case Builtin::ID::BIfread:
+  case Builtin::ID::BIfwrite:
+
+  // stdio.h: printf family.
+  case Builtin::ID::BIprintf:
+  case Builtin::ID::BI__builtin_printf:
+  case Builtin::ID::BIfprintf:
+  case Builtin::ID::BIsnprintf:
+  case Builtin::ID::BIsprintf:
+  case Builtin::ID::BIvprintf:
+  case Builtin::ID::BIvfprintf:
+  case Builtin::ID::BIvsnprintf:
+  case Builtin::ID::BIvsprintf:
+
+  // stdio.h: scanf family.
+  case Builtin::ID::BIscanf:
+  case Builtin::ID::BIfscanf:
+  case Builtin::ID::BIsscanf:
+  case Builtin::ID::BIvscanf:
+  case Builtin::ID::BIvfscanf:
+  case Builtin::ID::BIvsscanf:
+    Result.insert(FunctionEffect(FunctionEffect::Kind::Blocking));
+    break;
+  }
+
+  return Result;
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, or function pointer.
+struct CallableInfo {
+  // CDecl holds the function's definition, if any.
+  // FunctionDecl if CallableType::Function or Virtual
+  // BlockDecl if CallableType::Block
+  const Decl *CDecl;
+
+  // Remember whether the callable is a function, block, virtual method,
+  // or (presumed) function pointer.
+  CallableType CType = CallableType::Unknown;
+
+  // Remember whether the callable is an operator new or delete function,
+  // so that calls to them are reported more meaningfully, as memory
+  // allocations.
+  SpecialFuncType FuncType = SpecialFuncType::None;
+
+  // We inevitably want to know the callable's declared effects, so cache them.
+  FunctionEffectKindSet Effects;
+
+  CallableInfo(const Decl &CD, SpecialFuncType FT = SpecialFuncType::None)
+      : CDecl(&CD), FuncType(FT) {
+    FunctionEffectsRef DeclEffects;
+    if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+      // Use the function's definition, if any.
+      if (const FunctionDecl *Def = FD->getDefinition())
+        CDecl = FD = Def;
+      CType = CallableType::Function;
+      if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
+          Method && Method->isVirtual())
+        CType = CallableType::Virtual;
+      DeclEffects = FD->getFunctionEffects();
+    } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
+      CType = CallableType::Block;
+      DeclEffects = BD->getFunctionEffects();
+    } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
+      // ValueDecl is function, enum, or variable, so just look at its type.
+      DeclEffects = FunctionEffectsRef::get(VD->getType());
+    }
+    Effects = FunctionEffectKindSet(DeclEffects);
+  }
+
+  CallableType type() const { return CType; }
+
+  bool isCalledDirectly() const {
+    return CType == CallableType::Function || CType == CallableType::Block;
+  }
+
+  bool isVerifiable() const {
+    switch (CType) {
+    case CallableType::Unknown:
+    case CallableType::Virtual:
+      return false;
+    case CallableType::Block:
+      return true;
+    case CallableType::Function:
+      return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+    }
+    llvm_unreachable("undefined CallableType");
+  }
+
+  /// Generate a name for logging and diagnostics.
+  std::string getNameForDiagnostic(Sema &S) const {
+    std::string Name;
+    llvm::raw_string_ostream OS(Name);
+
+    if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
+      FD->getNameForDiagnostic(OS, S.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);
+    return Name;
+  }
+};
+
+// ----------
+// Map effects to single Violations, to hold the first (of potentially many)
+// violations pertaining to an effect, per function.
+class EffectToViolationMap {
+  // Since we currently only have a tiny number of effects (typically no more
+  // than 1), use a SmallVector with an inline capacity of 1. Since it
+  // is often empty, use a unique_ptr to the SmallVector.
+  // Note that Violation itself contains a FunctionEffect which is the key.
+  // FIXME: Is there a way to simplify this using existing data structures?
+  using ImplVec = llvm::SmallVector<Violation, 1>;
+  std::unique_ptr<ImplVec> Impl;
+
+public:
+  // Insert a new Violation if we do not already have one for its effect.
+  void maybeInsert(const Violation &Viol) {
+    if (Impl == nullptr)
+      Impl = std::make_unique<ImplVec>();
+    else if (lookup(Viol.Effect) != nullptr)
+      return;
+
+    Impl->push_back(Viol);
+  }
+
+  const Violation *lookup(FunctionEffect Key) {
+    if (Impl == nullptr)
+      return nullptr;
+
+    auto *Iter = llvm::find_if(
+        *Impl, [&](const auto &Item) { return Item.Effect == Key; });
+    return Iter != Impl->end() ? &*Iter : nullptr;
+  }
+
+  size_t size() const { return Impl ? Impl->size() : 0; }
+};
+
+// ----------
+// State pertaining to a function whose AST is walked and whose effect analysis
+// is dependent on a subsequent analysis of other functions.
+class PendingFunctionAnalysis {
+  friend class CompleteFunctionAnalysis;
+
+public:
+  struct DirectCall {
+    const Decl *Callee;
+    SourceLocation CallLoc;
+    // Not all recursive calls are detected, just enough
+    // to break cycles.
+    bool Recursed = false;
+    ViolationSite VSite;
+
+    DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite)
+        : Callee(D), CallLoc(CallLoc), VSite(VSite) {}
+  };
+
+  // We always have two disjoint sets of effects to verify:
+  // 1. Effects declared explicitly by this function.
+  // 2. All other inferrable effects needing verification.
+  FunctionEffectKindSet DeclaredVerifiableEffects;
+  FunctionEffectKindSet EffectsToInfer;
+
+private:
+  // Violations pertaining to the function's explicit effects.
+  SmallVector<Violation, 0> ViolationsForExplicitEffects;
+
+  // Violations pertaining to other, non-explicit, inferrable effects.
+  EffectToViolationMap InferrableEffectToFirstViolation;
+
+  // These unverified direct calls are what keeps the analysis "pending",
+  // until the callees can be verified.
+  SmallVector<DirectCall, 0> UnverifiedDirectCalls;
+
+public:
+  PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo,
+                          FunctionEffectKindSet AllInferrableEffectsToVerify)
+      : DeclaredVerifiableEffects(CInfo.Effects) {
+    // Check for effects we are not allowed to infer.
+    FunctionEffectKindSet InferrableEffects;
+
+    for (FunctionEffect effect : AllInferrableEffectsToVerify) {
+      std::optional<FunctionEffect> ProblemCalleeEffect =
+          effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
+      if (!ProblemCalleeEffect)
+        InferrableEffects.insert(effect);
+      else {
+        // Add a Violation for this effect if a caller were to
+        // try to infer it.
+        InferrableEffectToFirstViolation.maybeInsert(Violation(
+            effect, ViolationID::DeclDisallowsInference, ViolationSite{},
+            CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
+      }
+    }
+    // InferrableEffects is now the set of inferrable effects which are not
+    // prohibited.
+    EffectsToInfer = FunctionEffectKindSet::
diff erence(
+        InferrableEffects, DeclaredVerifiableEffects);
+  }
+
+  // Hide the way that Violations for explicitly required effects vs. inferred
+  // ones are handled 
diff erently.
+  void checkAddViolation(bool Inferring, const Violation &NewViol) {
+    if (!Inferring)
+      ViolationsForExplicitEffects.push_back(NewViol);
+    else
+      InferrableEffectToFirstViolation.maybeInsert(NewViol);
+  }
+
+  void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc,
+                               ViolationSite VSite) {
+    UnverifiedDirectCalls.emplace_back(D, CallLoc, VSite);
+  }
+
+  // Analysis is complete when there are no unverified direct calls.
+  bool isComplete() const { return UnverifiedDirectCalls.empty(); }
+
+  const Violation *violationForInferrableEffect(FunctionEffect effect) {
+    return InferrableEffectToFirstViolation.lookup(effect);
+  }
+
+  // Mutable because caller may need to set a DirectCall's Recursing flag.
+  MutableArrayRef<DirectCall> unverifiedCalls() {
+    assert(!isComplete());
+    return UnverifiedDirectCalls;
+  }
+
+  ArrayRef<Violation> getSortedViolationsForExplicitEffects(SourceManager &SM) {
+    if (!ViolationsForExplicitEffects.empty())
+      llvm::sort(ViolationsForExplicitEffects,
+                 [&SM](const Violation &LHS, const Violation &RHS) {
+                   return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+                 });
+    return ViolationsForExplicitEffects;
+  }
+
+  void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
+    OS << "Pending: Declared ";
+    DeclaredVerifiableEffects.dump(OS);
+    OS << ", " << ViolationsForExplicitEffects.size() << " violations; ";
+    OS << " Infer ";
+    EffectsToInfer.dump(OS);
+    OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
+    if (!UnverifiedDirectCalls.empty()) {
+      OS << "; Calls: ";
+      for (const DirectCall &Call : UnverifiedDirectCalls) {
+        CallableInfo CI(*Call.Callee);
+        OS << " " << CI.getNameForDiagnostic(SemaRef);
+      }
+    }
+    OS << "\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.
+  FunctionEffectKindSet VerifiedEffects;
+
+private:
+  // This is used to generate notes about failed inference.
+  EffectToViolationMap InferrableEffectToFirstViolation;
+
+public:
+  // The incoming Pending analysis is consumed (member(s) are moved-from).
+  CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending,
+                           FunctionEffectKindSet DeclaredEffects,
+                           FunctionEffectKindSet AllInferrableEffectsToVerify)
+      : VerifiedEffects(DeclaredEffects) {
+    for (FunctionEffect effect : AllInferrableEffectsToVerify)
+      if (Pending.violationForInferrableEffect(effect) == nullptr)
+        VerifiedEffects.insert(effect);
+
+    InferrableEffectToFirstViolation =
+        std::move(Pending.InferrableEffectToFirstViolation);
+  }
+
+  const Violation *firstViolationForEffect(FunctionEffect Effect) {
+    return InferrableEffectToFirstViolation.lookup(Effect);
+  }
+
+  void dump(llvm::raw_ostream &OS) const {
+    OS << "Complete: Verified ";
+    VerifiedEffects.dump(OS);
+    OS << "; Infer ";
+    OS << InferrableEffectToFirstViolation.size() << " violations\n";
+  }
+};
+
+// ==========
+class Analyzer {
+  Sema &S;
+
+  // Subset of Sema.AllEffectsToVerify
+  FunctionEffectKindSet AllInferrableEffectsToVerify;
+
+  using FuncAnalysisPtr =
+      llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
+
+  // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
+  // than complete state, so use 
diff erent objects to represent them.
+  // The state pointers are owned by the container.
+  class AnalysisMap : llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+    using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
+
+  public:
+    ~AnalysisMap();
+
+    // Use non-public inheritance in order to maintain the invariant
+    // that lookups and insertions are via the canonical Decls.
+
+    FuncAnalysisPtr lookup(const Decl *Key) const {
+      return Base::lookup(Key->getCanonicalDecl());
+    }
+
+    FuncAnalysisPtr &operator[](const Decl *Key) {
+      return Base::operator[](Key->getCanonicalDecl());
+    }
+
+    /// Shortcut for the case where we only care about completed analysis.
+    CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
+      if (FuncAnalysisPtr AP = lookup(D);
+          isa_and_nonnull<CompleteFunctionAnalysis *>(AP))
+        return AP.get<CompleteFunctionAnalysis *>();
+      return nullptr;
+    }
+
+    void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
+      OS << "\nAnalysisMap:\n";
+      for (const auto &item : *this) {
+        CallableInfo CI(*item.first);
+        const auto AP = item.second;
+        OS << item.first << " " << CI.getNameForDiagnostic(SemaRef) << " : ";
+        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(SemaRef, OS);
+        } else
+          llvm_unreachable("never");
+      }
+      OS << "---\n";
+    }
+  };
+  AnalysisMap DeclAnalysis;
+
+public:
+  Analyzer(Sema &S) : S(S) {}
+
+  void run(const TranslationUnitDecl &TU) {
+    // Gather all of the effects to be verified to see what operations need to
+    // be checked, and to see which ones are inferrable.
+    for (FunctionEffect Effect : S.AllEffectsToVerify) {
+      const FunctionEffect::Flags Flags = Effect.flags();
+      if (Flags & FunctionEffect::FE_InferrableOnCallees)
+        AllInferrableEffectsToVerify.insert(Effect);
+    }
+    LLVM_DEBUG(llvm::dbgs() << "AllInferrableEffectsToVerify: ";
+               AllInferrableEffectsToVerify.dump(llvm::dbgs());
+               llvm::dbgs() << "\n";);
+
+    // We can use DeclsWithEffectsToVerify as a stack for a
+    // depth-first traversal; there's no need for a second container. But first,
+    // reverse it, so when working from the end, Decls are verified in the order
+    // they are declared.
+    SmallVector<const Decl *> &VerificationQueue = S.DeclsWithEffectsToVerify;
+    std::reverse(VerificationQueue.begin(), VerificationQueue.end());
+
+    while (!VerificationQueue.empty()) {
+      const Decl *D = VerificationQueue.back();
+      if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
+        if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>()) {
+          // All children have been traversed; finish analysis.
+          finishPendingAnalysis(D, Pending);
+        }
+        VerificationQueue.pop_back();
+        continue;
+      }
+
+      // Not previously visited; begin a new analysis for this Decl.
+      PendingFunctionAnalysis *Pending = verifyDecl(D);
+      if (Pending == nullptr) {
+        // Completed now.
+        VerificationQueue.pop_back();
+        continue;
+      }
+
+      // Analysis remains pending because there are direct callees to be
+      // verified first. Push them onto the queue.
+      for (PendingFunctionAnalysis::DirectCall &Call :
+           Pending->unverifiedCalls()) {
+        FuncAnalysisPtr AP = DeclAnalysis.lookup(Call.Callee);
+        if (AP.isNull()) {
+          VerificationQueue.push_back(Call.Callee);
+          continue;
+        }
+
+        // This indicates recursion (not necessarily direct). For the
+        // purposes of effect analysis, we can just ignore it since
+        // no effects forbid recursion.
+        assert(isa<PendingFunctionAnalysis *>(AP));
+        Call.Recursed = true;
+      }
+    }
+  }
+
+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) {
+    CallableInfo CInfo(*D);
+    bool isExternC = false;
+
+    if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D))
+      isExternC = FD->getCanonicalDecl()->isExternCContext();
+
+    // For C++, with non-extern "C" linkage only - if any of the Decl's declared
+    // effects forbid throwing (e.g. nonblocking) then the function should also
+    // be declared noexcept.
+    if (S.getLangOpts().CPlusPlus && !isExternC) {
+      for (FunctionEffect Effect : CInfo.Effects) {
+        if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
+          continue;
+
+        bool IsNoexcept = false;
+        if (auto *FD = D->getAsFunction()) {
+          IsNoexcept = isNoexcept(FD);
+        } else if (auto *BD = dyn_cast<BlockDecl>(D)) {
+          if (auto *TSI = BD->getSignatureAsWritten()) {
+            auto *FPT = TSI->getType()->getAs<FunctionProtoType>();
+            IsNoexcept = FPT->isNothrow() || BD->hasAttr<NoThrowAttr>();
+          }
+        }
+        if (!IsNoexcept)
+          S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept)
+              << GetCallableDeclKind(D, nullptr) << Effect.name();
+        break;
+      }
+    }
+
+    // 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(S, CInfo, AllInferrableEffectsToVerify);
+
+    LLVM_DEBUG(llvm::dbgs()
+                   << "\nVerifying " << CInfo.getNameForDiagnostic(S) << " ";
+               FAnalysis.dump(S, llvm::dbgs()););
+
+    FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
+
+    Visitor.run();
+    if (FAnalysis.isComplete()) {
+      completeAnalysis(CInfo, std::move(FAnalysis));
+      return nullptr;
+    }
+    // Move the pending analysis to the heap and save it in the map.
+    PendingFunctionAnalysis *PendingPtr =
+        new PendingFunctionAnalysis(std::move(FAnalysis));
+    DeclAnalysis[D] = PendingPtr;
+    LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
+               DeclAnalysis.dump(S, llvm::dbgs()););
+    return PendingPtr;
+  }
+
+  // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
+  // inserted in the container.
+  void completeAnalysis(const CallableInfo &CInfo,
+                        PendingFunctionAnalysis &&Pending) {
+    if (ArrayRef<Violation> Viols =
+            Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
+        !Viols.empty())
+      emitDiagnostics(Viols, CInfo);
+
+    CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
+        S.getASTContext(), std::move(Pending), CInfo.Effects,
+        AllInferrableEffectsToVerify);
+    DeclAnalysis[CInfo.CDecl] = CompletePtr;
+    LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
+               DeclAnalysis.dump(S, llvm::dbgs()););
+  }
+
+  // Called after all direct calls requiring inference have been found -- or
+  // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without
+  // the possibility of inference. Deletes Pending.
+  void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
+    CallableInfo Caller(*D);
+    LLVM_DEBUG(llvm::dbgs() << "finishPendingAnalysis for "
+                            << Caller.getNameForDiagnostic(S) << " : ";
+               Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";);
+    for (const PendingFunctionAnalysis::DirectCall &Call :
+         Pending->unverifiedCalls()) {
+      if (Call.Recursed)
+        continue;
+
+      CallableInfo Callee(*Call.Callee);
+      followCall(Caller, *Pending, Callee, Call.CallLoc,
+                 /*AssertNoFurtherInference=*/true, Call.VSite);
+    }
+    completeAnalysis(Caller, std::move(*Pending));
+    delete Pending;
+  }
+
+  // Here we have a call to a Decl, either explicitly via a CallExpr or some
+  // other AST construct. PFA pertains to the caller.
+  void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
+                  const CallableInfo &Callee, SourceLocation CallLoc,
+                  bool AssertNoFurtherInference, ViolationSite VSite) {
+    const bool DirectCall = Callee.isCalledDirectly();
+
+    // Initially, the declared effects; inferred effects will be added.
+    FunctionEffectKindSet CalleeEffects = Callee.Effects;
+
+    bool IsInferencePossible = DirectCall;
+
+    if (DirectCall)
+      if (CompleteFunctionAnalysis *CFA =
+              DeclAnalysis.completedAnalysisForDecl(Callee.CDecl)) {
+        // Combine declared effects with those which may have been inferred.
+        CalleeEffects.insert(CFA->VerifiedEffects);
+        IsInferencePossible = false; // We've already traversed it.
+      }
+
+    if (AssertNoFurtherInference) {
+      assert(!IsInferencePossible);
+    }
+
+    if (!Callee.isVerifiable())
+      IsInferencePossible = false;
+
+    LLVM_DEBUG(llvm::dbgs()
+                   << "followCall from " << Caller.getNameForDiagnostic(S)
+                   << " to " << Callee.getNameForDiagnostic(S)
+                   << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+               CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
+               llvm::dbgs() << "  callee " << Callee.CDecl << " canonical "
+                            << Callee.CDecl->getCanonicalDecl() << "\n";);
+
+    auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
+      if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
+        return;
+
+      // If inference is not allowed, or the target is indirect (virtual
+      // method/function ptr?), generate a Violation now.
+      if (!IsInferencePossible ||
+          !(Effect.flags() & FunctionEffect::FE_InferrableOnCallees)) {
+        if (Callee.FuncType == SpecialFuncType::None)
+          PFA.checkAddViolation(Inferring,
+                                {Effect, ViolationID::CallsDeclWithoutEffect,
+                                 VSite, CallLoc, Callee.CDecl});
+        else
+          PFA.checkAddViolation(
+              Inferring,
+              {Effect, ViolationID::AllocatesMemory, VSite, CallLoc});
+      } else {
+        // Inference is allowed and necessary; defer it.
+        PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite);
+      }
+    };
+
+    for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects)
+      Check1Effect(Effect, false);
+
+    for (FunctionEffect Effect : PFA.EffectsToInfer)
+      Check1Effect(Effect, true);
+  }
+
+  // Describe a callable Decl for a diagnostic.
+  // (Not an enum class because the value is always converted to an integer for
+  // use in a diagnostic.)
+  enum CallableDeclKind {
+    CDK_Function,
+    CDK_Constructor,
+    CDK_Destructor,
+    CDK_Lambda,
+    CDK_Block,
+    CDK_MemberInitializer,
+  };
+
+  // Describe a call site or target using an enum mapping to a %select{}
+  // in a diagnostic, e.g. warn_func_effect_violation,
+  // warn_perf_constraint_implies_noexcept, and others.
+  static CallableDeclKind GetCallableDeclKind(const Decl *D,
+                                              const Violation *V) {
+    if (V != nullptr &&
+        V->Site.kind() == ViolationSite::Kind::MemberInitializer)
+      return CDK_MemberInitializer;
+    if (isa<BlockDecl>(D))
+      return CDK_Block;
+    if (auto *Method = dyn_cast<CXXMethodDecl>(D)) {
+      if (isa<CXXConstructorDecl>(D))
+        return CDK_Constructor;
+      if (isa<CXXDestructorDecl>(D))
+        return CDK_Destructor;
+      const CXXRecordDecl *Rec = Method->getParent();
+      if (Rec->isLambda())
+        return CDK_Lambda;
+    }
+    return CDK_Function;
+  };
+
+  // Should only be called when function's analysis is determined to be
+  // complete.
+  void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo) {
+    if (Viols.empty())
+      return;
+
+    auto MaybeAddTemplateNote = [&](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();
+        }
+      }
+    };
+
+    // For note_func_effect_call_indirect.
+    enum { Indirect_VirtualMethod, Indirect_FunctionPtr };
+
+    auto MaybeAddSiteContext = [&](const Decl *D, const Violation &V) {
+      // If a violation site is a member initializer, add a note pointing to
+      // the constructor which invoked it.
+      if (V.Site.kind() == ViolationSite::Kind::MemberInitializer) {
+        unsigned ImplicitCtor = 0;
+        if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D);
+            Ctor && Ctor->isImplicit())
+          ImplicitCtor = 1;
+        S.Diag(D->getLocation(), diag::note_func_effect_in_constructor)
+            << ImplicitCtor;
+      }
+
+      // If a violation site is a default argument expression, add a note
+      // pointing to the call site using the default argument.
+      else if (V.Site.kind() == ViolationSite::Kind::DefaultArgExpr)
+        S.Diag(V.Site.defaultArgExpr()->getUsedLocation(),
+               diag::note_in_evaluating_default_argument);
+    };
+
+    // Top-level violations are warnings.
+    for (const Violation &Viol1 : Viols) {
+      StringRef effectName = Viol1.Effect.name();
+      switch (Viol1.ID) {
+      case ViolationID::None:
+      case ViolationID::DeclDisallowsInference: // Shouldn't happen
+                                                // here.
+        llvm_unreachable("Unexpected violation kind");
+        break;
+      case ViolationID::AllocatesMemory:
+      case ViolationID::ThrowsOrCatchesExceptions:
+      case ViolationID::HasStaticLocalVariable:
+      case ViolationID::AccessesThreadLocalVariable:
+      case ViolationID::AccessesObjCMethodOrProperty:
+        S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
+            << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName
+            << Viol1.diagnosticSelectIndex();
+        MaybeAddSiteContext(CInfo.CDecl, Viol1);
+        MaybeAddTemplateNote(CInfo.CDecl);
+        break;
+      case ViolationID::CallsExprWithoutEffect:
+        S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
+            << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName;
+        MaybeAddSiteContext(CInfo.CDecl, Viol1);
+        MaybeAddTemplateNote(CInfo.CDecl);
+        break;
+
+      case ViolationID::CallsDeclWithoutEffect: {
+        CallableInfo CalleeInfo(*Viol1.Callee);
+        std::string CalleeName = CalleeInfo.getNameForDiagnostic(S);
+
+        S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
+            << GetCallableDeclKind(CInfo.CDecl, &Viol1) << effectName
+            << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << CalleeName;
+        MaybeAddSiteContext(CInfo.CDecl, Viol1);
+        MaybeAddTemplateNote(CInfo.CDecl);
+
+        // Emit notes explaining the transitive chain of inferences: Why isn't
+        // the callee safe?
+        for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) {
+          std::optional<CallableInfo> MaybeNextCallee;
+          CompleteFunctionAnalysis *Completed =
+              DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+          if (Completed == nullptr) {
+            // No result - could be
+            // - non-inline and extern
+            // - indirect (virtual or through function pointer)
+            // - effect has been explicitly disclaimed (e.g. "blocking")
+
+            CallableType CType = CalleeInfo.type();
+            if (CType == CallableType::Virtual)
+              S.Diag(Callee->getLocation(),
+                     diag::note_func_effect_call_indirect)
+                  << Indirect_VirtualMethod << effectName;
+            else if (CType == CallableType::Unknown)
+              S.Diag(Callee->getLocation(),
+                     diag::note_func_effect_call_indirect)
+                  << Indirect_FunctionPtr << effectName;
+            else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind()))
+              S.Diag(Callee->getLocation(),
+                     diag::note_func_effect_call_disallows_inference)
+                  << GetCallableDeclKind(CInfo.CDecl, nullptr) << effectName
+                  << FunctionEffect(Viol1.Effect.oppositeKind()).name();
+            else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
+                     FD == nullptr || FD->getBuiltinID() == 0) {
+              // A builtin callee generally doesn't have a useful source
+              // location at which to insert a note.
+              S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
+                  << effectName;
+            }
+            break;
+          }
+          const Violation *PtrViol2 =
+              Completed->firstViolationForEffect(Viol1.Effect);
+          if (PtrViol2 == nullptr)
+            break;
+
+          const Violation &Viol2 = *PtrViol2;
+          switch (Viol2.ID) {
+          case ViolationID::None:
+            llvm_unreachable("Unexpected violation kind");
+            break;
+          case ViolationID::DeclDisallowsInference:
+            S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference)
+                << GetCallableDeclKind(CalleeInfo.CDecl, nullptr) << effectName
+                << Viol2.CalleeEffectPreventingInference->name();
+            break;
+          case ViolationID::CallsExprWithoutEffect:
+            S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect)
+                << Indirect_FunctionPtr << effectName;
+            break;
+          case ViolationID::AllocatesMemory:
+          case ViolationID::ThrowsOrCatchesExceptions:
+          case ViolationID::HasStaticLocalVariable:
+          case ViolationID::AccessesThreadLocalVariable:
+          case ViolationID::AccessesObjCMethodOrProperty:
+            S.Diag(Viol2.Loc, diag::note_func_effect_violation)
+                << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName
+                << Viol2.diagnosticSelectIndex();
+            MaybeAddSiteContext(CalleeInfo.CDecl, Viol2);
+            break;
+          case ViolationID::CallsDeclWithoutEffect:
+            MaybeNextCallee.emplace(*Viol2.Callee);
+            S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
+                << GetCallableDeclKind(CalleeInfo.CDecl, &Viol2) << effectName
+                << GetCallableDeclKind(Viol2.Callee, nullptr)
+                << MaybeNextCallee->getNameForDiagnostic(S);
+            break;
+          }
+          MaybeAddTemplateNote(Callee);
+          Callee = Viol2.Callee;
+          if (MaybeNextCallee) {
+            CalleeInfo = *MaybeNextCallee;
+            CalleeName = CalleeInfo.getNameForDiagnostic(S);
+          }
+        }
+      } break;
+      }
+    }
+  }
+
+  // ----------
+  // 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.
+  //
+  // Violations are always routed to a PendingFunctionAnalysis.
+  struct FunctionBodyASTVisitor : RecursiveASTVisitor<FunctionBodyASTVisitor> {
+    using Base = RecursiveASTVisitor<FunctionBodyASTVisitor>;
+
+    Analyzer &Outer;
+    PendingFunctionAnalysis &CurrentFunction;
+    CallableInfo &CurrentCaller;
+    ViolationSite VSite;
+
+    FunctionBodyASTVisitor(Analyzer &Outer,
+                           PendingFunctionAnalysis &CurrentFunction,
+                           CallableInfo &CurrentCaller)
+        : Outer(Outer), CurrentFunction(CurrentFunction),
+          CurrentCaller(CurrentCaller) {}
+
+    // -- Entry point --
+    void run() {
+      // The target function may have implicit code paths beyond the
+      // body: member and base destructors. Visit these first.
+      if (auto *Dtor = dyn_cast<CXXDestructorDecl>(CurrentCaller.CDecl))
+        followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+
+      // Do an AST traversal of the function/block body
+      TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
+    }
+
+    // -- Methods implementing common logic --
+
+    // Handle a language construct forbidden by some effects. Only effects whose
+    // flags include the specified flag receive a violation. \p Flag describes
+    // the construct.
+    void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag,
+                                   ViolationID VID, SourceLocation Loc,
+                                   const Decl *Callee = nullptr) {
+      // If there are any declared verifiable effects which forbid the construct
+      // represented by the flag, store just one violation.
+      for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) {
+        if (Effect.flags() & Flag) {
+          addViolation(/*inferring=*/false, Effect, VID, Loc, Callee);
+          break;
+        }
+      }
+      // For each inferred effect which forbids the construct, store a
+      // violation, if we don't already have a violation for that effect.
+      for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
+        if (Effect.flags() & Flag)
+          addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
+    }
+
+    void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID,
+                      SourceLocation Loc, const Decl *Callee = nullptr) {
+      CurrentFunction.checkAddViolation(
+          Inferring, Violation(Effect, VID, VSite, 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(CallableInfo &CI, SourceLocation CallLoc) {
+      // Check for a call to a builtin function, whose effects are
+      // handled specially.
+      if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl)) {
+        if (unsigned BuiltinID = FD->getBuiltinID()) {
+          CI.Effects = getBuiltinFunctionEffects(BuiltinID);
+          if (CI.Effects.empty()) {
+            // A builtin with no known effects is assumed safe.
+            return;
+          }
+          // A builtin WITH effects doesn't get any special treatment for
+          // being noreturn/noexcept, e.g. longjmp(), so we skip the check
+          // below.
+        } else {
+          // If the callee is both `noreturn` and `noexcept`, it presumably
+          // terminates. Ignore it for the purposes of effect analysis.
+          // If not C++, `noreturn` alone is sufficient.
+          if (FD->isNoReturn() &&
+              (!Outer.S.getLangOpts().CPlusPlus || isNoexcept(FD)))
+            return;
+        }
+      }
+
+      Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+                       /*AssertNoFurtherInference=*/false, VSite);
+    }
+
+    void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
+      auto *FPT =
+          CalleeType->getAs<FunctionProtoType>(); // Null if FunctionType.
+      FunctionEffectKindSet CalleeEffects;
+      if (FPT)
+        CalleeEffects.insert(FPT->getFunctionEffects());
+
+      auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
+        if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
+                                  /*direct=*/false, CalleeEffects))
+          addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
+                       Call->getBeginLoc());
+      };
+
+      for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects)
+        Check1Effect(Effect, false);
+
+      for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
+        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) {
+      SourceLocation DtorLoc = Dtor->getLocation();
+      for (const FieldDecl *Field : Rec->fields())
+        followTypeDtor(Field->getType(), DtorLoc);
+
+      if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec))
+        for (const CXXBaseSpecifier &Base : Class->bases())
+          followTypeDtor(Base.getType(), DtorLoc);
+    }
+
+    void followTypeDtor(QualType QT, SourceLocation CallSite) {
+      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 (CXXDestructorDecl *Dtor = Class->getDestructor();
+              Dtor && !Dtor->isDeleted()) {
+            CallableInfo CI(*Dtor);
+            followCall(CI, CallSite);
+          }
+        }
+      }
+    }
+
+    // -- Methods for use of RecursiveASTVisitor --
+
+    bool shouldVisitImplicitCode() const { return true; }
+
+    bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+    bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Throw->getThrowLoc());
+      return true;
+    }
+
+    bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Catch->getCatchLoc());
+      return true;
+    }
+
+    bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Throw->getThrowLoc());
+      return true;
+    }
+
+    bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Catch->getAtCatchLoc());
+      return true;
+    }
+
+    bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
+                                ViolationID::AccessesObjCMethodOrProperty,
+                                Msg->getBeginLoc());
+      return true;
+    }
+
+    bool VisitSEHExceptStmt(SEHExceptStmt *Exc) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Exc->getExceptLoc());
+      return true;
+    }
+
+    bool VisitCallExpr(CallExpr *Call) {
+      LLVM_DEBUG(llvm::dbgs()
+                     << "VisitCallExpr : "
+                     << Call->getBeginLoc().printToString(Outer.S.SourceMgr)
+                     << "\n";);
+
+      Expr *CalleeExpr = Call->getCallee();
+      if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+        CallableInfo CI(*Callee);
+        followCall(CI, Call->getBeginLoc());
+        return true;
+      }
+
+      if (isa<CXXPseudoDestructorExpr>(CalleeExpr)) {
+        // Just destroying a scalar, fine.
+        return true;
+      }
+
+      // No Decl, just an Expr. Just check based on its type.
+      checkIndirectCall(Call, CalleeExpr->getType());
+
+      return true;
+    }
+
+    bool VisitVarDecl(VarDecl *Var) {
+      LLVM_DEBUG(llvm::dbgs()
+                     << "VisitVarDecl : "
+                     << Var->getBeginLoc().printToString(Outer.S.SourceMgr)
+                     << "\n";);
+
+      if (Var->isStaticLocal())
+        diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
+                                  ViolationID::HasStaticLocalVariable,
+                                  Var->getLocation());
+
+      const QualType::DestructionKind DK =
+          Var->needsDestruction(Outer.S.getASTContext());
+      if (DK == QualType::DK_cxx_destructor)
+        followTypeDtor(Var->getType(), Var->getLocation());
+      return true;
+    }
+
+    bool VisitCXXNewExpr(CXXNewExpr *New) {
+      // RecursiveASTVisitor does not visit the implicit call to operator new.
+      if (FunctionDecl *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 true;
+    }
+
+    bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
+      // RecursiveASTVisitor does not visit the implicit call to operator
+      // delete.
+      if (FunctionDecl *FD = Delete->getOperatorDelete()) {
+        CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
+        followCall(CI, Delete->getBeginLoc());
+      }
+
+      // It DOES however visit the called destructor
+
+      return true;
+    }
+
+    bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
+      LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
+                              << Construct->getBeginLoc().printToString(
+                                     Outer.S.SourceMgr)
+                              << "\n";);
+
+      // RecursiveASTVisitor does not visit the implicit call to the
+      // constructor.
+      const CXXConstructorDecl *Ctor = Construct->getConstructor();
+      CallableInfo CI(*Ctor);
+      followCall(CI, Construct->getLocation());
+
+      return true;
+    }
+
+    bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
+      ViolationSite PrevVS = VSite;
+      if (Init->isAnyMemberInitializer())
+        VSite.setKind(ViolationSite::Kind::MemberInitializer);
+      bool Result = Base::TraverseConstructorInitializer(Init);
+      VSite = PrevVS;
+      return Result;
+    }
+
+    bool TraverseCXXDefaultArgExpr(CXXDefaultArgExpr *E) {
+      LLVM_DEBUG(llvm::dbgs()
+                     << "TraverseCXXDefaultArgExpr : "
+                     << E->getUsedLocation().printToString(Outer.S.SourceMgr)
+                     << "\n";);
+
+      ViolationSite PrevVS = VSite;
+      if (VSite.kind() == ViolationSite::Kind::Default)
+        VSite = ViolationSite{E};
+
+      bool Result = Base::TraverseCXXDefaultArgExpr(E);
+      VSite = PrevVS;
+      return Result;
+    }
+
+    bool TraverseLambdaExpr(LambdaExpr *Lambda) {
+      // We override this so as to be able to skip traversal of the lambda's
+      // body. We have to explicitly traverse the captures. Why not return
+      // false from shouldVisitLambdaBody()? Because we need to visit a lambda's
+      // body when we are verifying the lambda itself; we only want to skip it
+      // in the context of the outer function.
+      for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
+        TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
+                              Lambda->capture_init_begin()[I]);
+
+      return true;
+    }
+
+    bool TraverseBlockExpr(BlockExpr * /*unused*/) {
+      // TODO: are the capture expressions (ctor call?) safe?
+      return true;
+    }
+
+    bool VisitDeclRefExpr(const DeclRefExpr *E) {
+      const ValueDecl *Val = E->getDecl();
+      if (const auto *Var = dyn_cast<VarDecl>(Val)) {
+        if (Var->getTLSKind() != VarDecl::TLS_None) {
+          // At least on macOS, thread-local variables are initialized on
+          // first access, including a heap allocation.
+          diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
+                                    ViolationID::AccessesThreadLocalVariable,
+                                    E->getLocation());
+        }
+      }
+      return true;
+    }
+
+    bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+      return TraverseStmt(Node->getResultExpr());
+    }
+    bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+      return true;
+    }
+
+    bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return true; }
+
+    bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return true; }
+
+    bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return true; }
+
+    bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; }
+
+    // Skip concept requirements since they don't generate code.
+    bool TraverseConceptRequirement(concepts::Requirement *R) { return true; }
+  };
+};
+
+Analyzer::AnalysisMap::~AnalysisMap() {
+  for (const auto &Item : *this) {
+    FuncAnalysisPtr AP = Item.second;
+    if (isa<PendingFunctionAnalysis *>(AP))
+      delete AP.get<PendingFunctionAnalysis *>();
+    else
+      delete AP.get<CompleteFunctionAnalysis *>();
+  }
+}
+
+} // anonymous namespace
+
+namespace clang {
+
+bool Sema::diagnoseConflictingFunctionEffect(
+    const FunctionEffectsRef &FX, const FunctionEffectWithCondition &NewEC,
+    SourceLocation NewAttrLoc) {
+  // If the new effect has a condition, we can't detect conflicts until the
+  // condition is resolved.
+  if (NewEC.Cond.getCondition() != nullptr)
+    return false;
+
+  // Diagnose the new attribute as incompatible with a previous one.
+  auto Incompatible = [&](const FunctionEffectWithCondition &PrevEC) {
+    Diag(NewAttrLoc, diag::err_attributes_are_not_compatible)
+        << ("'" + NewEC.description() + "'")
+        << ("'" + PrevEC.description() + "'") << false;
+    // We don't necessarily have the location of the previous attribute,
+    // so no note.
+    return true;
+  };
+
+  // Compare against previous attributes.
+  FunctionEffect::Kind NewKind = NewEC.Effect.kind();
+
+  for (const FunctionEffectWithCondition &PrevEC : FX) {
+    // Again, can't check yet when the effect is conditional.
+    if (PrevEC.Cond.getCondition() != nullptr)
+      continue;
+
+    FunctionEffect::Kind PrevKind = PrevEC.Effect.kind();
+    // Note that we allow PrevKind == NewKind; it's redundant and ignored.
+
+    if (PrevEC.Effect.oppositeKind() == NewKind)
+      return Incompatible(PrevEC);
+
+    // A new allocating is incompatible with a previous nonblocking.
+    if (PrevKind == FunctionEffect::Kind::NonBlocking &&
+        NewKind == FunctionEffect::Kind::Allocating)
+      return Incompatible(PrevEC);
+
+    // A new nonblocking is incompatible with a previous allocating.
+    if (PrevKind == FunctionEffect::Kind::Allocating &&
+        NewKind == FunctionEffect::Kind::NonBlocking)
+      return Incompatible(PrevEC);
+  }
+
+  return false;
+}
+
+void Sema::diagnoseFunctionEffectMergeConflicts(
+    const FunctionEffectSet::Conflicts &Errs, SourceLocation NewLoc,
+    SourceLocation OldLoc) {
+  for (const FunctionEffectSet::Conflict &Conflict : Errs) {
+    Diag(NewLoc, diag::warn_conflicting_func_effects)
+        << Conflict.Kept.description() << Conflict.Rejected.description();
+    Diag(OldLoc, diag::note_previous_declaration);
+  }
+}
+
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::maybeAddDeclWithEffects(const Decl *D,
+                                   const FunctionEffectsRef &FX) {
+  if (!D->hasBody()) {
+    if (const auto *FD = D->getAsFunction(); FD && !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.
+  // Without this check, we would analyze the function based on placeholder
+  // template parameters, and potentially generate spurious diagnostics.
+  if (cast<DeclContext>(D)->isDependentContext())
+    return;
+
+  addDeclWithEffects(D, FX);
+}
+
+void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
+  // To avoid the possibility of conflict, don't add effects which are
+  // not FE_InferrableOnCallees and therefore not verified; this removes
+  // blocking/allocating but keeps nonblocking/nonallocating.
+  // Also, ignore any conditions when building the list of effects.
+  bool AnyVerifiable = false;
+  for (const FunctionEffectWithCondition &EC : FX)
+    if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) {
+      AllEffectsToVerify.insert(EC.Effect);
+      AnyVerifiable = true;
+    }
+
+  // Record the declaration for later analysis.
+  if (AnyVerifiable)
+    DeclsWithEffectsToVerify.push_back(D);
+}
+
+void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) {
+  if (hasUncompilableErrorOccurred() || Diags.getIgnoreAllWarnings())
+    return;
+  if (TU == nullptr)
+    return;
+  Analyzer{*this}.run(*TU);
+}
+
+Sema::FunctionEffectDiffVector::FunctionEffectDiffVector(
+    const FunctionEffectsRef &Old, const FunctionEffectsRef &New) {
+
+  FunctionEffectsRef::iterator POld = Old.begin();
+  FunctionEffectsRef::iterator OldEnd = Old.end();
+  FunctionEffectsRef::iterator PNew = New.begin();
+  FunctionEffectsRef::iterator NewEnd = New.end();
+
+  while (true) {
+    int cmp = 0;
+    if (POld == OldEnd) {
+      if (PNew == NewEnd)
+        break;
+      cmp = 1;
+    } else if (PNew == NewEnd)
+      cmp = -1;
+    else {
+      FunctionEffectWithCondition Old = *POld;
+      FunctionEffectWithCondition New = *PNew;
+      if (Old.Effect.kind() < New.Effect.kind())
+        cmp = -1;
+      else if (New.Effect.kind() < Old.Effect.kind())
+        cmp = 1;
+      else {
+        cmp = 0;
+        if (Old.Cond.getCondition() != New.Cond.getCondition()) {
+          // FIXME: Cases where the expressions are equivalent but
+          // don't have the same identity.
+          push_back(FunctionEffectDiff{
+              Old.Effect.kind(), FunctionEffectDiff::Kind::ConditionMismatch,
+              Old, New});
+        }
+      }
+    }
+
+    if (cmp < 0) {
+      // removal
+      FunctionEffectWithCondition Old = *POld;
+      push_back(FunctionEffectDiff{Old.Effect.kind(),
+                                   FunctionEffectDiff::Kind::Removed, Old,
+                                   std::nullopt});
+      ++POld;
+    } else if (cmp > 0) {
+      // addition
+      FunctionEffectWithCondition New = *PNew;
+      push_back(FunctionEffectDiff{New.Effect.kind(),
+                                   FunctionEffectDiff::Kind::Added,
+                                   std::nullopt, New});
+      ++PNew;
+    } else {
+      ++POld;
+      ++PNew;
+    }
+  }
+}
+
+bool Sema::FunctionEffectDiff::shouldDiagnoseConversion(
+    QualType SrcType, const FunctionEffectsRef &SrcFX, QualType DstType,
+    const FunctionEffectsRef &DstFX) const {
+
+  switch (EffectKind) {
+  case FunctionEffect::Kind::NonAllocating:
+    // nonallocating can't be added (spoofed) during a conversion, unless we
+    // have nonblocking.
+    if (DiffKind == Kind::Added) {
+      for (const auto &CFE : SrcFX) {
+        if (CFE.Effect.kind() == FunctionEffect::Kind::NonBlocking)
+          return false;
+      }
+    }
+    [[fallthrough]];
+  case FunctionEffect::Kind::NonBlocking:
+    // nonblocking can't be added (spoofed) during a conversion.
+    switch (DiffKind) {
+    case Kind::Added:
+      return true;
+    case Kind::Removed:
+      return false;
+    case Kind::ConditionMismatch:
+      // FIXME: Condition mismatches are too coarse right now -- expressions
+      // which are equivalent but don't have the same identity are detected as
+      // mismatches. We're going to diagnose those anyhow until expression
+      // matching is better.
+      return true;
+    }
+  case FunctionEffect::Kind::Blocking:
+  case FunctionEffect::Kind::Allocating:
+    return false;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+bool Sema::FunctionEffectDiff::shouldDiagnoseRedeclaration(
+    const FunctionDecl &OldFunction, const FunctionEffectsRef &OldFX,
+    const FunctionDecl &NewFunction, const FunctionEffectsRef &NewFX) const {
+  switch (EffectKind) {
+  case FunctionEffect::Kind::NonAllocating:
+  case FunctionEffect::Kind::NonBlocking:
+    // nonblocking/nonallocating can't be removed in a redeclaration.
+    switch (DiffKind) {
+    case Kind::Added:
+      return false; // No diagnostic.
+    case Kind::Removed:
+      return true; // Issue diagnostic.
+    case Kind::ConditionMismatch:
+      // All these forms of mismatches are diagnosed.
+      return true;
+    }
+  case FunctionEffect::Kind::Blocking:
+  case FunctionEffect::Kind::Allocating:
+    return false;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+Sema::FunctionEffectDiff::OverrideResult
+Sema::FunctionEffectDiff::shouldDiagnoseMethodOverride(
+    const CXXMethodDecl &OldMethod, const FunctionEffectsRef &OldFX,
+    const CXXMethodDecl &NewMethod, const FunctionEffectsRef &NewFX) const {
+  switch (EffectKind) {
+  case FunctionEffect::Kind::NonAllocating:
+  case FunctionEffect::Kind::NonBlocking:
+    switch (DiffKind) {
+
+    // If added on an override, that's fine and not diagnosed.
+    case Kind::Added:
+      return OverrideResult::NoAction;
+
+    // If missing from an override (removed), propagate from base to derived.
+    case Kind::Removed:
+      return OverrideResult::Merge;
+
+    // If there's a mismatch involving the effect's polarity or condition,
+    // issue a warning.
+    case Kind::ConditionMismatch:
+      return OverrideResult::Warn;
+    }
+
+  case FunctionEffect::Kind::Blocking:
+  case FunctionEffect::Kind::Allocating:
+    return OverrideResult::NoAction;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
+} // namespace clang

diff  --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 15ed8572e60844..c2b35856111f3b 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1951,6 +1951,9 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
 ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
   LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
   ActOnFinishFunctionBody(LSI.CallOperator, Body);
+
+  maybeAddDeclWithEffects(LSI.CallOperator);
+
   return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
 }
 

diff  --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 0a4251c0e52404..4b599b7dbf3ce8 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3912,6 +3912,11 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
       FPPragmaOptions.swap(Record);
       break;
 
+    case DECLS_WITH_EFFECTS_TO_VERIFY:
+      for (unsigned I = 0, N = Record.size(); I != N; /*in loop*/)
+        DeclsWithEffectsToVerify.push_back(ReadDeclID(F, Record, I));
+      break;
+
     case OPENCL_EXTENSIONS:
       for (unsigned I = 0, E = Record.size(); I != E; ) {
         auto Name = ReadString(Record, I);
@@ -8413,6 +8418,17 @@ void ASTReader::InitializeSema(Sema &S) {
         NewOverrides.applyOverrides(SemaObj->getLangOpts());
   }
 
+  for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
+    Decl *D = GetDecl(ID);
+    if (auto *FD = dyn_cast<FunctionDecl>(D))
+      SemaObj->addDeclWithEffects(FD, FD->getFunctionEffects());
+    else if (auto *BD = dyn_cast<BlockDecl>(D))
+      SemaObj->addDeclWithEffects(BD, BD->getFunctionEffects());
+    else
+      llvm_unreachable("unexpected Decl type in DeclsWithEffectsToVerify");
+  }
+  DeclsWithEffectsToVerify.clear();
+
   SemaObj->OpenCLFeatures = OpenCLExtensions;
 
   UpdateSema();

diff  --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index aa9764e25c3233..375ddc90482b27 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4642,6 +4642,17 @@ void ASTWriter::WriteFloatControlPragmaOptions(Sema &SemaRef) {
   Stream.EmitRecord(FLOAT_CONTROL_PRAGMA_OPTIONS, Record);
 }
 
+/// Write Sema's collected list of declarations with unverified effects.
+void ASTWriter::WriteDeclsWithEffectsToVerify(Sema &SemaRef) {
+  if (SemaRef.DeclsWithEffectsToVerify.empty())
+    return;
+  RecordData Record;
+  for (const auto *D : SemaRef.DeclsWithEffectsToVerify) {
+    AddDeclRef(D, Record);
+  }
+  Stream.EmitRecord(DECLS_WITH_EFFECTS_TO_VERIFY, Record);
+}
+
 void ASTWriter::WriteModuleFileExtension(Sema &SemaRef,
                                          ModuleFileExtensionWriter &Writer) {
   // Enter the extension block.
@@ -5599,6 +5610,7 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
   }
   WritePackPragmaOptions(SemaRef);
   WriteFloatControlPragmaOptions(SemaRef);
+  WriteDeclsWithEffectsToVerify(SemaRef);
 
   // Some simple statistics
   RecordData::value_type Record[] = {

diff  --git a/clang/test/Headers/Inputs/include/setjmp.h b/clang/test/Headers/Inputs/include/setjmp.h
index 3d5e903eff6fce..c24951e92501f9 100644
--- a/clang/test/Headers/Inputs/include/setjmp.h
+++ b/clang/test/Headers/Inputs/include/setjmp.h
@@ -5,4 +5,7 @@ typedef struct {
   int x[42];
 } jmp_buf;
 
+ __attribute__((noreturn))
+void longjmp(jmp_buf, int);
+
 #endif

diff  --git a/clang/test/Misc/warning-wall.c b/clang/test/Misc/warning-wall.c
index 4909ab034ef30a..91de843f88c913 100644
--- a/clang/test/Misc/warning-wall.c
+++ b/clang/test/Misc/warning-wall.c
@@ -108,5 +108,6 @@ CHECK-NEXT:  -Wmisleading-indentation
 CHECK-NEXT:  -Wpacked-non-pod
 CHECK-NEXT:  -Wvla-cxx-extension
 CHECK-NEXT:    -Wvla-extension-static-assert
+CHECK-NEXT:  -Wperf-constraint-implies-noexcept
 
 CHECK-NOT:-W

diff  --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
new file mode 100644
index 00000000000000..dcbcef410987f5
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify -Wfunction-effects %s
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// These need '-fms-extensions' (and maybe '-fdeclspec')
+void f1() [[clang::nonblocking]] {
+    __try {} __except (1) {} // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
+}
+
+struct S {
+    int get_x(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+    __declspec(property(get = get_x)) int x;
+
+    int get_nb() { return 42; }
+    __declspec(property(get = get_nb)) int nb;
+
+    int get_nb2() [[clang::nonblocking]];
+    __declspec(property(get = get_nb2)) int nb2;
+};
+
+void f2() [[clang::nonblocking]] {
+    S a;
+    a.x; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'S::get_x'}}
+    a.nb;
+    a.nb2;
+}

diff  --git a/clang/test/Sema/attr-nonblocking-constraints.c b/clang/test/Sema/attr-nonblocking-constraints.c
new file mode 100644
index 00000000000000..e7ab661b3125a2
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-constraints.c
@@ -0,0 +1,27 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c89 -Wfunction-effects -internal-isystem %S/../Headers/Inputs/include %s
+
+// Tests for a few cases involving C functions without prototypes.
+
+void hasproto(void) __attribute__((blocking)); // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}}
+
+// Has no prototype, inferably safe.
+void nb1() {}
+
+// Has no prototype, noreturn.
+[[noreturn]]
+void aborts();
+
+void nb2(void) __attribute__((nonblocking)) {
+  hasproto(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasproto'}}
+  nb1();
+  aborts(); // no diagnostic because it's noreturn.
+}
+
+#include <setjmp.h>
+
+void nb3(int x, int y) __attribute__((nonblocking)) {
+  if (x != y) {
+    jmp_buf jb;
+    longjmp(jb, 0); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'longjmp'}}
+  }
+}

diff  --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
new file mode 100644
index 00000000000000..c694860069c960
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -0,0 +1,386 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify -Wfunction-effects %s
+// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
+// the FXAnalysis pass from running at all.
+
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// --- CONSTRAINTS ---
+
+void nb1() [[clang::nonblocking]]
+{
+	int *pInt = new int; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
+	delete pInt; // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
+}
+
+void nb2() [[clang::nonblocking]]
+{
+	static int global; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
+}
+
+void nb3() [[clang::nonblocking]]
+{
+	try {
+		throw 42; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
+	}
+	catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
+	}
+}
+
+void nb4_inline() {}
+void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+void nb4() [[clang::nonblocking]]
+{
+	nb4_inline(); // OK
+	nb4_not_inline(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+}
+
+
+struct HasVirtual {
+	virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
+};
+
+void nb5() [[clang::nonblocking]]
+{
+ 	HasVirtual hv;
+ 	hv.unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+}
+
+void nb6_unsafe(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb6_transitively_unsafe()
+{
+	nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+}
+
+void nb6() [[clang::nonblocking]]
+{
+	nb6_transitively_unsafe(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+}
+
+thread_local int tl_var{ 42 };
+
+bool tl_test() [[clang::nonblocking]]
+{
+	return tl_var > 0; // expected-warning {{function with 'nonblocking' attribute must not use thread-local variables}}
+}
+
+void nb7()
+{
+	// Make sure we verify blocks
+	auto blk = ^() [[clang::nonblocking]] {
+		throw 42; // expected-warning {{block with 'nonblocking' attribute must not throw or catch exceptions}}
+	};
+}
+
+void nb8()
+{
+	// Make sure we verify lambdas
+	auto lambda = []() [[clang::nonblocking]] {
+		throw 42; // expected-warning {{lambda with 'nonblocking' attribute must not throw or catch exceptions}}
+	};
+}
+
+void nb8a() [[clang::nonblocking]]
+{
+	// A blocking lambda shouldn't make the outer function unsafe.
+	auto unsafeLambda = []() {
+		throw 42;
+	};
+}
+
+void nb8b() [[clang::nonblocking]]
+{
+	// An unsafe lambda capture makes the outer function unsafe.
+	auto unsafeCapture = [foo = new int]() { // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
+		delete foo;
+	};
+}
+
+void nb8c()
+{
+	// An unsafe lambda capture does not make the lambda unsafe.
+	auto unsafeCapture = [foo = new int]() [[clang::nonblocking]] {
+	};
+}
+
+// Make sure template expansions are found and verified.
+	template <typename T>
+	struct Adder {
+		static T add_explicit(T x, T y) [[clang::nonblocking]]
+		{
+			return x + y; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+		}
+		static T add_implicit(T x, T y)
+		{
+			return x + y; // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+		}
+	};
+
+	struct Stringy {
+		friend Stringy operator+(const Stringy& x, const Stringy& y)
+		{
+			// Do something inferably unsafe
+			auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
+			return {};
+		}
+	};
+
+	struct Stringy2 {
+		friend Stringy2 operator+(const Stringy2& x, const Stringy2& y)
+		{
+			// Do something inferably unsafe
+			throw 42; // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+		}
+	};
+
+void nb9() [[clang::nonblocking]]
+{
+	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 {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} \
+		expected-note {{in template expansion here}}
+}
+
+void nb10(
+	void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
+	void (*fp2)() [[clang::nonblocking]]
+	) [[clang::nonblocking]]
+{
+	fp1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+	fp2();
+
+	// When there's a cast, there's a separate diagnostic.
+	static_cast<void (*)()>(fp1)(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' expression}}
+}
+
+// Interactions with nonblocking(false)
+void nb11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
+{
+}
+void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+
+template <bool V>
+struct ComputedNB {
+	void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}}
+};
+
+void nb11() [[clang::nonblocking]]
+{
+	nb11_no_inference_1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+	nb11_no_inference_2(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+
+	ComputedNB<true> CNB_true;
+	CNB_true.method();
+	
+	ComputedNB<false> CNB_false;
+	CNB_false.method(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
+}
+
+// Verify that when attached to a redeclaration, the attribute successfully attaches.
+void nb12() {
+	static int x; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
+}
+void nb12() [[clang::nonblocking]];
+void nb13() [[clang::nonblocking]] { nb12(); }
+
+// C++ member function pointers
+struct PTMFTester {
+	typedef void (PTMFTester::*ConvertFunction)() [[clang::nonblocking]];
+
+	void convert() [[clang::nonblocking]];
+
+	ConvertFunction mConvertFunc;
+};
+
+void PTMFTester::convert() [[clang::nonblocking]]
+{
+	(this->*mConvertFunc)();
+}
+
+// Block variables
+void nb17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
+	blk();
+}
+
+// References to blocks
+void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
+{
+	auto &ref = block;
+	ref();
+}
+
+// Builtin functions
+void nb19() [[clang::nonblocking]] {
+	__builtin_assume(1);
+	void *ptr = __builtin_malloc(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_malloc'}}
+	__builtin_free(ptr); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_free'}}
+	
+	void *p2 = __builtin_operator_new(1); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_new'}}
+	__builtin_operator_delete(p2); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function '__builtin_operator_delete'}}
+}
+
+// Function try-block
+void catches() try {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+
+void nb20() [[clang::nonblocking]] {
+	catches(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'catches'}}
+}
+
+struct S {
+    int x;
+    S(int x) try : x(x) {} catch (...) {} // expected-note {{constructor cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+    S(double) : x((throw 3, 3)) {} // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}} \
+                                      expected-note {{in constructor here}}
+};
+
+int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
+            // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+struct A {                // expected-note {{in implicit constructor here}}
+    int x = (throw 3, 3); // expected-note {{member initializer cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+};
+
+struct B {
+    int y = badi(); // expected-note {{member initializer cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
+};
+
+void f() [[clang::nonblocking]] {
+    S s1(3);   // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}}
+    S s2(3.0); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'S::S'}}
+    A a;       // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'A::A'}}
+    B b;       // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'B::B'}}
+}
+
+struct T {
+	int x = badi();               // expected-warning {{member initializer of constructor with 'nonblocking' attribute must not call non-'nonblocking' function 'badi'}}
+	T() [[clang::nonblocking]] {} // expected-note {{in constructor here}}
+	T(int x) [[clang::nonblocking]] : x(x) {} // OK
+};
+
+// Default arguments
+int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
+                           expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} \
+						   expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}} \
+                                                        expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badForDefaultArg'}}
+}
+
+void nb21() [[clang::nonblocking]] {
+	hasDefaultArg(); // expected-note {{in evaluating default argument here}} \
+	                    expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasDefaultArg'}}
+}
+
+void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'badForDefaultArg'}}
+}
+
+// Verify traversal of implicit code paths - constructors and destructors.
+struct Unsafe {
+  static void problem1();   // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+  static void problem2();   // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+  Unsafe() { problem1(); }  // expected-note {{constructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
+  ~Unsafe() { problem2(); } // expected-note {{destructor cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
+
+  Unsafe(int x); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}} expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+  // Delegating initializer.
+  Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+};
+
+struct DerivedFromUnsafe : public Unsafe {
+  DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+  DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+  ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
+};
+
+// Don't try to follow a deleted destructor, as with std::optional<T>.
+struct HasDtor {
+	~HasDtor() {}
+};
+
+template <typename T>
+struct Optional {
+	union {
+		char __null_state_;
+		T __val_;
+	};
+	bool engaged = false;
+
+	~Optional() {
+		if (engaged)
+			__val_.~T();
+	}
+};
+
+void nb_opt() [[clang::nonblocking]] {
+	Optional<HasDtor> x;
+}
+
+// Virtual inheritance
+struct VBase {
+  int *Ptr;
+
+  VBase() { Ptr = new int; }       // expected-note {{constructor cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
+  virtual ~VBase() { delete Ptr; } // expected-note {{virtual method cannot be inferred 'nonblocking'}}
+};
+
+struct VDerived : virtual VBase {
+  VDerived() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'VBase::VBase'}}
+
+  ~VDerived() [[clang::nonblocking]] {} // expected-warning {{destructor with 'nonblocking' attribute must not call non-'nonblocking' destructor 'VBase::~VBase'}}
+};
+
+// Contexts where there is no function call, no diagnostic.
+bool bad();
+
+template <bool>
+requires requires { bad(); }
+void g() [[clang::nonblocking]] {}
+
+void g() [[clang::nonblocking]] {
+    decltype(bad()) a; // doesn't generate a call so, OK
+    [[maybe_unused]] auto b = noexcept(bad());
+    [[maybe_unused]] auto c = sizeof(bad());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wassume"
+    [[assume(bad())]]; // never evaluated, but maybe still semantically questionable?
+#pragma clang diagnostic pop
+}
+
+// Make sure we are skipping concept requirements -- they can trigger an unexpected
+// warning involving use of a function pointer (e.g. std::reverse_iterator::operator==
+struct HasFoo { int foo() const { return 0; } };
+
+template <class A, class B>
+inline bool compare(const A& a, const B& b)
+	requires requires { 
+		a.foo();
+	}
+{
+	return a.foo() == b.foo();
+}
+
+void nb25() [[clang::nonblocking]] {
+	HasFoo a, b;
+	compare(a, b);
+}
+
+// If the callee is both noreturn and noexcept, it presumably terminates.
+// Ignore it for the purposes of effect analysis.
+[[noreturn]] void abort_wrapper() noexcept;
+
+void nb26() [[clang::nonblocking]] {
+	abort_wrapper(); // no diagnostic
+}
+
+// --- nonblocking implies noexcept ---
+#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
+
+void needs_noexcept() [[clang::nonblocking]] // expected-warning {{function with 'nonblocking' attribute should be declared noexcept}}
+{
+	auto lambda = []() [[clang::nonblocking]] {}; // expected-warning {{lambda with 'nonblocking' attribute should be declared noexcept}}
+}

diff  --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index 38bf2ac8f8a4cc..9056f81f5296b9 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -1,5 +1,5 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
-// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify -Wfunction-effects %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 -Wfunction-effects %s
 
 #if !__has_attribute(nonblocking)
 #error "the 'nonblocking' attribute is not available"
@@ -77,7 +77,7 @@ void type_conversions()
   void (*fp_nonallocating)() [[clang::nonallocating]];
   fp_nonallocating = nullptr;
   fp_nonallocating = nonallocating;
-  fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated;
+  fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating
   fp_nonallocating = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}}
 }
 

diff  --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 644ed754b04daa..90d074d01708f4 100644
--- a/clang/test/Sema/attr-nonblocking-syntax.cpp
+++ b/clang/test/Sema/attr-nonblocking-syntax.cpp
@@ -3,6 +3,7 @@
 // Make sure that the attribute gets parsed and attached to the correct AST elements.
 
 #pragma clang diagnostic ignored "-Wunused-variable"
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
 
 // =========================================================================================
 // Square brackets, true

diff  --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
new file mode 100644
index 00000000000000..abd0938ac321af
--- /dev/null
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify -Wfunction-effects %s
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// Objective-C
+ at interface OCClass
+- (void)method;
+ at end
+
+void nb1(OCClass *oc) [[clang::nonblocking]] {
+	[oc method]; // expected-warning {{function with 'nonblocking' attribute must not access ObjC methods or properties}}
+}
+void nb2(OCClass *oc) {
+	[oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
+}
+void nb3(OCClass *oc) [[clang::nonblocking]] {
+	nb2(oc); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'nb2'}}
+}
+
+void nb4() [[clang::nonblocking]] {
+	@try {
+		@throw @"foo"; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
+	}
+	@catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
+	}
+}


        


More information about the cfe-commits mailing list