[clang] [clang] add flow-sensitive nullability analysis for C/C++ (PR #189131)

Chad Smith via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 30 22:46:45 PDT 2026


https://github.com/cs01 updated https://github.com/llvm/llvm-project/pull/189131

>From b6e25e5de5d22a9a1fc7de018e9f35ef05ab940c Mon Sep 17 00:00:00 2001
From: Chad Smith <cssmith at fb.com>
Date: Mon, 30 Mar 2026 22:46:28 -0700
Subject: [PATCH] add flow-sensitive nullability analysis for C/C++

Adds a new compile-time analysis that detects null pointer dereferences
using flow-sensitive dataflow analysis on the CFG. The analysis tracks
nullability state through control flow, supporting null checks, early
returns, assertions, ternary operators, loops, and boolean intermediaries.

New flags:
  -fflow-sensitive-nullability    enables the analysis
  -fnullability-default=<mode>    sets default nullability (nullable|nonnull|unspecified)

The analysis follows the same architecture as ThreadSafety and
UninitializedValues: a standalone analysis in lib/Analysis/ invoked
from AnalysisBasedWarnings.cpp, reporting via a handler interface.
---
 .../clang/Analysis/Analyses/FlowNullability.h |   49 +
 clang/include/clang/Basic/DiagnosticGroups.td |    8 +
 .../include/clang/Basic/DiagnosticLexKinds.td |    1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   32 +
 clang/include/clang/Basic/LangOptions.def     |    4 +
 clang/include/clang/Basic/LangOptions.h       |    2 +
 clang/include/clang/Options/Options.td        |   12 +
 clang/include/clang/Sema/Sema.h               |   11 +-
 clang/lib/Analysis/CMakeLists.txt             |    1 +
 clang/lib/Analysis/FlowNullability.cpp        | 1533 +++++++++++++
 clang/lib/Driver/ToolChains/Clang.cpp         |    5 +
 clang/lib/Lex/PPLexerChange.cpp               |    8 +-
 clang/lib/Lex/Pragma.cpp                      |    2 +-
 clang/lib/Sema/AnalysisBasedWarnings.cpp      |   90 +-
 clang/lib/Sema/Sema.cpp                       |   44 +-
 clang/lib/Sema/SemaDecl.cpp                   |   19 +
 clang/lib/Sema/SemaExprCXX.cpp                |    2 +-
 clang/lib/Sema/SemaInit.cpp                   |    5 +-
 clang/lib/Sema/SemaOverload.cpp               |    4 +-
 clang/lib/Sema/SemaType.cpp                   |   72 +-
 clang/test/Driver/nullsafe-flags-negative.c   |   31 +
 clang/test/Driver/nullsafe-flags.c            |    8 +
 clang/test/Sema/flow-nullability-c.c          |  636 ++++++
 .../SemaCXX/flow-nullability-adoption.cpp     |  784 +++++++
 .../SemaCXX/flow-nullability-analysis.cpp     | 1997 +++++++++++++++++
 .../flow-nullability-crubit-regression.cpp    |  591 +++++
 .../SemaCXX/flow-nullability-cxx-features.cpp | 1041 +++++++++
 .../flow-nullability-default-nonnull.cpp      |   48 +
 .../flow-nullability-warning-groups.cpp       |   20 +
 clang/test/SemaObjC/flow-nullability-objc.m   |   26 +
 30 files changed, 7052 insertions(+), 34 deletions(-)
 create mode 100644 clang/include/clang/Analysis/Analyses/FlowNullability.h
 create mode 100644 clang/lib/Analysis/FlowNullability.cpp
 create mode 100644 clang/test/Driver/nullsafe-flags-negative.c
 create mode 100644 clang/test/Driver/nullsafe-flags.c
 create mode 100644 clang/test/Sema/flow-nullability-c.c
 create mode 100644 clang/test/SemaCXX/flow-nullability-adoption.cpp
 create mode 100644 clang/test/SemaCXX/flow-nullability-analysis.cpp
 create mode 100644 clang/test/SemaCXX/flow-nullability-crubit-regression.cpp
 create mode 100644 clang/test/SemaCXX/flow-nullability-cxx-features.cpp
 create mode 100644 clang/test/SemaCXX/flow-nullability-default-nonnull.cpp
 create mode 100644 clang/test/SemaCXX/flow-nullability-warning-groups.cpp
 create mode 100644 clang/test/SemaObjC/flow-nullability-objc.m

diff --git a/clang/include/clang/Analysis/Analyses/FlowNullability.h b/clang/include/clang/Analysis/Analyses/FlowNullability.h
new file mode 100644
index 0000000000000..62aa18359ca2d
--- /dev/null
+++ b/clang/include/clang/Analysis/Analyses/FlowNullability.h
@@ -0,0 +1,49 @@
+//=- FlowNullability.h - Flow-sensitive null dereference checking -*- C++ -*-=//
+//
+// 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 defines APIs for invoking flow-sensitive nullability analysis
+// that detects dereferences of nullable pointers.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_ANALYSES_FLOWNULLABILITY_H
+#define LLVM_CLANG_ANALYSIS_ANALYSES_FLOWNULLABILITY_H
+
+#include "clang/AST/Type.h"
+#include "clang/Basic/Specifiers.h"
+
+namespace clang {
+
+class AnalysisDeclContext;
+class Expr;
+class ParmVarDecl;
+class VarDecl;
+
+class FlowNullabilityHandler {
+public:
+  virtual ~FlowNullabilityHandler();
+  virtual void handleNullableDereference(const Expr *DerefExpr,
+                                         QualType PtrType) = 0;
+  virtual void handleNullableArithmetic(const Expr *ArithExpr,
+                                        QualType PtrType) {}
+  virtual void handleNullableReturn(const Expr *ReturnExpr, QualType ExprType,
+                                    QualType ReturnType) {}
+  virtual void handleNullableAssignment(const Expr *AssignExpr,
+                                        const VarDecl *LHSVar) {}
+  virtual void handleNullableArgument(const Expr *ArgExpr,
+                                      const ParmVarDecl *Param) {}
+};
+
+void runFlowNullabilityAnalysis(AnalysisDeclContext &AC,
+                                FlowNullabilityHandler &Handler,
+                                bool StrictMode,
+                                NullabilityKind DefaultNullability);
+
+} // namespace clang
+
+#endif // LLVM_CLANG_ANALYSIS_ANALYSES_FLOWNULLABILITY_H
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index a8d9745d91083..612d6453c0aa7 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -526,6 +526,14 @@ def CXX26Compat : DiagGroup<"c++2c-compat", [DeleteIncomplete]>;
 
 def ExitTimeDestructors : DiagGroup<"exit-time-destructors">;
 def FlexibleArrayExtensions : DiagGroup<"flexible-array-extensions">;
+def FlowNullableDereference : DiagGroup<"flow-nullable-dereference">;
+def FlowNullableArithmetic : DiagGroup<"flow-nullable-arithmetic">;
+def FlowNullableReturn : DiagGroup<"flow-nullable-return">;
+def FlowNullableAssignment : DiagGroup<"flow-nullable-assignment">;
+def FlowNullableArgument : DiagGroup<"flow-nullable-argument">;
+def FlowNullability : DiagGroup<"flow-nullability", [
+  FlowNullableDereference, FlowNullableArithmetic, FlowNullableReturn,
+  FlowNullableAssignment, FlowNullableArgument]>;
 def FourByteMultiChar : DiagGroup<"four-char-constants">;
 def GlobalConstructors : DiagGroup<"global-constructors"> {
  code Documentation = [{
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 5eceeced311f2..97bdf39930015 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -1055,6 +1055,7 @@ def err_pp_include_in_assume_nonnull : Error<
 def err_pp_eof_in_assume_nonnull : Error<
   "'#pragma clang assume_nonnull' was not ended within this file">;
 
+
 }
 
 let CategoryName = "Dependency Directive Source Scanner Issue" in {
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index db1e3630435d0..bd3f70bcba4fc 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12998,6 +12998,38 @@ def warn_nullability_lost : Warning<
   "implicit conversion from nullable pointer %0 to non-nullable pointer "
   "type %1">,
   InGroup<NullableToNonNullConversion>, DefaultIgnore;
+def warn_flow_nullable_dereference : Warning<
+  "dereference of nullable pointer %0">,
+  InGroup<FlowNullableDereference>;
+def warn_null_init_nonnull : Warning<
+  "null assigned to a variable of nonnull type %0">,
+  InGroup<FlowNullableDereference>;
+def note_nullable_dereference_fix : Note<
+  "add a null check before dereferencing, or annotate as '_Nonnull' if this "
+  "pointer cannot be null">;
+def warn_flow_nullable_arithmetic : Warning<
+  "pointer arithmetic on nullable pointer %0">,
+  InGroup<FlowNullableArithmetic>;
+def note_nullable_arithmetic_fix : Note<
+  "add a null check before performing arithmetic, or annotate as '_Nonnull' "
+  "if this pointer cannot be null">;
+def warn_flow_nullable_return : Warning<
+  "returning nullable pointer from function with nonnull return type">,
+  InGroup<FlowNullableReturn>;
+def note_nullable_return_fix : Note<
+  "add a null check before returning, or change the return type to '_Nullable'">;
+def warn_flow_nullable_assignment : Warning<
+  "assigning nullable pointer to nonnull variable %0">,
+  InGroup<FlowNullableAssignment>;
+def note_nullable_assignment_fix : Note<
+  "add a null check before assigning, or change the variable type to "
+  "'_Nullable'">;
+def warn_flow_nullable_argument : Warning<
+  "passing nullable pointer to nonnull parameter %0">,
+  InGroup<FlowNullableArgument>;
+def note_nullable_argument_fix : Note<
+  "add a null check before the call, or change the parameter type to "
+  "'_Nullable'">;
 def warn_zero_as_null_pointer_constant : Warning<
   "zero as null pointer constant">,
   InGroup<DiagGroup<"zero-as-null-pointer-constant">>, DefaultIgnore;
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index dd4c5a653d38b..49e4684bc86fc 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -141,6 +141,10 @@ LANGOPT(PointerAuthObjcClassROPointers, 1, 0, Benign, "class_ro_t pointer authen
 
 LANGOPT(PointerAuthBlockDescriptorPointers, 1, 0, NotCompatible, "enable signed block descriptors")
 
+// Nullability options
+ENUM_LANGOPT(NullabilityDefault, NullabilityKind, 2, NullabilityKind::Unspecified, NotCompatible, "default nullability for unannotated pointers")
+LANGOPT(FlowSensitiveNullability, 1, 0, NotCompatible, "enable flow-sensitive nullability analysis")
+
 LANGOPT(DoubleSquareBracketAttributes, 1, 0, NotCompatible, "'[[]]' attributes extension for all language standard modes")
 LANGOPT(ExperimentalLateParseAttributes, 1, 0, NotCompatible, "experimental late parsing of attributes")
 
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 64b12b6fd72c7..31d4d80b69e9b 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -20,6 +20,7 @@
 #include "clang/Basic/LangStandard.h"
 #include "clang/Basic/ObjCRuntime.h"
 #include "clang/Basic/Sanitizers.h"
+#include "clang/Basic/Specifiers.h"
 #include "clang/Basic/TargetCXXABI.h"
 #include "clang/Basic/Visibility.h"
 #include "llvm/ADT/FloatingPointMode.h"
@@ -77,6 +78,7 @@ class LangOptionsBase {
   using Visibility = clang::Visibility;
   using RoundingMode = llvm::RoundingMode;
   using CFBranchLabelSchemeKind = clang::CFBranchLabelSchemeKind;
+  using NullabilityKind = clang::NullabilityKind;
 
   /// For ASTs produced with different option value, signifies their level of
   /// compatibility.
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 215d4e885709c..b6cbfe5443ce7 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1641,6 +1641,18 @@ defm apple_pragma_pack : BoolFOption<"apple-pragma-pack",
   PosFlag<SetTrue, [], [ClangOption, CC1Option],
           "Enable Apple gcc-compatible #pragma pack handling">,
   NegFlag<SetFalse>>;
+def fnullability_default_EQ : Joined<["-"], "fnullability-default=">,
+  Group<f_Group>, Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Set default nullability for unannotated pointers (unspecified, nullable, nonnull)">,
+  Values<"unspecified,nullable,nonnull">, NormalizedValuesScope<"clang::NullabilityKind">,
+  NormalizedValues<["Unspecified", "Nullable", "NonNull"]>,
+  MarshallingInfoEnum<LangOpts<"NullabilityDefault">, "Unspecified">;
+
+defm flow_sensitive_nullability : BoolFOption<"flow-sensitive-nullability",
+  LangOpts<"FlowSensitiveNullability">, DefaultFalse,
+  PosFlag<SetTrue, [], [ClangOption, CC1Option],
+          "Enable flow-sensitive nullability analysis">,
+  NegFlag<SetFalse>>;
 defm xl_pragma_pack : BoolFOption<"xl-pragma-pack",
   LangOpts<"XLPragmaPack">, DefaultFalse,
   PosFlag<SetTrue, [], [ClangOption, CC1Option],
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4e6058b8e5f79..7d0c6e6c8ab5c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1186,9 +1186,16 @@ class Sema final : public SemaBase {
   NamedDecl *getCurFunctionOrMethodDecl() const;
 
   /// Warn if we're implicitly casting from a _Nullable pointer type to a
-  /// _Nonnull one.
+  /// _Nonnull one. If \p SrcExpr is provided and flow-sensitive nullability
+  /// is enabled, the warning is suppressed when the expression is provably
+  /// non-null despite its declared type.
   void diagnoseNullableToNonnullConversion(QualType DstType, QualType SrcType,
-                                           SourceLocation Loc);
+                                           SourceLocation Loc,
+                                           Expr *SrcExpr = nullptr);
+
+  /// Check if a function has any nullability annotations on its
+  /// parameters or return type.
+  bool functionHasNullabilityAnnotations(const FunctionDecl *FD) const;
 
   /// Warn when implicitly casting 0 to nullptr.
   void diagnoseZeroToNullptrConversion(CastKind Kind, const Expr *E);
diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt
index c5952dbdad51d..39f6b4fab80c6 100644
--- a/clang/lib/Analysis/CMakeLists.txt
+++ b/clang/lib/Analysis/CMakeLists.txt
@@ -20,6 +20,7 @@ add_clang_library(clangAnalysis
   CodeInjector.cpp
   Dominators.cpp
   ExprMutationAnalyzer.cpp
+  FlowNullability.cpp
   FixitUtil.cpp
   IntervalPartition.cpp
   IssueHash.cpp
diff --git a/clang/lib/Analysis/FlowNullability.cpp b/clang/lib/Analysis/FlowNullability.cpp
new file mode 100644
index 0000000000000..0c0b5984835b5
--- /dev/null
+++ b/clang/lib/Analysis/FlowNullability.cpp
@@ -0,0 +1,1533 @@
+//===- FlowNullability.cpp - Flow-sensitive null dereference checking -----===//
+//
+// 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 a CFG-based forward dataflow analysis that detects
+// dereferences of nullable pointers, tracking nullability narrowing through
+// control flow (null checks, early returns, assertions, etc.).
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Analyses/FlowNullability.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/OperationKinds.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/Type.h"
+#include "clang/Analysis/AnalysisDeclContext.h"
+#include "clang/Analysis/CFG.h"
+#include "clang/Analysis/FlowSensitive/DataflowWorklist.h"
+#include "clang/Basic/Builtins.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/raw_ostream.h"
+#include <optional>
+#include <utility>
+
+#define DEBUG_TYPE "flow-nullability"
+
+STATISTIC(NumFunctionsAnalyzed, "Number of functions analyzed");
+STATISTIC(NumBlocksProcessed, "Number of CFG blocks processed");
+STATISTIC(NumFixpointIterations, "Number of fixpoint iterations");
+STATISTIC(NumDereferenceWarnings, "Number of nullable dereference warnings");
+STATISTIC(NumArithmeticWarnings, "Number of nullable arithmetic warnings");
+STATISTIC(NumReturnWarnings, "Number of nullable return warnings");
+STATISTIC(NumAssignmentWarnings, "Number of nullable assignment warnings");
+STATISTIC(NumArgumentWarnings, "Number of nullable argument warnings");
+
+using namespace clang;
+
+FlowNullabilityHandler::~FlowNullabilityHandler() = default;
+
+namespace {
+
+using MemberKey = std::pair<const VarDecl *, const FieldDecl *>;
+
+/// Per-block dataflow lattice tracking which pointers are narrowed (known
+/// non-null) or nullable. Uses DenseSet for simplicity; a BitVector keyed
+/// by variable index would reduce fixpoint comparison cost for functions
+/// with many tracked pointers, but profiling hasn't shown this to be a
+/// bottleneck in practice (the perf stress test passes comfortably).
+struct NullState {
+  // Pointers proven non-null by control flow (null checks, nonnull init, etc.).
+  // A variable should not be in both NarrowedVars and NullableVars — narrowing
+  // is always erased before re-evaluating nullability on reassignment.
+  llvm::DenseSet<const VarDecl *> NarrowedVars;
+  llvm::DenseSet<MemberKey> NarrowedMembers;
+  llvm::DenseSet<const FieldDecl *> NarrowedThisMembers;
+  llvm::DenseSet<const VarDecl *> NullableVars;
+  // Smart pointer this-members known to be nullable in the current function
+  // (e.g., after reset() or std::move()). Used to avoid false positives on
+  // member smart pointers that are always initialized in the constructor.
+  llvm::DenseSet<const FieldDecl *> NullableThisMembers;
+
+  // Maps bool variables to the null-check they capture.
+  // E.g., bool valid = (p != nullptr) → {valid → (p, false)}
+  // The bool is true when the bool being true means the pointer IS null.
+  using BoolGuardMap =
+      llvm::DenseMap<const VarDecl *, std::pair<const VarDecl *, bool>>;
+  BoolGuardMap BoolGuards;
+
+  // Simple pointer alias tracking: y = x stores {y → x}, meaning y holds
+  // the same pointer value as x. When either is narrowed by a branch
+  // condition, the other is narrowed too (at the edge-state level).
+  // Depth-1 only: if z = y and y → x, we store z → x (canonical target).
+  using AliasMap = llvm::DenseMap<const VarDecl *, const VarDecl *>;
+  AliasMap Aliases;
+
+  bool operator==(const NullState &Other) const {
+    return NarrowedVars == Other.NarrowedVars &&
+           NarrowedMembers == Other.NarrowedMembers &&
+           NarrowedThisMembers == Other.NarrowedThisMembers &&
+           NullableVars == Other.NullableVars &&
+           NullableThisMembers == Other.NullableThisMembers &&
+           BoolGuards == Other.BoolGuards && Aliases == Other.Aliases;
+  }
+  bool operator!=(const NullState &Other) const { return !(*this == Other); }
+};
+
+static NullState join(const NullState &A, const NullState &B) {
+  NullState Result;
+  // Narrowed = intersection: only narrowed if ALL paths agree.
+  for (const auto *VD : A.NarrowedVars)
+    if (B.NarrowedVars.contains(VD))
+      Result.NarrowedVars.insert(VD);
+  for (const auto &MK : A.NarrowedMembers)
+    if (B.NarrowedMembers.contains(MK))
+      Result.NarrowedMembers.insert(MK);
+  for (const auto *FD : A.NarrowedThisMembers)
+    if (B.NarrowedThisMembers.contains(FD))
+      Result.NarrowedThisMembers.insert(FD);
+  // Nullable = union: if nullable on either path, it's nullable.
+  for (const auto *VD : A.NullableVars)
+    Result.NullableVars.insert(VD);
+  for (const auto *VD : B.NullableVars)
+    Result.NullableVars.insert(VD);
+  for (const auto *FD : A.NullableThisMembers)
+    Result.NullableThisMembers.insert(FD);
+  for (const auto *FD : B.NullableThisMembers)
+    Result.NullableThisMembers.insert(FD);
+  // BoolGuards: keep only entries present in both with the same mapping.
+  for (const auto &[BoolVD, GuardInfo] : A.BoolGuards) {
+    auto It = B.BoolGuards.find(BoolVD);
+    if (It != B.BoolGuards.end() && It->second == GuardInfo)
+      Result.BoolGuards[BoolVD] = GuardInfo;
+  }
+  // Aliases: intersection with value equality (same as BoolGuards).
+  for (const auto &[AliasVD, TargetVD] : A.Aliases) {
+    auto It = B.Aliases.find(AliasVD);
+    if (It != B.Aliases.end() && It->second == TargetVD)
+      Result.Aliases[AliasVD] = TargetVD;
+  }
+  // Invariant: a variable should not be both narrowed and nullable.
+  // Narrowed takes priority (proven non-null on all paths), so remove
+  // stale nullable entries that conflict. This prevents NullableVars
+  // from accumulating stale entries across fixpoint iterations.
+  for (const auto *VD : Result.NarrowedVars)
+    Result.NullableVars.erase(VD);
+  LLVM_DEBUG({
+    llvm::dbgs() << "  join: narrowed=" << Result.NarrowedVars.size()
+                 << " nullable=" << Result.NullableVars.size()
+                 << " members=" << Result.NarrowedMembers.size()
+                 << " aliases=" << Result.Aliases.size() << "\n";
+  });
+  return Result;
+}
+
+static const Expr *unwrapBuiltinExpect(const Expr *E) {
+  if (const auto *CE = dyn_cast<CallExpr>(E)) {
+    if (const auto *Callee = CE->getDirectCallee()) {
+      unsigned BuiltinID = Callee->getBuiltinID();
+      if ((BuiltinID == Builtin::BI__builtin_expect ||
+           BuiltinID == Builtin::BI__builtin_expect_with_probability) &&
+          CE->getNumArgs() >= 1) {
+        return CE->getArg(0)->IgnoreParenImpCasts();
+      }
+    }
+  }
+  return E;
+}
+
+/// Extract the rightmost leaf of a && / || chain.
+/// The CFG decomposes `a && b && c` into separate blocks — each operand
+/// becomes its own block's terminator condition. So for `if (a && b && c)`,
+/// the block evaluating 'c' has the full `a && b && c` as its terminator,
+/// but 'a' and 'b' are handled by their own blocks. We recurse into the
+/// RHS to find the leaf that's actually being evaluated in this block.
+static const Expr *getTerminalCondition(const Expr *E) {
+  E = E->IgnoreParenImpCasts();
+  if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
+    if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr)
+      return getTerminalCondition(BO->getRHS());
+  }
+  return E;
+}
+
+static bool isNullableType(QualType Ty, bool StrictMode,
+                           NullabilityKind Default) {
+  std::optional<NullabilityKind> Nullability = Ty->getNullability();
+  if (!Nullability)
+    return false;
+  // Explicit _Nullable always triggers.
+  if (*Nullability == NullabilityKind::Nullable)
+    return true;
+  // _Null_unspecified means "not explicitly annotated — use the default".
+  // Under -fnullability-default=nullable, treat as nullable.
+  // Under -fnullability-default=nonnull, treat as nonnull (no warning).
+  if (*Nullability == NullabilityKind::Unspecified &&
+      Default == NullabilityKind::Nullable)
+    return true;
+  return false;
+}
+
+static bool isNonnullType(QualType Ty) {
+  std::optional<NullabilityKind> Nullability = Ty->getNullability();
+  return Nullability && *Nullability == NullabilityKind::NonNull;
+}
+
+/// Check if a type is std::unique_ptr, std::shared_ptr, or std::weak_ptr.
+/// Uses getAsCXXRecordDecl() which operates on the canonical type, so
+/// type aliases (using/typedef) are handled. Does not match non-std
+/// smart pointers (e.g. boost::shared_ptr).
+static bool isSmartPointerType(QualType Ty) {
+  const auto *RD = Ty->getAsCXXRecordDecl();
+  if (!RD)
+    return false;
+  const auto *DC = RD->getDeclContext();
+  if (!DC || !DC->isStdNamespace())
+    return false;
+  StringRef Name = RD->getName();
+  return Name == "unique_ptr" || Name == "shared_ptr" || Name == "weak_ptr";
+}
+
+/// Check if a smart pointer expression (the implicit object of operator->)
+/// is narrowed in the current state.
+static bool isSmartPointerNarrowed(const Expr *E, const NullState &State) {
+  E = E->IgnoreParenImpCasts();
+  if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+    if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+      return State.NarrowedVars.contains(VD);
+  } else if (const auto *ME = dyn_cast<MemberExpr>(E)) {
+    if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+      const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+      if (isa<CXXThisExpr>(Base))
+        return State.NarrowedThisMembers.contains(FD);
+      if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(Base))
+        if (const auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl()))
+          return State.NarrowedMembers.contains({BaseVD, FD});
+    }
+  }
+  return false;
+}
+
+/// Strip implicit wrappers that real standard library headers introduce
+/// around expressions (ExprWithCleanups, CXXBindTemporaryExpr,
+/// MaterializeTemporaryExpr) plus the usual parens and implicit casts.
+/// Test mocks don't produce these wrappers, but real <memory> does.
+static const Expr *unwrapImplicitWrappers(const Expr *E) {
+  while (true) {
+    E = E->IgnoreParenImpCasts();
+    if (const auto *EWC = dyn_cast<ExprWithCleanups>(E))
+      E = EWC->getSubExpr();
+    else if (const auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
+      E = BTE->getSubExpr();
+    else if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(E))
+      E = MTE->getSubExpr();
+    else
+      break;
+  }
+  return E;
+}
+
+/// Check if a callee is std::make_unique or std::make_shared.
+static bool isMakeSmartPtrCall(const Expr *E) {
+  E = unwrapImplicitWrappers(E);
+  if (const auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+    if (CE->getNumArgs() == 1)
+      return isMakeSmartPtrCall(CE->getArg(0));
+  }
+  if (const auto *CE = dyn_cast<CallExpr>(E)) {
+    if (const auto *Callee = CE->getDirectCallee()) {
+      const auto *DC = Callee->getDeclContext();
+      if (DC && DC->isStdNamespace() && Callee->getDeclName().isIdentifier()) {
+        StringRef Name = Callee->getName();
+        return Name == "make_unique" || Name == "make_shared";
+      }
+    }
+  }
+  return false;
+}
+
+/// Get the VarDecl from a smart pointer expression, if it's a simple
+/// DeclRefExpr to a VarDecl.
+static const VarDecl *getSmartPtrVarDecl(const Expr *E) {
+  E = E->IgnoreParenImpCasts();
+  if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
+    if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+      if (isSmartPointerType(VD->getType()))
+        return VD;
+  return nullptr;
+}
+
+/// Get the FieldDecl from a smart pointer this->member expression.
+static const FieldDecl *getSmartPtrThisMemberDecl(const Expr *E) {
+  E = E->IgnoreParenImpCasts();
+  if (const auto *ME = dyn_cast<MemberExpr>(E)) {
+    const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+    if (isa<CXXThisExpr>(Base))
+      if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
+        if (isSmartPointerType(FD->getType()))
+          return FD;
+  }
+  return nullptr;
+}
+
+struct ConditionResult {
+  const VarDecl *VD = nullptr;
+  const FieldDecl *FD = nullptr;
+  bool IsThisMember = false;
+  bool Negated = false;
+};
+
+// Forward declaration — decomposeAnd calls analyzeCondition on leaves.
+static void
+analyzeCondition(const Expr *Cond, ASTContext &Ctx,
+                 SmallVectorImpl<ConditionResult> &Results,
+                 const NullState::BoolGuardMap *BoolGuards = nullptr);
+
+/// Recursively flatten a chain of && operators and analyze each leaf.
+/// Used by analyzeCondition to handle !(A && B && C).
+static void decomposeAnd(const Expr *E, ASTContext &Ctx,
+                         SmallVectorImpl<ConditionResult> &Results,
+                         const NullState::BoolGuardMap *BoolGuards) {
+  E = E->IgnoreParenImpCasts();
+  if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
+    if (BO->getOpcode() == BO_LAnd) {
+      decomposeAnd(BO->getLHS(), Ctx, Results, BoolGuards);
+      decomposeAnd(BO->getRHS(), Ctx, Results, BoolGuards);
+      return;
+    }
+  }
+  analyzeCondition(E, Ctx, Results, BoolGuards);
+}
+
+/// Analyze a branch condition to extract pointer null-check information.
+///
+/// Note: We decompose && (via decomposeAnd) but intentionally do NOT
+/// decompose ||. For || the CFG already splits each operand into its own
+/// block, so narrowing on the true-edge of individual operands is handled
+/// naturally. Decomposing || on the false-edge (where all operands are
+/// false) would be possible but adds complexity for limited practical gain
+/// — most real null-checks use && or standalone conditions.
+static void analyzeCondition(const Expr *Cond, ASTContext &Ctx,
+                             SmallVectorImpl<ConditionResult> &Results,
+                             const NullState::BoolGuardMap *BoolGuards) {
+  if (!Cond)
+    return;
+
+  const Expr *E = Cond->IgnoreParenImpCasts();
+  E = unwrapBuiltinExpect(E);
+
+  bool Negated = false;
+  while (auto *UO = dyn_cast<UnaryOperator>(E)) {
+    if (UO->getOpcode() != UO_LNot)
+      break;
+    Negated = !Negated;
+    E = UO->getSubExpr()->IgnoreParenImpCasts();
+  }
+
+  // !(A && B): the CFG merges the && operand paths before the if-decision,
+  // so individual narrowing from the && blocks is lost at the merge.
+  // Recursively decompose the && to narrow ALL operands on the false edge
+  // (where && was true → all operands are true → all pointers non-null).
+  if (Negated) {
+    if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
+      if (BO->getOpcode() == BO_LAnd) {
+        // Flatten nested && and analyze each leaf
+        decomposeAnd(BO, Ctx, Results, BoolGuards);
+        // Keep only sub-conditions where the pointer is non-null when the
+        // sub-condition is true (Negated=false). Flip to Negated=true so
+        // narrowing lands on the false edge of the outer !.
+        llvm::erase_if(Results,
+                       [](const ConditionResult &CR) { return CR.Negated; });
+        for (auto &CR : Results)
+          CR.Negated = true;
+        return;
+      }
+    }
+  }
+
+  if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
+    if (BO->getOpcode() == BO_NE || BO->getOpcode() == BO_EQ) {
+      const Expr *LHS = BO->getLHS()->IgnoreParenImpCasts();
+      const Expr *RHS = BO->getRHS()->IgnoreParenImpCasts();
+
+      bool LHSIsNull =
+          LHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull);
+      bool RHSIsNull =
+          RHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull);
+
+      if (LHSIsNull || RHSIsNull) {
+        const Expr *PtrExpr = LHSIsNull ? RHS : LHS;
+        bool EqNegated = Negated;
+        if (BO->getOpcode() == BO_EQ)
+          EqNegated = !EqNegated;
+
+        // Unwrap assignment-in-condition: (p = f()) != nullptr → narrow p
+        if (const auto *AssignBO = dyn_cast<BinaryOperator>(PtrExpr)) {
+          if (AssignBO->getOpcode() == BO_Assign)
+            PtrExpr = AssignBO->getLHS()->IgnoreParenImpCasts();
+        }
+
+        if (const auto *DRE = dyn_cast<DeclRefExpr>(PtrExpr)) {
+          if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+            Results.push_back({VD, nullptr, false, EqNegated});
+            return;
+          }
+        }
+        if (const auto *ME = dyn_cast<MemberExpr>(PtrExpr)) {
+          if (ME->getType()->isPointerType()) {
+            const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+            if (isa<CXXThisExpr>(Base)) {
+              if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+                Results.push_back({nullptr, FD, true, EqNegated});
+                return;
+              }
+            }
+            if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(Base)) {
+              if (const auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl())) {
+                if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+                  Results.push_back({BaseVD, FD, false, EqNegated});
+                  return;
+                }
+              }
+            }
+          }
+        }
+      }
+      return;
+    }
+  }
+
+  if (const auto *UO = dyn_cast<UnaryOperator>(E)) {
+    if (UO->getOpcode() == UO_Deref) {
+      const Expr *SubExpr = UO->getSubExpr()->IgnoreParenImpCasts();
+      if (auto *DRE = dyn_cast<DeclRefExpr>(SubExpr)) {
+        if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+          if (VD->getType()->isPointerType()) {
+            Results.push_back({VD, nullptr, false, Negated});
+            return;
+          }
+        }
+      }
+    }
+  }
+
+  // Unwrap assignment-in-condition for truthiness: while ((p = f())) → p
+  if (const auto *AssignBO = dyn_cast<BinaryOperator>(E)) {
+    if (AssignBO->getOpcode() == BO_Assign)
+      E = AssignBO->getLHS()->IgnoreParenImpCasts();
+  }
+
+  if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+    if (auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+      if (VD->getType()->isPointerType()) {
+        Results.push_back({VD, nullptr, false, Negated});
+        return;
+      }
+      // Bool intermediary: if (valid) where valid = (p != nullptr)
+      if (BoolGuards && VD->getType()->isBooleanType()) {
+        auto It = BoolGuards->find(VD);
+        if (It != BoolGuards->end()) {
+          // XOR: outer ! flips the guard's sense
+          Results.push_back(
+              {It->second.first, nullptr, false, Negated != It->second.second});
+          return;
+        }
+      }
+    }
+  }
+
+  if (const auto *ME = dyn_cast<MemberExpr>(E)) {
+    if (ME->getType()->isPointerType()) {
+      const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+      if (isa<CXXThisExpr>(Base)) {
+        if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+          Results.push_back({nullptr, FD, true, Negated});
+          return;
+        }
+      }
+      if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(Base)) {
+        if (const auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl())) {
+          if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+            Results.push_back({BaseVD, FD, false, Negated});
+            return;
+          }
+        }
+      }
+    }
+  }
+
+  // Handle smart pointer implicit bool conversion: if (sp) { ... }
+  // The AST represents this as a CXXMemberCallExpr to operator bool().
+  if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(E)) {
+    if (const auto *CD =
+            dyn_cast_or_null<CXXConversionDecl>(MCE->getMethodDecl())) {
+      if (CD->getConversionType()->isBooleanType()) {
+        const Expr *Obj = MCE->getImplicitObjectArgument();
+        if (Obj && isSmartPointerType(Obj->getType())) {
+          Obj = Obj->IgnoreParenImpCasts();
+          if (const auto *DRE = dyn_cast<DeclRefExpr>(Obj)) {
+            if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+              Results.push_back({VD, nullptr, false, Negated});
+              return;
+            }
+          }
+          if (const auto *ObjME = dyn_cast<MemberExpr>(Obj)) {
+            if (const auto *FD = dyn_cast<FieldDecl>(ObjME->getMemberDecl())) {
+              const Expr *ObjBase = ObjME->getBase()->IgnoreParenImpCasts();
+              if (isa<CXXThisExpr>(ObjBase)) {
+                Results.push_back({nullptr, FD, true, Negated});
+                return;
+              }
+              if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(ObjBase)) {
+                if (const auto *BaseVD =
+                        dyn_cast<VarDecl>(BaseDRE->getDecl())) {
+                  Results.push_back({BaseVD, FD, false, Negated});
+                  return;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+/// Transfer functions for the flow-sensitive nullability dataflow analysis.
+/// Processes each CFG statement to update the NullState lattice — tracking
+/// narrowing from null checks, invalidation from assignments, and reporting
+/// dereferences of nullable pointers via the Handler interface.
+class TransferFunctions {
+  NullState &State;
+  FlowNullabilityHandler &Handler;
+  ASTContext &Ctx;
+  bool StrictMode;
+  NullabilityKind DefaultNullability;
+
+  bool isNarrowed(const VarDecl *VD) const {
+    return State.NarrowedVars.contains(VD);
+  }
+
+  bool isMemberNarrowed(const VarDecl *BaseVD, const FieldDecl *FD) const {
+    return State.NarrowedMembers.contains({BaseVD, FD});
+  }
+
+  bool isThisMemberNarrowed(const FieldDecl *FD) const {
+    return State.NarrowedThisMembers.contains(FD);
+  }
+
+  /// Unwrap explicit casts and pointer arithmetic to find the original
+  /// pointer expression and whether a cast was traversed.  Template
+  /// instantiations can bake _Nullable into cast result types even when
+  /// the source is unannotated (e.g. reinterpret_cast<T*>(p) where T
+  /// is itself a pointer type).  When a cast is found, callers should
+  /// check nullability on the SOURCE type, not the cast result.
+  static const Expr *unwrapCastsAndArithmetic(const Expr *E, bool &FoundCast) {
+    FoundCast = false;
+    for (;;) {
+      if (const auto *CE = dyn_cast<ExplicitCastExpr>(E)) {
+        FoundCast = true;
+        E = CE->getSubExpr()->IgnoreParenImpCasts();
+      } else if (const auto *BO = dyn_cast<BinaryOperator>(E)) {
+        if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
+          E = BO->getLHS()->getType()->isPointerType()
+                  ? BO->getLHS()->IgnoreParenImpCasts()
+                  : BO->getRHS()->IgnoreParenImpCasts();
+        } else {
+          break;
+        }
+      } else {
+        break;
+      }
+    }
+    return E;
+  }
+
+  void checkDeref(const Expr *DerefExpr, QualType PtrType) {
+    if (isNullableType(PtrType, StrictMode, DefaultNullability)) {
+      LLVM_DEBUG(llvm::dbgs()
+                 << "  deref: nullable " << PtrType.getAsString() << "\n");
+      ++NumDereferenceWarnings;
+      Handler.handleNullableDereference(DerefExpr, PtrType);
+    }
+  }
+
+  /// Check dereference of a non-variable, non-member expression.
+  /// Unwraps casts/arithmetic to avoid template-instantiation false
+  /// positives where _Nullable is baked into cast result types.
+  void checkExprDeref(const Expr *DerefExpr, const Expr *PtrExpr) {
+    bool FoundCast = false;
+    const Expr *Origin = unwrapCastsAndArithmetic(PtrExpr, FoundCast);
+
+    // If the origin is inherently non-null, skip.
+    if (isa<CXXThisExpr>(Origin))
+      return;
+    if (const auto *UO = dyn_cast<UnaryOperator>(Origin))
+      if (UO->getOpcode() == UO_AddrOf)
+        return;
+
+    QualType CheckTy = FoundCast ? Origin->getType() : PtrExpr->getType();
+    checkDeref(DerefExpr, CheckTy);
+  }
+
+  void checkVarDeref(const Expr *DerefExpr, const VarDecl *VD) {
+    QualType Ty = VD->getType();
+    if (isNullableType(Ty, StrictMode, DefaultNullability) ||
+        State.NullableVars.contains(VD)) {
+      LLVM_DEBUG(llvm::dbgs()
+                 << "  deref: var '" << VD->getNameAsString() << "'\n");
+      ++NumDereferenceWarnings;
+      return Handler.handleNullableDereference(DerefExpr, Ty);
+    }
+  }
+
+  /// Warn on smart pointer dereference. For local vars/params, always warn
+  /// (they're nullable by default). For this->member smart pointers, only warn
+  /// if there's evidence of nullability in the current function (reset, move,
+  /// or null check) to avoid false positives on members set in constructors.
+  void warnSmartPtrDeref(const Expr *DerefExpr, const Expr *Obj) {
+    Obj = Obj->IgnoreParenImpCasts();
+    // Local variable or parameter — always warn when not narrowed
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(Obj)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+        LLVM_DEBUG(llvm::dbgs()
+                   << "  deref: smart ptr '" << VD->getNameAsString() << "'\n");
+        ++NumDereferenceWarnings;
+        Handler.handleNullableDereference(DerefExpr, VD->getType());
+        return;
+      }
+    }
+    // this->member — only warn if known nullable in current function
+    if (const auto *FD = getSmartPtrThisMemberDecl(Obj)) {
+      if (State.NullableThisMembers.contains(FD)) {
+        ++NumDereferenceWarnings;
+        Handler.handleNullableDereference(DerefExpr, FD->getType());
+      }
+    }
+  }
+
+  /// Remove any BoolGuards that reference the given pointer variable.
+  void invalidateBoolGuardsFor(const VarDecl *VD) {
+    SmallVector<const VarDecl *, 2> ToRemove;
+    for (const auto &[BoolVD, GuardInfo] : State.BoolGuards)
+      if (GuardInfo.first == VD)
+        ToRemove.push_back(BoolVD);
+    for (const auto *BoolVD : ToRemove)
+      State.BoolGuards.erase(BoolVD);
+  }
+
+  /// Remove any Aliases that target the given pointer variable (the alias
+  /// source was reassigned, so copies of its old value are stale).
+  void invalidateAliasesFor(const VarDecl *VD) {
+    SmallVector<const VarDecl *, 2> ToRemove;
+    for (const auto &[AliasVD, TargetVD] : State.Aliases)
+      if (TargetVD == VD)
+        ToRemove.push_back(AliasVD);
+    for (const auto *AliasVD : ToRemove)
+      State.Aliases.erase(AliasVD);
+  }
+
+  /// Resolve a VarDecl through the alias chain to its canonical target.
+  /// Returns VD itself if it's not an alias of anything.
+  const VarDecl *resolveAlias(const VarDecl *VD) const {
+    auto It = State.Aliases.find(VD);
+    return It != State.Aliases.end() ? It->second : VD;
+  }
+
+  void invalidateMembersFor(const VarDecl *VD) {
+    SmallVector<MemberKey, 4> ToRemove;
+    for (const auto &MK : State.NarrowedMembers)
+      if (MK.first == VD)
+        ToRemove.push_back(MK);
+    for (const auto &MK : ToRemove)
+      State.NarrowedMembers.erase(MK);
+  }
+
+public:
+  TransferFunctions(NullState &State, FlowNullabilityHandler &Handler,
+                    ASTContext &Ctx, bool StrictMode,
+                    NullabilityKind DefaultNullability)
+      : State(State), Handler(Handler), Ctx(Ctx), StrictMode(StrictMode),
+        DefaultNullability(DefaultNullability) {}
+
+  // The enclosing function declaration, needed for return type checking.
+  const FunctionDecl *EnclosingFunc = nullptr;
+
+  void setEnclosingFunc(const FunctionDecl *FD) { EnclosingFunc = FD; }
+
+  void visit(const Stmt *S) {
+    if (!S)
+      return;
+
+    if (const auto *DS = dyn_cast<DeclStmt>(S))
+      handleDeclStmt(DS);
+    else if (const auto *BO = dyn_cast<BinaryOperator>(S))
+      handleBinaryOperator(BO);
+    else if (const auto *UO = dyn_cast<UnaryOperator>(S))
+      handleUnaryOperator(UO);
+    else if (const auto *ME = dyn_cast<MemberExpr>(S))
+      handleMemberExpr(ME);
+    else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(S))
+      handleArraySubscript(ASE);
+    else if (const auto *CE = dyn_cast<CallExpr>(S))
+      handleCallExpr(CE);
+    else if (const auto *RS = dyn_cast<ReturnStmt>(S))
+      handleReturnStmt(RS);
+  }
+
+private:
+  void handleDeclStmt(const DeclStmt *DS) {
+    for (const auto *D : DS->decls()) {
+      if (const auto *VD = dyn_cast<VarDecl>(D)) {
+        // Track raw pointer initialization
+        if (VD->getType()->isPointerType()) {
+          // Alias tracking: int *y = x → {y → canonical(x)}
+          if (VD->hasInit()) {
+            const Expr *Init = VD->getInit()->IgnoreParenImpCasts();
+            if (const auto *InitDRE = dyn_cast<DeclRefExpr>(Init)) {
+              if (const auto *InitVD = dyn_cast<VarDecl>(InitDRE->getDecl())) {
+                if (InitVD->getType()->isPointerType())
+                  State.Aliases[VD] = resolveAlias(InitVD);
+              }
+            }
+          }
+          if (isNonnullType(VD->getType())) {
+            State.NarrowedVars.insert(VD);
+            // Flow-sensitive assignment check: warn when initializing a
+            // _Nonnull variable with a nullable value.
+            if (VD->hasInit()) {
+              const Expr *Init = VD->getInit()->IgnoreParenImpCasts();
+              // Don't warn if the init is provably non-null via narrowing.
+              bool InitIsNarrowed = false;
+              if (const auto *InitDRE = dyn_cast<DeclRefExpr>(Init))
+                if (const auto *InitVD = dyn_cast<VarDecl>(InitDRE->getDecl()))
+                  InitIsNarrowed = isNarrowed(InitVD);
+              if (!InitIsNarrowed && !isNonnullInit(Init) &&
+                  !isNonnullType(Init->getType()) &&
+                  (isNullableType(Init->getType(), StrictMode,
+                                  DefaultNullability) ||
+                   isNullableInit(Init))) {
+                ++NumAssignmentWarnings;
+                Handler.handleNullableAssignment(VD->getInit(), VD);
+              }
+            }
+          } else if (VD->hasInit()) {
+            const Expr *Init = VD->getInit()->IgnoreParenImpCasts();
+            if (const auto *UO = dyn_cast<UnaryOperator>(Init)) {
+              if (UO->getOpcode() == UO_AddrOf)
+                State.NarrowedVars.insert(VD);
+            } else if (isNonnullInit(Init) || isNonnullType(Init->getType())) {
+              State.NarrowedVars.insert(VD);
+            } else {
+              // Unwrap explicit casts to check the SOURCE type, not the
+              // cast result type. Template instantiations can bake
+              // _Nullable into cast result types even when the source is
+              // unannotated (e.g. static_cast<T*>(void_ptr)).
+              const Expr *TypeExpr = Init;
+              bool HasCast = false;
+              while (const auto *CE = dyn_cast<ExplicitCastExpr>(TypeExpr)) {
+                HasCast = true;
+                TypeExpr = CE->getSubExpr()->IgnoreParenImpCasts();
+              }
+              if (isNullableType(TypeExpr->getType(), StrictMode,
+                                 DefaultNullability) ||
+                  isNullableInit(Init)) {
+                State.NullableVars.insert(VD);
+              } else if (HasCast) {
+                // The cast source is not nullable — narrow the var to
+                // override any _Nullable baked into the var's own type
+                // by template instantiation.
+                State.NarrowedVars.insert(VD);
+              }
+            }
+          }
+          continue;
+        }
+
+        // Track smart pointer initialization
+        if (isSmartPointerType(VD->getType()) && VD->hasInit()) {
+          const Expr *Init = unwrapImplicitWrappers(VD->getInit());
+          if (isMakeSmartPtrCall(Init)) {
+            // make_unique/make_shared always return non-null
+            State.NarrowedVars.insert(VD);
+          }
+          // Default-constructed, nullptr, or moved-from → nullable (don't
+          // narrow)
+        }
+
+        // Track bool variables assigned from null-comparisons so that
+        // boolean intermediaries like bool valid = (p != nullptr) can
+        // later narrow p when used as a condition.
+        if (VD->getType()->isBooleanType() && VD->hasInit()) {
+          const Expr *Init = VD->getInit()->IgnoreParenImpCasts();
+          SmallVector<ConditionResult, 2> InitResults;
+          analyzeCondition(Init, Ctx, InitResults);
+          if (InitResults.size() == 1 && InitResults[0].VD &&
+              !InitResults[0].FD)
+            State.BoolGuards[VD] = {InitResults[0].VD, InitResults[0].Negated};
+        }
+      }
+    }
+  }
+
+  /// Check if an init expression is provably non-null (address-of, new,
+  /// this, _Nonnull typed, narrowed var, cast of non-null, pointer arith).
+  /// See also: isExprProvablyNonnull() in Sema.cpp, which is a similar
+  /// heuristic used to suppress nullable-to-nonnull conversion warnings.
+  bool isNonnullInit(const Expr *Init) const {
+    if (!Init)
+      return false;
+    Init = Init->IgnoreParenImpCasts();
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(Init)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+        if (isNonnullType(VD->getType()) || isNarrowed(VD))
+          return true;
+    }
+    // Throwing operator new never returns null.
+    if (const auto *NE = dyn_cast<CXXNewExpr>(Init)) {
+      if (!NE->shouldNullCheckAllocation())
+        return true;
+    }
+    // Look through explicit casts — they don't change null/nonnull status.
+    if (const auto *CE = dyn_cast<ExplicitCastExpr>(Init))
+      return isNonnullInit(CE->getSubExpr());
+    // this is always non-null.
+    if (isa<CXXThisExpr>(Init))
+      return true;
+    // Pointer arithmetic on a non-null pointer is non-null.
+    if (const auto *BO = dyn_cast<BinaryOperator>(Init)) {
+      if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
+        if (BO->getLHS()->getType()->isPointerType())
+          return isNonnullInit(BO->getLHS()->IgnoreParenImpCasts());
+        if (BO->getRHS()->getType()->isPointerType())
+          return isNonnullInit(BO->getRHS()->IgnoreParenImpCasts());
+      }
+    }
+    return false;
+  }
+
+  /// Check if an init expression is nullable — either by type or because it
+  /// refers to a variable known to be nullable.  Unwraps casts to propagate
+  /// nullability through cast chains (e.g., `(Derived *)nullableBase`).
+  bool isNullableInit(const Expr *Init) const {
+    if (!Init)
+      return false;
+    Init = Init->IgnoreParenImpCasts();
+    if (!Init)
+      return false;
+    if (const auto *CE = dyn_cast<ExplicitCastExpr>(Init))
+      return isNullableInit(CE->getSubExpr());
+    if (isNullableType(Init->getType(), StrictMode, DefaultNullability))
+      return true;
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(Init)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+        return State.NullableVars.contains(VD);
+    }
+    // nothrow new can return null.
+    if (const auto *NE = dyn_cast<CXXNewExpr>(Init))
+      return NE->shouldNullCheckAllocation();
+    return false;
+  }
+
+  void handleBinaryOperator(const BinaryOperator *BO) {
+    // Pointer arithmetic: p + i, i + p, p - i, p - p
+    // Warn if a nullable pointer is used in arithmetic (implies it must be
+    // valid). p + 0 and p - 0 are excluded as safe identity operations.
+    if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
+      const Expr *PtrExpr = nullptr;
+      const Expr *OtherExpr = nullptr;
+      if (BO->getLHS()->getType()->isPointerType()) {
+        PtrExpr = BO->getLHS()->IgnoreParenImpCasts();
+        OtherExpr = BO->getRHS()->IgnoreParenImpCasts();
+      } else if (BO->getRHS()->getType()->isPointerType()) {
+        PtrExpr = BO->getRHS()->IgnoreParenImpCasts();
+        OtherExpr = BO->getLHS()->IgnoreParenImpCasts();
+      }
+      if (PtrExpr) {
+        bool IsZeroOffset = false;
+        if (OtherExpr && !OtherExpr->getType()->isPointerType()) {
+          if (auto Val = OtherExpr->getIntegerConstantExpr(Ctx))
+            if (*Val == 0)
+              IsZeroOffset = true;
+        }
+        if (!IsZeroOffset) {
+          if (const auto *DRE = dyn_cast<DeclRefExpr>(PtrExpr)) {
+            if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+              if (!isNarrowed(VD) && (isNullableType(VD->getType(), StrictMode,
+                                                     DefaultNullability) ||
+                                      State.NullableVars.contains(VD))) {
+                ++NumArithmeticWarnings;
+                Handler.handleNullableArithmetic(BO, VD->getType());
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Compound pointer arithmetic: p += i, p -= i
+    if (BO->getOpcode() == BO_AddAssign || BO->getOpcode() == BO_SubAssign) {
+      const Expr *LHS = BO->getLHS()->IgnoreParenImpCasts();
+      if (LHS->getType()->isPointerType()) {
+        if (const auto *DRE = dyn_cast<DeclRefExpr>(LHS)) {
+          if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+            if (!isNarrowed(VD) && (isNullableType(VD->getType(), StrictMode,
+                                                   DefaultNullability) ||
+                                    State.NullableVars.contains(VD))) {
+              ++NumArithmeticWarnings;
+              Handler.handleNullableArithmetic(BO, VD->getType());
+            }
+          }
+        }
+      }
+    }
+
+    if (BO->isAssignmentOp()) {
+      const Expr *LHS = BO->getLHS()->IgnoreParenImpCasts();
+
+      // Assignment to a member (this->field, var->field, or s.field)
+      // invalidates any narrowing on that member.
+      if (const auto *ME = dyn_cast<MemberExpr>(LHS)) {
+        if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+          const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+          if (ME->isArrow()) {
+            if (isa<CXXThisExpr>(Base)) {
+              State.NarrowedThisMembers.erase(FD);
+              State.NullableThisMembers.erase(FD);
+            } else if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(Base)) {
+              if (const auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl()))
+                State.NarrowedMembers.erase({BaseVD, FD});
+            }
+          } else {
+            // Dot access: s.field = ... invalidates narrowing on s.field
+            if (isa<CXXThisExpr>(Base)) {
+              State.NarrowedThisMembers.erase(FD);
+              State.NullableThisMembers.erase(FD);
+            } else if (const auto *BaseDRE = dyn_cast<DeclRefExpr>(Base)) {
+              if (const auto *BaseVD = dyn_cast<VarDecl>(BaseDRE->getDecl()))
+                State.NarrowedMembers.erase({BaseVD, FD});
+            }
+          }
+        }
+      }
+
+      if (const auto *DRE = dyn_cast<DeclRefExpr>(LHS)) {
+        if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+          // Bool reassignment invalidates any stored guard
+          if (VD->getType()->isBooleanType()) {
+            State.BoolGuards.erase(VD);
+            return;
+          }
+          if (!VD->getType()->isPointerType())
+            return;
+          State.NarrowedVars.erase(VD);
+          State.NullableVars.erase(VD);
+          invalidateMembersFor(VD);
+          invalidateBoolGuardsFor(VD);
+          // Invalidate aliases: VD is being reassigned, so any alias
+          // pointing TO VD (i.e., "other = VD" from earlier) is stale.
+          invalidateAliasesFor(VD);
+          State.Aliases.erase(VD);
+
+          if (BO->getOpcode() == BO_Assign) {
+            const Expr *RHS = BO->getRHS()->IgnoreParenImpCasts();
+
+            // Alias tracking: y = x → {y → canonical(x)}
+            if (const auto *RHSDRE = dyn_cast<DeclRefExpr>(RHS)) {
+              if (const auto *RHSVD = dyn_cast<VarDecl>(RHSDRE->getDecl())) {
+                if (RHSVD->getType()->isPointerType())
+                  State.Aliases[VD] = resolveAlias(RHSVD);
+              }
+            }
+
+            if (const auto *RHSUO = dyn_cast<UnaryOperator>(RHS)) {
+              if (RHSUO->getOpcode() == UO_AddrOf) {
+                State.NarrowedVars.insert(VD);
+                return;
+              }
+            }
+            if (const auto *RHSDRE = dyn_cast<DeclRefExpr>(RHS)) {
+              if (const auto *RHSVD = dyn_cast<VarDecl>(RHSDRE->getDecl())) {
+                if (isNonnullType(RHSVD->getType()) || isNarrowed(RHSVD)) {
+                  State.NarrowedVars.insert(VD);
+                  return;
+                }
+              }
+            }
+            if (isNonnullInit(RHS)) {
+              State.NarrowedVars.insert(VD);
+              return;
+            }
+            if (isNonnullType(BO->getRHS()->getType())) {
+              State.NarrowedVars.insert(VD);
+            } else if (isNullableType(BO->getRHS()->getType(), StrictMode,
+                                      DefaultNullability) ||
+                       isNullableInit(RHS)) {
+              State.NullableVars.insert(VD);
+              // Flow-sensitive assignment check: warn when assigning a
+              // nullable value to a _Nonnull variable.
+              if (isNonnullType(VD->getType())) {
+                ++NumAssignmentWarnings;
+                Handler.handleNullableAssignment(BO, VD);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  void handleUnaryOperator(const UnaryOperator *UO) {
+    if (UO->getOpcode() == UO_Deref) {
+      const Expr *SubExpr = UO->getSubExpr()->IgnoreParenImpCasts();
+
+      if (const auto *DRE = dyn_cast<DeclRefExpr>(SubExpr)) {
+        if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+          if (!VD->isImplicit() && !isNarrowed(VD))
+            checkVarDeref(UO, VD);
+        }
+      } else if (const auto *ME = dyn_cast<MemberExpr>(SubExpr)) {
+        const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+        if (isa<CXXThisExpr>(Base)) {
+          if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+            if (!isThisMemberNarrowed(FD))
+              checkDeref(UO, ME->getType());
+          }
+        } else {
+          checkMemberExprDeref(UO, ME);
+        }
+      } else if (!isa<CXXThisExpr>(SubExpr)) {
+        checkExprDeref(UO, SubExpr);
+      }
+    }
+
+    // Pointer increment/decrement (p++, ++p, p--, --p): arithmetic on a
+    // nullable pointer is unsafe (implies the pointer must be valid).
+    // Also invalidates member narrowing, bool guards, and aliases since
+    // the pointer now points elsewhere.
+    if (UO->getOpcode() == UO_PostInc || UO->getOpcode() == UO_PreInc ||
+        UO->getOpcode() == UO_PostDec || UO->getOpcode() == UO_PreDec) {
+      const Expr *SubExpr = UO->getSubExpr()->IgnoreParenImpCasts();
+      if (const auto *DRE = dyn_cast<DeclRefExpr>(SubExpr)) {
+        if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+          if (VD->getType()->isPointerType()) {
+            // Warn on arithmetic of non-narrowed nullable pointer
+            if (!isNarrowed(VD) && (isNullableType(VD->getType(), StrictMode,
+                                                   DefaultNullability) ||
+                                    State.NullableVars.contains(VD))) {
+              ++NumArithmeticWarnings;
+              Handler.handleNullableArithmetic(UO, VD->getType());
+            }
+            invalidateMembersFor(VD);
+            invalidateBoolGuardsFor(VD);
+            invalidateAliasesFor(VD);
+            State.Aliases.erase(VD);
+          }
+        }
+      }
+    }
+  }
+
+  void handleMemberExpr(const MemberExpr *ME) {
+    if (!ME->isArrow())
+      return;
+
+    const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+
+    if (isa<CXXThisExpr>(Base))
+      return;
+
+    // Handle overloaded operator-> (smart pointers, iterators, etc.)
+    if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(Base)) {
+      if (OCE->getOperator() == OO_Arrow) {
+        // For smart pointers, warn if not narrowed.
+        // For non-smart-pointer types (iterators etc), skip as before.
+        if (OCE->getNumArgs() >= 1) {
+          const Expr *Obj = OCE->getArg(0);
+          if (isSmartPointerType(Obj->getType())) {
+            if (!isSmartPointerNarrowed(Obj, State))
+              warnSmartPtrDeref(ME, Obj);
+          }
+        }
+        return;
+      }
+    }
+
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(Base)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+        if (!isNarrowed(VD))
+          checkVarDeref(ME, VD);
+      }
+    } else if (const auto *BaseME = dyn_cast<MemberExpr>(Base)) {
+      checkMemberExprDeref(ME, BaseME);
+    } else {
+      checkExprDeref(ME, Base);
+    }
+  }
+
+  void handleArraySubscript(const ArraySubscriptExpr *ASE) {
+    const Expr *Base = ASE->getBase()->IgnoreParenImpCasts();
+    if (const auto *UO = dyn_cast<UnaryOperator>(Base))
+      if (UO->getOpcode() == UO_AddrOf)
+        return;
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(Base)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {
+        if (!isNarrowed(VD) && !VD->getType()->isArrayType())
+          checkVarDeref(ASE, VD);
+      }
+    } else {
+      QualType BaseTy = Base->getType();
+      if (!BaseTy->isArrayType())
+        checkExprDeref(ASE, Base);
+    }
+  }
+
+  /// Handle function calls. By design, calls do NOT invalidate pointer
+  /// narrowing — even when a pointer's address is taken (&p) and passed as
+  /// a T** argument. This is a pragmatic trade-off: invalidating on
+  /// address-escape would produce excessive false positives on common
+  /// patterns (output parameters, init functions). The same approach is
+  /// used by Clang's ThreadSafety analysis.
+  void handleCallExpr(const CallExpr *CE) {
+    if (const auto *Callee = CE->getDirectCallee()) {
+      // __builtin_assume(cond) narrows pointers mentioned in cond.
+      if (Callee->getBuiltinID() == Builtin::BI__builtin_assume &&
+          CE->getNumArgs() >= 1) {
+        const Expr *Arg = CE->getArg(0)->IgnoreParenImpCasts();
+        SmallVector<ConditionResult, 2> Results;
+        analyzeCondition(Arg, Ctx, Results, &State.BoolGuards);
+        for (const auto &CR : Results) {
+          if (CR.Negated)
+            continue;
+          if (CR.IsThisMember) {
+            State.NarrowedThisMembers.insert(CR.FD);
+          } else if (CR.VD) {
+            if (!CR.FD)
+              State.NarrowedVars.insert(CR.VD);
+            else
+              State.NarrowedMembers.insert({CR.VD, CR.FD});
+          }
+        }
+      }
+
+      // Narrow pointers passed to _Nonnull parameters — surviving the call
+      // proves the pointer was non-null. Recognizes both Clang _Nonnull
+      // and GCC-style __attribute__((nonnull)).
+      const auto *NNAttr = Callee->getAttr<NonNullAttr>();
+      for (unsigned I = 0,
+                    N = std::min(CE->getNumArgs(), Callee->getNumParams());
+           I < N; ++I) {
+        const ParmVarDecl *Param = Callee->getParamDecl(I);
+        if (!Param->getType()->isPointerType())
+          continue;
+        bool ParamIsNonnull =
+            isNonnullType(Param->getType()) || (NNAttr && NNAttr->isNonNull(I));
+        if (ParamIsNonnull) {
+          const Expr *Arg = CE->getArg(I)->IgnoreParenImpCasts();
+          // Flow-sensitive argument check: warn when passing a nullable
+          // pointer to a _Nonnull parameter.
+          if (isExprNullable(Arg)) {
+            ++NumArgumentWarnings;
+            Handler.handleNullableArgument(CE->getArg(I), Param);
+          }
+          if (const auto *DRE = dyn_cast<DeclRefExpr>(Arg)) {
+            if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+              if (VD->getType()->isPointerType())
+                State.NarrowedVars.insert(VD);
+          }
+        }
+      }
+    }
+
+    // Handle sp.reset() / sp.reset(ptr) — CXXMemberCallExpr
+    if (const auto *MCE = dyn_cast<CXXMemberCallExpr>(CE)) {
+      const Expr *Obj = MCE->getImplicitObjectArgument();
+      if (Obj && isSmartPointerType(Obj->getType())) {
+        if (const auto *MD = MCE->getMethodDecl()) {
+          if (MD->getDeclName().isIdentifier() && MD->getName() == "reset") {
+            // reset(nullptr) makes it null; reset(ptr) makes it non-null;
+            // reset() with no args makes it null.
+            bool ResetsToNonnull =
+                MCE->getNumArgs() > 0 &&
+                !MCE->getArg(0)->IgnoreParenImpCasts()->isNullPointerConstant(
+                    Ctx, Expr::NPC_ValueDependentIsNotNull);
+            // Local variable
+            if (const auto *VD = getSmartPtrVarDecl(Obj)) {
+              State.NarrowedVars.erase(VD);
+              if (ResetsToNonnull)
+                State.NarrowedVars.insert(VD);
+            }
+            // this->member
+            if (const auto *FD = getSmartPtrThisMemberDecl(Obj)) {
+              State.NarrowedThisMembers.erase(FD);
+              if (ResetsToNonnull) {
+                State.NarrowedThisMembers.insert(FD);
+                State.NullableThisMembers.erase(FD);
+              } else {
+                State.NullableThisMembers.insert(FD);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    // Handle sp = nullptr / sp = make_unique(...) / sp = std::move(other)
+    if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(CE)) {
+      if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() >= 2) {
+        const VarDecl *LhsVD = getSmartPtrVarDecl(OCE->getArg(0));
+        if (LhsVD) {
+          State.NarrowedVars.erase(LhsVD);
+          const Expr *RHS = unwrapImplicitWrappers(OCE->getArg(1));
+
+          if (isMakeSmartPtrCall(RHS)) {
+            // sp = make_unique<T>(...) — non-null
+            State.NarrowedVars.insert(LhsVD);
+          } else if (const auto *RhsCE = dyn_cast<CallExpr>(RHS)) {
+            if (RhsCE->isCallToStdMove() && RhsCE->getNumArgs() >= 1) {
+              // sp = std::move(other) — LHS inherits source's state
+              if (const auto *SrcVD = getSmartPtrVarDecl(RhsCE->getArg(0))) {
+                // Only narrow LHS if source was narrowed (known non-null)
+                if (State.NarrowedVars.contains(SrcVD))
+                  State.NarrowedVars.insert(LhsVD);
+                State.NarrowedVars.erase(SrcVD);
+              }
+            } else if (isNonnullType(RhsCE->getType())) {
+              // sp = someFunction() — only narrow if return type is _Nonnull
+              State.NarrowedVars.insert(LhsVD);
+            }
+          }
+          // sp = nullptr or non-call — remains nullable (erased above)
+        }
+      }
+    }
+
+    // Handle *sp (operator*) on smart pointers — same as operator->
+    if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(CE)) {
+      if (OCE->getOperator() == OO_Star && OCE->getNumArgs() >= 1) {
+        const Expr *Obj = OCE->getArg(0);
+        if (isSmartPointerType(Obj->getType())) {
+          if (!isSmartPointerNarrowed(Obj, State))
+            warnSmartPtrDeref(CE, Obj);
+        }
+      }
+    }
+
+    // Handle std::move(sp) — marks the source as nullable
+    if (CE->isCallToStdMove() && CE->getNumArgs() >= 1) {
+      if (const auto *VD = getSmartPtrVarDecl(CE->getArg(0))) {
+        State.NarrowedVars.erase(VD);
+      }
+      if (const auto *FD = getSmartPtrThisMemberDecl(CE->getArg(0))) {
+        State.NarrowedThisMembers.erase(FD);
+        State.NullableThisMembers.insert(FD);
+      }
+    }
+  }
+
+  /// Flow-aware nullable check: returns true if the expression is nullable
+  /// considering both declared type and dynamic state (NarrowedVars,
+  /// NullableVars). This goes beyond the type-based check — it respects
+  /// null checks (narrowing suppresses the warning) and dynamic nullability
+  /// (reset/move makes a variable nullable even if its type isn't).
+  bool isVarNullable(const VarDecl *VD) const {
+    if (isNarrowed(VD))
+      return false;
+    if (isNonnullType(VD->getType()))
+      return false;
+    if (isNullableType(VD->getType(), StrictMode, DefaultNullability))
+      return true;
+    if (State.NullableVars.contains(VD))
+      return true;
+    return false;
+  }
+
+  /// Check if an expression resolves to a nullable pointer, considering flow.
+  bool isExprNullable(const Expr *E) const {
+    if (!E)
+      return false;
+    E = E->IgnoreParenImpCasts();
+    if (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull))
+      return true;
+    if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) {
+      if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+        return isVarNullable(VD);
+    }
+    // For non-variable expressions, fall back to type-based check
+    if (isNullableType(E->getType(), StrictMode, DefaultNullability))
+      return true;
+    return false;
+  }
+
+  void handleReturnStmt(const ReturnStmt *RS) {
+    if (!EnclosingFunc)
+      return;
+    const Expr *RetVal = RS->getRetValue();
+    if (!RetVal)
+      return;
+    QualType RetType = EnclosingFunc->getReturnType();
+    if (!RetType->isPointerType() || !isNonnullType(RetType))
+      return;
+    if (isExprNullable(RetVal)) {
+      ++NumReturnWarnings;
+      Handler.handleNullableReturn(RetVal, RetVal->getType(), RetType);
+    }
+  }
+
+  void checkMemberExprDeref(const Expr *DerefExpr, const MemberExpr *ME) {
+    const Expr *Base = ME->getBase()->IgnoreParenImpCasts();
+
+    if (const auto *OCE = dyn_cast<CXXOperatorCallExpr>(Base)) {
+      if (OCE->getOperator() == OO_Arrow) {
+        if (OCE->getNumArgs() >= 1) {
+          const Expr *Obj = OCE->getArg(0);
+          if (isSmartPointerType(Obj->getType())) {
+            if (!isSmartPointerNarrowed(Obj, State))
+              warnSmartPtrDeref(DerefExpr, Obj);
+          }
+        }
+        return;
+      }
+    }
+
+    if (const auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl())) {
+      if (isa<CXXThisExpr>(Base)) {
+        if (!isThisMemberNarrowed(FD))
+          checkDeref(DerefExpr, ME->getType());
+      } else if (const auto *DRE = dyn_cast<DeclRefExpr>(Base)) {
+        if (const auto *BaseVD = dyn_cast<VarDecl>(DRE->getDecl())) {
+          if (!isMemberNarrowed(BaseVD, FD))
+            checkDeref(DerefExpr, ME->getType());
+        }
+      }
+    }
+  }
+};
+
+} // end anonymous namespace
+
+void clang::runFlowNullabilityAnalysis(AnalysisDeclContext &AC,
+                                       FlowNullabilityHandler &Handler,
+                                       bool StrictMode,
+                                       NullabilityKind Default) {
+  CFG *Cfg = AC.getCFG();
+  if (!Cfg)
+    return;
+
+  ++NumFunctionsAnalyzed;
+  ASTContext &Ctx = AC.getASTContext();
+  LLVM_DEBUG({
+    if (const auto *ND = dyn_cast_or_null<NamedDecl>(AC.getDecl()))
+      llvm::dbgs() << "flow-nullability: analyzing '" << ND->getNameAsString()
+                   << "' (" << Cfg->size() << " blocks)\n";
+  });
+
+  using EdgeKey = std::pair<unsigned, unsigned>;
+  llvm::DenseMap<EdgeKey, NullState> EdgeStates;
+  llvm::DenseMap<unsigned, NullState> BlockEntryStates;
+
+  ForwardDataflowWorklist Worklist(*Cfg, AC);
+
+  const CFGBlock &Entry = Cfg->getEntry();
+  NullState InitState;
+
+  if (const auto *FD = dyn_cast_or_null<FunctionDecl>(AC.getDecl())) {
+    for (const auto *Param : FD->parameters()) {
+      if (Param->getType()->isPointerType() && isNonnullType(Param->getType()))
+        InitState.NarrowedVars.insert(Param);
+    }
+  }
+
+  BlockEntryStates[Entry.getBlockID()] = InitState;
+  Worklist.enqueueBlock(&Entry);
+
+  // Fixpoint iteration. Termination is guaranteed because the lattice has
+  // finite height (bounded by the number of declarations in the function)
+  // and the edge-state comparison ensures each block is only re-processed
+  // when its entry state actually changes.
+  while (const CFGBlock *Block = Worklist.dequeue()) {
+    unsigned BlockID = Block->getBlockID();
+    ++NumBlocksProcessed;
+    ++NumFixpointIterations;
+    LLVM_DEBUG(llvm::dbgs() << "  block B" << BlockID << " (preds:");
+
+    NullState State;
+    bool FirstPred = true;
+
+    if (BlockID == Entry.getBlockID()) {
+      State = BlockEntryStates[BlockID];
+      FirstPred = false;
+    }
+
+    for (auto PI = Block->pred_begin(), PE = Block->pred_end(); PI != PE;
+         ++PI) {
+      if (const CFGBlock *Pred = *PI) {
+        LLVM_DEBUG(llvm::dbgs() << " B" << Pred->getBlockID());
+        EdgeKey EK = {Pred->getBlockID(), BlockID};
+        auto It = EdgeStates.find(EK);
+        if (It != EdgeStates.end()) {
+          if (FirstPred) {
+            State = It->second;
+            FirstPred = false;
+          } else {
+            State = join(State, It->second);
+          }
+        }
+      }
+    }
+    LLVM_DEBUG(llvm::dbgs() << ")\n");
+
+    if (FirstPred)
+      continue;
+
+    // Standard fixpoint check: skip re-processing if entry state is unchanged.
+    // This prevents duplicate warnings when the worklist re-visits a block.
+    // Skip this check for the entry block — its state is pre-seeded, so it
+    // would always match and prevent the first visit from propagating.
+    if (BlockID != Entry.getBlockID()) {
+      auto OldIt = BlockEntryStates.find(BlockID);
+      if (OldIt != BlockEntryStates.end() && OldIt->second == State) {
+        LLVM_DEBUG(llvm::dbgs() << "    converged, skipping\n");
+        continue;
+      }
+    }
+    BlockEntryStates[BlockID] = State;
+
+    TransferFunctions TF(State, Handler, Ctx, StrictMode, Default);
+    if (const auto *FD = dyn_cast_or_null<FunctionDecl>(AC.getDecl()))
+      TF.setEnclosingFunc(FD);
+    for (const auto &Elem : *Block) {
+      if (std::optional<CFGStmt> CS = Elem.getAs<CFGStmt>())
+        TF.visit(CS->getStmt());
+    }
+
+    NullState TrueState = State;
+    NullState FalseState = State;
+
+    if (const Stmt *Term = Block->getTerminatorStmt()) {
+      const Expr *Cond = nullptr;
+      if (const auto *IS = dyn_cast<IfStmt>(Term)) {
+        const Expr *IfCond = IS->getCond();
+        if (IfCond)
+          IfCond = IfCond->IgnoreParenImpCasts();
+        if (IfCond) {
+          if (const auto *BO = dyn_cast<BinaryOperator>(IfCond)) {
+            if (BO->getOpcode() == BO_LAnd) {
+              SmallVector<ConditionResult, 2> AndResults;
+              decomposeAnd(BO, Ctx, AndResults, &State.BoolGuards);
+              for (const auto &CR : AndResults) {
+                if (CR.VD && !CR.FD && !CR.IsThisMember && !CR.Negated) {
+                  TrueState.NarrowedVars.insert(CR.VD);
+                  // Also narrow alias target and all siblings
+                  const VarDecl *Target = CR.VD;
+                  auto AliasIt = TrueState.Aliases.find(CR.VD);
+                  if (AliasIt != TrueState.Aliases.end()) {
+                    Target = AliasIt->second;
+                    TrueState.NarrowedVars.insert(Target);
+                  }
+                  for (const auto &[AV, TV] : TrueState.Aliases)
+                    if (TV == CR.VD || TV == Target)
+                      TrueState.NarrowedVars.insert(AV);
+                }
+              }
+            }
+          }
+        }
+        Cond = getTerminalCondition(IS->getCond());
+      } else if (const auto *WS = dyn_cast<WhileStmt>(Term)) {
+        Cond = getTerminalCondition(WS->getCond());
+      } else if (const auto *FS = dyn_cast<ForStmt>(Term)) {
+        if (FS->getCond())
+          Cond = getTerminalCondition(FS->getCond());
+      } else if (const auto *DS = dyn_cast<DoStmt>(Term)) {
+        Cond = getTerminalCondition(DS->getCond());
+      } else if (const auto *BO = dyn_cast<BinaryOperator>(Term)) {
+        if (BO->getOpcode() == BO_LAnd || BO->getOpcode() == BO_LOr)
+          Cond = getTerminalCondition(BO->getLHS());
+      } else if (const auto *CO = dyn_cast<ConditionalOperator>(Term)) {
+        Cond = getTerminalCondition(CO->getCond());
+      }
+
+      // Propagate narrowing through aliases: when VD is narrowed on an edge,
+      // also narrow its alias target and all vars sharing the same canonical
+      // target. E.g., y = x; z = x; if (z) → narrow z, x, AND y.
+      auto narrowWithAliases = [&](NullState &NS, const VarDecl *VD) {
+        NS.NarrowedVars.insert(VD);
+        NS.NullableVars.erase(VD);
+        // Forward: VD aliases Target → also narrow Target
+        const VarDecl *Target = VD;
+        auto AliasIt = NS.Aliases.find(VD);
+        if (AliasIt != NS.Aliases.end()) {
+          Target = AliasIt->second;
+          NS.NarrowedVars.insert(Target);
+          NS.NullableVars.erase(Target);
+        }
+        // Reverse: narrow all vars aliasing VD or its canonical target
+        for (const auto &[AliasVD, AliasTarget] : NS.Aliases) {
+          if (AliasTarget == VD || AliasTarget == Target) {
+            NS.NarrowedVars.insert(AliasVD);
+            NS.NullableVars.erase(AliasVD);
+          }
+        }
+      };
+
+      if (Cond) {
+        SmallVector<ConditionResult, 2> Results;
+        analyzeCondition(Cond, Ctx, Results, &State.BoolGuards);
+        for (const auto &CR : Results) {
+          NullState &Narrow = CR.Negated ? FalseState : TrueState;
+          if (CR.IsThisMember) {
+            Narrow.NarrowedThisMembers.insert(CR.FD);
+          } else if (CR.VD) {
+            if (!CR.FD)
+              narrowWithAliases(Narrow, CR.VD);
+            else
+              Narrow.NarrowedMembers.insert({CR.VD, CR.FD});
+          }
+        }
+      }
+    }
+
+    unsigned SucIdx = 0;
+    for (auto SI = Block->succ_begin(), SE = Block->succ_end(); SI != SE;
+         ++SI, ++SucIdx) {
+      if (const CFGBlock *Succ = *SI) {
+        const NullState &SuccState =
+            (Block->succ_size() == 2) ? (SucIdx == 0 ? TrueState : FalseState)
+                                      : State;
+        EdgeKey EK = {BlockID, Succ->getBlockID()};
+        auto It = EdgeStates.find(EK);
+        if (It == EdgeStates.end() || It->second != SuccState) {
+          LLVM_DEBUG(llvm::dbgs()
+                     << "    edge B" << BlockID << "->B" << Succ->getBlockID()
+                     << " changed, enqueuing\n");
+          EdgeStates[EK] = SuccState;
+          Worklist.enqueueBlock(Succ);
+        }
+      }
+    }
+  }
+}
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index db82695f87d6b..c3535c5164825 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7716,6 +7716,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
   Args.addOptInFlag(CmdArgs, options::OPT_fapple_pragma_pack,
                     options::OPT_fno_apple_pragma_pack);
 
+  Args.addOptInFlag(CmdArgs, options::OPT_fflow_sensitive_nullability,
+                    options::OPT_fno_flow_sensitive_nullability);
+  if (Arg *A = Args.getLastArg(options::OPT_fnullability_default_EQ))
+    A->render(Args, CmdArgs);
+
   // Remarks can be enabled with any of the `-f.*optimization-record.*` flags.
   if (willEmitRemarks(Args) && checkRemarksOptions(D, Args, Triple))
     renderRemarksOptions(Args, CmdArgs, Triple, Input, Output, JA);
diff --git a/clang/lib/Lex/PPLexerChange.cpp b/clang/lib/Lex/PPLexerChange.cpp
index 05affedd48a86..38ecbef518cd6 100644
--- a/clang/lib/Lex/PPLexerChange.cpp
+++ b/clang/lib/Lex/PPLexerChange.cpp
@@ -415,16 +415,12 @@ bool Preprocessor::HandleEndOfFile(Token &Result, bool isEndOfMacro) {
   // Complain about reaching a true EOF within assume_nonnull.
   // We don't want to complain about reaching the end of a macro
   // instantiation or a _Pragma.
-  if (PragmaAssumeNonNullLoc.isValid() &&
-      !isEndOfMacro && !(CurLexer && CurLexer->Is_PragmaLexer)) {
-    // If we're at the end of generating a preamble, we should record the
-    // unterminated \#pragma clang assume_nonnull so we can restore it later
-    // when the preamble is loaded into the main file.
+  if (PragmaAssumeNonNullLoc.isValid() && !isEndOfMacro &&
+      !(CurLexer && CurLexer->Is_PragmaLexer)) {
     if (isRecordingPreamble() && isInPrimaryFile())
       PreambleRecordedPragmaAssumeNonNullLoc = PragmaAssumeNonNullLoc;
     else
       Diag(PragmaAssumeNonNullLoc, diag::err_pp_eof_in_assume_nonnull);
-    // Recover by leaving immediately.
     PragmaAssumeNonNullLoc = SourceLocation();
   }
 
diff --git a/clang/lib/Lex/Pragma.cpp b/clang/lib/Lex/Pragma.cpp
index 1a5a6ce61ecf1..f110e3be5f227 100644
--- a/clang/lib/Lex/Pragma.cpp
+++ b/clang/lib/Lex/Pragma.cpp
@@ -1973,11 +1973,11 @@ struct PragmaAssumeNonNullHandler : public PragmaHandler {
     PPCallbacks *Callbacks = PP.getPPCallbacks();
 
     if (IsBegin) {
-      // Complain about attempts to re-enter an audit.
       if (BeginLoc.isValid()) {
         PP.Diag(Loc, diag::err_pp_double_begin_of_assume_nonnull);
         PP.Diag(BeginLoc, diag::note_pragma_entered_here);
       }
+
       NewLoc = Loc;
       if (Callbacks)
         Callbacks->PragmaAssumeNonNullBegin(NewLoc);
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 37ed7488bb927..ba689c99391f6 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -32,6 +32,7 @@
 #include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
 #include "clang/Analysis/Analyses/CalledOnceCheck.h"
 #include "clang/Analysis/Analyses/Consumed.h"
+#include "clang/Analysis/Analyses/FlowNullability.h"
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
 #include "clang/Analysis/Analyses/ReachableCode.h"
 #include "clang/Analysis/Analyses/ThreadSafety.h"
@@ -2931,8 +2932,50 @@ LifetimeSafetyTUAnalysis(Sema &S, TranslationUnitDecl *TU,
   }
 }
 
+namespace {
+class FlowNullabilityReporter : public FlowNullabilityHandler {
+  Sema &S;
+
+public:
+  FlowNullabilityReporter(Sema &S) : S(S) {}
+
+  void handleNullableDereference(const Expr *DerefExpr,
+                                 QualType PtrType) override {
+    S.Diag(DerefExpr->getExprLoc(), diag::warn_flow_nullable_dereference)
+        << PtrType;
+    S.Diag(DerefExpr->getExprLoc(), diag::note_nullable_dereference_fix);
+  }
+
+  void handleNullableArithmetic(const Expr *ArithExpr,
+                                QualType PtrType) override {
+    S.Diag(ArithExpr->getExprLoc(), diag::warn_flow_nullable_arithmetic)
+        << PtrType;
+    S.Diag(ArithExpr->getExprLoc(), diag::note_nullable_arithmetic_fix);
+  }
+
+  void handleNullableReturn(const Expr *ReturnExpr, QualType ExprType,
+                            QualType ReturnType) override {
+    S.Diag(ReturnExpr->getExprLoc(), diag::warn_flow_nullable_return);
+    S.Diag(ReturnExpr->getExprLoc(), diag::note_nullable_return_fix);
+  }
+
+  void handleNullableAssignment(const Expr *AssignExpr,
+                                const VarDecl *LHSVar) override {
+    S.Diag(AssignExpr->getExprLoc(), diag::warn_flow_nullable_assignment)
+        << LHSVar;
+    S.Diag(AssignExpr->getExprLoc(), diag::note_nullable_assignment_fix);
+  }
+
+  void handleNullableArgument(const Expr *ArgExpr,
+                              const ParmVarDecl *Param) override {
+    S.Diag(ArgExpr->getExprLoc(), diag::warn_flow_nullable_argument) << Param;
+    S.Diag(ArgExpr->getExprLoc(), diag::note_nullable_argument_fix);
+  }
+};
+} // anonymous namespace
+
 void clang::sema::AnalysisBasedWarnings::IssueWarnings(
-     TranslationUnitDecl *TU) {
+    TranslationUnitDecl *TU) {
   if (!TU)
     return; // This is unexpected, give up quietly.
 
@@ -3047,19 +3090,24 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
   // prototyping, but we need a way for analyses to say what expressions they
   // expect to always be CFGElements and then fill in the BuildOptions
   // appropriately.  This is essentially a layering violation.
+  bool EnableFlowNullability =
+      S.getLangOpts().FlowSensitiveNullability &&
+      !Diags.isIgnored(diag::warn_flow_nullable_dereference, D->getBeginLoc());
+
   if (P.enableCheckUnreachable || P.enableThreadSafetyAnalysis ||
-      P.enableConsumedAnalysis || EnableLifetimeSafetyAnalysis) {
-    // Unreachable code analysis and thread safety require a linearized CFG.
+      P.enableConsumedAnalysis || EnableLifetimeSafetyAnalysis ||
+      EnableFlowNullability) {
+    // These analyses require a linearized CFG with all statements visible.
     AC.getCFGBuildOptions().setAllAlwaysAdd();
   } else {
     AC.getCFGBuildOptions()
-      .setAlwaysAdd(Stmt::BinaryOperatorClass)
-      .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
-      .setAlwaysAdd(Stmt::BlockExprClass)
-      .setAlwaysAdd(Stmt::CStyleCastExprClass)
-      .setAlwaysAdd(Stmt::DeclRefExprClass)
-      .setAlwaysAdd(Stmt::ImplicitCastExprClass)
-      .setAlwaysAdd(Stmt::UnaryOperatorClass);
+        .setAlwaysAdd(Stmt::BinaryOperatorClass)
+        .setAlwaysAdd(Stmt::CompoundAssignOperatorClass)
+        .setAlwaysAdd(Stmt::BlockExprClass)
+        .setAlwaysAdd(Stmt::CStyleCastExprClass)
+        .setAlwaysAdd(Stmt::DeclRefExprClass)
+        .setAlwaysAdd(Stmt::ImplicitCastExprClass)
+        .setAlwaysAdd(Stmt::UnaryOperatorClass);
   }
   if (EnableLifetimeSafetyAnalysis)
     AC.getCFGBuildOptions().AddLifetime = true;
@@ -3117,6 +3165,28 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings(
     Reporter.emitDiagnostics();
   }
 
+  // Gradual adoption: only run flow-sensitive nullability when the function
+  // opts in — either via -fnullability-default, an active assume_nonnull
+  // pragma, or explicit nullability annotations on the function signature.
+  // Computed here (not stored on Sema) to avoid scoping bugs when lambda
+  // bodies interleave with the enclosing function's processing.
+  if (EnableFlowNullability) {
+    bool FlowNullabilityForFunc = S.getLangOpts().getNullabilityDefault() !=
+                                      NullabilityKind::Unspecified ||
+                                  S.PP.getPragmaAssumeNonNullLoc().isValid();
+    if (!FlowNullabilityForFunc) {
+      if (const auto *FD = dyn_cast<FunctionDecl>(D))
+        FlowNullabilityForFunc = S.functionHasNullabilityAnnotations(FD);
+    }
+    if (FlowNullabilityForFunc && AC.getCFG()) {
+      llvm::TimeTraceScope TimeProfile("FlowNullabilityAnalysis");
+      FlowNullabilityReporter Reporter(S);
+      NullabilityKind Default = S.getLangOpts().getNullabilityDefault();
+      bool StrictMode = (Default != NullabilityKind::Unspecified);
+      runFlowNullabilityAnalysis(AC, Reporter, StrictMode, Default);
+    }
+  }
+
   // Check for violations of consumed properties.
   if (P.enableConsumedAnalysis) {
     consumed::ConsumedWarningsHandler WarningHandler(S);
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 98318fc597f36..e3833d3ff3bf1 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -680,7 +680,8 @@ void Sema::PrintStats() const {
 
 void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
                                                QualType SrcType,
-                                               SourceLocation Loc) {
+                                               SourceLocation Loc,
+                                               Expr *SrcExpr) {
   std::optional<NullabilityKind> ExprNullability = SrcType->getNullability();
   if (!ExprNullability || (*ExprNullability != NullabilityKind::Nullable &&
                            *ExprNullability != NullabilityKind::NullableResult))
@@ -690,9 +691,46 @@ void Sema::diagnoseNullableToNonnullConversion(QualType DstType,
   if (!TypeNullability || *TypeNullability != NullabilityKind::NonNull)
     return;
 
+  // When flow-sensitive nullability is enabled, the flow analysis provides
+  // strictly better coverage: it respects null checks (suppresses after
+  // narrowing), handles dynamic nullability (reset/move/reassignment), and
+  // works correctly under -fnullability-default=nullable. The type-based
+  // warning would only add false positives (e.g., after if (p) return p;
+  // where p's declared type is still _Nullable but the flow proves nonnull).
+  if (getLangOpts().FlowSensitiveNullability)
+    return;
+
   Diag(Loc, diag::warn_nullability_lost) << SrcType << DstType;
 }
 
+bool Sema::functionHasNullabilityAnnotations(const FunctionDecl *FD) const {
+  if (!FD || FD->isInvalidDecl())
+    return false;
+
+  // Check return type
+  QualType ReturnType = FD->getReturnType();
+  if (!ReturnType.isNull() && !ReturnType->isDependentType()) {
+    if (ReturnType->getNullability())
+      return true;
+  }
+
+  // Check parameters — during early function processing, parameters might
+  // not be fully set up, so guard with param_empty().
+  if (!FD->param_empty()) {
+    for (const ParmVarDecl *Param : FD->parameters()) {
+      if (!Param)
+        continue;
+      QualType ParamType = Param->getType();
+      if (!ParamType.isNull() && !ParamType->isDependentType()) {
+        if (ParamType->getNullability())
+          return true;
+      }
+    }
+  }
+
+  return false;
+}
+
 // Generate diagnostics when adding or removing effects in a type conversion.
 void Sema::diagnoseFunctionEffectConversion(QualType DstType, QualType SrcType,
                                             SourceLocation Loc) {
@@ -782,7 +820,9 @@ ExprResult Sema::ImpCastExprToType(Expr *E, QualType Ty,
          "can't cast prvalue to glvalue");
 #endif
 
-  diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc());
+  // Pass the source expression so flow-sensitive analysis can suppress the
+  // warning when the expression is provably non-null despite its declared type.
+  diagnoseNullableToNonnullConversion(Ty, E->getType(), E->getBeginLoc(), E);
   diagnoseZeroToNullptrConversion(Kind, E);
   if (Context.hasAnyFunctionEffects() && !isCast(CCK) &&
       Kind != CK_NullToPointer && Kind != CK_NullToMemberPointer)
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 2951fd09294d8..2145ff3223518 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -14142,6 +14142,25 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
     }
 
     Init = Result.getAs<Expr>();
+
+    // Note: this may fire in constexpr-if discarded branches during template
+    // instantiation. Suppressing that case cleanly requires tracking whether
+    // we're inside a discarded branch at declaration processing time, which
+    // Clang doesn't currently expose here. In practice, the scenario
+    // (explicit _Nonnull p = nullptr in a discarded branch) is rare.
+    if (VDecl && Init && getLangOpts().FlowSensitiveNullability) {
+      QualType VDeclType = VDecl->getType();
+      if (auto Nullability = VDeclType->getNullability()) {
+        if (*Nullability == NullabilityKind::NonNull) {
+          if (Init->isNullPointerConstant(Context,
+                                          Expr::NPC_ValueDependentIsNotNull)) {
+            Diag(Init->getBeginLoc(), diag::warn_null_init_nonnull)
+                << VDeclType << Init->getSourceRange();
+          }
+        }
+      }
+    }
+
     IsParenListInit = !InitSeq.steps().empty() &&
                       InitSeq.step_begin()->Kind ==
                           InitializationSequence::SK_ParenthesizedListInit;
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index f7e005a40363c..820efcfdf8dc7 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -5431,7 +5431,7 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType,
   // _Nullable type to a _Nonnull one, complain.
   if (!isCast(CCK))
     diagnoseNullableToNonnullConversion(ToType, InitialFromType,
-                                        From->getBeginLoc());
+                                        From->getBeginLoc(), From);
 
   return From;
 }
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index ede2b9beef49b..13b04aaadd3ee 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -7593,8 +7593,9 @@ PerformConstructorInitialization(Sema &S,
 
   // A smart pointer constructed from a nullable pointer is nullable.
   if (NumArgs == 1 && !Kind.isExplicitCast())
-    S.diagnoseNullableToNonnullConversion(
-        Entity.getType(), Args.front()->getType(), Kind.getLocation());
+    S.diagnoseNullableToNonnullConversion(Entity.getType(),
+                                          Args.front()->getType(),
+                                          Kind.getLocation(), Args.front());
 
   // Determine the arguments required to actually perform the constructor
   // call.
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 1ca340e8b72c7..6e554368c1789 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -15615,8 +15615,8 @@ ExprResult Sema::CreateOverloadedBinOp(SourceLocation OpLoc,
         // This won't be caught in the arg's initialization: the parameter to
         // the assignment operator is not marked nonnull.
         if (Op == OO_Equal)
-          diagnoseNullableToNonnullConversion(Args[0]->getType(),
-                                              Args[1]->getType(), OpLoc);
+          diagnoseNullableToNonnullConversion(
+              Args[0]->getType(), Args[1]->getType(), OpLoc, Args[1]);
 
         // Convert the arguments.
         if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FnDecl)) {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 846474fe94adf..6fade9f4fb02a 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -4463,7 +4463,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
     }
   } else {
     bool isFunctionOrMethod = false;
-    switch (auto context = state.getDeclarator().getContext()) {
+    switch (state.getDeclarator().getContext()) {
     case DeclaratorContext::ObjCParameter:
     case DeclaratorContext::ObjCResult:
     case DeclaratorContext::Prototype:
@@ -4504,12 +4504,24 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
         break;
 
       case PointerDeclaratorKind::SingleLevelPointer:
-        // Infer _Nonnull if we are in an assumes-nonnull region.
-        if (inAssumeNonNullRegion) {
+        // Infer nullability based on pragma or default mode
+        // Pragma takes precedence and works in all modes (including
+        // unspecified) Skip -fnullability-default for system headers to avoid
+        // false positives on std library code (e.g. std::chrono, vsnprintf).
+        // Explicit #pragma clang assume_nonnull still works in system headers.
+        if (inAssumeNonNullRegion ||
+            (!S.getSourceManager().isInSystemHeader(D.getBeginLoc()) &&
+             S.getLangOpts().getNullabilityDefault() !=
+                 NullabilityKind::Unspecified)) {
           complainAboutInferringWithinChunk = wrappingKind;
-          inferNullability = NullabilityKind::NonNull;
-          inferNullabilityCS = (context == DeclaratorContext::ObjCParameter ||
-                                context == DeclaratorContext::ObjCResult);
+          if (inAssumeNonNullRegion) {
+            inferNullability = NullabilityKind::NonNull;
+          } else {
+            // Use Unspecified instead of the raw default so the flow checker
+            // can distinguish explicit _Nullable from default-inferred.
+            inferNullability = NullabilityKind::Unspecified;
+          }
+          inferNullabilityCS = false;
         }
         break;
 
@@ -4541,6 +4553,15 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
             }
           }
         }
+        // For double-pointers (T**) without CF attrs, apply the same
+        // Unspecified default as SingleLevelPointer so the flow checker
+        // doesn't treat them as explicitly _Nullable.
+        if (!inferNullability && !inAssumeNonNullRegion &&
+            !S.getSourceManager().isInSystemHeader(D.getBeginLoc()) &&
+            S.getLangOpts().getNullabilityDefault() !=
+                NullabilityKind::Unspecified) {
+          inferNullability = NullabilityKind::Unspecified;
+        }
         break;
       }
       break;
@@ -4569,7 +4590,37 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
     case DeclaratorContext::FunctionalCast:
     case DeclaratorContext::RequiresExpr:
     case DeclaratorContext::Association:
-      // Don't infer in these contexts.
+      // Upstream: don't infer nullability in these contexts (locals,
+      // template args, casts, etc.).  When flow-sensitive nullability is
+      // active we silently tag single-level pointers as Unspecified so the
+      // flow checker can track them, but we never fire the consistency
+      // warning ("pointer is missing a nullability type specifier") here.
+      if (S.getLangOpts().FlowSensitiveNullability) {
+        auto wrappingKind = PointerWrappingDeclaratorKind::None;
+        switch (classifyPointerDeclarator(S, T, D, wrappingKind)) {
+        case PointerDeclaratorKind::NonPointer:
+        case PointerDeclaratorKind::MultiLevelPointer:
+        case PointerDeclaratorKind::CFErrorRefPointer:
+        case PointerDeclaratorKind::NSErrorPointerPointer:
+          break;
+
+        case PointerDeclaratorKind::SingleLevelPointer:
+          if (!inAssumeNonNullRegion &&
+              !S.getSourceManager().isInSystemHeader(D.getBeginLoc())) {
+            inferNullability = NullabilityKind::Unspecified;
+          }
+          inferNullabilityCS = false;
+          break;
+
+        case PointerDeclaratorKind::MaybePointerToCFRef:
+          if (!inAssumeNonNullRegion &&
+              !S.getSourceManager().isInSystemHeader(D.getBeginLoc())) {
+            inferNullability = NullabilityKind::Unspecified;
+          }
+          inferNullabilityCS = false;
+          break;
+        }
+      }
       break;
     }
   }
@@ -4657,7 +4708,12 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
   // If the type itself could have nullability but does not, infer pointer
   // nullability and perform consistency checking.
   if (S.CodeSynthesisContexts.empty()) {
-    if (shouldHaveNullability(T) && !T->getNullability()) {
+    // Skip conversion operators (operator T*()) — their return type is
+    // part of the operator's identity, and applying default nullability
+    // would change the type identity, breaking overload resolution and
+    // causing spurious diagnostics on the conversion result type.
+    if (D.getName().getKind() != UnqualifiedIdKind::IK_ConversionFunctionId &&
+        shouldHaveNullability(T) && !T->getNullability()) {
       if (isVaList(T)) {
         // Record that we've seen a pointer, but do nothing else.
         if (NumPointersRemaining > 0)
diff --git a/clang/test/Driver/nullsafe-flags-negative.c b/clang/test/Driver/nullsafe-flags-negative.c
new file mode 100644
index 0000000000000..4255fb162fff1
--- /dev/null
+++ b/clang/test/Driver/nullsafe-flags-negative.c
@@ -0,0 +1,31 @@
+// Negative driver tests for nullsafe flags.
+// Verifies flag interaction patterns and valid/invalid combinations.
+
+// === -fflow-sensitive-nullability without -fnullability-default ===
+// Should be accepted — defaults to unspecified.
+// RUN: %clang -### -fflow-sensitive-nullability %s 2>&1 | FileCheck -check-prefix=FLOW-ONLY %s
+// FLOW-ONLY: "-fflow-sensitive-nullability"
+
+// === All three valid values for -fnullability-default ===
+// RUN: %clang -### -fnullability-default=nullable %s 2>&1 | FileCheck -check-prefix=NULLABLE %s
+// RUN: %clang -### -fnullability-default=nonnull %s 2>&1 | FileCheck -check-prefix=NONNULL %s
+// RUN: %clang -### -fnullability-default=unspecified %s 2>&1 | FileCheck -check-prefix=UNSPEC %s
+// NULLABLE: "-fnullability-default=nullable"
+// NONNULL: "-fnullability-default=nonnull"
+// UNSPEC: "-fnullability-default=unspecified"
+
+// === Invalid -fnullability-default value is passed through to cc1 ===
+// RUN: %clang -### -fnullability-default=invalid %s 2>&1 | FileCheck -check-prefix=INVALID %s
+// INVALID: "-fnullability-default=invalid"
+
+// === cc1 rejects invalid -fnullability-default value ===
+// (tested in Sema/flow-nullability-warning-groups.cpp — cc1 tests can't live in Driver/)
+
+// === -fno-flow-sensitive-nullability disables the flag ===
+// RUN: %clang -### -fflow-sensitive-nullability -fno-flow-sensitive-nullability %s 2>&1 | FileCheck -check-prefix=NO-FLOW %s
+// NO-FLOW-NOT: "-fflow-sensitive-nullability"
+
+// === All flags together ===
+// RUN: %clang -### -fflow-sensitive-nullability -fnullability-default=nullable %s 2>&1 | FileCheck -check-prefix=ALL %s
+// ALL: "-fflow-sensitive-nullability"
+// ALL: "-fnullability-default=nullable"
diff --git a/clang/test/Driver/nullsafe-flags.c b/clang/test/Driver/nullsafe-flags.c
new file mode 100644
index 0000000000000..e73eae4215502
--- /dev/null
+++ b/clang/test/Driver/nullsafe-flags.c
@@ -0,0 +1,8 @@
+// RUN: %clang -### -fflow-sensitive-nullability %s 2>&1 | FileCheck -check-prefix=FLOW %s
+// RUN: %clang -### -fnullability-default=nullable %s 2>&1 | FileCheck -check-prefix=DEFAULT %s
+// RUN: %clang -### -fflow-sensitive-nullability -fnullability-default=nullable %s 2>&1 | FileCheck -check-prefix=BOTH %s
+
+// FLOW: "-fflow-sensitive-nullability"
+// DEFAULT: "-fnullability-default=nullable"
+// BOTH: "-fflow-sensitive-nullability"
+// BOTH: "-fnullability-default=nullable"
diff --git a/clang/test/Sema/flow-nullability-c.c b/clang/test/Sema/flow-nullability-c.c
new file mode 100644
index 0000000000000..1dca8c15ecb5e
--- /dev/null
+++ b/clang/test/Sema/flow-nullability-c.c
@@ -0,0 +1,636 @@
+// Consolidated C tests for flow-sensitive nullability analysis. Covers basic
+// narrowing, C-specific patterns (nested structs, restrict, compound literals,
+// flexible array members, malloc/free, container_of, goto cleanup), C idioms
+// (macros, callbacks, errno), and call invalidation semantics.
+//
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-nullable-to-nonnull-conversion -std=c11 %s -verify
+
+typedef __SIZE_TYPE__ size_t;
+typedef _Bool bool;
+#define true 1
+#define false 0
+#define NULL ((void *)0)
+#define offsetof(type, member) __builtin_offsetof(type, member)
+
+//===----------------------------------------------------------------------===//
+// Shared declarations
+//===----------------------------------------------------------------------===//
+
+struct Point {
+    int x;
+    int y;
+};
+
+struct Line {
+    struct Point * _Nullable start;
+    struct Point * _Nullable end;
+};
+
+struct Node {
+    int value;
+    struct Node * _Nullable next;
+    struct Node * _Nullable prev;
+};
+
+struct Buffer {
+    char * _Nullable data;
+    size_t len;
+    size_t cap;
+};
+
+struct Point * _Nullable getPoint(void);
+struct Node * _Nullable getNode(void);
+int getInt(void);
+
+// Simulated stdlib declarations
+void * _Nullable malloc(size_t);
+void * _Nullable calloc(size_t, size_t);
+void * _Nullable realloc(void * _Nullable, size_t);
+void free(void * _Nullable);
+void abort(void) __attribute__((noreturn));
+void exit(int) __attribute__((noreturn));
+
+//===----------------------------------------------------------------------===//
+// Basic narrowing
+//===----------------------------------------------------------------------===//
+
+void test_basic_star_deref_warns(int *p) {
+    *p = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_basic_star_after_check(int *p) {
+    if (p) {
+        *p = 42; // OK - narrowed
+    }
+}
+
+void test_basic_arrow_deref_warns(struct Node *p) {
+    p->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_basic_arrow_after_check(struct Node *p) {
+    if (p) {
+        p->value = 1; // OK
+    }
+}
+
+void test_basic_early_return(struct Node *p) {
+    if (!p) return;
+    p->value = 1; // OK - narrowed by early return
+}
+
+void test_basic_null_comparison(struct Node *p) {
+    if (p != 0) {
+        p->value = 1; // OK
+    }
+}
+
+void test_basic_linked_list(struct Node * _Nullable head) {
+    for (struct Node * _Nullable p = head; p; p = p->next) {
+        p->value = 0; // OK
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Nested struct pointer access
+//===----------------------------------------------------------------------===//
+
+void test_nested_struct(struct Line * _Nullable line) {
+    if (line && line->start) {
+        line->start->x = 1; // OK - both narrowed
+    }
+}
+
+void test_nested_not_checked(struct Line * _Nonnull line) {
+    line->start->x = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+//===----------------------------------------------------------------------===//
+// Double-linked list traversal
+//===----------------------------------------------------------------------===//
+
+void test_doubly_linked(struct Node * _Nullable head) {
+    for (struct Node * _Nullable p = head; p; p = p->next) {
+        p->value = 0; // OK - narrowed by loop condition
+        if (p->prev) {
+            p->prev->value = -1; // OK - narrowed
+        }
+    }
+}
+
+void test_reverse_traversal(struct Node * _Nullable tail) {
+    struct Node * _Nullable p = tail;
+    while (p) {
+        p->value = 0; // OK
+        p = p->prev;
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// restrict pointer
+//===----------------------------------------------------------------------===//
+
+void test_restrict(int * restrict p) {
+    *p = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_restrict_checked(int * restrict _Nullable p) {
+    if (p)
+        *p = 42; // OK
+}
+
+//===----------------------------------------------------------------------===//
+// Compound literal
+//===----------------------------------------------------------------------===//
+
+void test_compound_literal(void) {
+    int *p = &(int){42};
+    *p = 0; // OK - address-of compound literal is nonnull
+}
+
+//===----------------------------------------------------------------------===//
+// Designated initializer
+//===----------------------------------------------------------------------===//
+
+void test_designated_init(void) {
+    struct Point pt = {.x = 1, .y = 2};
+    struct Point *pp = &pt;
+    pp->x = 3; // OK - address-of
+}
+
+//===----------------------------------------------------------------------===//
+// Array of pointers
+//===----------------------------------------------------------------------===//
+
+void test_pointer_array(struct Node * _Nullable * _Nonnull nodes, int n) {
+    for (int i = 0; i < n; i++) {
+        struct Node * _Nullable node = nodes[i];
+        if (node) {
+            node->value = i; // OK - narrowed via local variable
+        }
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Multiple sequential checks
+//===----------------------------------------------------------------------===//
+
+void test_sequential_checks(struct Node * _Nullable a,
+                            struct Node * _Nullable b,
+                            struct Node * _Nullable c) {
+    if (!a) return;
+    if (!b) return;
+    if (!c) return;
+    a->value = b->value + c->value; // OK - all narrowed
+}
+
+//===----------------------------------------------------------------------===//
+// Null check with comparison operators
+//===----------------------------------------------------------------------===//
+
+void test_comparison_styles(struct Node *p) {
+    if (p != 0) {
+        p->value = 1; // OK
+    }
+}
+
+void test_comparison_null_macro(struct Node *p) {
+    if (p != ((void*)0)) {
+        p->value = 1; // OK
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Function returning _Nonnull
+//===----------------------------------------------------------------------===//
+
+struct Node * _Nonnull createNode(void);
+
+void test_nonnull_return(void) {
+    struct Node *n = createNode();
+    n->value = 1; // OK - _Nonnull return
+}
+
+//===----------------------------------------------------------------------===//
+// Void pointer cast patterns
+//===----------------------------------------------------------------------===//
+
+void test_void_ptr_cast(void * _Nonnull raw) {
+    struct Node *n = (struct Node *)raw;
+    n->value = 1; // OK - nonnull source
+}
+
+void test_void_ptr_nullable(void * _Nullable raw) {
+    struct Node *n = (struct Node *)raw;
+    n->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+//===----------------------------------------------------------------------===//
+// Conditional operator
+//===----------------------------------------------------------------------===//
+
+void test_cond_op(struct Node * _Nullable p, struct Node * _Nullable q) {
+    struct Node *r = p ? p : q;
+    if (r)
+        r->value = 1; // OK - narrowed
+}
+
+//===----------------------------------------------------------------------===//
+// Nested conditionals
+//===----------------------------------------------------------------------===//
+
+void test_nested_cond(struct Node * _Nullable p) {
+    if (p) {
+        if (p->next) {
+            if (p->next->next) {
+                p->next->next->value = 0; // OK - all narrowed
+            }
+        }
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Goto-based cleanup pattern
+//===----------------------------------------------------------------------===//
+
+int test_goto_cleanup(struct Node * _Nullable p) {
+    int result = -1;
+    if (!p) goto out;
+    result = p->value; // OK - narrowed
+out:
+    return result;
+}
+
+//===----------------------------------------------------------------------===//
+// Switch with null check in cases
+//===----------------------------------------------------------------------===//
+
+void test_switch_null_check(struct Node * _Nullable p, int choice) {
+    switch (choice) {
+    case 0:
+        if (p)
+            p->value = 0; // OK
+        break;
+    case 1:
+        if (!p) return;
+        p->value = 1; // OK
+        break;
+    default:
+        break;
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Comma operator
+//===----------------------------------------------------------------------===//
+
+void test_comma(struct Node * _Nullable p) {
+    if (!p) return;
+    (void)p->value; // OK - narrowed
+}
+
+//===----------------------------------------------------------------------===//
+// sizeof does not evaluate
+//===----------------------------------------------------------------------===//
+
+void test_sizeof_unevaluated(struct Node * _Nullable p) {
+    int s = sizeof(p->value); // OK - sizeof is unevaluated
+    (void)s;
+}
+
+//===----------------------------------------------------------------------===//
+// Pointer subtraction
+//===----------------------------------------------------------------------===//
+
+void test_ptr_subtraction(int *a, int *b) {
+    long diff = a - b; // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+    (void)diff;
+}
+
+//===----------------------------------------------------------------------===//
+// Macro-heavy null-check patterns
+//===----------------------------------------------------------------------===//
+
+#define CHECK_NULL(ptr) do { if (!(ptr)) return; } while(0)
+#define CHECK_NULL_RET(ptr, ret) do { if (!(ptr)) return (ret); } while(0)
+#define ASSERT_NONNULL(ptr) do { if (!(ptr)) abort(); } while(0)
+#define DEREF(p) ((p)->value)
+#define SAFE_DEREF(p, fallback) ((p) ? (p)->value : (fallback))
+
+void test_check_null_macro(struct Node *p) {
+    CHECK_NULL(p);
+    p->value = 1; // OK - macro expanded to if(!p) return
+}
+
+int test_check_null_ret_macro(struct Node *p) {
+    CHECK_NULL_RET(p, -1);
+    return p->value; // OK
+}
+
+void test_assert_nonnull_macro(struct Node *p) {
+    ASSERT_NONNULL(p);
+    p->value = 1; // OK - abort() is noreturn
+}
+
+void test_deref_macro(struct Node *p) {
+    int v = DEREF(p); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    (void)v;
+}
+
+void test_deref_macro_guarded(struct Node *p) {
+    if (p) {
+        int v = DEREF(p); // OK - narrowed before macro
+        (void)v;
+    }
+}
+
+void test_safe_deref_macro(struct Node *p) {
+    int v = SAFE_DEREF(p, -1); // OK - ternary checks p
+    (void)v;
+}
+
+//===----------------------------------------------------------------------===//
+// malloc/free patterns
+//===----------------------------------------------------------------------===//
+
+void test_malloc_no_check(void) {
+    struct Node * _Nullable n = malloc(sizeof(struct Node));
+    n->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    free(n);
+}
+
+void test_malloc_checked(void) {
+    struct Node *n = (struct Node *)malloc(sizeof(struct Node));
+    if (!n) return;
+    n->value = 1; // OK - narrowed
+    n->next = NULL;
+    free(n);
+}
+
+void test_malloc_abort(void) {
+    struct Node *n = (struct Node *)malloc(sizeof(struct Node));
+    if (!n) abort();
+    n->value = 1; // OK - abort is noreturn
+    free(n);
+}
+
+void test_calloc_checked(void) {
+    struct Node *n = (struct Node *)calloc(1, sizeof(struct Node));
+    if (!n) return;
+    n->value = 1; // OK
+    free(n);
+}
+
+//===----------------------------------------------------------------------===//
+// realloc pattern
+//===----------------------------------------------------------------------===//
+
+void test_realloc(struct Buffer * _Nonnull buf) {
+    char * _Nullable new_data = (char *)realloc(buf->data, buf->cap * 2);
+    if (!new_data) return;
+    buf->data = new_data;
+    buf->cap *= 2;
+}
+
+//===----------------------------------------------------------------------===//
+// Linked list construction and traversal
+//===----------------------------------------------------------------------===//
+
+struct Node * _Nullable list_prepend(struct Node * _Nullable head, int val) {
+    struct Node *n = (struct Node *)malloc(sizeof(struct Node));
+    if (!n) return head;
+    n->value = val; // OK - checked
+    n->next = head;
+    return n;
+}
+
+void list_free(struct Node * _Nullable head) {
+    struct Node * _Nullable p = head;
+    while (p) {
+        struct Node * _Nullable next_node = p->next; // OK - p narrowed
+        free(p);
+        p = next_node;
+    }
+}
+
+int list_sum(struct Node * _Nullable head) {
+    int sum = 0;
+    for (struct Node * _Nullable p = head; p; p = p->next) {
+        sum += p->value; // OK - narrowed by loop condition
+    }
+    return sum;
+}
+
+//===----------------------------------------------------------------------===//
+// Callback / function pointer patterns
+//===----------------------------------------------------------------------===//
+
+typedef void (*node_visitor_fn)(struct Node * _Nonnull, void * _Nullable);
+
+void list_foreach(struct Node * _Nullable head, node_visitor_fn _Nonnull fn, void * _Nullable ctx) {
+    for (struct Node * _Nullable p = head; p; p = p->next) {
+        fn(p, ctx); // OK - p narrowed
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// errno-style error checking
+//===----------------------------------------------------------------------===//
+
+struct File;
+struct File * _Nullable file_open(const char * _Nonnull path);
+int file_read(struct File * _Nonnull f, char * _Nonnull buf, int len);
+void file_close(struct File * _Nonnull f);
+
+int test_errno_pattern(void) {
+    struct File * _Nullable f = file_open("/tmp/test");
+    if (!f) return -1;
+    char buf[256];
+    int n = file_read(f, buf, 256); // OK
+    file_close(f); // OK
+    return n;
+}
+
+//===----------------------------------------------------------------------===//
+// container_of macro pattern
+//===----------------------------------------------------------------------===//
+
+#define container_of(ptr, type, member) \
+    ((type *)((char *)(ptr) - offsetof(type, member)))
+
+struct list_head {
+    struct list_head * _Nullable next;
+    struct list_head * _Nullable prev;
+};
+
+struct my_item {
+    int data;
+    struct list_head link;
+};
+
+void test_container_of(struct list_head * _Nullable pos) {
+    if (!pos) return;
+    struct my_item *item = container_of(pos, struct my_item, link);
+    item->data = 42; // OK - arithmetic on non-null pointer
+}
+
+//===----------------------------------------------------------------------===//
+// Multi-level goto cleanup
+//===----------------------------------------------------------------------===//
+
+int test_multi_level_cleanup(void) {
+    int ret = -1;
+    struct Node *a = (struct Node *)malloc(sizeof(struct Node));
+    if (!a) goto out;
+
+    struct Node *b = (struct Node *)malloc(sizeof(struct Node));
+    if (!b) goto free_a;
+
+    a->value = 1; // OK - narrowed past goto
+    b->value = 2; // OK - narrowed past goto
+    a->next = b;
+    ret = a->value + b->value;
+
+free_a:
+    free(a);
+out:
+    return ret;
+}
+
+//===----------------------------------------------------------------------===//
+// Bitfield struct with nullable pointer
+//===----------------------------------------------------------------------===//
+
+struct Options {
+    unsigned verbose : 1;
+    unsigned debug : 1;
+    struct Node * _Nullable config;
+};
+
+void test_bitfield_struct(struct Options * _Nonnull opts) {
+    if (opts->config) {
+        opts->config->value = opts->verbose; // OK - narrowed
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Null check via helper function (intraprocedural limitation)
+//===----------------------------------------------------------------------===//
+
+static bool is_valid(const struct Node * _Nullable p) {
+    return p != NULL;
+}
+
+void test_helper_check(struct Node *p) {
+    // The analysis can't see inside helper functions -- accepted limitation.
+    if (is_valid(p)) {
+        p->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// __builtin_expect / LIKELY / UNLIKELY macros
+//===----------------------------------------------------------------------===//
+
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+
+void test_likely_macro(struct Node *p) {
+    if (LIKELY(p != NULL)) {
+        p->value = 1; // OK
+    }
+}
+
+void test_unlikely_null(struct Node *p) {
+    if (UNLIKELY(p == NULL)) return;
+    p->value = 1; // OK
+}
+
+//===----------------------------------------------------------------------===//
+// Flexible array member
+//===----------------------------------------------------------------------===//
+
+struct FlexArray {
+    int count;
+    struct Node * _Nullable items[];
+};
+
+void test_flex_array(struct FlexArray * _Nonnull fa) {
+    for (int i = 0; i < fa->count; i++) {
+        struct Node * _Nullable item = fa->items[i];
+        if (item) {
+            item->value = i; // OK
+        }
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// void** output parameter pattern
+//===----------------------------------------------------------------------===//
+
+int get_node_out(struct Node * _Nullable * _Nonnull out);
+
+void test_output_param(void) {
+    struct Node * _Nullable n = NULL;
+    if (get_node_out(&n) == 0 && n) {
+        n->value = 42; // OK - checked via &&
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Static assert + null check
+//===----------------------------------------------------------------------===//
+
+_Static_assert(sizeof(struct Node) > 0, "Node must have size");
+
+void test_with_static_assert(struct Node *p) {
+    _Static_assert(sizeof(*p) == sizeof(struct Node), "size match");
+    if (p) {
+        p->value = 1; // OK
+    }
+}
+
+//===----------------------------------------------------------------------===//
+// Call invalidation: function calls do NOT invalidate narrowing
+//===----------------------------------------------------------------------===//
+// Functions receive a copy of pointer arguments, so they cannot modify
+// the original pointer variable to make it null.
+
+void takes_int(int x);
+void takes_ptr(int *p);
+
+void test_narrowing_preserved_after_call(int *p) {
+    if (p) {
+        takes_int(42);
+        *p = 1; // OK - p is still nonnull
+    }
+}
+
+void test_narrowing_preserved_pass_ptr(int *p) {
+    if (p) {
+        takes_ptr(p);
+        *p = 1; // OK - pass by value
+    }
+}
+
+void test_multiple_calls(int *p, int *q) {
+    if (p && q) {
+        takes_ptr(p);
+        takes_ptr(q);
+        takes_int(42);
+        *p = 1; // OK - narrowing preserved through all calls
+        *q = 2; // OK
+    }
+}
+
+// Known false negative: passing a pointer's address lets the callee
+// set *out = NULL, invalidating narrowing. The analysis intentionally
+// does not invalidate on address-taken (matching ThreadSafety's approach).
+void nullify(int **out);
+
+void test_address_taken_false_negative(int *p) {
+    if (p) {
+        nullify(&p);
+        *p = 1; // no warning - known false negative
+    }
+}
diff --git a/clang/test/SemaCXX/flow-nullability-adoption.cpp b/clang/test/SemaCXX/flow-nullability-adoption.cpp
new file mode 100644
index 0000000000000..8c3367b87007d
--- /dev/null
+++ b/clang/test/SemaCXX/flow-nullability-adoption.cpp
@@ -0,0 +1,784 @@
+// Consolidated adoption, configuration, and meta tests for flow-sensitive
+// nullability analysis. Covers gradual adoption gating,
+// false-positive suppression, type identity preservation, and performance
+// stress patterns.
+//
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -std=c++17 %s -verify
+// UNSUPPORTED: asan, msan, ubsan
+
+//===----------------------------------------------------------------------===//
+// Shared declarations
+//===----------------------------------------------------------------------===//
+
+struct Node {
+    int value;
+    Node * _Nullable next;
+    Node * _Nullable left;
+    Node * _Nullable right;
+};
+
+struct Entity {
+    int x;
+    int value() const { return x; }
+};
+
+Node * _Nullable getNode();
+Entity * _Nullable getHead();
+Entity * _Nullable getChest();
+Entity *getEntityUnannotated();
+int getInt();
+
+//===----------------------------------------------------------------------===//
+// Gradual adoption: per-function gating
+//===----------------------------------------------------------------------===//
+// Adapted from gradual-adoption tests. Under -fnullability-default=nullable,
+// all pointers default to nullable so analysis is always active. We wrap tests
+// in assume_nonnull to mirror how real code opts into the analysis.
+
+// Outside any pragma, with nullable default, unannotated params ARE nullable.
+void test_adoption_outside_pragma_explicit_nullable(Entity * _Nullable p) {
+    p->x = 1;              // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    (*p).x = 1;            // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    getHead()->x = 1;      // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull begin
+
+void test_adoption_explicit_nullable_arrow(Entity * _Nullable p) {
+    p->x = 1;              // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_adoption_explicit_nullable_arrow_checked(Entity * _Nullable p) {
+    if (!p) return;
+    p->x = 1;              // OK - narrowed to nonnull
+}
+
+void test_adoption_explicit_nullable_star(Entity * _Nullable p) {
+    (*p).x = 1;            // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_adoption_explicit_nullable_star_checked(Entity * _Nullable p) {
+    if (!p) return;
+    (*p).x = 1;            // OK - narrowed to nonnull
+}
+
+void test_adoption_chained_nullable_arrow() {
+    getHead()->x = 1;      // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_adoption_chained_nullable_method() {
+    int v = getHead()->value(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// getEntityUnannotated() is declared outside assume_nonnull, so under
+// -fnullability-default=nullable its return type is nullable. Under
+// -fnullability-default=unspecified (the original gradual-adoption test),
+// it would NOT warn. This function validates nullable-mode behavior.
+Entity * _Nonnull getEntityNonnull();
+
+void test_adoption_unannotated_no_warn() {
+    // Use a _Nonnull-declared function to mirror the assume_nonnull behavior
+    Entity *e = getEntityNonnull();
+    e->x = 1;              // OK - nonnull return
+    (*e).x = 1;            // OK
+}
+
+void test_adoption_unannotated_param_no_warn(Entity *p) {
+    p->x = 1;              // OK - nonnull via pragma
+}
+
+// Lambda scoping: analysis must still run for outer function.
+// Regression test: lambda bodies call ActOnStartOfFunctionDef, which must
+// not clobber the per-function analysis decision for the enclosing function.
+
+void test_adoption_lambda_no_clobber(Entity * _Nullable p) {
+    auto f = [](int x) { return x + 1; };
+    (void)f(1);
+    p->x = 1;              // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_adoption_nested_lambda_scoping(Entity * _Nullable p) {
+    auto outer = [](int x) {
+        auto inner = [](int y) { return y; };
+        return inner(x);
+    };
+    (void)outer(1);
+    (*p).x = 1;            // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+
+//===----------------------------------------------------------------------===//
+// False-positive regression suite
+//===----------------------------------------------------------------------===//
+// Every test case in this section must produce NO warnings. These represent
+// common C++ patterns that an overly-aggressive analysis might flag.
+
+#pragma clang assume_nonnull begin
+
+// --- Conditional initialization on all paths ---
+
+void test_fp_conditional_init(bool cond) {
+    int x = 0, y = 0;
+    int *p;
+    if (cond) {
+        p = &x;
+    } else {
+        p = &y;
+    }
+    (void)*p; // OK - assigned nonnull on both paths
+}
+
+// --- Static local variable ---
+
+void test_fp_static_local() {
+    static int x = 42;
+    int *p = &x;
+    (void)*p; // OK - address-of is always nonnull
+}
+
+// --- Global variable access ---
+
+int g_fp_value = 0;
+
+void test_fp_global_addr() {
+    int *p = &g_fp_value;
+    (void)*p; // OK - address-of
+}
+
+// --- Function pointer call ---
+
+typedef int (*IntFn)(int);
+
+void test_fp_fn_ptr(IntFn fn) {
+    int result = fn(42); // OK - no * dereference
+}
+
+// --- Chained method calls on nonnull ---
+
+struct Builder {
+    Builder *setX(int) { return this; }
+    Builder *setY(int) { return this; }
+    int build() { return 0; }
+};
+
+void test_fp_builder_pattern() {
+    Builder b;
+    b.setX(1)->setY(2)->build(); // OK - this is nonnull
+}
+
+// --- Address-of array element ---
+
+void test_fp_array_element_addr() {
+    int arr[10];
+    int *p = &arr[5];
+    (void)*p; // OK - address-of
+}
+
+// --- Pointer to member of stack object ---
+
+void test_fp_member_addr() {
+    Node n;
+    int *p = &n.value;
+    (void)*p; // OK - address-of
+}
+
+// --- Ternary with nonnull on both sides ---
+
+void test_fp_ternary_both_nonnull(bool cond) {
+    int x = 1, y = 2;
+    int *p = cond ? &x : &y;
+    (void)*p; // OK - nonnull on both branches
+}
+
+// --- Cast of nonnull ---
+
+void test_fp_cast_nonnull() {
+    int x = 42;
+    void *vp = &x;
+    int *ip = static_cast<int *>(vp);
+    (void)*ip; // OK - source was nonnull (address-of)
+}
+
+// --- new expression ---
+
+void test_fp_throwing_new() {
+    int *p = new int(42);
+    (void)*p; // OK - throwing new never returns null
+}
+
+// --- Multiple checks, then use ---
+
+void test_fp_multi_check(Node * _Nullable a, Node * _Nullable b, Node * _Nullable c) {
+    if (a && b && c) {
+        (void)a->value; // OK
+        (void)b->value; // OK
+        (void)c->value; // OK
+    }
+}
+
+// --- Reassign to nonnull after nullable ---
+
+void test_fp_reassign_nonnull() {
+    int x;
+    int * _Nullable p = nullptr;
+    p = &x;
+    (void)*p; // OK - reassigned to nonnull
+}
+
+// --- Loop variable always nonnull ---
+
+void test_fp_loop_var() {
+    int arr[10];
+    for (int i = 0; i < 10; i++) {
+        int *p = &arr[i];
+        (void)*p; // OK - address-of
+    }
+}
+
+// --- Nested struct access on nonnull ---
+
+struct Outer {
+    Node node;
+};
+
+void test_fp_nested_nonnull_access() {
+    Outer o;
+    int v = o.node.value; // OK - dot access on stack object
+}
+
+// --- Pointer arithmetic on nonnull ---
+
+void test_fp_ptr_arith() {
+    int arr[10];
+    int *p = arr;
+    int *q = arr + 5;
+    (void)*q; // OK
+}
+
+// --- Reference binding ---
+
+void test_fp_reference(int * _Nonnull p) {
+    int &ref = *p; // OK - _Nonnull
+    ref = 42;
+}
+
+// --- Comma operator with pointer ---
+
+void test_fp_comma_op() {
+    int x;
+    int *p = (getInt(), &x);
+    (void)*p; // OK - comma evaluates to &x which is nonnull
+}
+
+// --- Narrowing survives function calls ---
+
+void fp_external_fn();
+
+void test_fp_narrowing_survives_call(Node * _Nullable p) {
+    if (!p) return;
+    fp_external_fn();
+    (void)p->value; // OK - function call doesn't invalidate narrowing
+}
+
+// --- sizeof/alignof don't dereference ---
+
+void test_fp_sizeof_no_deref(Node * _Nullable p) {
+    auto s = sizeof(*p); // OK - sizeof doesn't evaluate its operand
+    (void)s;
+}
+
+// --- decltype doesn't dereference ---
+
+void test_fp_decltype_no_deref(Node * _Nullable p) {
+    using T = decltype(p->value); // OK - decltype is unevaluated
+    T x = 0;
+    (void)x;
+}
+
+// --- this pointer in member functions ---
+
+struct FPObj {
+    int x;
+    void method() {
+        this->x = 1; // OK - this is never null
+        (*this).x = 2; // OK - *this is suppressed
+    }
+};
+
+// --- Non-std iterator dereference ---
+
+struct FPIterator {
+    Node *current;
+    Node &operator*() { return *current; }
+    Node *operator->() { return current; }
+};
+
+void test_fp_iterator_deref(FPIterator it) {
+    (void)it->value; // OK - non-std operator-> is not checked
+}
+
+#pragma clang assume_nonnull end
+
+//===----------------------------------------------------------------------===//
+// Type identity: nullability must not affect type system
+//===----------------------------------------------------------------------===//
+// Nullability qualifiers are type sugar in Clang -- they don't participate
+// in template argument deduction, std::is_same, decltype, or overload
+// resolution.
+
+template<typename T, typename U>
+struct is_same { static constexpr bool value = false; };
+template<typename T>
+struct is_same<T, T> { static constexpr bool value = true; };
+
+#pragma clang assume_nonnull begin
+
+void test_ti_decltype_local() {
+    int x = 0;
+    int *p = &x;
+    static_assert(is_same<decltype(p), int*>::value, "");
+}
+
+void test_ti_decltype_param(int *p) {
+    static_assert(is_same<decltype(p), int*>::value, "");
+}
+
+void test_ti_auto_deduction() {
+    int x = 0;
+    auto p = &x;
+    static_assert(is_same<decltype(p), int*>::value, "");
+}
+
+template<typename T>
+void ti_accept(T) {
+    static_assert(is_same<T, int*>::value, "");
+}
+
+void test_ti_template_deduction() {
+    int x = 0;
+    int *p = &x;
+    ti_accept(p);
+}
+
+template<typename T> void ti_accept_ptr(T*) {}
+
+void test_ti_explicit_template_arg() {
+    int x = 0;
+    int *p = &x;
+    ti_accept_ptr<int>(p);
+}
+
+void test_ti_nullability_is_sugar() {
+    int x;
+    int *bare = &x;
+    int * _Nullable nullable = &x;
+    int * _Nonnull nonnull = &x;
+    int * _Null_unspecified unspec = &x;
+
+    static_assert(is_same<decltype(bare), decltype(nullable)>::value, "");
+    static_assert(is_same<decltype(bare), decltype(nonnull)>::value, "");
+    static_assert(is_same<decltype(bare), decltype(unspec)>::value, "");
+}
+
+auto ti_make_ptr() {
+    int *p = new int(42);
+    return p;
+}
+
+void test_ti_return_type_deduction() {
+    static_assert(is_same<decltype(ti_make_ptr()), int*>::value, "");
+}
+
+void test_ti_const_ptr() {
+    const int x = 0;
+    const int *p = &x;
+    static_assert(is_same<decltype(p), const int*>::value, "");
+}
+
+void test_ti_ptr_to_ptr() {
+    int x;
+    int *p = &x;
+    int **pp = &p;
+    static_assert(is_same<decltype(pp), int**>::value, "");
+}
+
+#pragma clang assume_nonnull end
+
+//===----------------------------------------------------------------------===//
+// Performance stress tests
+//===----------------------------------------------------------------------===//
+// Generates a large amount of work for the analysis. Must compile within the
+// default lit timeout. If the analysis has a complexity regression, this test
+// will time out. Modeled after clang/test/Analysis/runtime-regression.c.
+
+#pragma clang assume_nonnull begin
+
+// --- Pattern 1: Many sequential null checks (tests linear scaling) ---
+
+#define CHECK_AND_USE(N) \
+    { Node * _Nullable p##N = getNode(); if (p##N) p##N->value = N; }
+
+void stress_sequential() {
+    CHECK_AND_USE(0)  CHECK_AND_USE(1)  CHECK_AND_USE(2)  CHECK_AND_USE(3)
+    CHECK_AND_USE(4)  CHECK_AND_USE(5)  CHECK_AND_USE(6)  CHECK_AND_USE(7)
+    CHECK_AND_USE(8)  CHECK_AND_USE(9)  CHECK_AND_USE(10) CHECK_AND_USE(11)
+    CHECK_AND_USE(12) CHECK_AND_USE(13) CHECK_AND_USE(14) CHECK_AND_USE(15)
+    CHECK_AND_USE(16) CHECK_AND_USE(17) CHECK_AND_USE(18) CHECK_AND_USE(19)
+    CHECK_AND_USE(20) CHECK_AND_USE(21) CHECK_AND_USE(22) CHECK_AND_USE(23)
+    CHECK_AND_USE(24) CHECK_AND_USE(25) CHECK_AND_USE(26) CHECK_AND_USE(27)
+    CHECK_AND_USE(28) CHECK_AND_USE(29) CHECK_AND_USE(30) CHECK_AND_USE(31)
+    CHECK_AND_USE(32) CHECK_AND_USE(33) CHECK_AND_USE(34) CHECK_AND_USE(35)
+    CHECK_AND_USE(36) CHECK_AND_USE(37) CHECK_AND_USE(38) CHECK_AND_USE(39)
+    CHECK_AND_USE(40) CHECK_AND_USE(41) CHECK_AND_USE(42) CHECK_AND_USE(43)
+    CHECK_AND_USE(44) CHECK_AND_USE(45) CHECK_AND_USE(46) CHECK_AND_USE(47)
+    CHECK_AND_USE(48) CHECK_AND_USE(49) CHECK_AND_USE(50) CHECK_AND_USE(51)
+    CHECK_AND_USE(52) CHECK_AND_USE(53) CHECK_AND_USE(54) CHECK_AND_USE(55)
+    CHECK_AND_USE(56) CHECK_AND_USE(57) CHECK_AND_USE(58) CHECK_AND_USE(59)
+    CHECK_AND_USE(60) CHECK_AND_USE(61) CHECK_AND_USE(62) CHECK_AND_USE(63)
+    CHECK_AND_USE(64) CHECK_AND_USE(65) CHECK_AND_USE(66) CHECK_AND_USE(67)
+    CHECK_AND_USE(68) CHECK_AND_USE(69) CHECK_AND_USE(70) CHECK_AND_USE(71)
+    CHECK_AND_USE(72) CHECK_AND_USE(73) CHECK_AND_USE(74) CHECK_AND_USE(75)
+    CHECK_AND_USE(76) CHECK_AND_USE(77) CHECK_AND_USE(78) CHECK_AND_USE(79)
+    CHECK_AND_USE(80) CHECK_AND_USE(81) CHECK_AND_USE(82) CHECK_AND_USE(83)
+    CHECK_AND_USE(84) CHECK_AND_USE(85) CHECK_AND_USE(86) CHECK_AND_USE(87)
+    CHECK_AND_USE(88) CHECK_AND_USE(89) CHECK_AND_USE(90) CHECK_AND_USE(91)
+    CHECK_AND_USE(92) CHECK_AND_USE(93) CHECK_AND_USE(94) CHECK_AND_USE(95)
+    CHECK_AND_USE(96) CHECK_AND_USE(97) CHECK_AND_USE(98) CHECK_AND_USE(99)
+}
+
+// --- Pattern 2: Branch fan-out (tests intersect scaling) ---
+
+#define BRANCH(N) if (getInt()) { s##N = &nodes[N]; }
+
+void stress_fanout() {
+    Node nodes[50];
+    Node * _Nullable s0 = nullptr, * _Nullable s1 = nullptr;
+    Node * _Nullable s2 = nullptr, * _Nullable s3 = nullptr;
+    Node * _Nullable s4 = nullptr, * _Nullable s5 = nullptr;
+    Node * _Nullable s6 = nullptr, * _Nullable s7 = nullptr;
+    Node * _Nullable s8 = nullptr, * _Nullable s9 = nullptr;
+    Node * _Nullable s10 = nullptr, * _Nullable s11 = nullptr;
+    Node * _Nullable s12 = nullptr, * _Nullable s13 = nullptr;
+    Node * _Nullable s14 = nullptr, * _Nullable s15 = nullptr;
+    Node * _Nullable s16 = nullptr, * _Nullable s17 = nullptr;
+    Node * _Nullable s18 = nullptr, * _Nullable s19 = nullptr;
+    Node * _Nullable s20 = nullptr, * _Nullable s21 = nullptr;
+    Node * _Nullable s22 = nullptr, * _Nullable s23 = nullptr;
+    Node * _Nullable s24 = nullptr, * _Nullable s25 = nullptr;
+    Node * _Nullable s26 = nullptr, * _Nullable s27 = nullptr;
+    Node * _Nullable s28 = nullptr, * _Nullable s29 = nullptr;
+    Node * _Nullable s30 = nullptr, * _Nullable s31 = nullptr;
+    Node * _Nullable s32 = nullptr, * _Nullable s33 = nullptr;
+    Node * _Nullable s34 = nullptr, * _Nullable s35 = nullptr;
+    Node * _Nullable s36 = nullptr, * _Nullable s37 = nullptr;
+    Node * _Nullable s38 = nullptr, * _Nullable s39 = nullptr;
+    Node * _Nullable s40 = nullptr, * _Nullable s41 = nullptr;
+    Node * _Nullable s42 = nullptr, * _Nullable s43 = nullptr;
+    Node * _Nullable s44 = nullptr, * _Nullable s45 = nullptr;
+    Node * _Nullable s46 = nullptr, * _Nullable s47 = nullptr;
+    Node * _Nullable s48 = nullptr, * _Nullable s49 = nullptr;
+
+    BRANCH(0)  BRANCH(1)  BRANCH(2)  BRANCH(3)  BRANCH(4)
+    BRANCH(5)  BRANCH(6)  BRANCH(7)  BRANCH(8)  BRANCH(9)
+    BRANCH(10) BRANCH(11) BRANCH(12) BRANCH(13) BRANCH(14)
+    BRANCH(15) BRANCH(16) BRANCH(17) BRANCH(18) BRANCH(19)
+    BRANCH(20) BRANCH(21) BRANCH(22) BRANCH(23) BRANCH(24)
+    BRANCH(25) BRANCH(26) BRANCH(27) BRANCH(28) BRANCH(29)
+    BRANCH(30) BRANCH(31) BRANCH(32) BRANCH(33) BRANCH(34)
+    BRANCH(35) BRANCH(36) BRANCH(37) BRANCH(38) BRANCH(39)
+    BRANCH(40) BRANCH(41) BRANCH(42) BRANCH(43) BRANCH(44)
+    BRANCH(45) BRANCH(46) BRANCH(47) BRANCH(48) BRANCH(49)
+}
+
+// --- Pattern 3: Many small functions (realistic workload) ---
+
+#define SMALL_FN(N) \
+    void small_fn_##N(Node * _Nullable p) { \
+        if (!p) return; \
+        p->value = N; \
+        if (p->next) p->next->value = N + 1; \
+    }
+
+SMALL_FN(0)  SMALL_FN(1)  SMALL_FN(2)  SMALL_FN(3)  SMALL_FN(4)
+SMALL_FN(5)  SMALL_FN(6)  SMALL_FN(7)  SMALL_FN(8)  SMALL_FN(9)
+SMALL_FN(10) SMALL_FN(11) SMALL_FN(12) SMALL_FN(13) SMALL_FN(14)
+SMALL_FN(15) SMALL_FN(16) SMALL_FN(17) SMALL_FN(18) SMALL_FN(19)
+SMALL_FN(20) SMALL_FN(21) SMALL_FN(22) SMALL_FN(23) SMALL_FN(24)
+SMALL_FN(25) SMALL_FN(26) SMALL_FN(27) SMALL_FN(28) SMALL_FN(29)
+SMALL_FN(30) SMALL_FN(31) SMALL_FN(32) SMALL_FN(33) SMALL_FN(34)
+SMALL_FN(35) SMALL_FN(36) SMALL_FN(37) SMALL_FN(38) SMALL_FN(39)
+SMALL_FN(40) SMALL_FN(41) SMALL_FN(42) SMALL_FN(43) SMALL_FN(44)
+SMALL_FN(45) SMALL_FN(46) SMALL_FN(47) SMALL_FN(48) SMALL_FN(49)
+SMALL_FN(50) SMALL_FN(51) SMALL_FN(52) SMALL_FN(53) SMALL_FN(54)
+SMALL_FN(55) SMALL_FN(56) SMALL_FN(57) SMALL_FN(58) SMALL_FN(59)
+SMALL_FN(60) SMALL_FN(61) SMALL_FN(62) SMALL_FN(63) SMALL_FN(64)
+SMALL_FN(65) SMALL_FN(66) SMALL_FN(67) SMALL_FN(68) SMALL_FN(69)
+SMALL_FN(70) SMALL_FN(71) SMALL_FN(72) SMALL_FN(73) SMALL_FN(74)
+SMALL_FN(75) SMALL_FN(76) SMALL_FN(77) SMALL_FN(78) SMALL_FN(79)
+SMALL_FN(80) SMALL_FN(81) SMALL_FN(82) SMALL_FN(83) SMALL_FN(84)
+SMALL_FN(85) SMALL_FN(86) SMALL_FN(87) SMALL_FN(88) SMALL_FN(89)
+SMALL_FN(90) SMALL_FN(91) SMALL_FN(92) SMALL_FN(93) SMALL_FN(94)
+SMALL_FN(95) SMALL_FN(96) SMALL_FN(97) SMALL_FN(98) SMALL_FN(99)
+
+// --- Pattern 4: Deep nesting (tests edge state tracking) ---
+
+void stress_deep_nesting(
+    Node * _Nullable p0,  Node * _Nullable p1,  Node * _Nullable p2,
+    Node * _Nullable p3,  Node * _Nullable p4,  Node * _Nullable p5,
+    Node * _Nullable p6,  Node * _Nullable p7,  Node * _Nullable p8,
+    Node * _Nullable p9,  Node * _Nullable p10, Node * _Nullable p11,
+    Node * _Nullable p12, Node * _Nullable p13, Node * _Nullable p14) {
+    if (p0) {
+     if (p1) {
+      if (p2) {
+       if (p3) {
+        if (p4) {
+         if (p5) {
+          if (p6) {
+           if (p7) {
+            if (p8) {
+             if (p9) {
+              if (p10) {
+               if (p11) {
+                if (p12) {
+                 if (p13) {
+                  if (p14) {
+                    p0->value = p1->value + p2->value + p3->value;
+                    p4->value = p5->value + p6->value + p7->value;
+                    p8->value = p9->value + p10->value + p11->value;
+                    p12->value = p13->value + p14->value;
+                  }
+                 }
+                }
+               }
+              }
+             }
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+}
+
+// --- Pattern 5: Linked list traversal with operations ---
+
+void stress_linked_list() {
+    Node * _Nullable head = getNode();
+    int sum = 0;
+    for (Node * _Nullable p = head; p; p = p->next) {
+        sum += p->value;
+        if (p->left) {
+            sum += p->left->value;
+            if (p->left->right) {
+                sum += p->left->right->value;
+            }
+        }
+        if (p->right) {
+            sum += p->right->value;
+        }
+    }
+    (void)sum;
+}
+
+// --- Pattern 6: Diamond CFG merges ---
+
+#define DIAMOND(N) \
+    if (getInt()) { \
+        if (p##N) p##N->value = N; \
+    } else { \
+        if (q##N) q##N->value = N; \
+    }
+
+void stress_diamond_merges() {
+    Node * _Nullable p0 = getNode(), * _Nullable q0 = getNode();
+    Node * _Nullable p1 = getNode(), * _Nullable q1 = getNode();
+    Node * _Nullable p2 = getNode(), * _Nullable q2 = getNode();
+    Node * _Nullable p3 = getNode(), * _Nullable q3 = getNode();
+    Node * _Nullable p4 = getNode(), * _Nullable q4 = getNode();
+    Node * _Nullable p5 = getNode(), * _Nullable q5 = getNode();
+    Node * _Nullable p6 = getNode(), * _Nullable q6 = getNode();
+    Node * _Nullable p7 = getNode(), * _Nullable q7 = getNode();
+    Node * _Nullable p8 = getNode(), * _Nullable q8 = getNode();
+    Node * _Nullable p9 = getNode(), * _Nullable q9 = getNode();
+    Node * _Nullable p10 = getNode(), * _Nullable q10 = getNode();
+    Node * _Nullable p11 = getNode(), * _Nullable q11 = getNode();
+    Node * _Nullable p12 = getNode(), * _Nullable q12 = getNode();
+    Node * _Nullable p13 = getNode(), * _Nullable q13 = getNode();
+    Node * _Nullable p14 = getNode(), * _Nullable q14 = getNode();
+    Node * _Nullable p15 = getNode(), * _Nullable q15 = getNode();
+    Node * _Nullable p16 = getNode(), * _Nullable q16 = getNode();
+    Node * _Nullable p17 = getNode(), * _Nullable q17 = getNode();
+    Node * _Nullable p18 = getNode(), * _Nullable q18 = getNode();
+    Node * _Nullable p19 = getNode(), * _Nullable q19 = getNode();
+    Node * _Nullable p20 = getNode(), * _Nullable q20 = getNode();
+    Node * _Nullable p21 = getNode(), * _Nullable q21 = getNode();
+    Node * _Nullable p22 = getNode(), * _Nullable q22 = getNode();
+    Node * _Nullable p23 = getNode(), * _Nullable q23 = getNode();
+    Node * _Nullable p24 = getNode(), * _Nullable q24 = getNode();
+
+    DIAMOND(0)  DIAMOND(1)  DIAMOND(2)  DIAMOND(3)  DIAMOND(4)
+    DIAMOND(5)  DIAMOND(6)  DIAMOND(7)  DIAMOND(8)  DIAMOND(9)
+    DIAMOND(10) DIAMOND(11) DIAMOND(12) DIAMOND(13) DIAMOND(14)
+    DIAMOND(15) DIAMOND(16) DIAMOND(17) DIAMOND(18) DIAMOND(19)
+    DIAMOND(20) DIAMOND(21) DIAMOND(22) DIAMOND(23) DIAMOND(24)
+}
+
+// --- Pattern 7: Boolean guard stress ---
+
+#define BOOL_GUARD(N) \
+    bool valid_##N = (getNode() != nullptr); \
+    Node * _Nullable bg_##N = getNode();
+
+#define BOOL_CHECK(N) \
+    if (valid_##N && bg_##N) { bg_##N->value = N; }
+
+void stress_bool_guards() {
+    BOOL_GUARD(0)  BOOL_GUARD(1)  BOOL_GUARD(2)  BOOL_GUARD(3)
+    BOOL_GUARD(4)  BOOL_GUARD(5)  BOOL_GUARD(6)  BOOL_GUARD(7)
+    BOOL_GUARD(8)  BOOL_GUARD(9)  BOOL_GUARD(10) BOOL_GUARD(11)
+    BOOL_GUARD(12) BOOL_GUARD(13) BOOL_GUARD(14) BOOL_GUARD(15)
+    BOOL_GUARD(16) BOOL_GUARD(17) BOOL_GUARD(18) BOOL_GUARD(19)
+    BOOL_GUARD(20) BOOL_GUARD(21) BOOL_GUARD(22) BOOL_GUARD(23)
+    BOOL_GUARD(24) BOOL_GUARD(25) BOOL_GUARD(26) BOOL_GUARD(27)
+    BOOL_GUARD(28) BOOL_GUARD(29) BOOL_GUARD(30) BOOL_GUARD(31)
+    BOOL_GUARD(32) BOOL_GUARD(33) BOOL_GUARD(34) BOOL_GUARD(35)
+    BOOL_GUARD(36) BOOL_GUARD(37) BOOL_GUARD(38) BOOL_GUARD(39)
+
+    BOOL_CHECK(0)  BOOL_CHECK(1)  BOOL_CHECK(2)  BOOL_CHECK(3)
+    BOOL_CHECK(4)  BOOL_CHECK(5)  BOOL_CHECK(6)  BOOL_CHECK(7)
+    BOOL_CHECK(8)  BOOL_CHECK(9)  BOOL_CHECK(10) BOOL_CHECK(11)
+    BOOL_CHECK(12) BOOL_CHECK(13) BOOL_CHECK(14) BOOL_CHECK(15)
+    BOOL_CHECK(16) BOOL_CHECK(17) BOOL_CHECK(18) BOOL_CHECK(19)
+    BOOL_CHECK(20) BOOL_CHECK(21) BOOL_CHECK(22) BOOL_CHECK(23)
+    BOOL_CHECK(24) BOOL_CHECK(25) BOOL_CHECK(26) BOOL_CHECK(27)
+    BOOL_CHECK(28) BOOL_CHECK(29) BOOL_CHECK(30) BOOL_CHECK(31)
+    BOOL_CHECK(32) BOOL_CHECK(33) BOOL_CHECK(34) BOOL_CHECK(35)
+    BOOL_CHECK(36) BOOL_CHECK(37) BOOL_CHECK(38) BOOL_CHECK(39)
+}
+
+// --- Pattern 8: Member narrowing stress ---
+
+struct Tree {
+    int data;
+    Tree * _Nullable left;
+    Tree * _Nullable right;
+    Tree * _Nullable parent;
+};
+
+#define MEMBER_NARROW(N) \
+    void member_fn_##N(Tree * _Nullable t) { \
+        if (!t) return; \
+        if (t->left) { \
+            t->left->data = N; \
+            if (t->left->right) t->left->right->data = N; \
+        } \
+        if (t->right) { \
+            t->right->data = N; \
+            if (t->right->parent) t->right->parent->data = N; \
+        } \
+    }
+
+MEMBER_NARROW(0)  MEMBER_NARROW(1)  MEMBER_NARROW(2)  MEMBER_NARROW(3)
+MEMBER_NARROW(4)  MEMBER_NARROW(5)  MEMBER_NARROW(6)  MEMBER_NARROW(7)
+MEMBER_NARROW(8)  MEMBER_NARROW(9)  MEMBER_NARROW(10) MEMBER_NARROW(11)
+MEMBER_NARROW(12) MEMBER_NARROW(13) MEMBER_NARROW(14) MEMBER_NARROW(15)
+MEMBER_NARROW(16) MEMBER_NARROW(17) MEMBER_NARROW(18) MEMBER_NARROW(19)
+MEMBER_NARROW(20) MEMBER_NARROW(21) MEMBER_NARROW(22) MEMBER_NARROW(23)
+MEMBER_NARROW(24) MEMBER_NARROW(25) MEMBER_NARROW(26) MEMBER_NARROW(27)
+MEMBER_NARROW(28) MEMBER_NARROW(29) MEMBER_NARROW(30) MEMBER_NARROW(31)
+MEMBER_NARROW(32) MEMBER_NARROW(33) MEMBER_NARROW(34) MEMBER_NARROW(35)
+MEMBER_NARROW(36) MEMBER_NARROW(37) MEMBER_NARROW(38) MEMBER_NARROW(39)
+MEMBER_NARROW(40) MEMBER_NARROW(41) MEMBER_NARROW(42) MEMBER_NARROW(43)
+MEMBER_NARROW(44) MEMBER_NARROW(45) MEMBER_NARROW(46) MEMBER_NARROW(47)
+MEMBER_NARROW(48) MEMBER_NARROW(49)
+
+// --- Pattern 9: Compound conditions stress ---
+
+#define AND_CHAIN_3(A, B, C) if (A && B && C) { A->value = B->value + C->value; }
+
+void stress_compound_conditions() {
+    Node * _Nullable a0 = getNode(), * _Nullable b0 = getNode(), * _Nullable c0 = getNode();
+    Node * _Nullable a1 = getNode(), * _Nullable b1 = getNode(), * _Nullable c1 = getNode();
+    Node * _Nullable a2 = getNode(), * _Nullable b2 = getNode(), * _Nullable c2 = getNode();
+    Node * _Nullable a3 = getNode(), * _Nullable b3 = getNode(), * _Nullable c3 = getNode();
+    Node * _Nullable a4 = getNode(), * _Nullable b4 = getNode(), * _Nullable c4 = getNode();
+    Node * _Nullable a5 = getNode(), * _Nullable b5 = getNode(), * _Nullable c5 = getNode();
+    Node * _Nullable a6 = getNode(), * _Nullable b6 = getNode(), * _Nullable c6 = getNode();
+    Node * _Nullable a7 = getNode(), * _Nullable b7 = getNode(), * _Nullable c7 = getNode();
+    Node * _Nullable a8 = getNode(), * _Nullable b8 = getNode(), * _Nullable c8 = getNode();
+    Node * _Nullable a9 = getNode(), * _Nullable b9 = getNode(), * _Nullable c9 = getNode();
+
+    AND_CHAIN_3(a0, b0, c0) AND_CHAIN_3(a1, b1, c1)
+    AND_CHAIN_3(a2, b2, c2) AND_CHAIN_3(a3, b3, c3)
+    AND_CHAIN_3(a4, b4, c4) AND_CHAIN_3(a5, b5, c5)
+    AND_CHAIN_3(a6, b6, c6) AND_CHAIN_3(a7, b7, c7)
+    AND_CHAIN_3(a8, b8, c8) AND_CHAIN_3(a9, b9, c9)
+}
+
+// --- Pattern 10: Large switch statement ---
+
+void stress_switch() {
+    Node * _Nullable p = getNode();
+    int x = getInt();
+    switch (x) {
+    case 0:  if (p) p->value = 0;  break;
+    case 1:  if (p) p->value = 1;  break;
+    case 2:  if (p) p->value = 2;  break;
+    case 3:  if (p) p->value = 3;  break;
+    case 4:  if (p) p->value = 4;  break;
+    case 5:  if (p) p->value = 5;  break;
+    case 6:  if (p) p->value = 6;  break;
+    case 7:  if (p) p->value = 7;  break;
+    case 8:  if (p) p->value = 8;  break;
+    case 9:  if (p) p->value = 9;  break;
+    case 10: if (p) p->value = 10; break;
+    case 11: if (p) p->value = 11; break;
+    case 12: if (p) p->value = 12; break;
+    case 13: if (p) p->value = 13; break;
+    case 14: if (p) p->value = 14; break;
+    case 15: if (p) p->value = 15; break;
+    case 16: if (p) p->value = 16; break;
+    case 17: if (p) p->value = 17; break;
+    case 18: if (p) p->value = 18; break;
+    case 19: if (p) p->value = 19; break;
+    case 20: if (p) p->value = 20; break;
+    case 21: if (p) p->value = 21; break;
+    case 22: if (p) p->value = 22; break;
+    case 23: if (p) p->value = 23; break;
+    case 24: if (p) p->value = 24; break;
+    case 25: if (p) p->value = 25; break;
+    case 26: if (p) p->value = 26; break;
+    case 27: if (p) p->value = 27; break;
+    case 28: if (p) p->value = 28; break;
+    case 29: if (p) p->value = 29; break;
+    case 30: if (p) p->value = 30; break;
+    case 31: if (p) p->value = 31; break;
+    case 32: if (p) p->value = 32; break;
+    case 33: if (p) p->value = 33; break;
+    case 34: if (p) p->value = 34; break;
+    case 35: if (p) p->value = 35; break;
+    case 36: if (p) p->value = 36; break;
+    case 37: if (p) p->value = 37; break;
+    case 38: if (p) p->value = 38; break;
+    case 39: if (p) p->value = 39; break;
+    case 40: if (p) p->value = 40; break;
+    case 41: if (p) p->value = 41; break;
+    case 42: if (p) p->value = 42; break;
+    case 43: if (p) p->value = 43; break;
+    case 44: if (p) p->value = 44; break;
+    case 45: if (p) p->value = 45; break;
+    case 46: if (p) p->value = 46; break;
+    case 47: if (p) p->value = 47; break;
+    case 48: if (p) p->value = 48; break;
+    case 49: if (p) p->value = 49; break;
+    default: break;
+    }
+}
+
+#pragma clang assume_nonnull end
diff --git a/clang/test/SemaCXX/flow-nullability-analysis.cpp b/clang/test/SemaCXX/flow-nullability-analysis.cpp
new file mode 100644
index 0000000000000..01be4d6cc7c6f
--- /dev/null
+++ b/clang/test/SemaCXX/flow-nullability-analysis.cpp
@@ -0,0 +1,1997 @@
+// flow-nullability-analysis.cpp - Core flow-sensitive nullability analysis tests.
+//
+// Consolidated from ~30 individual test files. Tests the CFG-based forward
+// dataflow analysis: narrowing, dereference checking, condition decomposition,
+// alias tracking, and control flow patterns.
+//
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-nullable-to-nonnull-conversion -std=c++11 -fcxx-exceptions -Wno-unused-value %s -verify
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-nullable-to-nonnull-conversion -std=c++17 -fcxx-exceptions -Wno-unused-value %s -verify
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-nullable-to-nonnull-conversion -std=c++20 -fcxx-exceptions -Wno-unused-value %s -verify
+
+// ===----------------------------------------------------------------------===//
+// Common types and helpers
+// ===----------------------------------------------------------------------===//
+
+struct Entity {
+    int x;
+    int value() const { return x; }
+};
+
+struct Node {
+    int value;
+    Node * _Nullable next;
+    Node * _Nullable left;
+    Node * _Nullable right;
+    Node * _Nullable parent;
+    Node * _Nullable child;
+};
+
+struct Container {
+    Node * _Nullable root;
+    Node * _Nullable head;
+    int size;
+};
+
+typedef unsigned long size_t;
+typedef unsigned char uint8_t;
+
+Entity * _Nullable getNullableEntity();
+Entity * _Nonnull getNonnullEntity();
+Node * _Nullable getNode();
+Container * _Nullable getContainer();
+int getInt();
+int * _Nullable getNullableInt();
+
+[[noreturn]] void fatal(const char* msg);
+[[noreturn]] void abort_handler(const char* msg);
+void log_msg(const char* msg);
+
+// ===----------------------------------------------------------------------===//
+// Address-of expressions
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void addr_test_local() {
+    int x = 0;
+    int *p = &x;
+    *p = 1; // OK - &x is nonnull
+}
+
+void addr_test_direct() {
+    Entity e;
+    Entity *p = &e;
+    p->x = 1; // OK - &e is nonnull
+}
+
+void addr_test_member(Entity *_Nonnull obj) {
+    int *p = &(obj->x);
+    *p = 1; // OK - &(obj->x) is nonnull
+}
+
+void addr_test_reassign_nullable_warns() {
+    int x = 0;
+    int *p = &x;
+    *p = 1; // OK - initially nonnull
+    p = getNullableInt();
+    *p = 2; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void addr_test_nullable_control() {
+    Entity *e = getNullableEntity();
+    e->x = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Alias tracking
+// ===----------------------------------------------------------------------===//
+
+// When y = x, checking y for null also narrows x (and vice versa).
+
+void alias_test_check_alias_narrow_original(int *_Nullable x) {
+  int *y = x;
+  if (y) {
+    (void)*x; // no warning -- y aliases x, y is checked
+  }
+  (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void alias_test_check_original_narrow_alias(int *_Nullable x) {
+  int *y = x;
+  if (x) {
+    (void)*y; // no warning -- x is checked, y aliases x
+  }
+  (void)*y; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void alias_test_invalidated_by_reassignment(int *_Nullable x, int *_Nullable q) {
+  int *y = x;
+  y = q;       // y no longer aliases x
+  if (y) {
+    (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+void alias_test_invalidated_by_source_reassignment(int *_Nullable x, int *_Nullable q) {
+  int *y = x;
+  x = q;       // x reassigned -- alias y -> x is stale
+  if (y) {
+    (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+void alias_test_multiple(int *_Nullable x) {
+  int *y = x;
+  int *z = x;
+  if (z) {
+    (void)*x; // no warning -- z aliases x, z is checked
+    (void)*y; // no warning -- y also aliases x, x is narrowed
+  }
+}
+
+void alias_test_chain(int *_Nullable x) {
+  int *y = x;
+  int *z = y;  // z -> canonical(y) -> x
+  if (z) {
+    (void)*x; // no warning -- z ultimately aliases x
+    (void)*y; // no warning -- y aliases x too
+  }
+}
+
+void alias_test_invalidated_by_increment(int *_Nullable x) {
+  int *y = x;
+  x++;         // x changed -- alias is stale // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  if (y) {
+    (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+void alias_test_early_return(int *_Nullable x) {
+  int *y = x;
+  if (!y)
+    return;
+  (void)*x; // no warning -- early return means y (and thus x) is non-null
+}
+
+int *_Nullable alias_get_ptr();
+
+void alias_test_no_alias_for_call_result(int *_Nullable x) {
+  int *y = alias_get_ptr(); // y does NOT alias x
+  if (y) {
+    (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+void alias_test_and_shortcircuit(int *_Nullable a, int *_Nullable b) {
+  int *x = a;
+  int *y = b;
+  if (x && y) {
+    (void)*a; // no warning -- x aliases a, x is checked
+    (void)*b; // no warning -- y aliases b, y is checked
+  }
+}
+
+void alias_test_negated_and(int *_Nullable a, int *_Nullable b) {
+  int *x = a;
+  int *y = b;
+  if (!(x && y))
+    return;
+  (void)*a; // no warning
+  (void)*b; // no warning
+}
+
+void alias_test_with_bool_guard(int *_Nullable x) {
+  int *y = x;
+  bool ok = (y != nullptr);
+  if (ok) {
+    (void)*x; // no warning -- bool guard resolves y, alias propagates to x
+  }
+}
+
+// ===----------------------------------------------------------------------===//
+// AND short-circuit narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void and_test_basic(Node* _Nullable p) {
+    if (p && p->value == 42) {
+        p->value = 0; // OK - p narrowed by && LHS
+    }
+}
+
+void and_test_star(Node* _Nullable p) {
+    if (p && (*p).value == 42) {
+        (*p).value = 0; // OK
+    }
+}
+
+void and_test_chained_no_warning(Node* _Nullable p) {
+    if (p && p->next && p->next->value > 0) {
+        p->next->value = 0; // OK - member narrowing works throughout
+    }
+}
+
+void and_test_two_vars(Node* _Nullable p, Node* _Nullable q) {
+    if (p && q) {
+        p->value = q->value; // OK - both narrowed
+    }
+}
+
+void and_test_three_vars(Node* _Nullable a, Node* _Nullable b, Node* _Nullable c) {
+    if (a && b && c) {
+        a->value = b->value + c->value; // OK
+    }
+}
+
+void and_test_member_two_part(Node* _Nullable p) {
+    if (p && p->next) {
+        p->next->value = 1; // OK - both p and p->next narrowed
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Array subscript
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void subscript_test_warns(int* _Nullable p) {
+    p[0] = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void subscript_test_after_check(int* _Nullable p) {
+    if (p) {
+        p[0] = 42; // OK - narrowed by check
+    }
+}
+
+void subscript_test_offset(int* _Nullable p) {
+    p[5] = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void subscript_test_fixed_array_no_warn() {
+    int arr[4] = {1, 2, 3, 4};
+    arr[0] = 10; // OK - fixed-size array, not a pointer
+}
+
+struct SubscriptS {
+    float gridColor[4];
+    struct { int x; } nested[2];
+};
+
+void subscript_test_member_fixed_array(SubscriptS s) {
+    float r = s.gridColor[0]; // OK - fixed-size array member
+    int x = s.nested[1].x;   // OK - fixed-size array member
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Arrow and star dereference
+// ===----------------------------------------------------------------------===//
+
+Entity* _Nullable getHead();
+Entity* _Nullable getChest();
+
+#pragma clang assume_nonnull begin
+
+void arrow_test_warns(Entity* _Nullable p) {
+    p->x = 1;              // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    int v = p->value();     // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void arrow_test_after_null_check(Entity* _Nullable p) {
+    if (p) {
+        p->x = 1;          // OK - narrowed to nonnull
+        int v = p->value(); // OK - narrowed to nonnull
+    }
+}
+
+void arrow_test_no_check() {
+    Entity* head = getHead();
+    head->x = 1;            // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void arrow_test_with_check() {
+    Entity* head = getHead();
+    if (!head) return;
+    head->x = 1;            // OK - narrowed to nonnull
+}
+
+void arrow_test_star_still_works(Entity* _Nullable p) {
+    (*p).x = 1;             // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void arrow_test_star_after_check(Entity* _Nullable p) {
+    if (p) {
+        (*p).x = 1;         // OK - narrowed to nonnull
+    }
+}
+
+// Member field assignment invalidation.
+struct ArrowContainer {
+    Entity* _Nullable child;
+
+    void test_member_assign_invalidates() {
+        if (child) {
+            child->x = 1;    // OK -- narrowed
+            child = nullptr;
+            child->x = 1;    // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        }
+    }
+};
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Assignment in condition
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+Node *_Nullable assign_get_next(Node *n);
+
+void assign_cond_while_ne_null(Node *_Nullable head) {
+    Node *p;
+    while ((p = assign_get_next(head)) != nullptr) { // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+        p->value = 1; // OK -- p narrowed by != nullptr
+    }
+}
+
+void assign_cond_while_truthiness(Node *_Nullable head) {
+    Node *p;
+    while ((p = assign_get_next(head))) { // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+        p->value = 1; // OK -- p narrowed by truthiness
+    }
+}
+
+void assign_cond_if_ne_null(Node *_Nullable head) {
+    Node *p;
+    if ((p = assign_get_next(head)) != nullptr) { // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+        p->value = 1; // OK -- p narrowed
+    }
+}
+
+void assign_cond_for_ne_null(Node *_Nullable head) {
+    for (Node *p; (p = assign_get_next(head)) != nullptr;) { // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+        p->value = 1; // OK -- p narrowed
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Boolean intermediary narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void bool_test_ne_nullptr(Node * _Nullable p) {
+    bool valid = (p != nullptr);
+    if (valid) {
+        (void)p->value; // OK
+    }
+    // Outside the if, p is still nullable
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void bool_test_eq_nullptr_negated(Node * _Nullable p) {
+    bool isNull = (p == nullptr);
+    if (!isNull) {
+        (void)p->value; // OK
+    }
+}
+
+void bool_test_pointer_truthiness(Node * _Nullable p) {
+    bool valid = p;
+    if (valid) {
+        (void)p->value; // OK
+    }
+}
+
+void bool_test_negated_pointer(Node * _Nullable p) {
+    bool isNull = !p;
+    if (!isNull) {
+        (void)p->value; // OK
+    }
+}
+
+// Invalidation
+
+void bool_test_pointer_reassigned(Node * _Nullable p, Node * _Nullable q) {
+    bool valid = (p != nullptr);
+    p = q; // reassign pointer -- bool guard is stale
+    if (valid) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void bool_test_bool_reassigned(Node * _Nullable p) {
+    bool valid = (p != nullptr);
+    valid = false;
+    if (valid) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void bool_test_pointer_incremented(int * _Nullable p) {
+    bool valid = (p != nullptr);
+    p++; // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+    if (valid) {
+        (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+// Negated conjunction
+
+void bool_test_negated_and_return(Node * _Nullable p, Node * _Nullable q) {
+    if (!(p && q)) return;
+    (void)p->value; // OK
+    (void)q->value; // OK
+}
+
+void bool_test_negated_and_else(Node * _Nullable p, Node * _Nullable q) {
+    if (!(p && q)) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    } else {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+void bool_test_negated_triple_and(Node * _Nullable a, Node * _Nullable b, Node * _Nullable c) {
+    if (!(a && b && c)) return;
+    (void)a->value; // OK
+    (void)b->value; // OK
+    (void)c->value; // OK
+}
+
+void bool_test_negated_and_ne_nullptr(Node * _Nullable p, Node * _Nullable q) {
+    if (!(p != nullptr && q != nullptr)) return;
+    (void)p->value; // OK
+    (void)q->value; // OK
+}
+
+// Combined: bool guard + negated &&
+
+void bool_test_guard_in_and(Node * _Nullable p, Node * _Nullable q) {
+    bool pOk = (p != nullptr);
+    if (pOk && q) {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+// Bool guard does not track compound conditions
+
+void bool_test_compound_not_tracked(Node * _Nullable p, Node * _Nullable q) {
+    bool both = (p && q);
+    if (both) {
+        // Compound conditions are not decomposed into per-variable guards
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        (void)q->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Brace-wrapped assertion macros
+// ===----------------------------------------------------------------------===//
+
+#define INVARIANT(cond) \
+  { \
+    if (!(cond)) { \
+      abort_handler("invariant failed"); \
+    } \
+  }
+
+#define INVARIANT_MSG(cond, msg) \
+  { \
+    if (!(cond)) { \
+      abort_handler(msg); \
+    } \
+  }
+
+#pragma clang assume_nonnull begin
+
+void brace_test_basic(Node* _Nullable p) {
+    INVARIANT(p);
+    p->value = 1; // OK - INVARIANT ensures p is non-null
+}
+
+void brace_test_with_message(Node* _Nullable p) {
+    INVARIANT_MSG(p, "p must not be null");
+    p->value = 1; // OK
+}
+
+void brace_test_ne_nullptr(Node* _Nullable p) {
+    INVARIANT(p != nullptr);
+    p->value = 1; // OK - p != nullptr checked
+}
+
+void brace_test_multiple_vars(Node* _Nullable p, Node* _Nullable q) {
+    INVARIANT(p);
+    INVARIANT(q);
+    p->value = q->value; // OK - both narrowed
+}
+
+void brace_test_member(Node* _Nullable p) {
+    INVARIANT(p);
+    INVARIANT(p->next);
+    p->next->value = 1; // OK - both p and p->next narrowed through macro
+}
+
+void brace_test_no_assert_still_warns(Node* _Nullable p) {
+    p->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void brace_test_manual_bare_noreturn(Node* _Nullable p) {
+    {
+        if (!p) {
+            abort_handler("null");
+        }
+    }
+    p->value = 1; // OK - bare braces with noreturn narrow outward
+}
+
+void brace_test_nested(Node* _Nullable p, Node* _Nullable q) {
+    {
+        if (!p) { abort_handler("p"); }
+        if (!q) { abort_handler("q"); }
+    }
+    p->value = q->value; // OK - both narrowed
+}
+
+void brace_test_does_not_affect_unrelated(Node* _Nullable p, Node* _Nullable q) {
+    INVARIANT(p);
+    q->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+struct Widget {
+    Node* _Nullable data;
+    int x;
+
+    void test_this_arrow() {
+        this->x = 1; // OK - 'this' is never null
+    }
+
+    int test_this_deref() {
+        return (*this).x; // OK - 'this' is never null
+    }
+
+    void test_this_member_narrowing() {
+        INVARIANT(data);
+        data->value = 1; // OK - data narrowed by INVARIANT
+    }
+
+    void test_this_member_if_narrowing() {
+        if (data) {
+            data->value = 1; // OK - data narrowed by if
+        }
+    }
+
+    void test_this_member_no_narrowing() {
+        data->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    }
+};
+
+void brace_test_and_member_narrowing(Node* _Nullable p) {
+    if (p && p->next) {
+        p->next->value = 1; // OK - both p and p->next narrowed by && condition
+    }
+}
+
+void brace_test_and_member_no_narrowing(Node* _Nullable p) {
+    if (p) {
+        p->next->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    }
+}
+
+void brace_test_or_member_early_return(Node* _Nullable p) {
+    if (!p || !p->next) return;
+    p->next->value = 1; // OK - both p and p->next narrowed by early return
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// __builtin_expect and assertion macros
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void builtin_test_expect_if(Entity* _Nullable p) {
+    if (__builtin_expect(!!(p), 1)) {
+        p->x = 1; // OK - narrowed through __builtin_expect
+    }
+}
+
+void builtin_test_expect_negated(Entity* _Nullable p) {
+    if (__builtin_expect(!!(p == nullptr), 0))
+        return;
+    p->x = 1; // OK - early return narrowing through __builtin_expect
+}
+
+void builtin_test_expect_early_return(Entity* _Nullable p) {
+    if (__builtin_expect(!!(!p), 0))
+        return;
+    p->x = 1; // OK
+}
+
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+
+void builtin_test_likely_macro(Entity* _Nullable p) {
+    if (LIKELY(p)) {
+        p->x = 1; // OK - narrowed
+    }
+}
+
+void builtin_test_unlikely_null_check(Entity* _Nullable p) {
+    if (UNLIKELY(!p))
+        return;
+    p->x = 1; // OK
+}
+
+#define CHECK(cond) do { if (__builtin_expect(!(cond), 0)) fatal("CHECK failed"); } while(0)
+
+void builtin_test_check_macro(Entity* _Nullable p) {
+    CHECK(p);
+    p->x = 1; // OK - CHECK asserted non-null
+}
+
+void builtin_test_check_macro_two_vars(Entity* _Nullable p, Entity* _Nullable q) {
+    CHECK(p);
+    CHECK(q);
+    p->x = q->x; // OK
+}
+
+void builtin_test_assume_simple(Entity* _Nullable p) {
+    __builtin_assume(p != nullptr);
+    p->x = 1; // OK - narrowed by __builtin_assume
+}
+
+void builtin_test_assume_truthiness(Entity* _Nullable p) {
+    __builtin_assume(p);
+    p->x = 1; // OK - narrowed by __builtin_assume(p)
+}
+
+void builtin_test_assume_two_vars(Entity* _Nullable p, Entity* _Nullable q) {
+    __builtin_assume(p != nullptr);
+    __builtin_assume(q != nullptr);
+    p->x = q->x; // OK
+}
+
+void builtin_test_no_narrowing_without_check(Entity* _Nullable p) {
+    p->x = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Cast propagation
+// ===----------------------------------------------------------------------===//
+
+struct Base {
+    int x;
+};
+
+struct Derived : Base {
+    int y;
+};
+
+Base * _Nullable getCastNullable();
+Base * _Nonnull getCastNonnull();
+
+#pragma clang assume_nonnull begin
+
+void cast_test_c_style_nonnull() {
+    Base *b = getCastNonnull();
+    Derived *d = (Derived *)b;
+    d->y = 1; // OK - nonnull propagated through C-style cast
+}
+
+void cast_test_static_cast_nonnull() {
+    Base *b = getCastNonnull();
+    Derived *d = static_cast<Derived *>(b);
+    d->y = 1; // OK - nonnull propagated through static_cast
+}
+
+void cast_test_reinterpret_cast_nonnull() {
+    Base *b = getCastNonnull();
+    int *ip = reinterpret_cast<int *>(b);
+    *ip = 1; // OK - nonnull propagated through reinterpret_cast
+}
+
+void cast_test_c_style_nullable_warns() {
+    Base *b = getCastNullable();
+    Derived *d = (Derived *)b;
+    d->y = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void cast_test_explicit_nonnull_dest() {
+    Base *b = getCastNullable();
+    Derived * _Nonnull d = (Derived * _Nonnull)b;
+    d->y = 1; // OK - explicit _Nonnull on dest type
+}
+
+// reinterpret_cast on this + pointer arithmetic
+
+struct CastFoo {
+    int x;
+    void test_cast_this() {
+        auto* p = reinterpret_cast<uint8_t*>(this) + 4;
+        *p = 0; // OK -- this is always non-null, arithmetic preserves it
+    }
+};
+
+void cast_test_ptr_arith_nonnull(int* p) {
+    auto* q = p + 1;
+    *q = 0; // OK -- p is nonnull (assume_nonnull), arithmetic preserves it
+}
+
+void cast_test_ptr_arith_nullable(int* _Nullable p) {
+    auto* q = p + 1; // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+    *q = 0; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+struct DerivedReinterpret : Base {
+    void test_reinterpret_cast_this_to_base() {
+        Base *b = reinterpret_cast<Base*>(this);
+        b->x = 1; // OK -- this is non-null
+
+        uint8_t *raw = reinterpret_cast<uint8_t*>(this) + 4;
+        *raw = 0; // OK -- this is always non-null
+    }
+};
+
+struct Bar {
+    int val;
+    void test_deref_static_cast_this() {
+        (*static_cast<Bar*>(this)).val = 42; // OK -- this is non-null
+    }
+};
+
+struct DerivedBar : Base {
+    void test_deref_cast_this_to_base() {
+        (*static_cast<Base*>(this)).x = 1; // OK -- this is non-null
+    }
+};
+
+struct Baz {
+    int z;
+};
+
+void cast_test_deref_cast_addr_of(Baz& other) {
+    (*static_cast<Baz*>(&other)).z = 1; // OK -- address-of is non-null
+}
+
+void cast_test_deref_cast_addr_of_different_type(Derived& d) {
+    (*static_cast<Base*>(&d)).x = 1; // OK -- address-of is non-null
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Chained and nested dereferences
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void chain_test_direct_warns() {
+    int v = getNode()->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    (void)v;
+}
+
+void chain_test_direct_guarded() {
+    Node * _Nullable n = getNode();
+    if (n) {
+        int v = n->value; // OK
+        (void)v;
+    }
+}
+
+void chain_test_double_warns() {
+    (void)getContainer()->root; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void chain_test_double_partial_guard() {
+    Container * _Nullable c = getContainer();
+    if (c) {
+        c->root->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void chain_test_double_full_guard() {
+    Container * _Nullable c = getContainer();
+    if (c && c->root) {
+        c->root->value = 1; // OK -- both narrowed
+    }
+}
+
+void chain_test_triple(Node * _Nullable head) {
+    if (head && head->next && head->next->next) {
+        head->next->next->value = 42; // OK -- all three narrowed
+    }
+}
+
+// Known limitation: multi-level member narrowing
+void chain_test_triple_partial(Node * _Nullable head) {
+    if (head && head->next) {
+        // head->next is narrowed, but head->next->next is still nullable.
+        // The analysis currently does not warn here (accepted false negative).
+        head->next->next->value = 42; // no warning (known limitation)
+    }
+}
+
+// Method return chaining
+
+struct Builder {
+    Node * _Nullable node;
+
+    Builder * _Nullable setNode(Node * _Nonnull n) {
+        node = n;
+        return this;
+    }
+
+    Node * _Nullable getResult() {
+        return node;
+    }
+};
+
+Builder * _Nullable getBuilder();
+
+void chain_test_builder_warns() {
+    getBuilder()->getResult(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void chain_test_builder_guarded() {
+    Builder * _Nullable b = getBuilder();
+    if (b) {
+        Node * _Nullable result = b->getResult();
+        if (result) {
+            (void)result->value; // OK -- both guarded
+        }
+    }
+}
+
+// Pointer-to-pointer (T**) -- known limitation
+void chain_test_ptr_to_ptr(Node * _Nullable * _Nullable pp) {
+    if (pp && *pp) {
+        (*pp)->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void chain_test_ptr_to_ptr_via_local(Node * _Nullable * _Nullable pp) {
+    if (!pp) return;
+    Node * _Nullable p = *pp;
+    if (p) {
+        p->value = 1; // OK -- local variable is tracked
+    }
+}
+
+void chain_test_array_of_nullable(Node * _Nullable nodes[], int n) {
+    for (int i = 0; i < n; i++) {
+        Node * _Nullable cur = nodes[i];
+        if (cur) {
+            cur->value = i; // OK -- narrowed via local
+        }
+    }
+}
+
+void chain_test_conditional(Node * _Nullable p) {
+    Node * _Nullable next = p ? p->next : nullptr;
+    if (next) {
+        next->value = 1; // OK -- narrowed
+    }
+}
+
+void chain_test_assign_from() {
+    Node * _Nullable n = getNode();
+    if (!n) return;
+    Node * _Nullable child = n->next;
+    if (child) {
+        child->value = 1; // OK
+    }
+}
+
+void chain_test_in_loop() {
+    Node * _Nullable head = getNode();
+    for (Node * _Nullable p = head; p; p = p->next) {
+        if (p->left && p->left->right) {
+            p->left->right->value = 0; // OK -- all narrowed
+        }
+    }
+}
+
+Node * _Nullable chain_get_grandchild(Node * _Nullable n) {
+    if (n && n->next) {
+        return n->next->next; // OK -- n->next narrowed; returns nullable
+    }
+    return nullptr;
+}
+
+void chain_test_container_accessor() {
+    Container * _Nullable c = getContainer();
+    if (!c) return;
+    if (!c->root) return;
+    c->root->value = 1; // OK -- both narrowed
+
+    if (c->root->next) {
+        c->root->next->value = 2; // OK
+    }
+}
+
+Node * _Nullable chain_safe_next(Node * _Nullable n) {
+    if (!n) return nullptr;
+    return n->next; // OK -- n narrowed
+}
+
+void chain_test_cascade() {
+    Node * _Nullable n = getNode();
+    Node * _Nullable child = chain_safe_next(n);
+    if (child) {
+        child->value = 1; // OK
+    }
+}
+
+struct Tree {
+    int data;
+    Tree * _Nullable left;
+    Tree * _Nullable right;
+    Tree * _Nullable parent;
+};
+
+void chain_test_tree_traversal(Tree * _Nullable root) {
+    if (!root) return;
+    if (root->left) {
+        root->left->data = 1; // OK
+        if (root->left->left) {
+            root->left->left->data = 2; // OK -- deeply narrowed
+        }
+    }
+    if (root->right && root->right->parent) {
+        root->right->parent->data = 3; // OK
+    }
+}
+
+void chain_test_invalidation(Node * _Nullable p) {
+    if (p && p->next) {
+        p->next->value = 1; // OK -- both narrowed
+        p = getNode();       // reassign p -- narrowing gone
+        if (p) {
+            p->value = 2; // OK -- re-narrowed
+        }
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Complex CFG patterns (diamonds, loops, merges)
+// ===----------------------------------------------------------------------===//
+
+Node * _Nonnull getSafeNode();
+
+#pragma clang assume_nonnull begin
+
+void cfg_test_diamond_both_narrow(Node * _Nullable p) {
+    if (getInt()) {
+        if (!p) return;
+    } else {
+        if (!p) return;
+    }
+    (void)p->value; // OK -- narrowed on both paths
+}
+
+void cfg_test_diamond_one_narrows(Node * _Nullable p) {
+    if (getInt()) {
+        if (!p) return;
+    } else {
+        // NOT narrowed here
+    }
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void cfg_test_diamond_assign_both(int * _Nullable p) {
+    int x = 0, y = 0;
+    if (getInt()) {
+        p = &x;
+    } else {
+        p = &y;
+    }
+    (void)*p; // OK -- both branches assign nonnull (address-of)
+}
+
+void cfg_test_diamond_assign_one(Node * _Nullable p) {
+    int x;
+    if (getInt()) {
+        // p unchanged -- still nullable
+    } else {
+        if (!p) return;
+    }
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void cfg_test_nested_three_levels(Node * _Nullable p, Node * _Nullable q) {
+    if (!p) return;
+    if (getInt()) {
+        if (!q) return;
+        (void)p->value; // OK
+        (void)q->value; // OK
+    } else {
+        (void)p->value; // OK -- outer guard still holds
+        (void)q->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void cfg_test_loop_multi_exit(Node * _Nullable p) {
+    for (int i = 0; i < 10; i++) {
+        if (!p) break;
+        (void)p->value; // OK -- narrowed by break guard
+    }
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void cfg_test_sequential_narrow(Node * _Nullable p, Node * _Nullable q, Node * _Nullable r) {
+    if (!p) return;
+    if (!q) return;
+    if (!r) return;
+    (void)p->value; // OK
+    (void)q->value; // OK
+    (void)r->value; // OK
+}
+
+void cfg_test_reassign_in_branch(Node * _Nullable p) {
+    if (!p) return;
+    if (getInt()) {
+        p = getNode(); // reassigned to nullable
+    }
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void cfg_test_do_while(Node * _Nullable p) {
+    if (!p) return;
+    do {
+        (void)p->value; // OK -- narrowed on entry
+    } while (getInt() && p);
+}
+
+void cfg_test_nested_loops(Node * _Nullable p) {
+    if (!p) return;
+    for (int i = 0; i < 10; i++) {
+        for (int j = 0; j < 10; j++) {
+            (void)p->value; // OK -- narrowed, loops don't invalidate
+        }
+    }
+}
+
+void cfg_test_switch_fallthrough(Node * _Nullable p) {
+    switch (getInt()) {
+    case 0:
+        if (!p) return;
+        [[fallthrough]];
+    case 1:
+        // Reached from case 0 (narrowed) OR case 1 (not narrowed)
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        break;
+    default:
+        break;
+    }
+}
+
+void cfg_test_post_loop_narrowing(Node * _Nullable p) {
+    while (true) {
+        if (p) break;
+        p = getNode();
+    }
+    (void)p->value; // OK -- only exit is via break where p is narrowed
+}
+
+Node * _Nonnull cfg_test_both_return(Node * _Nullable p) {
+    if (p) {
+        return p; // OK
+    } else {
+        return getSafeNode();
+    }
+}
+
+void cfg_test_ternary_chain(Node * _Nullable a, Node * _Nullable b, Node * _Nullable c) {
+    Node *picked = a ? a : (b ? b : c);
+    if (picked)
+        (void)picked->value; // OK -- narrowed
+}
+
+void cfg_test_while_reassign(Node * _Nullable p) {
+    while (p) {
+        (void)p->value; // OK -- narrowed by while condition
+        p = p->left;
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Compound conditions (&&, ||, De Morgan)
+// ===----------------------------------------------------------------------===//
+
+bool isValid(Node * _Nonnull p);
+
+#pragma clang assume_nonnull begin
+
+void compound_test_and_both(Node * _Nullable p, Node * _Nullable q) {
+    if (p && q) {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+void compound_test_or_neither(Node * _Nullable p, Node * _Nullable q) {
+    if (p || q) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        (void)q->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void compound_test_demorgan_return(Node * _Nullable p, Node * _Nullable q) {
+    if (!p || !q) return;
+    (void)p->value; // OK
+    (void)q->value; // OK
+}
+
+void compound_test_chain(Node * _Nullable p) {
+    if (p && p->next) {
+        (void)p->value; // OK
+        (void)p->next->value; // OK
+    }
+}
+
+void compound_test_triple_and(Node * _Nullable a, Node * _Nullable b, Node * _Nullable c) {
+    if (a && b && c) {
+        (void)a->value; // OK
+        (void)b->value; // OK
+        (void)c->value; // OK
+    }
+}
+
+void compound_test_negated_and(Node * _Nullable p, Node * _Nullable q) {
+    if (!(p && q)) return;
+    (void)p->value; // OK
+    (void)q->value; // OK
+}
+
+void compound_test_ne_null_and(Node * _Nullable p, Node * _Nullable q) {
+    if (p != nullptr && q != nullptr) {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+void compound_test_eq_null_or_return(Node * _Nullable p, Node * _Nullable q) {
+    if (p == nullptr || q == nullptr) return;
+    (void)p->value; // OK
+    (void)q->value; // OK
+}
+
+void compound_test_mixed_condition(Node * _Nullable p) {
+    if (p && p->value > 0) {
+        (void)p->value; // OK
+    }
+}
+
+void compound_test_condition_with_call(Node * _Nullable p) {
+    if (p && isValid(p)) {
+        (void)p->value; // OK
+    }
+}
+
+void compound_test_while_and(Node * _Nullable p) {
+    while (p && p->next) {
+        (void)p->value; // OK
+        p = p->next;
+    }
+}
+
+void compound_test_for_and_condition(Node * _Nullable p) {
+    for (int i = 0; p && i < 10; i++) {
+        (void)p->value; // OK -- narrowed by for condition
+    }
+}
+
+void compound_test_ternary_null_check(Node * _Nullable p) {
+    int v = p ? p->value : -1; // OK -- p narrowed in true branch
+}
+
+void compound_test_multi_ternary(Node * _Nullable a, Node * _Nullable b) {
+    int v = a ? a->value : (b ? b->value : 0); // OK
+}
+
+void compound_test_bool_intermediary(Node * _Nullable p) {
+    bool valid = (p != nullptr);
+    if (valid) {
+        (void)p->value; // OK
+    }
+}
+
+void compound_test_bool_eq_null(Node * _Nullable p) {
+    bool isNull = (p == nullptr);
+    if (!isNull) {
+        (void)p->value; // OK
+    }
+}
+
+void compound_test_bool_truthiness(Node * _Nullable p) {
+    bool valid = p;
+    if (valid) {
+        (void)p->value; // OK
+    }
+}
+
+void compound_test_bool_negated_ptr(Node * _Nullable p) {
+    bool isNull = !p;
+    if (!isNull) {
+        (void)p->value; // OK
+    }
+    if (isNull) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void compound_test_bool_ptr_reassigned(Node * _Nullable p, Node * _Nullable q) {
+    bool valid = (p != nullptr);
+    p = q;
+    if (valid) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void compound_test_bool_reassigned(Node * _Nullable p) {
+    bool valid = (p != nullptr);
+    valid = false;
+    if (valid) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void compound_test_negated_triple_and(Node * _Nullable a, Node * _Nullable b, Node * _Nullable c) {
+    if (!(a && b && c)) return;
+    (void)a->value; // OK
+    (void)b->value; // OK
+    (void)c->value; // OK
+}
+
+void compound_test_negated_and_body(Node * _Nullable p, Node * _Nullable q) {
+    if (!(p && q)) {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    } else {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Duplicate diagnostic suppression
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void dupdiag_take_nonnull(int * _Nonnull p);
+
+void dupdiag_test_pass_to_nonnull(int * _Nullable p) {
+    dupdiag_take_nonnull(p); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    *p = 42; // OK -- narrowed by nonnull call, no second warning
+}
+
+void dupdiag_test_deref_only(int * _Nullable p) {
+    *p = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void dupdiag_test_assign_to_nonnull(int * _Nullable p) {
+    int * _Nonnull q = p; // expected-warning{{assigning nullable pointer to nonnull variable}} expected-note{{add a null check before assigning}}
+}
+
+void dupdiag_test_checked(int * _Nullable p) {
+    if (!p) return;
+    dupdiag_take_nonnull(p); // OK -- narrowed, no warning
+    *p = 42;                 // OK -- narrowed, no deref warning
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Else-branch narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void else_test_simple(Entity* _Nullable p) {
+    if (!p) {
+        return;
+    } else {
+        p->x = 1; // OK - narrowed in else branch
+    }
+}
+
+void else_test_or_two_vars(Entity* _Nullable p, Entity* _Nullable q) {
+    if (!p || !q) {
+        return;
+    } else {
+        p->x = q->x; // OK - both narrowed in else branch
+    }
+}
+
+void else_test_or_three_vars(Entity* _Nullable p, Entity* _Nullable q, Entity* _Nullable r) {
+    if (!p || !q || !r) {
+        return;
+    } else {
+        p->x = q->x + r->x; // OK - all narrowed in else branch
+    }
+}
+
+void else_test_early_return_or(Entity* _Nullable p, Entity* _Nullable q) {
+    if (!p || !q)
+        return;
+    p->x = q->x; // OK - both narrowed after early return
+}
+
+void else_test_positive_no_narrow(Entity* _Nullable p) {
+    if (p) {
+        p->x = 1; // OK - narrowed in then branch
+    } else {
+        p->x = 2; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void else_test_member_narrowing(Entity* _Nullable p) {
+    if (!p) {
+        // p is null here
+    } else {
+        p->x = 1; // OK - narrowed in else
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// For-loop narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void for_test_linked_list(Node* _Nullable head) {
+    for (Node* _Nullable p = head; p; p = p->next) {
+        p->value = 0; // OK - p narrowed from condition
+    }
+}
+
+void for_test_simple_increment(Node* _Nullable p) {
+    for (; p; p = p->next) {
+        p->value = 0; // OK
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Nested if-init (regression test for && narrowing at IfStmt merge)
+// ===----------------------------------------------------------------------===//
+
+template<typename K, typename V> struct DenseMap {
+    struct Iter { V second; bool operator!=(Iter o) const; };
+    Iter find(K key) const;
+    Iter end() const;
+};
+
+struct VarDecl {
+    struct QT { bool isPointerType() const; bool isBooleanType() const; };
+    QT getType() const;
+};
+
+using BoolGuardMap = DenseMap<const VarDecl *, int>;
+
+struct Expr {};
+template<typename T, typename U> T *dyn_cast(U *);
+
+void nested_if_init_and_narrowing(const Expr * _Nullable E, const BoolGuardMap * _Nullable BoolGuards) {
+    if (auto *VD = dyn_cast<VarDecl, const Expr>(E)) {
+        if (VD->getType().isPointerType())
+            return;
+        if (BoolGuards && VD->getType().isBooleanType()) {
+            BoolGuards->find(VD); // OK -- BoolGuards narrowed by &&
+            (void)BoolGuards->end(); // OK
+        }
+    }
+}
+
+// ===----------------------------------------------------------------------===//
+// __attribute__((nonnull)) interactions
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+__attribute__((nonnull))
+void nonnull_attr_consume_all(Node *a, Node *b) {}
+
+void nonnull_attr_test_fn_level(Node * _Nullable p, Node * _Nullable q) {
+    if (!p || !q) return;
+    nonnull_attr_consume_all(p, q); // OK -- both narrowed
+    (void)p->value; // OK
+    (void)q->value; // OK
+}
+
+__attribute__((nonnull(1, 3)))
+void nonnull_attr_consume_specific(Node *a, Node * _Nullable b, Node *c) {}
+
+void nonnull_attr_test_param_level(Node * _Nullable p, Node * _Nullable q, Node * _Nullable r) {
+    nonnull_attr_consume_specific(p, q, r); // expected-warning 2{{passing nullable pointer to nonnull parameter}} expected-note 2{{add a null check before the call}}
+    (void)p->value; // OK -- narrowed by passing to nonnull param 1
+    (void)r->value; // OK -- narrowed by passing to nonnull param 3
+}
+
+__attribute__((returns_nonnull))
+Node *nonnull_attr_createSafe();
+
+void nonnull_attr_test_returns_nonnull() {
+    Node *p = nonnull_attr_createSafe();
+    (void)p->value; // OK -- _Nonnull return type
+}
+
+void nonnull_attr_take_nonnull(Node * _Nonnull p) {}
+
+void nonnull_attr_test_type_qualifier(Node * _Nullable p) {
+    nonnull_attr_take_nonnull(p); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    (void)p->value; // OK -- narrowed by passing to _Nonnull param
+}
+
+void nonnull_attr_test_multi_call(Node * _Nullable a, Node * _Nullable b) {
+    nonnull_attr_take_nonnull(a); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    nonnull_attr_take_nonnull(b); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    (void)a->value; // OK
+    (void)b->value; // OK
+}
+
+void nonnull_attr_unrelated_fn();
+
+void nonnull_attr_test_survives_calls(Node * _Nonnull p) {
+    nonnull_attr_unrelated_fn();
+    (void)p->value; // OK -- _Nonnull parameter, calls don't invalidate
+}
+
+extern "C" {
+    __attribute__((nonnull(1)))
+    void nonnull_attr_c_consumer(Node *p, int x);
+}
+
+void nonnull_attr_test_c_fn(Node * _Nullable p) {
+    nonnull_attr_c_consumer(p, 42); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    (void)p->value; // OK -- narrowed by passing to nonnull param
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// _Nonnull parameter tests (no diagnostics expected)
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void nonnull_param_test_star(Entity* _Nonnull p) {
+    (*p).x = 1; // OK - _Nonnull never warns
+}
+
+void nonnull_param_test_arrow(Entity* _Nonnull p) {
+    p->x = 1; // OK - _Nonnull never warns
+}
+
+void nonnull_param_test_method(Entity* _Nonnull p) {
+    int v = p->value(); // OK
+}
+
+void nonnull_param_test_local() {
+    Entity e;
+    Entity* _Nonnull p = &e;
+    p->x = 1; // OK - _Nonnull local
+}
+
+void nonnull_param_test_mixed(Entity* _Nonnull safe, Entity* _Nullable risky) {
+    safe->x = 1; // OK - _Nonnull
+    if (risky) {
+        risky->x = safe->x; // OK - risky narrowed, safe is _Nonnull
+    }
+}
+
+void nonnull_param_test_after_null_check(Entity* _Nonnull p) {
+    if (p) {
+        p->x = 1; // OK - redundant check, but still fine
+    }
+    p->x = 2; // OK - _Nonnull
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// _Nonnull parameter narrowing (passing nullable to nonnull)
+// ===----------------------------------------------------------------------===//
+
+// Simulate system header declarations with _Nonnull params
+size_t my_strlen(const char * _Nonnull s);
+void my_use(const char * _Nonnull s);
+void unannotated_use(const char *s);
+void two_params(const char * _Nonnull a, const char *b);
+
+// GCC-style nonnull attribute
+size_t gcc_strlen(const char *s) __attribute__((nonnull(1)));
+void gcc_all_nonnull(const char *a, const char *b) __attribute__((nonnull));
+void gcc_partial_nonnull(const char *a, const char *b) __attribute__((nonnull(1)));
+
+#pragma clang assume_nonnull begin
+
+void narrow_param_test_nonnull(const char * _Nullable filePath) {
+    my_strlen(filePath); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    const char c = *filePath; // OK -- narrowed by call above
+}
+
+void narrow_param_test_unannotated_no_narrow(const char * _Nullable filePath) {
+    unannotated_use(filePath);
+    const char c = *filePath; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void narrow_param_test_mixed(const char * _Nullable a, const char * _Nullable b) {
+    two_params(a, b); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    const char c1 = *a; // OK -- narrowed
+    const char c2 = *b; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void narrow_param_test_multiple_calls(const char * _Nullable p, const char * _Nullable q) {
+    my_use(p); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    my_strlen(q); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    const char c1 = *p; // OK
+    const char c2 = *q; // OK
+}
+
+void narrow_param_test_gcc_nonnull(const char * _Nullable filePath) {
+    gcc_strlen(filePath); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    const char c = *filePath; // OK -- narrowed by gcc nonnull attr
+}
+
+void narrow_param_test_gcc_all(const char * _Nullable a, const char * _Nullable b) {
+    gcc_all_nonnull(a, b); // expected-warning 2{{passing nullable pointer to nonnull parameter}} expected-note 2{{add a null check before the call}}
+    const char c1 = *a; // OK -- narrowed
+    const char c2 = *b; // OK -- narrowed
+}
+
+void narrow_param_test_gcc_partial(const char * _Nullable a, const char * _Nullable b) {
+    gcc_partial_nonnull(a, b); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check before the call}}
+    const char c1 = *a; // OK -- narrowed (param 1 is nonnull)
+    const char c2 = *b; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Noreturn, if-else termination, do-while assertions
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void noreturn_test_if_else_both_return(Entity* _Nullable p) {
+    if (!p) {
+        if (true) { return; }
+        else { return; }
+    }
+    p->x = 1; // OK - if always terminates
+}
+
+void noreturn_test_if_else_return_and_noreturn(Entity* _Nullable p) {
+    if (!p) {
+        if (true) { return; }
+        else { fatal("unreachable"); }
+    }
+    p->x = 1; // OK
+}
+
+void noreturn_test_nested_if_else(Entity* _Nullable p) {
+    if (!p) {
+        if (true) {
+            if (true) { return; }
+            else { return; }
+        } else {
+            fatal("unreachable");
+        }
+    }
+    p->x = 1; // OK - deeply nested, both paths terminate
+}
+
+void noreturn_test_if_without_else(Entity* _Nullable p, bool flag) {
+    if (!p) {
+        if (flag) { return; }
+    }
+    p->x = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void noreturn_test_function(Entity* _Nullable p) {
+    if (!p) {
+        fatal("p is null");
+    }
+    p->x = 1; // OK - noreturn guarantees we don't reach here if p was null
+}
+
+void noreturn_test_in_compound(Entity* _Nullable p) {
+    if (!p) {
+        log_msg("about to die");
+        fatal("p is null");
+    }
+    p->x = 1; // OK
+}
+
+#define MY_ASSERT(cond) do { if (!(cond)) fatal("assertion failed: " #cond); } while(0)
+
+void noreturn_test_do_while_assert(Entity* _Nullable p) {
+    MY_ASSERT(p);
+    p->x = 1; // OK - asserted non-null
+}
+
+void noreturn_test_do_while_assert_two_vars(Entity* _Nullable p, Entity* _Nullable q) {
+    MY_ASSERT(p);
+    MY_ASSERT(q);
+    p->x = q->x; // OK - both asserted non-null
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Reassignment invalidation
+// ===----------------------------------------------------------------------===//
+
+Entity* _Nullable getEntityForReassign();
+
+#pragma clang assume_nonnull begin
+
+void reassign_test_invalidates(Entity* _Nullable p, Entity* _Nullable other) {
+    if (p) {
+        p = other;
+        (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void reassign_test_deref_before(Entity* _Nullable p, Entity* _Nullable other) {
+    if (p) {
+        (*p).x = 1; // OK - narrowed
+        p = other;
+        (*p).x = 2; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+void reassign_test_then_recheck(Entity* _Nullable p) {
+    p = getEntityForReassign();
+    if (p) {
+        (*p).x = 1; // OK - re-narrowed after reassignment
+    }
+}
+
+void reassign_test_increment_preserves(Entity* _Nullable p) {
+    if (p) {
+        p++;
+        (void)*p; // OK -- p++ on non-null is still non-null
+    }
+}
+
+void reassign_test_decrement_preserves(Entity* _Nullable p) {
+    if (p) {
+        --p;
+        (void)*p; // OK -- --p on non-null is still non-null
+    }
+}
+
+// Member narrowing IS invalidated by pointer arithmetic
+struct Chain {
+    int value;
+    Chain * _Nullable next;
+};
+
+void reassign_test_increment_invalidates_member(Chain * _Nullable p) {
+    if (p && p->next) {
+        p++;
+        p->next->value = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Switch statement narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void switch_test_before(Entity* _Nullable p, int kind) {
+    if (!p) return;
+    switch (kind) {
+    case 0:
+        p->x = 0; // OK - narrowed before switch
+        break;
+    case 1:
+        p->x = 1; // OK - narrowing carries into cases
+        break;
+    default:
+        p->x = -1; // OK
+        break;
+    }
+}
+
+void switch_test_null_check_then(Entity* _Nullable p, int kind) {
+    if (p) {
+        switch (kind) {
+        case 0:
+            p->x = 0; // OK
+            break;
+        case 1:
+            p->x = 1; // OK
+            break;
+        }
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Terminators: throw, goto, break, continue
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void term_test_throw_narrows(Entity* _Nullable p) {
+    if (!p) throw "null pointer";
+    p->x = 1; // OK - throw terminates
+}
+
+void term_test_throw_in_compound(Entity* _Nullable p) {
+    if (!p) {
+        throw "null";
+    }
+    p->x = 1; // OK
+}
+
+void term_test_goto_narrows(Entity* _Nullable p) {
+    if (!p) goto cleanup;
+    p->x = 1; // OK - goto terminates
+cleanup:
+    return;
+}
+
+void term_test_break_narrows(Entity* _Nullable p) {
+    for (int i = 0; i < 10; i++) {
+        if (!p) break;
+        p->x = i; // OK - break terminates
+    }
+}
+
+void term_test_break_while(Entity* _Nullable p) {
+    while (true) {
+        if (!p) break;
+        p->x = 1; // OK
+    }
+}
+
+void term_test_continue_narrows(Entity* _Nullable p) {
+    for (int i = 0; i < 10; i++) {
+        if (!p) continue;
+        p->x = i; // OK - continue terminates
+    }
+}
+
+void term_test_positive_check_else_return(Entity* _Nullable p) {
+    if (p) {
+        // use p
+    } else {
+        return;
+    }
+    p->x = 1; // OK - only reachable when p is non-null
+}
+
+void term_test_noreturn_then_deref(Entity* _Nullable p) {
+    if (!p) fatal("null");
+    p->x = 1; // OK
+}
+
+void term_test_two_checks_return(Entity* _Nullable p, Entity* _Nullable q) {
+    if (!p) return;
+    if (!q) return;
+    p->x = q->x; // OK - both narrowed
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Ternary operator narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void ternary_test_true_branch(Node* _Nullable p) {
+    int x = p ? p->value : 0; // OK - p narrowed to nonnull in true branch
+}
+
+void ternary_test_false_branch_negated(Node* _Nullable p) {
+    int x = !p ? 0 : p->value; // OK - p narrowed to nonnull in false branch
+}
+
+void ternary_test_no_narrowing_false(Node* _Nullable p) {
+    int x = p ? 0 : p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void ternary_test_deref_star(Node* _Nullable p) {
+    Node n = p ? *p : (Node){0, nullptr, nullptr, nullptr, nullptr, nullptr}; // OK - p narrowed to nonnull
+    (void)n;
+}
+
+void ternary_test_ne_null(Node* _Nullable p) {
+    int x = (p != nullptr) ? p->value : -1; // OK
+}
+
+void ternary_test_eq_null(Node* _Nullable p) {
+    int x = (p == nullptr) ? -1 : p->value; // OK - narrowed in false branch
+}
+
+void ternary_test_and_both(Node* _Nullable p, Node* _Nullable q) {
+    int x = (p && q) ? p->value + q->value : 0; // OK - both narrowed
+}
+
+void ternary_test_nested(Node* _Nullable p, Node* _Nullable q) {
+    int x = p ? (q ? p->value + q->value : p->value) : 0; // OK
+}
+
+void ternary_test_unrelated_cond(int flag, Node* _Nullable p) {
+    int x = flag ? p->value : 0; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Unannotated false positives (expected under nullable-default)
+// ===----------------------------------------------------------------------===//
+
+// Under -fnullability-default=nullable, unannotated pointers are nullable.
+// These patterns correctly warn.
+
+inline bool unannotated_getData(const uint8_t** buffers, int readIndex) {
+    auto buffer = buffers[readIndex]; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    return buffer != nullptr;
+}
+
+struct UnannotatedWidget {
+    int x;
+    ~UnannotatedWidget() {}
+};
+
+void unannotated_test_deleter() {
+    auto* ptr = new UnannotatedWidget;
+    auto deleter = [](UnannotatedWidget* w) {
+        w->~UnannotatedWidget(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    };
+    deleter(ptr);
+}
+
+struct UnannotatedBuffer {
+    int offset;
+    uint8_t* getBuffer() {
+        return reinterpret_cast<uint8_t*>(this) + offset;
+    }
+    void use() {
+        uint8_t val = getBuffer()[0]; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+};
+
+// ===----------------------------------------------------------------------===//
+// void* cast patterns
+// ===----------------------------------------------------------------------===//
+
+struct Data {
+    int value;
+};
+
+void voidstar_test_cast_deref(void* obj) {
+    Data* p = static_cast<Data*>(obj);
+    *p = Data{42}; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    p->value = 1;  // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void voidstar_test_reinterpret_cast(void* obj) {
+    *reinterpret_cast<void**>(obj) = nullptr; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void voidstar_test_nullable(void* _Nullable obj) {
+    Data* p = static_cast<Data*>(obj);
+    *p = Data{42}; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void voidstar_test_double_ptr(void* obj) {
+    *reinterpret_cast<void**>(obj) = nullptr; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    *reinterpret_cast<int**>(obj) = nullptr;  // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void voidstar_test_double_ptr_local(void* obj) {
+    void** pp = reinterpret_cast<void**>(obj);
+    *pp = nullptr; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void voidstar_test_checked(void* obj) {
+    if (obj) {
+        Data* p = static_cast<Data*>(obj);
+        *p = Data{42}; // OK -- obj was checked
+    }
+}
+
+// ===----------------------------------------------------------------------===//
+// While-loop narrowing
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+void while_test_basic(Node* _Nullable p) {
+    while (p) {
+        p->value = 1; // OK - p narrowed by while condition
+    }
+}
+
+void while_test_linked_list(Node* _Nullable head) {
+    Node* _Nullable p = head;
+    while (p) {
+        p->value = 0;
+        p = p->next; // OK - p narrowed, so p->next is safe
+    }
+}
+
+void while_test_nested(Node* _Nullable p) {
+    while (p) {
+        Node* _Nullable q = p->next;
+        while (q) {
+            q->value = p->value; // OK - both narrowed
+            q = q->next;
+        }
+        p = p->next;
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Range-for loop (works under both nullable and nonnull defaults)
+// ===----------------------------------------------------------------------===//
+
+struct Item { int value; };
+
+template <typename T, int N>
+struct Array {
+    T data_[N];
+    T* begin() { return data_; }
+    T* end() { return data_ + N; }
+    const T* begin() const { return data_; }
+    const T* end() const { return data_ + N; }
+};
+
+void range_for_test_no_warn() {
+    Array<Item, 3> arr = {};
+    for (const auto& item : arr) {
+        (void)item.value;
+    }
+}
+
+void range_for_test_c_array() {
+    Item items[4] = {};
+    for (const auto& item : items) {
+        (void)item.value;
+    }
+}
+
+void range_for_test_deref_still_warns(int* _Nullable p) {
+    (void)*p; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
diff --git a/clang/test/SemaCXX/flow-nullability-crubit-regression.cpp b/clang/test/SemaCXX/flow-nullability-crubit-regression.cpp
new file mode 100644
index 0000000000000..5a5ff550f792d
--- /dev/null
+++ b/clang/test/SemaCXX/flow-nullability-crubit-regression.cpp
@@ -0,0 +1,591 @@
+// Regression tests ported from Google Crubit's nullability checker test suite.
+// Each section maps to a specific Crubit test file, testing that nullable-clang
+// handles the same patterns (and more). The final sections document where we
+// exceed Crubit and where we have permanent gaps.
+//
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -std=c++17 -Wno-unused-value -Wno-nonnull %s -verify
+
+#pragma clang assume_nonnull begin
+
+// Forward declarations used throughout.
+int *_Nullable GetNullable();
+int *_Nonnull GetNonnull();
+int *GetUnknown();
+bool cond();
+[[noreturn]] void fatal(const char *msg);
+[[noreturn]] void abort_fn();
+
+// ==========================================================================
+// BASIC: dereference, assignment, return, argument passing
+// (from crubit/nullability/test/basic.cc)
+// ==========================================================================
+
+// --- Deref nullptr ---
+void test_deref_nullptr() {
+  int *_Nullable x = nullptr;
+  (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// --- Deref address-of is always safe ---
+void test_deref_addr_of() {
+  int i;
+  int *x = &i;
+  (void)*x; // no warning
+}
+
+// --- Deref address-of, transitive ---
+void test_deref_addr_of_transitive() {
+  int i;
+  int *x = &i;
+  int *y = x;
+  (void)*y; // no warning
+}
+
+// --- Deref nonnull param ---
+void test_deref_nonnull_param(int *_Nonnull x) {
+  (void)*x; // no warning
+}
+
+// --- Deref nonnull param, transitive ---
+void test_deref_nonnull_transitive(int *_Nonnull x) {
+  int *y = x;
+  (void)*y; // no warning
+}
+
+// --- Deref nullable param without check ---
+void test_deref_nullable_unguarded(int *_Nullable x) {
+  (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// --- Deref nullable param, transitive ---
+void test_deref_nullable_transitive(int *_Nullable x) {
+  int *y = x;
+  (void)*y; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// --- Arrow operator on nullable ---
+struct Foo {
+  int val;
+  Foo *next;
+  Foo *getNext();
+};
+
+void test_arrow_nullable_field(Foo *_Nullable f) {
+  (void)f->val; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  if (f) {
+    (void)f->val; // no warning -- narrowed
+  }
+}
+
+void test_arrow_nullable_method(Foo *_Nullable f) {
+  (void)f->getNext(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  if (f) {
+    (void)f->getNext(); // no warning
+  }
+}
+
+// --- Arrow on nonnull is always safe ---
+void test_arrow_nonnull(Foo *_Nonnull f) {
+  (void)f->val;       // no warning
+  (void)f->getNext(); // no warning
+}
+
+// --- Array subscript (p[n] is *(p+n)) ---
+void test_subscript_nullable(int *_Nonnull nonnull, int *_Nullable nullable) {
+  (void)nonnull[0];  // no warning
+  (void)nullable[0]; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// --- Assignment: nullable to nonnull ---
+void test_assign_nullable_to_nonnull(int *_Nullable nullable) {
+  int *_Nonnull nn = GetNonnull();
+  nn = nullable; // expected-warning{{assigning nullable pointer to nonnull variable}} expected-note{{add a null check}}
+}
+
+// --- Assignment: safe cases ---
+void test_assign_safe(int *_Nonnull nonnull) {
+  int *_Nullable x = nullptr;  // no warning
+  int *_Nonnull y = nonnull;   // no warning
+}
+
+// --- Argument passing: nullable to nonnull parameter ---
+void takes_nonnull(int *_Nonnull p);
+
+void test_pass_nullable_to_nonnull(int *_Nullable p) {
+  takes_nonnull(p); // expected-warning{{passing nullable pointer to nonnull parameter}} expected-note{{add a null check}}
+}
+
+void test_pass_nonnull_ok(int *_Nonnull p) {
+  takes_nonnull(p); // no warning
+}
+
+// --- Return: nullable from nonnull return type ---
+int *_Nonnull test_return_nullable_param(int *_Nullable p) {
+  return p; // expected-warning{{returning nullable pointer from function with nonnull return type}} expected-note{{add a null check}}
+}
+
+int *_Nonnull test_return_nonnull_ok(int *_Nonnull p) {
+  return p; // no warning
+}
+
+// --- Return nullable from nullable return type (always ok) ---
+int *_Nullable test_return_null_from_nullable() {
+  return nullptr; // no warning
+}
+
+// --- Multiple returns: one path safe, one not ---
+int *_Nonnull test_return_multiple(bool b, int *_Nonnull nn) {
+  if (b) {
+    return GetNullable(); // expected-warning{{returning nullable pointer from function with nonnull return type}} expected-note{{add a null check}}
+  }
+  return nn; // no warning
+}
+
+// --- Return after null-check (narrowed) ---
+int *_Nonnull test_return_narrowed(int *_Nullable p, int *_Nonnull fallback) {
+  if (p) {
+    return p; // no warning -- p is narrowed to nonnull
+  }
+  return fallback;
+}
+
+// ==========================================================================
+// POINTER ARITHMETIC
+// (from crubit/nullability/test/pointer_arithmetic_diagnosis.cc)
+// ==========================================================================
+
+// --- Arithmetic on nullable warns ---
+void test_ptr_arith_nullable(int *_Nullable nullable, int i) {
+  int *orig = nullable;
+
+  nullable + i;  // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  nullable - i;  // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+
+  nullable++;    // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  nullable = orig;
+
+  ++nullable;    // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  nullable = orig;
+
+  nullable--;    // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  nullable = orig;
+
+  --nullable;    // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  nullable = orig;
+
+  nullable += 1; // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+  nullable = orig;
+
+  nullable -= 1; // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+}
+
+// --- Arithmetic on nonnull is safe ---
+void test_ptr_arith_nonnull(int *_Nonnull nonnull, int i) {
+  int *orig = nonnull;
+  nonnull + i;
+  nonnull - i;
+  nonnull++;
+  nonnull = orig;
+  ++nonnull;
+  nonnull = orig;
+  nonnull--;
+  nonnull = orig;
+  --nonnull;
+  nonnull = orig;
+  nonnull += 1;
+  nonnull = orig;
+  nonnull -= 1;
+  // no warnings anywhere
+}
+
+// --- Arithmetic on nullable after null-check is safe ---
+void test_ptr_arith_after_check(int *_Nullable nullable) {
+  if (nullable) {
+    nullable + 1; // no warning -- narrowed
+    nullable++;   // no warning
+  }
+}
+
+// ==========================================================================
+// PATH-SENSITIVE: null checks suppress warnings, narrowing
+// (from crubit/nullability/test/path_sensitive.cc)
+// ==========================================================================
+
+// --- Basic if-check narrows ---
+void test_if_narrows(int *_Nullable p) {
+  if (p) {
+    (void)*p; // no warning
+  }
+  (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// --- if/else narrowing ---
+void test_if_else(int *_Nullable p) {
+  if (p) {
+    (void)*p; // no warning -- true branch
+  } else {
+    (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// --- Early return narrows ---
+void test_early_return(int *_Nullable p) {
+  if (!p)
+    return;
+  (void)*p; // no warning
+}
+
+// --- Ternary narrows ---
+int test_ternary(int *_Nullable p) {
+  return p ? *p : 0; // no warning -- p is checked
+}
+
+// --- != nullptr narrows ---
+void test_ne_nullptr(int *_Nullable p) {
+  if (p != nullptr) {
+    (void)*p; // no warning
+  }
+}
+
+// --- == nullptr + early return ---
+void test_eq_nullptr_return(int *_Nullable p) {
+  if (p == nullptr)
+    return;
+  (void)*p; // no warning
+}
+
+// --- ComplexLoopCondition: compound && with assignment in while ---
+void test_complex_loop_condition() {
+  int *p1;
+  int *p2;
+  while ((p1 = GetNullable()) != nullptr && (p2 = GetNullable()) != nullptr) {
+    (void)*p1; // no warning -- checked in condition
+    (void)*p2; // no warning -- checked in condition
+  }
+}
+
+// --- For-loop can't prove body executes ---
+void test_for_loop_no_guarantee() {
+  int *_Nullable p = nullptr;
+  int x = 0;
+  for (int i = 0; i < 10; ++i) {
+    p = &x;
+  }
+  *p = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// --- Do-while guarantees at least one iteration ---
+void test_do_while_guarantees_execution() {
+  int *_Nullable p = nullptr;
+  int x = 0;
+  int i = 0;
+  do {
+    p = &x;
+    ++i;
+  } while (i < 10);
+  *p = 1; // no warning -- do-while body always executes
+}
+
+// --- ConditionalInitialization2: bool guard does not imply nonnull ---
+void test_conditional_init_unsafe() {
+  int *_Nullable p = nullptr;
+  bool b = false;
+  b = cond();
+  if (!b)
+    p = GetNonnull();
+  (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// ==========================================================================
+// ALIASES: y = x; if (y) *x works
+// (from crubit/nullability/test/variable_aliasing.cc)
+// ==========================================================================
+
+// --- Check alias, deref original ---
+void test_alias_check_deref_original(int *_Nullable x) {
+  int *y = x;
+  if (y) {
+    (void)*x; // no warning -- y aliases x, y is checked
+  } else {
+    (void)*x; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// --- Check original, deref alias ---
+void test_alias_check_original_deref_alias(int *_Nullable x) {
+  int *y = x;
+  if (x) {
+    (void)*y; // no warning -- x is checked, y aliases x
+  } else {
+    (void)*y; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// ==========================================================================
+// RETURN STATEMENTS (additional patterns)
+// (from crubit/nullability/test/return_statements.cc)
+// ==========================================================================
+
+// --- Return from merged paths: one path null, one nonnull ---
+int *_Nonnull test_return_merged_paths(bool b, int i) {
+  int *_Nullable ptr;
+  if (b) {
+    ptr = &i;
+  } else {
+    ptr = nullptr;
+  }
+  return ptr; // expected-warning{{returning nullable pointer from function with nonnull return type}} expected-note{{add a null check}}
+}
+
+// --- Return nullable after narrowing (safe) ---
+int *_Nonnull test_return_nullable_narrowed(int *_Nullable p1,
+                                            int *_Nonnull fallback) {
+  if (p1) {
+    return p1; // no warning -- narrowed
+  }
+  return fallback;
+}
+
+// ==========================================================================
+// CHECK MACROS: if (!p) abort(), assert(p) style
+// (from crubit/nullability/test/check_macros.cc and basic.cc)
+// ==========================================================================
+
+// --- Simple CHECK macro (if + __builtin_abort) ---
+#define CHECK(x) \
+  if (!(x))      \
+    __builtin_abort();
+
+void test_check_macro(int *_Nullable p) {
+  CHECK(p);
+  (void)*p; // no warning -- CHECK asserted nonnull
+}
+
+// --- CHECK with noreturn function ---
+#define ASSERT(cond) \
+  do {               \
+    if (!(cond))     \
+      fatal("fail"); \
+  } while (0)
+
+void test_assert_macro(int *_Nullable p) {
+  ASSERT(p);
+  (void)*p; // no warning
+}
+
+// --- CHECK two variables ---
+void test_check_two_vars(int *_Nullable p, int *_Nullable q) {
+  CHECK(p);
+  CHECK(q);
+  (void)(*p + *q); // no warning
+}
+
+// --- if (!p) abort(); explicit pattern ---
+void test_if_abort(int *_Nullable p) {
+  if (!p)
+    abort_fn();
+  (void)*p; // no warning
+}
+
+// ==========================================================================
+// CONVERGENCE: loops with nullable pointers
+// (from crubit/nullability/test/convergence.cc)
+// ==========================================================================
+
+// --- Loop: nullable init, nonnull update still warns ---
+void test_loop_nullable_nonnull() {
+  for (int *p = GetNullable();; p = GetNonnull()) {
+    (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// --- Loop: both nullable ---
+void test_loop_nullable_nullable() {
+  for (int *p = GetNullable();; p = GetNullable()) {
+    (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// --- Loop: nonnull init, nullable update ---
+void test_loop_nonnull_nullable() {
+  for (int *p = GetNonnull();; p = GetNullable()) {
+    (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// --- Loop: both nonnull (safe) ---
+void test_loop_nonnull_nonnull() {
+  for (int *p = GetNonnull();; p = GetNonnull()) {
+    (void)*p; // no warning
+  }
+}
+
+// --- Loop with null check in condition (safe) ---
+void test_loop_checked() {
+  for (int *p = GetNullable(); p != nullptr; p = GetNullable()) {
+    (void)*p; // no warning -- loop condition checks p
+  }
+}
+
+// --- Loop with unrelated condition ---
+void test_loop_unrelated_condition() {
+  for (int *p = GetNonnull(); cond(); p = GetNullable()) {
+    (void)*p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+  }
+}
+
+// --- While assignment: while ((p = f())) ---
+void test_while_assignment() {
+  int *p;
+  while ((p = GetNullable())) {
+    (void)*p; // no warning -- loop condition checks p
+  }
+}
+
+// --- InconsistentLoopStateRepro (Crubit: b/300979650) ---
+// A prior loop must not corrupt subsequent null-checked code.
+void test_inconsistent_loop_state(int *b, int *e) {
+  for (; b != e; ++b)
+    ;
+  int *ptr = GetNullable();
+  if (ptr != nullptr) {
+    while (cond()) {
+      (void)*ptr; // no warning -- ptr is checked
+    }
+  }
+}
+
+// --- ReproForFalsePositiveTriggeredByUnrelatedLoop ---
+struct Node {
+  const Node *_Nonnull parent() const;
+};
+
+void test_unrelated_loop_no_false_positive(const Node *_Nonnull node) {
+  for (bool b2 = cond(); cond(); b2 = false) {
+  }
+  while (cond()) {
+    node = node->parent(); // no warning -- node is _Nonnull
+  }
+}
+
+// --- WidenAfterContradiction ---
+void test_widen_after_contradiction_var() {
+  bool b = true;
+  while (b) {
+    b = cond();
+  }
+  int *p = GetUnknown();
+  while (cond()) {
+    (void)*p; // no warning -- p is unknown (not nullable)
+  }
+}
+
+void test_widen_after_contradiction_arbitrary() {
+  bool b = true;
+  while (cond()) {
+    b = false;
+  }
+  if (b)
+    return;
+  int *p = GetUnknown();
+  while (cond()) {
+    (void)*p; // no warning
+  }
+}
+
+// --- TriplyNestedForLoopSingleIteration ---
+// Minimized from ABSL_LOG_INTERNAL_STATEFUL_CONDITION.
+void test_triply_nested_loop() {
+  for (bool b = true; b;)
+    for (int x = 0; b;)
+      for (int c = 0; b; b = false) {
+        (void)0;
+      }
+}
+
+// ==========================================================================
+// PATTERNS NULLABLE-CLANG HANDLES THAT CRUBIT DOESN'T
+// These demonstrate advantages of the CFG-based approach over Crubit's
+// dataflow framework.
+// ==========================================================================
+
+// --- Aliases: bidirectional narrowing propagation ---
+// Crubit handles this too, but our implementation tracks alias chains
+// (y -> x -> canonical) and invalidates on reassignment.
+void test_alias_chain(int *_Nullable x) {
+  int *y = x;
+  int *z = y; // z -> y -> x
+  if (z) {
+    (void)*x; // no warning -- z aliases x transitively
+    (void)*y; // no warning
+  }
+}
+
+// --- __builtin_expect / LIKELY / UNLIKELY macros ---
+// Crubit requires special modeling for each macro. Our analysis sees
+// through __builtin_expect transparently because the CFG decomposes it.
+#define LIKELY(x) __builtin_expect(!!(x), 1)
+#define UNLIKELY(x) __builtin_expect(!!(x), 0)
+
+void test_likely_narrowing(int *_Nullable p) {
+  if (LIKELY(p)) {
+    (void)*p; // no warning -- sees through __builtin_expect
+  }
+}
+
+void test_unlikely_early_return(int *_Nullable p) {
+  if (UNLIKELY(!p))
+    return;
+  (void)*p; // no warning
+}
+
+// --- __builtin_assume ---
+// Crubit has no equivalent; our analysis treats __builtin_assume(p) as
+// an unconditional narrowing hint.
+void test_builtin_assume(int *_Nullable p) {
+  __builtin_assume(p != nullptr);
+  (void)*p; // no warning
+}
+
+// --- Bare-brace assertion macros ---
+// Patterns like `{ if (!(p)) abort(); }` that don't use do-while.
+// Our CFG-based approach handles any terminating pattern naturally.
+#define BRACE_ASSERT(cond) \
+  {                        \
+    if (!(cond))           \
+      fatal("assert");     \
+  }
+
+void test_brace_assert(int *_Nullable p) {
+  BRACE_ASSERT(p);
+  (void)*p; // no warning
+}
+
+// --- Pointer arithmetic warning (Crubit has this too) ---
+// We emit a distinct warning group (-Wflow-nullable-arithmetic) so users
+// can enable/disable pointer arithmetic checks independently.
+void test_arith_warning_group(int *_Nullable p) {
+  p + 1; // expected-warning{{pointer arithmetic on nullable pointer}} expected-note{{add a null check before performing arithmetic}}
+}
+
+// ==========================================================================
+// PERMANENT GAPS: patterns requiring SAT-solver disjunctive reasoning
+// Crubit can prove these safe; nullable-clang cannot (by design).
+// ==========================================================================
+
+// --- Disjunctive reasoning: if (!p1 && !p2) return ---
+// After the early return, at least one of p1/p2 is non-null, but we can't
+// determine WHICH one without a SAT solver. In the else branch below,
+// p1 is null so p2 must be non-null -- but we don't track that.
+void test_disjunctive_gap(int *_Nullable p1, int *_Nullable p2) {
+  if (!p1 && !p2)
+    return;
+  if (p1)
+    (void)*p1; // no warning -- p1 is checked
+  else
+    (void)*p2; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+               // Crubit would NOT warn here (SAT-based disjunctive reasoning).
+               // This is a fundamental limitation of our set-intersection approach.
+}
+
+#pragma clang assume_nonnull end
diff --git a/clang/test/SemaCXX/flow-nullability-cxx-features.cpp b/clang/test/SemaCXX/flow-nullability-cxx-features.cpp
new file mode 100644
index 0000000000000..80581011d7595
--- /dev/null
+++ b/clang/test/SemaCXX/flow-nullability-cxx-features.cpp
@@ -0,0 +1,1041 @@
+// Consolidated tests for C++-specific features with flow-sensitive nullability.
+// Covers: templates, lambdas, coroutines, structured bindings, smart pointers,
+// conversion operators, new expressions, exceptions, if-constexpr, and
+// nullable-default template return types.
+//
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -std=c++20 -fcxx-exceptions -I%S/Inputs %s -verify
+
+// ===----------------------------------------------------------------------===//
+// Shared type definitions
+// ===----------------------------------------------------------------------===//
+
+struct Node {
+    int value;
+    Node * _Nullable next;
+};
+
+Node * _Nullable getNode();
+Node * _Nonnull getSafeNode();
+
+// ===----------------------------------------------------------------------===//
+// Templates
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+template <typename T>
+void template_deref_unchecked(T * _Nullable p) {
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+template <typename T>
+void template_deref_guarded(T * _Nullable p) {
+    if (p)
+        (void)p->value; // OK -- narrowed
+}
+
+template <typename T>
+void template_deref_nonnull(T * _Nonnull p) {
+    (void)p->value; // OK -- _Nonnull
+}
+
+void template_test_functions() {
+    Node * _Nullable n = getNode();
+    template_deref_unchecked(n); // expected-note{{in instantiation of function template specialization 'template_deref_unchecked<Node>' requested here}}
+    template_deref_guarded(n);
+    template_deref_nonnull(getSafeNode());
+}
+
+// === Template class with nullable member ===
+
+template <typename T>
+struct Wrapper {
+    T * _Nullable ptr;
+
+    void use_unchecked() {
+        (void)ptr->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+
+    void use_guarded() {
+        if (ptr)
+            (void)ptr->value; // OK
+    }
+};
+
+void template_test_class() {
+    Wrapper<Node> w;
+    w.use_unchecked(); // expected-note{{in instantiation of member function 'Wrapper<Node>::use_unchecked' requested here}}
+    w.use_guarded();
+}
+
+// === Template with multiple pointer params of different nullability ===
+
+template <typename T>
+void template_mixed_nullability(T * _Nonnull safe, T * _Nullable risky) {
+    (void)safe->value; // OK -- _Nonnull
+    (void)risky->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void template_test_mixed() {
+    template_mixed_nullability(getSafeNode(), getNode()); // expected-note{{in instantiation of function template specialization 'template_mixed_nullability<Node>' requested here}}
+}
+
+// === Template that narrows then uses ===
+
+template <typename T>
+T* _Nullable template_find(T * _Nullable head, int target) {
+    for (T * _Nullable p = head; p; p = p->next) {
+        if (p->value == target) // OK -- narrowed by loop condition
+            return p;
+    }
+    return nullptr;
+}
+
+void template_test_find() {
+    Node * _Nullable head = getNode();
+    template_find(head, 42);
+}
+
+// === Template with cast -- the key false-positive scenario ===
+// Template instantiation can produce casts with _Nullable in the dest type.
+// The analysis should look through these casts to the source type.
+
+template <typename T>
+T* template_cast_and_use(void *raw) {
+    T *p = static_cast<T *>(raw);
+    // raw is void* (unannotated in nullable-default mode), but static_cast
+    // may bake the template param's nullability into the result.
+    // Should not warn -- source (raw) is not explicitly _Nullable.
+    (void)p->value; // OK -- unannotated source through cast
+    return p;
+}
+
+void template_test_cast() {
+    int dummy;
+    template_cast_and_use<Node>(&dummy);
+}
+
+// === Non-type template parameters (no effect on nullability) ===
+
+template <int N>
+void template_fixed_iteration(Node * _Nullable p) {
+    if (!p) return;
+    for (int i = 0; i < N; i++)
+        (void)p->value; // OK -- narrowed
+}
+
+void template_test_non_type() {
+    template_fixed_iteration<10>(getNode());
+}
+
+// === Template with auto return type ===
+
+template <typename T>
+auto template_safe_access(T * _Nullable p, int fallback) {
+    if (p)
+        return p->value; // OK
+    return fallback;
+}
+
+void template_test_auto_return() {
+    template_safe_access(getNode(), -1);
+}
+
+// === Dependent type that resolves to pointer ===
+
+template <typename T>
+struct PointerHolder {
+    using Ptr = T*;
+    Ptr _Nullable held;
+
+    void use() {
+        if (held)
+            (void)held->value; // OK -- narrowed
+    }
+};
+
+void template_test_dependent_type() {
+    PointerHolder<Node> h;
+    h.use();
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Lambdas
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+// === Capture nullable by value -- warns inside lambda ===
+
+void lambda_test_capture_nullable_by_value(Node * _Nullable p) {
+    auto f = [p]() {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    };
+    f();
+}
+
+// === Capture narrowed by value -- still nullable inside lambda ===
+// Even though p was narrowed before the lambda, the capture creates a new
+// copy. The analysis treats each function body independently.
+
+void lambda_test_capture_narrowed_by_value(Node * _Nullable p) {
+    if (p) {
+        auto f = [p]() {
+            // p is captured by value from narrowed context, but the lambda
+            // is a separate function body. The analysis sees p as the
+            // lambda's parameter (implicitly nullable in nullable-default).
+            (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        };
+        f();
+        (void)p->value; // OK -- still narrowed in outer scope
+    }
+}
+
+// === Capture by reference -- narrowing does not propagate ===
+
+void lambda_test_capture_by_ref(Node * _Nullable p) {
+    if (p) {
+        auto f = [&p]() {
+            (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        };
+        f();
+    }
+}
+
+// === Lambda with its own null check ===
+
+void lambda_test_own_check(Node * _Nullable p) {
+    auto f = [p]() {
+        if (p)
+            (void)p->value; // OK -- narrowed inside lambda
+    };
+    f();
+}
+
+// === Immediately-invoked lambda expression ===
+
+void lambda_test_iife(Node * _Nullable p) {
+    [p]() {
+        if (p)
+            (void)p->value; // OK -- narrowed
+    }();
+}
+
+// === Lambda capturing nonnull pointer ===
+
+void lambda_test_capture_nonnull(Node * _Nonnull p) {
+    auto f = [p]() {
+        (void)p->value; // OK -- _Nonnull captured
+    };
+    f();
+}
+
+// === Generic lambda with auto parameter ===
+
+void lambda_test_generic() {
+    auto f = [](auto * _Nullable p) {
+        if (p)
+            (void)p->value; // OK -- narrowed
+    };
+    Node * _Nullable n = nullptr;
+    f(n);
+}
+
+// === Lambda returning nullable pointer ===
+
+void lambda_test_return() {
+    Node * _Nullable n = nullptr;
+    auto getter = [&n]() -> Node * _Nullable { return n; };
+    Node * _Nullable result = getter();
+    (void)result->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// === Nested lambdas ===
+
+void lambda_test_nested(Node * _Nullable p) {
+    auto outer = [p]() {
+        auto inner = [p]() {
+            (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+        };
+        inner();
+    };
+    outer();
+}
+
+// === Lambda with no captures -- unrelated pointer ===
+
+void lambda_test_no_capture() {
+    auto f = [](Node * _Nullable p) {
+        if (!p) return;
+        (void)p->value; // OK -- narrowed by early return
+    };
+    f(nullptr);
+}
+
+// === Mutable lambda modifying captured pointer ===
+
+void lambda_test_mutable_capture(Node * _Nullable p) {
+    auto f = [p]() mutable {
+        p = nullptr; // mutate the captured copy
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    };
+    f();
+}
+
+// === Init-capture (C++14) -- captures are independent variables ===
+
+void lambda_test_init_capture_warns() {
+    auto f = [p = getNode()]() {
+        (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    };
+    f();
+}
+
+void lambda_test_init_capture_with_check() {
+    auto f = [p = getNode()]() {
+        if (p)
+            (void)p->value; // OK -- narrowed inside lambda
+    };
+    f();
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Coroutines
+// ===----------------------------------------------------------------------===//
+
+#include "std-coroutine.h"
+
+// --- Generator coroutine type ---
+
+struct Generator {
+    struct promise_type {
+        Node * _Nullable current;
+        Generator get_return_object() { return {}; }
+        std::suspend_always initial_suspend() { return {}; }
+        std::suspend_always final_suspend() noexcept { return {}; }
+        void unhandled_exception() {}
+        std::suspend_always yield_value(Node * _Nullable val) {
+            current = val;
+            return {};
+        }
+        void return_void() {}
+    };
+};
+
+// --- Task coroutine type ---
+
+struct Task {
+    struct promise_type {
+        Task get_return_object() { return {}; }
+        std::suspend_never initial_suspend() { return {}; }
+        std::suspend_always final_suspend() noexcept { return {}; }
+        void unhandled_exception() {}
+        void return_void() {}
+    };
+};
+
+// --- Awaitable that returns a nullable pointer ---
+
+struct NullableAwaitable {
+    bool await_ready() const noexcept { return true; }
+    void await_suspend(std::coroutine_handle<>) const noexcept {}
+    Node * _Nullable await_resume() const noexcept { return nullptr; }
+};
+
+#pragma clang assume_nonnull begin
+
+// === Basic coroutine with nullable check ===
+
+Generator coroutine_yield_nodes(Node * _Nullable head) {
+    for (Node * _Nullable p = head; p; p = p->next) {
+        (void)p->value; // OK -- narrowed by loop condition
+        co_yield p;
+    }
+}
+
+// === co_await returning nullable ===
+
+Task coroutine_consume_awaitable() {
+    NullableAwaitable awaitable;
+    Node * _Nullable result = co_await awaitable;
+    if (result) {
+        (void)result->value; // OK -- narrowed
+    }
+    co_return;
+}
+
+// === Null check before co_yield ===
+
+Generator coroutine_guarded_yield(Node * _Nullable n) {
+    if (n) {
+        (void)n->value; // OK -- narrowed
+        co_yield n;
+        (void)n->value; // OK -- still narrowed (no reassignment)
+    }
+}
+
+// === Multiple co_yields with independent checks ===
+
+Generator coroutine_multi_yield(Node * _Nullable a, Node * _Nullable b) {
+    if (a) {
+        co_yield a;
+    }
+    if (b) {
+        co_yield b;
+    }
+}
+
+// === Coroutine with nonnull parameter ===
+
+Generator coroutine_nonnull_param(Node * _Nonnull n) {
+    (void)n->value; // OK -- _Nonnull
+    co_yield n;
+    (void)n->value; // OK -- _Nonnull
+}
+
+// === Unchecked nullable deref in coroutine body -- should warn ===
+
+Task coroutine_test_unchecked_deref(Node * _Nullable p) {
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    co_return;
+}
+
+// === co_await result used without check -- should warn ===
+
+Task coroutine_test_unchecked_co_await_result() {
+    NullableAwaitable awaitable;
+    Node * _Nullable result = co_await awaitable;
+    (void)result->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Structured Bindings
+// ===----------------------------------------------------------------------===//
+
+// Pair-like type for structured bindings
+struct PtrPair {
+    Node * _Nullable first;
+    Node * _Nullable second;
+};
+
+PtrPair getPair();
+
+// Tuple-like for testing get<> protocol
+struct Triple {
+    Node * _Nullable a;
+    Node * _Nullable b;
+    int c;
+};
+
+Triple getTriple();
+
+#pragma clang assume_nonnull begin
+
+// === Basic struct decomposition ===
+// Structured binding variables are BindingDecls, not VarDecls.
+// The analysis does not currently track narrowing on BindingDecls,
+// so these accesses do not warn even without null checks.
+// This is a known false negative -- documenting that no crash occurs.
+
+void binding_test_struct_decomp() {
+    PtrPair pair = getPair();
+    auto [p, q] = pair;
+    if (p) {
+        (void)p->value; // OK -- narrowed (even though binding)
+    }
+    if (q) {
+        (void)q->value; // OK
+    }
+}
+
+// === Decomposition with && guard ===
+
+void binding_test_decomp_both_checked() {
+    auto [p, q] = getPair();
+    if (p && q) {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+// === Mixed nullable/non-nullable struct ===
+
+struct MixedPair {
+    Node * _Nonnull safe;
+    Node * _Nullable risky;
+};
+
+MixedPair getMixed();
+
+void binding_test_mixed_decomp() {
+    auto [safe, risky] = getMixed();
+    (void)safe->value;  // OK -- source is _Nonnull
+}
+
+void binding_test_mixed_decomp_guarded() {
+    auto [safe, risky] = getMixed();
+    (void)safe->value; // OK
+    if (risky) {
+        (void)risky->value; // OK -- checked
+    }
+}
+
+// === Decomposition from triple ===
+
+void binding_test_triple_decomp() {
+    auto [a, b, c] = getTriple();
+    if (a && b) {
+        (void)a->value; // OK
+        (void)b->value; // OK
+    }
+    (void)c; // OK -- int, not a pointer
+}
+
+// === Reference binding through structured bindings ===
+
+void binding_test_ref_decomp() {
+    PtrPair pair = getPair();
+    auto &[p, q] = pair;
+    if (p) {
+        (void)p->value; // OK
+    }
+}
+
+// === Workaround: capture into local variable for narrowing ===
+
+void binding_test_capture_workaround() {
+    auto [first, second] = getPair();
+    Node * _Nullable p = first;
+    Node * _Nullable q = second;
+    if (p && q) {
+        (void)p->value; // OK -- local VarDecl is tracked
+        (void)q->value; // OK
+    }
+}
+
+// === Structured binding in if-init (C++17) ===
+
+void binding_test_if_init_decomp() {
+    if (auto [p, q] = getPair(); p && q) {
+        (void)p->value; // OK
+        (void)q->value; // OK
+    }
+}
+
+// === Structured binding in for-range-init ===
+
+struct PairList {
+    PtrPair pairs[3];
+    PtrPair *begin() { return pairs; }
+    PtrPair *end() { return pairs + 3; }
+};
+
+void binding_test_range_decomp(PairList &list) {
+    for (auto [p, q] : list) {
+        if (p) {
+            (void)p->value; // OK
+        }
+    }
+}
+
+// === Decomposition of stack-allocated struct ===
+
+void binding_test_stack_decomp() {
+    int x = 42;
+    Node node{0, nullptr};
+    struct { Node * _Nonnull p; int *q; } s = {&node, &x};
+    auto [p, q] = s;
+    (void)p->value; // OK -- source is _Nonnull
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Smart Pointers
+// ===----------------------------------------------------------------------===//
+
+// Minimal std smart pointer mocks -- must be in namespace std for detection.
+namespace std {
+
+template <typename T>
+struct unique_ptr {
+    T* ptr;
+    using pointer = T*;
+    using element_type = T;
+    pointer operator->() { return ptr; }
+    element_type& operator*() { return *ptr; }
+    pointer get() { return ptr; }
+    explicit operator bool() const { return ptr != nullptr; }
+    void reset() { ptr = nullptr; }
+    void reset(T* p) { ptr = p; }
+    unique_ptr() : ptr(nullptr) {}
+    unique_ptr(unique_ptr&& other) : ptr(other.ptr) { other.ptr = nullptr; }
+    unique_ptr& operator=(unique_ptr&& other) { ptr = other.ptr; other.ptr = nullptr; return *this; }
+    unique_ptr(const unique_ptr&) = delete;
+    unique_ptr& operator=(const unique_ptr&) = delete;
+};
+
+template <typename T>
+struct shared_ptr {
+    T* ptr;
+    T* operator->() { return ptr; }
+    T& operator*() { return *ptr; }
+    T* get() { return ptr; }
+    explicit operator bool() const { return ptr != nullptr; }
+    void reset() { ptr = nullptr; }
+    void reset(T* p) { ptr = p; }
+};
+
+template <typename T, typename... Args>
+unique_ptr<T> make_unique(Args&&... args);
+
+template <typename T, typename... Args>
+shared_ptr<T> make_shared(Args&&... args);
+
+template <typename T>
+T&& move(T& t) noexcept;
+
+} // namespace std
+
+#pragma clang assume_nonnull begin
+
+// Non-std smart pointer (should NOT trigger smart pointer warnings)
+template <typename T>
+struct CustomPtr {
+    T* ptr;
+    T* operator->() { return ptr; }
+    T& operator*() { return *ptr; }
+};
+
+// Iterator (should NOT trigger smart pointer warnings)
+struct Container {
+    struct Iterator {
+        Node* ptr;
+        Node* operator->() { return ptr; }
+        Node& operator*() { return *ptr; }
+    };
+    Iterator begin();
+    Iterator end();
+};
+
+// --- Basic dereference warnings ---
+
+void smartptr_test_deref_warns(std::unique_ptr<Node> sp) {
+    sp->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void smartptr_test_shared_deref_warns(std::shared_ptr<Node> sp) {
+    sp->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+// --- Narrowing via null check ---
+
+void smartptr_test_narrowed_by_check(std::unique_ptr<Node> sp) {
+    if (sp) {
+        sp->value = 1; // OK -- narrowed by bool check
+    }
+}
+
+void smartptr_test_narrowed_negated(std::unique_ptr<Node> sp) {
+    if (!sp)
+        return;
+    sp->value = 1; // OK -- narrowed by early return
+}
+
+// --- make_unique/make_shared narrow ---
+
+void smartptr_test_make_unique_narrows() {
+    auto sp = std::make_unique<Node>();
+    sp->value = 1; // OK -- make_unique always returns non-null
+}
+
+void smartptr_test_make_shared_narrows() {
+    auto sp = std::make_shared<Node>();
+    sp->value = 1; // OK -- make_shared always returns non-null
+}
+
+// --- reset() makes nullable ---
+
+void smartptr_test_reset_makes_nullable(std::unique_ptr<Node> sp) {
+    if (sp) {
+        sp->value = 1; // OK
+    }
+    sp.reset();
+    sp->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void smartptr_test_reset_with_arg_narrows(std::unique_ptr<Node> sp) {
+    sp.reset(new Node());
+    sp->value = 1; // OK -- reset(ptr) gives it a value
+}
+
+void smartptr_test_reset_nullptr_stays_nullable(std::unique_ptr<Node> sp) {
+    if (sp) {
+        sp->value = 1; // OK -- narrowed
+    }
+    sp.reset(nullptr);
+    sp->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+// --- std::move makes source nullable ---
+
+void smartptr_test_move_makes_source_nullable(std::unique_ptr<Node> sp) {
+    if (sp) {
+        sp->value = 1; // OK
+    }
+    auto other = std::move(sp);
+    sp->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+// --- Member smart pointers ---
+
+struct Owner {
+    std::unique_ptr<Node> csm_;
+
+    void use_no_evidence() {
+        csm_->value = 1; // OK -- no evidence of nullability
+    }
+
+    void use_after_reset() {
+        csm_->value = 1; // OK -- before reset
+        csm_.reset();
+        csm_->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    }
+
+    void use_after_reset_with_arg() {
+        csm_.reset(new Node());
+        csm_->value = 1; // OK -- reset(ptr) narrows
+    }
+
+    void use_after_move() {
+        auto other = std::move(csm_);
+        csm_->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    }
+
+    void use_safe_after_reset() {
+        csm_.reset();
+        if (csm_) {
+            csm_->value = 1; // OK -- narrowed
+        }
+    }
+};
+
+// --- Assignment from make_unique re-narrows ---
+
+void smartptr_test_assign_from_make_unique() {
+    std::unique_ptr<Node> sp;
+    sp->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+    sp = std::make_unique<Node>();
+    sp->value = 1; // OK -- assignment from make_unique narrows
+}
+
+// --- Non-std smart pointers should NOT warn ---
+
+void smartptr_test_custom_ptr_no_warn(CustomPtr<Node> cp) {
+    cp->value = 1; // OK -- not a std smart pointer, skip operator->
+}
+
+void smartptr_test_iterator_no_warn(Container c) {
+    auto it = c.begin();
+    it->value = 1; // OK -- iterator, not a smart pointer
+}
+
+// --- .get() returns an unannotated raw pointer, no warning ---
+
+void smartptr_test_get_no_warning(std::unique_ptr<Node> sp) {
+    sp.get()->value = 1; // OK -- get() return type is unannotated
+}
+
+// --- Raw pointers still work as before ---
+
+void smartptr_test_raw_ptr_still_warns(Node* _Nullable p) {
+    p->value = 1; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+void smartptr_test_raw_ptr_narrowed(Node* _Nullable p) {
+    if (p) {
+        p->value = 1; // OK -- narrowed
+    }
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Conversion Operators
+// ===----------------------------------------------------------------------===//
+
+// NOTE: The original test ran under both -fnullability-default=nonnull and
+// -fnullability-default=nullable. This consolidated file uses nullable only.
+// The key thing being tested is that conversion operators (operator T*())
+// don't trigger spurious nullability-inference warnings.
+
+typedef void* bool_type;
+
+struct ConvertToRawPtr {
+    void* data;
+    operator void*() const { return data; }
+};
+
+struct ConvertToTypedef {
+    bool_type data;
+    operator bool_type() const { return data; }
+};
+
+struct ConvertToNonPointer {
+    int value;
+    operator int() const { return value; }
+};
+
+void convop_test_conversions() {
+    ConvertToRawPtr a;
+    void* p = a;
+
+    ConvertToTypedef b;
+    bool_type q = b;
+
+    ConvertToNonPointer c;
+    int n = c;
+}
+
+void convop_test_deref_still_warns(int* _Nullable p) {
+    (void)*p; // expected-warning {{dereference of nullable pointer}} expected-note {{add a null check}}
+}
+
+// ===----------------------------------------------------------------------===//
+// New Expressions
+// ===----------------------------------------------------------------------===//
+
+typedef __SIZE_TYPE__ size_t;
+
+namespace std {
+  struct nothrow_t {};
+  extern const nothrow_t nothrow;
+}
+
+void *operator new(size_t, const std::nothrow_t &) noexcept;
+
+struct Widget {
+    int value;
+};
+
+Widget * _Nullable getNullableWidget();
+
+#pragma clang assume_nonnull begin
+
+void newexpr_test_direct_deref() {
+    Widget *w = new Widget();
+    w->value = 42; // OK - throwing new never returns null
+}
+
+void newexpr_test_var_deref() {
+    Widget *w = new Widget();
+    int v = w->value; // OK - narrowed via new
+}
+
+void newexpr_test_nothrow_warns() {
+    Widget *w = new (std::nothrow) Widget();
+    w->value = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void newexpr_test_nullable_control() {
+    Widget *w = getNullableWidget();
+    w->value = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Exceptions
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+// === Narrowing before try block persists inside ===
+
+void exception_test_narrow_before_try(Node * _Nullable p) {
+    if (!p) return;
+    try {
+        (void)p->value; // OK -- narrowed before try
+    } catch (...) {
+    }
+}
+
+// === throw in null guard narrows ===
+
+void exception_test_throw_guard(Node * _Nullable p) {
+    if (!p) throw "null";
+    (void)p->value; // OK -- throw terminates null path
+}
+
+// === try body with null check ===
+
+void exception_test_null_check_in_try(Node * _Nullable p) {
+    try {
+        if (!p) throw "null";
+        (void)p->value; // OK -- narrowed by throw guard
+    } catch (...) {
+    }
+}
+
+// === catch block should not inherit narrowing from try ===
+
+void exception_test_after_try_catch(Node * _Nullable p) {
+    try {
+        if (p)
+            (void)p->value; // OK -- narrowed
+    } catch (...) {
+    }
+    // After try/catch, p's narrowing depends on merge of try and catch edges.
+    // Conservative: should warn.
+    (void)p->value; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// === Narrowing in both try and catch ===
+
+void exception_test_narrow_in_both(Node * _Nullable p) {
+    try {
+        if (!p) throw "null";
+        (void)p->value; // OK
+    } catch (...) {
+        if (!p) return;
+        (void)p->value; // OK -- narrowed in catch too
+    }
+    // Both try (throw guard) and catch (early return) narrowed p,
+    // so the merge point should preserve narrowing.
+    (void)p->value; // OK -- narrowed on all paths
+}
+
+// === Multiple catch blocks ===
+
+void exception_test_multiple_catch(Node * _Nullable p) {
+    if (!p) return;
+    try {
+        (void)p->value; // OK -- narrowed
+    } catch (int) {
+    } catch (...) {
+    }
+}
+
+// === throw expression in ternary ===
+
+void exception_test_throw_ternary(Node * _Nullable p) {
+    int v = p ? p->value : throw "null"; // OK -- throw terminates null path
+    (void)v;
+}
+
+// === Noexcept function -- no exception CFG edges ===
+
+void exception_test_noexcept_narrowing(Node * _Nullable p) noexcept {
+    if (!p) return;
+    (void)p->value; // OK -- narrowed, no exception edges to worry about
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// if constexpr
+// ===----------------------------------------------------------------------===//
+
+#pragma clang assume_nonnull begin
+
+// Known limitation: the warn_null_init_nonnull check fires during
+// declaration processing, before if-constexpr discarding. This means
+// _Nonnull p = nullptr in a discarded branch still warns. Suppressing
+// this would require tracking discarded-branch state at decl processing
+// time, which Clang doesn't expose. In practice, writing explicit
+// _Nonnull p = nullptr in a discarded branch is very rare.
+
+void constexpr_test_discarded() {
+    if constexpr (false) {
+        int * _Nonnull p = nullptr; // expected-warning{{null assigned to a variable of nonnull type}}
+    }
+}
+
+// Live branch correctly warns
+void constexpr_test_live() {
+    if constexpr (true) {
+        int * _Nonnull p = nullptr; // expected-warning{{null assigned to a variable of nonnull type}}
+    }
+}
+
+// Flow analysis narrowing works in live constexpr branches
+void constexpr_test_narrowing(int * _Nullable p) {
+    if constexpr (true) {
+        if (p) {
+            *p = 42; // OK -- narrowed
+        }
+    }
+}
+
+// Dereference in live branch warns correctly
+void constexpr_test_deref(int * _Nullable p) {
+    if constexpr (true) {
+        *p = 42; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    }
+}
+
+// Template with if constexpr -- both instantiations checked
+template<bool B>
+void constexpr_template_branch() {
+    if constexpr (B) {
+        int * _Nonnull p = nullptr; // expected-warning 2{{null assigned to a variable of nonnull type}}
+    }
+}
+
+void constexpr_instantiate_both() {
+    constexpr_template_branch<false>();
+    constexpr_template_branch<true>(); // expected-note{{in instantiation}}
+}
+
+#pragma clang assume_nonnull end
+
+// ===----------------------------------------------------------------------===//
+// Nullable-Default Template Return Types
+// ===----------------------------------------------------------------------===//
+
+// Tests that explicit _Nullable return types on template methods are caught.
+// Mimics the getComponent<T>() pattern from Clay ECS.
+
+struct Component {
+    int value;
+    void setValue(int v) { value = v; }
+};
+
+struct Entity {
+    template<typename T>
+    T* _Nullable getComponent() { return nullptr; }
+
+    Component* _Nullable getFirstComponent() { return nullptr; }
+};
+
+// Case 1: Non-template function -> local var -> arrow deref
+void nullable_template_test_non_template(Entity* e) {
+    Component* c = e->getFirstComponent(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    c->setValue(42);     // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// Case 2: Template function -> local var -> arrow deref
+void nullable_template_test_template(Entity* e) {
+    Component* c = e->getComponent<Component>(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    c->setValue(42);     // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// Case 3: Chained call -> local var -> data member access
+void nullable_template_test_data_member(Entity* e) {
+    Component* c = e->getComponent<Component>(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    c->value = 1;        // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// Case 4: With null check -- should NOT warn for c, still warns for e
+void nullable_template_test_with_check(Entity* e) {
+    Component* c = e->getComponent<Component>(); // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    if (c != nullptr) {
+        c->setValue(42); // OK -- c is narrowed
+    }
+}
diff --git a/clang/test/SemaCXX/flow-nullability-default-nonnull.cpp b/clang/test/SemaCXX/flow-nullability-default-nonnull.cpp
new file mode 100644
index 0000000000000..8ec744b9c2f24
--- /dev/null
+++ b/clang/test/SemaCXX/flow-nullability-default-nonnull.cpp
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nonnull -std=c++17 %s -verify
+
+struct Entity {
+    int x;
+};
+
+Entity* _Nullable getNullable();
+Entity* getUnannotated();
+
+#pragma clang assume_nonnull begin
+
+void test_unannotated_param_no_warn(Entity* p) {
+    p->x = 1; // OK - parameter gets _Nonnull from assume_nonnull pragma
+}
+
+void test_unannotated_star(Entity* p) {
+    (*p).x = 1; // OK - parameter gets _Nonnull from pragma
+}
+
+void test_explicit_nullable_warns(Entity* _Nullable p) {
+    p->x = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+void test_explicit_nullable_after_check(Entity* _Nullable p) {
+    if (p) {
+        p->x = 1; // OK - narrowed
+    }
+}
+
+void test_return_nullable_warns() {
+    Entity* e = getNullable();
+    e->x = 1; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}
+
+// With -fnullability-default=nonnull, unannotated pointers are treated as
+// nonnull. getUnannotated() has no _Nullable, so it's safe.
+void test_return_unannotated_ok() {
+    Entity* e = getUnannotated();
+    e->x = 1; // OK - unannotated return treated as nonnull per default
+}
+
+void test_local_nonnull_ok() {
+    Entity stack;
+    Entity* _Nonnull p = &stack;
+    p->x = 1; // OK - explicit _Nonnull
+}
+
+#pragma clang assume_nonnull end
diff --git a/clang/test/SemaCXX/flow-nullability-warning-groups.cpp b/clang/test/SemaCXX/flow-nullability-warning-groups.cpp
new file mode 100644
index 0000000000000..7d0978977ea0f
--- /dev/null
+++ b/clang/test/SemaCXX/flow-nullability-warning-groups.cpp
@@ -0,0 +1,20 @@
+// Tests for warning group suppression and control.
+//
+// -Wno-flow-nullable-dereference suppresses the warning:
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-flow-nullable-dereference -verify=suppressed %s
+//
+// Parent group -Wno-flow-nullability also suppresses:
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-flow-nullability -verify=suppressed %s
+//
+// -Werror=flow-nullable-dereference promotes to error:
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Werror=flow-nullable-dereference -verify=werror %s
+//
+// cc1 rejects invalid -fnullability-default value:
+// RUN: not %clang_cc1 -fnullability-default=invalid %s 2>&1 | FileCheck %s
+// CHECK: error: invalid value 'invalid' in '-fnullability-default=invalid'
+
+// suppressed-no-diagnostics
+
+void test(int * _Nullable p) {
+    *p = 42; // werror-error {{dereference of nullable pointer}} werror-note {{add a null check}}
+}
diff --git a/clang/test/SemaObjC/flow-nullability-objc.m b/clang/test/SemaObjC/flow-nullability-objc.m
new file mode 100644
index 0000000000000..0a907fb0a6cde
--- /dev/null
+++ b/clang/test/SemaObjC/flow-nullability-objc.m
@@ -0,0 +1,26 @@
+// Smoke test: flow-sensitive nullability in Objective-C.
+// Verifies that the analysis works with ObjC pointer types and
+// _Nullable/_Nonnull annotations that originated in the ObjC ecosystem.
+//
+// RUN: %clang_cc1 -fsyntax-only -fflow-sensitive-nullability -fnullability-default=nullable -Wno-unused-value %s -verify
+
+void deref_nullable_raw(int * _Nullable p) {
+    *p; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+    if (p) {
+        *p; // OK — narrowed
+    }
+}
+
+void nonnull_param(int * _Nonnull safe) {
+    *safe; // OK — _Nonnull
+}
+
+void narrowing(int * _Nullable a, int * _Nullable b) {
+    if (a != 0) {
+        *a; // OK — narrowed
+    }
+    if (b) {
+        *b; // OK — narrowed
+    }
+    *a; // expected-warning{{dereference of nullable pointer}} expected-note{{add a null check}}
+}



More information about the cfe-commits mailing list