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

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Sat Jul 20 08:57:31 PDT 2024


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

>From 8c5f85492091df2432701f15f4ec4b6acfe19944 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sun, 5 May 2024 12:36:53 -0700
Subject: [PATCH 1/2] nonblocking/nonallocating attributes: 2nd pass
 caller/callee analysis/verification

---
 clang/include/clang/AST/Type.h                |    2 +-
 clang/include/clang/Basic/DiagnosticGroups.td |    1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   49 +
 clang/include/clang/Sema/Sema.h               |   13 +
 .../include/clang/Serialization/ASTBitCodes.h |    4 +
 clang/include/clang/Serialization/ASTReader.h |    3 +
 clang/include/clang/Serialization/ASTWriter.h |    1 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp      | 1259 +++++++++++++++++
 clang/lib/Sema/SemaDecl.cpp                   |   56 +
 clang/lib/Sema/SemaExpr.cpp                   |    4 +
 clang/lib/Sema/SemaLambda.cpp                 |    5 +
 clang/lib/Serialization/ASTReader.cpp         |   19 +
 clang/lib/Serialization/ASTWriter.cpp         |   12 +
 .../Sema/attr-nonblocking-constraints.cpp     |  194 +++
 clang/test/Sema/attr-nonblocking-syntax.cpp   |    1 +
 .../attr-nonblocking-constraints.mm           |   23 +
 16 files changed, 1645 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/Sema/attr-nonblocking-constraints.cpp
 create mode 100644 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 25defea58c2dc..08141f75de8db 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4699,7 +4699,7 @@ class FunctionEffect {
 
 private:
   LLVM_PREFERRED_TYPE(Kind)
-  unsigned FKind : 3;
+  uint8_t FKind : 3;
 
   // Expansion: for hypothetical TCB+types, there could be one Kind for TCB,
   // then ~16(?) bits "SubKind" to map to a specific named TCB. SubKind would
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 19c3f1e043349..55d9442a939da 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1557,6 +1557,7 @@ def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInCon
 // Warnings and notes related to the function effects system underlying
 // the nonblocking and nonallocating attributes.
 def FunctionEffects : DiagGroup<"function-effects">;
+def PerfConstraintImpliesNoexcept : DiagGroup<"perf-constraint-implies-noexcept">;
 
 // 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 d60f32674ca3a..ec02c02d158c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10928,6 +10928,55 @@ def warn_imp_cast_drops_unaligned : Warning<
   InGroup<DiagGroup<"unaligned-qualifier-implicit-cast">>;
 
 // Function effects
+def warn_func_effect_allocates : Warning<
+  "'%0' function must not allocate or deallocate memory">,
+  InGroup<FunctionEffects>;
+def note_func_effect_allocates : Note<
+  "function cannot be inferred '%0' because it allocates/deallocates memory">;
+def warn_func_effect_throws_or_catches : Warning<
+  "'%0' function must not throw or catch exceptions">,
+  InGroup<FunctionEffects>;
+def note_func_effect_throws_or_catches : Note<
+  "function cannot be inferred '%0' because it throws or catches exceptions">;
+def warn_func_effect_has_static_local : Warning<
+  "'%0' function must not have static locals">,
+  InGroup<FunctionEffects>;
+def note_func_effect_has_static_local : Note<
+  "function cannot be inferred '%0' because it has a static local">;
+def warn_func_effect_uses_thread_local : Warning<
+  "'%0' function must not use thread-local variables">,
+  InGroup<FunctionEffects>;
+def note_func_effect_uses_thread_local : Note<
+  "function cannot be inferred '%0' because it uses a thread-local variable">;
+def warn_func_effect_calls_objc : Warning<
+  "'%0' function must not access an ObjC method or property">,
+  InGroup<FunctionEffects>;
+def note_func_effect_calls_objc : Note<
+  "function cannot be inferred '%0' because it accesses an ObjC method or property">;
+def warn_func_effect_calls_func_without_effect : Warning<
+  "'%0' function must not call non-'%0' function '%1'">,
+  InGroup<FunctionEffects>;
+def warn_func_effect_calls_expr_without_effect : Warning<
+  "'%0' function must not call non-'%0' expression">,
+  InGroup<FunctionEffects>;
+def note_func_effect_calls_func_without_effect : Note<
+  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+def note_func_effect_call_extern : Note<
+  "function cannot be inferred '%0' because it has no definition in this translation unit">;
+def note_func_effect_call_disallows_inference : Note<
+  "function does not permit inference of '%0'">;
+def note_func_effect_call_virtual : Note<
+  "virtual method cannot be inferred '%0'">;
+def note_func_effect_call_func_ptr : Note<
+  "function pointer cannot be inferred '%0'">;
+def warn_perf_constraint_implies_noexcept : Warning<
+  "'%0' function should be declared noexcept">,
+  InGroup<PerfConstraintImpliesNoexcept>;
+
+// FIXME: It would be nice if we could provide fuller template expansion notes.
+def note_func_effect_from_template : Note<
+  "in template expansion here">;
+
 // spoofing nonblocking/nonallocating
 def warn_invalid_add_func_effects : Warning<
   "attribute '%0' should not be added via type conversion">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d638d31e050dc..e1867348497da 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -875,6 +875,13 @@ class Sema final : public SemaBase {
 
   // ----- function effects ---
 
+  /// 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.
+  FunctionEffectSet AllEffectsToVerify;
+
   /// Warn when implicitly changing function effects.
   void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                         SourceLocation Loc);
@@ -891,6 +898,12 @@ class Sema final : public SemaBase {
                                        SourceLocation NewLoc,
                                        SourceLocation OldLoc);
 
+  /// 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);
+
   /// Try to parse the conditional expression attached to an effect attribute
   /// (e.g. 'nonblocking'). (c.f. Sema::ActOnNoexceptSpec). Return an empty
   /// optional on error.
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 5dd0ba33f8a9c..b975db88dbaae 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -721,6 +721,10 @@ enum ASTRecordTypes {
 
   /// Record code for \#pragma clang unsafe_buffer_usage begin/end
   PP_UNSAFE_BUFFER_USAGE = 69,
+
+  /// Record code for Sema's vector of functions/blocks with effects to
+  /// be verified.
+  DECLS_WITH_EFFECTS_TO_VERIFY = 70,
 };
 
 /// 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 76e51ac7ab979..1d8985602146b 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -948,6 +948,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, 0> DeclsWithEffectsToVerify;
+
 private:
   struct ImportedSubmodule {
     serialization::SubmoduleID ID;
diff --git a/clang/include/clang/Serialization/ASTWriter.h b/clang/include/clang/Serialization/ASTWriter.h
index a0e475ec9f862..4eaf77e8cb8d9 100644
--- a/clang/include/clang/Serialization/ASTWriter.h
+++ b/clang/include/clang/Serialization/ASTWriter.h
@@ -592,6 +592,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/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 0f604c61fa3af..3909d5b44a32e 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2397,6 +2397,1262 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 };
 } // namespace
 
+// =============================================================================
+
+namespace FXAnalysis {
+
+enum class DiagnosticID : uint8_t {
+  None = 0, // sentinel for an empty Diagnostic
+  Throws,
+  Catches,
+  CallsObjC,
+  AllocatesMemory,
+  HasStaticLocal,
+  AccessesThreadLocal,
+
+  // These only apply to callees, where the analysis stops at the Decl
+  DeclDisallowsInference,
+
+  CallsDeclWithoutEffect,
+  CallsExprWithoutEffect,
+};
+
+// Holds an effect diagnosis, 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.
+struct Diagnostic {
+  FunctionEffect Effect;
+  DiagnosticID ID = DiagnosticID::None;
+  SourceLocation Loc;
+  const Decl *Callee = nullptr; // only valid for Calls*
+
+  Diagnostic() = default;
+
+  Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
+             const Decl *Callee = nullptr)
+      : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+};
+
+enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
+enum class CallType {
+  // 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->hasBody() || FD->isInlined())) {
+    // externally defined; we couldn't verify if we wanted to.
+    return false;
+  }
+  if (FD->isTrivial()) {
+    // Otherwise `struct x { int a; };` would have an unverifiable default
+    // constructor.
+    return true;
+  }
+  return true;
+}
+
+/// A mutable set of FunctionEffect, for use in places where any conditions
+/// have been resolved or can be ignored.
+class EffectSet {
+  // This implementation optimizes footprint, since we hold one of these for
+  // every function visited, which, due to inference, can be many more functions
+  // than have declared effects.
+
+  template <typename T, typename SizeT, SizeT Capacity> struct FixedVector {
+    SizeT Count = 0;
+    T Items[Capacity] = {};
+
+    using value_type = T;
+
+    using iterator = T *;
+    using const_iterator = const T *;
+    iterator begin() { return &Items[0]; }
+    iterator end() { return &Items[Count]; }
+    const_iterator begin() const { return &Items[0]; }
+    const_iterator end() const { return &Items[Count]; }
+    const_iterator cbegin() const { return &Items[0]; }
+    const_iterator cend() const { return &Items[Count]; }
+
+    void insert(iterator I, const T &Value) {
+      assert(Count < Capacity);
+      iterator E = end();
+      if (I != E)
+        std::copy_backward(I, E, E + 1);
+      *I = Value;
+      ++Count;
+    }
+
+    void push_back(const T &Value) {
+      assert(Count < Capacity);
+      Items[Count++] = Value;
+    }
+  };
+
+  // As long as FunctionEffect is only 1 byte, and there are only 2 verifiable
+  // effects, this fixed-size vector with a capacity of 7 is more than
+  // sufficient and is only 8 bytes.
+  FixedVector<FunctionEffect, uint8_t, 7> Impl;
+
+public:
+  EffectSet() = default;
+  explicit EffectSet(FunctionEffectsRef FX) { insert(FX); }
+
+  operator ArrayRef<FunctionEffect>() const {
+    return ArrayRef(Impl.cbegin(), Impl.cend());
+  }
+
+  using iterator = const FunctionEffect *;
+  iterator begin() const { return Impl.cbegin(); }
+  iterator end() const { return Impl.cend(); }
+
+  void insert(const FunctionEffect &Effect) {
+    FunctionEffect *Iter = Impl.begin();
+    FunctionEffect *End = Impl.end();
+    // linear search; lower_bound is overkill for a tiny vector like this
+    for (; Iter != End; ++Iter) {
+      if (*Iter == Effect)
+        return;
+      if (Effect < *Iter)
+        break;
+    }
+    Impl.insert(Iter, Effect);
+  }
+  void insert(const EffectSet &Set) {
+    for (const FunctionEffect &Item : Set) {
+      // push_back because set is already sorted
+      Impl.push_back(Item);
+    }
+  }
+  void insert(FunctionEffectsRef FX) {
+    for (const FunctionEffectWithCondition &EC : FX) {
+      assert(EC.Cond.getCondition() ==
+             nullptr); // should be resolved by now, right?
+      // push_back because set is already sorted
+      Impl.push_back(EC.Effect);
+    }
+  }
+  bool contains(const FunctionEffect::Kind EK) const {
+    for (const FunctionEffect &E : Impl)
+      if (E.kind() == EK)
+        return true;
+    return false;
+  }
+
+  void dump(llvm::raw_ostream &OS) const;
+
+  static EffectSet difference(ArrayRef<FunctionEffect> LHS,
+                              ArrayRef<FunctionEffect> RHS) {
+    EffectSet Result;
+    std::set_difference(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+                        std::back_inserter(Result.Impl));
+    return Result;
+  }
+};
+
+LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
+  OS << "Effects{";
+  bool First = true;
+  for (const FunctionEffect &Effect : *this) {
+    if (!First)
+      OS << ", ";
+    else
+      First = false;
+    OS << Effect.name();
+  }
+  OS << "}";
+}
+
+// Transitory, more extended information about a callable, which can be a
+// function, block, function pointer, etc.
+struct CallableInfo {
+  // CDecl holds the function's definition, if any.
+  // FunctionDecl if CallType::Function or Virtual
+  // BlockDecl if CallType::Block
+  const Decl *CDecl;
+  mutable std::optional<std::string> MaybeName;
+  SpecialFuncType FuncType = SpecialFuncType::None;
+  EffectSet Effects;
+  CallType CType = CallType::Unknown;
+
+  CallableInfo(Sema &SemaRef, const Decl &CD,
+               SpecialFuncType FT = SpecialFuncType::None)
+      : CDecl(&CD), FuncType(FT) {
+    FunctionEffectsRef FXRef;
+
+    if (auto *FD = dyn_cast<FunctionDecl>(CDecl)) {
+      // Use the function's definition, if any.
+      if (const FunctionDecl *Def = FD->getDefinition())
+        CDecl = FD = Def;
+      CType = CallType::Function;
+      if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
+          Method && Method->isVirtual())
+        CType = CallType::Virtual;
+      FXRef = FD->getFunctionEffects();
+    } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
+      CType = CallType::Block;
+      FXRef = BD->getFunctionEffects();
+    } else if (auto *VD = dyn_cast<ValueDecl>(CDecl)) {
+      // ValueDecl is function, enum, or variable, so just look at its type.
+      FXRef = FunctionEffectsRef::get(VD->getType());
+    }
+    Effects = EffectSet(FXRef);
+  }
+
+  bool isDirectCall() const {
+    return CType == CallType::Function || CType == CallType::Block;
+  }
+
+  bool isVerifiable() const {
+    switch (CType) {
+    case CallType::Unknown:
+    case CallType::Virtual:
+      break;
+    case CallType::Block:
+      return true;
+    case CallType::Function:
+      return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+    }
+    return false;
+  }
+
+  /// Generate a name for logging and diagnostics.
+  std::string name(Sema &Sem) const {
+    if (!MaybeName) {
+      std::string Name;
+      llvm::raw_string_ostream OS(Name);
+
+      if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
+        FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+                                 /*Qualified=*/true);
+      else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
+        OS << "(block " << BD->getBlockManglingNumber() << ")";
+      else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
+        VD->printQualifiedName(OS);
+      MaybeName = Name;
+    }
+    return *MaybeName;
+  }
+};
+
+// ----------
+// Map effects to single diagnostics, to hold the first (of potentially many)
+// diagnostics pertaining to an effect, per function.
+class EffectToDiagnosticMap {
+  // Since we currently only have a tiny number of effects (typically no more
+  // than 1), use a sorted SmallVector with an inline capacity of 1. Since it
+  // is often empty, use a unique_ptr to the SmallVector.
+  // Note that Diagnostic itself contains a FunctionEffect which is the key.
+  using ImplVec = llvm::SmallVector<Diagnostic, 1>;
+  std::unique_ptr<ImplVec> Impl;
+
+public:
+  // Insert a new diagnostic if we do not already have one for its effect.
+  void maybeInsert(const Diagnostic &Diag) {
+    if (Impl == nullptr)
+      Impl = std::make_unique<ImplVec>();
+    auto *Iter = _find(Diag.Effect);
+    if (Iter != Impl->end() && Iter->Effect == Diag.Effect)
+      return;
+
+    Impl->insert(Iter, Diag);
+  }
+
+  const Diagnostic *lookup(FunctionEffect Key) {
+    if (Impl == nullptr)
+      return nullptr;
+
+    auto *Iter = _find(Key);
+    if (Iter != Impl->end() && Iter->Effect == Key)
+      return &*Iter;
+
+    return nullptr;
+  }
+
+  size_t size() const { return Impl ? Impl->size() : 0; }
+
+private:
+  ImplVec::iterator _find(const FunctionEffect &key) {
+    // A linear search suffices for a tiny number of possible effects.
+    auto *End = Impl->end();
+    for (auto *Iter = Impl->begin(); Iter != End; ++Iter)
+      if (!(Iter->Effect < key))
+        return Iter;
+    return End;
+  }
+};
+
+// ----------
+// 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;
+
+    DirectCall(const Decl *D, SourceLocation CallLoc)
+        : Callee(D), CallLoc(CallLoc) {}
+  };
+
+  // We always have two disjoint sets of effects to verify:
+  // 1. Effects declared explicitly by this function.
+  // 2. All other inferrable effects needing verification.
+  EffectSet DeclaredVerifiableEffects;
+  EffectSet FXToInfer;
+
+private:
+  // Diagnostics pertaining to the function's explicit effects.
+  SmallVector<Diagnostic, 0> DiagnosticsForExplicitFX;
+
+  // Diagnostics pertaining to other, non-explicit, inferrable effects.
+  EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+  // These unverified direct calls are what keeps the analysis "pending",
+  // until the callees can be verified.
+  SmallVector<DirectCall, 0> UnverifiedDirectCalls;
+
+public:
+  PendingFunctionAnalysis(
+      Sema &Sem, const CallableInfo &CInfo,
+      ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
+    DeclaredVerifiableEffects = CInfo.Effects;
+
+    // Check for effects we are not allowed to infer
+    EffectSet InferrableFX;
+
+    for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
+      if (effect.canInferOnFunction(*CInfo.CDecl))
+        InferrableFX.insert(effect);
+      else {
+        // Add a diagnostic for this effect if a caller were to
+        // try to infer it.
+        InferrableEffectToFirstDiagnostic.maybeInsert(
+            Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
+                       CInfo.CDecl->getLocation()));
+      }
+    }
+    // InferrableFX is now the set of inferrable effects which are not
+    // prohibited
+    FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects);
+  }
+
+  // Hide the way that diagnostics for explicitly required effects vs. inferred
+  // ones are handled differently.
+  void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+    if (!Inferring)
+      DiagnosticsForExplicitFX.push_back(NewDiag);
+    else
+      InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag);
+  }
+
+  void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
+    UnverifiedDirectCalls.emplace_back(D, CallLoc);
+  }
+
+  // Analysis is complete when there are no unverified direct calls.
+  bool isComplete() const { return UnverifiedDirectCalls.empty(); }
+
+  const Diagnostic *diagnosticForInferrableEffect(FunctionEffect effect) {
+    return InferrableEffectToFirstDiagnostic.lookup(effect);
+  }
+
+  SmallVector<DirectCall, 0> &unverifiedCalls() {
+    assert(!isComplete());
+    return UnverifiedDirectCalls;
+  }
+
+  SmallVector<Diagnostic, 0> &getDiagnosticsForExplicitFX() {
+    return DiagnosticsForExplicitFX;
+  }
+
+  void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
+    OS << "Pending: Declared ";
+    DeclaredVerifiableEffects.dump(OS);
+    OS << ", " << DiagnosticsForExplicitFX.size() << " diags; ";
+    OS << " Infer ";
+    FXToInfer.dump(OS);
+    OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+    if (!UnverifiedDirectCalls.empty()) {
+      OS << "; Calls: ";
+      for (const DirectCall &Call : UnverifiedDirectCalls) {
+        CallableInfo CI(SemaRef, *Call.Callee);
+        OS << " " << CI.name(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.
+  EffectSet VerifiedEffects;
+
+private:
+  // This is used to generate notes about failed inference.
+  EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+
+public:
+  // The incoming Pending analysis is consumed (member(s) are moved-from).
+  CompleteFunctionAnalysis(
+      ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+      const EffectSet &DeclaredEffects,
+      ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
+    VerifiedEffects.insert(DeclaredEffects);
+    for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
+      if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
+        VerifiedEffects.insert(effect);
+
+    InferrableEffectToFirstDiagnostic =
+        std::move(Pending.InferrableEffectToFirstDiagnostic);
+  }
+
+  const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) {
+    return InferrableEffectToFirstDiagnostic.lookup(Effect);
+  }
+
+  void dump(llvm::raw_ostream &OS) const {
+    OS << "Complete: Verified ";
+    VerifiedEffects.dump(OS);
+    OS << "; Infer ";
+    OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+  }
+};
+
+const Decl *CanonicalFunctionDecl(const Decl *D) {
+  if (auto *FD = dyn_cast<FunctionDecl>(D)) {
+    FD = FD->getCanonicalDecl();
+    assert(FD != nullptr);
+    return FD;
+  }
+  return D;
+}
+
+// ==========
+class Analyzer {
+  constexpr static int DebugLogLevel = 0;
+  // --
+  Sema &Sem;
+
+  // Subset of Sema.AllEffectsToVerify
+  EffectSet AllInferrableEffectsToVerify;
+
+  using FuncAnalysisPtr =
+      llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
+
+  // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
+  // than complete state, so use different objects to represent them.
+  // The state pointers are owned by the container.
+  class AnalysisMap : protected 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(CanonicalFunctionDecl(Key));
+    }
+
+    FuncAnalysisPtr &operator[](const Decl *Key) {
+      return Base::operator[](CanonicalFunctionDecl(Key));
+    }
+
+    /// Shortcut for the case where we only care about completed analysis.
+    CompleteFunctionAnalysis *completedAnalysisForDecl(const Decl *D) const {
+      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(SemaRef, *item.first);
+        const auto AP = item.second;
+        OS << item.first << " " << CI.name(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) : Sem(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 (const FunctionEffectWithCondition &CFE : Sem.AllEffectsToVerify) {
+      const FunctionEffect &Effect = CFE.Effect;
+      const FunctionEffect::Flags Flags = Effect.flags();
+      if (Flags & FunctionEffect::FE_InferrableOnCallees)
+        AllInferrableEffectsToVerify.insert(Effect);
+    }
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "AllInferrableEffectsToVerify: ";
+      AllInferrableEffectsToVerify.dump(llvm::outs());
+      llvm::outs() << "\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 = Sem.DeclsWithEffectsToVerify;
+    std::reverse(VerificationQueue.begin(), VerificationQueue.end());
+
+    while (!VerificationQueue.empty()) {
+      const Decl *D = VerificationQueue.back();
+      if (FuncAnalysisPtr AP = DeclAnalysis.lookup(D)) {
+        if (isa<CompleteFunctionAnalysis *>(AP)) {
+          // already done
+          VerificationQueue.pop_back();
+          continue;
+        }
+        if (isa<PendingFunctionAnalysis *>(AP)) {
+          // All children have been traversed; finish analysis.
+          auto *Pending = AP.get<PendingFunctionAnalysis *>();
+          finishPendingAnalysis(D, Pending);
+          VerificationQueue.pop_back();
+          continue;
+        }
+        llvm_unreachable("unexpected DeclAnalysis item");
+      }
+
+      // 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;
+        }
+        if (isa<PendingFunctionAnalysis *>(AP)) {
+          // This indicates recursion (not necessarily direct). For the
+          // purposes of effect analysis, we can just ignore it since
+          // no effects forbid recursion.
+          Call.Recursed = true;
+          continue;
+        }
+        llvm_unreachable("unexpected DeclAnalysis item");
+      }
+    }
+  }
+
+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(Sem, *D);
+    bool isExternC = false;
+
+    if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+      assert(FD->getBuiltinID() == 0);
+      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 (Sem.getLangOpts().CPlusPlus && !isExternC) {
+      for (const 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)
+          Sem.Diag(D->getBeginLoc(),
+                   diag::warn_perf_constraint_implies_noexcept)
+              << 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(Sem, CInfo, AllInferrableEffectsToVerify);
+
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "\nVerifying " << CInfo.name(Sem) << " ";
+      FAnalysis.dump(Sem, llvm::outs());
+    }
+
+    FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
+
+    Visitor.run();
+    if (FAnalysis.isComplete()) {
+      completeAnalysis(CInfo, 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;
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "inserted pending " << PendingPtr << "\n";
+      DeclAnalysis.dump(Sem, llvm::outs());
+    }
+    return PendingPtr;
+  }
+
+  // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
+  // inserted in the container.
+  void completeAnalysis(const CallableInfo &CInfo,
+                        PendingFunctionAnalysis &Pending) {
+    if (SmallVector<Diagnostic, 0> &Diags =
+            Pending.getDiagnosticsForExplicitFX();
+        !Diags.empty())
+      emitDiagnostics(Diags, CInfo, Sem);
+
+    CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
+        Sem.getASTContext(), Pending, CInfo.Effects,
+        AllInferrableEffectsToVerify);
+    DeclAnalysis[CInfo.CDecl] = CompletePtr;
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "inserted complete " << CompletePtr << "\n";
+      DeclAnalysis.dump(Sem, llvm::outs());
+    }
+  }
+
+  // Called after all direct calls requiring inference have been found -- or
+  // not. Repeats calls to FunctionBodyASTVisitor::followCall() but without
+  // the possibility of inference. Deletes Pending.
+  void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
+    CallableInfo Caller(Sem, *D);
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+      Pending->dump(Sem, llvm::outs());
+      llvm::outs() << "\n";
+    }
+    for (const PendingFunctionAnalysis::DirectCall &Call :
+         Pending->unverifiedCalls()) {
+      if (Call.Recursed)
+        continue;
+
+      CallableInfo Callee(Sem, *Call.Callee);
+      followCall(Caller, *Pending, Callee, Call.CallLoc,
+                 /*AssertNoFurtherInference=*/true);
+    }
+    completeAnalysis(Caller, *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) {
+    const bool DirectCall = Callee.isDirectCall();
+
+    // Initially, the declared effects; inferred effects will be added.
+    EffectSet 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;
+
+    if constexpr (DebugLogLevel > 0) {
+      llvm::outs() << "followCall from " << Caller.name(Sem) << " to "
+                   << Callee.name(Sem)
+                   << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+      CalleeEffects.dump(llvm::outs());
+      llvm::outs() << "\n";
+      llvm::outs() << "  callee " << Callee.CDecl << " canonical "
+                   << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+      for (Decl *D : Callee.CDecl->redecls())
+        llvm::outs() << " " << D;
+
+      llvm::outs() << "\n";
+    }
+
+    auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+      FunctionEffect::Flags Flags = Effect.flags();
+      bool Diagnose =
+          Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
+      if (Diagnose) {
+        // If inference is not allowed, or the target is indirect (virtual
+        // method/function ptr?), generate a diagnostic now.
+        if (!IsInferencePossible ||
+            !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
+          if (Callee.FuncType == SpecialFuncType::None)
+            PFA.checkAddDiagnostic(
+                Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
+                            CallLoc, Callee.CDecl});
+          else
+            PFA.checkAddDiagnostic(
+                Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
+        } else {
+          // Inference is allowed and necessary; defer it.
+          PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+        }
+      }
+    };
+
+    for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
+      check1Effect(Effect, false);
+
+    for (const FunctionEffect &Effect : PFA.FXToInfer)
+      check1Effect(Effect, true);
+  }
+
+  // Should only be called when determined to be complete.
+  void emitDiagnostics(SmallVector<Diagnostic, 0> &Diags,
+                       const CallableInfo &CInfo, Sema &S) {
+    if (Diags.empty())
+      return;
+    const SourceManager &SM = S.getSourceManager();
+    std::sort(Diags.begin(), Diags.end(),
+              [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+                return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+              });
+
+    auto checkAddTemplateNote = [&](const Decl *D) {
+      if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
+        while (FD != nullptr && FD->isTemplateInstantiation()) {
+          S.Diag(FD->getPointOfInstantiation(),
+                 diag::note_func_effect_from_template);
+          FD = FD->getTemplateInstantiationPattern();
+        }
+      }
+    };
+
+    // Top-level diagnostics are warnings.
+    for (const Diagnostic &Diag : Diags) {
+      StringRef effectName = Diag.Effect.name();
+      switch (Diag.ID) {
+      case DiagnosticID::None:
+      case DiagnosticID::DeclDisallowsInference: // shouldn't happen
+                                                 // here
+        llvm_unreachable("Unexpected diagnostic kind");
+        break;
+      case DiagnosticID::AllocatesMemory:
+        S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
+        checkAddTemplateNote(CInfo.CDecl);
+        break;
+      case DiagnosticID::Throws:
+      case DiagnosticID::Catches:
+        S.Diag(Diag.Loc, diag::warn_func_effect_throws_or_catches)
+            << effectName;
+        checkAddTemplateNote(CInfo.CDecl);
+        break;
+      case DiagnosticID::HasStaticLocal:
+        S.Diag(Diag.Loc, diag::warn_func_effect_has_static_local) << effectName;
+        checkAddTemplateNote(CInfo.CDecl);
+        break;
+      case DiagnosticID::AccessesThreadLocal:
+        S.Diag(Diag.Loc, diag::warn_func_effect_uses_thread_local)
+            << effectName;
+        checkAddTemplateNote(CInfo.CDecl);
+        break;
+      case DiagnosticID::CallsObjC:
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_objc) << effectName;
+        checkAddTemplateNote(CInfo.CDecl);
+        break;
+      case DiagnosticID::CallsExprWithoutEffect:
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_expr_without_effect)
+            << effectName;
+        checkAddTemplateNote(CInfo.CDecl);
+        break;
+
+      case DiagnosticID::CallsDeclWithoutEffect: {
+        CallableInfo CalleeInfo(S, *Diag.Callee);
+        std::string CalleeName = CalleeInfo.name(S);
+
+        S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
+            << effectName << CalleeName;
+        checkAddTemplateNote(CInfo.CDecl);
+
+        // Emit notes explaining the transitive chain of inferences: Why isn't
+        // the callee safe?
+        for (const Decl *Callee = Diag.Callee; Callee != nullptr;) {
+          std::optional<CallableInfo> MaybeNextCallee;
+          CompleteFunctionAnalysis *Completed =
+              DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
+          if (Completed == nullptr) {
+            // No result - could be
+            // - non-inline
+            // - indirect (virtual or through function pointer)
+            // - effect has been explicitly disclaimed (e.g. "blocking")
+            if (CalleeInfo.CType == CallType::Virtual)
+              S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
+                  << effectName;
+            else if (CalleeInfo.CType == CallType::Unknown)
+              S.Diag(Callee->getLocation(),
+                     diag::note_func_effect_call_func_ptr)
+                  << effectName;
+            else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind()))
+              S.Diag(Callee->getLocation(),
+                     diag::note_func_effect_call_disallows_inference)
+                  << effectName;
+            else
+              S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
+                  << effectName;
+
+            break;
+          }
+          const Diagnostic *PtrDiag2 =
+              Completed->firstDiagnosticForEffect(Diag.Effect);
+          if (PtrDiag2 == nullptr)
+            break;
+
+          const Diagnostic &Diag2 = *PtrDiag2;
+          switch (Diag2.ID) {
+          case DiagnosticID::None:
+            llvm_unreachable("Unexpected diagnostic kind");
+            break;
+          case DiagnosticID::DeclDisallowsInference:
+            S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
+                << effectName;
+            break;
+          case DiagnosticID::CallsExprWithoutEffect:
+            S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+                << effectName;
+            break;
+          case DiagnosticID::AllocatesMemory:
+            S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
+            break;
+          case DiagnosticID::Throws:
+          case DiagnosticID::Catches:
+            S.Diag(Diag2.Loc, diag::note_func_effect_throws_or_catches)
+                << effectName;
+            break;
+          case DiagnosticID::HasStaticLocal:
+            S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+                << effectName;
+            break;
+          case DiagnosticID::AccessesThreadLocal:
+            S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+                << effectName;
+            break;
+          case DiagnosticID::CallsObjC:
+            S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
+            break;
+          case DiagnosticID::CallsDeclWithoutEffect:
+            MaybeNextCallee.emplace(S, *Diag2.Callee);
+            S.Diag(Diag2.Loc, diag::note_func_effect_calls_func_without_effect)
+                << effectName << MaybeNextCallee->name(S);
+            break;
+          }
+          checkAddTemplateNote(Callee);
+          Callee = Diag2.Callee;
+          if (MaybeNextCallee) {
+            CalleeInfo = *MaybeNextCallee;
+            CalleeName = CalleeInfo.name(S);
+          }
+        }
+      } break;
+      }
+    }
+  }
+
+  // ----------
+  // This AST visitor is used to traverse the body of a function during effect
+  // verification. This happens in 2 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.
+  //
+  // Diagnostics are always routed to a PendingFunctionAnalysis, which holds
+  // all diagnostic output.
+  //
+  // Q: Currently we create a new RecursiveASTVisitor for every function
+  // analysis. Is it so lightweight that this is OK? It would appear so.
+  struct FunctionBodyASTVisitor
+      : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+    // The meanings of the boolean values returned by the Visit methods can be
+    // difficult to remember.
+    constexpr static bool Stop = false;
+    constexpr static bool Proceed = true;
+
+    Analyzer &Outer;
+    PendingFunctionAnalysis &CurrentFunction;
+    CallableInfo &CurrentCaller;
+
+    FunctionBodyASTVisitor(Analyzer &outer,
+                           PendingFunctionAnalysis &CurrentFunction,
+                           CallableInfo &CurrentCaller)
+        : Outer(outer), CurrentFunction(CurrentFunction),
+          CurrentCaller(CurrentCaller) {}
+
+    // -- Entry point --
+    void run() {
+      // The target function itself may have some implicit code paths beyond the
+      // body: member and base constructors and destructors. Visit these first.
+      if (const auto *FD = dyn_cast<const FunctionDecl>(CurrentCaller.CDecl)) {
+        if (auto *Ctor = dyn_cast<CXXConstructorDecl>(FD)) {
+          for (const CXXCtorInitializer *Initer : Ctor->inits())
+            if (Expr *Init = Initer->getInit())
+              VisitStmt(Init);
+        } else if (auto *Dtor = dyn_cast<CXXDestructorDecl>(FD))
+          followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
+      }
+      // else could be BlockDecl
+
+      // 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 diagnostic. \p Flag describes
+    // the construct.
+    void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+                                   SourceLocation Loc,
+                                   const Decl *Callee = nullptr) {
+      // If there are any declared verifiable effects which forbid the construct
+      // represented by the flag, store just one diagnostic.
+      for (const FunctionEffect &Effect :
+           CurrentFunction.DeclaredVerifiableEffects) {
+        if (Effect.flags() & Flag) {
+          addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee);
+          break;
+        }
+      }
+      // For each inferred effect which forbids the construct, store a
+      // diagnostic, if we don't already have a diagnostic for that effect.
+      for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+        if (Effect.flags() & Flag)
+          addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee);
+    }
+
+    void addDiagnostic(bool Inferring, const FunctionEffect &Effect,
+                       DiagnosticID D, SourceLocation Loc,
+                       const Decl *Callee = nullptr) {
+      CurrentFunction.checkAddDiagnostic(Inferring,
+                                         Diagnostic(Effect, D, Loc, Callee));
+    }
+
+    // Here we have a call to a Decl, either explicitly via a CallExpr or some
+    // other AST construct. CallableInfo pertains to the callee.
+    void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
+      // Currently, built-in functions are always considered safe.
+      // FIXME: Some are not.
+      if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
+          FD && FD->getBuiltinID() != 0)
+        return;
+
+      Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
+                       /*AssertNoFurtherInference=*/false);
+    }
+
+    void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
+      const QualType CalleeType = CalleeExpr->getType();
+      auto *FPT =
+          CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
+      EffectSet CalleeFX;
+      if (FPT)
+        CalleeFX.insert(FPT->getFunctionEffects());
+
+      auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+        if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
+                                  /*direct=*/false, CalleeFX))
+          addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect,
+                        Call->getBeginLoc());
+      };
+
+      for (const FunctionEffect &Effect :
+           CurrentFunction.DeclaredVerifiableEffects)
+        check1Effect(Effect, false);
+
+      for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+        check1Effect(Effect, true);
+    }
+
+    // This destructor's body should be followed by the caller, but here we
+    // follow the field and base destructors.
+    void followDestructor(const CXXRecordDecl *Rec,
+                          const CXXDestructorDecl *Dtor) {
+      for (const FieldDecl *Field : Rec->fields())
+        followTypeDtor(Field->getType());
+
+      if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+        for (const CXXBaseSpecifier &Base : Class->bases())
+          followTypeDtor(Base.getType());
+
+        for (const CXXBaseSpecifier &Base : Class->vbases())
+          followTypeDtor(Base.getType());
+      }
+    }
+
+    void followTypeDtor(QualType QT) {
+      const Type *Ty = QT.getTypePtr();
+      while (Ty->isArrayType()) {
+        const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
+        QT = Arr->getElementType();
+        Ty = QT.getTypePtr();
+      }
+
+      if (Ty->isRecordType()) {
+        if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
+          if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
+            CallableInfo CI(Outer.Sem, *Dtor);
+            followCall(CI, Dtor->getLocation());
+          }
+        }
+      }
+    }
+
+    // -- Methods for use of RecursiveASTVisitor --
+
+    bool shouldVisitImplicitCode() const { return true; }
+
+    bool shouldWalkTypesOfTypeLocs() const { return false; }
+
+    bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+                                DiagnosticID::Throws, Throw->getThrowLoc());
+      return Proceed;
+    }
+
+    bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                DiagnosticID::Catches, Catch->getCatchLoc());
+      return Proceed;
+    }
+
+    bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
+                                DiagnosticID::CallsObjC, Msg->getBeginLoc());
+      return Proceed;
+    }
+
+    bool VisitCallExpr(CallExpr *Call) {
+      if constexpr (DebugLogLevel > 2) {
+        llvm::errs() << "VisitCallExpr : "
+                     << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+                     << "\n";
+      }
+
+      Expr *CalleeExpr = Call->getCallee();
+      if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
+        CallableInfo CI(Outer.Sem, *Callee);
+        followCall(CI, Call->getBeginLoc());
+        return Proceed;
+      }
+
+      if (isa<CXXPseudoDestructorExpr>(CalleeExpr))
+        // just destroying a scalar, fine.
+        return Proceed;
+
+      // No Decl, just an Expr. Just check based on its type.
+      checkIndirectCall(Call, CalleeExpr);
+
+      return Proceed;
+    }
+
+    bool VisitVarDecl(VarDecl *Var) {
+      if constexpr (DebugLogLevel > 2) {
+        llvm::errs() << "VisitVarDecl : "
+                     << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+                     << "\n";
+      }
+
+      if (Var->isStaticLocal())
+        diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
+                                  DiagnosticID::HasStaticLocal,
+                                  Var->getLocation());
+
+      const QualType::DestructionKind DK =
+          Var->needsDestruction(Outer.Sem.getASTContext());
+      if (DK == QualType::DK_cxx_destructor) {
+        QualType QT = Var->getType();
+        if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
+          if (const auto *CxxRec =
+                  dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
+            if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
+              CallableInfo CI(Outer.Sem, *Dtor);
+              followCall(CI, Var->getLocation());
+            }
+          }
+        }
+      }
+      return Proceed;
+    }
+
+    bool VisitCXXNewExpr(CXXNewExpr *New) {
+      // BUG? It seems incorrect that RecursiveASTVisitor does not
+      // visit the call to operator new.
+      if (FunctionDecl *FD = New->getOperatorNew()) {
+        CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorNew);
+        followCall(CI, New->getBeginLoc());
+      }
+
+      // It's a bit excessive to check operator delete here, since it's
+      // just a fallback for operator new followed by a failed constructor.
+      // We could check it via New->getOperatorDelete().
+
+      // It DOES however visit the called constructor
+      return Proceed;
+    }
+
+    bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
+      // BUG? It seems incorrect that RecursiveASTVisitor does not
+      // visit the call to operator delete.
+      if (FunctionDecl *FD = Delete->getOperatorDelete()) {
+        CallableInfo CI(Outer.Sem, *FD, SpecialFuncType::OperatorDelete);
+        followCall(CI, Delete->getBeginLoc());
+      }
+
+      // It DOES however visit the called destructor
+
+      return Proceed;
+    }
+
+    bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
+      if constexpr (DebugLogLevel > 2) {
+        llvm::errs() << "VisitCXXConstructExpr : "
+                     << Construct->getBeginLoc().printToString(
+                            Outer.Sem.SourceMgr)
+                     << "\n";
+      }
+
+      // BUG? It seems incorrect that RecursiveASTVisitor does not
+      // visit the call to the constructor.
+      const CXXConstructorDecl *Ctor = Construct->getConstructor();
+      CallableInfo CI(Outer.Sem, *Ctor);
+      followCall(CI, Construct->getLocation());
+
+      return Proceed;
+    }
+
+    bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
+      if (Expr *E = DEI->getExpr())
+        TraverseStmt(E);
+
+      return Proceed;
+    }
+
+    bool TraverseLambdaExpr(LambdaExpr *Lambda) {
+      // We override this so as the be able to skip traversal of the lambda's
+      // body. We have to explicitly traverse the captures.
+      for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
+        if (TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
+                                  Lambda->capture_init_begin()[I]) == Stop)
+          return Stop;
+
+      return Proceed;
+    }
+
+    bool TraverseBlockExpr(BlockExpr * /*unused*/) {
+      // TODO: are the capture expressions (ctor call?) safe?
+      return Proceed;
+    }
+
+    bool VisitDeclRefExpr(const DeclRefExpr *E) {
+      const ValueDecl *Val = E->getDecl();
+      if (isa<VarDecl>(Val)) {
+        const VarDecl *Var = cast<VarDecl>(Val);
+        VarDecl::TLSKind TLSK = Var->getTLSKind();
+        if (TLSK != VarDecl::TLS_None) {
+          // At least on macOS, thread-local variables are initialized on
+          // first access, including a heap allocation.
+          diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
+                                    DiagnosticID::AccessesThreadLocal,
+                                    E->getLocation());
+        }
+      }
+      return Proceed;
+    }
+
+    // Unevaluated contexts: need to skip
+    // see https://reviews.llvm.org/rG777eb4bcfc3265359edb7c979d3e5ac699ad4641
+
+    bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) {
+      return TraverseStmt(Node->getResultExpr());
+    }
+    bool TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) {
+      return Proceed;
+    }
+
+    bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node) { return Proceed; }
+
+    bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node) { return Proceed; }
+
+    bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) { return Proceed; }
+
+    bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return Proceed; }
+  };
+};
+
+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 *>();
+  }
+}
+
+} // namespace FXAnalysis
+
+// =============================================================================
+
 //===----------------------------------------------------------------------===//
 // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
 //  warnings on a function, method, or block.
@@ -2551,6 +3807,9 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
                        SourceLocation())) {
     CallableVisitor(CallAnalyzers).TraverseTranslationUnitDecl(TU);
   }
+
+  if (S.Context.hasAnyFunctionEffects())
+    FXAnalysis::Analyzer{S}.run(*TU);
 }
 
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index bb25a0b3a45ae..adb4a8f4de685 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -3854,6 +3854,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);
+        }
       }
     }
   }
@@ -10897,6 +10902,53 @@ Attr *Sema::getImplicitCodeSegOrSectionAttrForFunction(const FunctionDecl *FD,
   return nullptr;
 }
 
+// Should only be called when getFunctionEffects() returns a non-empty set.
+// Decl should be a FunctionDecl or BlockDecl.
+void Sema::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) {
+  FunctionEffectSet::Conflicts Errs;
+
+  // 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(FunctionEffectWithCondition(EC.Effect, nullptr),
+                                Errs);
+      AnyVerifiable = true;
+    }
+  assert(Errs.empty() && "effects conflicts should not be possible here");
+
+  // Record the declaration for later analysis.
+  if (AnyVerifiable)
+    DeclsWithEffectsToVerify.push_back(D);
+}
+
 bool Sema::canFullyTypeCheckRedeclaration(ValueDecl *NewD, ValueDecl *OldD,
                                           QualType NewT, QualType OldT) {
   if (!NewD->getLexicalDeclContext()->isDependentContext())
@@ -15609,6 +15661,10 @@ Decl *Sema::ActOnStartOfFunctionDef(Scope *FnBodyScope, Decl *D,
       getCurLexicalContext()->getDeclKind() != Decl::ObjCImplementation)
     Diag(FD->getLocation(), diag::warn_function_def_in_objc_container);
 
+  if (Context.hasAnyFunctionEffects())
+    if (const auto FX = FD->getFunctionEffects(); !FX.empty())
+      maybeAddDeclWithEffects(FD, FX);
+
   return D;
 }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 8d24e34520e77..f9b04f5361f33 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16065,6 +16065,10 @@ ExprResult Sema::ActOnBlockStmtExpr(SourceLocation CaretLoc,
   BlockScopeInfo *BSI = cast<BlockScopeInfo>(FunctionScopes.back());
   BlockDecl *BD = BSI->TheDecl;
 
+  if (Context.hasAnyFunctionEffects())
+    if (const auto FX = BD->getFunctionEffects(); !FX.empty())
+      maybeAddDeclWithEffects(BD, FX);
+
   if (BSI->HasImplicitReturnType)
     deduceClosureReturnType(*BSI);
 
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index 601077e9f3334..e3bf582db3027 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1947,6 +1947,11 @@ ExprResult Sema::BuildCaptureInit(const Capture &Cap,
 ExprResult Sema::ActOnLambdaExpr(SourceLocation StartLoc, Stmt *Body) {
   LambdaScopeInfo LSI = *cast<LambdaScopeInfo>(FunctionScopes.back());
   ActOnFinishFunctionBody(LSI.CallOperator, Body);
+
+  if (Context.hasAnyFunctionEffects())
+    if (const auto FX = LSI.CallOperator->getFunctionEffects(); !FX.empty())
+      maybeAddDeclWithEffects(LSI.CallOperator, FX);
+
   return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
 }
 
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 3cb96df12e4da..5d81d921d0fff 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -3874,6 +3874,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);
@@ -8279,6 +8284,20 @@ void ASTReader::InitializeSema(Sema &S) {
         NewOverrides.applyOverrides(SemaObj->getLangOpts());
   }
 
+  if (!DeclsWithEffectsToVerify.empty()) {
+    for (GlobalDeclID ID : DeclsWithEffectsToVerify) {
+      Decl *D = GetDecl(ID);
+      FunctionEffectsRef FX;
+      if (auto *FD = dyn_cast<FunctionDecl>(D))
+        FX = FD->getFunctionEffects();
+      else if (auto *BD = dyn_cast<BlockDecl>(D))
+        FX = BD->getFunctionEffects();
+      if (!FX.empty())
+        SemaObj->addDeclWithEffects(D, FX);
+    }
+    DeclsWithEffectsToVerify.clear();
+  }
+
   SemaObj->OpenCLFeatures = OpenCLExtensions;
 
   UpdateSema();
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index c78d8943d6d92..faf9b9490bafc 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4627,6 +4627,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.
@@ -5564,6 +5575,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/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
new file mode 100644
index 0000000000000..c248293cf7634
--- /dev/null
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// These are in a separate file because errors (e.g. incompatible attributes) currently prevent
+// the AnalysisBasedWarnings pass from running at all.
+
+// This diagnostic is re-enabled and exercised in isolation later in this file.
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// --- CONSTRAINTS ---
+
+void nl1() [[clang::nonblocking]]
+{
+	auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+}
+
+void nl2() [[clang::nonblocking]]
+{
+	static int global; // expected-warning {{'nonblocking' function must not have static locals}}
+}
+
+void nl3() [[clang::nonblocking]]
+{
+	try {
+		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	}
+	catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	}
+}
+
+void nl4_inline() {}
+void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+void nl4() [[clang::nonblocking]]
+{
+	nl4_inline(); // OK
+	nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+
+struct HasVirtual {
+	virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
+};
+
+void nl5() [[clang::nonblocking]]
+{
+ 	HasVirtual hv;
+ 	hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+void nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nl6_transitively_unsafe()
+{
+	nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+}
+
+void nl6() [[clang::nonblocking]]
+{
+	nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+thread_local int tl_var{ 42 };
+
+bool tl_test() [[clang::nonblocking]]
+{
+	return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
+}
+
+void nl7()
+{
+	// Make sure we verify blocks
+	auto blk = ^() [[clang::nonblocking]] {
+		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	};
+}
+
+void nl8()
+{
+	// Make sure we verify lambdas
+	auto lambda = []() [[clang::nonblocking]] {
+		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	};
+}
+
+// Make sure template expansions are found and verified.
+	template <typename T>
+	struct Adder {
+		static T add_explicit(T x, T y) [[clang::nonblocking]]
+		{
+			return x + y; // expected-warning {{'nonblocking' function 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/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 nl9() [[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 {{'nonblocking' function must not call non-'nonblocking' function}} \
+		expected-note {{in template expansion here}}
+}
+
+void nl10(
+	void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
+	void (*fp2)() [[clang::nonblocking]]
+	) [[clang::nonblocking]]
+{
+	fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	fp2();
+}
+
+// Interactions with nonblocking(false)
+void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
+{
+}
+void nl11_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'}}
+};
+
+void nl11() [[clang::nonblocking]]
+{
+	nl11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	nl11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+
+	ComputedNB<true> CNB_true;
+	CNB_true.method();
+	
+	ComputedNB<false> CNB_false;
+	CNB_false.method(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+}
+
+// Verify that when attached to a redeclaration, the attribute successfully attaches.
+void nl12() {
+	static int x; // expected-warning {{'nonblocking' function must not have static locals}}
+}
+void nl12() [[clang::nonblocking]];
+void nl13() [[clang::nonblocking]] { nl12(); }
+
+// 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 nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
+	blk();
+}
+
+// References to blocks
+void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
+{
+	auto &ref = block;
+	ref();
+}
+
+
+// --- nonblocking implies noexcept ---
+#pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
+
+void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+{
+}
diff --git a/clang/test/Sema/attr-nonblocking-syntax.cpp b/clang/test/Sema/attr-nonblocking-syntax.cpp
index 644ed754b04da..90d074d01708f 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 0000000000000..aeb8b21f56e44
--- /dev/null
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+
+#if !__has_attribute(clang_nonblocking)
+#error "the 'nonblocking' attribute is not available"
+#endif
+
+#pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
+
+// Objective-C
+ at interface OCClass
+- (void)method;
+ at end
+
+void nl14(OCClass *oc) [[clang::nonblocking]] {
+	[oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
+}
+void nl15(OCClass *oc) {
+	[oc method]; // expected-note {{function cannot be inferred 'nonblocking' because it accesses an ObjC method or property}}
+}
+void nl16(OCClass *oc) [[clang::nonblocking]] {
+	nl15(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nl15'}}
+}
+

>From 95b7a00467423e1a0e320a2fe45811739ce4d61e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 20 Jul 2024 08:57:16 -0700
Subject: [PATCH 2/2] - Sema.h: Move function decls to be in the correct
 per-source-file sections. - Fix ObjC++ test which was using the attribute's
 old name.

---
 clang/include/clang/Sema/Sema.h               | 50 +++++++++----------
 .../attr-nonblocking-constraints.mm           |  4 --
 2 files changed, 24 insertions(+), 30 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e1867348497da..e3259f147ec88 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -873,8 +873,6 @@ class Sema final : public SemaBase {
   /// Warn when implicitly casting 0 to nullptr.
   void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
 
-  // ----- function effects ---
-
   /// All functions/lambdas/blocks which have bodies and which have a non-empty
   /// FunctionEffectsRef to be verified.
   SmallVector<const Decl *> DeclsWithEffectsToVerify;
@@ -886,30 +884,6 @@ class Sema final : public SemaBase {
   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);
-
-  /// 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);
-
-  /// 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
@@ -4343,6 +4317,24 @@ class Sema final : public SemaBase {
   // Whether the callee should be ignored in CUDA/HIP/OpenMP host/device check.
   bool shouldIgnoreInHostDeviceCheck(FunctionDecl *Callee);
 
+  /// 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);
+
+  /// 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);
+
 private:
   /// Function or variable declarations to be checked for whether the deferred
   /// diagnostics should be emitted.
@@ -15015,6 +15007,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,
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index aeb8b21f56e44..0600062e89c04 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -1,9 +1,5 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
 
-#if !__has_attribute(clang_nonblocking)
-#error "the 'nonblocking' attribute is not available"
-#endif
-
 #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
 
 // Objective-C



More information about the cfe-commits mailing list