[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