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

Doug Wyatt via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 9 07:58:52 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 01/48] 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 25defea58c2dc2..08141f75de8dbc 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 19c3f1e0433496..55d9442a939dae 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 d60f32674ca3a6..ec02c02d158c89 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 d638d31e050dc6..e1867348497da8 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 5dd0ba33f8a9c2..b975db88dbaae6 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 76e51ac7ab9792..1d8985602146ba 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 a0e475ec9f862c..4eaf77e8cb8d99 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 0f604c61fa3af9..3909d5b44a32e7 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 bb25a0b3a45ae9..adb4a8f4de6853 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 8d24e34520e778..f9b04f5361f337 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 601077e9f3334d..e3bf582db30279 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 3cb96df12e4da0..5d81d921d0fffd 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 c78d8943d6d92e..faf9b9490bafc2 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 00000000000000..c248293cf76342
--- /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 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..aeb8b21f56e442
--- /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 02/48] - 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 e1867348497da8..e3259f147ec884 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 aeb8b21f56e442..0600062e89c045 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

>From 21c780aaeea3deb35c3ffc972eb126e02b48ec7a Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 20 Jul 2024 09:08:16 -0700
Subject: [PATCH 03/48] clang-format

---
 clang/include/clang/Sema/Sema.h | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e3259f147ec884..a42f6af3c70e48 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4317,7 +4317,8 @@ 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.
+  /// 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);

>From ff104137caa083888b51a78da5d439d45db04a1f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Jul 2024 07:40:33 -0700
Subject: [PATCH 04/48] - Detect ObjC @throw and @catch, diagnose identically
 to their C++ counterparts. - Tweak test cases.

---
 clang/lib/Sema/AnalysisBasedWarnings.cpp        | 12 ++++++++++++
 .../test/Sema/attr-nonblocking-constraints.cpp  |  5 +++--
 .../SemaObjCXX/attr-nonblocking-constraints.mm  | 17 ++++++++++++-----
 3 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 3909d5b44a32e7..f26468cfcdad54 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -3475,6 +3475,18 @@ class Analyzer {
       return Proceed;
     }
 
+    bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+                                DiagnosticID::Throws, Throw->getThrowLoc());
+      return Proceed;
+    }
+
+    bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                DiagnosticID::Catches, Catch->getAtCatchLoc());
+      return Proceed;
+    }
+
     bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
                                 DiagnosticID::CallsObjC, Msg->getBeginLoc());
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index c248293cf76342..e50c5b436daafc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -1,6 +1,6 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
 // These are in a separate file because errors (e.g. incompatible attributes) currently prevent
-// the AnalysisBasedWarnings pass from running at all.
+// 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"
@@ -9,7 +9,8 @@
 
 void nl1() [[clang::nonblocking]]
 {
-	auto* pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+	int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+	delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
 }
 
 void nl2() [[clang::nonblocking]]
diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index 0600062e89c045..ff5873c11c4fe7 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -fobjc-exceptions -verify %s
 
 #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
 
@@ -7,13 +7,20 @@ @interface OCClass
 - (void)method;
 @end
 
-void nl14(OCClass *oc) [[clang::nonblocking]] {
+void nb1(OCClass *oc) [[clang::nonblocking]] {
 	[oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
 }
-void nl15(OCClass *oc) {
+void nb2(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'}}
+void nb3(OCClass *oc) [[clang::nonblocking]] {
+	nb2(oc); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'nb2'}}
 }
 
+void nb4() [[clang::nonblocking]] {
+	@try {
+		@throw @"foo"; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	}
+	@catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	}
+}

>From d472964eb11d063b6d0afc433c69f6369d1ae1ae 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 05/48] FunctionEffect constructors: cast Kind to uint8_t Make
 FunctionEffect just have a Kind instead of a bitfield

(from PR #100753)
---
 clang/include/clang/AST/Type.h | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 08141f75de8dbc..509f0d95643b0b 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4698,26 +4698,25 @@ class FunctionEffect {
   };
 
 private:
-  LLVM_PREFERRED_TYPE(Kind)
-  uint8_t FKind : 3;
+  Kind FKind;
 
   // 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
   // be considered for uniqueness.
 
 public:
-  FunctionEffect() : FKind(unsigned(Kind::None)) {}
+  FunctionEffect() : FKind(Kind::None) {}
 
-  explicit FunctionEffect(Kind K) : FKind(unsigned(K)) {}
+  explicit FunctionEffect(Kind K) : FKind(K) {}
 
   /// The kind of the effect.
-  Kind kind() const { return Kind(FKind); }
+  Kind kind() const { return FKind; }
 
   /// Return the opposite kind, for effects which have opposites.
   Kind oppositeKind() const;
 
   /// For serialization.
-  uint32_t toOpaqueInt32() const { return FKind; }
+  uint32_t toOpaqueInt32() const { return uint32_t(FKind); }
   static FunctionEffect fromOpaqueInt32(uint32_t Value) {
     return FunctionEffect(Kind(Value));
   }

>From f1142db51dff9794be2f63c2a2aca585760b5620 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Jul 2024 12:48:59 -0700
Subject: [PATCH 06/48] New file: EFfectAnalysis.cpp. Use LLVM_DEBUG for debug
 logging. Remove unneeded isInline() check in functionIsVerifiable

---
 clang/include/clang/Sema/Sema.h          |    4 +
 clang/lib/Sema/AnalysisBasedWarnings.cpp | 1269 ---------------------
 clang/lib/Sema/CMakeLists.txt            |    1 +
 clang/lib/Sema/EffectAnalysis.cpp        | 1289 ++++++++++++++++++++++
 clang/lib/Sema/Sema.cpp                  |    3 +
 5 files changed, 1297 insertions(+), 1269 deletions(-)
 create mode 100644 clang/lib/Sema/EffectAnalysis.cpp

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a42f6af3c70e48..fae0a2f4f46dee 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,6 +530,10 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
                             const FunctionEffectsRef &New);
 };
 
+// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and move 
+// more of the effects implementation into that file?
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
+
 /// Sema - This implements semantic analysis and AST building for C.
 /// \nosubgrouping
 class Sema final : public SemaBase {
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index f26468cfcdad54..8268117a0addab 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2399,1272 +2399,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 
 // =============================================================================
 
-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 VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
-      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
-                                DiagnosticID::Throws, Throw->getThrowLoc());
-      return Proceed;
-    }
-
-    bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
-      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
-                                DiagnosticID::Catches, Catch->getAtCatchLoc());
-      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.
@@ -3819,9 +2553,6 @@ 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/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 2cee4f5ef6e99c..ea827323395d74 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -19,6 +19,7 @@ add_clang_library(clangSema
   CodeCompleteConsumer.cpp
   DeclSpec.cpp
   DelayedDiagnostic.cpp
+  EffectAnalysis.cpp
   HLSLExternalSemaSource.cpp
   IdentifierResolver.cpp
   JumpDiagnostics.cpp
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
new file mode 100644
index 00000000000000..ca1213658f4596
--- /dev/null
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -0,0 +1,1289 @@
+//=== EffectAnalysis.cpp - Sema warnings for 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 caller/callee analysis for function effects.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Sema/SemaInternal.h"
+
+#define DEBUG_TYPE "fxanalysis"
+
+using namespace clang;
+
+namespace {
+
+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->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()->castAs<FunctionProtoType>();
+  if (FPT->isNothrow() || FD->hasAttr<NoThrowAttr>())
+    return true;
+  return false;
+}
+
+/// 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:
+      return false;
+    case CallType::Block:
+      return true;
+    case CallType::Function:
+      return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
+    }
+    llvm_unreachable("undefined CallType");
+  }
+
+  /// 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 {
+  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);
+    }
+    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 = 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);
+
+    LLVM_DEBUG(
+      llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
+      FAnalysis.dump(Sem, llvm::dbgs());
+    );
+
+    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;
+    LLVM_DEBUG(
+      llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
+      DeclAnalysis.dump(Sem, llvm::dbgs());
+    );
+    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;
+    LLVM_DEBUG(
+      llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
+      DeclAnalysis.dump(Sem, 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(Sem, *D);
+    LLVM_DEBUG(
+      llvm::dbgs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+      Pending->dump(Sem, llvm::dbgs());
+      llvm::dbgs() << "\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;
+
+    LLVM_DEBUG(
+      llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
+                   << Callee.name(Sem)
+                   << "; verifiable: " << Callee.isVerifiable() << "; callee ";
+      CalleeEffects.dump(llvm::dbgs());
+      llvm::dbgs() << "\n";
+      llvm::dbgs() << "  callee " << Callee.CDecl << " canonical "
+                   << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+      for (Decl *D : Callee.CDecl->redecls())
+        llvm::dbgs() << " " << D;
+
+      llvm::dbgs() << "\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.
+  struct FunctionBodyASTVisitor
+      : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+
+    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 true;
+    }
+
+    bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                DiagnosticID::Catches, Catch->getCatchLoc());
+      return true;
+    }
+
+    bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
+                                DiagnosticID::Throws, Throw->getThrowLoc());
+      return true;
+    }
+
+    bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                DiagnosticID::Catches, Catch->getAtCatchLoc());
+      return true;
+    }
+
+    bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
+                                DiagnosticID::CallsObjC, Msg->getBeginLoc());
+      return true;
+    }
+
+    bool VisitCallExpr(CallExpr *Call) {
+      LLVM_DEBUG(
+        llvm::dbgs() << "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 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);
+
+      return true;
+    }
+
+    bool VisitVarDecl(VarDecl *Var) {
+      LLVM_DEBUG(
+        llvm::dbgs() << "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 true;
+    }
+
+    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 true;
+    }
+
+    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 true;
+    }
+
+    bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
+      LLVM_DEBUG(
+        llvm::dbgs() << "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 true;
+    }
+
+    bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
+      if (Expr *E = DEI->getExpr())
+        TraverseStmt(E);
+
+      return true;
+    }
+
+    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)
+        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 (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 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; }
+  };
+};
+
+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 {
+
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU)
+{
+	if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+	  // exit if having uncompilable errors or ignoring all warnings:
+	  return;
+	if (TU == nullptr)
+	  return;
+	Analyzer{S}.run(*TU);
+}
+
+} // namespace clang
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 2e989f0ba6fe45..35ba1da9770c4e 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1509,6 +1509,9 @@ void Sema::ActOnEndOfTranslationUnit() {
 
   AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl());
 
+  if (Context.hasAnyFunctionEffects())
+    performEffectAnalysis(*this, 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.

>From efe1b93804ed91634bd89d1ea9b64947c5af79d4 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 26 Jul 2024 14:29:42 -0700
Subject: [PATCH 07/48] CallableInfo doesn't need to cache the name. Clean up
 traversal of constructor initializers and implicit destructors (with test).

---
 clang/lib/Sema/EffectAnalysis.cpp             | 51 ++++++-------
 .../Sema/attr-nonblocking-constraints.cpp     | 71 +++++++++++--------
 2 files changed, 62 insertions(+), 60 deletions(-)

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index ca1213658f4596..862cb17b8bd783 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -199,7 +199,6 @@ struct CallableInfo {
   // 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;
@@ -247,20 +246,17 @@ struct CallableInfo {
 
   /// 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;
+    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);
+    return Name;
   }
 };
 
@@ -955,17 +951,10 @@ class Analyzer {
 
     // -- 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
+      // 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));
@@ -1043,18 +1032,18 @@ class Analyzer {
     void followDestructor(const CXXRecordDecl *Rec,
                           const CXXDestructorDecl *Dtor) {
       for (const FieldDecl *Field : Rec->fields())
-        followTypeDtor(Field->getType());
+        followTypeDtor(Field->getType(), Dtor);
 
       if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
         for (const CXXBaseSpecifier &Base : Class->bases())
-          followTypeDtor(Base.getType());
+          followTypeDtor(Base.getType(), Dtor);
 
         for (const CXXBaseSpecifier &Base : Class->vbases())
-          followTypeDtor(Base.getType());
+          followTypeDtor(Base.getType(), Dtor);
       }
     }
 
-    void followTypeDtor(QualType QT) {
+    void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) {
       const Type *Ty = QT.getTypePtr();
       while (Ty->isArrayType()) {
         const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
@@ -1066,7 +1055,7 @@ class Analyzer {
         if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
           if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
             CallableInfo CI(Outer.Sem, *Dtor);
-            followCall(CI, Dtor->getLocation());
+            followCall(CI, OuterDtor->getLocation());
           }
         }
       }
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index e50c5b436daafc..7c7e322df80964 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -7,18 +7,18 @@
 
 // --- CONSTRAINTS ---
 
-void nl1() [[clang::nonblocking]]
+void nb1() [[clang::nonblocking]]
 {
 	int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
 	delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
 }
 
-void nl2() [[clang::nonblocking]]
+void nb2() [[clang::nonblocking]]
 {
 	static int global; // expected-warning {{'nonblocking' function must not have static locals}}
 }
 
-void nl3() [[clang::nonblocking]]
+void nb3() [[clang::nonblocking]]
 {
 	try {
 		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
@@ -27,13 +27,13 @@ void nl3() [[clang::nonblocking]]
 	}
 }
 
-void nl4_inline() {}
-void nl4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb4_inline() {}
+void nb4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
 
-void nl4() [[clang::nonblocking]]
+void nb4() [[clang::nonblocking]]
 {
-	nl4_inline(); // OK
-	nl4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	nb4_inline(); // OK
+	nb4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
 }
 
 
@@ -41,21 +41,21 @@ struct HasVirtual {
 	virtual void unsafe(); // expected-note {{virtual method cannot be inferred 'nonblocking'}}
 };
 
-void nl5() [[clang::nonblocking]]
+void nb5() [[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()
+void nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb6_transitively_unsafe()
 {
-	nl6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
+	nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function}}
 }
 
-void nl6() [[clang::nonblocking]]
+void nb6() [[clang::nonblocking]]
 {
-	nl6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	nb6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
 }
 
 thread_local int tl_var{ 42 };
@@ -65,7 +65,7 @@ bool tl_test() [[clang::nonblocking]]
 	return tl_var > 0; // expected-warning {{'nonblocking' function must not use thread-local variables}}
 }
 
-void nl7()
+void nb7()
 {
 	// Make sure we verify blocks
 	auto blk = ^() [[clang::nonblocking]] {
@@ -73,7 +73,7 @@ void nl7()
 	};
 }
 
-void nl8()
+void nb8()
 {
 	// Make sure we verify lambdas
 	auto lambda = []() [[clang::nonblocking]] {
@@ -111,7 +111,7 @@ void nl8()
 		}
 	};
 
-void nl9() [[clang::nonblocking]]
+void nb9() [[clang::nonblocking]]
 {
 	Adder<int>::add_explicit(1, 2);
 	Adder<int>::add_implicit(1, 2);
@@ -121,7 +121,7 @@ void nl9() [[clang::nonblocking]]
 		expected-note {{in template expansion here}}
 }
 
-void nl10(
+void nb10(
 	void (*fp1)(), // expected-note {{function pointer cannot be inferred 'nonblocking'}}
 	void (*fp2)() [[clang::nonblocking]]
 	) [[clang::nonblocking]]
@@ -131,20 +131,20 @@ void nl10(
 }
 
 // Interactions with nonblocking(false)
-void nl11_no_inference_1() [[clang::nonblocking(false)]] // expected-note {{function does not permit inference of 'nonblocking'}}
+void nb11_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'}}
+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'}}
 };
 
-void nl11() [[clang::nonblocking]]
+void nb11() [[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}}
+	nb11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	nb11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
 
 	ComputedNB<true> CNB_true;
 	CNB_true.method();
@@ -154,11 +154,11 @@ void nl11() [[clang::nonblocking]]
 }
 
 // Verify that when attached to a redeclaration, the attribute successfully attaches.
-void nl12() {
+void nb12() {
 	static int x; // expected-warning {{'nonblocking' function must not have static locals}}
 }
-void nl12() [[clang::nonblocking]];
-void nl13() [[clang::nonblocking]] { nl12(); }
+void nb12() [[clang::nonblocking]];
+void nb13() [[clang::nonblocking]] { nb12(); }
 
 // C++ member function pointers
 struct PTMFTester {
@@ -175,21 +175,34 @@ void PTMFTester::convert() [[clang::nonblocking]]
 }
 
 // Block variables
-void nl17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
+void nb17(void (^blk)() [[clang::nonblocking]]) [[clang::nonblocking]] {
 	blk();
 }
 
 // References to blocks
-void nl18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
+void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
 {
 	auto &ref = block;
 	ref();
 }
 
+// Verify traversal of implicit code paths - constructors and destructors.
+struct Unsafe {
+  static void problem1(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+  static void problem2(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+  Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
+  ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
+};
+
+struct DerivedFromUnsafe : public Unsafe {
+  DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
+  ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
+};
 
 // --- nonblocking implies noexcept ---
 #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
 
-void nl19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+void nb19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
 {
 }

>From 7ffdbefa56e25f8398b32104c4616690ab076afe Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 1 Aug 2024 18:09:59 -0700
Subject: [PATCH 08/48] Simpler bitmap implementation of FunctionEffectKindSet

---
 clang/include/clang/AST/Type.h    |  75 +++++++++++++++-
 clang/include/clang/Sema/Sema.h   |   6 +-
 clang/lib/AST/Type.cpp            |  17 +++-
 clang/lib/Sema/EffectAnalysis.cpp | 137 +++++++++++++-----------------
 clang/lib/Sema/SemaDecl.cpp       |   6 +-
 5 files changed, 153 insertions(+), 88 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 89a74ff1fb285d..f4e6c8f04b8be8 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -119,6 +119,7 @@ class Expr;
 class ExtQualsTypeCommonBase;
 class FunctionDecl;
 class FunctionEffectSet;
+class FunctionEffectKindSet;
 class IdentifierInfo;
 class NamedDecl;
 class ObjCInterfaceDecl;
@@ -4755,7 +4756,7 @@ class FunctionEffect {
   // 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;
+                                  const FunctionEffectKindSet &CalleeFX) const;
 
   friend bool operator==(const FunctionEffect &LHS, const FunctionEffect &RHS) {
     return LHS.FKind == RHS.FKind;
@@ -4902,6 +4903,78 @@ 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.
+  using KindBitsT = uint8_t;
+  constexpr static size_t EndBitPos = 8;
+
+  KindBitsT KindBits = 0;
+
+  static KindBitsT kindToBit(FunctionEffect::Kind K) {
+    return 1u << KindBitsT(K);
+  }
+
+  explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
+
+public:
+  FunctionEffectKindSet() = default;
+  explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }
+
+  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 advanceIdx() {
+      while (Idx < EndBitPos && !(Outer->KindBits & (1u << Idx)))
+        ++Idx;
+    }
+
+  public:
+    iterator();
+    iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
+      advanceIdx();
+    }
+    bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
+    bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }
+
+    iterator operator++() {
+      ++Idx;
+      advanceIdx();
+      return *this;
+    }
+
+    FunctionEffect operator*() const {
+      assert(Idx < EndBitPos);
+      return FunctionEffect(FunctionEffect::Kind(Idx));
+    }
+  };
+
+  iterator begin() const { return iterator(*this, 0); }
+  iterator end() const { return iterator(*this, EndBitPos); }
+
+  void insert(const FunctionEffect &Effect) {
+    KindBits |= kindToBit(Effect.kind());
+  }
+  void insert(FunctionEffectsRef FX) {
+    for (const FunctionEffect &Item : FX.effects())
+      insert(Item);
+  }
+  void insert(const FunctionEffectKindSet &Set) { KindBits |= Set.KindBits; }
+
+  bool contains(const FunctionEffect::Kind EK) const {
+    return (KindBits & kindToBit(EK)) != 0;
+  }
+  void dump(llvm::raw_ostream &OS) const;
+
+  static FunctionEffectKindSet difference(const FunctionEffectKindSet &LHS,
+                                          const 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/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 49315edcca7464..b166825bef4a5f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,8 +530,8 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
                             const FunctionEffectsRef &New);
 };
 
-// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and move 
-// more of the effects implementation into that file?
+// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and
+// move more of the effects implementation into that file?
 void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
 
 /// Sema - This implements semantic analysis and AST building for C.
@@ -882,7 +882,7 @@ class Sema final : public SemaBase {
   SmallVector<const Decl *> DeclsWithEffectsToVerify;
   /// The union of all effects present on DeclsWithEffectsToVerify. Conditions
   /// are all null.
-  FunctionEffectSet AllEffectsToVerify;
+  FunctionEffectKindSet AllEffectsToVerify;
 
   /// Warn when implicitly changing function effects.
   void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index fdaab8e4345936..6152375eb091aa 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5163,12 +5163,12 @@ bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
 }
 
 bool FunctionEffect::shouldDiagnoseFunctionCall(
-    bool Direct, ArrayRef<FunctionEffect> CalleeFX) const {
+    bool Direct, const FunctionEffectKindSet &CalleeFX) const {
   switch (kind()) {
   case Kind::NonAllocating:
   case Kind::NonBlocking: {
     const Kind CallerKind = kind();
-    for (const auto &Effect : CalleeFX) {
+    for (const FunctionEffect &Effect : CalleeFX) {
       const Kind EK = Effect.kind();
       // Does callee have same or stronger constraint?
       if (EK == CallerKind ||
@@ -5311,6 +5311,19 @@ 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{";
+  bool First = true;
+  for (const auto &Effect : *this) {
+    if (!First)
+      OS << ", ";
+    else
+      First = false;
+    OS << Effect.name();
+  }
+  OS << "}";
+}
+
 FunctionEffectsRef
 FunctionEffectsRef::create(ArrayRef<FunctionEffect> FX,
                            ArrayRef<EffectConditionExpr> Conds) {
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 862cb17b8bd783..83c3a07b2c66c9 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -11,8 +11,8 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/AST/Decl.h"
-#include "clang/AST/Type.h"
 #include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Type.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Sema/SemaInternal.h"
 
@@ -81,6 +81,7 @@ static bool isNoexcept(const FunctionDecl *FD) {
   return false;
 }
 
+#if 0
 /// A mutable set of FunctionEffect, for use in places where any conditions
 /// have been resolved or can be ignored.
 class EffectSet {
@@ -191,6 +192,7 @@ LLVM_DUMP_METHOD void EffectSet::dump(llvm::raw_ostream &OS) const {
   }
   OS << "}";
 }
+#endif
 
 // Transitory, more extended information about a callable, which can be a
 // function, block, function pointer, etc.
@@ -200,7 +202,7 @@ struct CallableInfo {
   // BlockDecl if CallType::Block
   const Decl *CDecl;
   SpecialFuncType FuncType = SpecialFuncType::None;
-  EffectSet Effects;
+  FunctionEffectKindSet Effects;
   CallType CType = CallType::Unknown;
 
   CallableInfo(Sema &SemaRef, const Decl &CD,
@@ -224,7 +226,7 @@ struct CallableInfo {
       // ValueDecl is function, enum, or variable, so just look at its type.
       FXRef = FunctionEffectsRef::get(VD->getType());
     }
-    Effects = EffectSet(FXRef);
+    Effects = FunctionEffectKindSet(FXRef);
   }
 
   bool isDirectCall() const {
@@ -251,7 +253,7 @@ struct CallableInfo {
 
     if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
       FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
-                                /*Qualified=*/true);
+                               /*Qualified=*/true);
     else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
       OS << "(block " << BD->getBlockManglingNumber() << ")";
     else if (auto *VD = dyn_cast<NamedDecl>(CDecl))
@@ -328,8 +330,8 @@ class PendingFunctionAnalysis {
   // 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;
+  FunctionEffectKindSet DeclaredVerifiableEffects;
+  FunctionEffectKindSet FXToInfer;
 
 private:
   // Diagnostics pertaining to the function's explicit effects.
@@ -345,11 +347,10 @@ class PendingFunctionAnalysis {
 public:
   PendingFunctionAnalysis(
       Sema &Sem, const CallableInfo &CInfo,
-      ArrayRef<FunctionEffect> AllInferrableEffectsToVerify) {
-    DeclaredVerifiableEffects = CInfo.Effects;
-
+      const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+      : DeclaredVerifiableEffects(CInfo.Effects) {
     // Check for effects we are not allowed to infer
-    EffectSet InferrableFX;
+    FunctionEffectKindSet InferrableFX;
 
     for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
       if (effect.canInferOnFunction(*CInfo.CDecl))
@@ -364,7 +365,8 @@ class PendingFunctionAnalysis {
     }
     // InferrableFX is now the set of inferrable effects which are not
     // prohibited
-    FXToInfer = EffectSet::difference(InferrableFX, DeclaredVerifiableEffects);
+    FXToInfer = FunctionEffectKindSet::difference(InferrableFX,
+                                                  DeclaredVerifiableEffects);
   }
 
   // Hide the way that diagnostics for explicitly required effects vs. inferred
@@ -422,7 +424,7 @@ class CompleteFunctionAnalysis {
   // 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;
+  FunctionEffectKindSet VerifiedEffects;
 
 private:
   // This is used to generate notes about failed inference.
@@ -432,9 +434,9 @@ class CompleteFunctionAnalysis {
   // 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);
+      const FunctionEffectKindSet &DeclaredEffects,
+      const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+      : VerifiedEffects(DeclaredEffects) {
     for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
       if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
         VerifiedEffects.insert(effect);
@@ -469,7 +471,7 @@ class Analyzer {
   Sema &Sem;
 
   // Subset of Sema.AllEffectsToVerify
-  EffectSet AllInferrableEffectsToVerify;
+  FunctionEffectKindSet AllInferrableEffectsToVerify;
 
   using FuncAnalysisPtr =
       llvm::PointerUnion<PendingFunctionAnalysis *, CompleteFunctionAnalysis *>;
@@ -532,17 +534,14 @@ class Analyzer {
   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;
+    for (const FunctionEffect &Effect : Sem.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";
-    );
+    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,
@@ -640,10 +639,8 @@ class Analyzer {
     // a fairly trivial move to a heap-allocated object.
     PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
 
-    LLVM_DEBUG(
-      llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
-      FAnalysis.dump(Sem, llvm::dbgs());
-    );
+    LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
+               FAnalysis.dump(Sem, llvm::dbgs()););
 
     FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
 
@@ -656,10 +653,8 @@ class Analyzer {
     PendingFunctionAnalysis *PendingPtr =
         new PendingFunctionAnalysis(std::move(FAnalysis));
     DeclAnalysis[D] = PendingPtr;
-    LLVM_DEBUG(
-      llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
-      DeclAnalysis.dump(Sem, llvm::dbgs());
-    );
+    LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
+               DeclAnalysis.dump(Sem, llvm::dbgs()););
     return PendingPtr;
   }
 
@@ -676,10 +671,8 @@ class Analyzer {
         Sem.getASTContext(), Pending, CInfo.Effects,
         AllInferrableEffectsToVerify);
     DeclAnalysis[CInfo.CDecl] = CompletePtr;
-    LLVM_DEBUG(
-      llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
-      DeclAnalysis.dump(Sem, llvm::dbgs());
-    );
+    LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
+               DeclAnalysis.dump(Sem, llvm::dbgs()););
   }
 
   // Called after all direct calls requiring inference have been found -- or
@@ -687,11 +680,9 @@ class Analyzer {
   // the possibility of inference. Deletes Pending.
   void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
     CallableInfo Caller(Sem, *D);
-    LLVM_DEBUG(
-      llvm::dbgs() << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
-      Pending->dump(Sem, llvm::dbgs());
-      llvm::dbgs() << "\n";
-    );
+    LLVM_DEBUG(llvm::dbgs()
+                   << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
+               Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";);
     for (const PendingFunctionAnalysis::DirectCall &Call :
          Pending->unverifiedCalls()) {
       if (Call.Recursed)
@@ -713,7 +704,7 @@ class Analyzer {
     const bool DirectCall = Callee.isDirectCall();
 
     // Initially, the declared effects; inferred effects will be added.
-    EffectSet CalleeEffects = Callee.Effects;
+    FunctionEffectKindSet CalleeEffects = Callee.Effects;
 
     bool IsInferencePossible = DirectCall;
 
@@ -733,19 +724,16 @@ class Analyzer {
     if (!Callee.isVerifiable())
       IsInferencePossible = false;
 
-    LLVM_DEBUG(
-      llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
-                   << Callee.name(Sem)
-                   << "; verifiable: " << Callee.isVerifiable() << "; callee ";
-      CalleeEffects.dump(llvm::dbgs());
-      llvm::dbgs() << "\n";
-      llvm::dbgs() << "  callee " << Callee.CDecl << " canonical "
-                   << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
-      for (Decl *D : Callee.CDecl->redecls())
-        llvm::dbgs() << " " << D;
+    LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
+                            << Callee.name(Sem) << "; verifiable: "
+                            << Callee.isVerifiable() << "; callee ";
+               CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
+               llvm::dbgs()
+               << "  callee " << Callee.CDecl << " canonical "
+               << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
+               for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
 
-      llvm::dbgs() << "\n";
-    );
+               llvm::dbgs() << "\n";);
 
     auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
       FunctionEffect::Flags Flags = Effect.flags();
@@ -1008,7 +996,7 @@ class Analyzer {
       const QualType CalleeType = CalleeExpr->getType();
       auto *FPT =
           CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
-      EffectSet CalleeFX;
+      FunctionEffectKindSet CalleeFX;
       if (FPT)
         CalleeFX.insert(FPT->getFunctionEffects());
 
@@ -1098,11 +1086,10 @@ class Analyzer {
     }
 
     bool VisitCallExpr(CallExpr *Call) {
-      LLVM_DEBUG(
-        llvm::dbgs() << "VisitCallExpr : "
+      LLVM_DEBUG(llvm::dbgs()
+                     << "VisitCallExpr : "
                      << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
-                     << "\n";
-      );
+                     << "\n";);
 
       Expr *CalleeExpr = Call->getCallee();
       if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
@@ -1122,11 +1109,10 @@ class Analyzer {
     }
 
     bool VisitVarDecl(VarDecl *Var) {
-      LLVM_DEBUG(
-        llvm::dbgs() << "VisitVarDecl : "
+      LLVM_DEBUG(llvm::dbgs()
+                     << "VisitVarDecl : "
                      << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
-                     << "\n";
-      );
+                     << "\n";);
 
       if (Var->isStaticLocal())
         diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
@@ -1180,12 +1166,10 @@ class Analyzer {
     }
 
     bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
-      LLVM_DEBUG(
-        llvm::dbgs() << "VisitCXXConstructExpr : "
-                     << Construct->getBeginLoc().printToString(
-                            Outer.Sem.SourceMgr)
-                     << "\n";
-      );
+      LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
+                              << Construct->getBeginLoc().printToString(
+                                     Outer.Sem.SourceMgr)
+                              << "\n";);
 
       // BUG? It seems incorrect that RecursiveASTVisitor does not
       // visit the call to the constructor.
@@ -1208,7 +1192,7 @@ class Analyzer {
       // body. We have to explicitly traverse the captures.
       for (unsigned I = 0, N = Lambda->capture_size(); I < N; ++I)
         TraverseLambdaCapture(Lambda, Lambda->capture_begin() + I,
-                                  Lambda->capture_init_begin()[I]);
+                              Lambda->capture_init_begin()[I]);
 
       return true;
     }
@@ -1265,14 +1249,13 @@ Analyzer::AnalysisMap::~AnalysisMap() {
 
 namespace clang {
 
-void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU)
-{
-	if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
-	  // exit if having uncompilable errors or ignoring all warnings:
-	  return;
-	if (TU == nullptr)
-	  return;
-	Analyzer{S}.run(*TU);
+void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
+  if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+    // exit if having uncompilable errors or ignoring all warnings:
+    return;
+  if (TU == nullptr)
+    return;
+  Analyzer{S}.run(*TU);
 }
 
 } // namespace clang
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b54da058c43c5e..ef27d42b92ce69 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10929,8 +10929,6 @@ void Sema::maybeAddDeclWithEffects(const Decl *D,
 }
 
 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.
@@ -10938,11 +10936,9 @@ void Sema::addDeclWithEffects(const Decl *D, const FunctionEffectsRef &FX) {
   bool AnyVerifiable = false;
   for (const FunctionEffectWithCondition &EC : FX)
     if (EC.Effect.flags() & FunctionEffect::FE_InferrableOnCallees) {
-      AllEffectsToVerify.insert(FunctionEffectWithCondition(EC.Effect, nullptr),
-                                Errs);
+      AllEffectsToVerify.insert(EC.Effect);
       AnyVerifiable = true;
     }
-  assert(Errs.empty() && "effects conflicts should not be possible here");
 
   // Record the declaration for later analysis.
   if (AnyVerifiable)

>From c39e28e78f882a148914e7c17c60c01e461bea07 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 1 Aug 2024 21:36:11 -0700
Subject: [PATCH 09/48] - function does not permit inference of '%0': include
 the name of the effect preventing inference. - EffectAnalysis: Rename
 Diagnostic to Violation to clarify that it is an abstraction which can be
 reported as either a warning or a note depending on context.

---
 clang/include/clang/AST/Type.h                |  13 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   2 +-
 clang/lib/AST/Type.cpp                        |  21 +-
 clang/lib/Sema/EffectAnalysis.cpp             | 296 +++++++++---------
 .../Sema/attr-nonblocking-constraints.cpp     |   2 +-
 5 files changed, 172 insertions(+), 162 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index f4e6c8f04b8be8..b13ba7d784b3a9 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -118,8 +118,9 @@ class EnumDecl;
 class Expr;
 class ExtQualsTypeCommonBase;
 class FunctionDecl;
-class FunctionEffectSet;
+class FunctionEffectsRef;
 class FunctionEffectKindSet;
+class FunctionEffectSet;
 class IdentifierInfo;
 class NamedDecl;
 class ObjCInterfaceDecl;
@@ -4745,11 +4746,15 @@ 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.
+  /// 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,
+                             const FunctionEffectsRef &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
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 709535dace2cf8..eedc2722a47993 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10969,7 +10969,7 @@ def note_func_effect_calls_func_without_effect : Note<
 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'">;
+  "function does not permit inference of '%0' because it is declared '%1'">;
 def note_func_effect_call_virtual : Note<
   "virtual method cannot be inferred '%0'">;
 def note_func_effect_call_func_ptr : Note<
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 6152375eb091aa..06f6bb03d1d9f1 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5128,35 +5128,30 @@ StringRef FunctionEffect::name() const {
   llvm_unreachable("unknown effect kind");
 }
 
-bool FunctionEffect::canInferOnFunction(const Decl &Callee) const {
+std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
+    const Decl &Callee, const FunctionEffectsRef &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) {
       // nonblocking/nonallocating cannot call allocating.
       if (CalleeEC.Effect.kind() == Kind::Allocating)
-        return false;
+        return CalleeEC.Effect;
       // nonblocking cannot call blocking.
       if (kind() == Kind::NonBlocking &&
           CalleeEC.Effect.kind() == Kind::Blocking)
-        return false;
+        return CalleeEC.Effect;
     }
-    return true;
+    return std::nullopt;
   }
 
   case Kind::Allocating:
   case Kind::Blocking:
-    return false;
+    assert(0 && "effectProhibitingInference with non-inferable effect kind");
+    break;
 
   case Kind::None:
-    assert(0 && "canInferOnFunction with None");
+    assert(0 && "effectProhibitingInference with None");
     break;
   }
   llvm_unreachable("unknown effect kind");
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 83c3a07b2c66c9..a8bbb73faf9fd8 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -22,8 +22,8 @@ using namespace clang;
 
 namespace {
 
-enum class DiagnosticID : uint8_t {
-  None = 0, // sentinel for an empty Diagnostic
+enum class ViolationID : uint8_t {
+  None = 0, // sentinel for an empty Violation
   Throws,
   Catches,
   CallsObjC,
@@ -38,20 +38,28 @@ enum class DiagnosticID : uint8_t {
   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 {
+// 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;
-  DiagnosticID ID = DiagnosticID::None;
+  FunctionEffect CalleeEffectPreventingInference; // only for certain IDs
+  ViolationID ID = ViolationID::None;
   SourceLocation Loc;
   const Decl *Callee = nullptr; // only valid for Calls*
 
-  Diagnostic() = default;
+  Violation() = default;
 
-  Diagnostic(const FunctionEffect &Effect, DiagnosticID ID, SourceLocation Loc,
-             const Decl *Callee = nullptr)
-      : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {}
+  Violation(const FunctionEffect &Effect, ViolationID ID, SourceLocation Loc,
+            const Decl *Callee = nullptr,
+            const FunctionEffect *CalleeEffect = nullptr)
+      : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
+    if (CalleeEffect != nullptr)
+      CalleeEffectPreventingInference = *CalleeEffect;
+  }
 };
 
 enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
@@ -202,13 +210,13 @@ struct CallableInfo {
   // BlockDecl if CallType::Block
   const Decl *CDecl;
   SpecialFuncType FuncType = SpecialFuncType::None;
+  FunctionEffectsRef DeclEffects;
   FunctionEffectKindSet 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.
@@ -218,15 +226,15 @@ struct CallableInfo {
       if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
           Method && Method->isVirtual())
         CType = CallType::Virtual;
-      FXRef = FD->getFunctionEffects();
+      DeclEffects = FD->getFunctionEffects();
     } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
       CType = CallType::Block;
-      FXRef = BD->getFunctionEffects();
+      DeclEffects = 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());
+      DeclEffects = FunctionEffectsRef::get(VD->getType());
     }
-    Effects = FunctionEffectKindSet(FXRef);
+    Effects = FunctionEffectKindSet(DeclEffects);
   }
 
   bool isDirectCall() const {
@@ -263,29 +271,29 @@ struct CallableInfo {
 };
 
 // ----------
-// Map effects to single diagnostics, to hold the first (of potentially many)
-// diagnostics pertaining to an effect, per function.
-class EffectToDiagnosticMap {
+// 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 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>;
+  // Note that Violation itself contains a FunctionEffect which is the key.
+  using ImplVec = llvm::SmallVector<Violation, 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) {
+  // 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>();
-    auto *Iter = _find(Diag.Effect);
-    if (Iter != Impl->end() && Iter->Effect == Diag.Effect)
+    auto *Iter = _find(Viol.Effect);
+    if (Iter != Impl->end() && Iter->Effect == Viol.Effect)
       return;
 
-    Impl->insert(Iter, Diag);
+    Impl->insert(Iter, Viol);
   }
 
-  const Diagnostic *lookup(FunctionEffect Key) {
+  const Violation *lookup(FunctionEffect Key) {
     if (Impl == nullptr)
       return nullptr;
 
@@ -334,11 +342,11 @@ class PendingFunctionAnalysis {
   FunctionEffectKindSet FXToInfer;
 
 private:
-  // Diagnostics pertaining to the function's explicit effects.
-  SmallVector<Diagnostic, 0> DiagnosticsForExplicitFX;
+  // Violations pertaining to the function's explicit effects.
+  SmallVector<Violation, 0> ViolationsForExplicitFX;
 
-  // Diagnostics pertaining to other, non-explicit, inferrable effects.
-  EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+  // 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.
@@ -353,14 +361,16 @@ class PendingFunctionAnalysis {
     FunctionEffectKindSet InferrableFX;
 
     for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
-      if (effect.canInferOnFunction(*CInfo.CDecl))
+      std::optional<FunctionEffect> ProblemCalleeEffect =
+          effect.effectProhibitingInference(*CInfo.CDecl, CInfo.DeclEffects);
+      if (!ProblemCalleeEffect)
         InferrableFX.insert(effect);
       else {
-        // Add a diagnostic for this effect if a caller were to
+        // Add a Violation for this effect if a caller were to
         // try to infer it.
-        InferrableEffectToFirstDiagnostic.maybeInsert(
-            Diagnostic(effect, DiagnosticID::DeclDisallowsInference,
-                       CInfo.CDecl->getLocation()));
+        InferrableEffectToFirstViolation.maybeInsert(Violation(
+            effect, ViolationID::DeclDisallowsInference,
+            CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
       }
     }
     // InferrableFX is now the set of inferrable effects which are not
@@ -369,13 +379,13 @@ class PendingFunctionAnalysis {
                                                   DeclaredVerifiableEffects);
   }
 
-  // Hide the way that diagnostics for explicitly required effects vs. inferred
+  // Hide the way that Violations for explicitly required effects vs. inferred
   // ones are handled differently.
-  void checkAddDiagnostic(bool Inferring, const Diagnostic &NewDiag) {
+  void checkAddViolation(bool Inferring, const Violation &NewViol) {
     if (!Inferring)
-      DiagnosticsForExplicitFX.push_back(NewDiag);
+      ViolationsForExplicitFX.push_back(NewViol);
     else
-      InferrableEffectToFirstDiagnostic.maybeInsert(NewDiag);
+      InferrableEffectToFirstViolation.maybeInsert(NewViol);
   }
 
   void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
@@ -385,8 +395,8 @@ class PendingFunctionAnalysis {
   // 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);
+  const Violation *violationForInferrableEffect(FunctionEffect effect) {
+    return InferrableEffectToFirstViolation.lookup(effect);
   }
 
   SmallVector<DirectCall, 0> &unverifiedCalls() {
@@ -394,17 +404,17 @@ class PendingFunctionAnalysis {
     return UnverifiedDirectCalls;
   }
 
-  SmallVector<Diagnostic, 0> &getDiagnosticsForExplicitFX() {
-    return DiagnosticsForExplicitFX;
+  SmallVector<Violation, 0> &getViolationsForExplicitFX() {
+    return ViolationsForExplicitFX;
   }
 
   void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
     OS << "Pending: Declared ";
     DeclaredVerifiableEffects.dump(OS);
-    OS << ", " << DiagnosticsForExplicitFX.size() << " diags; ";
+    OS << ", " << ViolationsForExplicitFX.size() << " violations; ";
     OS << " Infer ";
     FXToInfer.dump(OS);
-    OS << ", " << InferrableEffectToFirstDiagnostic.size() << " diags";
+    OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
     if (!UnverifiedDirectCalls.empty()) {
       OS << "; Calls: ";
       for (const DirectCall &Call : UnverifiedDirectCalls) {
@@ -428,7 +438,7 @@ class CompleteFunctionAnalysis {
 
 private:
   // This is used to generate notes about failed inference.
-  EffectToDiagnosticMap InferrableEffectToFirstDiagnostic;
+  EffectToViolationMap InferrableEffectToFirstViolation;
 
 public:
   // The incoming Pending analysis is consumed (member(s) are moved-from).
@@ -438,22 +448,22 @@ class CompleteFunctionAnalysis {
       const FunctionEffectKindSet &AllInferrableEffectsToVerify)
       : VerifiedEffects(DeclaredEffects) {
     for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
-      if (Pending.diagnosticForInferrableEffect(effect) == nullptr)
+      if (Pending.violationForInferrableEffect(effect) == nullptr)
         VerifiedEffects.insert(effect);
 
-    InferrableEffectToFirstDiagnostic =
-        std::move(Pending.InferrableEffectToFirstDiagnostic);
+    InferrableEffectToFirstViolation =
+        std::move(Pending.InferrableEffectToFirstViolation);
   }
 
-  const Diagnostic *firstDiagnosticForEffect(const FunctionEffect &Effect) {
-    return InferrableEffectToFirstDiagnostic.lookup(Effect);
+  const Violation *firstViolationForEffect(const FunctionEffect &Effect) {
+    return InferrableEffectToFirstViolation.lookup(Effect);
   }
 
   void dump(llvm::raw_ostream &OS) const {
     OS << "Complete: Verified ";
     VerifiedEffects.dump(OS);
     OS << "; Infer ";
-    OS << InferrableEffectToFirstDiagnostic.size() << " diags\n";
+    OS << InferrableEffectToFirstViolation.size() << " violations\n";
   }
 };
 
@@ -662,10 +672,9 @@ class Analyzer {
   // inserted in the container.
   void completeAnalysis(const CallableInfo &CInfo,
                         PendingFunctionAnalysis &Pending) {
-    if (SmallVector<Diagnostic, 0> &Diags =
-            Pending.getDiagnosticsForExplicitFX();
-        !Diags.empty())
-      emitDiagnostics(Diags, CInfo, Sem);
+    if (SmallVector<Violation, 0> &Viols = Pending.getViolationsForExplicitFX();
+        !Viols.empty())
+      emitDiagnostics(Viols, CInfo, Sem);
 
     CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
         Sem.getASTContext(), Pending, CInfo.Effects,
@@ -741,16 +750,16 @@ class Analyzer {
           Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects);
       if (Diagnose) {
         // If inference is not allowed, or the target is indirect (virtual
-        // method/function ptr?), generate a diagnostic now.
+        // method/function ptr?), generate a Violation now.
         if (!IsInferencePossible ||
             !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
           if (Callee.FuncType == SpecialFuncType::None)
-            PFA.checkAddDiagnostic(
-                Inferring, {Effect, DiagnosticID::CallsDeclWithoutEffect,
-                            CallLoc, Callee.CDecl});
+            PFA.checkAddViolation(Inferring,
+                                  {Effect, ViolationID::CallsDeclWithoutEffect,
+                                   CallLoc, Callee.CDecl});
           else
-            PFA.checkAddDiagnostic(
-                Inferring, {Effect, DiagnosticID::AllocatesMemory, CallLoc});
+            PFA.checkAddViolation(
+                Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
         } else {
           // Inference is allowed and necessary; defer it.
           PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
@@ -766,13 +775,13 @@ class Analyzer {
   }
 
   // Should only be called when determined to be complete.
-  void emitDiagnostics(SmallVector<Diagnostic, 0> &Diags,
+  void emitDiagnostics(SmallVector<Violation, 0> &Viols,
                        const CallableInfo &CInfo, Sema &S) {
-    if (Diags.empty())
+    if (Viols.empty())
       return;
     const SourceManager &SM = S.getSourceManager();
-    std::sort(Diags.begin(), Diags.end(),
-              [&SM](const Diagnostic &LHS, const Diagnostic &RHS) {
+    std::sort(Viols.begin(), Viols.end(),
+              [&SM](const Violation &LHS, const Violation &RHS) {
                 return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
               });
 
@@ -786,55 +795,56 @@ class Analyzer {
       }
     };
 
-    // 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");
+    // 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 DiagnosticID::AllocatesMemory:
-        S.Diag(Diag.Loc, diag::warn_func_effect_allocates) << effectName;
+      case ViolationID::AllocatesMemory:
+        S.Diag(Viol1.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)
+      case ViolationID::Throws:
+      case ViolationID::Catches:
+        S.Diag(Viol1.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;
+      case ViolationID::HasStaticLocal:
+        S.Diag(Viol1.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)
+      case ViolationID::AccessesThreadLocal:
+        S.Diag(Viol1.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;
+      case ViolationID::CallsObjC:
+        S.Diag(Viol1.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)
+      case ViolationID::CallsExprWithoutEffect:
+        S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
             << effectName;
         checkAddTemplateNote(CInfo.CDecl);
         break;
 
-      case DiagnosticID::CallsDeclWithoutEffect: {
-        CallableInfo CalleeInfo(S, *Diag.Callee);
+      case ViolationID::CallsDeclWithoutEffect: {
+        CallableInfo CalleeInfo(S, *Viol1.Callee);
         std::string CalleeName = CalleeInfo.name(S);
 
-        S.Diag(Diag.Loc, diag::warn_func_effect_calls_func_without_effect)
+        S.Diag(Viol1.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;) {
+        for (const Decl *Callee = Viol1.Callee; Callee != nullptr;) {
           std::optional<CallableInfo> MaybeNextCallee;
           CompleteFunctionAnalysis *Completed =
               DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
@@ -850,61 +860,62 @@ class Analyzer {
               S.Diag(Callee->getLocation(),
                      diag::note_func_effect_call_func_ptr)
                   << effectName;
-            else if (CalleeInfo.Effects.contains(Diag.Effect.oppositeKind()))
+            else if (CalleeInfo.Effects.contains(Viol1.Effect.oppositeKind()))
               S.Diag(Callee->getLocation(),
                      diag::note_func_effect_call_disallows_inference)
-                  << effectName;
+                  << effectName
+                  << FunctionEffect(Viol1.Effect.oppositeKind()).name();
             else
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_extern)
                   << effectName;
 
             break;
           }
-          const Diagnostic *PtrDiag2 =
-              Completed->firstDiagnosticForEffect(Diag.Effect);
-          if (PtrDiag2 == nullptr)
+          const Violation *PtrViol2 =
+              Completed->firstViolationForEffect(Viol1.Effect);
+          if (PtrViol2 == nullptr)
             break;
 
-          const Diagnostic &Diag2 = *PtrDiag2;
-          switch (Diag2.ID) {
-          case DiagnosticID::None:
-            llvm_unreachable("Unexpected diagnostic kind");
+          const Violation &Viol2 = *PtrViol2;
+          switch (Viol2.ID) {
+          case ViolationID::None:
+            llvm_unreachable("Unexpected violation kind");
             break;
-          case DiagnosticID::DeclDisallowsInference:
-            S.Diag(Diag2.Loc, diag::note_func_effect_call_disallows_inference)
-                << effectName;
+          case ViolationID::DeclDisallowsInference:
+            S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference)
+                << effectName << Viol2.CalleeEffectPreventingInference.name();
             break;
-          case DiagnosticID::CallsExprWithoutEffect:
-            S.Diag(Diag2.Loc, diag::note_func_effect_call_func_ptr)
+          case ViolationID::CallsExprWithoutEffect:
+            S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr)
                 << effectName;
             break;
-          case DiagnosticID::AllocatesMemory:
-            S.Diag(Diag2.Loc, diag::note_func_effect_allocates) << effectName;
+          case ViolationID::AllocatesMemory:
+            S.Diag(Viol2.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)
+          case ViolationID::Throws:
+          case ViolationID::Catches:
+            S.Diag(Viol2.Loc, diag::note_func_effect_throws_or_catches)
                 << effectName;
             break;
-          case DiagnosticID::HasStaticLocal:
-            S.Diag(Diag2.Loc, diag::note_func_effect_has_static_local)
+          case ViolationID::HasStaticLocal:
+            S.Diag(Viol2.Loc, diag::note_func_effect_has_static_local)
                 << effectName;
             break;
-          case DiagnosticID::AccessesThreadLocal:
-            S.Diag(Diag2.Loc, diag::note_func_effect_uses_thread_local)
+          case ViolationID::AccessesThreadLocal:
+            S.Diag(Viol2.Loc, diag::note_func_effect_uses_thread_local)
                 << effectName;
             break;
-          case DiagnosticID::CallsObjC:
-            S.Diag(Diag2.Loc, diag::note_func_effect_calls_objc) << effectName;
+          case ViolationID::CallsObjC:
+            S.Diag(Viol2.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)
+          case ViolationID::CallsDeclWithoutEffect:
+            MaybeNextCallee.emplace(S, *Viol2.Callee);
+            S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
                 << effectName << MaybeNextCallee->name(S);
             break;
           }
           checkAddTemplateNote(Callee);
-          Callee = Diag2.Callee;
+          Callee = Viol2.Callee;
           if (MaybeNextCallee) {
             CalleeInfo = *MaybeNextCallee;
             CalleeName = CalleeInfo.name(S);
@@ -922,8 +933,7 @@ class Analyzer {
   //  [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.
+  // Violations are always routed to a PendingFunctionAnalysis.
   struct FunctionBodyASTVisitor
       : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
 
@@ -951,32 +961,32 @@ class Analyzer {
     // -- 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
+    // flags include the specified flag receive a violation. \p Flag describes
     // the construct.
-    void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, DiagnosticID D,
+    void diagnoseLanguageConstruct(FunctionEffect::FlagBit Flag, ViolationID 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.
+      // represented by the flag, store just one violation..
       for (const FunctionEffect &Effect :
            CurrentFunction.DeclaredVerifiableEffects) {
         if (Effect.flags() & Flag) {
-          addDiagnostic(/*inferring=*/false, Effect, D, Loc, Callee);
+          addViolation(/*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.
+      // violation, if we don't already have a violation for that effect.
       for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
         if (Effect.flags() & Flag)
-          addDiagnostic(/*inferring=*/true, Effect, D, Loc, Callee);
+          addViolation(/*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));
+    void addViolation(bool Inferring, const FunctionEffect &Effect,
+                      ViolationID D, SourceLocation Loc,
+                      const Decl *Callee = nullptr) {
+      CurrentFunction.checkAddViolation(Inferring,
+                                        Violation(Effect, D, Loc, Callee));
     }
 
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -1003,8 +1013,8 @@ class Analyzer {
       auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
         if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
                                   /*direct=*/false, CalleeFX))
-          addDiagnostic(Inferring, Effect, DiagnosticID::CallsExprWithoutEffect,
-                        Call->getBeginLoc());
+          addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
+                       Call->getBeginLoc());
       };
 
       for (const FunctionEffect &Effect :
@@ -1057,31 +1067,31 @@ class Analyzer {
 
     bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
-                                DiagnosticID::Throws, Throw->getThrowLoc());
+                                ViolationID::Throws, Throw->getThrowLoc());
       return true;
     }
 
     bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
-                                DiagnosticID::Catches, Catch->getCatchLoc());
+                                ViolationID::Catches, Catch->getCatchLoc());
       return true;
     }
 
     bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
-                                DiagnosticID::Throws, Throw->getThrowLoc());
+                                ViolationID::Throws, Throw->getThrowLoc());
       return true;
     }
 
     bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
-                                DiagnosticID::Catches, Catch->getAtCatchLoc());
+                                ViolationID::Catches, Catch->getAtCatchLoc());
       return true;
     }
 
     bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
-                                DiagnosticID::CallsObjC, Msg->getBeginLoc());
+                                ViolationID::CallsObjC, Msg->getBeginLoc());
       return true;
     }
 
@@ -1116,7 +1126,7 @@ class Analyzer {
 
       if (Var->isStaticLocal())
         diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
-                                  DiagnosticID::HasStaticLocal,
+                                  ViolationID::HasStaticLocal,
                                   Var->getLocation());
 
       const QualType::DestructionKind DK =
@@ -1211,7 +1221,7 @@ class Analyzer {
           // At least on macOS, thread-local variables are initialized on
           // first access, including a heap allocation.
           diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
-                                    DiagnosticID::AccessesThreadLocal,
+                                    ViolationID::AccessesThreadLocal,
                                     E->getLocation());
         }
       }
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 7c7e322df80964..00fcc1714e22cf 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -138,7 +138,7 @@ void nb11_no_inference_2() [[clang::nonblocking(false)]]; // expected-note {{fun
 
 template <bool V>
 struct ComputedNB {
-	void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking'}}
+	void method() [[clang::nonblocking(V)]]; // expected-note {{function does not permit inference of 'nonblocking' because it is declared 'blocking'}}
 };
 
 void nb11() [[clang::nonblocking]]

>From 06ca4c53d6de8575abd3b8c9981feb7d2e1cea0e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 07:26:41 -0700
Subject: [PATCH 10/48] Add tests around lambda traversal and contexts like
 decltype, sizeof, etc.

---
 .../Sema/attr-nonblocking-constraints.cpp     | 43 ++++++++++++++++++-
 1 file changed, 42 insertions(+), 1 deletion(-)

diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 00fcc1714e22cf..bad5309dbb543c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -1,4 +1,4 @@
-// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
+// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -std=c++20 -verify %s
 // These are in a separate file because errors (e.g. incompatible attributes) currently prevent
 // the FXAnalysis pass from running at all.
 
@@ -81,6 +81,29 @@ void nb8()
 	};
 }
 
+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 {{'nonblocking' function 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 {
@@ -200,6 +223,24 @@ struct DerivedFromUnsafe : public Unsafe {
   ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
 };
 
+// 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
+}
+
+
 // --- nonblocking implies noexcept ---
 #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
 

>From 9e45e6f59d6f6fc501181bc535741c415c1b8ead Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 08:06:30 -0700
Subject: [PATCH 11/48] Inline checks preceding maybeAddDeclWithEffects

---
 clang/include/clang/Sema/Sema.h | 9 +++++++++
 clang/lib/Sema/SemaDecl.cpp     | 4 +---
 clang/lib/Sema/SemaExpr.cpp     | 4 +---
 clang/lib/Sema/SemaLambda.cpp   | 4 +---
 4 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b166825bef4a5f..e27517f2ac541a 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4337,6 +4337,15 @@ class Sema final : public SemaBase {
                                        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);
 
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index b21617a5db39ec..4d81594673678d 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15679,9 +15679,7 @@ 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);
+  maybeAddDeclWithEffects(FD);
 
   return D;
 }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 126e924666356d..2412e84588b763 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -16091,9 +16091,7 @@ 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);
+  maybeAddDeclWithEffects(BD);
 
   if (BSI->HasImplicitReturnType)
     deduceClosureReturnType(*BSI);
diff --git a/clang/lib/Sema/SemaLambda.cpp b/clang/lib/Sema/SemaLambda.cpp
index e3bf582db30279..bb797df28d09ad 100644
--- a/clang/lib/Sema/SemaLambda.cpp
+++ b/clang/lib/Sema/SemaLambda.cpp
@@ -1948,9 +1948,7 @@ 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);
+  maybeAddDeclWithEffects(LSI.CallOperator);
 
   return BuildLambdaExpr(StartLoc, Body->getEndLoc(), &LSI);
 }

>From b99f7841ada916df8ed177006856ecfa38402067 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 08:41:49 -0700
Subject: [PATCH 12/48] Remove dead code in EffectAnalysis.cpp. Add comment
 about AST traversal and lambdas.

add tests involving ms-extensions
---
 clang/lib/Sema/EffectAnalysis.cpp             | 125 ++----------------
 .../Sema/attr-nonblocking-constraints-ms.cpp  |  26 ++++
 2 files changed, 37 insertions(+), 114 deletions(-)
 create mode 100644 clang/test/Sema/attr-nonblocking-constraints-ms.cpp

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index a8bbb73faf9fd8..d9ec4197b9d428 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -12,6 +12,7 @@
 
 #include "clang/AST/Decl.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"
@@ -89,119 +90,6 @@ static bool isNoexcept(const FunctionDecl *FD) {
   return false;
 }
 
-#if 0
-/// 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 << "}";
-}
-#endif
-
 // Transitory, more extended information about a callable, which can be a
 // function, block, function pointer, etc.
 struct CallableInfo {
@@ -1095,6 +983,12 @@ class Analyzer {
       return true;
     }
 
+    bool VisitSEHExceptStmt(SEHExceptStmt *Exc) {
+      diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
+                                ViolationID::Catches, Exc->getExceptLoc());
+      return true;
+    }
+
     bool VisitCallExpr(CallExpr *Call) {
       LLVM_DEBUG(llvm::dbgs()
                      << "VisitCallExpr : "
@@ -1199,7 +1093,10 @@ class Analyzer {
 
     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.
+      // 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]);
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..dbdc39a304ba1b
--- /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 %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 {{'nonblocking' function must not throw or catch exceptions}}
+}
+
+struct S {
+    int get_x(); // expected-note {{function 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 {{'nonblocking' function must not call non-'nonblocking' function 'S::get_x'}}
+    a.nb;
+    a.nb2;
+}

>From 1b9874f8731ab5227b9698200015a38d9df77f78 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 7 Aug 2024 09:10:40 -0700
Subject: [PATCH 13/48] patch what the bot's clang-format wishes clang-format
 on my system would do

---
 clang/lib/Sema/EffectAnalysis.cpp | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index d9ec4197b9d428..80dbb0f4adeb4f 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -628,7 +628,9 @@ class Analyzer {
                llvm::dbgs()
                << "  callee " << Callee.CDecl << " canonical "
                << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
-               for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
+               for (Decl *D
+                    : Callee.CDecl->redecls()) llvm::dbgs()
+               << " " << D;
 
                llvm::dbgs() << "\n";);
 

>From 8390e691ed71f4a35e4f9bd2e9eeb83ea0f64c31 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 8 Aug 2024 06:23:29 -0700
Subject: [PATCH 14/48] "function cannot be inferred" -> "declaration cannot be
 inferred"

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td    | 2 +-
 clang/test/Sema/attr-nonblocking-constraints-ms.cpp | 2 +-
 clang/test/Sema/attr-nonblocking-constraints.cpp    | 8 ++++----
 3 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f1ffe4689daeff..ccec94c353d953 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10975,7 +10975,7 @@ def warn_func_effect_calls_expr_without_effect : Warning<
 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">;
+  "declaration 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' because it is declared '%1'">;
 def note_func_effect_call_virtual : Note<
diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
index dbdc39a304ba1b..d2c25da462c404 100644
--- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -8,7 +8,7 @@ void f1() [[clang::nonblocking]] {
 }
 
 struct S {
-    int get_x(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+    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; }
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index bad5309dbb543c..869e56374fe2c3 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -28,7 +28,7 @@ void nb3() [[clang::nonblocking]]
 }
 
 void nb4_inline() {}
-void nb4_not_inline(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
 
 void nb4() [[clang::nonblocking]]
 {
@@ -47,7 +47,7 @@ void nb5() [[clang::nonblocking]]
  	hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
 }
 
-void nb6_unsafe(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+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}}
@@ -211,8 +211,8 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
 
 // Verify traversal of implicit code paths - constructors and destructors.
 struct Unsafe {
-  static void problem1(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
-  static void problem2(); // expected-note {{function cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+  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 {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
   ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}

>From 7acda8c8891bdb19edd429863fcb8a1fc80ea2fc Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 8 Aug 2024 09:42:03 -0700
Subject: [PATCH 15/48] No need to override VisitCXXDefaultInitExpr. Minor
 terseness improvements.

CallableInfo ctor doesn't need a Sema.
FunctionEffect::effectProhibitingInference can receive a FunctionEffectKindSet and eliminate the need for CallableInfo to cache a FunctionEffectsRef.
CallType -> CallableType; isDirectCall() -> isCalledDirectly().
---
 clang/include/clang/AST/Type.h    |   2 +-
 clang/lib/AST/Type.cpp            |  13 ++-
 clang/lib/Sema/EffectAnalysis.cpp | 141 +++++++++++++++---------------
 3 files changed, 76 insertions(+), 80 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 5d730ce98c1b13..cff10ffdc3a469 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4766,7 +4766,7 @@ class FunctionEffect {
   /// function.
   std::optional<FunctionEffect>
   effectProhibitingInference(const Decl &Callee,
-                             const FunctionEffectsRef &CalleeFX) const;
+                             const 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
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index cfc961647d04d5..4fd4032725f1a9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5138,18 +5138,17 @@ StringRef FunctionEffect::name() const {
 }
 
 std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
-    const Decl &Callee, const FunctionEffectsRef &CalleeFX) const {
+    const Decl &Callee, const FunctionEffectKindSet &CalleeFX) const {
   switch (kind()) {
   case Kind::NonAllocating:
   case Kind::NonBlocking: {
-    for (const FunctionEffectWithCondition &CalleeEC : CalleeFX) {
+    for (const FunctionEffect &Effect : CalleeFX) {
       // nonblocking/nonallocating cannot call allocating.
-      if (CalleeEC.Effect.kind() == Kind::Allocating)
-        return CalleeEC.Effect;
+      if (Effect.kind() == Kind::Allocating)
+        return Effect;
       // nonblocking cannot call blocking.
-      if (kind() == Kind::NonBlocking &&
-          CalleeEC.Effect.kind() == Kind::Blocking)
-        return CalleeEC.Effect;
+      if (kind() == Kind::NonBlocking && Effect.kind() == Kind::Blocking)
+        return Effect;
     }
     return std::nullopt;
   }
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 80dbb0f4adeb4f..799500aa8caff7 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -64,7 +64,7 @@ struct Violation {
 };
 
 enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
-enum class CallType {
+enum class CallableType {
   // unknown: probably function pointer
   Unknown,
   Function,
@@ -91,32 +91,39 @@ static bool isNoexcept(const FunctionDecl *FD) {
 }
 
 // Transitory, more extended information about a callable, which can be a
-// function, block, function pointer, etc.
+// function, block, or function pointer.
 struct CallableInfo {
   // CDecl holds the function's definition, if any.
-  // FunctionDecl if CallType::Function or Virtual
-  // BlockDecl if CallType::Block
+  // 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;
-  FunctionEffectsRef DeclEffects;
+
+  // We inevitably want to know the callable's declared effects, so cache them.
   FunctionEffectKindSet Effects;
-  CallType CType = CallType::Unknown;
 
-  CallableInfo(Sema &SemaRef, const Decl &CD,
-               SpecialFuncType FT = SpecialFuncType::None)
+  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 = CallType::Function;
+      CType = CallableType::Function;
       if (auto *Method = dyn_cast<CXXMethodDecl>(FD);
           Method && Method->isVirtual())
-        CType = CallType::Virtual;
+        CType = CallableType::Virtual;
       DeclEffects = FD->getFunctionEffects();
     } else if (auto *BD = dyn_cast<BlockDecl>(CDecl)) {
-      CType = CallType::Block;
+      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.
@@ -125,21 +132,21 @@ struct CallableInfo {
     Effects = FunctionEffectKindSet(DeclEffects);
   }
 
-  bool isDirectCall() const {
-    return CType == CallType::Function || CType == CallType::Block;
+  bool isCalledDirectly() const {
+    return CType == CallableType::Function || CType == CallableType::Block;
   }
 
   bool isVerifiable() const {
     switch (CType) {
-    case CallType::Unknown:
-    case CallType::Virtual:
+    case CallableType::Unknown:
+    case CallableType::Virtual:
       return false;
-    case CallType::Block:
+    case CallableType::Block:
       return true;
-    case CallType::Function:
+    case CallableType::Function:
       return functionIsVerifiable(dyn_cast<FunctionDecl>(CDecl));
     }
-    llvm_unreachable("undefined CallType");
+    llvm_unreachable("undefined CallableType");
   }
 
   /// Generate a name for logging and diagnostics.
@@ -250,7 +257,7 @@ class PendingFunctionAnalysis {
 
     for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
       std::optional<FunctionEffect> ProblemCalleeEffect =
-          effect.effectProhibitingInference(*CInfo.CDecl, CInfo.DeclEffects);
+          effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
       if (!ProblemCalleeEffect)
         InferrableFX.insert(effect);
       else {
@@ -306,7 +313,7 @@ class PendingFunctionAnalysis {
     if (!UnverifiedDirectCalls.empty()) {
       OS << "; Calls: ";
       for (const DirectCall &Call : UnverifiedDirectCalls) {
-        CallableInfo CI(SemaRef, *Call.Callee);
+        CallableInfo CI(*Call.Callee);
         OS << " " << CI.name(SemaRef);
       }
     }
@@ -405,7 +412,7 @@ class Analyzer {
     void dump(Sema &SemaRef, llvm::raw_ostream &OS) {
       OS << "\nAnalysisMap:\n";
       for (const auto &item : *this) {
-        CallableInfo CI(SemaRef, *item.first);
+        CallableInfo CI(*item.first);
         const auto AP = item.second;
         OS << item.first << " " << CI.name(SemaRef) << " : ";
         if (AP.isNull())
@@ -499,7 +506,7 @@ class Analyzer {
   // Verify a single Decl. Return the pending structure if that was the result,
   // else null. This method must not recurse.
   PendingFunctionAnalysis *verifyDecl(const Decl *D) {
-    CallableInfo CInfo(Sem, *D);
+    CallableInfo CInfo(*D);
     bool isExternC = false;
 
     if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
@@ -576,7 +583,7 @@ class Analyzer {
   // 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);
+    CallableInfo Caller(*D);
     LLVM_DEBUG(llvm::dbgs()
                    << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
                Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";);
@@ -585,7 +592,7 @@ class Analyzer {
       if (Call.Recursed)
         continue;
 
-      CallableInfo Callee(Sem, *Call.Callee);
+      CallableInfo Callee(*Call.Callee);
       followCall(Caller, *Pending, Callee, Call.CallLoc,
                  /*AssertNoFurtherInference=*/true);
     }
@@ -598,21 +605,20 @@ class Analyzer {
   void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
                   const CallableInfo &Callee, SourceLocation CallLoc,
                   bool AssertNoFurtherInference) {
-    const bool DirectCall = Callee.isDirectCall();
+    const bool DirectCall = Callee.isCalledDirectly();
 
     // Initially, the declared effects; inferred effects will be added.
     FunctionEffectKindSet CalleeEffects = Callee.Effects;
 
     bool IsInferencePossible = DirectCall;
 
-    if (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);
@@ -635,25 +641,23 @@ class Analyzer {
                llvm::dbgs() << "\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 Violation now.
-        if (!IsInferencePossible ||
-            !(Flags & FunctionEffect::FE_InferrableOnCallees)) {
-          if (Callee.FuncType == SpecialFuncType::None)
-            PFA.checkAddViolation(Inferring,
-                                  {Effect, ViolationID::CallsDeclWithoutEffect,
-                                   CallLoc, Callee.CDecl});
-          else
-            PFA.checkAddViolation(
-                Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
-        } else {
-          // Inference is allowed and necessary; defer it.
-          PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
-        }
+      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,
+                                 CallLoc, Callee.CDecl});
+        else
+          PFA.checkAddViolation(
+              Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
+      } else {
+        // Inference is allowed and necessary; defer it.
+        PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
       }
     };
 
@@ -664,7 +668,8 @@ class Analyzer {
       check1Effect(Effect, true);
   }
 
-  // Should only be called when determined to be complete.
+  // Should only be called when function's analysis is determined to be
+  // complete.
   void emitDiagnostics(SmallVector<Violation, 0> &Viols,
                        const CallableInfo &CInfo, Sema &S) {
     if (Viols.empty())
@@ -725,7 +730,7 @@ class Analyzer {
         break;
 
       case ViolationID::CallsDeclWithoutEffect: {
-        CallableInfo CalleeInfo(S, *Viol1.Callee);
+        CallableInfo CalleeInfo(*Viol1.Callee);
         std::string CalleeName = CalleeInfo.name(S);
 
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
@@ -743,10 +748,10 @@ class Analyzer {
             // - non-inline
             // - indirect (virtual or through function pointer)
             // - effect has been explicitly disclaimed (e.g. "blocking")
-            if (CalleeInfo.CType == CallType::Virtual)
+            if (CalleeInfo.CType == CallableType::Virtual)
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
                   << effectName;
-            else if (CalleeInfo.CType == CallType::Unknown)
+            else if (CalleeInfo.CType == CallableType::Unknown)
               S.Diag(Callee->getLocation(),
                      diag::note_func_effect_call_func_ptr)
                   << effectName;
@@ -799,7 +804,7 @@ class Analyzer {
             S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName;
             break;
           case ViolationID::CallsDeclWithoutEffect:
-            MaybeNextCallee.emplace(S, *Viol2.Callee);
+            MaybeNextCallee.emplace(*Viol2.Callee);
             S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
                 << effectName << MaybeNextCallee->name(S);
             break;
@@ -853,15 +858,15 @@ class Analyzer {
     // 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 D,
-                                   SourceLocation Loc,
+    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 (const FunctionEffect &Effect :
            CurrentFunction.DeclaredVerifiableEffects) {
         if (Effect.flags() & Flag) {
-          addViolation(/*inferring=*/false, Effect, D, Loc, Callee);
+          addViolation(/*inferring=*/false, Effect, VID, Loc, Callee);
           break;
         }
       }
@@ -869,7 +874,7 @@ class Analyzer {
       // violation, if we don't already have a violation for that effect.
       for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
         if (Effect.flags() & Flag)
-          addViolation(/*inferring=*/true, Effect, D, Loc, Callee);
+          addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
     }
 
     void addViolation(bool Inferring, const FunctionEffect &Effect,
@@ -892,8 +897,7 @@ class Analyzer {
                        /*AssertNoFurtherInference=*/false);
     }
 
-    void checkIndirectCall(CallExpr *Call, Expr *CalleeExpr) {
-      const QualType CalleeType = CalleeExpr->getType();
+    void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
       auto *FPT =
           CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
       FunctionEffectKindSet CalleeFX;
@@ -942,7 +946,7 @@ class Analyzer {
       if (Ty->isRecordType()) {
         if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
           if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
-            CallableInfo CI(Outer.Sem, *Dtor);
+            CallableInfo CI(*Dtor);
             followCall(CI, OuterDtor->getLocation());
           }
         }
@@ -999,7 +1003,7 @@ class Analyzer {
 
       Expr *CalleeExpr = Call->getCallee();
       if (const Decl *Callee = CalleeExpr->getReferencedDeclOfCallee()) {
-        CallableInfo CI(Outer.Sem, *Callee);
+        CallableInfo CI(*Callee);
         followCall(CI, Call->getBeginLoc());
         return true;
       }
@@ -1009,7 +1013,7 @@ class Analyzer {
         return true;
 
       // No Decl, just an Expr. Just check based on its type.
-      checkIndirectCall(Call, CalleeExpr);
+      checkIndirectCall(Call, CalleeExpr->getType());
 
       return true;
     }
@@ -1033,7 +1037,7 @@ class Analyzer {
           if (const auto *CxxRec =
                   dyn_cast<CXXRecordDecl>(ClsType->getDecl())) {
             if (const CXXDestructorDecl *Dtor = CxxRec->getDestructor()) {
-              CallableInfo CI(Outer.Sem, *Dtor);
+              CallableInfo CI(*Dtor);
               followCall(CI, Var->getLocation());
             }
           }
@@ -1046,7 +1050,7 @@ class Analyzer {
       // 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);
+        CallableInfo CI(*FD, SpecialFuncType::OperatorNew);
         followCall(CI, New->getBeginLoc());
       }
 
@@ -1062,7 +1066,7 @@ class Analyzer {
       // 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);
+        CallableInfo CI(*FD, SpecialFuncType::OperatorDelete);
         followCall(CI, Delete->getBeginLoc());
       }
 
@@ -1080,19 +1084,12 @@ class Analyzer {
       // BUG? It seems incorrect that RecursiveASTVisitor does not
       // visit the call to the constructor.
       const CXXConstructorDecl *Ctor = Construct->getConstructor();
-      CallableInfo CI(Outer.Sem, *Ctor);
+      CallableInfo CI(*Ctor);
       followCall(CI, Construct->getLocation());
 
       return true;
     }
 
-    bool VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DEI) {
-      if (Expr *E = DEI->getExpr())
-        TraverseStmt(E);
-
-      return true;
-    }
-
     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. Why not return

>From bffacc582a8c139feddfed26cc0b43dac488bc5c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 13 Aug 2024 13:32:45 -0700
Subject: [PATCH 16/48] Begin a list of unsafe builtin functions, starting with
 malloc and friends.

---
 clang/lib/Sema/EffectAnalysis.cpp             | 48 +++++++++++++++----
 .../Sema/attr-nonblocking-constraints.cpp     |  7 +++
 2 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index 799500aa8caff7..b59d0786c57dc1 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -132,6 +132,8 @@ struct CallableInfo {
     Effects = FunctionEffectKindSet(DeclEffects);
   }
 
+  CallableType type() const { return CType; }
+
   bool isCalledDirectly() const {
     return CType == CallableType::Function || CType == CallableType::Block;
   }
@@ -745,13 +747,15 @@ class Analyzer {
               DeclAnalysis.completedAnalysisForDecl(CalleeInfo.CDecl);
           if (Completed == nullptr) {
             // No result - could be
-            // - non-inline
+            // - non-inline and extern
             // - indirect (virtual or through function pointer)
             // - effect has been explicitly disclaimed (e.g. "blocking")
-            if (CalleeInfo.CType == CallableType::Virtual)
+
+            CallableType CType = CalleeInfo.type();
+            if (CType == CallableType::Virtual)
               S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
                   << effectName;
-            else if (CalleeInfo.CType == CallableType::Unknown)
+            else if (CType == CallableType::Unknown)
               S.Diag(Callee->getLocation(),
                      diag::note_func_effect_call_func_ptr)
                   << effectName;
@@ -760,10 +764,13 @@ class Analyzer {
                      diag::note_func_effect_call_disallows_inference)
                   << effectName
                   << FunctionEffect(Viol1.Effect.oppositeKind()).name();
-            else
+            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 =
@@ -887,16 +894,41 @@ class Analyzer {
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
     // other AST construct. CallableInfo pertains to the callee.
     void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
-      // 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)
+          FD && isSafeBuiltinFunction(FD))
         return;
 
       Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
                        /*AssertNoFurtherInference=*/false);
     }
 
+    // FIXME: This is currently specific to the `nonblocking` and
+    // `nonallocating` effects. More ideally, the builtin functions themselves
+    // would have the `allocating` attribute.
+    static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
+      unsigned BuiltinID = FD->getBuiltinID();
+      switch (BuiltinID) {
+      case 0: // not builtin
+        return false;
+      default: // not disallowed via cases below
+        return true;
+
+      // Disallow list
+      case Builtin::ID::BIaligned_alloc:
+      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::BIcalloc:
+      case Builtin::ID::BImalloc:
+      case Builtin::ID::BImemalign:
+      case Builtin::ID::BIrealloc:
+      case Builtin::ID::BIfree:
+        return false;
+      }
+      llvm_unreachable("above switch is exhaustive");
+    }
+
     void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
       auto *FPT =
           CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 869e56374fe2c3..540267d2efae4b 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -209,6 +209,13 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
 	ref();
 }
 
+// Builtin functions
+void nb18a() [[clang::nonblocking]] {
+	__builtin_assume(1);
+	void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}}
+	__builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}}
+}
+
 // 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}}

>From c718b5ab2de778ab4372615514f994ef43c85d34 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Wed, 14 Aug 2024 08:03:00 -0700
Subject: [PATCH 17/48] Apply suggestions from code review

Co-authored-by: Sirraide <aeternalmail at gmail.com>
---
 clang/lib/Sema/EffectAnalysis.cpp | 61 ++++++++++++-------------------
 1 file changed, 24 insertions(+), 37 deletions(-)

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index b59d0786c57dc1..ef5a8de628abcc 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -64,7 +64,7 @@ struct Violation {
 };
 
 enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
-enum class CallableType {
+enum class CallableType : uint8_t {
   // unknown: probably function pointer
   Unknown,
   Function,
@@ -296,12 +296,12 @@ class PendingFunctionAnalysis {
     return InferrableEffectToFirstViolation.lookup(effect);
   }
 
-  SmallVector<DirectCall, 0> &unverifiedCalls() {
+  ArrayRef<DirectCall> unverifiedCalls() {
     assert(!isComplete());
     return UnverifiedDirectCalls;
   }
 
-  SmallVector<Violation, 0> &getViolationsForExplicitFX() {
+  ArrayRef<Violation> getViolationsForExplicitFX() {
     return ViolationsForExplicitFX;
   }
 
@@ -386,7 +386,7 @@ class Analyzer {
   // Map all Decls analyzed to FuncAnalysisPtr. Pending state is larger
   // than complete state, so use different objects to represent them.
   // The state pointers are owned by the container.
-  class AnalysisMap : protected llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
+  class AnalysisMap : llvm::DenseMap<const Decl *, FuncAnalysisPtr> {
     using Base = llvm::DenseMap<const Decl *, FuncAnalysisPtr>;
 
   public:
@@ -460,19 +460,11 @@ class Analyzer {
     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 *>();
+        // All children have been traversed; finish analysis.
+        if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>())
           finishPendingAnalysis(D, Pending);
-          VerificationQueue.pop_back();
-          continue;
-        }
-        llvm_unreachable("unexpected DeclAnalysis item");
+        VerificationQueue.pop_back();
+        continue;
       }
 
       // Not previously visited; begin a new analysis for this Decl.
@@ -492,14 +484,12 @@ class Analyzer {
           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");
+
+        // 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;
       }
     }
   }
@@ -569,7 +559,7 @@ class Analyzer {
   // inserted in the container.
   void completeAnalysis(const CallableInfo &CInfo,
                         PendingFunctionAnalysis &Pending) {
-    if (SmallVector<Violation, 0> &Viols = Pending.getViolationsForExplicitFX();
+    if (ArrayRef<Violation> Viols = Pending.getViolationsForExplicitFX();
         !Viols.empty())
       emitDiagnostics(Viols, CInfo, Sem);
 
@@ -642,7 +632,7 @@ class Analyzer {
 
                llvm::dbgs() << "\n";);
 
-    auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+    auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
       if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
         return;
 
@@ -672,7 +662,7 @@ class Analyzer {
 
   // Should only be called when function's analysis is determined to be
   // complete.
-  void emitDiagnostics(SmallVector<Violation, 0> &Viols,
+  void emitDiagnostics(ArrayRef<Violation> Viols,
                        const CallableInfo &CInfo, Sema &S) {
     if (Viols.empty())
       return;
@@ -682,7 +672,7 @@ class Analyzer {
                 return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
               });
 
-    auto checkAddTemplateNote = [&](const Decl *D) {
+    auto CheckAddTemplateNote = [&](const Decl *D) {
       if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
         while (FD != nullptr && FD->isTemplateInstantiation()) {
           S.Diag(FD->getPointOfInstantiation(),
@@ -837,13 +827,13 @@ class Analyzer {
   //
   // Violations are always routed to a PendingFunctionAnalysis.
   struct FunctionBodyASTVisitor
-      : public RecursiveASTVisitor<FunctionBodyASTVisitor> {
+      : RecursiveASTVisitor<FunctionBodyASTVisitor> {
 
     Analyzer &Outer;
     PendingFunctionAnalysis &CurrentFunction;
     CallableInfo &CurrentCaller;
 
-    FunctionBodyASTVisitor(Analyzer &outer,
+    FunctionBodyASTVisitor(Analyzer &Outer,
                            PendingFunctionAnalysis &CurrentFunction,
                            CallableInfo &CurrentCaller)
         : Outer(outer), CurrentFunction(CurrentFunction),
@@ -869,7 +859,7 @@ class Analyzer {
                                    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..
+      // represented by the flag, store just one violation.
       for (const FunctionEffect &Effect :
            CurrentFunction.DeclaredVerifiableEffects) {
         if (Effect.flags() & Flag) {
@@ -926,7 +916,6 @@ class Analyzer {
       case Builtin::ID::BIfree:
         return false;
       }
-      llvm_unreachable("above switch is exhaustive");
     }
 
     void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
@@ -936,7 +925,7 @@ class Analyzer {
       if (FPT)
         CalleeFX.insert(FPT->getFunctionEffects());
 
-      auto check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+      auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
         if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
                                   /*direct=*/false, CalleeFX))
           addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
@@ -1142,10 +1131,8 @@ class Analyzer {
 
     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) {
+      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,

>From 8b225f43a7a7ac3fbdcdce7b0f078d4725310928 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 08:05:04 -0700
Subject: [PATCH 18/48] - FunctionEffect and FunctionEffectKindSet are tiny,
 pass by value wherever possible and obtain values from iterators. -
 Diagnostics: "local" -> "local variable" - No change needed to
 AnalysisBasedWarnings - Fixes following review suggestions.

---
 clang/include/clang/AST/Type.h                |  25 ++---
 .../clang/Basic/DiagnosticSemaKinds.td        |   4 +-
 clang/lib/AST/Type.cpp                        |   8 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |   2 -
 clang/lib/Sema/EffectAnalysis.cpp             | 104 +++++++++---------
 .../Sema/attr-nonblocking-constraints.cpp     |   4 +-
 6 files changed, 69 insertions(+), 78 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index cff10ffdc3a469..e7a0d79447dc4e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4766,22 +4766,22 @@ class FunctionEffect {
   /// function.
   std::optional<FunctionEffect>
   effectProhibitingInference(const Decl &Callee,
-                             const FunctionEffectKindSet &CalleeFX) const;
+                             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,
-                                  const FunctionEffectKindSet &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;
   }
 };
@@ -4810,8 +4810,7 @@ struct FunctionEffectWithCondition {
   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.
@@ -4972,22 +4971,20 @@ class FunctionEffectKindSet {
   iterator begin() const { return iterator(*this, 0); }
   iterator end() const { return iterator(*this, EndBitPos); }
 
-  void insert(const FunctionEffect &Effect) {
-    KindBits |= kindToBit(Effect.kind());
-  }
+  void insert(FunctionEffect Effect) { KindBits |= kindToBit(Effect.kind()); }
   void insert(FunctionEffectsRef FX) {
-    for (const FunctionEffect &Item : FX.effects())
+    for (FunctionEffect Item : FX.effects())
       insert(Item);
   }
-  void insert(const FunctionEffectKindSet &Set) { KindBits |= Set.KindBits; }
+  void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
 
   bool contains(const FunctionEffect::Kind EK) const {
     return (KindBits & kindToBit(EK)) != 0;
   }
   void dump(llvm::raw_ostream &OS) const;
 
-  static FunctionEffectKindSet difference(const FunctionEffectKindSet &LHS,
-                                          const FunctionEffectKindSet &RHS) {
+  static FunctionEffectKindSet difference(FunctionEffectKindSet LHS,
+                                          FunctionEffectKindSet RHS) {
     return FunctionEffectKindSet(LHS.KindBits & ~RHS.KindBits);
   }
 };
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ccec94c353d953..7e25afb98d95ff 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10952,10 +10952,10 @@ def warn_func_effect_throws_or_catches : Warning<
 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">,
+  "'%0' function must not have static local variables">,
   InGroup<FunctionEffects>;
 def note_func_effect_has_static_local : Note<
-  "function cannot be inferred '%0' because it has a static local">;
+  "function cannot be inferred '%0' because it has a static local variable">;
 def warn_func_effect_uses_thread_local : Warning<
   "'%0' function must not use thread-local variables">,
   InGroup<FunctionEffects>;
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 4fd4032725f1a9..8d36aa15c59572 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5138,11 +5138,11 @@ StringRef FunctionEffect::name() const {
 }
 
 std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
-    const Decl &Callee, const FunctionEffectKindSet &CalleeFX) const {
+    const Decl &Callee, FunctionEffectKindSet CalleeFX) const {
   switch (kind()) {
   case Kind::NonAllocating:
   case Kind::NonBlocking: {
-    for (const FunctionEffect &Effect : CalleeFX) {
+    for (FunctionEffect Effect : CalleeFX) {
       // nonblocking/nonallocating cannot call allocating.
       if (Effect.kind() == Kind::Allocating)
         return Effect;
@@ -5166,12 +5166,12 @@ std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
 }
 
 bool FunctionEffect::shouldDiagnoseFunctionCall(
-    bool Direct, const FunctionEffectKindSet &CalleeFX) const {
+    bool Direct, FunctionEffectKindSet CalleeFX) const {
   switch (kind()) {
   case Kind::NonAllocating:
   case Kind::NonBlocking: {
     const Kind CallerKind = kind();
-    for (const FunctionEffect &Effect : CalleeFX) {
+    for (FunctionEffect Effect : CalleeFX) {
       const Kind EK = Effect.kind();
       // Does callee have same or stronger constraint?
       if (EK == CallerKind ||
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 8268117a0addab..0f604c61fa3af9 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -2397,8 +2397,6 @@ class UnsafeBufferUsageReporter : public UnsafeBufferUsageHandler {
 };
 } // namespace
 
-// =============================================================================
-
 //===----------------------------------------------------------------------===//
 // AnalysisBasedWarnings - Worker object used by Sema to execute analysis-based
 //  warnings on a function, method, or block.
diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index ef5a8de628abcc..a074aece910c33 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -54,7 +54,7 @@ struct Violation {
 
   Violation() = default;
 
-  Violation(const FunctionEffect &Effect, ViolationID ID, SourceLocation Loc,
+  Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc,
             const Decl *Callee = nullptr,
             const FunctionEffect *CalleeEffect = nullptr)
       : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
@@ -204,7 +204,7 @@ class EffectToViolationMap {
   size_t size() const { return Impl ? Impl->size() : 0; }
 
 private:
-  ImplVec::iterator _find(const FunctionEffect &key) {
+  ImplVec::iterator _find(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)
@@ -250,14 +250,13 @@ class PendingFunctionAnalysis {
   SmallVector<DirectCall, 0> UnverifiedDirectCalls;
 
 public:
-  PendingFunctionAnalysis(
-      Sema &Sem, const CallableInfo &CInfo,
-      const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+  PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
+                          FunctionEffectKindSet AllInferrableEffectsToVerify)
       : DeclaredVerifiableEffects(CInfo.Effects) {
     // Check for effects we are not allowed to infer
     FunctionEffectKindSet InferrableFX;
 
-    for (const FunctionEffect &effect : AllInferrableEffectsToVerify) {
+    for (FunctionEffect effect : AllInferrableEffectsToVerify) {
       std::optional<FunctionEffect> ProblemCalleeEffect =
           effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
       if (!ProblemCalleeEffect)
@@ -296,12 +295,18 @@ class PendingFunctionAnalysis {
     return InferrableEffectToFirstViolation.lookup(effect);
   }
 
-  ArrayRef<DirectCall> unverifiedCalls() {
+  // Mutable because caller may need to set a DirectCall's Recursing flag.
+  MutableArrayRef<DirectCall> unverifiedCalls() {
     assert(!isComplete());
     return UnverifiedDirectCalls;
   }
 
-  ArrayRef<Violation> getViolationsForExplicitFX() {
+  ArrayRef<Violation> getSortedViolationsForExplicitFX(SourceManager &SM) {
+    if (!ViolationsForExplicitFX.empty())
+      std::sort(ViolationsForExplicitFX.begin(), ViolationsForExplicitFX.end(),
+                [&SM](const Violation &LHS, const Violation &RHS) {
+                  return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
+                });
     return ViolationsForExplicitFX;
   }
 
@@ -339,12 +344,11 @@ class CompleteFunctionAnalysis {
 
 public:
   // The incoming Pending analysis is consumed (member(s) are moved-from).
-  CompleteFunctionAnalysis(
-      ASTContext &Ctx, PendingFunctionAnalysis &Pending,
-      const FunctionEffectKindSet &DeclaredEffects,
-      const FunctionEffectKindSet &AllInferrableEffectsToVerify)
+  CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+                           FunctionEffectKindSet DeclaredEffects,
+                           FunctionEffectKindSet AllInferrableEffectsToVerify)
       : VerifiedEffects(DeclaredEffects) {
-    for (const FunctionEffect &effect : AllInferrableEffectsToVerify)
+    for (FunctionEffect effect : AllInferrableEffectsToVerify)
       if (Pending.violationForInferrableEffect(effect) == nullptr)
         VerifiedEffects.insert(effect);
 
@@ -352,7 +356,7 @@ class CompleteFunctionAnalysis {
         std::move(Pending.InferrableEffectToFirstViolation);
   }
 
-  const Violation *firstViolationForEffect(const FunctionEffect &Effect) {
+  const Violation *firstViolationForEffect(FunctionEffect Effect) {
     return InferrableEffectToFirstViolation.lookup(Effect);
   }
 
@@ -441,7 +445,7 @@ class Analyzer {
   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 FunctionEffect &Effect : Sem.AllEffectsToVerify) {
+    for (FunctionEffect Effect : Sem.AllEffectsToVerify) {
       const FunctionEffect::Flags Flags = Effect.flags();
       if (Flags & FunctionEffect::FE_InferrableOnCallees)
         AllInferrableEffectsToVerify.insert(Effect);
@@ -487,7 +491,7 @@ class Analyzer {
 
         // This indicates recursion (not necessarily direct). For the
         // purposes of effect analysis, we can just ignore it since
-        // no effects forbid recursion.        
+        // no effects forbid recursion.
         assert(isa<PendingFunctionAnalysis *>(AP));
         Call.Recursed = true;
       }
@@ -510,7 +514,7 @@ class Analyzer {
     // 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) {
+      for (FunctionEffect Effect : CInfo.Effects) {
         if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
           continue;
 
@@ -559,7 +563,8 @@ class Analyzer {
   // inserted in the container.
   void completeAnalysis(const CallableInfo &CInfo,
                         PendingFunctionAnalysis &Pending) {
-    if (ArrayRef<Violation> Viols = Pending.getViolationsForExplicitFX();
+    if (ArrayRef<Violation> Viols =
+            Pending.getSortedViolationsForExplicitFX(Sem.getSourceManager());
         !Viols.empty())
       emitDiagnostics(Viols, CInfo, Sem);
 
@@ -632,7 +637,7 @@ class Analyzer {
 
                llvm::dbgs() << "\n";);
 
-    auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+    auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
       if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
         return;
 
@@ -653,26 +658,21 @@ class Analyzer {
       }
     };
 
-    for (const FunctionEffect &Effect : PFA.DeclaredVerifiableEffects)
-      check1Effect(Effect, false);
+    for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects)
+      Check1Effect(Effect, false);
 
-    for (const FunctionEffect &Effect : PFA.FXToInfer)
-      check1Effect(Effect, true);
+    for (FunctionEffect Effect : PFA.FXToInfer)
+      Check1Effect(Effect, true);
   }
 
   // Should only be called when function's analysis is determined to be
   // complete.
-  void emitDiagnostics(ArrayRef<Violation> Viols,
-                       const CallableInfo &CInfo, Sema &S) {
+  void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo,
+                       Sema &S) {
     if (Viols.empty())
       return;
-    const SourceManager &SM = S.getSourceManager();
-    std::sort(Viols.begin(), Viols.end(),
-              [&SM](const Violation &LHS, const Violation &RHS) {
-                return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
-              });
 
-    auto CheckAddTemplateNote = [&](const Decl *D) {
+    auto MaybeAddTemplateNote = [&](const Decl *D) {
       if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
         while (FD != nullptr && FD->isTemplateInstantiation()) {
           S.Diag(FD->getPointOfInstantiation(),
@@ -693,32 +693,32 @@ class Analyzer {
         break;
       case ViolationID::AllocatesMemory:
         S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::Throws:
       case ViolationID::Catches:
         S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches)
             << effectName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::HasStaticLocal:
         S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local)
             << effectName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::AccessesThreadLocal:
         S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local)
             << effectName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::CallsObjC:
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::CallsExprWithoutEffect:
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
             << effectName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
         break;
 
       case ViolationID::CallsDeclWithoutEffect: {
@@ -727,7 +727,7 @@ class Analyzer {
 
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
             << effectName << CalleeName;
-        checkAddTemplateNote(CInfo.CDecl);
+        MaybeAddTemplateNote(CInfo.CDecl);
 
         // Emit notes explaining the transitive chain of inferences: Why isn't
         // the callee safe?
@@ -806,7 +806,7 @@ class Analyzer {
                 << effectName << MaybeNextCallee->name(S);
             break;
           }
-          checkAddTemplateNote(Callee);
+          MaybeAddTemplateNote(Callee);
           Callee = Viol2.Callee;
           if (MaybeNextCallee) {
             CalleeInfo = *MaybeNextCallee;
@@ -826,8 +826,7 @@ class Analyzer {
   //      being checked for implicit conformance.
   //
   // Violations are always routed to a PendingFunctionAnalysis.
-  struct FunctionBodyASTVisitor
-      : RecursiveASTVisitor<FunctionBodyASTVisitor> {
+  struct FunctionBodyASTVisitor : RecursiveASTVisitor<FunctionBodyASTVisitor> {
 
     Analyzer &Outer;
     PendingFunctionAnalysis &CurrentFunction;
@@ -836,7 +835,7 @@ class Analyzer {
     FunctionBodyASTVisitor(Analyzer &Outer,
                            PendingFunctionAnalysis &CurrentFunction,
                            CallableInfo &CurrentCaller)
-        : Outer(outer), CurrentFunction(CurrentFunction),
+        : Outer(Outer), CurrentFunction(CurrentFunction),
           CurrentCaller(CurrentCaller) {}
 
     // -- Entry point --
@@ -860,8 +859,7 @@ class Analyzer {
                                    const Decl *Callee = nullptr) {
       // If there are any declared verifiable effects which forbid the construct
       // represented by the flag, store just one violation.
-      for (const FunctionEffect &Effect :
-           CurrentFunction.DeclaredVerifiableEffects) {
+      for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects) {
         if (Effect.flags() & Flag) {
           addViolation(/*inferring=*/false, Effect, VID, Loc, Callee);
           break;
@@ -869,14 +867,13 @@ class Analyzer {
       }
       // For each inferred effect which forbids the construct, store a
       // violation, if we don't already have a violation for that effect.
-      for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
+      for (FunctionEffect Effect : CurrentFunction.FXToInfer)
         if (Effect.flags() & Flag)
           addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
     }
 
-    void addViolation(bool Inferring, const FunctionEffect &Effect,
-                      ViolationID D, SourceLocation Loc,
-                      const Decl *Callee = nullptr) {
+    void addViolation(bool Inferring, FunctionEffect Effect, ViolationID D,
+                      SourceLocation Loc, const Decl *Callee = nullptr) {
       CurrentFunction.checkAddViolation(Inferring,
                                         Violation(Effect, D, Loc, Callee));
     }
@@ -925,19 +922,18 @@ class Analyzer {
       if (FPT)
         CalleeFX.insert(FPT->getFunctionEffects());
 
-      auto Check1Effect = [&](const FunctionEffect &Effect, bool Inferring) {
+      auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
         if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
                                   /*direct=*/false, CalleeFX))
           addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
                        Call->getBeginLoc());
       };
 
-      for (const FunctionEffect &Effect :
-           CurrentFunction.DeclaredVerifiableEffects)
-        check1Effect(Effect, false);
+      for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects)
+        Check1Effect(Effect, false);
 
-      for (const FunctionEffect &Effect : CurrentFunction.FXToInfer)
-        check1Effect(Effect, true);
+      for (FunctionEffect Effect : CurrentFunction.FXToInfer)
+        Check1Effect(Effect, true);
     }
 
     // This destructor's body should be followed by the caller, but here we
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 540267d2efae4b..02247cb71975cf 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -15,7 +15,7 @@ void nb1() [[clang::nonblocking]]
 
 void nb2() [[clang::nonblocking]]
 {
-	static int global; // expected-warning {{'nonblocking' function must not have static locals}}
+	static int global; // expected-warning {{'nonblocking' function must not have static local variables}}
 }
 
 void nb3() [[clang::nonblocking]]
@@ -178,7 +178,7 @@ void nb11() [[clang::nonblocking]]
 
 // Verify that when attached to a redeclaration, the attribute successfully attaches.
 void nb12() {
-	static int x; // expected-warning {{'nonblocking' function must not have static locals}}
+	static int x; // expected-warning {{'nonblocking' function must not have static local variables}}
 }
 void nb12() [[clang::nonblocking]];
 void nb13() [[clang::nonblocking]] { nb12(); }

>From 2cb4539d5cf5abf7091dd9d6f735b039f86d3a9c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 08:59:53 -0700
Subject: [PATCH 19/48] - Comments begin with capital letters and end with full
 stops. - Simplify EffectToViolationMap. - FX -> Effects. - getCanonicalDecl()
 is a method of Decl, no need to wrap it for functions. - Sem -> S. - Clean up
 comments around RecursiveASTVisitor and implicit calls.

---
 clang/lib/Sema/EffectAnalysis.cpp | 196 +++++++++++++-----------------
 1 file changed, 85 insertions(+), 111 deletions(-)

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index a074aece910c33..dbc429f22c2367 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -17,14 +17,14 @@
 #include "clang/Basic/SourceManager.h"
 #include "clang/Sema/SemaInternal.h"
 
-#define DEBUG_TYPE "fxanalysis"
+#define DEBUG_TYPE "effectanalysis"
 
 using namespace clang;
 
 namespace {
 
 enum class ViolationID : uint8_t {
-  None = 0, // sentinel for an empty Violation
+  None = 0, // Sentinel for an empty Violation.
   Throws,
   Catches,
   CallsObjC,
@@ -32,7 +32,7 @@ enum class ViolationID : uint8_t {
   HasStaticLocal,
   AccessesThreadLocal,
 
-  // These only apply to callees, where the analysis stops at the Decl
+  // These only apply to callees, where the analysis stops at the Decl.
   DeclDisallowsInference,
 
   CallsDeclWithoutEffect,
@@ -47,10 +47,10 @@ enum class ViolationID : uint8_t {
 // be inferred as holding that effect.
 struct Violation {
   FunctionEffect Effect;
-  FunctionEffect CalleeEffectPreventingInference; // only for certain IDs
+  FunctionEffect CalleeEffectPreventingInference; // Only for certain IDs.
   ViolationID ID = ViolationID::None;
   SourceLocation Loc;
-  const Decl *Callee = nullptr; // only valid for Calls*
+  const Decl *Callee = nullptr; // Only valid for Calls*.
 
   Violation() = default;
 
@@ -65,7 +65,7 @@ struct Violation {
 
 enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
 enum class CallableType : uint8_t {
-  // unknown: probably function pointer
+  // Unknown: probably function pointer
   Unknown,
   Function,
   Virtual,
@@ -152,12 +152,12 @@ struct CallableInfo {
   }
 
   /// Generate a name for logging and diagnostics.
-  std::string name(Sema &Sem) const {
+  std::string name(Sema &S) const {
     std::string Name;
     llvm::raw_string_ostream OS(Name);
 
     if (auto *FD = dyn_cast<FunctionDecl>(CDecl))
-      FD->getNameForDiagnostic(OS, Sem.getPrintingPolicy(),
+      FD->getNameForDiagnostic(OS, S.getPrintingPolicy(),
                                /*Qualified=*/true);
     else if (auto *BD = dyn_cast<BlockDecl>(CDecl))
       OS << "(block " << BD->getBlockManglingNumber() << ")";
@@ -172,7 +172,7 @@ struct CallableInfo {
 // 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 sorted SmallVector with an inline capacity of 1. Since it
+  // 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.
   using ImplVec = llvm::SmallVector<Violation, 1>;
@@ -183,35 +183,23 @@ class EffectToViolationMap {
   void maybeInsert(const Violation &Viol) {
     if (Impl == nullptr)
       Impl = std::make_unique<ImplVec>();
-    auto *Iter = _find(Viol.Effect);
-    if (Iter != Impl->end() && Iter->Effect == Viol.Effect)
+    else if (lookup(Viol.Effect) != nullptr)
       return;
 
-    Impl->insert(Iter, Viol);
+    Impl->push_back(Viol);
   }
 
   const Violation *lookup(FunctionEffect Key) {
     if (Impl == nullptr)
       return nullptr;
 
-    auto *Iter = _find(Key);
-    if (Iter != Impl->end() && Iter->Effect == Key)
-      return &*Iter;
-
-    return nullptr;
+    auto *Iter =
+        std::find_if(Impl->begin(), Impl->end(),
+                     [&](const auto &Item) { return Item.Effect == Key; });
+    return Iter != Impl->end() ? &*Iter : nullptr;
   }
 
   size_t size() const { return Impl ? Impl->size() : 0; }
-
-private:
-  ImplVec::iterator _find(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;
-  }
 };
 
 // ----------
@@ -236,11 +224,11 @@ class PendingFunctionAnalysis {
   // 1. Effects declared explicitly by this function.
   // 2. All other inferrable effects needing verification.
   FunctionEffectKindSet DeclaredVerifiableEffects;
-  FunctionEffectKindSet FXToInfer;
+  FunctionEffectKindSet EffectsToInfer;
 
 private:
   // Violations pertaining to the function's explicit effects.
-  SmallVector<Violation, 0> ViolationsForExplicitFX;
+  SmallVector<Violation, 0> ViolationsForExplicitEffects;
 
   // Violations pertaining to other, non-explicit, inferrable effects.
   EffectToViolationMap InferrableEffectToFirstViolation;
@@ -250,17 +238,17 @@ class PendingFunctionAnalysis {
   SmallVector<DirectCall, 0> UnverifiedDirectCalls;
 
 public:
-  PendingFunctionAnalysis(Sema &Sem, const CallableInfo &CInfo,
+  PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo,
                           FunctionEffectKindSet AllInferrableEffectsToVerify)
       : DeclaredVerifiableEffects(CInfo.Effects) {
     // Check for effects we are not allowed to infer
-    FunctionEffectKindSet InferrableFX;
+    FunctionEffectKindSet InferrableEffects;
 
     for (FunctionEffect effect : AllInferrableEffectsToVerify) {
       std::optional<FunctionEffect> ProblemCalleeEffect =
           effect.effectProhibitingInference(*CInfo.CDecl, CInfo.Effects);
       if (!ProblemCalleeEffect)
-        InferrableFX.insert(effect);
+        InferrableEffects.insert(effect);
       else {
         // Add a Violation for this effect if a caller were to
         // try to infer it.
@@ -269,17 +257,17 @@ class PendingFunctionAnalysis {
             CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
       }
     }
-    // InferrableFX is now the set of inferrable effects which are not
+    // InferrableEffects is now the set of inferrable effects which are not
     // prohibited
-    FXToInfer = FunctionEffectKindSet::difference(InferrableFX,
-                                                  DeclaredVerifiableEffects);
+    EffectsToInfer = FunctionEffectKindSet::difference(
+        InferrableEffects, DeclaredVerifiableEffects);
   }
 
   // Hide the way that Violations for explicitly required effects vs. inferred
   // ones are handled differently.
   void checkAddViolation(bool Inferring, const Violation &NewViol) {
     if (!Inferring)
-      ViolationsForExplicitFX.push_back(NewViol);
+      ViolationsForExplicitEffects.push_back(NewViol);
     else
       InferrableEffectToFirstViolation.maybeInsert(NewViol);
   }
@@ -301,21 +289,22 @@ class PendingFunctionAnalysis {
     return UnverifiedDirectCalls;
   }
 
-  ArrayRef<Violation> getSortedViolationsForExplicitFX(SourceManager &SM) {
-    if (!ViolationsForExplicitFX.empty())
-      std::sort(ViolationsForExplicitFX.begin(), ViolationsForExplicitFX.end(),
+  ArrayRef<Violation> getSortedViolationsForExplicitEffects(SourceManager &SM) {
+    if (!ViolationsForExplicitEffects.empty())
+      std::sort(ViolationsForExplicitEffects.begin(),
+                ViolationsForExplicitEffects.end(),
                 [&SM](const Violation &LHS, const Violation &RHS) {
                   return SM.isBeforeInTranslationUnit(LHS.Loc, RHS.Loc);
                 });
-    return ViolationsForExplicitFX;
+    return ViolationsForExplicitEffects;
   }
 
   void dump(Sema &SemaRef, llvm::raw_ostream &OS) const {
     OS << "Pending: Declared ";
     DeclaredVerifiableEffects.dump(OS);
-    OS << ", " << ViolationsForExplicitFX.size() << " violations; ";
+    OS << ", " << ViolationsForExplicitEffects.size() << " violations; ";
     OS << " Infer ";
-    FXToInfer.dump(OS);
+    EffectsToInfer.dump(OS);
     OS << ", " << InferrableEffectToFirstViolation.size() << " violations";
     if (!UnverifiedDirectCalls.empty()) {
       OS << "; Calls: ";
@@ -368,18 +357,9 @@ class CompleteFunctionAnalysis {
   }
 };
 
-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 {
-  Sema &Sem;
+  Sema &S;
 
   // Subset of Sema.AllEffectsToVerify
   FunctionEffectKindSet AllInferrableEffectsToVerify;
@@ -400,11 +380,11 @@ class Analyzer {
     // that lookups and insertions are via the canonical Decls.
 
     FuncAnalysisPtr lookup(const Decl *Key) const {
-      return Base::lookup(CanonicalFunctionDecl(Key));
+      return Base::lookup(Key->getCanonicalDecl());
     }
 
     FuncAnalysisPtr &operator[](const Decl *Key) {
-      return Base::operator[](CanonicalFunctionDecl(Key));
+      return Base::operator[](Key->getCanonicalDecl());
     }
 
     /// Shortcut for the case where we only care about completed analysis.
@@ -440,12 +420,12 @@ class Analyzer {
   AnalysisMap DeclAnalysis;
 
 public:
-  Analyzer(Sema &S) : Sem(S) {}
+  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 : Sem.AllEffectsToVerify) {
+    for (FunctionEffect Effect : S.AllEffectsToVerify) {
       const FunctionEffect::Flags Flags = Effect.flags();
       if (Flags & FunctionEffect::FE_InferrableOnCallees)
         AllInferrableEffectsToVerify.insert(Effect);
@@ -458,15 +438,16 @@ class Analyzer {
     // 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;
+    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)) {
-        // All children have been traversed; finish analysis.
-        if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>())
+        if (auto *Pending = AP.dyn_cast<PendingFunctionAnalysis *>()) {
+          // All children have been traversed; finish analysis.
           finishPendingAnalysis(D, Pending);
+        }
         VerificationQueue.pop_back();
         continue;
       }
@@ -474,7 +455,7 @@ class Analyzer {
       // Not previously visited; begin a new analysis for this Decl.
       PendingFunctionAnalysis *Pending = verifyDecl(D);
       if (Pending == nullptr) {
-        // completed now
+        // Completed now.
         VerificationQueue.pop_back();
         continue;
       }
@@ -505,15 +486,13 @@ class Analyzer {
     CallableInfo CInfo(*D);
     bool isExternC = false;
 
-    if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D)) {
-      assert(FD->getBuiltinID() == 0);
+    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 (Sem.getLangOpts().CPlusPlus && !isExternC) {
+    if (S.getLangOpts().CPlusPlus && !isExternC) {
       for (FunctionEffect Effect : CInfo.Effects) {
         if (!(Effect.flags() & FunctionEffect::FE_ExcludeThrow))
           continue;
@@ -528,8 +507,7 @@ class Analyzer {
           }
         }
         if (!IsNoexcept)
-          Sem.Diag(D->getBeginLoc(),
-                   diag::warn_perf_constraint_implies_noexcept)
+          S.Diag(D->getBeginLoc(), diag::warn_perf_constraint_implies_noexcept)
               << Effect.name();
         break;
       }
@@ -538,10 +516,10 @@ class Analyzer {
     // Build a PendingFunctionAnalysis on the stack. If it turns out to be
     // complete, we'll have avoided a heap allocation; if it's incomplete, it's
     // a fairly trivial move to a heap-allocated object.
-    PendingFunctionAnalysis FAnalysis(Sem, CInfo, AllInferrableEffectsToVerify);
+    PendingFunctionAnalysis FAnalysis(S, CInfo, AllInferrableEffectsToVerify);
 
-    LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(Sem) << " ";
-               FAnalysis.dump(Sem, llvm::dbgs()););
+    LLVM_DEBUG(llvm::dbgs() << "\nVerifying " << CInfo.name(S) << " ";
+               FAnalysis.dump(S, llvm::dbgs()););
 
     FunctionBodyASTVisitor Visitor(*this, FAnalysis, CInfo);
 
@@ -555,7 +533,7 @@ class Analyzer {
         new PendingFunctionAnalysis(std::move(FAnalysis));
     DeclAnalysis[D] = PendingPtr;
     LLVM_DEBUG(llvm::dbgs() << "inserted pending " << PendingPtr << "\n";
-               DeclAnalysis.dump(Sem, llvm::dbgs()););
+               DeclAnalysis.dump(S, llvm::dbgs()););
     return PendingPtr;
   }
 
@@ -564,16 +542,16 @@ class Analyzer {
   void completeAnalysis(const CallableInfo &CInfo,
                         PendingFunctionAnalysis &Pending) {
     if (ArrayRef<Violation> Viols =
-            Pending.getSortedViolationsForExplicitFX(Sem.getSourceManager());
+            Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
         !Viols.empty())
-      emitDiagnostics(Viols, CInfo, Sem);
+      emitDiagnostics(Viols, CInfo, S);
 
-    CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
-        Sem.getASTContext(), Pending, CInfo.Effects,
-        AllInferrableEffectsToVerify);
+    CompleteFunctionAnalysis *CompletePtr =
+        new CompleteFunctionAnalysis(S.getASTContext(), Pending, CInfo.Effects,
+                                     AllInferrableEffectsToVerify);
     DeclAnalysis[CInfo.CDecl] = CompletePtr;
     LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
-               DeclAnalysis.dump(Sem, llvm::dbgs()););
+               DeclAnalysis.dump(S, llvm::dbgs()););
   }
 
   // Called after all direct calls requiring inference have been found -- or
@@ -582,8 +560,8 @@ class Analyzer {
   void finishPendingAnalysis(const Decl *D, PendingFunctionAnalysis *Pending) {
     CallableInfo Caller(*D);
     LLVM_DEBUG(llvm::dbgs()
-                   << "finishPendingAnalysis for " << Caller.name(Sem) << " : ";
-               Pending->dump(Sem, llvm::dbgs()); llvm::dbgs() << "\n";);
+                   << "finishPendingAnalysis for " << Caller.name(S) << " : ";
+               Pending->dump(S, llvm::dbgs()); llvm::dbgs() << "\n";);
     for (const PendingFunctionAnalysis::DirectCall &Call :
          Pending->unverifiedCalls()) {
       if (Call.Recursed)
@@ -614,7 +592,7 @@ class Analyzer {
               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
+        IsInferencePossible = false; // We've already traversed it.
       }
 
     if (AssertNoFurtherInference) {
@@ -624,16 +602,13 @@ class Analyzer {
     if (!Callee.isVerifiable())
       IsInferencePossible = false;
 
-    LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(Sem) << " to "
-                            << Callee.name(Sem) << "; verifiable: "
+    LLVM_DEBUG(llvm::dbgs() << "followCall from " << Caller.name(S) << " to "
+                            << Callee.name(S) << "; verifiable: "
                             << Callee.isVerifiable() << "; callee ";
                CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
-               llvm::dbgs()
-               << "  callee " << Callee.CDecl << " canonical "
-               << CanonicalFunctionDecl(Callee.CDecl) << " redecls";
-               for (Decl *D
-                    : Callee.CDecl->redecls()) llvm::dbgs()
-               << " " << D;
+               llvm::dbgs() << "  callee " << Callee.CDecl << " canonical "
+                            << Callee.CDecl->getCanonicalDecl() << " redecls";
+               for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
 
                llvm::dbgs() << "\n";);
 
@@ -661,7 +636,7 @@ class Analyzer {
     for (FunctionEffect Effect : PFA.DeclaredVerifiableEffects)
       Check1Effect(Effect, false);
 
-    for (FunctionEffect Effect : PFA.FXToInfer)
+    for (FunctionEffect Effect : PFA.EffectsToInfer)
       Check1Effect(Effect, true);
   }
 
@@ -687,8 +662,8 @@ class Analyzer {
       StringRef effectName = Viol1.Effect.name();
       switch (Viol1.ID) {
       case ViolationID::None:
-      case ViolationID::DeclDisallowsInference: // shouldn't happen
-                                                // here
+      case ViolationID::DeclDisallowsInference: // Shouldn't happen
+                                                // here.
         llvm_unreachable("Unexpected violation kind");
         break;
       case ViolationID::AllocatesMemory:
@@ -867,7 +842,7 @@ class Analyzer {
       }
       // 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.FXToInfer)
+      for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
         if (Effect.flags() & Flag)
           addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
     }
@@ -895,9 +870,9 @@ class Analyzer {
     static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
       unsigned BuiltinID = FD->getBuiltinID();
       switch (BuiltinID) {
-      case 0: // not builtin
+      case 0: // Not builtin.
         return false;
-      default: // not disallowed via cases below
+      default: // Not disallowed via cases below.
         return true;
 
       // Disallow list
@@ -917,14 +892,14 @@ class Analyzer {
 
     void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
       auto *FPT =
-          CalleeType->getAs<FunctionProtoType>(); // null if FunctionType
-      FunctionEffectKindSet CalleeFX;
+          CalleeType->getAs<FunctionProtoType>(); // Null if FunctionType.
+      FunctionEffectKindSet CalleeEffects;
       if (FPT)
-        CalleeFX.insert(FPT->getFunctionEffects());
+        CalleeEffects.insert(FPT->getFunctionEffects());
 
       auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
         if (FPT == nullptr || Effect.shouldDiagnoseFunctionCall(
-                                  /*direct=*/false, CalleeFX))
+                                  /*direct=*/false, CalleeEffects))
           addViolation(Inferring, Effect, ViolationID::CallsExprWithoutEffect,
                        Call->getBeginLoc());
       };
@@ -932,7 +907,7 @@ class Analyzer {
       for (FunctionEffect Effect : CurrentFunction.DeclaredVerifiableEffects)
         Check1Effect(Effect, false);
 
-      for (FunctionEffect Effect : CurrentFunction.FXToInfer)
+      for (FunctionEffect Effect : CurrentFunction.EffectsToInfer)
         Check1Effect(Effect, true);
     }
 
@@ -1015,7 +990,7 @@ class Analyzer {
     bool VisitCallExpr(CallExpr *Call) {
       LLVM_DEBUG(llvm::dbgs()
                      << "VisitCallExpr : "
-                     << Call->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+                     << Call->getBeginLoc().printToString(Outer.S.SourceMgr)
                      << "\n";);
 
       Expr *CalleeExpr = Call->getCallee();
@@ -1025,9 +1000,10 @@ class Analyzer {
         return true;
       }
 
-      if (isa<CXXPseudoDestructorExpr>(CalleeExpr))
-        // just destroying a scalar, fine.
+      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());
@@ -1038,7 +1014,7 @@ class Analyzer {
     bool VisitVarDecl(VarDecl *Var) {
       LLVM_DEBUG(llvm::dbgs()
                      << "VisitVarDecl : "
-                     << Var->getBeginLoc().printToString(Outer.Sem.SourceMgr)
+                     << Var->getBeginLoc().printToString(Outer.S.SourceMgr)
                      << "\n";);
 
       if (Var->isStaticLocal())
@@ -1047,7 +1023,7 @@ class Analyzer {
                                   Var->getLocation());
 
       const QualType::DestructionKind DK =
-          Var->needsDestruction(Outer.Sem.getASTContext());
+          Var->needsDestruction(Outer.S.getASTContext());
       if (DK == QualType::DK_cxx_destructor) {
         QualType QT = Var->getType();
         if (const auto *ClsType = QT.getTypePtr()->getAs<RecordType>()) {
@@ -1064,8 +1040,7 @@ class Analyzer {
     }
 
     bool VisitCXXNewExpr(CXXNewExpr *New) {
-      // BUG? It seems incorrect that RecursiveASTVisitor does not
-      // visit the call to operator 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());
@@ -1080,8 +1055,8 @@ class Analyzer {
     }
 
     bool VisitCXXDeleteExpr(CXXDeleteExpr *Delete) {
-      // BUG? It seems incorrect that RecursiveASTVisitor does not
-      // visit the call to operator 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());
@@ -1095,11 +1070,11 @@ class Analyzer {
     bool VisitCXXConstructExpr(CXXConstructExpr *Construct) {
       LLVM_DEBUG(llvm::dbgs() << "VisitCXXConstructExpr : "
                               << Construct->getBeginLoc().printToString(
-                                     Outer.Sem.SourceMgr)
+                                     Outer.S.SourceMgr)
                               << "\n";);
 
-      // BUG? It seems incorrect that RecursiveASTVisitor does not
-      // visit the call to the constructor.
+      // RecursiveASTVisitor does not visit the implicit call to the
+      // constructor.
       const CXXConstructorDecl *Ctor = Construct->getConstructor();
       CallableInfo CI(*Ctor);
       followCall(CI, Construct->getLocation());
@@ -1172,7 +1147,6 @@ namespace clang {
 
 void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
   if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
-    // exit if having uncompilable errors or ignoring all warnings:
     return;
   if (TU == nullptr)
     return;

>From 0e07315aa4b880762f469bed476f55a507a1b793 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 09:55:10 -0700
Subject: [PATCH 20/48] Implement FunctionEffectKindSet with std::bitset.

---
 clang/include/clang/AST/Type.h | 26 ++++++++++++++------------
 1 file changed, 14 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index e7a0d79447dc4e..5579db711b59e7 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -48,6 +48,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>
@@ -4922,43 +4923,44 @@ class FunctionEffectsRef {
 /// A mutable set of FunctionEffect::Kind.
 class FunctionEffectKindSet {
   // For now this only needs to be a bitmap.
-  using KindBitsT = uint8_t;
   constexpr static size_t EndBitPos = 8;
+  using KindBitsT = std::bitset<EndBitPos>;
 
-  KindBitsT KindBits = 0;
-
-  static KindBitsT kindToBit(FunctionEffect::Kind K) {
-    return 1u << KindBitsT(K);
-  }
+  KindBitsT KindBits{};
 
   explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
 
+  constexpr static size_t kindToPos(FunctionEffect::Kind K) {
+    return size_t(K);
+  }
+
 public:
   FunctionEffectKindSet() = default;
   explicit FunctionEffectKindSet(FunctionEffectsRef FX) { insert(FX); }
 
+  // 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 advanceIdx() {
-      while (Idx < EndBitPos && !(Outer->KindBits & (1u << Idx)))
+    void advanceToNextSetBit() {
+      while (Idx < EndBitPos && !Outer->KindBits.test(Idx))
         ++Idx;
     }
 
   public:
     iterator();
     iterator(const FunctionEffectKindSet &O, size_t I) : Outer(&O), Idx(I) {
-      advanceIdx();
+      advanceToNextSetBit();
     }
     bool operator==(const iterator &Other) const { return Idx == Other.Idx; }
     bool operator!=(const iterator &Other) const { return Idx != Other.Idx; }
 
     iterator operator++() {
       ++Idx;
-      advanceIdx();
+      advanceToNextSetBit();
       return *this;
     }
 
@@ -4971,7 +4973,7 @@ class FunctionEffectKindSet {
   iterator begin() const { return iterator(*this, 0); }
   iterator end() const { return iterator(*this, EndBitPos); }
 
-  void insert(FunctionEffect Effect) { KindBits |= kindToBit(Effect.kind()); }
+  void insert(FunctionEffect Effect) { KindBits.set(kindToPos(Effect.kind())); }
   void insert(FunctionEffectsRef FX) {
     for (FunctionEffect Item : FX.effects())
       insert(Item);
@@ -4979,7 +4981,7 @@ class FunctionEffectKindSet {
   void insert(FunctionEffectKindSet Set) { KindBits |= Set.KindBits; }
 
   bool contains(const FunctionEffect::Kind EK) const {
-    return (KindBits & kindToBit(EK)) != 0;
+    return KindBits.test(kindToPos(EK));
   }
   void dump(llvm::raw_ostream &OS) const;
 

>From fddd9d2775e7ee5fa88c9a12888872cafc411ead Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 14 Aug 2024 14:26:02 -0700
Subject: [PATCH 21/48] - Diagnose __builtin_operator_new and delete - More
 test cases involving function try-block and member initializers. - remove
 construct which is blowing clang-format's mind

---
 clang/lib/Sema/EffectAnalysis.cpp             |  7 ++-
 .../Sema/attr-nonblocking-constraints.cpp     | 46 ++++++++++++++++++-
 2 files changed, 47 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Sema/EffectAnalysis.cpp b/clang/lib/Sema/EffectAnalysis.cpp
index dbc429f22c2367..c0f679d9ece206 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/EffectAnalysis.cpp
@@ -607,10 +607,7 @@ class Analyzer {
                             << Callee.isVerifiable() << "; callee ";
                CalleeEffects.dump(llvm::dbgs()); llvm::dbgs() << "\n";
                llvm::dbgs() << "  callee " << Callee.CDecl << " canonical "
-                            << Callee.CDecl->getCanonicalDecl() << " redecls";
-               for (Decl *D : Callee.CDecl->redecls()) llvm::dbgs() << " " << D;
-
-               llvm::dbgs() << "\n";);
+                            << Callee.CDecl->getCanonicalDecl() << "\n";);
 
     auto Check1Effect = [&](FunctionEffect Effect, bool Inferring) {
       if (!Effect.shouldDiagnoseFunctionCall(DirectCall, CalleeEffects))
@@ -881,6 +878,8 @@ class Analyzer {
       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::BIcalloc:
       case Builtin::ID::BImalloc:
       case Builtin::ID::BImemalign:
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 02247cb71975cf..3515f7f4b7d6cc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -210,12 +210,54 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
 }
 
 // Builtin functions
-void nb18a() [[clang::nonblocking]] {
+void nb19() [[clang::nonblocking]] {
 	__builtin_assume(1);
 	void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}}
 	__builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}}
+	
+	void *p2 = __builtin_operator_new(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_new'}}
+	__builtin_operator_delete(p2); // expected-warning {{'nonblocking' function 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 {{'nonblocking' function must not call non-'nonblocking' function 'catches'}}
 }
 
+struct S {
+    int x;
+    S(int x) try : x(x) {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+    S(double) : x((throw 3, 3)) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+};
+
+int badi(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+
+struct A {
+    int x = (throw 3, 3); // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+};
+
+struct B {
+    int y = badi(); // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
+};
+
+void f() [[clang::nonblocking]] {
+    S s1(3);   // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
+    S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
+    A a;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'A::A'}}
+    B b;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'B::B'}}
+}
+
+#if 0
+// FIXME: can we do better with default member initializers?
+struct T {
+	int x = badi();
+	T() [[clang::nonblocking]] {} // Warning: this calls bad().
+	T(int x) [[clang::nonblocking]] : x(x) {} // This does not.
+};
+#endif
+
 // 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}}
@@ -251,6 +293,6 @@ void g() [[clang::nonblocking]] {
 // --- nonblocking implies noexcept ---
 #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
 
-void nb19() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+void needs_noexcept() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
 {
 }

>From eb536aba9985c4087d3cd68bd75f4013a8530ff6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 15 Aug 2024 11:01:13 -0700
Subject: [PATCH 22/48] EffectAnalysis.cpp => SemaFunctionEffects.cpp (minimal
 first step; more reorg to follow)

---
 clang/include/clang/Sema/Sema.h                                | 2 +-
 clang/lib/Sema/CMakeLists.txt                                  | 2 +-
 clang/lib/Sema/{EffectAnalysis.cpp => SemaFunctionEffects.cpp} | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)
 rename clang/lib/Sema/{EffectAnalysis.cpp => SemaFunctionEffects.cpp} (99%)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c17cc9ce8bc443..f23d1aa9c95dfe 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,7 +530,7 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
                             const FunctionEffectsRef &New);
 };
 
-// Defined in EffectAnalysis.cpp. TODO: Maybe make this a method of Sema and
+// Defined in SemaFunctionEffects.cpp. TODO: Maybe make this a method of Sema and
 // move more of the effects implementation into that file?
 void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
 
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index ea827323395d74..719c3a9312ec15 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -19,7 +19,6 @@ add_clang_library(clangSema
   CodeCompleteConsumer.cpp
   DeclSpec.cpp
   DelayedDiagnostic.cpp
-  EffectAnalysis.cpp
   HLSLExternalSemaSource.cpp
   IdentifierResolver.cpp
   JumpDiagnostics.cpp
@@ -56,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/EffectAnalysis.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
similarity index 99%
rename from clang/lib/Sema/EffectAnalysis.cpp
rename to clang/lib/Sema/SemaFunctionEffects.cpp
index c0f679d9ece206..2144cbf3a72bc6 100644
--- a/clang/lib/Sema/EffectAnalysis.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1,4 +1,4 @@
-//=== EffectAnalysis.cpp - Sema warnings for function effects -------------===//
+//=== SemaFunctionEffects.cpp - Sema warnings for 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.

>From 15b399fd8b6a8bb9f59ac3367467bb84e20efd0c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 15 Aug 2024 11:28:59 -0700
Subject: [PATCH 23/48] Move most Sema methods involving effects into
 SemaFunctionEffects.cpp. Two remain elsewhere: -
 diagnoseFunctionEffectConversion (still in Sema.cpp next to
 diagnoseNullableToNonnullConversion) - ActOnEffectExpression (still in
 SemaType.cpp near handleNonBlockingNonAllocatingTypeAttr)

---
 clang/include/clang/Sema/Sema.h        |  90 +++++++++++---------
 clang/lib/Sema/Sema.cpp                |   2 +-
 clang/lib/Sema/SemaDecl.cpp            |  99 ----------------------
 clang/lib/Sema/SemaFunctionEffects.cpp | 109 +++++++++++++++++++++++--
 4 files changed, 156 insertions(+), 144 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index f23d1aa9c95dfe..c46b2f6e7e5fa5 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -530,10 +530,6 @@ struct FunctionEffectDifferences : public SmallVector<FunctionEffectDiff> {
                             const FunctionEffectsRef &New);
 };
 
-// Defined in SemaFunctionEffects.cpp. TODO: Maybe make this a method of Sema and
-// move more of the effects implementation into that file?
-void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU);
-
 /// Sema - This implements semantic analysis and AST building for C.
 /// \nosubgrouping
 class Sema final : public SemaBase {
@@ -877,13 +873,6 @@ class Sema final : public SemaBase {
   /// Warn when implicitly casting 0 to nullptr.
   void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
 
-  /// 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;
-
   /// Warn when implicitly changing function effects.
   void diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                         SourceLocation Loc);
@@ -4324,34 +4313,6 @@ 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);
-
-  /// 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);
-
 private:
   /// Function or variable declarations to be checked for whether the deferred
   /// diagnostics should be emitted.
@@ -15119,6 +15080,57 @@ class Sema final : public SemaBase {
                                  bool OrNull);
 
   ///@}
+
+  //
+  //
+  // -------------------------------------------------------------------------
+  //
+  //
+
+  /// \name Function Effects
+  /// Implementations are in SemaFunctionEffects.cpp
+  ///@{
+public:
+  /// 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/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 7afc2b5b35d4ba..12f0a0e7bfd753 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1529,7 +1529,7 @@ void Sema::ActOnEndOfTranslationUnit() {
   AnalysisWarnings.IssueWarnings(Context.getTranslationUnitDecl());
 
   if (Context.hasAnyFunctionEffects())
-    performEffectAnalysis(*this, Context.getTranslationUnitDecl());
+    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
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 8b464ab29498fb..554b35527b2201 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -10919,49 +10919,6 @@ 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) {
-  // 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);
-}
-
 bool Sema::canFullyTypeCheckRedeclaration(ValueDecl *NewD, ValueDecl *OldD,
                                           QualType NewT, QualType OldT) {
   if (!NewD->getLexicalDeclContext()->isDependentContext())
@@ -20331,59 +20288,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/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 2144cbf3a72bc6..7624a072d2fca8 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1,4 +1,4 @@
-//=== SemaFunctionEffects.cpp - Sema warnings for function effects --------===//
+//=== 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.
@@ -6,7 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// This file implements caller/callee analysis for function effects.
+// This file implements Sema handling of function effects.
 //
 //===----------------------------------------------------------------------===//
 
@@ -1144,12 +1144,111 @@ Analyzer::AnalysisMap::~AnalysisMap() {
 
 namespace clang {
 
-void performEffectAnalysis(Sema &S, TranslationUnitDecl *TU) {
-  if (S.hasUncompilableErrorOccurred() || S.Diags.getIgnoreAllWarnings())
+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);
+  }
+}
+
+// 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) {
+  // 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{S}.run(*TU);
+  Analyzer{*this}.run(*TU);
 }
 
 } // namespace clang

>From dfebc1aa962a524e8767d8ca77b73c7b3840a069 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 07:34:07 -0700
Subject: [PATCH 24/48] Move FunctionEffectDiff/Differences into
 SemaFunctionEffects.cpp.

---
 clang/include/clang/Sema/Sema.h        |  99 ++++++++--------
 clang/lib/Sema/Sema.cpp                | 150 -------------------------
 clang/lib/Sema/SemaFunctionEffects.cpp | 150 +++++++++++++++++++++++++
 3 files changed, 200 insertions(+), 199 deletions(-)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c46b2f6e7e5fa5..43f4d72a8fbefc 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -481,55 +481,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 differing 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 {
@@ -568,6 +519,7 @@ class Sema final : public SemaBase {
   // 30. Constraints and Concepts (SemaConcept.cpp)
   // 31. Types (SemaType.cpp)
   // 32. FixIt Helpers (SemaFixItUtils.cpp)
+  // 33. Function Effects (SemaFunctionEffects.cpp)
 
   /// \name Semantic Analysis
   /// Implementations are in Sema.cpp
@@ -15091,6 +15043,55 @@ class Sema final : public SemaBase {
   /// Implementations are in SemaFunctionEffects.cpp
   ///@{
 public:
+  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 differing 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);
+  };
+
   /// All functions/lambdas/blocks which have bodies and which have a non-empty
   /// FunctionEffectsRef to be verified.
   SmallVector<const Decl *> DeclsWithEffectsToVerify;
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 12f0a0e7bfd753..5c7460d480db07 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2783,153 +2783,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;
-    }
-  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;
-    }
-  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;
-    }
-
-  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/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 7624a072d2fca8..526c0bb8d114e6 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1251,4 +1251,154 @@ void Sema::performFunctionEffectAnalysis(TranslationUnitDecl *TU) {
   Analyzer{*this}.run(*TU);
 }
 
+Sema::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 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;
+  case FunctionEffect::Kind::None:
+    break;
+  }
+  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;
+  case FunctionEffect::Kind::None:
+    break;
+  }
+  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;
+
+  case FunctionEffect::Kind::None:
+    break;
+  }
+  llvm_unreachable("unknown effect kind");
+}
+
 } // namespace clang

>From 82cb07d0dbd6a0a6bc21bd6d6406c05e05507d09 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 08:09:45 -0700
Subject: [PATCH 25/48] Combine the diagnostics for 5 violations into one using
 %select{}

---
 .../clang/Basic/DiagnosticSemaKinds.td        | 35 ++------
 clang/lib/Sema/SemaFunctionEffects.cpp        | 89 ++++++++-----------
 .../Sema/attr-nonblocking-constraints.cpp     |  2 +-
 3 files changed, 47 insertions(+), 79 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index be8aa4edb02c1b..b231207690f6c8 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10945,39 +10945,22 @@ 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">,
+def warn_func_effect_violation : Warning<
+  "'%0' function must not %select{allocate or deallocate memory|throw or catch exceptions|"
+  "have static local variables|use thread-local variables|access ObjC methods or properties}1">,
   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 local variables">,
-  InGroup<FunctionEffects>;
-def note_func_effect_has_static_local : Note<
-  "function cannot be inferred '%0' because it has a static local variable">;
-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 note_func_effect_violation : Note<
+  "function cannot be inferred '%0' 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}1">;
 def warn_func_effect_calls_func_without_effect : Warning<
   "'%0' function must not call non-'%0' function '%1'">,
   InGroup<FunctionEffects>;
+def note_func_effect_calls_func_without_effect : Note<
+  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
 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<
   "declaration cannot be inferred '%0' because it has no definition in this translation unit">;
 def note_func_effect_call_disallows_inference : Note<
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 526c0bb8d114e6..de1abf773082f1 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -25,12 +25,13 @@ namespace {
 
 enum class ViolationID : uint8_t {
   None = 0, // Sentinel for an empty Violation.
-  Throws,
-  Catches,
-  CallsObjC,
-  AllocatesMemory,
-  HasStaticLocal,
-  AccessesThreadLocal,
+  // These first few map to a %select{} in a diagnostic.
+  BaseDiagnosticIndex,
+  AllocatesMemory = BaseDiagnosticIndex,
+  ThrowsOrCatchesExceptions,
+  HasStaticLocalVariable,
+  AccessesThreadLocalVariable,
+  AccessesObjCMethodOrProperty,
 
   // These only apply to callees, where the analysis stops at the Decl.
   DeclDisallowsInference,
@@ -61,6 +62,10 @@ struct Violation {
     if (CalleeEffect != nullptr)
       CalleeEffectPreventingInference = *CalleeEffect;
   }
+
+  unsigned diagnosticSelectIndex() const {
+    return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex);
+  }
 };
 
 enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
@@ -664,27 +669,12 @@ class Analyzer {
         llvm_unreachable("Unexpected violation kind");
         break;
       case ViolationID::AllocatesMemory:
-        S.Diag(Viol1.Loc, diag::warn_func_effect_allocates) << effectName;
-        MaybeAddTemplateNote(CInfo.CDecl);
-        break;
-      case ViolationID::Throws:
-      case ViolationID::Catches:
-        S.Diag(Viol1.Loc, diag::warn_func_effect_throws_or_catches)
-            << effectName;
-        MaybeAddTemplateNote(CInfo.CDecl);
-        break;
-      case ViolationID::HasStaticLocal:
-        S.Diag(Viol1.Loc, diag::warn_func_effect_has_static_local)
-            << effectName;
-        MaybeAddTemplateNote(CInfo.CDecl);
-        break;
-      case ViolationID::AccessesThreadLocal:
-        S.Diag(Viol1.Loc, diag::warn_func_effect_uses_thread_local)
-            << effectName;
-        MaybeAddTemplateNote(CInfo.CDecl);
-        break;
-      case ViolationID::CallsObjC:
-        S.Diag(Viol1.Loc, diag::warn_func_effect_calls_objc) << effectName;
+      case ViolationID::ThrowsOrCatchesExceptions:
+      case ViolationID::HasStaticLocalVariable:
+      case ViolationID::AccessesThreadLocalVariable:
+      case ViolationID::AccessesObjCMethodOrProperty:
+        S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
+            << effectName << Viol1.diagnosticSelectIndex();
         MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::CallsExprWithoutEffect:
@@ -754,23 +744,12 @@ class Analyzer {
                 << effectName;
             break;
           case ViolationID::AllocatesMemory:
-            S.Diag(Viol2.Loc, diag::note_func_effect_allocates) << effectName;
-            break;
-          case ViolationID::Throws:
-          case ViolationID::Catches:
-            S.Diag(Viol2.Loc, diag::note_func_effect_throws_or_catches)
-                << effectName;
-            break;
-          case ViolationID::HasStaticLocal:
-            S.Diag(Viol2.Loc, diag::note_func_effect_has_static_local)
-                << effectName;
-            break;
-          case ViolationID::AccessesThreadLocal:
-            S.Diag(Viol2.Loc, diag::note_func_effect_uses_thread_local)
-                << effectName;
-            break;
-          case ViolationID::CallsObjC:
-            S.Diag(Viol2.Loc, diag::note_func_effect_calls_objc) << effectName;
+          case ViolationID::ThrowsOrCatchesExceptions:
+          case ViolationID::HasStaticLocalVariable:
+          case ViolationID::AccessesThreadLocalVariable:
+          case ViolationID::AccessesObjCMethodOrProperty:
+            S.Diag(Viol2.Loc, diag::note_func_effect_violation)
+                << effectName << Viol2.diagnosticSelectIndex();
             break;
           case ViolationID::CallsDeclWithoutEffect:
             MaybeNextCallee.emplace(*Viol2.Callee);
@@ -952,37 +931,43 @@ class Analyzer {
 
     bool VisitCXXThrowExpr(CXXThrowExpr *Throw) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
-                                ViolationID::Throws, Throw->getThrowLoc());
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Throw->getThrowLoc());
       return true;
     }
 
     bool VisitCXXCatchStmt(CXXCatchStmt *Catch) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
-                                ViolationID::Catches, Catch->getCatchLoc());
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Catch->getCatchLoc());
       return true;
     }
 
     bool VisitObjCAtThrowStmt(ObjCAtThrowStmt *Throw) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThrow,
-                                ViolationID::Throws, Throw->getThrowLoc());
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Throw->getThrowLoc());
       return true;
     }
 
     bool VisitObjCAtCatchStmt(ObjCAtCatchStmt *Catch) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
-                                ViolationID::Catches, Catch->getAtCatchLoc());
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Catch->getAtCatchLoc());
       return true;
     }
 
     bool VisitObjCMessageExpr(ObjCMessageExpr *Msg) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeObjCMessageSend,
-                                ViolationID::CallsObjC, Msg->getBeginLoc());
+                                ViolationID::AccessesObjCMethodOrProperty,
+                                Msg->getBeginLoc());
       return true;
     }
 
     bool VisitSEHExceptStmt(SEHExceptStmt *Exc) {
       diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeCatch,
-                                ViolationID::Catches, Exc->getExceptLoc());
+                                ViolationID::ThrowsOrCatchesExceptions,
+                                Exc->getExceptLoc());
       return true;
     }
 
@@ -1018,7 +1003,7 @@ class Analyzer {
 
       if (Var->isStaticLocal())
         diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeStaticLocalVars,
-                                  ViolationID::HasStaticLocal,
+                                  ViolationID::HasStaticLocalVariable,
                                   Var->getLocation());
 
       const QualType::DestructionKind DK =
@@ -1106,7 +1091,7 @@ class Analyzer {
           // At least on macOS, thread-local variables are initialized on
           // first access, including a heap allocation.
           diagnoseLanguageConstruct(FunctionEffect::FE_ExcludeThreadLocalVars,
-                                    ViolationID::AccessesThreadLocal,
+                                    ViolationID::AccessesThreadLocalVariable,
                                     E->getLocation());
         }
       }
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 3515f7f4b7d6cc..0ff43608ac5a23 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -121,7 +121,7 @@ void nb8c()
 		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}}
+			auto* z = new char[42]; // expected-note {{function cannot be inferred 'nonblocking' because it allocates or deallocates memory}}
 			return {};
 		}
 	};

>From f93ee015a672ba2e3a221bc3d47f464be7f885bb Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 10:35:19 -0700
Subject: [PATCH 26/48] Violation constructor: use optional instead of pointer.

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index de1abf773082f1..58615a45e7a204 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -57,9 +57,9 @@ struct Violation {
 
   Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc,
             const Decl *Callee = nullptr,
-            const FunctionEffect *CalleeEffect = nullptr)
+            std::optional<FunctionEffect> CalleeEffect = std::nullopt)
       : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
-    if (CalleeEffect != nullptr)
+    if (CalleeEffect)
       CalleeEffectPreventingInference = *CalleeEffect;
   }
 
@@ -259,7 +259,7 @@ class PendingFunctionAnalysis {
         // try to infer it.
         InferrableEffectToFirstViolation.maybeInsert(Violation(
             effect, ViolationID::DeclDisallowsInference,
-            CInfo.CDecl->getLocation(), nullptr, &*ProblemCalleeEffect));
+            CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
       }
     }
     // InferrableEffects is now the set of inferrable effects which are not
@@ -338,7 +338,7 @@ class CompleteFunctionAnalysis {
 
 public:
   // The incoming Pending analysis is consumed (member(s) are moved-from).
-  CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &Pending,
+  CompleteFunctionAnalysis(ASTContext &Ctx, PendingFunctionAnalysis &&Pending,
                            FunctionEffectKindSet DeclaredEffects,
                            FunctionEffectKindSet AllInferrableEffectsToVerify)
       : VerifiedEffects(DeclaredEffects) {
@@ -530,7 +530,7 @@ class Analyzer {
 
     Visitor.run();
     if (FAnalysis.isComplete()) {
-      completeAnalysis(CInfo, FAnalysis);
+      completeAnalysis(CInfo, std::move(FAnalysis));
       return nullptr;
     }
     // Move the pending analysis to the heap and save it in the map.
@@ -545,14 +545,14 @@ class Analyzer {
   // Consume PendingFunctionAnalysis, create with it a CompleteFunctionAnalysis,
   // inserted in the container.
   void completeAnalysis(const CallableInfo &CInfo,
-                        PendingFunctionAnalysis &Pending) {
+                        PendingFunctionAnalysis &&Pending) {
     if (ArrayRef<Violation> Viols =
             Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
         !Viols.empty())
       emitDiagnostics(Viols, CInfo, S);
 
     CompleteFunctionAnalysis *CompletePtr =
-        new CompleteFunctionAnalysis(S.getASTContext(), Pending, CInfo.Effects,
+        new CompleteFunctionAnalysis(S.getASTContext(), std::move(Pending), CInfo.Effects,
                                      AllInferrableEffectsToVerify);
     DeclAnalysis[CInfo.CDecl] = CompletePtr;
     LLVM_DEBUG(llvm::dbgs() << "inserted complete " << CompletePtr << "\n";
@@ -576,7 +576,7 @@ class Analyzer {
       followCall(Caller, *Pending, Callee, Call.CallLoc,
                  /*AssertNoFurtherInference=*/true);
     }
-    completeAnalysis(Caller, *Pending);
+    completeAnalysis(Caller, std::move(*Pending));
     delete Pending;
   }
 

>From 6cc0a6275ad3ce1065b28eaaee2aa68d4a634e74 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 10:36:27 -0700
Subject: [PATCH 27/48] clang-format

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 58615a45e7a204..f357593668bee1 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -551,9 +551,9 @@ class Analyzer {
         !Viols.empty())
       emitDiagnostics(Viols, CInfo, S);
 
-    CompleteFunctionAnalysis *CompletePtr =
-        new CompleteFunctionAnalysis(S.getASTContext(), std::move(Pending), CInfo.Effects,
-                                     AllInferrableEffectsToVerify);
+    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()););

>From 69e1ae67a43e36c1cc0b8fb2dd209ba2e364aded Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 11:19:16 -0700
Subject: [PATCH 28/48] emitDiagnostics doesn't need to receive another S

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index f357593668bee1..2b4d321153d1b9 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -549,7 +549,7 @@ class Analyzer {
     if (ArrayRef<Violation> Viols =
             Pending.getSortedViolationsForExplicitEffects(S.getSourceManager());
         !Viols.empty())
-      emitDiagnostics(Viols, CInfo, S);
+      emitDiagnostics(Viols, CInfo);
 
     CompleteFunctionAnalysis *CompletePtr = new CompleteFunctionAnalysis(
         S.getASTContext(), std::move(Pending), CInfo.Effects,
@@ -644,8 +644,7 @@ class Analyzer {
 
   // Should only be called when function's analysis is determined to be
   // complete.
-  void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo,
-                       Sema &S) {
+  void emitDiagnostics(ArrayRef<Violation> Viols, const CallableInfo &CInfo) {
     if (Viols.empty())
       return;
 

>From 076302ec7091cd46445df014ced97862e0f47e0c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 11:31:10 -0700
Subject: [PATCH 29/48] VisitVarDecl can reuse followTypeDtor().

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 25 ++++++++-----------------
 1 file changed, 8 insertions(+), 17 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 2b4d321153d1b9..f36bd2c0ec1d9b 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -892,19 +892,20 @@ class Analyzer {
     // 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(), Dtor);
+        followTypeDtor(Field->getType(), DtorLoc);
 
       if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
         for (const CXXBaseSpecifier &Base : Class->bases())
-          followTypeDtor(Base.getType(), Dtor);
+          followTypeDtor(Base.getType(), DtorLoc);
 
         for (const CXXBaseSpecifier &Base : Class->vbases())
-          followTypeDtor(Base.getType(), Dtor);
+          followTypeDtor(Base.getType(), DtorLoc);
       }
     }
 
-    void followTypeDtor(QualType QT, const CXXDestructorDecl *OuterDtor) {
+    void followTypeDtor(QualType QT, SourceLocation CallSite) {
       const Type *Ty = QT.getTypePtr();
       while (Ty->isArrayType()) {
         const ArrayType *Arr = Ty->getAsArrayTypeUnsafe();
@@ -916,7 +917,7 @@ class Analyzer {
         if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
           if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
             CallableInfo CI(*Dtor);
-            followCall(CI, OuterDtor->getLocation());
+            followCall(CI, CallSite);
           }
         }
       }
@@ -1007,18 +1008,8 @@ class Analyzer {
 
       const QualType::DestructionKind DK =
           Var->needsDestruction(Outer.S.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(*Dtor);
-              followCall(CI, Var->getLocation());
-            }
-          }
-        }
-      }
+      if (DK == QualType::DK_cxx_destructor)
+        followTypeDtor(Var->getType(), Var->getLocation());
       return true;
     }
 

>From 7b891c630e0e7eb55e27c3eca374f7af83e4ecca Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 15:35:12 -0700
Subject: [PATCH 30/48] Add a test for a delegating initializer.

---
 clang/test/Sema/attr-nonblocking-constraints.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 0ff43608ac5a23..f51ca1a855880e 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -265,10 +265,16 @@ struct Unsafe {
 
   Unsafe() { problem1(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
   ~Unsafe() { problem2(); } // expected-note {{function 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 {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
 };
 
 struct DerivedFromUnsafe : public Unsafe {
   DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
+  DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
   ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
 };
 

>From d1fcceb9b2d54674673f1162b0621d12017ab7e6 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 15:35:40 -0700
Subject: [PATCH 31/48] Fix typo in comment for TraverseLambdaExpr.

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index f36bd2c0ec1d9b..f93193a2f93bdb 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1057,7 +1057,7 @@ class Analyzer {
     }
 
     bool TraverseLambdaExpr(LambdaExpr *Lambda) {
-      // We override this so as the be able to skip traversal of the lambda's
+      // 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

>From abcf022fc5a1b27a46d9d4a6ab50d897cbdbf489 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 21:37:53 -0700
Subject: [PATCH 32/48] Fix test broken by rewording of diagnostics.

---
 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index ff5873c11c4fe7..262d647142d548 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -8,7 +8,7 @@ - (void)method;
 @end
 
 void nb1(OCClass *oc) [[clang::nonblocking]] {
-	[oc method]; // expected-warning {{'nonblocking' function must not access an ObjC method or property}}
+	[oc method]; // expected-warning {{'nonblocking' function 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}}

>From 47ebf2767f59dd9a62b63c32b47aef0343a83c7c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Fri, 16 Aug 2024 21:38:32 -0700
Subject: [PATCH 33/48] Diagnostics are now more specific about where a
 construct was found, specially handling member initializers, and more
 specifically describing Decls which are constructors, destructors, lambdas
 and blocks.

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  37 +++--
 clang/lib/Sema/SemaFunctionEffects.cpp        | 145 ++++++++++++++----
 .../Sema/attr-nonblocking-constraints.cpp     |  51 +++---
 3 files changed, 161 insertions(+), 72 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3ebc7089a93d7b..e60088f0703ca3 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10951,29 +10951,38 @@ def warn_imp_cast_drops_unaligned : Warning<
 
 // Function effects
 def warn_func_effect_violation : Warning<
-  "'%0' function must not %select{allocate or deallocate memory|throw or catch exceptions|"
-  "have static local variables|use thread-local variables|access ObjC methods or properties}1">,
+  "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+  "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>;
 def note_func_effect_violation : Note<
-  "function cannot be inferred '%0' 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}1">;
+  "%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<
-  "'%0' function must not call non-'%0' function '%1'">,
+  "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+  "must not call non-'%0' "
+  "%select{function|constructor|destructor|lambda|block}2 "
+  "'%3'">,
   InGroup<FunctionEffects>;
 def note_func_effect_calls_func_without_effect : Note<
-  "function cannot be inferred '%0' because it calls non-'%0' function '%1'">;
+  "%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<
-  "'%0' function must not call non-'%0' expression">,
+  "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+  "must not call non-'%0' expression">,
   InGroup<FunctionEffects>;
 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<
-  "function does not permit inference of '%0' because it is declared '%1'">;
-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'">;
+  "%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<
   "'%0' function should be declared noexcept">,
   InGroup<PerfConstraintImpliesNoexcept>;
@@ -10981,6 +10990,8 @@ def warn_perf_constraint_implies_noexcept : Warning<
 // 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">;
 
 // spoofing nonblocking/nonallocating
 def warn_invalid_add_func_effects : Warning<
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index f93193a2f93bdb..acf459ec9555c1 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #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"
@@ -40,6 +41,13 @@ enum class ViolationID : uint8_t {
   CallsExprWithoutEffect,
 };
 
+// Bits, describing the AST context in which a violation was found.
+// If none present, the context was the function body.
+using ViolationSite = unsigned;
+enum {
+  VSite_MemberInitializer = 1,
+};
+
 // 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
@@ -48,17 +56,19 @@ enum class ViolationID : uint8_t {
 // be inferred as holding that effect.
 struct Violation {
   FunctionEffect Effect;
-  FunctionEffect CalleeEffectPreventingInference; // Only for certain IDs.
+  FunctionEffect
+      CalleeEffectPreventingInference; // Only for certain IDs; can be None.
   ViolationID ID = ViolationID::None;
+  ViolationSite Site;
   SourceLocation Loc;
   const Decl *Callee = nullptr; // Only valid for Calls*.
 
   Violation() = default;
 
-  Violation(FunctionEffect Effect, ViolationID ID, SourceLocation Loc,
-            const Decl *Callee = nullptr,
+  Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS,
+            SourceLocation Loc, const Decl *Callee = nullptr,
             std::optional<FunctionEffect> CalleeEffect = std::nullopt)
-      : Effect(Effect), ID(ID), Loc(Loc), Callee(Callee) {
+      : Effect(Effect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) {
     if (CalleeEffect)
       CalleeEffectPreventingInference = *CalleeEffect;
   }
@@ -220,9 +230,10 @@ class PendingFunctionAnalysis {
     // Not all recursive calls are detected, just enough
     // to break cycles.
     bool Recursed = false;
+    ViolationSite VSite;
 
-    DirectCall(const Decl *D, SourceLocation CallLoc)
-        : Callee(D), CallLoc(CallLoc) {}
+    DirectCall(const Decl *D, SourceLocation CallLoc, ViolationSite VSite)
+        : Callee(D), CallLoc(CallLoc), VSite(VSite) {}
   };
 
   // We always have two disjoint sets of effects to verify:
@@ -246,7 +257,7 @@ class PendingFunctionAnalysis {
   PendingFunctionAnalysis(Sema &S, const CallableInfo &CInfo,
                           FunctionEffectKindSet AllInferrableEffectsToVerify)
       : DeclaredVerifiableEffects(CInfo.Effects) {
-    // Check for effects we are not allowed to infer
+    // Check for effects we are not allowed to infer.
     FunctionEffectKindSet InferrableEffects;
 
     for (FunctionEffect effect : AllInferrableEffectsToVerify) {
@@ -258,12 +269,12 @@ class PendingFunctionAnalysis {
         // Add a Violation for this effect if a caller were to
         // try to infer it.
         InferrableEffectToFirstViolation.maybeInsert(Violation(
-            effect, ViolationID::DeclDisallowsInference,
+            effect, ViolationID::DeclDisallowsInference, 0,
             CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
       }
     }
     // InferrableEffects is now the set of inferrable effects which are not
-    // prohibited
+    // prohibited.
     EffectsToInfer = FunctionEffectKindSet::difference(
         InferrableEffects, DeclaredVerifiableEffects);
   }
@@ -277,8 +288,9 @@ class PendingFunctionAnalysis {
       InferrableEffectToFirstViolation.maybeInsert(NewViol);
   }
 
-  void addUnverifiedDirectCall(const Decl *D, SourceLocation CallLoc) {
-    UnverifiedDirectCalls.emplace_back(D, CallLoc);
+  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.
@@ -574,7 +586,7 @@ class Analyzer {
 
       CallableInfo Callee(*Call.Callee);
       followCall(Caller, *Pending, Callee, Call.CallLoc,
-                 /*AssertNoFurtherInference=*/true);
+                 /*AssertNoFurtherInference=*/true, Call.VSite);
     }
     completeAnalysis(Caller, std::move(*Pending));
     delete Pending;
@@ -584,7 +596,7 @@ class Analyzer {
   // other AST construct. PFA pertains to the caller.
   void followCall(const CallableInfo &Caller, PendingFunctionAnalysis &PFA,
                   const CallableInfo &Callee, SourceLocation CallLoc,
-                  bool AssertNoFurtherInference) {
+                  bool AssertNoFurtherInference, ViolationSite VSite) {
     const bool DirectCall = Callee.isCalledDirectly();
 
     // Initially, the declared effects; inferred effects will be added.
@@ -625,13 +637,14 @@ class Analyzer {
         if (Callee.FuncType == SpecialFuncType::None)
           PFA.checkAddViolation(Inferring,
                                 {Effect, ViolationID::CallsDeclWithoutEffect,
-                                 CallLoc, Callee.CDecl});
+                                 VSite, CallLoc, Callee.CDecl});
         else
           PFA.checkAddViolation(
-              Inferring, {Effect, ViolationID::AllocatesMemory, CallLoc});
+              Inferring,
+              {Effect, ViolationID::AllocatesMemory, VSite, CallLoc});
       } else {
         // Inference is allowed and necessary; defer it.
-        PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc);
+        PFA.addUnverifiedDirectCall(Callee.CDecl, CallLoc, VSite);
       }
     };
 
@@ -658,6 +671,50 @@ class Analyzer {
       }
     };
 
+    // For note_func_effect_call_indirect.
+    enum { Indirect_VirtualMethod, Indirect_FunctionPtr };
+
+    // Describe a call site or target using an enum mapping to a %select{}
+    // in a diagnostic.
+    auto SiteDescIndex = [](const Decl *D, const Violation *V) {
+      enum {
+        VS_Function,
+        VS_Constructor,
+        VS_Destructor,
+        VS_Lambda,
+        VS_Block,
+        VS_MemberInitializer,
+      };
+
+      if (V != nullptr && V->Site == VSite_MemberInitializer)
+        return VS_MemberInitializer;
+      if (isa<BlockDecl>(D))
+        return VS_Block;
+      if (auto *Method = dyn_cast<CXXMethodDecl>(D)) {
+        if (isa<CXXConstructorDecl>(D))
+          return VS_Constructor;
+        if (isa<CXXDestructorDecl>(D))
+          return VS_Destructor;
+        const CXXRecordDecl *Rec = Method->getParent();
+        if (Rec->isLambda())
+          return VS_Lambda;
+      }
+      return VS_Function;
+    };
+
+    // If a Violation's site is a member initializer, adds a note referring to
+    // the constructor which invoked it.
+    auto MaybeAddCtorContext = [&](const Decl *D, const Violation &V) {
+      if (V.Site == VSite_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;
+      }
+    };
+
     // Top-level violations are warnings.
     for (const Violation &Viol1 : Viols) {
       StringRef effectName = Viol1.Effect.name();
@@ -673,12 +730,15 @@ class Analyzer {
       case ViolationID::AccessesThreadLocalVariable:
       case ViolationID::AccessesObjCMethodOrProperty:
         S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
-            << effectName << Viol1.diagnosticSelectIndex();
+            << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+            << Viol1.diagnosticSelectIndex();
+        MaybeAddCtorContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::CallsExprWithoutEffect:
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
-            << effectName;
+            << effectName << SiteDescIndex(CInfo.CDecl, &Viol1);
+        MaybeAddCtorContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
         break;
 
@@ -687,7 +747,9 @@ class Analyzer {
         std::string CalleeName = CalleeInfo.name(S);
 
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
-            << effectName << CalleeName;
+            << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+            << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName;
+        MaybeAddCtorContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
 
         // Emit notes explaining the transitive chain of inferences: Why isn't
@@ -704,16 +766,17 @@ class Analyzer {
 
             CallableType CType = CalleeInfo.type();
             if (CType == CallableType::Virtual)
-              S.Diag(Callee->getLocation(), diag::note_func_effect_call_virtual)
-                  << effectName;
+              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_func_ptr)
-                  << effectName;
+                     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)
-                  << effectName
+                  << SiteDescIndex(CInfo.CDecl, nullptr) << effectName
                   << FunctionEffect(Viol1.Effect.oppositeKind()).name();
             else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(Callee);
                      FD == nullptr || FD->getBuiltinID() == 0) {
@@ -736,11 +799,12 @@ class Analyzer {
             break;
           case ViolationID::DeclDisallowsInference:
             S.Diag(Viol2.Loc, diag::note_func_effect_call_disallows_inference)
-                << effectName << Viol2.CalleeEffectPreventingInference.name();
+                << SiteDescIndex(CalleeInfo.CDecl, nullptr) << effectName
+                << Viol2.CalleeEffectPreventingInference.name();
             break;
           case ViolationID::CallsExprWithoutEffect:
-            S.Diag(Viol2.Loc, diag::note_func_effect_call_func_ptr)
-                << effectName;
+            S.Diag(Viol2.Loc, diag::note_func_effect_call_indirect)
+                << Indirect_FunctionPtr << effectName;
             break;
           case ViolationID::AllocatesMemory:
           case ViolationID::ThrowsOrCatchesExceptions:
@@ -748,12 +812,16 @@ class Analyzer {
           case ViolationID::AccessesThreadLocalVariable:
           case ViolationID::AccessesObjCMethodOrProperty:
             S.Diag(Viol2.Loc, diag::note_func_effect_violation)
-                << effectName << Viol2.diagnosticSelectIndex();
+                << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName
+                << Viol2.diagnosticSelectIndex();
+            MaybeAddCtorContext(CalleeInfo.CDecl, Viol2);
             break;
           case ViolationID::CallsDeclWithoutEffect:
             MaybeNextCallee.emplace(*Viol2.Callee);
             S.Diag(Viol2.Loc, diag::note_func_effect_calls_func_without_effect)
-                << effectName << MaybeNextCallee->name(S);
+                << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName
+                << SiteDescIndex(Viol2.Callee, nullptr)
+                << MaybeNextCallee->name(S);
             break;
           }
           MaybeAddTemplateNote(Callee);
@@ -777,10 +845,12 @@ class Analyzer {
   //
   // Violations are always routed to a PendingFunctionAnalysis.
   struct FunctionBodyASTVisitor : RecursiveASTVisitor<FunctionBodyASTVisitor> {
+    using Base = RecursiveASTVisitor<FunctionBodyASTVisitor>;
 
     Analyzer &Outer;
     PendingFunctionAnalysis &CurrentFunction;
     CallableInfo &CurrentCaller;
+    ViolationSite VSite = 0;
 
     FunctionBodyASTVisitor(Analyzer &Outer,
                            PendingFunctionAnalysis &CurrentFunction,
@@ -822,10 +892,10 @@ class Analyzer {
           addViolation(/*inferring=*/true, Effect, VID, Loc, Callee);
     }
 
-    void addViolation(bool Inferring, FunctionEffect Effect, ViolationID D,
+    void addViolation(bool Inferring, FunctionEffect Effect, ViolationID VID,
                       SourceLocation Loc, const Decl *Callee = nullptr) {
-      CurrentFunction.checkAddViolation(Inferring,
-                                        Violation(Effect, D, Loc, Callee));
+      CurrentFunction.checkAddViolation(
+          Inferring, Violation(Effect, VID, VSite, Loc, Callee));
     }
 
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
@@ -836,7 +906,7 @@ class Analyzer {
         return;
 
       Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
-                       /*AssertNoFurtherInference=*/false);
+                       /*AssertNoFurtherInference=*/false, VSite);
     }
 
     // FIXME: This is currently specific to the `nonblocking` and
@@ -1056,6 +1126,15 @@ class Analyzer {
       return true;
     }
 
+    bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
+      ViolationSite PrevVS = VSite;
+      if (Init->isAnyMemberInitializer())
+        VSite = VSite_MemberInitializer;
+      bool Result = Base::TraverseConstructorInitializer(Init);
+      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
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index f51ca1a855880e..6caa3d1ad6b226 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -69,7 +69,7 @@ void nb7()
 {
 	// Make sure we verify blocks
 	auto blk = ^() [[clang::nonblocking]] {
-		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+		throw 42; // expected-warning {{'nonblocking' block must not throw or catch exceptions}}
 	};
 }
 
@@ -77,7 +77,7 @@ void nb8()
 {
 	// Make sure we verify lambdas
 	auto lambda = []() [[clang::nonblocking]] {
-		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+		throw 42; // expected-warning {{'nonblocking' lambda must not throw or catch exceptions}}
 	};
 }
 
@@ -228,54 +228,53 @@ void nb20() [[clang::nonblocking]] {
 
 struct S {
     int x;
-    S(int x) try : x(x) {} catch (...) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
-    S(double) : x((throw 3, 3)) {} // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+    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}}
+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 {
-    int x = (throw 3, 3); // expected-note {{function cannot be inferred 'nonblocking' because it throws or catches exceptions}}
+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 {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'badi'}}
+    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 {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
-    S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::S'}}
-    A a;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'A::A'}}
-    B b;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'B::B'}}
+    S s1(3);   // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
+    S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
+    A a;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'A::A'}}
+    B b;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'B::B'}}
 }
 
-#if 0
-// FIXME: can we do better with default member initializers?
 struct T {
-	int x = badi();
-	T() [[clang::nonblocking]] {} // Warning: this calls bad().
-	T(int x) [[clang::nonblocking]] : x(x) {} // This does not.
+	int x = badi();               // expected-warning {{'nonblocking' constructor's member initializer must not call non-'nonblocking' function 'badi'}}
+	T() [[clang::nonblocking]] {} // expected-note {{in constructor here}}
+	T(int x) [[clang::nonblocking]] : x(x) {} // OK
 };
-#endif
 
 // 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}}
+  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 {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem1'}}
-  ~Unsafe() { problem2(); } // expected-note {{function cannot be inferred 'nonblocking' because it calls non-'nonblocking' function 'Unsafe::problem2'}}
+  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 {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
+  Unsafe(float y) [[clang::nonblocking]] : Unsafe(int(y)) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
 };
 
 struct DerivedFromUnsafe : public Unsafe {
-  DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
-  DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::Unsafe'}}
-  ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'Unsafe::~Unsafe'}}
+  DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+  DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+  ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
 };
 
 // Contexts where there is no function call, no diagnostic.

>From 6650c1fae8d1023dffb8debf42ea7f4b2b6753d9 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 20 Aug 2024 08:39:01 -0700
Subject: [PATCH 34/48] Two fixes for default arguments: - they are never
 attributed to the function, only its callers. - while the warning points to
 the expression in the declaration, add a note pointing to the caller.

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  2 +
 clang/lib/Sema/SemaFunctionEffects.cpp        | 70 ++++++++++++++-----
 .../Sema/attr-nonblocking-constraints.cpp     | 10 +++
 3 files changed, 65 insertions(+), 17 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e60088f0703ca3..ee6072fbb38ca4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10992,6 +10992,8 @@ 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<
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index acf459ec9555c1..9166aaa3d1ab7b 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -41,11 +41,17 @@ enum class ViolationID : uint8_t {
   CallsExprWithoutEffect,
 };
 
-// Bits, describing the AST context in which a violation was found.
-// If none present, the context was the function body.
-using ViolationSite = unsigned;
-enum {
-  VSite_MemberInitializer = 1,
+// Information about the AST context in which a violation was found, so
+// that diagnostics can point to the correct source.
+struct ViolationSite {
+  enum class Kind : uint8_t {
+    Default = 0, // Function body.
+    MemberInitializer = 1,
+    DefaultArgExpr = 2
+  };
+
+  Kind VKind = Kind::Default;
+  CXXDefaultArgExpr *DefaultArgExpr = nullptr;
 };
 
 // Represents a violation of the rules, potentially for the entire duration of
@@ -269,7 +275,7 @@ class PendingFunctionAnalysis {
         // Add a Violation for this effect if a caller were to
         // try to infer it.
         InferrableEffectToFirstViolation.maybeInsert(Violation(
-            effect, ViolationID::DeclDisallowsInference, 0,
+            effect, ViolationID::DeclDisallowsInference, ViolationSite{},
             CInfo.CDecl->getLocation(), nullptr, ProblemCalleeEffect));
       }
     }
@@ -686,7 +692,8 @@ class Analyzer {
         VS_MemberInitializer,
       };
 
-      if (V != nullptr && V->Site == VSite_MemberInitializer)
+      if (V != nullptr &&
+          V->Site.VKind == ViolationSite::Kind::MemberInitializer)
         return VS_MemberInitializer;
       if (isa<BlockDecl>(D))
         return VS_Block;
@@ -702,10 +709,10 @@ class Analyzer {
       return VS_Function;
     };
 
-    // If a Violation's site is a member initializer, adds a note referring to
-    // the constructor which invoked it.
-    auto MaybeAddCtorContext = [&](const Decl *D, const Violation &V) {
-      if (V.Site == VSite_MemberInitializer) {
+    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.VKind == ViolationSite::Kind::MemberInitializer) {
         unsigned ImplicitCtor = 0;
         if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D);
             Ctor && Ctor->isImplicit())
@@ -713,6 +720,12 @@ class Analyzer {
         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.VKind == ViolationSite::Kind::DefaultArgExpr)
+        S.Diag(V.Site.DefaultArgExpr->getUsedLocation(),
+               diag::note_in_evaluating_default_argument);
     };
 
     // Top-level violations are warnings.
@@ -732,13 +745,13 @@ class Analyzer {
         S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
             << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
             << Viol1.diagnosticSelectIndex();
-        MaybeAddCtorContext(CInfo.CDecl, Viol1);
+        MaybeAddSiteContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
         break;
       case ViolationID::CallsExprWithoutEffect:
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_expr_without_effect)
             << effectName << SiteDescIndex(CInfo.CDecl, &Viol1);
-        MaybeAddCtorContext(CInfo.CDecl, Viol1);
+        MaybeAddSiteContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
         break;
 
@@ -749,7 +762,7 @@ class Analyzer {
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
             << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
             << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName;
-        MaybeAddCtorContext(CInfo.CDecl, Viol1);
+        MaybeAddSiteContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
 
         // Emit notes explaining the transitive chain of inferences: Why isn't
@@ -814,7 +827,7 @@ class Analyzer {
             S.Diag(Viol2.Loc, diag::note_func_effect_violation)
                 << SiteDescIndex(CalleeInfo.CDecl, &Viol2) << effectName
                 << Viol2.diagnosticSelectIndex();
-            MaybeAddCtorContext(CalleeInfo.CDecl, Viol2);
+            MaybeAddSiteContext(CalleeInfo.CDecl, Viol2);
             break;
           case ViolationID::CallsDeclWithoutEffect:
             MaybeNextCallee.emplace(*Viol2.Callee);
@@ -850,7 +863,7 @@ class Analyzer {
     Analyzer &Outer;
     PendingFunctionAnalysis &CurrentFunction;
     CallableInfo &CurrentCaller;
-    ViolationSite VSite = 0;
+    ViolationSite VSite;
 
     FunctionBodyASTVisitor(Analyzer &Outer,
                            PendingFunctionAnalysis &CurrentFunction,
@@ -1129,12 +1142,35 @@ class Analyzer {
     bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
       ViolationSite PrevVS = VSite;
       if (Init->isAnyMemberInitializer())
-        VSite = VSite_MemberInitializer;
+        VSite.VKind = 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.VKind == ViolationSite::Kind::Default)
+        VSite = ViolationSite{.VKind = ViolationSite::Kind::DefaultArgExpr,
+                              .DefaultArgExpr = E};
+
+      bool Result = Base::TraverseCXXDefaultArgExpr(E);
+      VSite = PrevVS;
+      return Result;
+    }
+
+    bool TraverseParmVarDecl(ParmVarDecl *PV) {
+      // By traversing a ParmVarDecl as if it were a simple VarDecl, we avoid
+      // incorrectly attributing default argument expressions to this function;
+      // they are properly attributed to callers, via a CXXDefaultArgExpr.
+      return Base::TraverseVarDecl(PV);
+    }
+
     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
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 6caa3d1ad6b226..1e90696f00e0be 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -257,6 +257,16 @@ struct T {
 	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}}
+
+void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
+}
+
+void nb21() [[clang::nonblocking]] {
+	hasDefaultArg(); // expected-note {{in evaluating default argument here}}
+}
+
 // 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}}

>From 250b80becb3769f09d2af32434e0322bfa08b83f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Tue, 20 Aug 2024 08:39:28 -0700
Subject: [PATCH 35/48] Type.cpp: remove asserts preceding llvm_unreachable().

---
 clang/lib/AST/Type.cpp | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 8d36aa15c59572..9c50b6b6087fa4 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5159,10 +5159,9 @@ std::optional<FunctionEffect> FunctionEffect::effectProhibitingInference(
     break;
 
   case Kind::None:
-    assert(0 && "effectProhibitingInference with None");
     break;
   }
-  llvm_unreachable("unknown effect kind");
+  llvm_unreachable("unknown effect kind or None");
 }
 
 bool FunctionEffect::shouldDiagnoseFunctionCall(
@@ -5185,10 +5184,9 @@ bool FunctionEffect::shouldDiagnoseFunctionCall(
   case Kind::Blocking:
     return false;
   case Kind::None:
-    assert(0 && "shouldDiagnoseFunctionCall with None");
     break;
   }
-  llvm_unreachable("unknown effect kind");
+  llvm_unreachable("unknown effect kind or None");
 }
 
 // =====

>From e8bcd9f6962a2b5c2286fccfcd1716b5b180bf47 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 22 Aug 2024 09:28:27 -0700
Subject: [PATCH 36/48] Fix pre-C++20 compile error. Test an unsafe default
 argument.

---
 clang/include/clang/AST/Type.h                 |  1 +
 clang/lib/Sema/SemaFunctionEffects.cpp         | 18 +++++++++---------
 .../test/Sema/attr-nonblocking-constraints.cpp | 13 ++++++++++---
 3 files changed, 20 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 59221dcb85eeba..49077750744fff 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4998,6 +4998,7 @@ class FunctionEffectKindSet {
   }
   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));
   }
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 9166aaa3d1ab7b..9310171a401b0d 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -37,6 +37,10 @@ enum class ViolationID : uint8_t {
   // These only apply to callees, where the analysis stops at the Decl.
   DeclDisallowsInference,
 
+  // These both apply to indirect calls. The difference 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,
 };
@@ -52,6 +56,10 @@ struct ViolationSite {
 
   Kind VKind = Kind::Default;
   CXXDefaultArgExpr *DefaultArgExpr = nullptr;
+
+  ViolationSite() = default;
+  explicit ViolationSite(CXXDefaultArgExpr *E)
+      : VKind(Kind::DefaultArgExpr), DefaultArgExpr(E) {}
 };
 
 // Represents a violation of the rules, potentially for the entire duration of
@@ -1156,21 +1164,13 @@ class Analyzer {
 
       ViolationSite PrevVS = VSite;
       if (VSite.VKind == ViolationSite::Kind::Default)
-        VSite = ViolationSite{.VKind = ViolationSite::Kind::DefaultArgExpr,
-                              .DefaultArgExpr = E};
+        VSite = ViolationSite{E};
 
       bool Result = Base::TraverseCXXDefaultArgExpr(E);
       VSite = PrevVS;
       return Result;
     }
 
-    bool TraverseParmVarDecl(ParmVarDecl *PV) {
-      // By traversing a ParmVarDecl as if it were a simple VarDecl, we avoid
-      // incorrectly attributing default argument expressions to this function;
-      // they are properly attributed to callers, via a CXXDefaultArgExpr.
-      return Base::TraverseVarDecl(PV);
-    }
-
     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
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 1e90696f00e0be..ca9fe199f5f668 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -258,13 +258,20 @@ struct T {
 };
 
 // Default arguments
-int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonblocking' because it has no definition in this translation unit}}
+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 {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
+void hasDefaultArg(int param = badForDefaultArg()) { // expected-warning {{'nonblocking' function 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}}
+	hasDefaultArg(); // expected-note {{in evaluating default argument here}} \
+	                    expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'hasDefaultArg'}}
+}
+
+void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
 }
 
 // Verify traversal of implicit code paths - constructors and destructors.

>From ea7f3fc8c0b5269e430916c3f8955c694dbc9af5 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 29 Aug 2024 09:43:54 -0500
Subject: [PATCH 37/48] Review feedback: ViolationSite can use a
 PointerIntPair. Expand list of known builtins and make the implementation
 slightly less specific to nonblocking/nonallocating.

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 140 +++++++++++++++++--------
 1 file changed, 98 insertions(+), 42 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 9310171a401b0d..186be26f767d91 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -47,19 +47,27 @@ enum class ViolationID : uint8_t {
 
 // Information about the AST context in which a violation was found, so
 // that diagnostics can point to the correct source.
-struct ViolationSite {
+class ViolationSite {
+public:
   enum class Kind : uint8_t {
     Default = 0, // Function body.
     MemberInitializer = 1,
     DefaultArgExpr = 2
   };
 
-  Kind VKind = Kind::Default;
-  CXXDefaultArgExpr *DefaultArgExpr = nullptr;
+private:
+  llvm::PointerIntPair<CXXDefaultArgExpr *, 2, Kind> Impl;
 
+public:
   ViolationSite() = default;
+
   explicit ViolationSite(CXXDefaultArgExpr *E)
-      : VKind(Kind::DefaultArgExpr), DefaultArgExpr(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
@@ -119,6 +127,74 @@ static bool isNoexcept(const FunctionDecl *FD) {
   return false;
 }
 
+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::BI__builtin_unwind_init: // ????
+  // __builtin_eh_return?
+  // __builtin_allow_runtime_check
+  // va_copy
+  // printf, fprintf, snprintf, sprintf, vprintf, vfprintf, vsnprintf
+  // scanf family
+  // coroutine intrinsics?
+
+  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.
+  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:
+
+  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:
+
+  case Builtin::ID::BIfread:
+  case Builtin::ID::BIfwrite:
+    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 {
@@ -701,7 +777,7 @@ class Analyzer {
       };
 
       if (V != nullptr &&
-          V->Site.VKind == ViolationSite::Kind::MemberInitializer)
+          V->Site.kind() == ViolationSite::Kind::MemberInitializer)
         return VS_MemberInitializer;
       if (isa<BlockDecl>(D))
         return VS_Block;
@@ -720,7 +796,7 @@ class Analyzer {
     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.VKind == ViolationSite::Kind::MemberInitializer) {
+      if (V.Site.kind() == ViolationSite::Kind::MemberInitializer) {
         unsigned ImplicitCtor = 0;
         if (auto *Ctor = dyn_cast<CXXConstructorDecl>(D);
             Ctor && Ctor->isImplicit())
@@ -731,8 +807,8 @@ class Analyzer {
 
       // 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.VKind == ViolationSite::Kind::DefaultArgExpr)
-        S.Diag(V.Site.DefaultArgExpr->getUsedLocation(),
+      else if (V.Site.kind() == ViolationSite::Kind::DefaultArgExpr)
+        S.Diag(V.Site.defaultArgExpr()->getUsedLocation(),
                diag::note_in_evaluating_default_argument);
     };
 
@@ -921,43 +997,23 @@ class Analyzer {
 
     // Here we have a call to a Decl, either explicitly via a CallExpr or some
     // other AST construct. CallableInfo pertains to the callee.
-    void followCall(const CallableInfo &CI, SourceLocation CallLoc) {
-      if (const auto *FD = dyn_cast<FunctionDecl>(CI.CDecl);
-          FD && isSafeBuiltinFunction(FD))
-        return;
+    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;
+          }
+        }
+      }
 
       Outer.followCall(CurrentCaller, CurrentFunction, CI, CallLoc,
                        /*AssertNoFurtherInference=*/false, VSite);
     }
 
-    // FIXME: This is currently specific to the `nonblocking` and
-    // `nonallocating` effects. More ideally, the builtin functions themselves
-    // would have the `allocating` attribute.
-    static bool isSafeBuiltinFunction(const FunctionDecl *FD) {
-      unsigned BuiltinID = FD->getBuiltinID();
-      switch (BuiltinID) {
-      case 0: // Not builtin.
-        return false;
-      default: // Not disallowed via cases below.
-        return true;
-
-      // Disallow list
-      case Builtin::ID::BIaligned_alloc:
-      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::BIcalloc:
-      case Builtin::ID::BImalloc:
-      case Builtin::ID::BImemalign:
-      case Builtin::ID::BIrealloc:
-      case Builtin::ID::BIfree:
-        return false;
-      }
-    }
-
     void checkIndirectCall(CallExpr *Call, QualType CalleeType) {
       auto *FPT =
           CalleeType->getAs<FunctionProtoType>(); // Null if FunctionType.
@@ -1150,7 +1206,7 @@ class Analyzer {
     bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
       ViolationSite PrevVS = VSite;
       if (Init->isAnyMemberInitializer())
-        VSite.VKind = ViolationSite::Kind::MemberInitializer;
+        VSite.setKind(ViolationSite::Kind::MemberInitializer);
       bool Result = Base::TraverseConstructorInitializer(Init);
       VSite = PrevVS;
       return Result;
@@ -1163,7 +1219,7 @@ class Analyzer {
                      << "\n";);
 
       ViolationSite PrevVS = VSite;
-      if (VSite.VKind == ViolationSite::Kind::Default)
+      if (VSite.kind() == ViolationSite::Kind::Default)
         VSite = ViolationSite{E};
 
       bool Result = Base::TraverseCXXDefaultArgExpr(E);

>From d1a39e2792c200fa7647451bd7d4a30fc9fdb815 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 29 Aug 2024 10:47:17 -0700
Subject: [PATCH 38/48] Fix: was traversing virtual base class destructors
 twice, exposed by new test.

---
 clang/lib/Sema/SemaFunctionEffects.cpp           |  6 +-----
 clang/test/Sema/attr-nonblocking-constraints.cpp | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 186be26f767d91..d2e3f1b44ba0cb 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1043,13 +1043,9 @@ class Analyzer {
       for (const FieldDecl *Field : Rec->fields())
         followTypeDtor(Field->getType(), DtorLoc);
 
-      if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec)) {
+      if (const auto *Class = dyn_cast<CXXRecordDecl>(Rec))
         for (const CXXBaseSpecifier &Base : Class->bases())
           followTypeDtor(Base.getType(), DtorLoc);
-
-        for (const CXXBaseSpecifier &Base : Class->vbases())
-          followTypeDtor(Base.getType(), DtorLoc);
-      }
     }
 
     void followTypeDtor(QualType QT, SourceLocation CallSite) {
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index ca9fe199f5f668..2890199c609106 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -294,6 +294,20 @@ struct DerivedFromUnsafe : public Unsafe {
   ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~Unsafe'}}
 };
 
+// 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 {{'nonblocking' constructor must not call non-'nonblocking' constructor 'VBase::VBase'}}
+
+  ~VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'VBase::~VBase'}}
+};
+
 // Contexts where there is no function call, no diagnostic.
 bool bad();
 

>From 75365efc34f8d4255f965940b7a5e3f3aa3fd122 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 29 Aug 2024 11:03:09 -0700
Subject: [PATCH 39/48] More builtin functions.

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 40 +++++++++++++++++++-------
 1 file changed, 30 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index d2e3f1b44ba0cb..423ea54ec56313 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -63,7 +63,7 @@ class ViolationSite {
 
   explicit ViolationSite(CXXDefaultArgExpr *E)
       : Impl(E, Kind::DefaultArgExpr) {}
-  
+
   Kind kind() const { return static_cast<Kind>(Impl.getInt()); }
   CXXDefaultArgExpr *defaultArgExpr() const { return Impl.getPointer(); }
 
@@ -127,6 +127,13 @@ static bool isNoexcept(const FunctionDecl *FD) {
   return false;
 }
 
+// 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;
 
@@ -149,14 +156,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
   case Builtin::ID::BIrealloc:
   case Builtin::ID::BIfree:
 
-  case Builtin::ID::BI__builtin_unwind_init: // ????
-  // __builtin_eh_return?
-  // __builtin_allow_runtime_check
-  // va_copy
-  // printf, fprintf, snprintf, sprintf, vprintf, vfprintf, vsnprintf
-  // scanf family
-  // coroutine intrinsics?
-
   case Builtin::ID::BIfopen:
   case Builtin::ID::BIpthread_create:
   case Builtin::ID::BI_Block_object_dispose:
@@ -170,6 +169,7 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
   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:
@@ -185,8 +185,29 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
   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;
   }
@@ -194,7 +215,6 @@ static FunctionEffectKindSet getBuiltinFunctionEffects(unsigned BuiltinID) {
   return Result;
 }
 
-
 // Transitory, more extended information about a callable, which can be a
 // function, block, or function pointer.
 struct CallableInfo {

>From 6aadec09c3e1d2fd6b22b1e3f475433eaf81a31e Mon Sep 17 00:00:00 2001
From: Doug Wyatt <doug at sonosphere.com>
Date: Wed, 4 Sep 2024 11:10:21 -0700
Subject: [PATCH 40/48] Apply suggestions from code review

Co-authored-by: Erich Keane <ekeane at nvidia.com>
---
 clang/include/clang/AST/Type.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 49077750744fff..ad3fdf425b4316 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4949,7 +4949,7 @@ class FunctionEffectKindSet {
   explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
 
   constexpr static size_t kindToPos(FunctionEffect::Kind K) {
-    return size_t(K);
+    return static_assert<size_t>(K);
   }
 
 public:
@@ -4983,7 +4983,7 @@ class FunctionEffectKindSet {
     }
 
     FunctionEffect operator*() const {
-      assert(Idx < EndBitPos);
+      assert(Idx < EndBitPos && "Dereference of end iterator");
       return FunctionEffect(FunctionEffect::Kind(Idx));
     }
   };

>From 9b123a64f1da2ad903defcbe2395ad67779ba628 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 4 Sep 2024 13:26:36 -0700
Subject: [PATCH 41/48] Review feedback: - Add operator<< for FunctionEffect
 and FunctionEffectWithCondition. - Simplified dump methods for
 FunctionEffectsRef and FunctionEffectKindSet. - Diagnostics now say "function
 with 'nonblocking' attribute" (where appropriate). - Other more minor tweaks.

---
 clang/include/clang/AST/Type.h                | 11 ++-
 .../clang/Basic/DiagnosticSemaKinds.td        | 13 ++--
 clang/include/clang/Sema/Sema.h               |  4 +-
 clang/include/clang/Serialization/ASTReader.h |  2 +-
 clang/lib/AST/Type.cpp                        | 38 ++++-----
 clang/lib/Sema/SemaFunctionEffects.cpp        | 16 ++--
 clang/lib/Serialization/ASTReader.cpp         | 21 ++---
 .../Sema/attr-nonblocking-constraints-ms.cpp  |  4 +-
 .../Sema/attr-nonblocking-constraints.cpp     | 78 +++++++++----------
 9 files changed, 94 insertions(+), 93 deletions(-)

diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index ad3fdf425b4316..e86a628cbc9e7e 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -4777,6 +4777,12 @@ class FunctionEffect {
   /// The description printed in diagnostics, e.g. 'nonblocking'.
   StringRef name() const;
 
+  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
@@ -4834,6 +4840,9 @@ struct FunctionEffectWithCondition {
 
   /// 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
@@ -4949,7 +4958,7 @@ class FunctionEffectKindSet {
   explicit FunctionEffectKindSet(KindBitsT KB) : KindBits(KB) {}
 
   constexpr static size_t kindToPos(FunctionEffect::Kind K) {
-    return static_assert<size_t>(K);
+    return static_cast<size_t>(K);
   }
 
 public:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ee6072fbb38ca4..efdfd53616cc9d 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10951,7 +10951,8 @@ def warn_imp_cast_drops_unaligned : Warning<
 
 // Function effects
 def warn_func_effect_violation : Warning<
-  "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+  "%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>;
@@ -10962,8 +10963,9 @@ def note_func_effect_violation : Note<
   "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<
-  "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
-  "must not call non-'%0' "
+  "%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>;
@@ -10973,7 +10975,8 @@ def note_func_effect_calls_func_without_effect : Note<
   "%select{function|constructor|destructor|lambda|block}2 "
   "'%3'">;
 def warn_func_effect_calls_expr_without_effect : Warning<
-  "'%0' %select{function|constructor|destructor|lambda|block|constructor's member initializer}1 "
+  "%select{function|constructor|destructor|lambda|block|member initializer of constructor}0 "
+  "with '%1' attribute "
   "must not call non-'%0' expression">,
   InGroup<FunctionEffects>;
 def note_func_effect_call_extern : Note<
@@ -10984,7 +10987,7 @@ def note_func_effect_call_disallows_inference : Note<
 def note_func_effect_call_indirect : Note<
   "%select{virtual method|function pointer}0 cannot be inferred '%1'">;
 def warn_perf_constraint_implies_noexcept : Warning<
-  "'%0' function should be declared noexcept">,
+  "function with '%0' attribute should be declared noexcept">,
   InGroup<PerfConstraintImpliesNoexcept>;
 
 // FIXME: It would be nice if we could provide fuller template expansion notes.
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e5f9c3c7dbb09b..370985f7ce4bba 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -15053,8 +15053,8 @@ class Sema final : public SemaBase {
 
     FunctionEffect::Kind EffectKind;
     Kind DiffKind;
-    FunctionEffectWithCondition Old; // invalid when Added.
-    FunctionEffectWithCondition New; // invalid when Removed.
+    FunctionEffectWithCondition Old; // invalid when Kind is Added.
+    FunctionEffectWithCondition New; // invalid when Kind is Removed.
 
     StringRef effectName() const {
       if (Old.Effect.kind() != FunctionEffect::Kind::None)
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 67032b684f4df0..46369b39cc4559 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -967,7 +967,7 @@ class ASTReader
   llvm::SmallSetVector<GlobalDeclID, 4> DeclsToCheckForDeferredDiags;
 
   /// The IDs of all decls with function effects to be checked.
-  SmallVector<GlobalDeclID, 0> DeclsWithEffectsToVerify;
+  SmallVector<GlobalDeclID> DeclsWithEffectsToVerify;
 
 private:
   struct ImportedSubmodule {
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index 9c50b6b6087fa4..05b5e2c9b494c9 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5290,21 +5290,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 << "}";
 }
 
@@ -5314,14 +5317,7 @@ LLVM_DUMP_METHOD void FunctionEffectSet::dump(llvm::raw_ostream &OS) const {
 
 LLVM_DUMP_METHOD void FunctionEffectKindSet::dump(llvm::raw_ostream &OS) const {
   OS << "Effects{";
-  bool First = true;
-  for (const auto &Effect : *this) {
-    if (!First)
-      OS << ", ";
-    else
-      First = false;
-    OS << Effect.name();
-  }
+  llvm::interleaveComma(*this, OS);
   OS << "}";
 }
 
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 423ea54ec56313..c1df87bcacea01 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -90,10 +90,9 @@ struct Violation {
   Violation(FunctionEffect Effect, ViolationID ID, ViolationSite VS,
             SourceLocation Loc, const Decl *Callee = nullptr,
             std::optional<FunctionEffect> CalleeEffect = std::nullopt)
-      : Effect(Effect), ID(ID), Site(VS), Loc(Loc), Callee(Callee) {
-    if (CalleeEffect)
-      CalleeEffectPreventingInference = *CalleeEffect;
-  }
+      : Effect(Effect), CalleeEffectPreventingInference(
+                            CalleeEffect.value_or(FunctionEffect())),
+        ID(ID), Site(VS), Loc(Loc), Callee(Callee) {}
 
   unsigned diagnosticSelectIndex() const {
     return unsigned(ID) - unsigned(ViolationID::BaseDiagnosticIndex);
@@ -102,7 +101,7 @@ struct Violation {
 
 enum class SpecialFuncType : uint8_t { None, OperatorNew, OperatorDelete };
 enum class CallableType : uint8_t {
-  // Unknown: probably function pointer
+  // Unknown: probably function pointer.
   Unknown,
   Function,
   Virtual,
@@ -847,14 +846,14 @@ class Analyzer {
       case ViolationID::AccessesThreadLocalVariable:
       case ViolationID::AccessesObjCMethodOrProperty:
         S.Diag(Viol1.Loc, diag::warn_func_effect_violation)
-            << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+            << SiteDescIndex(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)
-            << effectName << SiteDescIndex(CInfo.CDecl, &Viol1);
+            << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName;
         MaybeAddSiteContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
         break;
@@ -864,7 +863,7 @@ class Analyzer {
         std::string CalleeName = CalleeInfo.name(S);
 
         S.Diag(Viol1.Loc, diag::warn_func_effect_calls_func_without_effect)
-            << effectName << SiteDescIndex(CInfo.CDecl, &Viol1)
+            << SiteDescIndex(CInfo.CDecl, &Viol1) << effectName
             << SiteDescIndex(CalleeInfo.CDecl, nullptr) << CalleeName;
         MaybeAddSiteContext(CInfo.CDecl, Viol1);
         MaybeAddTemplateNote(CInfo.CDecl);
@@ -1362,7 +1361,6 @@ void Sema::diagnoseFunctionEffectMergeConflicts(
   }
 }
 
-// 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) {
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 306bbc097a7df4..2a87631a603ab2 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8397,19 +8397,14 @@ 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();
-  }
+  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());
+  }
+  DeclsWithEffectsToVerify.clear();
 
   SemaObj->OpenCLFeatures = OpenCLExtensions;
 
diff --git a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
index d2c25da462c404..b81d3d475e47fc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -4,7 +4,7 @@
 
 // These need '-fms-extensions' (and maybe '-fdeclspec')
 void f1() [[clang::nonblocking]] {
-    __try {} __except (1) {} // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+    __try {} __except (1) {} // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
 }
 
 struct S {
@@ -20,7 +20,7 @@ struct S {
 
 void f2() [[clang::nonblocking]] {
     S a;
-    a.x; // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'S::get_x'}}
+    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.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 2890199c609106..9ae30a39ff772c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -9,21 +9,21 @@
 
 void nb1() [[clang::nonblocking]]
 {
-	int *pInt = new int; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
-	delete pInt; // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+	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 {{'nonblocking' function must not have static local variables}}
+	static int global; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
 }
 
 void nb3() [[clang::nonblocking]]
 {
 	try {
-		throw 42; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+		throw 42; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
 	}
-	catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
 	}
 }
 
@@ -33,7 +33,7 @@ void nb4_not_inline(); // expected-note {{declaration cannot be inferred 'nonblo
 void nb4() [[clang::nonblocking]]
 {
 	nb4_inline(); // OK
-	nb4_not_inline(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	nb4_not_inline(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
 }
 
 
@@ -44,7 +44,7 @@ struct HasVirtual {
 void nb5() [[clang::nonblocking]]
 {
  	HasVirtual hv;
- 	hv.unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+ 	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}}
@@ -55,21 +55,21 @@ void nb6_transitively_unsafe()
 
 void nb6() [[clang::nonblocking]]
 {
-	nb6_transitively_unsafe(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	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 {{'nonblocking' function must not use thread-local variables}}
+	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 {{'nonblocking' block must not throw or catch exceptions}}
+		throw 42; // expected-warning {{block with 'nonblocking' attribute must not throw or catch exceptions}}
 	};
 }
 
@@ -77,7 +77,7 @@ void nb8()
 {
 	// Make sure we verify lambdas
 	auto lambda = []() [[clang::nonblocking]] {
-		throw 42; // expected-warning {{'nonblocking' lambda must not throw or catch exceptions}}
+		throw 42; // expected-warning {{lambda with 'nonblocking' attribute must not throw or catch exceptions}}
 	};
 }
 
@@ -92,7 +92,7 @@ void nb8a() [[clang::nonblocking]]
 void nb8b() [[clang::nonblocking]]
 {
 	// An unsafe lambda capture makes the outer function unsafe.
-	auto unsafeCapture = [foo = new int]() { // expected-warning {{'nonblocking' function must not allocate or deallocate memory}}
+	auto unsafeCapture = [foo = new int]() { // expected-warning {{function with 'nonblocking' attribute must not allocate or deallocate memory}}
 		delete foo;
 	};
 }
@@ -109,7 +109,7 @@ void nb8c()
 	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}}
+			return x + y; // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
 		}
 		static T add_implicit(T x, T y)
 		{
@@ -140,7 +140,7 @@ void nb9() [[clang::nonblocking]]
 	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}} \
+	Adder<Stringy2>::add_implicit({}, {}); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}} \
 		expected-note {{in template expansion here}}
 }
 
@@ -149,7 +149,7 @@ void nb10(
 	void (*fp2)() [[clang::nonblocking]]
 	) [[clang::nonblocking]]
 {
-	fp1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	fp1(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function}}
 	fp2();
 }
 
@@ -166,19 +166,19 @@ struct ComputedNB {
 
 void nb11() [[clang::nonblocking]]
 {
-	nb11_no_inference_1(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
-	nb11_no_inference_2(); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function}}
+	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 {{'nonblocking' function must not call non-'nonblocking' function}}
+	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 {{'nonblocking' function must not have static local variables}}
+	static int x; // expected-warning {{function with 'nonblocking' attribute must not have static local variables}}
 }
 void nb12() [[clang::nonblocking]];
 void nb13() [[clang::nonblocking]] { nb12(); }
@@ -212,18 +212,18 @@ void nb18(void (^block)() [[clang::nonblocking]]) [[clang::nonblocking]]
 // Builtin functions
 void nb19() [[clang::nonblocking]] {
 	__builtin_assume(1);
-	void *ptr = __builtin_malloc(1); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_malloc'}}
-	__builtin_free(ptr); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_free'}}
+	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 {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_new'}}
-	__builtin_operator_delete(p2); // expected-warning {{'nonblocking' function must not call non-'nonblocking' function '__builtin_operator_delete'}}
+	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 {{'nonblocking' function must not call non-'nonblocking' function 'catches'}}
+	catches(); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'catches'}}
 }
 
 struct S {
@@ -245,14 +245,14 @@ struct B {
 };
 
 void f() [[clang::nonblocking]] {
-    S s1(3);   // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
-    S s2(3.0); // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'S::S'}}
-    A a;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'A::A'}}
-    B b;       // expected-warning {{'nonblocking' function must not call non-'nonblocking' constructor 'B::B'}}
+    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 {{'nonblocking' constructor's member initializer must not call non-'nonblocking' function 'badi'}}
+	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
 };
@@ -262,16 +262,16 @@ int badForDefaultArg(); // expected-note {{declaration cannot be inferred 'nonbl
                            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 {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}} \
+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 {{'nonblocking' function must not call non-'nonblocking' function 'hasDefaultArg'}}
+	                    expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'hasDefaultArg'}}
 }
 
-void nb22(int param = badForDefaultArg()) [[clang::nonblocking]] { // expected-warning {{'nonblocking' function must not call non-'nonblocking' function 'badForDefaultArg'}}
+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.
@@ -285,13 +285,13 @@ struct Unsafe {
   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 {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
+  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 {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
-  DerivedFromUnsafe(int x) [[clang::nonblocking]] : Unsafe(x) {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'Unsafe::Unsafe'}}
-  ~DerivedFromUnsafe() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor 'Unsafe::~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'}}
 };
 
 // Virtual inheritance
@@ -303,9 +303,9 @@ struct VBase {
 };
 
 struct VDerived : virtual VBase {
-  VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' constructor must not call non-'nonblocking' constructor 'VBase::VBase'}}
+  VDerived() [[clang::nonblocking]] {} // expected-warning {{constructor with 'nonblocking' attribute must not call non-'nonblocking' constructor 'VBase::VBase'}}
 
-  ~VDerived() [[clang::nonblocking]] {} // expected-warning {{'nonblocking' destructor must not call non-'nonblocking' destructor '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.
@@ -329,6 +329,6 @@ void g() [[clang::nonblocking]] {
 // --- nonblocking implies noexcept ---
 #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"
 
-void needs_noexcept() [[clang::nonblocking]] // expected-warning {{'nonblocking' function should be declared noexcept}}
+void needs_noexcept() [[clang::nonblocking]] // expected-warning {{function with 'nonblocking' attribute should be declared noexcept}}
 {
 }

>From ba57bfbde08716c353110516bc0c9b78a7c2c95c Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 4 Sep 2024 16:43:35 -0700
Subject: [PATCH 42/48] Fix: Don't try to follow a deleted destructor. (Happens
 with a std::optional<T>, which contains a union with a deleted destructor.
 Exposed by a recent fix which wasn't following the complete chain of
 destructors.)

---
 clang/lib/Sema/SemaFunctionEffects.cpp        |  3 ++-
 .../Sema/attr-nonblocking-constraints.cpp     | 23 +++++++++++++++++++
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index c1df87bcacea01..36bc20d52aeeab 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1077,7 +1077,8 @@ class Analyzer {
 
       if (Ty->isRecordType()) {
         if (const CXXRecordDecl *Class = Ty->getAsCXXRecordDecl()) {
-          if (CXXDestructorDecl *Dtor = Class->getDestructor()) {
+          if (CXXDestructorDecl *Dtor = Class->getDestructor();
+              Dtor && !Dtor->isDeleted()) {
             CallableInfo CI(*Dtor);
             followCall(CI, CallSite);
           }
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 9ae30a39ff772c..9838d842abcafc 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -294,6 +294,29 @@ struct DerivedFromUnsafe : public 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;

>From 54feb05e7e697de5a98089693d1a6b3cabb0c37f Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Wed, 4 Sep 2024 17:33:42 -0700
Subject: [PATCH 43/48] Fix test broken by rewording of warnings.

---
 clang/test/SemaObjCXX/attr-nonblocking-constraints.mm | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
index 262d647142d548..0ff72d3d6d19de 100644
--- a/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
+++ b/clang/test/SemaObjCXX/attr-nonblocking-constraints.mm
@@ -8,19 +8,19 @@ - (void)method;
 @end
 
 void nb1(OCClass *oc) [[clang::nonblocking]] {
-	[oc method]; // expected-warning {{'nonblocking' function must not access ObjC methods or properties}}
+	[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 {{'nonblocking' function must not call non-'nonblocking' function 'nb2'}}
+	nb2(oc); // expected-warning {{function with 'nonblocking' attribute must not call non-'nonblocking' function 'nb2'}}
 }
 
 void nb4() [[clang::nonblocking]] {
 	@try {
-		@throw @"foo"; // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+		@throw @"foo"; // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
 	}
-	@catch (...) { // expected-warning {{'nonblocking' function must not throw or catch exceptions}}
+	@catch (...) { // expected-warning {{function with 'nonblocking' attribute must not throw or catch exceptions}}
 	}
 }

>From 93cb74ea642cc231d551ed715e758fe63bd42707 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 5 Sep 2024 06:55:26 -0700
Subject: [PATCH 44/48] Fix: need to ignore concept requirements.

---
 clang/lib/Sema/SemaFunctionEffects.cpp         |  5 +++++
 .../test/Sema/attr-nonblocking-constraints.cpp | 18 ++++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 36bc20d52aeeab..b65281ca9df2f7 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1289,6 +1289,11 @@ class Analyzer {
     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;
+    }
   };
 };
 
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 9838d842abcafc..2a491a16dc723c 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -348,6 +348,24 @@ void g() [[clang::nonblocking]] {
 #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);
+}
+
 
 // --- nonblocking implies noexcept ---
 #pragma clang diagnostic warning "-Wperf-constraint-implies-noexcept"

>From d583c85fdd19028075b103481d2376c1b2ee6505 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Thu, 5 Sep 2024 07:12:22 -0700
Subject: [PATCH 45/48] clang-format

---
 clang/lib/Sema/SemaFunctionEffects.cpp | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index b65281ca9df2f7..cc140386ed26f9 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -1291,9 +1291,7 @@ class Analyzer {
     bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) { return true; }
 
     // Skip concept requirements since they don't generate code.
-    bool TraverseConceptRequirement(concepts::Requirement *R) {
-      return true;
-    }
+    bool TraverseConceptRequirement(concepts::Requirement *R) { return true; }
   };
 };
 

>From cda1a9c520f01a9d51f782fb5a589c8f7686cd89 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 7 Sep 2024 06:15:39 -0700
Subject: [PATCH 46/48] Make the FunctionEffects and
 PerfConstraintImpliesNoexcept diagnostics disabled by default, but enabled
 with -Wall.

---
 clang/include/clang/Basic/DiagnosticGroups.td    | 13 +++++++------
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 16 ++++++++--------
 .../Sema/attr-nonblocking-constraints-ms.cpp     |  1 +
 clang/test/Sema/attr-nonblocking-constraints.cpp |  1 +
 clang/test/Sema/attr-nonblocking-sema.cpp        |  2 ++
 5 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 55d9442a939dae..5f79d2dad8cfd7 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1128,12 +1128,18 @@ def ThreadSafetyBeta : DiagGroup<"thread-safety-beta">;
 // Uniqueness Analysis warnings
 def Consumed       : DiagGroup<"consumed">;
 
+// 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">;
+
 // Note that putting warnings in -Wall will not disable them by default. If a
 // warning should be active _only_ when -Wall is passed in, mark it as
 // DefaultIgnore in addition to putting it here.
 def All : DiagGroup<"all", [Most, Parentheses, Switch, SwitchBool,
                             MisleadingIndentation, PackedNonPod,
-                            VLACxxExtension]>;
+                            VLACxxExtension, FunctionEffects,
+                            PerfConstraintImpliesNoexcept]>;
 
 // Warnings that should be in clang-cl /w4.
 def : DiagGroup<"CL4", [All, Extra]>;
@@ -1554,11 +1560,6 @@ def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
 def UnsafeBufferUsageInContainer : DiagGroup<"unsafe-buffer-usage-in-container">;
 def UnsafeBufferUsage : DiagGroup<"unsafe-buffer-usage", [UnsafeBufferUsageInContainer]>;
 
-// Warnings and notes related to the function effects system underlying
-// the 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 efdfd53616cc9d..9f5e8aa5f6dc8b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10955,7 +10955,7 @@ def warn_func_effect_violation : Warning<
   "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>;
+  InGroup<FunctionEffects>, DefaultIgnore;
 def note_func_effect_violation : Note<
   "%select{function|constructor|destructor|lambda|block|member initializer}0 "
   "cannot be inferred '%1' because it "
@@ -10968,7 +10968,7 @@ def warn_func_effect_calls_func_without_effect : Warning<
   "must not call non-'%1' "
   "%select{function|constructor|destructor|lambda|block}2 "
   "'%3'">,
-  InGroup<FunctionEffects>;
+  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' "
@@ -10978,7 +10978,7 @@ 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-'%0' expression">,
-  InGroup<FunctionEffects>;
+  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<
@@ -10988,7 +10988,7 @@ def note_func_effect_call_indirect : Note<
   "%select{virtual method|function pointer}0 cannot be inferred '%1'">;
 def warn_perf_constraint_implies_noexcept : Warning<
   "function with '%0' attribute should be declared noexcept">,
-  InGroup<PerfConstraintImpliesNoexcept>;
+  InGroup<PerfConstraintImpliesNoexcept>, DefaultIgnore;
 
 // FIXME: It would be nice if we could provide fuller template expansion notes.
 def note_func_effect_from_template : Note<
@@ -11001,16 +11001,16 @@ def note_in_evaluating_default_argument : Note<
 // 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/test/Sema/attr-nonblocking-constraints-ms.cpp b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
index b81d3d475e47fc..5bf38dceedaed7 100644
--- a/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints-ms.cpp
@@ -1,5 +1,6 @@
 // RUN: %clang_cc1 -triple=x86_64-pc-win32 -fsyntax-only -fblocks -fcxx-exceptions -fms-extensions -verify %s
 
+#pragma clang diagnostic warning "-Wfunction-effects"
 #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
 
 // These need '-fms-extensions' (and maybe '-fdeclspec')
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index 2a491a16dc723c..c3bf05169e3247 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -2,6 +2,7 @@
 // These are in a separate file because errors (e.g. incompatible attributes) currently prevent
 // the FXAnalysis pass from running at all.
 
+#pragma clang diagnostic warning "-Wfunction-effects"
 // This diagnostic is re-enabled and exercised in isolation later in this file.
 #pragma clang diagnostic ignored "-Wperf-constraint-implies-noexcept"
 
diff --git a/clang/test/Sema/attr-nonblocking-sema.cpp b/clang/test/Sema/attr-nonblocking-sema.cpp
index 38bf2ac8f8a4cc..157fba79075fae 100644
--- a/clang/test/Sema/attr-nonblocking-sema.cpp
+++ b/clang/test/Sema/attr-nonblocking-sema.cpp
@@ -1,6 +1,8 @@
 // RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s
 // RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s
 
+#pragma clang diagnostic warning "-Wfunction-effects"
+
 #if !__has_attribute(nonblocking)
 #error "the 'nonblocking' attribute is not available"
 #endif

>From 9c971c6e639039833d88446c5cc68064419fbcaa Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Sat, 7 Sep 2024 06:28:02 -0700
Subject: [PATCH 47/48] Add a release note.

---
 clang/docs/ReleaseNotes.rst | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index ffdd063ec99037..c8462acff45fa9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -387,6 +387,14 @@ 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 (the ``nonblocking`` and ``nonallocating`` "performance constraint" attributes)
+  are now verified. Functions with the attribute are checked for language constructs which allocate
+  memory or block, such as using exceptions, static local or thread-local variables, or Objective-C
+  methods or properties. Implicit and explicit function calls are also checked. Called functions
+  must either have the required attribute, or have inline implementations which can be themselves
+  verified. The warnings are controlled by ``-Wfunction-effects``, which is now 
+  disabled by default.
+
 Crash and bug fixes
 ^^^^^^^^^^^^^^^^^^^
 

>From 9bfbe12976b013bcc0c11343b699f1e2aeaed6b0 Mon Sep 17 00:00:00 2001
From: Doug Wyatt <dwyatt at apple.com>
Date: Mon, 9 Sep 2024 07:57:53 -0700
Subject: [PATCH 48/48] Fix botched format string in
 warn_func_effect_calls_expr_without_effect

---
 clang/include/clang/Basic/DiagnosticSemaKinds.td | 2 +-
 clang/test/Sema/attr-nonblocking-constraints.cpp | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index ec2906ffe08676..c071991e277539 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -10969,7 +10969,7 @@ def note_func_effect_calls_func_without_effect : Note<
 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-'%0' expression">,
+  "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">;
diff --git a/clang/test/Sema/attr-nonblocking-constraints.cpp b/clang/test/Sema/attr-nonblocking-constraints.cpp
index c3bf05169e3247..f1ac518389150b 100644
--- a/clang/test/Sema/attr-nonblocking-constraints.cpp
+++ b/clang/test/Sema/attr-nonblocking-constraints.cpp
@@ -152,6 +152,9 @@ void nb10(
 {
 	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)



More information about the cfe-commits mailing list