Rename /nullability_verification to /nullability in preparation for adding/relocating inference functionality here.
PiperOrigin-RevId: 528495113
diff --git a/nullability/BUILD b/nullability/BUILD
new file mode 100644
index 0000000..7272c47
--- /dev/null
+++ b/nullability/BUILD
@@ -0,0 +1,88 @@
+# Verification and Inference for null safety
+
+package(default_applicable_licenses = ["//:license"])
+
+cc_library(
+ name = "pointer_nullability_lattice",
+ hdrs = ["pointer_nullability_lattice.h"],
+ deps = [
+ "@absl//absl/container:flat_hash_map",
+ "@absl//absl/log:check",
+ "@llvm-project//clang:analysis",
+ ],
+)
+
+cc_library(
+ name = "pointer_nullability_matchers",
+ srcs = ["pointer_nullability_matchers.cc"],
+ hdrs = ["pointer_nullability_matchers.h"],
+ deps = [
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:ast_matchers",
+ ],
+)
+
+cc_library(
+ name = "pointer_nullability_analysis",
+ srcs = ["pointer_nullability_analysis.cc"],
+ hdrs = ["pointer_nullability_analysis.h"],
+ visibility = ["//nullability/test:__pkg__"],
+ deps = [
+ ":pointer_nullability",
+ ":pointer_nullability_lattice",
+ ":pointer_nullability_matchers",
+ "@absl//absl/log:check",
+ "@absl//absl/strings",
+ "@llvm-project//clang:analysis",
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:ast_matchers",
+ "@llvm-project//clang:basic",
+ ],
+)
+
+cc_library(
+ name = "pointer_nullability_diagnosis",
+ srcs = ["pointer_nullability_diagnosis.cc"],
+ hdrs = ["pointer_nullability_diagnosis.h"],
+ visibility = ["//nullability/test:__pkg__"],
+ deps = [
+ ":pointer_nullability",
+ ":pointer_nullability_lattice",
+ ":pointer_nullability_matchers",
+ "@llvm-project//clang:analysis",
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:ast_matchers",
+ "@llvm-project//clang:basic",
+ "@llvm-project//llvm:Support",
+ ],
+)
+
+cc_library(
+ name = "pointer_nullability",
+ srcs = [
+ "pointer_nullability.cc",
+ "type_nullability.cc",
+ ],
+ hdrs = ["pointer_nullability.h"],
+ deps = [
+ ":pointer_nullability_lattice",
+ "@absl//absl/log:check",
+ "@llvm-project//clang:analysis",
+ "@llvm-project//clang:ast",
+ "@llvm-project//clang:basic",
+ "@llvm-project//llvm:Support",
+ ],
+)
+
+cc_test(
+ name = "pointer_nullability_test",
+ srcs = ["pointer_nullability_test.cc"],
+ deps = [
+ ":pointer_nullability",
+ "@llvm-project//clang:testing",
+ "@llvm-project//llvm:Support",
+ "@llvm-project//third-party/unittest:gmock",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
diff --git a/nullability/formal_methods/gradual_nullability_code_synthesis.smt2 b/nullability/formal_methods/gradual_nullability_code_synthesis.smt2
new file mode 100644
index 0000000..0c2544b
--- /dev/null
+++ b/nullability/formal_methods/gradual_nullability_code_synthesis.smt2
@@ -0,0 +1,1578 @@
+;; Part of the Crubit project, under the Apache License v2.0 with LLVM
+;; Exceptions. See /LICENSE for license information.
+;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+;; Run: cvc5 --lang smt --incremental --fmf-bound gradual_nullability_code_synthesis.smt2
+
+(set-option :produce-models true)
+(set-option :produce-assertions true)
+(set-option :produce-assignments true)
+(set-option :produce-unsat-cores true)
+(set-logic HO_ALL)
+
+(declare-datatype FlowConditions
+ ((make-FlowConditions
+ (_get-fc-1 Bool)
+ (_get-fc-2 Bool)
+ (_get-fc-3 Bool)
+ (_get-fc-4 Bool)
+ (_get-fc-5 Bool)
+ (_get-fc-6 Bool))))
+
+;; Redefine selectors as regular functions, because predefined ones
+;; can't be converted to values of function type for some reason.
+(define-fun get-fc-1 ((fcs FlowConditions)) Bool
+ (_get-fc-1 fcs))
+
+(define-fun get-fc-2 ((fcs FlowConditions)) Bool
+ (_get-fc-2 fcs))
+
+(define-fun get-fc-3 ((fcs FlowConditions)) Bool
+ (_get-fc-3 fcs))
+
+(define-fun get-fc-4 ((fcs FlowConditions)) Bool
+ (_get-fc-4 fcs))
+
+(define-fun get-fc-5 ((fcs FlowConditions)) Bool
+ (_get-fc-5 fcs))
+
+(define-fun get-fc-6 ((fcs FlowConditions)) Bool
+ (_get-fc-6 fcs))
+
+(define-fun join-fc ((c Bool) (fc-then Bool) (fc-else Bool)) Bool
+ (or (and c fc-then)
+ (and (not c) fc-else)))
+
+(assert
+ (forall ((c Bool) (fc-then Bool) (fc-else Bool))
+ (= (join-fc c fc-then fc-else)
+ (join-fc (not c) fc-else fc-then))))
+
+(declare-datatype PointerValue
+ ((make-PointerValue
+ (get-x0 Bool)
+ (get-x1 Bool)
+)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Solution: unconstrained declarations.
+
+;; Flow condition conjuncts that constrain a pointer according to its
+;; annotation.
+(declare-fun fc-conj--ptr-is-null (PointerValue) Bool)
+(declare-fun fc-conj--ptr-is-unknown (PointerValue) Bool)
+(declare-fun fc-conj--ptr-is-nonnull (PointerValue) Bool)
+(declare-fun fc-conj--ptr-is-nullable (PointerValue) Bool)
+
+;; Flow condition conjunct that constrains the result of comparing two
+;; pointers for equality.
+;;
+;; Args: lhs, rhs, are-equal.
+(declare-fun fc-conj--ptrs-were-compared (PointerValue PointerValue Bool) Bool)
+
+;; Flow condition conjunct that constrains the new joined pointer,
+;; by combining the pointers coming from the "then" and the "else" branches
+;; of an "if" statement.
+;;
+;; Args: condition, ptr-then, ptr-else, ptr-joined.
+(declare-fun fc-conj--join-ptr (Bool PointerValue PointerValue PointerValue) Bool)
+
+;; Safety criteria, tells us whether it is safe to dereference a given
+;; pointer at a given program point. The program point is identified
+;; by its flow condition.
+;;
+;; Args: flow-condition, pointer.
+(declare-fun is-unsafe-to-deref (Bool PointerValue) Bool)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Solution S1: two bits, no quantifiers
+;; - Bit 1 represents if the pointer's nullability is known
+;; - Bit 2 represents if the pointer is not null
+
+(define-fun enable-solution-s1 () Bool false)
+
+(assert (=> enable-solution-s1
+ (forall ((p PointerValue))
+ (= (fc-conj--ptr-is-null p)
+ (= p (make-PointerValue true false))))))
+
+(assert (=> enable-solution-s1
+ (forall ((p PointerValue))
+ (= (fc-conj--ptr-is-unknown p)
+ (or
+ (= p (make-PointerValue false false))
+ (= p (make-PointerValue false true)))))))
+
+(assert (=> enable-solution-s1
+ (forall ((p PointerValue))
+ (= (fc-conj--ptr-is-nonnull p)
+ (= p (make-PointerValue true true))))))
+
+(assert (=> enable-solution-s1
+ (forall ((p PointerValue))
+ (= (fc-conj--ptr-is-nullable p)
+ (or
+ (= p (make-PointerValue true false))
+ (= p (make-PointerValue true true)))))))
+
+(assert (=> enable-solution-s1
+ (forall ((lhs PointerValue) (rhs PointerValue) (eq Bool))
+ (= (fc-conj--ptrs-were-compared lhs rhs eq)
+ (and
+ ;; nullptr == nullptr
+ (=> (and (fc-conj--ptr-is-null lhs) (fc-conj--ptr-is-null rhs))
+ eq)
+
+ ;; nullptr != nonnull
+ (=> (and (fc-conj--ptr-is-null lhs) (fc-conj--ptr-is-nonnull rhs))
+ (not eq))
+
+ ;; nonnull != nullptr
+ (=> (and (fc-conj--ptr-is-nonnull lhs) (fc-conj--ptr-is-null rhs))
+ (not eq)))))))
+
+(assert (=> enable-solution-s1
+ (forall ((c Bool)
+ (ptr-then PointerValue)
+ (ptr-else PointerValue)
+ (ptr-joined PointerValue))
+ (= (fc-conj--join-ptr c ptr-then ptr-else ptr-joined)
+ (or (and c (= ptr-joined ptr-then))
+ (and (not c) (= ptr-joined ptr-else)))))))
+
+(assert (=> enable-solution-s1
+ (forall ((fc Bool) (p PointerValue))
+ (= (is-unsafe-to-deref fc p)
+ (and fc (fc-conj--ptr-is-null p))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Solution S2: ??? bits, with quantifiers.
+;; TODO: Implement.
+
+(define-fun enable-solution-s2 () Bool false)
+
+;(assert (=> enable-solution-s2
+; (forall ((p PointerValue))
+; (= (fc-conj--ptr-is-null p)
+; TODO))))
+
+;(assert (=> enable-solution-s2
+; (forall ((p PointerValue))
+; (= (fc-conj--ptr-is-unknown p)
+; TODO))))
+
+;(assert (=> enable-solution-s2
+; (forall ((p PointerValue))
+; (= (fc-conj--ptr-is-nonnull p)
+; TODO))))
+
+;(assert (=> enable-solution-s2
+; (forall ((p PointerValue))
+; (= (fc-conj--ptr-is-nullable p)
+; TODO))))
+
+;(assert (=> enable-solution-s2
+; (forall ((lhs PointerValue) (rhs PointerValue) (eq Bool))
+; (= (fc-conj--ptrs-were-compared lhs rhs eq)
+; TODO))))
+
+;(assert (=> enable-solution-s2
+; (forall ((c Bool)
+; (ptr-then PointerValue)
+; (ptr-else PointerValue)
+; (ptr-joined PointerValue))
+; (= (fc-conj--join-ptr c ptr-then ptr-else ptr-joined)
+; TODO))))
+
+;(assert (=> enable-solution-s2
+; (forall ((fc Bool) (p PointerValue))
+; (= (is-unsafe-to-deref fc p)
+; TODO))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Check the solution.
+
+(echo "Checking satisfiability of the selected solution.")
+(check-sat)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Properties that the solution must satisfy.
+
+(define-fun is-valid-pointer ((p PointerValue)) Bool
+ (or (fc-conj--ptr-is-null p)
+ (fc-conj--ptr-is-unknown p)
+ (fc-conj--ptr-is-nonnull p)
+ (fc-conj--ptr-is-nullable p)))
+
+;; Reflexivity for fc-conj--ptrs-were-compared.
+(assert
+ (forall ((p PointerValue))
+ (=> (is-valid-pointer p)
+ (fc-conj--ptrs-were-compared p p true))))
+
+;; Symmetry for fc-conj--ptrs-were-compared.
+(assert
+ (forall ((p1 PointerValue) (p2 PointerValue) (are-equal Bool))
+ (=> (and (is-valid-pointer p1) (is-valid-pointer p2))
+ (= (fc-conj--ptrs-were-compared p1 p2 are-equal)
+ (fc-conj--ptrs-were-compared p2 p1 are-equal)))))
+
+;; Transitivity for fc-conj--ptrs-were-compared.
+(assert
+ (=>
+ ;; Transitivity does not hold in S1.
+ ;; Consider (nonnull == unknown), (unknown == null).
+ ;; However (nonnull == null) never holds.
+ ;; ```
+ ;; void target(int * _NonNull x, int *y) {
+ ;; if (x == y && y == nullptr) {
+ ;; // dead code that we can't detect
+ ;; }
+ ;; }
+ ;; ```
+ ;; TODO: How big of a problem is it?
+ (not enable-solution-s1)
+ (forall ((p1 PointerValue) (p2 PointerValue) (p3 PointerValue))
+ (=> (and (is-valid-pointer p1)
+ (is-valid-pointer p2)
+ (is-valid-pointer p3)
+ (fc-conj--ptrs-were-compared p1 p2 true)
+ (fc-conj--ptrs-were-compared p2 p3 true))
+ (fc-conj--ptrs-were-compared p1 p3 true)))))
+
+;; Symmetry for fc-conj--join-ptr with regards to pointers.
+(assert
+ (forall ((b Bool) (p1 PointerValue) (p2 PointerValue) (p3 PointerValue))
+ (= (fc-conj--join-ptr b p1 p2 p3)
+ (fc-conj--join-ptr (not b) p2 p1 p3))))
+
+(echo "Checking whether the selected solution satisfies the properties.")
+(check-sat)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example DerefAllUnchecked
+;;
+;; ```
+;; void target(
+;; int *ptr_unknown,
+;; int * _NonNull ptr_nonnull,
+;; int * _Nullable ptr_nullable) {
+;; int *ptr_nullptr = nullptr;
+;; // (1)
+;; *ptr_unknown; // safe
+;; *ptr_nonnull; // safe
+;; *ptr_nullable; // unsafe
+;; *ptr_nullptr; // unsafe
+;; }
+;; ```
+
+(declare-datatype State-DerefAllUnchecked
+ ((make-State-DerefAllUnchecked
+ (_get-ptr_unknown PointerValue)
+ (_get-ptr_nonnull PointerValue)
+ (_get-ptr_nullable PointerValue)
+ (_get-ptr_nullptr PointerValue))))
+
+(define-fun get-ptr_unknown-DerefAllUnchecked
+ ((state State-DerefAllUnchecked)) PointerValue
+ (_get-ptr_unknown state))
+
+(define-fun get-ptr_nonnull-DerefAllUnchecked
+ ((state State-DerefAllUnchecked)) PointerValue
+ (_get-ptr_nonnull state))
+
+(define-fun get-ptr_nullable-DerefAllUnchecked
+ ((state State-DerefAllUnchecked)) PointerValue
+ (_get-ptr_nullable state))
+
+(define-fun get-ptr_nullptr-DerefAllUnchecked
+ ((state State-DerefAllUnchecked)) PointerValue
+ (_get-ptr_nullptr state))
+
+(define-fun run-DerefAllUnchecked
+ ((state State-DerefAllUnchecked))
+ FlowConditions
+ (match state
+ (((make-State-DerefAllUnchecked
+ ptr_unknown ptr_nonnull ptr_nullable ptr_nullptr)
+ (let ((fc-1 (and (fc-conj--ptr-is-unknown ptr_unknown)
+ (fc-conj--ptr-is-nonnull ptr_nonnull)
+ (fc-conj--ptr-is-nullable ptr_nullable)
+ (fc-conj--ptr-is-null ptr_nullptr))))
+ (make-FlowConditions fc-1 false false false false false))))))
+
+(define-fun is-reachable-DerefAllUnchecked
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-DerefAllUnchecked))
+ (let ((fcs (run-DerefAllUnchecked state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-DerefAllUnchecked
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-DerefAllUnchecked PointerValue)))
+ Bool
+ (exists ((state State-DerefAllUnchecked))
+ (let ((fcs (run-DerefAllUnchecked state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-DerefAllUnchecked get-fc-1)
+ :named DerefAllUnchecked-reachable-1))
+
+(assert
+ (! (not (is-unsafe-deref-DerefAllUnchecked get-fc-1 get-ptr_unknown-DerefAllUnchecked))
+ :named DerefAllUnchecked-deref-unknown))
+
+(assert
+ (! (not (is-unsafe-deref-DerefAllUnchecked get-fc-1 get-ptr_nonnull-DerefAllUnchecked))
+ :named DerefAllUnchecked-deref-nonnull))
+
+(assert
+ (! (is-unsafe-deref-DerefAllUnchecked get-fc-1 get-ptr_nullable-DerefAllUnchecked)
+ :named DerefAllUnchecked-deref-nullable))
+
+(assert
+ (! (is-unsafe-deref-DerefAllUnchecked get-fc-1 get-ptr_nullptr-DerefAllUnchecked)
+ :named DerefAllUnchecked-deref-nullptr))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example MixedNullableAndNonNull
+;;
+;; ```
+;; void target(int * _Nullable x, bool b) {
+;; // (1)
+;; int i;
+;; *x; // unsafe
+;; if (b) {
+;; // (2)
+;; *x; // unsafe
+;; x = &i;
+;; // (3)
+;; *x; // safe
+;; }
+;; // (4)
+;; *x; // unsafe
+;; if (b) {
+;; // (5)
+;; *x; // safe
+;; } else {
+;; // (6)
+;; *x; // unsafe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-MixedNullableAndNonNull
+ ((make-State-MixedNullableAndNonNull
+ (_get-x-1 PointerValue)
+ (_get-x-3 PointerValue)
+ (_get-x-4 PointerValue)
+ (b Bool))))
+
+(define-fun get-x-1-MixedNullableAndNonNull
+ ((state State-MixedNullableAndNonNull)) PointerValue
+ (_get-x-1 state))
+
+(define-fun get-x-3-MixedNullableAndNonNull
+ ((state State-MixedNullableAndNonNull)) PointerValue
+ (_get-x-3 state))
+
+(define-fun get-x-4-MixedNullableAndNonNull
+ ((state State-MixedNullableAndNonNull)) PointerValue
+ (_get-x-4 state))
+
+(define-fun run-MixedNullableAndNonNull
+ ((state State-MixedNullableAndNonNull))
+ FlowConditions
+ (match state
+ (((make-State-MixedNullableAndNonNull x-1 x-3 x-4 b)
+ (let ((fc-1 (fc-conj--ptr-is-nullable x-1)))
+ (let ((fc-2 (and fc-1 b)))
+ (let ((fc-3 (and fc-2 (fc-conj--ptr-is-nonnull x-3))))
+ (let ((fc-4 (and (join-fc b fc-3 fc-1)
+ (fc-conj--join-ptr b x-3 x-1 x-4))))
+ (let ((fc-5 (and fc-4 b)))
+ (let ((fc-6 (and fc-4 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 fc-5 fc-6)))))))))))
+
+(define-fun is-reachable-MixedNullableAndNonNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-MixedNullableAndNonNull))
+ (let ((fcs (run-MixedNullableAndNonNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-MixedNullableAndNonNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-MixedNullableAndNonNull PointerValue)))
+ Bool
+ (exists ((state State-MixedNullableAndNonNull))
+ (let ((fcs (run-MixedNullableAndNonNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-MixedNullableAndNonNull get-fc-1)
+ :named MixedNullableAndNonNull-reachable-1))
+
+(assert
+ (! (is-reachable-MixedNullableAndNonNull get-fc-2)
+ :named MixedNullableAndNonNull-reachable-2))
+
+(assert
+ (! (is-reachable-MixedNullableAndNonNull get-fc-3)
+ :named MixedNullableAndNonNull-reachable-3))
+
+(assert
+ (! (is-reachable-MixedNullableAndNonNull get-fc-4)
+ :named MixedNullableAndNonNull-reachable-4))
+
+(assert
+ (! (is-reachable-MixedNullableAndNonNull get-fc-5)
+ :named MixedNullableAndNonNull-reachable-5))
+
+(assert
+ (! (is-reachable-MixedNullableAndNonNull get-fc-6)
+ :named MixedNullableAndNonNull-reachable-6))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndNonNull get-fc-1 get-x-1-MixedNullableAndNonNull)
+ :named MixedNullableAndNonNull-deref-1))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndNonNull get-fc-2 get-x-1-MixedNullableAndNonNull)
+ :named MixedNullableAndNonNull-deref-2))
+
+(assert
+ (! (not (is-unsafe-deref-MixedNullableAndNonNull get-fc-3 get-x-3-MixedNullableAndNonNull))
+ :named MixedNullableAndNonNull-deref-3))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndNonNull get-fc-4 get-x-4-MixedNullableAndNonNull)
+ :named MixedNullableAndNonNull-deref-4))
+
+(assert
+ (! (not (is-unsafe-deref-MixedNullableAndNonNull get-fc-5 get-x-4-MixedNullableAndNonNull))
+ :named MixedNullableAndNonNull-deref-5))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndNonNull get-fc-6 get-x-4-MixedNullableAndNonNull)
+ :named MixedNullableAndNonNull-deref-6))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example MixedNullableAndUnknown
+;;
+;; ```
+;; void target(int * _Nullable x, bool b) {
+;; // (1)
+;; *x; // unsafe
+;; if (b) {
+;; // (2)
+;; *x; // unsafe
+;; x = MakeUnknown();
+;; // (3)
+;; *x; // safe
+;; }
+;; // (4)
+;; *x; // unsafe
+;; if (b) {
+;; // (5)
+;; *x; // safe
+;; } else {
+;; // (6)
+;; *x; // unsafe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-MixedNullableAndUnknown
+ ((make-State-MixedNullableAndUnknown
+ (_get-x-1 PointerValue)
+ (_get-x-3 PointerValue)
+ (_get-x-4 PointerValue)
+ (b Bool))))
+
+(define-fun get-x-1-MixedNullableAndUnknown
+ ((state State-MixedNullableAndUnknown)) PointerValue
+ (_get-x-1 state))
+
+(define-fun get-x-3-MixedNullableAndUnknown
+ ((state State-MixedNullableAndUnknown)) PointerValue
+ (_get-x-3 state))
+
+(define-fun get-x-4-MixedNullableAndUnknown
+ ((state State-MixedNullableAndUnknown)) PointerValue
+ (_get-x-4 state))
+
+(define-fun run-MixedNullableAndUnknown
+ ((state State-MixedNullableAndUnknown))
+ FlowConditions
+ (match state
+ (((make-State-MixedNullableAndUnknown x-1 x-3 x-4 b)
+ (let ((fc-1 (fc-conj--ptr-is-nullable x-1)))
+ (let ((fc-2 (and fc-1 b)))
+ (let ((fc-3 (and fc-2 (fc-conj--ptr-is-unknown x-3))))
+ (let ((fc-4 (and (join-fc b fc-3 fc-1)
+ (fc-conj--join-ptr b x-3 x-1 x-4))))
+ (let ((fc-5 (and fc-4 b)))
+ (let ((fc-6 (and fc-4 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 fc-5 fc-6)))))))))))
+
+(define-fun is-reachable-MixedNullableAndUnknown
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-MixedNullableAndUnknown))
+ (let ((fcs (run-MixedNullableAndUnknown state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-MixedNullableAndUnknown
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-MixedNullableAndUnknown PointerValue)))
+ Bool
+ (exists ((state State-MixedNullableAndUnknown))
+ (let ((fcs (run-MixedNullableAndUnknown state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-MixedNullableAndUnknown get-fc-1)
+ :named MixedNullableAndUnknown-reachable-1))
+
+(assert
+ (! (is-reachable-MixedNullableAndUnknown get-fc-2)
+ :named MixedNullableAndUnknown-reachable-2))
+
+(assert
+ (! (is-reachable-MixedNullableAndUnknown get-fc-3)
+ :named MixedNullableAndUnknown-reachable-3))
+
+(assert
+ (! (is-reachable-MixedNullableAndUnknown get-fc-4)
+ :named MixedNullableAndUnknown-reachable-4))
+
+(assert
+ (! (is-reachable-MixedNullableAndUnknown get-fc-5)
+ :named MixedNullableAndUnknown-reachable-5))
+
+(assert
+ (! (is-reachable-MixedNullableAndUnknown get-fc-6)
+ :named MixedNullableAndUnknown-reachable-6))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndUnknown get-fc-1 get-x-1-MixedNullableAndUnknown)
+ :named MixedNullableAndUnknown-deref-1))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndUnknown get-fc-2 get-x-1-MixedNullableAndUnknown)
+ :named MixedNullableAndUnknown-deref-2))
+
+(assert
+ (! (not (is-unsafe-deref-MixedNullableAndUnknown get-fc-3 get-x-3-MixedNullableAndUnknown))
+ :named MixedNullableAndUnknown-deref-3))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndUnknown get-fc-4 get-x-4-MixedNullableAndUnknown)
+ :named MixedNullableAndUnknown-deref-4))
+
+(assert
+ (! (not (is-unsafe-deref-MixedNullableAndUnknown get-fc-5 get-x-4-MixedNullableAndUnknown))
+ :named MixedNullableAndUnknown-deref-5))
+
+(assert
+ (! (is-unsafe-deref-MixedNullableAndUnknown get-fc-6 get-x-4-MixedNullableAndUnknown)
+ :named MixedNullableAndUnknown-deref-6))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example MixedUnknownAndNull
+;;
+;; ```
+;; void target(int *x, bool b) {
+;; // (1)
+;; *x; // safe
+;; if (b) {
+;; // (2)
+;; *x; // safe
+;; x = nullptr;
+;; // (3)
+;; *x; // unsafe
+;; }
+;; // (4)
+;; *x; // unsafe
+;; if (b) {
+;; // (5)
+;; *x; // unsafe
+;; } else {
+;; // (6)
+;; *x; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-MixedUnknownAndNull
+ ((make-State-MixedUnknownAndNull
+ (_get-x-1 PointerValue)
+ (_get-x-3 PointerValue)
+ (_get-x-4 PointerValue)
+ (b Bool))))
+
+(define-fun get-x-1-MixedUnknownAndNull
+ ((state State-MixedUnknownAndNull)) PointerValue
+ (_get-x-1 state))
+
+(define-fun get-x-3-MixedUnknownAndNull
+ ((state State-MixedUnknownAndNull)) PointerValue
+ (_get-x-3 state))
+
+(define-fun get-x-4-MixedUnknownAndNull
+ ((state State-MixedUnknownAndNull)) PointerValue
+ (_get-x-4 state))
+
+(define-fun run-MixedUnknownAndNull
+ ((state State-MixedUnknownAndNull))
+ FlowConditions
+ (match state
+ (((make-State-MixedUnknownAndNull x-1 x-3 x-4 b)
+ (let ((fc-1 (fc-conj--ptr-is-unknown x-1)))
+ (let ((fc-2 (and fc-1 b)))
+ (let ((fc-3 (and fc-2 (fc-conj--ptr-is-null x-3))))
+ (let ((fc-4 (and (join-fc b fc-3 fc-1)
+ (fc-conj--join-ptr b x-3 x-1 x-4))))
+ (let ((fc-5 (and fc-4 b)))
+ (let ((fc-6 (and fc-4 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 fc-5 fc-6)))))))))))
+
+(define-fun is-reachable-MixedUnknownAndNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-MixedUnknownAndNull))
+ (let ((fcs (run-MixedUnknownAndNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-MixedUnknownAndNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-MixedUnknownAndNull PointerValue)))
+ Bool
+ (exists ((state State-MixedUnknownAndNull))
+ (let ((fcs (run-MixedUnknownAndNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNull get-fc-1)
+ :named MixedUnknownAndNull-reachable-1))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNull get-fc-2)
+ :named MixedUnknownAndNull-reachable-2))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNull get-fc-3)
+ :named MixedUnknownAndNull-reachable-3))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNull get-fc-4)
+ :named MixedUnknownAndNull-reachable-4))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNull get-fc-5)
+ :named MixedUnknownAndNull-reachable-5))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNull get-fc-6)
+ :named MixedUnknownAndNull-reachable-6))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNull get-fc-1 get-x-1-MixedUnknownAndNull))
+ :named MixedUnknownAndNull-deref-1))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNull get-fc-2 get-x-1-MixedUnknownAndNull))
+ :named MixedUnknownAndNull-deref-2))
+
+(assert
+ (! (is-unsafe-deref-MixedUnknownAndNull get-fc-3 get-x-3-MixedUnknownAndNull)
+ :named MixedUnknownAndNull-deref-3))
+
+(assert
+ (! (is-unsafe-deref-MixedUnknownAndNull get-fc-4 get-x-4-MixedUnknownAndNull)
+ :named MixedUnknownAndNull-deref-4))
+
+(assert
+ (! (is-unsafe-deref-MixedUnknownAndNull get-fc-5 get-x-4-MixedUnknownAndNull)
+ :named MixedUnknownAndNull-deref-5))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNull get-fc-6 get-x-4-MixedUnknownAndNull))
+ :named MixedUnknownAndNull-deref-6))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example MixedUnknownAndNonNull
+;;
+;; ```
+;; void target(int *x, bool b) {
+;; // (1)
+;; *x; // safe
+;; if (b) {
+;; // (2)
+;; *x; // safe
+;; x = MakeNonNull();
+;; // (3)
+;; *x; // safe
+;; }
+;; // (4)
+;; *x; // safe
+;; if (b) {
+;; // (5)
+;; *x; // safe
+;; } else {
+;; // (6)
+;; *x; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-MixedUnknownAndNonNull
+ ((make-State-MixedUnknownAndNonNull
+ (_get-x-1 PointerValue)
+ (_get-x-3 PointerValue)
+ (_get-x-4 PointerValue)
+ (b Bool))))
+
+(define-fun get-x-1-MixedUnknownAndNonNull
+ ((state State-MixedUnknownAndNonNull)) PointerValue
+ (_get-x-1 state))
+
+(define-fun get-x-3-MixedUnknownAndNonNull
+ ((state State-MixedUnknownAndNonNull)) PointerValue
+ (_get-x-3 state))
+
+(define-fun get-x-4-MixedUnknownAndNonNull
+ ((state State-MixedUnknownAndNonNull)) PointerValue
+ (_get-x-4 state))
+
+(define-fun run-MixedUnknownAndNonNull
+ ((state State-MixedUnknownAndNonNull))
+ FlowConditions
+ (match state
+ (((make-State-MixedUnknownAndNonNull x-1 x-3 x-4 b)
+ (let ((fc-1 (fc-conj--ptr-is-unknown x-1)))
+ (let ((fc-2 (and fc-1 b)))
+ (let ((fc-3 (and fc-2 (fc-conj--ptr-is-nonnull x-3))))
+ (let ((fc-4 (and (join-fc b fc-3 fc-1)
+ (fc-conj--join-ptr b x-3 x-1 x-4))))
+ (let ((fc-5 (and fc-4 b)))
+ (let ((fc-6 (and fc-4 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 fc-5 fc-6)))))))))))
+
+(define-fun is-reachable-MixedUnknownAndNonNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-MixedUnknownAndNonNull))
+ (let ((fcs (run-MixedUnknownAndNonNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-MixedUnknownAndNonNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-MixedUnknownAndNonNull PointerValue)))
+ Bool
+ (exists ((state State-MixedUnknownAndNonNull))
+ (let ((fcs (run-MixedUnknownAndNonNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNonNull get-fc-1)
+ :named MixedUnknownAndNonNull-reachable-1))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNonNull get-fc-2)
+ :named MixedUnknownAndNonNull-reachable-2))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNonNull get-fc-3)
+ :named MixedUnknownAndNonNull-reachable-3))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNonNull get-fc-4)
+ :named MixedUnknownAndNonNull-reachable-4))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNonNull get-fc-5)
+ :named MixedUnknownAndNonNull-reachable-5))
+
+(assert
+ (! (is-reachable-MixedUnknownAndNonNull get-fc-6)
+ :named MixedUnknownAndNonNull-reachable-6))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNonNull get-fc-1 get-x-1-MixedUnknownAndNonNull))
+ :named MixedUnknownAndNonNull-deref-1))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNonNull get-fc-2 get-x-1-MixedUnknownAndNonNull))
+ :named MixedUnknownAndNonNull-deref-2))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNonNull get-fc-3 get-x-3-MixedUnknownAndNonNull))
+ :named MixedUnknownAndNonNull-deref-3))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNonNull get-fc-4 get-x-4-MixedUnknownAndNonNull))
+ :named MixedUnknownAndNonNull-deref-4))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNonNull get-fc-5 get-x-4-MixedUnknownAndNonNull))
+ :named MixedUnknownAndNonNull-deref-5))
+
+(assert
+ (! (not (is-unsafe-deref-MixedUnknownAndNonNull get-fc-6 get-x-4-MixedUnknownAndNonNull))
+ :named MixedUnknownAndNonNull-deref-6))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareNullAndNull
+;;
+;; ```
+;; void target() {
+;; int *x = nullptr;
+;; int *y = nullptr;
+;; // (1)
+;; bool b = x == y;
+;; // (2)
+;; if (b) {
+;; // (3)
+;; *x; // unsafe
+;; *y; // unsafe
+;; } else {
+;; // (4) - unreachable
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareNullAndNull
+ ((make-State-CompareNullAndNull
+ (_get-x PointerValue)
+ (_get-y PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareNullAndNull
+ ((state State-CompareNullAndNull)) PointerValue
+ (_get-x state))
+
+(define-fun get-y-CompareNullAndNull
+ ((state State-CompareNullAndNull)) PointerValue
+ (_get-y state))
+
+(define-fun run-CompareNullAndNull
+ ((state State-CompareNullAndNull))
+ FlowConditions
+ (match state
+ (((make-State-CompareNullAndNull x y b)
+ (let ((fc-1 (and (fc-conj--ptr-is-null x)
+ (fc-conj--ptr-is-null y))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x y b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareNullAndNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareNullAndNull))
+ (let ((fcs (run-CompareNullAndNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareNullAndNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareNullAndNull PointerValue)))
+ Bool
+ (exists ((state State-CompareNullAndNull))
+ (let ((fcs (run-CompareNullAndNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-CompareNullAndNull get-fc-1)
+ :named CompareNullAndNull-reachable-1))
+
+(assert
+ (! (is-reachable-CompareNullAndNull get-fc-2)
+ :named CompareNullAndNull-reachable-2))
+
+(assert
+ (! (is-reachable-CompareNullAndNull get-fc-3)
+ :named CompareNullAndNull-reachable-3))
+
+(assert
+ (! (not (is-reachable-CompareNullAndNull get-fc-4))
+ :named CompareNullAndNull-reachable-4))
+
+(assert
+ (! (is-unsafe-deref-CompareNullAndNull get-fc-3 get-x-CompareNullAndNull)
+ :named CompareNullAndNull-deref-3-x))
+
+(assert
+ (! (is-unsafe-deref-CompareNullAndNull get-fc-3 get-y-CompareNullAndNull)
+ :named CompareNullAndNull-deref-3-y))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareUnknownAndUnknown
+;;
+;; ```
+;; void target(int *x, int *y) {
+;; // (1)
+;; bool b = x == y;
+;; // (2)
+;; if (b) {
+;; // (3)
+;; *x; // safe
+;; *y; // safe
+;; } else {
+;; // (4)
+;; *x; // safe
+;; *y; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareUnknownAndUnknown
+ ((make-State-CompareUnknownAndUnknown
+ (_get-x PointerValue)
+ (_get-y PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareUnknownAndUnknown
+ ((state State-CompareUnknownAndUnknown)) PointerValue
+ (_get-x state))
+
+(define-fun get-y-CompareUnknownAndUnknown
+ ((state State-CompareUnknownAndUnknown)) PointerValue
+ (_get-y state))
+
+(define-fun run-CompareUnknownAndUnknown
+ ((state State-CompareUnknownAndUnknown))
+ FlowConditions
+ (match state
+ (((make-State-CompareUnknownAndUnknown x y b)
+ (let ((fc-1 (and (fc-conj--ptr-is-unknown x)
+ (fc-conj--ptr-is-unknown y))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x y b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareUnknownAndUnknown
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareUnknownAndUnknown))
+ (let ((fcs (run-CompareUnknownAndUnknown state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareUnknownAndUnknown
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareUnknownAndUnknown PointerValue)))
+ Bool
+ (exists ((state State-CompareUnknownAndUnknown))
+ (let ((fcs (run-CompareUnknownAndUnknown state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-CompareUnknownAndUnknown get-fc-1)
+ :named CompareUnknownAndUnknown-reachable-1))
+
+(assert
+ (! (is-reachable-CompareUnknownAndUnknown get-fc-2)
+ :named CompareUnknownAndUnknown-reachable-2))
+
+(assert
+ (! (is-reachable-CompareUnknownAndUnknown get-fc-3)
+ :named CompareUnknownAndUnknown-reachable-3))
+
+(assert
+ (! (is-reachable-CompareUnknownAndUnknown get-fc-4)
+ :named CompareUnknownAndUnknown-reachable-4))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndUnknown get-fc-3 get-x-CompareUnknownAndUnknown))
+ :named CompareUnknownAndUnknown-deref-3-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndUnknown get-fc-3 get-y-CompareUnknownAndUnknown))
+ :named CompareUnknownAndUnknown-deref-3-y))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndUnknown get-fc-4 get-x-CompareUnknownAndUnknown))
+ :named CompareUnknownAndUnknown-deref-4-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndUnknown get-fc-4 get-y-CompareUnknownAndUnknown))
+ :named CompareUnknownAndUnknown-deref-4-y))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareNonNullAndNonNull
+;;
+;; ```
+;; void target(int * _NonNull x, int * _NonNull y) {
+;; // (1)
+;; bool b = x == y;
+;; // (2)
+;; if (b) {
+;; // (3)
+;; *x; // safe
+;; *y; // safe
+;; } else {
+;; // (4)
+;; *x; // safe
+;; *y; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareNonNullAndNonNull
+ ((make-State-CompareNonNullAndNonNull
+ (_get-x PointerValue)
+ (_get-y PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareNonNullAndNonNull
+ ((state State-CompareNonNullAndNonNull)) PointerValue
+ (_get-x state))
+
+(define-fun get-y-CompareNonNullAndNonNull
+ ((state State-CompareNonNullAndNonNull)) PointerValue
+ (_get-y state))
+
+(define-fun run-CompareNonNullAndNonNull
+ ((state State-CompareNonNullAndNonNull))
+ FlowConditions
+ (match state
+ (((make-State-CompareNonNullAndNonNull x y b)
+ (let ((fc-1 (and (fc-conj--ptr-is-nonnull x)
+ (fc-conj--ptr-is-nonnull y))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x y b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareNonNullAndNonNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareNonNullAndNonNull))
+ (let ((fcs (run-CompareNonNullAndNonNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareNonNullAndNonNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareNonNullAndNonNull PointerValue)))
+ Bool
+ (exists ((state State-CompareNonNullAndNonNull))
+ (let ((fcs (run-CompareNonNullAndNonNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNonNull get-fc-1)
+ :named CompareNonNullAndNonNull-reachable-1))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNonNull get-fc-2)
+ :named CompareNonNullAndNonNull-reachable-2))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNonNull get-fc-3)
+ :named CompareNonNullAndNonNull-reachable-3))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNonNull get-fc-4)
+ :named CompareNonNullAndNonNull-reachable-4))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNonNull get-fc-3 get-x-CompareNonNullAndNonNull))
+ :named CompareNonNullAndNonNull-deref-3-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNonNull get-fc-3 get-y-CompareNonNullAndNonNull))
+ :named CompareNonNullAndNonNull-deref-3-y))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNonNull get-fc-4 get-x-CompareNonNullAndNonNull))
+ :named CompareNonNullAndNonNull-deref-4-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNonNull get-fc-4 get-y-CompareNonNullAndNonNull))
+ :named CompareNonNullAndNonNull-deref-4-y))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareNonNullAndNull
+;;
+;; ```
+;; void target(int * _NonNull x) {
+;; // (1)
+;; *x; // safe
+;; bool b = x == nullptr;
+;; // (2)
+;; *x; // safe
+;; if (b) {
+;; // (3) - unreachable
+;; } else {
+;; // (4)
+;; *x; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareNonNullAndNull
+ ((make-State-CompareNonNullAndNull
+ (_get-the-nullptr PointerValue)
+ (_get-x PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareNonNullAndNull
+ ((state State-CompareNonNullAndNull)) PointerValue
+ (_get-x state))
+
+(define-fun run-CompareNonNullAndNull
+ ((state State-CompareNonNullAndNull))
+ FlowConditions
+ (match state
+ (((make-State-CompareNonNullAndNull the-nullptr x b)
+ (let ((fc-1 (and (fc-conj--ptr-is-null the-nullptr)
+ (fc-conj--ptr-is-nonnull x))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x the-nullptr b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareNonNullAndNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareNonNullAndNull))
+ (let ((fcs (run-CompareNonNullAndNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareNonNullAndNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareNonNullAndNull PointerValue)))
+ Bool
+ (exists ((state State-CompareNonNullAndNull))
+ (let ((fcs (run-CompareNonNullAndNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNull get-fc-1)
+ :named CompareNonNullAndNull-reachable-1))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNull get-fc-2)
+ :named CompareNonNullAndNull-reachable-2))
+
+(assert
+ (! (not (is-reachable-CompareNonNullAndNull get-fc-3))
+ :named CompareNonNullAndNull-reachable-3))
+
+(assert
+ (! (is-reachable-CompareNonNullAndNull get-fc-4)
+ :named CompareNonNullAndNull-reachable-4))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNull get-fc-1 get-x-CompareNonNullAndNull))
+ :named CompareNonNullAndNull-deref-1))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNull get-fc-2 get-x-CompareNonNullAndNull))
+ :named CompareNonNullAndNull-deref-2))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNonNullAndNull get-fc-4 get-x-CompareNonNullAndNull))
+ :named CompareNonNullAndNull-deref-4))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareNullableAndNull
+;;
+;; ```
+;; void target(int * _Nullable x) {
+;; // (1)
+;; *x; // unsafe
+;; bool b = x == nullptr;
+;; // (2)
+;; *x; // unsafe
+;; if (b) {
+;; // (3)
+;; *x; // unsafe
+;; } else {
+;; // (4)
+;; *x; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareNullableAndNull
+ ((make-State-CompareNullableAndNull
+ (_get-the-nullptr PointerValue)
+ (_get-x PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareNullableAndNull
+ ((state State-CompareNullableAndNull)) PointerValue
+ (_get-x state))
+
+(define-fun run-CompareNullableAndNull
+ ((state State-CompareNullableAndNull))
+ FlowConditions
+ (match state
+ (((make-State-CompareNullableAndNull the-nullptr x b)
+ (let ((fc-1 (and (fc-conj--ptr-is-null the-nullptr)
+ (fc-conj--ptr-is-nullable x))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x the-nullptr b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareNullableAndNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareNullableAndNull))
+ (let ((fcs (run-CompareNullableAndNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareNullableAndNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareNullableAndNull PointerValue)))
+ Bool
+ (exists ((state State-CompareNullableAndNull))
+ (let ((fcs (run-CompareNullableAndNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-CompareNullableAndNull get-fc-1)
+ :named CompareNullableAndNull-reachable-1))
+
+(assert
+ (! (is-reachable-CompareNullableAndNull get-fc-2)
+ :named CompareNullableAndNull-reachable-2))
+
+(assert
+ (! (is-reachable-CompareNullableAndNull get-fc-3)
+ :named CompareNullableAndNull-reachable-3))
+
+(assert
+ (! (is-reachable-CompareNullableAndNull get-fc-4)
+ :named CompareNullableAndNull-reachable-4))
+
+(assert
+ (! (is-unsafe-deref-CompareNullableAndNull get-fc-1 get-x-CompareNullableAndNull)
+ :named CompareNullableAndNull-deref-1))
+
+(assert
+ (! (is-unsafe-deref-CompareNullableAndNull get-fc-2 get-x-CompareNullableAndNull)
+ :named CompareNullableAndNull-deref-2))
+
+(assert
+ (! (is-unsafe-deref-CompareNullableAndNull get-fc-3 get-x-CompareNullableAndNull)
+ :named CompareNullableAndNull-deref-3))
+
+(assert
+ (! (not (is-unsafe-deref-CompareNullableAndNull get-fc-4 get-x-CompareNullableAndNull))
+ :named CompareNullableAndNull-deref-4))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareUnknownAndNullSimple
+;;
+;; ```
+;; void target(int *x) {
+;; // (1)
+;; *x; // safe - false negative
+;; bool b = x == nullptr;
+;; // (2)
+;; *x; // safe - false negative
+;; if (b) {
+;; // (3)
+;; *x; // safe - false negative
+;; } else {
+;; // (4)
+;; *x; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareUnknownAndNullSimple
+ ((make-State-CompareUnknownAndNullSimple
+ (_get-the-nullptr PointerValue)
+ (_get-x PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareUnknownAndNullSimple
+ ((state State-CompareUnknownAndNullSimple)) PointerValue
+ (_get-x state))
+
+(define-fun run-CompareUnknownAndNullSimple
+ ((state State-CompareUnknownAndNullSimple))
+ FlowConditions
+ (match state
+ (((make-State-CompareUnknownAndNullSimple the-nullptr x b)
+ (let ((fc-1 (and (fc-conj--ptr-is-null the-nullptr)
+ (fc-conj--ptr-is-unknown x))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x the-nullptr b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareUnknownAndNullSimple
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareUnknownAndNullSimple))
+ (let ((fcs (run-CompareUnknownAndNullSimple state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareUnknownAndNullSimple
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareUnknownAndNullSimple PointerValue)))
+ Bool
+ (exists ((state State-CompareUnknownAndNullSimple))
+ (let ((fcs (run-CompareUnknownAndNullSimple state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert (=> enable-solution-s1
+ (! (is-reachable-CompareUnknownAndNullSimple get-fc-1)
+ :named CompareUnknownAndNullSimple-reachable-1)))
+
+(assert (=> enable-solution-s1
+ (! (is-reachable-CompareUnknownAndNullSimple get-fc-2)
+ :named CompareUnknownAndNullSimple-reachable-2)))
+
+(assert (=> enable-solution-s1
+ (! (is-reachable-CompareUnknownAndNullSimple get-fc-3)
+ :named CompareUnknownAndNullSimple-reachable-3)))
+
+(assert (=> enable-solution-s1
+ (! (is-reachable-CompareUnknownAndNullSimple get-fc-4)
+ :named CompareUnknownAndNullSimple-reachable-4)))
+
+(assert (=> enable-solution-s1
+ (! (not (is-unsafe-deref-CompareUnknownAndNullSimple get-fc-1 get-x-CompareUnknownAndNullSimple))
+ :named CompareUnknownAndNullSimple-deref-1)))
+
+(assert (=> enable-solution-s1
+ (! (not (is-unsafe-deref-CompareUnknownAndNullSimple get-fc-2 get-x-CompareUnknownAndNullSimple))
+ :named CompareUnknownAndNullSimple-deref-2)))
+
+(assert (=> enable-solution-s1
+ (! (not (is-unsafe-deref-CompareUnknownAndNullSimple get-fc-3 get-x-CompareUnknownAndNullSimple))
+ :named CompareUnknownAndNullSimple-deref-3)))
+
+(assert (=> enable-solution-s1
+ (! (not (is-unsafe-deref-CompareUnknownAndNullSimple get-fc-4 get-x-CompareUnknownAndNullSimple))
+ :named CompareUnknownAndNullSimple-deref-4)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareUnknownAndNonNull
+;;
+;; ```
+;; void target(int *x, int * _NonNull y) {
+;; // (1)
+;; *x; // safe
+;; *y; // safe
+;; bool b = x == y;
+;; // (2)
+;; *x; // safe
+;; *y; // safe
+;; if (b) {
+;; // (3)
+;; *x; // safe
+;; *y; // safe
+;; } else {
+;; // (4)
+;; *x; // safe
+;; *y; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareUnknownAndNonNull
+ ((make-State-CompareUnknownAndNonNull
+ (_get-x PointerValue)
+ (_get-y PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareUnknownAndNonNull
+ ((state State-CompareUnknownAndNonNull)) PointerValue
+ (_get-x state))
+
+(define-fun get-y-CompareUnknownAndNonNull
+ ((state State-CompareUnknownAndNonNull)) PointerValue
+ (_get-y state))
+
+(define-fun run-CompareUnknownAndNonNull
+ ((state State-CompareUnknownAndNonNull))
+ FlowConditions
+ (match state
+ (((make-State-CompareUnknownAndNonNull x y b)
+ (let ((fc-1 (and (fc-conj--ptr-is-unknown x)
+ (fc-conj--ptr-is-nonnull y))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x y b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareUnknownAndNonNull
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareUnknownAndNonNull))
+ (let ((fcs (run-CompareUnknownAndNonNull state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareUnknownAndNonNull
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareUnknownAndNonNull PointerValue)))
+ Bool
+ (exists ((state State-CompareUnknownAndNonNull))
+ (let ((fcs (run-CompareUnknownAndNonNull state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert
+ (! (is-reachable-CompareUnknownAndNonNull get-fc-1)
+ :named CompareUnknownAndNonNull-reachable-1))
+
+(assert
+ (! (is-reachable-CompareUnknownAndNonNull get-fc-2)
+ :named CompareUnknownAndNonNull-reachable-2))
+
+(assert
+ (! (is-reachable-CompareUnknownAndNonNull get-fc-3)
+ :named CompareUnknownAndNonNull-reachable-3))
+
+(assert
+ (! (is-reachable-CompareUnknownAndNonNull get-fc-4)
+ :named CompareUnknownAndNonNull-reachable-4))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-1 get-x-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-1-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-1 get-y-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-1-y))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-2 get-x-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-2-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-2 get-y-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-2-y))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-3 get-x-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-3-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-3 get-y-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-3-y))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-4 get-x-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-4-x))
+
+(assert
+ (! (not (is-unsafe-deref-CompareUnknownAndNonNull get-fc-4 get-y-CompareUnknownAndNonNull))
+ :named CompareUnknownAndNonNull-deref-4-y))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example CompareUnknownAndNullAdvanced
+;;
+;; ```
+;; void target(int *x) {
+;; // (1)
+;; *x; // unsafe
+;; bool b = x == nullptr;
+;; // (2)
+;; *x; // unsafe
+;; if (b) {
+;; // (3)
+;; *x; // unsafe
+;; } else {
+;; // (4)
+;; *x; // safe
+;; }
+;; }
+;; ```
+
+(declare-datatype State-CompareUnknownAndNullAdvanced
+ ((make-State-CompareUnknownAndNullAdvanced
+ (_get-the-nullptr PointerValue)
+ (_get-x PointerValue)
+ (_get-b Bool))))
+
+(define-fun get-x-CompareUnknownAndNullAdvanced
+ ((state State-CompareUnknownAndNullAdvanced)) PointerValue
+ (_get-x state))
+
+(define-fun run-CompareUnknownAndNullAdvanced
+ ((state State-CompareUnknownAndNullAdvanced))
+ FlowConditions
+ (match state
+ (((make-State-CompareUnknownAndNullAdvanced the-nullptr x b)
+ (let ((fc-1 (and (fc-conj--ptr-is-null the-nullptr)
+ (fc-conj--ptr-is-unknown x))))
+ (let ((fc-2 (and fc-1 (fc-conj--ptrs-were-compared x the-nullptr b))))
+ (let ((fc-3 (and fc-2 b)))
+ (let ((fc-4 (and fc-2 (not b))))
+ (make-FlowConditions fc-1 fc-2 fc-3 fc-4 false false)))))))))
+
+(define-fun is-reachable-CompareUnknownAndNullAdvanced
+ ((fc-getter (-> FlowConditions Bool)))
+ Bool
+ (exists ((state State-CompareUnknownAndNullAdvanced))
+ (let ((fcs (run-CompareUnknownAndNullAdvanced state)))
+ (fc-getter fcs))))
+
+(define-fun is-unsafe-deref-CompareUnknownAndNullAdvanced
+ ((fc-getter (-> FlowConditions Bool))
+ (ptr-getter (-> State-CompareUnknownAndNullAdvanced PointerValue)))
+ Bool
+ (exists ((state State-CompareUnknownAndNullAdvanced))
+ (let ((fcs (run-CompareUnknownAndNullAdvanced state)))
+ (is-unsafe-to-deref (fc-getter fcs) (ptr-getter state)))))
+
+(assert (=> (not enable-solution-s1)
+ (! (is-reachable-CompareUnknownAndNullAdvanced get-fc-1)
+ :named CompareUnknownAndNullAdvanced-reachable-1)))
+
+(assert (=> (not enable-solution-s1)
+ (! (is-reachable-CompareUnknownAndNullAdvanced get-fc-2)
+ :named CompareUnknownAndNullAdvanced-reachable-2)))
+
+(assert (=> (not enable-solution-s1)
+ (! (is-reachable-CompareUnknownAndNullAdvanced get-fc-3)
+ :named CompareUnknownAndNullAdvanced-reachable-3)))
+
+(assert (=> (not enable-solution-s1)
+ (! (is-reachable-CompareUnknownAndNullAdvanced get-fc-4)
+ :named CompareUnknownAndNullAdvanced-reachable-4)))
+
+(assert (=> (not enable-solution-s1)
+ ;; The dereference at (1) is actually unsafe, but the structure of the
+ ;; dataflow analysis defined in this file, can't detect that. The issue is
+ ;; that the flow condition at (1) does not have information about what happens
+ ;; later.
+ (! (not (is-unsafe-deref-CompareUnknownAndNullAdvanced get-fc-1 get-x-CompareUnknownAndNullAdvanced))
+ :named CompareUnknownAndNullAdvanced-deref-1)))
+
+(assert (=> (not enable-solution-s1)
+ (! (is-unsafe-deref-CompareUnknownAndNullAdvanced get-fc-2 get-x-CompareUnknownAndNullAdvanced)
+ :named CompareUnknownAndNullAdvanced-deref-2)))
+
+(assert (=> (not enable-solution-s1)
+ (! (is-unsafe-deref-CompareUnknownAndNullAdvanced get-fc-3 get-x-CompareUnknownAndNullAdvanced)
+ :named CompareUnknownAndNullAdvanced-deref-3)))
+
+(assert (=> (not enable-solution-s1)
+ (! (not (is-unsafe-deref-CompareUnknownAndNullAdvanced get-fc-4 get-x-CompareUnknownAndNullAdvanced))
+ :named CompareUnknownAndNullAdvanced-deref-4)))
+
+(echo "Verifying the selected solution against test cases.")
+(check-sat)
+(get-unsat-core)
+(get-value (fc-conj--ptr-is-null))
+(get-value (fc-conj--ptr-is-unknown))
+(get-value (fc-conj--ptr-is-nonnull))
+(get-value (fc-conj--ptr-is-nullable))
+(get-value (fc-conj--ptrs-were-compared))
+(get-value (fc-conj--join-ptr))
+(get-value (is-unsafe-to-deref))
+
+; vim: set syntax=scheme:
diff --git a/nullability/formal_methods/optional.smt2 b/nullability/formal_methods/optional.smt2
new file mode 100644
index 0000000..5ed2495
--- /dev/null
+++ b/nullability/formal_methods/optional.smt2
@@ -0,0 +1,121 @@
+;; Part of the Crubit project, under the Apache License v2.0 with LLVM
+;; Exceptions. See /LICENSE for license information.
+;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+;; Run: cvc5 --lang smt --incremental optional.smt2
+
+(set-option :produce-models true)
+(set-option :produce-assertions true)
+(set-option :produce-assignments true)
+(set-option :produce-unsat-cores true)
+(set-logic HO_ALL)
+
+(define-fun join-fc ((c Bool) (fc-then Bool) (fc-else Bool)) Bool
+ (or
+ (and c fc-then)
+ (and (not c) fc-else)))
+
+(declare-datatype OptionalValue
+ ((make-optional-value (get-x0 Bool))))
+
+(define-fun get-has-value ((opt OptionalValue)) Bool
+ (get-x0 opt))
+
+(define-fun is-unsafe-to-unwrap ((fc Bool) (opt OptionalValue)) Bool
+ (and fc (not (get-has-value opt))))
+
+(echo "=============================")
+(echo "=== Example Regular-Check ===")
+(push 1)
+
+; void foo(optional<int> x) {
+; // (1)
+; x.value();
+; if (x.has_value()) {
+; // (2)
+; x.value();
+; }
+; }
+
+(declare-fun x () OptionalValue)
+
+(define-fun fc-1 () Bool
+ true)
+
+(define-fun fc-2 () Bool
+ (get-has-value x))
+
+(push 1)
+ (assert (is-unsafe-to-unwrap fc-1 x))
+ (echo "Expected: sat")
+ (echo "Actual:")
+ (check-sat)
+ (get-value (fc-1 fc-2 x))
+(pop 1)
+
+(push 1)
+ (assert (is-unsafe-to-unwrap fc-2 x))
+ (echo "Expected: unsat")
+ (echo "Actual:")
+ (check-sat)
+ (get-value (fc-1 fc-2 x))
+(pop 1)
+
+(pop 1)
+
+(echo "============================")
+(echo "=== Example Mixed-Values ===")
+(push 1)
+
+; void foo(optional<int> x, bool b) {
+; // (1)
+; if (b) {
+; // (2)
+; x = 42;
+; // (3)
+; }
+; // (4)
+; x.value();
+; if (b) {
+; // (5)
+; x.value();
+; }
+; }
+
+(declare-fun x () OptionalValue)
+(declare-fun b () Bool)
+
+(define-fun fc-1 () Bool
+ true)
+
+(define-fun fc-2 () Bool
+ (and fc-1 b))
+
+(define-fun fc-3 () Bool
+ (and fc-2 (get-has-value x)))
+
+(define-fun fc-4 () Bool
+ (join-fc b fc-3 fc-1))
+
+(define-fun fc-5 () Bool
+ (and fc-4 b))
+
+(push 1)
+ (assert (is-unsafe-to-unwrap fc-4 x))
+ (echo "Expected: sat")
+ (echo "Actual:")
+ (check-sat)
+ (get-value (fc-1 fc-2 fc-3 fc-4 fc-5 x b))
+(pop 1)
+
+(push 1)
+ (assert (is-unsafe-to-unwrap fc-5 x))
+ (echo "Expected: unsat")
+ (echo "Actual:")
+ (check-sat)
+ (get-value (fc-1 fc-2 fc-3 fc-4 fc-5 x b))
+(pop 1)
+
+(pop 1)
+
+; vim: set syntax=scheme:
diff --git a/nullability/formal_methods/optional_code_synthesis.smt2 b/nullability/formal_methods/optional_code_synthesis.smt2
new file mode 100644
index 0000000..93d7a7d
--- /dev/null
+++ b/nullability/formal_methods/optional_code_synthesis.smt2
@@ -0,0 +1,149 @@
+;; Part of the Crubit project, under the Apache License v2.0 with LLVM
+;; Exceptions. See /LICENSE for license information.
+;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+;; Run: cvc5 --lang smt --incremental --fmf-bound optional_code_synthesis.smt2
+
+(set-option :produce-models true)
+(set-option :produce-assertions true)
+(set-option :produce-assignments true)
+(set-option :produce-unsat-cores true)
+(set-logic HO_ALL)
+
+(declare-datatype FlowConditions
+ ((make-flow-conditions
+ (get-fc-1 Bool)
+ (get-fc-2 Bool)
+ (get-fc-3 Bool)
+ (get-fc-4 Bool)
+ (get-fc-5 Bool))))
+
+(define-fun join-fc ((c Bool) (fc-then Bool) (fc-else Bool)) Bool
+ (or
+ (and c fc-then)
+ (and (not c) fc-else)))
+
+(declare-datatype OptionalValue
+ ((make-optional-value (get-x0 Bool))))
+
+(declare-fun get-has-value (OptionalValue) Bool)
+
+;; Args: flow-condition, opt.
+(declare-fun is-unsafe-to-unwrap (Bool OptionalValue) Bool)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example RegularCheck
+;;
+;; ```
+;; void foo(optional<int> x) {
+;; // (1)
+;; x.value();
+;; if (x.has_value()) {
+;; // (2)
+;; x.value();
+;; }
+;; }
+;; ```
+
+(define-fun run-RegularCheck
+ ((x OptionalValue) (body (-> FlowConditions Bool))) Bool
+ (let ((fc-1 true))
+ (let ((fc-2 (and fc-1 (get-has-value x))))
+ (body (make-flow-conditions fc-1 fc-2 false false false)))))
+
+;; Every program point is reachable.
+(assert
+ (exists ((x OptionalValue))
+ (run-RegularCheck x (lambda ((fcs FlowConditions)) (get-fc-1 fcs)))))
+
+(assert
+ (exists ((x OptionalValue))
+ (run-RegularCheck x (lambda ((fcs FlowConditions)) (get-fc-2 fcs)))))
+
+;; Unwrap at (1) is unsafe.
+(assert
+ (exists ((x OptionalValue))
+ (run-RegularCheck
+ x
+ (lambda ((fcs FlowConditions))
+ (is-unsafe-to-unwrap (get-fc-1 fcs) x)))))
+
+;; Unwrap at (2) is safe.
+(assert
+ (forall ((x OptionalValue))
+ (run-RegularCheck
+ x
+ (lambda ((fcs FlowConditions))
+ (not (is-unsafe-to-unwrap (get-fc-2 fcs) x))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Example MixedValues
+;;
+;; ```
+;; void foo(optional<int> x, bool b) {
+;; // (1)
+;; if (b) {
+;; // (2)
+;; x = 42;
+;; // (3)
+;; }
+;; // (4)
+;; x.value();
+;; if (b) {
+;; // (5)
+;; x.value();
+;; }
+;; }
+;; ```
+
+(define-fun run-MixedValues
+ ((x OptionalValue) (b Bool) (body (-> FlowConditions Bool))) Bool
+ (let ((fc-1 true))
+ (let ((fc-2 (and fc-1 b)))
+ (let ((fc-3 (and fc-2 (get-has-value x))))
+ (let ((fc-4 (join-fc b fc-3 fc-1)))
+ (let ((fc-5 (and fc-4 b)))
+ (body (make-flow-conditions fc-1 fc-2 fc-3 fc-4 fc-5))))))))
+
+;; Every program point is reachable.
+(assert
+ (exists ((x OptionalValue) (b Bool))
+ (run-MixedValues x b (lambda ((fcs FlowConditions)) (get-fc-1 fcs)))))
+
+(assert
+ (exists ((x OptionalValue) (b Bool))
+ (run-MixedValues x b (lambda ((fcs FlowConditions)) (get-fc-2 fcs)))))
+
+(assert
+ (exists ((x OptionalValue) (b Bool))
+ (run-MixedValues x b (lambda ((fcs FlowConditions)) (get-fc-3 fcs)))))
+
+(assert
+ (exists ((x OptionalValue) (b Bool))
+ (run-MixedValues x b (lambda ((fcs FlowConditions)) (get-fc-4 fcs)))))
+
+(assert
+ (exists ((x OptionalValue) (b Bool))
+ (run-MixedValues x b (lambda ((fcs FlowConditions)) (get-fc-5 fcs)))))
+
+;; Unwrap at (4) is unsafe.
+(assert
+ (exists ((x OptionalValue) (b Bool))
+ (run-MixedValues
+ x b
+ (lambda ((fcs FlowConditions))
+ (is-unsafe-to-unwrap (get-fc-4 fcs) x)))))
+
+;; Unwrap at (5) is safe.
+(assert
+ (forall ((x OptionalValue) (b Bool))
+ (run-MixedValues
+ x b
+ (lambda ((fcs FlowConditions))
+ (not (is-unsafe-to-unwrap (get-fc-5 fcs) x))))))
+
+(check-sat)
+(get-value (get-has-value))
+(get-value (is-unsafe-to-unwrap))
+
+; vim: set syntax=scheme:
diff --git a/nullability/pointer_nullability.cc b/nullability/pointer_nullability.cc
new file mode 100644
index 0000000..34e16ff
--- /dev/null
+++ b/nullability/pointer_nullability.cc
@@ -0,0 +1,316 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/pointer_nullability.h"
+
+#include "absl/log/check.h"
+#include "nullability/pointer_nullability_lattice.h"
+#include "clang/AST/ASTDumper.h"
+#include "clang/AST/TypeVisitor.h"
+#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/SaveAndRestore.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using dataflow::AtomicBoolValue;
+using dataflow::BoolValue;
+using dataflow::Environment;
+using dataflow::PointerValue;
+using dataflow::SkipPast;
+using dataflow::TransferState;
+
+/// The nullness information of a pointer is represented by two properties
+/// which indicate if a pointer's nullability (i.e., if the pointer can hold
+/// null) is `Known` and if the pointer's value is `Null`.
+constexpr llvm::StringLiteral kKnown = "is_known";
+constexpr llvm::StringLiteral kNull = "is_null";
+
+NullabilityKind getNullabilityKind(QualType Type, ASTContext& Ctx) {
+ return Type->getNullability().value_or(NullabilityKind::Unspecified);
+}
+
+PointerValue* getPointerValueFromExpr(const Expr* PointerExpr,
+ const Environment& Env) {
+ return cast_or_null<PointerValue>(
+ Env.getValue(*PointerExpr, SkipPast::Reference));
+}
+
+std::pair<AtomicBoolValue&, AtomicBoolValue&> getPointerNullState(
+ const PointerValue& PointerVal, const Environment& Env) {
+ auto& PointerKnown = *cast<AtomicBoolValue>(PointerVal.getProperty(kKnown));
+ auto& PointerNull = *cast<AtomicBoolValue>(PointerVal.getProperty(kNull));
+ return {PointerKnown, PointerNull};
+}
+
+void initPointerBoolProperty(PointerValue& PointerVal, llvm::StringRef Name,
+ BoolValue* BoolVal, Environment& Env) {
+ if (PointerVal.getProperty(Name) == nullptr) {
+ PointerVal.setProperty(Name,
+ BoolVal ? *BoolVal : Env.makeAtomicBoolValue());
+ }
+}
+
+void initPointerNullState(PointerValue& PointerVal, Environment& Env,
+ BoolValue* KnownConstraint,
+ BoolValue* NullConstraint) {
+ initPointerBoolProperty(PointerVal, kKnown, KnownConstraint, Env);
+ initPointerBoolProperty(PointerVal, kNull, NullConstraint, Env);
+}
+
+bool isNullable(const PointerValue& PointerVal, const Environment& Env) {
+ auto [PointerKnown, PointerNull] = getPointerNullState(PointerVal, Env);
+ auto& PointerNotKnownNull =
+ Env.makeNot(Env.makeAnd(PointerKnown, PointerNull));
+ return !Env.flowConditionImplies(PointerNotKnownNull);
+}
+
+std::string nullabilityToString(ArrayRef<NullabilityKind> Nullability) {
+ std::string Result = "[";
+ llvm::interleave(
+ Nullability,
+ [&](const NullabilityKind n) {
+ Result += getNullabilitySpelling(n).str();
+ },
+ [&] { Result += ", "; });
+ Result += "]";
+ return Result;
+}
+
+namespace {
+// Traverses a Type to find the points where it might be nullable.
+// This will visit the contained PointerType in the correct order to produce
+// the TypeNullability vector.
+//
+// Subclasses must provide `void report(const PointerType*, NullabilityKind)`,
+// and may override TypeVisitor Visit*Type methods to customize the traversal.
+//
+// Canonically-equivalent Types produce equivalent sequences of report() calls:
+// - corresponding PointerTypes are canonically-equivalent
+// - the NullabilityKind may be different, as it derives from type sugar
+template <class Impl>
+class NullabilityWalker : public TypeVisitor<Impl> {
+ using Base = TypeVisitor<Impl>;
+ Impl& derived() { return *static_cast<Impl*>(this); }
+
+ // A nullability attribute we've seen, waiting to attach to a pointer type.
+ // There may be sugar in between: Attributed -> Typedef -> Typedef -> Pointer.
+ // All non-sugar types must consume nullability, most will ignore it.
+ std::optional<NullabilityKind> PendingNullability;
+
+ void ignoreUnexpectedNullability() {
+ // TODO: Can we upgrade this to an assert?
+ // clang is pretty thorough about ensuring we can't put _Nullable on
+ // non-pointers, even failing template instantiation on this basis.
+ PendingNullability.reset();
+ }
+
+ // While walking the underlying type of alias TemplateSpecializationTypes,
+ // we see SubstTemplateTypeParmTypes where type parameters were referenced.
+ // The directly-available underlying types lack sugar, but we can retrieve the
+ // sugar from the arguments of the original TemplateSpecializationType.
+ //
+ // It is only possible to reference params of the immediately enclosing alias,
+ // so we keep details of the alias specialization we're currently processing.
+ struct AliasArgs {
+ const Decl* AssociatedDecl;
+ ArrayRef<TemplateArgument> Args;
+ // The alias context in which the alias specialization itself appeared.
+ // (The alias's args may reference params from this context.)
+ const AliasArgs* Parent;
+ };
+ const AliasArgs* CurrentAliasTemplate = nullptr;
+
+ public:
+ void Visit(QualType T) { Base::Visit(T.getTypePtr()); }
+ void Visit(const TemplateArgument& TA) {
+ if (TA.getKind() == TemplateArgument::Type) Visit(TA.getAsType());
+ if (TA.getKind() == TemplateArgument::Pack)
+ for (const auto& PackElt : TA.getPackAsArray()) Visit(PackElt);
+ }
+ void Visit(const DeclContext* DC) {
+ // For now, only consider enclosing classes.
+ // TODO: The nullability of template functions can affect local classes too,
+ // this can be relevant e.g. when instantiating templates with such types.
+ if (auto* CRD = llvm::dyn_cast<CXXRecordDecl>(DC))
+ Visit(DC->getParentASTContext().getRecordType(CRD));
+ }
+
+ void VisitType(const Type* T) {
+ // For sugar not explicitly handled below, desugar and continue.
+ // (We need to walk the full structure of the canonical type.)
+ if (auto* Desugar =
+ T->getLocallyUnqualifiedSingleStepDesugaredType().getTypePtr();
+ Desugar != T)
+ return Base::Visit(Desugar);
+
+ // We don't expect to see any nullable non-sugar types except PointerType.
+ ignoreUnexpectedNullability();
+ Base::VisitType(T);
+ }
+
+ void VisitFunctionProtoType(const FunctionProtoType* FPT) {
+ ignoreUnexpectedNullability();
+ Visit(FPT->getReturnType());
+ for (auto ParamType : FPT->getParamTypes()) Visit(ParamType);
+ }
+
+ void VisitTemplateSpecializationType(const TemplateSpecializationType* TST) {
+ if (TST->isTypeAlias()) {
+ // Aliases are sugar, visit the underlying type.
+ // Record template args so we can resugar substituted params.
+ const AliasArgs Args{TST->getTemplateName().getAsTemplateDecl(),
+ TST->template_arguments(), CurrentAliasTemplate};
+ llvm::SaveAndRestore UseAlias(CurrentAliasTemplate, &Args);
+ VisitType(TST);
+ return;
+ }
+
+ auto* CRD = TST->getAsCXXRecordDecl();
+ CHECK(CRD) << "Expected an alias or class specialization in concrete code";
+ ignoreUnexpectedNullability();
+ Visit(CRD->getDeclContext());
+ for (auto TA : TST->template_arguments()) Visit(TA);
+ }
+
+ void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType* T) {
+ if (isa<TypeAliasTemplateDecl>(T->getAssociatedDecl())) {
+ if (CurrentAliasTemplate != nullptr) {
+ CHECK(T->getAssociatedDecl() == CurrentAliasTemplate->AssociatedDecl);
+ unsigned Index = T->getIndex();
+ // Valid because pack must be the last param in alias templates.
+ if (auto PackIndex = T->getPackIndex())
+ Index = CurrentAliasTemplate->Args.size() - 1 - *PackIndex;
+ const TemplateArgument& Arg = CurrentAliasTemplate->Args[Index];
+
+ llvm::SaveAndRestore OriginalContext(CurrentAliasTemplate,
+ CurrentAliasTemplate->Parent);
+ return Visit(Arg);
+ } else {
+ // Our top-level type references an unbound type alias param.
+ // Presumably our original input was the underlying type of an alias
+ // instantiation, we now lack the context needed to resugar it.
+ // TODO: maybe this could be an assert? We would need to trust all
+ // callers are obtaining types appropriately, and that clang never
+ // partially-desugars in a problematic way.
+ }
+ }
+ VisitType(T);
+ }
+
+ void VisitRecordType(const RecordType* RT) {
+ ignoreUnexpectedNullability();
+ Visit(RT->getDecl()->getDeclContext());
+ if (auto* CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl())) {
+ // TODO: if this is an instantiation, these args lack sugar.
+ // We can try to retrieve it from the current template context.
+ for (auto& TA : CTSD->getTemplateArgs().asArray()) Visit(TA);
+ }
+ }
+
+ void VisitAttributedType(const AttributedType* AT) {
+ if (auto NK = AT->getImmediateNullability()) {
+ // If we see nullability applied twice, the outer one wins.
+ if (!PendingNullability.has_value()) PendingNullability = *NK;
+ }
+ Visit(AT->getModifiedType());
+ CHECK(!PendingNullability.has_value())
+ << "Should have been consumed by modified type! "
+ << AT->getModifiedType().getAsString();
+ }
+
+ void VisitPointerType(const PointerType* PT) {
+ derived().report(PT,
+ PendingNullability.value_or(NullabilityKind::Unspecified));
+ PendingNullability.reset();
+ Visit(PT->getPointeeType());
+ }
+};
+
+template <typename T>
+unsigned countPointers(const T& Object) {
+ struct Walker : public NullabilityWalker<Walker> {
+ unsigned Count = 0;
+ void report(const PointerType*, NullabilityKind) { ++Count; }
+ } PointerCountWalker;
+ PointerCountWalker.Visit(Object);
+ return PointerCountWalker.Count;
+}
+
+} // namespace
+
+unsigned countPointersInType(QualType T) { return countPointers(T); }
+
+unsigned countPointersInType(const DeclContext* DC) {
+ return countPointers(DC);
+}
+unsigned countPointersInType(TemplateArgument TA) { return countPointers(TA); }
+
+QualType exprType(const Expr* E) {
+ if (E->hasPlaceholderType(BuiltinType::BoundMember))
+ return Expr::findBoundMemberType(E);
+ return E->getType();
+}
+
+unsigned countPointersInType(const Expr* E) {
+ return countPointersInType(exprType(E));
+}
+
+std::vector<NullabilityKind> getNullabilityAnnotationsFromType(
+ QualType T,
+ llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam) {
+ struct Walker : NullabilityWalker<Walker> {
+ std::vector<NullabilityKind> Annotations;
+ llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam;
+
+ void report(const PointerType*, NullabilityKind NK) {
+ Annotations.push_back(NK);
+ }
+
+ void VisitSubstTemplateTypeParmType(const SubstTemplateTypeParmType* ST) {
+ if (SubstituteTypeParam) {
+ if (auto Subst = SubstituteTypeParam(ST)) {
+ DCHECK_EQ(Subst->size(),
+ countPointersInType(ST->getCanonicalTypeInternal()))
+ << "Substituted nullability has the wrong structure: "
+ << QualType(ST, 0).getAsString();
+ llvm::append_range(Annotations, *Subst);
+ return;
+ }
+ }
+ NullabilityWalker::VisitSubstTemplateTypeParmType(ST);
+ }
+ } AnnotationVisitor;
+ AnnotationVisitor.SubstituteTypeParam = SubstituteTypeParam;
+ AnnotationVisitor.Visit(T);
+ return std::move(AnnotationVisitor.Annotations);
+}
+
+std::vector<NullabilityKind> unspecifiedNullability(const Expr* E) {
+ return std::vector<NullabilityKind>(countPointersInType(E),
+ NullabilityKind::Unspecified);
+}
+
+ArrayRef<NullabilityKind> getNullabilityForChild(
+ const Expr* E, TransferState<PointerNullabilityLattice>& State) {
+ return State.Lattice.insertExprNullabilityIfAbsent(E, [&] {
+ // Since we process child nodes before parents, we should already have
+ // computed the child nullability. However, this is not true in all test
+ // cases. So, we return unspecified nullability annotations.
+ // TODO: fix this issue, and CHECK() instead.
+ llvm::dbgs() << "=== Missing child nullability: ===\n";
+ dump(E, llvm::dbgs());
+ llvm::dbgs() << "==================================\n";
+
+ return unspecifiedNullability(E);
+ });
+}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/pointer_nullability.h b/nullability/pointer_nullability.h
new file mode 100644
index 0000000..b88a889
--- /dev/null
+++ b/nullability/pointer_nullability.h
@@ -0,0 +1,151 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_NULLABILITY_POINTER_NULLABILITY_H_
+#define CRUBIT_NULLABILITY_POINTER_NULLABILITY_H_
+
+#include <utility>
+
+#include "nullability/pointer_nullability_lattice.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTDumper.h"
+#include "clang/AST/Expr.h"
+#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "clang/Basic/Specifiers.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using dataflow::TransferState;
+
+/// Returns the `NullabilityKind` corresponding to the nullability annotation on
+/// `Type` if present. Otherwise, returns `NullabilityKind::Unspecified`.
+NullabilityKind getNullabilityKind(QualType Type, ASTContext& Ctx);
+
+/// Returns the `PointerValue` allocated to `PointerExpr` if available.
+/// Otherwise, returns nullptr.
+dataflow::PointerValue* getPointerValueFromExpr(
+ const Expr* PointerExpr, const dataflow::Environment& Env);
+
+/// Returns the properties representing the nullness information of a pointer.
+///
+/// The first boolean indicates if the pointer's nullability is known.
+/// The second boolean indicates if the pointer's value is null.
+std::pair<dataflow::AtomicBoolValue&, dataflow::AtomicBoolValue&>
+getPointerNullState(const dataflow::PointerValue& PointerVal,
+ const dataflow::Environment& Env);
+
+/// Sets the nullness properties on `PointerVal` if not already initialised.
+///
+/// The boolean properties may be constrained by specifying `KnownConstraint`
+/// and `NullConstraint`. Otherwise, the properties are set to freshly
+/// created atomic booleans.
+void initPointerNullState(dataflow::PointerValue& PointerVal,
+ dataflow::Environment& Env,
+ dataflow::BoolValue* KnownConstraint = nullptr,
+ dataflow::BoolValue* NullConstraint = nullptr);
+
+/// Sets the nullness properties on `PointerVal` representing a nullptr if not
+/// already initialised.
+///
+/// `Known` is constrained to true, `Null` is constrained to true.
+inline void initNullPointer(dataflow::PointerValue& PointerVal,
+ dataflow::Environment& Env) {
+ initPointerNullState(PointerVal, Env,
+ /*KnownConstraint=*/&Env.getBoolLiteralValue(true),
+ /*NullConstraint=*/&Env.getBoolLiteralValue(true));
+}
+
+/// Sets the nullness properties on `PointerVal` representing a pointer that is
+/// not null if not already initialised.
+///
+/// `Known` is constrained to true, `Null` is constrained to false.
+inline void initNotNullPointer(dataflow::PointerValue& PointerVal,
+ dataflow::Environment& Env) {
+ initPointerNullState(PointerVal, Env,
+ /*KnownConstraint=*/&Env.getBoolLiteralValue(true),
+ /*NullConstraint=*/&Env.getBoolLiteralValue(false));
+}
+
+/// Sets the nullness properties on `PointerVal` representing a pointer that is
+/// nullable if not already initialised.
+///
+/// `Known` is constrained to true, `Null` is unconstrained.
+inline void initNullablePointer(dataflow::PointerValue& PointerVal,
+ dataflow::Environment& Env) {
+ initPointerNullState(PointerVal, Env,
+ /*KnownConstraint=*/&Env.getBoolLiteralValue(true));
+}
+
+/// Sets the nullness properties on `PointerVal` representing a pointer with
+/// unknown nullability if not already initialised.
+///
+/// `Known` is constrained to false, `Null` is unconstrained.
+inline void initUnknownPointer(dataflow::PointerValue& PointerVal,
+ dataflow::Environment& Env) {
+ initPointerNullState(PointerVal, Env,
+ /*KnownConstraint=*/&Env.getBoolLiteralValue(false));
+}
+
+/// Returns true if there is evidence that `PointerVal` may hold a nullptr.
+bool isNullable(const dataflow::PointerValue& PointerVal,
+ const dataflow::Environment& Env);
+
+/// Returns a human-readable debug representation of a nullability vector.
+std::string nullabilityToString(ArrayRef<NullabilityKind> Nullability);
+
+/// A function that may provide enhanced nullability information for a
+/// substituted template parameter (which has no sugar of its own).
+using GetTypeParamNullability = std::optional<std::vector<NullabilityKind>>(
+ const SubstTemplateTypeParmType* ST);
+/// Traverse over a type to get its nullability. For example, if T is the type
+/// Struct3Arg<int * _Nonnull, int, pair<int * _Nullable, int *>> * _Nonnull,
+/// the resulting nullability annotations will be {_Nonnull, _Nonnull,
+/// _Nullable, _Unknown}. Note that non-pointer elements (e.g., the second
+/// argument of Struct3Arg) do not get a nullability annotation.
+std::vector<NullabilityKind> getNullabilityAnnotationsFromType(
+ QualType T,
+ llvm::function_ref<GetTypeParamNullability> SubstituteTypeParam = nullptr);
+
+/// Prints QualType's underlying canonical type, annotated with nullability.
+/// See rebuildWithNullability().
+std::string printWithNullability(QualType, ArrayRef<NullabilityKind>,
+ ASTContext&);
+/// Returns an equivalent type annotated with the provided nullability.
+/// Any existing sugar (including nullability) is discarded.
+/// rebuildWithNullability(int *, {Nullable}) ==> int * _Nullable.
+QualType rebuildWithNullability(QualType, ArrayRef<NullabilityKind>,
+ ASTContext&);
+
+/// Computes the number of pointer slots within a type.
+/// Each of these could conceptually be nullable, so this is the length of
+/// the nullability vector computed by getNullabilityAnnotationsFromType().
+unsigned countPointersInType(QualType T);
+unsigned countPointersInType(const Expr* E);
+unsigned countPointersInType(TemplateArgument TA);
+unsigned countPointersInType(const DeclContext* DC);
+
+QualType exprType(const Expr* E);
+
+std::vector<NullabilityKind> unspecifiedNullability(const Expr* E);
+
+// Work around the lack of Expr.dump() etc with an ostream but no ASTContext.
+template <typename T>
+void dump(const T& Node, llvm::raw_ostream& OS) {
+ clang::ASTDumper(OS, /*ShowColors=*/false).Visit(Node);
+}
+
+// Returns the computed nullability for a subexpr of the current expression.
+// This is always available as we compute bottom-up.
+ArrayRef<NullabilityKind> getNullabilityForChild(
+ const Expr* E, TransferState<PointerNullabilityLattice>& State);
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // CRUBIT_NULLABILITY_POINTER_NULLABILITY_H_
diff --git a/nullability/pointer_nullability_analysis.cc b/nullability/pointer_nullability_analysis.cc
new file mode 100644
index 0000000..7ab5c82
--- /dev/null
+++ b/nullability/pointer_nullability_analysis.cc
@@ -0,0 +1,697 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/pointer_nullability_analysis.h"
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "absl/log/check.h"
+#include "nullability/pointer_nullability.h"
+#include "nullability/pointer_nullability_lattice.h"
+#include "nullability/pointer_nullability_matchers.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTDumper.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/OperationKinds.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/Type.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/Specifiers.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using ast_matchers::MatchFinder;
+using dataflow::BoolValue;
+using dataflow::CFGMatchSwitchBuilder;
+using dataflow::Environment;
+using dataflow::PointerValue;
+using dataflow::SkipPast;
+using dataflow::TransferState;
+using dataflow::Value;
+
+namespace {
+
+std::vector<NullabilityKind> prepend(NullabilityKind Head,
+ ArrayRef<NullabilityKind> Tail) {
+ std::vector<NullabilityKind> Result = {Head};
+ Result.insert(Result.end(), Tail.begin(), Tail.end());
+ return Result;
+}
+
+void computeNullability(const Expr* E,
+ TransferState<PointerNullabilityLattice>& State,
+ std::function<std::vector<NullabilityKind>()> Compute) {
+ (void)State.Lattice.insertExprNullabilityIfAbsent(E, [&] {
+ auto Nullability = Compute();
+ if (unsigned ExpectedSize = countPointersInType(E);
+ ExpectedSize != Nullability.size()) {
+ // A nullability vector must have one entry per pointer in the type.
+ // If this is violated, we probably failed to handle some AST node.
+ llvm::dbgs()
+ << "=== Nullability vector has wrong number of entries: ===\n";
+ llvm::dbgs() << "Expression: \n";
+ dump(E, llvm::dbgs());
+ llvm::dbgs() << "\nNullability (" << Nullability.size()
+ << " pointers): " << nullabilityToString(Nullability)
+ << "\n";
+ llvm::dbgs() << "\nType (" << ExpectedSize << " pointers): \n";
+ dump(exprType(E), llvm::dbgs());
+ llvm::dbgs() << "=================================\n";
+
+ // We can't meaningfully interpret the vector, so discard it.
+ // TODO: fix all broken cases and upgrade to CHECK or DCHECK or so.
+ Nullability.assign(ExpectedSize, NullabilityKind::Unspecified);
+ }
+ return Nullability;
+ });
+}
+
+/// Compute the nullability annotation of type `T`, which contains types
+/// originally written as a class template type parameter.
+///
+/// Example:
+///
+/// \code
+/// template <typename F, typename S>
+/// struct pair {
+/// S *_Nullable getNullablePtrToSecond();
+/// };
+/// \endcode
+///
+/// Consider the following member call:
+///
+/// \code
+/// pair<int *, int *_Nonnull> x;
+/// x.getNullablePtrToSecond();
+/// \endcode
+///
+/// The class template specialization `x` has the following substitutions:
+///
+/// F=int *, whose nullability is [_Unspecified]
+/// S=int * _Nonnull, whose nullability is [_Nonnull]
+///
+/// The return type of the member call `x.getNullablePtrToSecond()` is
+/// S * _Nullable.
+///
+/// When we call `substituteNullabilityAnnotationsInClassTemplate` with the type
+/// `S * _Nullable` and the `base` node of the member call (in this case, a
+/// `DeclRefExpr`), it returns the nullability of the given type after applying
+/// substitutions, which in this case is [_Nullable, _Nonnull].
+std::vector<NullabilityKind> substituteNullabilityAnnotationsInClassTemplate(
+ QualType T, ArrayRef<NullabilityKind> BaseNullabilityAnnotations,
+ QualType BaseType) {
+ return getNullabilityAnnotationsFromType(
+ T,
+ [&](const SubstTemplateTypeParmType* ST)
+ -> std::optional<std::vector<NullabilityKind>> {
+ // The class specialization that is BaseType and owns ST.
+ const ClassTemplateSpecializationDecl* Specialization = nullptr;
+ if (auto RT = BaseType->getAs<RecordType>())
+ Specialization =
+ dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl());
+ // TODO: handle nested templates, where associated decl != base type
+ // (e.g. PointerNullabilityTest.MemberFunctionTemplateOfTemplateStruct)
+ if (!Specialization || Specialization != ST->getAssociatedDecl())
+ return std::nullopt;
+
+ unsigned ArgIndex = ST->getIndex();
+ auto TemplateArgs = Specialization->getTemplateArgs().asArray();
+
+ unsigned PointerCount =
+ countPointersInType(Specialization->getDeclContext());
+ for (auto TA : TemplateArgs.take_front(ArgIndex)) {
+ PointerCount += countPointersInType(TA);
+ }
+ unsigned SliceSize = countPointersInType(TemplateArgs[ArgIndex]);
+ return BaseNullabilityAnnotations.slice(PointerCount, SliceSize).vec();
+ });
+}
+
+/// Compute nullability annotations of `T`, which might contain template type
+/// variable substitutions bound by the call `CE`.
+///
+/// Example:
+///
+/// \code
+/// template<typename F, typename S>
+/// std::pair<S, F> flip(std::pair<F, S> p);
+/// \endcode
+///
+/// Consider the following CallExpr:
+///
+/// \code
+/// flip<int * _Nonnull, int * _Nullable>(std::make_pair(&x, &y));
+/// \endcode
+///
+/// This CallExpr has the following substitutions:
+/// F=int * _Nonnull, whose nullability is [_Nonnull]
+/// S=int * _Nullable, whose nullability is [_Nullable]
+///
+/// The return type of this CallExpr is `std::pair<S, F>`.
+///
+/// When we call `substituteNullabilityAnnotationsInFunctionTemplate` with the
+/// type `std::pair<S, F>` and the above CallExpr, it returns the nullability
+/// the given type after applying substitutions, which in this case is
+/// [_Nullable, _Nonnull].
+std::vector<NullabilityKind> substituteNullabilityAnnotationsInFunctionTemplate(
+ QualType T, const CallExpr* CE) {
+ return getNullabilityAnnotationsFromType(
+ T,
+ [&](const SubstTemplateTypeParmType* ST)
+ -> std::optional<std::vector<NullabilityKind>> {
+ // TODO: Handle calls that use template argument deduction.
+ // TODO: Handle nested templates (...->getDepth() > 0).
+ if (auto* DRE =
+ dyn_cast<DeclRefExpr>(CE->getCallee()->IgnoreImpCasts());
+ DRE != nullptr && ST->getReplacedParameter()->getDepth() == 0 &&
+ // Some or all of the template arguments may be deduced, and we
+ // won't see those on the `DeclRefExpr`. If the template argument
+ // was deduced, we don't have any sugar for it.
+ // TODO(b/268348533): Can we somehow obtain it from the function
+ // param it was deduced from?
+ // TODO(b/268345783): This check, as well as the index into
+ // `template_arguments` below, may be incorrect in the presence of
+ // parameters packs. In function templates, parameter packs may
+ // appear anywhere in the parameter list. The index may therefore
+ // refer to one of the pack arguments, but we might incorrectly
+ // interpret it as referring to an argument that follows the pack.
+ ST->getIndex() < DRE->template_arguments().size()) {
+ TypeSourceInfo* TSI =
+ DRE->template_arguments()[ST->getIndex()].getTypeSourceInfo();
+ if (TSI == nullptr) return std::nullopt;
+ return getNullabilityAnnotationsFromType(TSI->getType());
+ }
+ return std::nullopt;
+ });
+}
+
+NullabilityKind getPointerNullability(const Expr* E,
+ PointerNullabilityAnalysis::Lattice& L) {
+ QualType ExprType = E->getType();
+ std::optional<NullabilityKind> Nullability = ExprType->getNullability();
+
+ // If the expression's type does not contain nullability information, it may
+ // be a template instantiation. Look up the nullability in the
+ // `ExprToNullability` map.
+ if (Nullability.value_or(NullabilityKind::Unspecified) ==
+ NullabilityKind::Unspecified) {
+ if (auto MaybeNullability = L.getExprNullability(E)) {
+ if (!MaybeNullability->empty()) {
+ // Return the nullability of the topmost pointer in the type.
+ Nullability = (*MaybeNullability)[0];
+ }
+ }
+ }
+ return Nullability.value_or(NullabilityKind::Unspecified);
+}
+
+void initPointerFromAnnotations(
+ PointerValue& PointerVal, const Expr* E,
+ TransferState<PointerNullabilityLattice>& State) {
+ NullabilityKind Nullability = getPointerNullability(E, State.Lattice);
+ switch (Nullability) {
+ case NullabilityKind::NonNull:
+ initNotNullPointer(PointerVal, State.Env);
+ break;
+ case NullabilityKind::Nullable:
+ initNullablePointer(PointerVal, State.Env);
+ break;
+ default:
+ initUnknownPointer(PointerVal, State.Env);
+ }
+}
+
+void transferFlowSensitiveNullPointer(
+ const Expr* NullPointer, const MatchFinder::MatchResult&,
+ TransferState<PointerNullabilityLattice>& State) {
+ if (auto* PointerVal = getPointerValueFromExpr(NullPointer, State.Env)) {
+ initNullPointer(*PointerVal, State.Env);
+ }
+}
+
+void transferFlowSensitiveNotNullPointer(
+ const Expr* NotNullPointer, const MatchFinder::MatchResult&,
+ TransferState<PointerNullabilityLattice>& State) {
+ if (auto* PointerVal = getPointerValueFromExpr(NotNullPointer, State.Env)) {
+ initNotNullPointer(*PointerVal, State.Env);
+ }
+}
+
+void transferFlowSensitivePointer(
+ const Expr* PointerExpr, const MatchFinder::MatchResult& Result,
+ TransferState<PointerNullabilityLattice>& State) {
+ if (auto* PointerVal = getPointerValueFromExpr(PointerExpr, State.Env)) {
+ initPointerFromAnnotations(*PointerVal, PointerExpr, State);
+ }
+}
+
+// TODO(b/233582219): Implement promotion of nullability knownness for initially
+// unknown pointers when there is evidence that it is nullable, for example
+// when the pointer is compared to nullptr, or casted to boolean.
+void transferFlowSensitiveNullCheckComparison(
+ const BinaryOperator* BinaryOp, const MatchFinder::MatchResult& result,
+ TransferState<PointerNullabilityLattice>& State) {
+ // Boolean representing the comparison between the two pointer values,
+ // automatically created by the dataflow framework.
+ auto& PointerComparison =
+ *cast<BoolValue>(State.Env.getValue(*BinaryOp, SkipPast::None));
+
+ CHECK(BinaryOp->getOpcode() == BO_EQ || BinaryOp->getOpcode() == BO_NE);
+ auto& PointerEQ = BinaryOp->getOpcode() == BO_EQ
+ ? PointerComparison
+ : State.Env.makeNot(PointerComparison);
+ auto& PointerNE = BinaryOp->getOpcode() == BO_EQ
+ ? State.Env.makeNot(PointerComparison)
+ : PointerComparison;
+
+ auto* LHS = getPointerValueFromExpr(BinaryOp->getLHS(), State.Env);
+ auto* RHS = getPointerValueFromExpr(BinaryOp->getRHS(), State.Env);
+
+ if (!LHS || !RHS) return;
+
+ auto [LHSKnown, LHSNull] = getPointerNullState(*LHS, State.Env);
+ auto [RHSKnown, RHSNull] = getPointerNullState(*RHS, State.Env);
+ auto& LHSKnownNotNull =
+ State.Env.makeAnd(LHSKnown, State.Env.makeNot(LHSNull));
+ auto& RHSKnownNotNull =
+ State.Env.makeAnd(RHSKnown, State.Env.makeNot(RHSNull));
+ auto& LHSKnownNull = State.Env.makeAnd(LHSKnown, LHSNull);
+ auto& RHSKnownNull = State.Env.makeAnd(RHSKnown, RHSNull);
+
+ // nullptr == nullptr
+ State.Env.addToFlowCondition(State.Env.makeImplication(
+ State.Env.makeAnd(LHSKnownNull, RHSKnownNull), PointerEQ));
+ // nullptr != notnull
+ State.Env.addToFlowCondition(State.Env.makeImplication(
+ State.Env.makeAnd(LHSKnownNull, RHSKnownNotNull), PointerNE));
+ // notnull != nullptr
+ State.Env.addToFlowCondition(State.Env.makeImplication(
+ State.Env.makeAnd(LHSKnownNotNull, RHSKnownNull), PointerNE));
+}
+
+void transferFlowSensitiveNullCheckImplicitCastPtrToBool(
+ const Expr* CastExpr, const MatchFinder::MatchResult&,
+ TransferState<PointerNullabilityLattice>& State) {
+ auto* PointerVal =
+ getPointerValueFromExpr(CastExpr->IgnoreImplicit(), State.Env);
+ if (!PointerVal) return;
+
+ auto [PointerKnown, PointerNull] =
+ getPointerNullState(*PointerVal, State.Env);
+ auto& CastExprLoc = State.Env.createStorageLocation(*CastExpr);
+ State.Env.setValue(CastExprLoc, State.Env.makeNot(PointerNull));
+ State.Env.setStorageLocation(*CastExpr, CastExprLoc);
+}
+
+void transferFlowSensitiveCallExpr(
+ const CallExpr* CallExpr, const MatchFinder::MatchResult& Result,
+ TransferState<PointerNullabilityLattice>& State) {
+ // The dataflow framework itself does not create values for `CallExpr`s.
+ // However, we need these in some cases, so we produce them ourselves.
+
+ if (CallExpr->getType()->isAnyPointerType()) {
+ // Create a pointer so that we can attach nullability to it and have the
+ // nullability propagate with the pointer.
+ auto* PointerVal = getPointerValueFromExpr(CallExpr, State.Env);
+ if (!PointerVal) {
+ PointerVal =
+ cast<PointerValue>(State.Env.createValue(CallExpr->getType()));
+ auto& CallExprLoc = State.Env.createStorageLocation(*CallExpr);
+ State.Env.setValue(CallExprLoc, *PointerVal);
+ State.Env.setStorageLocation(*CallExpr, CallExprLoc);
+ }
+ initPointerFromAnnotations(*PointerVal, CallExpr, State);
+ } else if (CallExpr->isGLValue()) {
+ // The function returned a reference. Create a storage location for the
+ // expression so that if code creates a pointer from the reference, we will
+ // produce a `PointerValue`.
+ auto* Loc = State.Env.getStorageLocation(*CallExpr, SkipPast::None);
+ if (!Loc) {
+ // This is subtle: We call `createStorageLocation(QualType)`, not
+ // `createStorageLocation(const Expr &)`, so that we create a new
+ // storage location every time.
+ auto& NewLoc = State.Env.createStorageLocation(CallExpr->getType());
+ State.Env.setStorageLocation(*CallExpr, NewLoc);
+ }
+ }
+}
+
+void transferNonFlowSensitiveDeclRefExpr(
+ const DeclRefExpr* DRE, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(DRE, State, [&] {
+ return getNullabilityAnnotationsFromType(DRE->getType());
+ });
+}
+
+void transferNonFlowSensitiveMemberExpr(
+ const MemberExpr* ME, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(ME, State, [&]() {
+ auto BaseNullability = getNullabilityForChild(ME->getBase(), State);
+ QualType MemberType = ME->getType();
+ // When a MemberExpr is a part of a member function call
+ // (a child of CXXMemberCallExpr), the MemberExpr models a
+ // partially-applied member function, which isn't a real C++ construct.
+ // The AST does not provide rich type information for such MemberExprs.
+ // Instead, the AST specifies a placeholder type, specifically
+ // BuiltinType::BoundMember. So we have to look at the type of the member
+ // function declaration.
+ if (ME->hasPlaceholderType(BuiltinType::BoundMember)) {
+ MemberType = ME->getMemberDecl()->getType();
+ }
+ return substituteNullabilityAnnotationsInClassTemplate(
+ MemberType, BaseNullability, ME->getBase()->getType());
+ });
+}
+
+void transferNonFlowSensitiveMemberCallExpr(
+ const CXXMemberCallExpr* MCE, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(MCE, State, [&]() {
+ return getNullabilityForChild(MCE->getCallee(), State)
+ .take_front(countPointersInType(MCE))
+ .vec();
+ });
+}
+
+void transferNonFlowSensitiveCastExpr(
+ const CastExpr* CE, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(CE, State, [&]() -> std::vector<NullabilityKind> {
+ // Most casts that can convert ~unrelated types drop nullability in general.
+ // As a special case, preserve nullability of outer pointer types.
+ // For example, int* p; (void*)p; is a BitCast, but preserves nullability.
+ auto PreserveTopLevelPointers = [&](std::vector<NullabilityKind> V) {
+ auto ArgNullability = getNullabilityForChild(CE->getSubExpr(), State);
+ const PointerType* ArgType = dyn_cast<PointerType>(
+ CE->getSubExpr()->getType().getCanonicalType().getTypePtr());
+ const PointerType* CastType =
+ dyn_cast<PointerType>(CE->getType().getCanonicalType().getTypePtr());
+ for (int I = 0; ArgType && CastType; ++I) {
+ V[I] = ArgNullability[I];
+ ArgType = dyn_cast<PointerType>(ArgType->getPointeeType().getTypePtr());
+ CastType =
+ dyn_cast<PointerType>(CastType->getPointeeType().getTypePtr());
+ }
+ return V;
+ };
+
+ switch (CE->getCastKind()) {
+ // Casts between unrelated types: we can't say anything about nullability.
+ case CK_LValueBitCast:
+ case CK_BitCast:
+ case CK_LValueToRValueBitCast:
+ return PreserveTopLevelPointers(unspecifiedNullability(CE));
+
+ // Casts between equivalent types.
+ case CK_LValueToRValue:
+ case CK_NoOp:
+ case CK_AtomicToNonAtomic:
+ case CK_NonAtomicToAtomic:
+ case CK_AddressSpaceConversion:
+ return getNullabilityForChild(CE->getSubExpr(), State).vec();
+
+ // Controlled conversions between types
+ // TODO: these should be doable somehow
+ case CK_BaseToDerived:
+ case CK_DerivedToBase:
+ case CK_UncheckedDerivedToBase:
+ return PreserveTopLevelPointers(unspecifiedNullability(CE));
+ case CK_UserDefinedConversion:
+ case CK_ConstructorConversion:
+ return unspecifiedNullability(CE);
+
+ case CK_Dynamic: {
+ auto Result = unspecifiedNullability(CE);
+ // A dynamic_cast to pointer is null if the runtime check fails.
+ if (isa<PointerType>(CE->getType().getCanonicalType()))
+ Result.front() = NullabilityKind::Nullable;
+ return Result;
+ }
+
+ // Primitive values have no nullability.
+ case CK_ToVoid:
+ case CK_MemberPointerToBoolean:
+ case CK_PointerToBoolean:
+ case CK_PointerToIntegral:
+ case CK_IntegralCast:
+ case CK_IntegralToBoolean:
+ case CK_IntegralToFloating:
+ case CK_FloatingToFixedPoint:
+ case CK_FixedPointToFloating:
+ case CK_FixedPointCast:
+ case CK_FixedPointToIntegral:
+ case CK_IntegralToFixedPoint:
+ case CK_FixedPointToBoolean:
+ case CK_FloatingToIntegral:
+ case CK_FloatingToBoolean:
+ case CK_BooleanToSignedIntegral:
+ case CK_FloatingCast:
+ case CK_FloatingRealToComplex:
+ case CK_FloatingComplexToReal:
+ case CK_FloatingComplexToBoolean:
+ case CK_FloatingComplexCast:
+ case CK_FloatingComplexToIntegralComplex:
+ case CK_IntegralRealToComplex:
+ case CK_IntegralComplexToReal:
+ case CK_IntegralComplexToBoolean:
+ case CK_IntegralComplexCast:
+ case CK_IntegralComplexToFloatingComplex:
+ return {};
+
+ // This can definitely be null!
+ case CK_NullToPointer: {
+ auto Nullability = getNullabilityAnnotationsFromType(CE->getType());
+ Nullability.front() = NullabilityKind::Nullable;
+ return Nullability;
+ }
+
+ // Pointers out of thin air, who knows?
+ case CK_IntegralToPointer:
+ return unspecifiedNullability(CE);
+
+ // Decayed objects are never null.
+ case CK_ArrayToPointerDecay:
+ case CK_FunctionToPointerDecay:
+ case CK_BuiltinFnToFnPtr:
+ return prepend(NullabilityKind::NonNull,
+ getNullabilityForChild(CE->getSubExpr(), State));
+
+ // TODO: what is our model of member pointers?
+ case CK_BaseToDerivedMemberPointer:
+ case CK_DerivedToBaseMemberPointer:
+ case CK_NullToMemberPointer:
+ case CK_ReinterpretMemberPointer:
+ case CK_ToUnion: // and unions?
+ return unspecifiedNullability(CE);
+
+ // TODO: Non-C/C++ constructs, do we care about these?
+ case CK_CPointerToObjCPointerCast:
+ case CK_ObjCObjectLValueCast:
+ case CK_MatrixCast:
+ case CK_VectorSplat:
+ case CK_BlockPointerToObjCPointerCast:
+ case CK_AnyPointerToBlockPointerCast:
+ case CK_ARCProduceObject:
+ case CK_ARCConsumeObject:
+ case CK_ARCReclaimReturnedObject:
+ case CK_ARCExtendBlockObject:
+ case CK_CopyAndAutoreleaseBlockObject:
+ case CK_ZeroToOCLOpaqueType:
+ case CK_IntToOCLSampler:
+ return unspecifiedNullability(CE);
+
+ case CK_Dependent:
+ CHECK(false) << "Shouldn't see dependent casts here?";
+ }
+ });
+}
+
+void transferNonFlowSensitiveMaterializeTemporaryExpr(
+ const MaterializeTemporaryExpr* MTE, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(MTE, State, [&]() {
+ return getNullabilityForChild(MTE->getSubExpr(), State).vec();
+ });
+}
+
+void transferNonFlowSensitiveCallExpr(
+ const CallExpr* CE, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ // TODO: Check CallExpr arguments in the diagnoser against the nullability of
+ // parameters.
+ computeNullability(CE, State, [&]() {
+ // TODO(mboehme): Instead of relying on Clang to propagate nullability sugar
+ // to the `CallExpr`'s type, we should extract nullability directly from the
+ // callee `Expr .
+ return substituteNullabilityAnnotationsInFunctionTemplate(CE->getType(),
+ CE);
+ });
+}
+
+void transferNonFlowSensitiveUnaryOperator(
+ const UnaryOperator* UO, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(UO, State, [&]() -> std::vector<NullabilityKind> {
+ switch (UO->getOpcode()) {
+ case UO_AddrOf:
+ return prepend(NullabilityKind::NonNull,
+ getNullabilityForChild(UO->getSubExpr(), State));
+ case UO_Deref:
+ return getNullabilityForChild(UO->getSubExpr(), State)
+ .drop_front()
+ .vec();
+
+ case UO_PostInc:
+ case UO_PostDec:
+ case UO_PreInc:
+ case UO_PreDec:
+ case UO_Plus:
+ case UO_Minus:
+ case UO_Not:
+ case UO_LNot:
+ case UO_Real:
+ case UO_Imag:
+ case UO_Extension:
+ return getNullabilityForChild(UO->getSubExpr(), State);
+
+ case UO_Coawait:
+ // TODO: work out what to do here!
+ return unspecifiedNullability(UO);
+ }
+ });
+}
+
+void transferNonFlowSensitiveNewExpr(
+ const CXXNewExpr* NE, const MatchFinder::MatchResult& MR,
+ TransferState<PointerNullabilityLattice>& State) {
+ computeNullability(NE, State, [&]() {
+ std::vector<NullabilityKind> result =
+ getNullabilityAnnotationsFromType(NE->getType());
+ result.front() = NE->shouldNullCheckAllocation() ? NullabilityKind::Nullable
+ : NullabilityKind::NonNull;
+ return result;
+ });
+}
+
+auto buildNonFlowSensitiveTransferer() {
+ return CFGMatchSwitchBuilder<TransferState<PointerNullabilityLattice>>()
+ .CaseOfCFGStmt<DeclRefExpr>(ast_matchers::declRefExpr(),
+ transferNonFlowSensitiveDeclRefExpr)
+ .CaseOfCFGStmt<MemberExpr>(ast_matchers::memberExpr(),
+ transferNonFlowSensitiveMemberExpr)
+ .CaseOfCFGStmt<CXXMemberCallExpr>(ast_matchers::cxxMemberCallExpr(),
+ transferNonFlowSensitiveMemberCallExpr)
+ .CaseOfCFGStmt<CastExpr>(ast_matchers::castExpr(),
+ transferNonFlowSensitiveCastExpr)
+ .CaseOfCFGStmt<MaterializeTemporaryExpr>(
+ ast_matchers::materializeTemporaryExpr(),
+ transferNonFlowSensitiveMaterializeTemporaryExpr)
+ .CaseOfCFGStmt<CallExpr>(ast_matchers::callExpr(),
+ transferNonFlowSensitiveCallExpr)
+ .CaseOfCFGStmt<UnaryOperator>(ast_matchers::unaryOperator(),
+ transferNonFlowSensitiveUnaryOperator)
+ .CaseOfCFGStmt<CXXNewExpr>(ast_matchers::cxxNewExpr(),
+ transferNonFlowSensitiveNewExpr)
+ .Build();
+}
+
+auto buildFlowSensitiveTransferer() {
+ return CFGMatchSwitchBuilder<TransferState<PointerNullabilityLattice>>()
+ // Handles initialization of the null states of pointers.
+ .CaseOfCFGStmt<Expr>(isCXXThisExpr(), transferFlowSensitiveNotNullPointer)
+ .CaseOfCFGStmt<Expr>(isAddrOf(), transferFlowSensitiveNotNullPointer)
+ .CaseOfCFGStmt<Expr>(isNullPointerLiteral(),
+ transferFlowSensitiveNullPointer)
+ .CaseOfCFGStmt<CallExpr>(isCallExpr(), transferFlowSensitiveCallExpr)
+ .CaseOfCFGStmt<Expr>(isPointerExpr(), transferFlowSensitivePointer)
+ // Handles comparison between 2 pointers.
+ .CaseOfCFGStmt<BinaryOperator>(isPointerCheckBinOp(),
+ transferFlowSensitiveNullCheckComparison)
+ // Handles checking of pointer as boolean.
+ .CaseOfCFGStmt<Expr>(isImplicitCastPointerToBool(),
+ transferFlowSensitiveNullCheckImplicitCastPtrToBool)
+ .Build();
+}
+} // namespace
+
+PointerNullabilityAnalysis::PointerNullabilityAnalysis(ASTContext& Context)
+ : DataflowAnalysis<PointerNullabilityAnalysis, PointerNullabilityLattice>(
+ Context),
+ NonFlowSensitiveTransferer(buildNonFlowSensitiveTransferer()),
+ FlowSensitiveTransferer(buildFlowSensitiveTransferer()) {}
+
+void PointerNullabilityAnalysis::transfer(const CFGElement& Elt,
+ PointerNullabilityLattice& Lattice,
+ Environment& Env) {
+ TransferState<PointerNullabilityLattice> State(Lattice, Env);
+ NonFlowSensitiveTransferer(Elt, getASTContext(), State);
+ FlowSensitiveTransferer(Elt, getASTContext(), State);
+}
+
+BoolValue& mergeBoolValues(BoolValue& Bool1, const Environment& Env1,
+ BoolValue& Bool2, const Environment& Env2,
+ Environment& MergedEnv) {
+ if (&Bool1 == &Bool2) {
+ return Bool1;
+ }
+
+ auto& MergedBool = MergedEnv.makeAtomicBoolValue();
+
+ // If `Bool1` and `Bool2` is constrained to the same true / false value,
+ // `MergedBool` can be constrained similarly without needing to consider the
+ // path taken - this simplifies the flow condition tracked in `MergedEnv`.
+ // Otherwise, information about which path was taken is used to associate
+ // `MergedBool` with `Bool1` and `Bool2`.
+ if (Env1.flowConditionImplies(Bool1) && Env2.flowConditionImplies(Bool2)) {
+ MergedEnv.addToFlowCondition(MergedBool);
+ } else if (Env1.flowConditionImplies(Env1.makeNot(Bool1)) &&
+ Env2.flowConditionImplies(Env2.makeNot(Bool2))) {
+ MergedEnv.addToFlowCondition(MergedEnv.makeNot(MergedBool));
+ } else {
+ // TODO(b/233582219): Flow conditions are not necessarily mutually
+ // exclusive, a fix is in order: https://reviews.llvm.org/D130270. Update
+ // this section when the patch is commited.
+ auto& FC1 = Env1.getFlowConditionToken();
+ auto& FC2 = Env2.getFlowConditionToken();
+ MergedEnv.addToFlowCondition(MergedEnv.makeOr(
+ MergedEnv.makeAnd(FC1, MergedEnv.makeIff(MergedBool, Bool1)),
+ MergedEnv.makeAnd(FC2, MergedEnv.makeIff(MergedBool, Bool2))));
+ }
+ return MergedBool;
+}
+
+bool PointerNullabilityAnalysis::merge(QualType Type, const Value& Val1,
+ const Environment& Env1,
+ const Value& Val2,
+ const Environment& Env2,
+ Value& MergedVal,
+ Environment& MergedEnv) {
+ if (!Type->isAnyPointerType()) {
+ return false;
+ }
+
+ auto [Known1, Null1] = getPointerNullState(cast<PointerValue>(Val1), Env1);
+ auto [Known2, Null2] = getPointerNullState(cast<PointerValue>(Val2), Env2);
+
+ auto& Known = mergeBoolValues(Known1, Env1, Known2, Env2, MergedEnv);
+ auto& Null = mergeBoolValues(Null1, Env1, Null2, Env2, MergedEnv);
+
+ initPointerNullState(cast<PointerValue>(MergedVal), MergedEnv, &Known, &Null);
+
+ return true;
+}
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/pointer_nullability_analysis.h b/nullability/pointer_nullability_analysis.h
new file mode 100644
index 0000000..0b88497
--- /dev/null
+++ b/nullability/pointer_nullability_analysis.h
@@ -0,0 +1,57 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_NULLABILITY_POINTER_NULLABILITY_ANALYSIS_H_
+#define CRUBIT_NULLABILITY_POINTER_NULLABILITY_ANALYSIS_H_
+
+#include "nullability/pointer_nullability_lattice.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Type.h"
+#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/Value.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+/// Analyses constructs in the source code to collect nullability information
+/// about pointers at each program point.
+class PointerNullabilityAnalysis
+ : public dataflow::DataflowAnalysis<PointerNullabilityAnalysis,
+ PointerNullabilityLattice> {
+ private:
+ absl::flat_hash_map<const Expr*, std::vector<NullabilityKind>>
+ ExprToNullability;
+
+ public:
+ explicit PointerNullabilityAnalysis(ASTContext& context);
+
+ PointerNullabilityLattice initialElement() {
+ return PointerNullabilityLattice(ExprToNullability);
+ }
+
+ void transfer(const CFGElement& Elt, PointerNullabilityLattice& Lattice,
+ dataflow::Environment& Env);
+
+ bool merge(QualType Type, const dataflow::Value& Val1,
+ const dataflow::Environment& Env1, const dataflow::Value& Val2,
+ const dataflow::Environment& Env2, dataflow::Value& MergedVal,
+ dataflow::Environment& MergedEnv) override;
+
+ private:
+ // Applies non-flow-sensitive transfer functions on statements
+ dataflow::CFGMatchSwitch<dataflow::TransferState<PointerNullabilityLattice>>
+ NonFlowSensitiveTransferer;
+
+ // Applies flow-sensitive transfer functions on statements
+ dataflow::CFGMatchSwitch<dataflow::TransferState<PointerNullabilityLattice>>
+ FlowSensitiveTransferer;
+};
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // CRUBIT_NULLABILITY_POINTER_NULLABILITY_ANALYSIS_H_
diff --git a/nullability/pointer_nullability_diagnosis.cc b/nullability/pointer_nullability_diagnosis.cc
new file mode 100644
index 0000000..8289142
--- /dev/null
+++ b/nullability/pointer_nullability_diagnosis.cc
@@ -0,0 +1,312 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/pointer_nullability_diagnosis.h"
+
+#include <optional>
+#include <string>
+
+#include "nullability/pointer_nullability.h"
+#include "nullability/pointer_nullability_matchers.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/Stmt.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Basic/Specifiers.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using ast_matchers::MatchFinder;
+using dataflow::CFGMatchSwitchBuilder;
+using dataflow::Environment;
+using dataflow::TransferStateForDiagnostics;
+
+namespace {
+
+// Returns true if `Expr` is uninterpreted or known to be nullable.
+bool isNullableOrUntracked(const Expr* E, const Environment& Env) {
+ auto* ActualVal = getPointerValueFromExpr(E, Env);
+ if (ActualVal == nullptr) {
+ llvm::dbgs()
+ << "The dataflow analysis framework does not model a PointerValue for "
+ "the following Expr, and thus its dereference is marked as "
+ "unsafe:\n";
+ E->dump();
+ }
+ return !ActualVal || isNullable(*ActualVal, Env);
+}
+
+// Returns true if an uninterpreted or nullable `Expr` was assigned to a
+// construct with a non-null `DeclaredType`.
+bool isIncompatibleAssignment(QualType DeclaredType, const Expr* E,
+ const Environment& Env, ASTContext& Ctx) {
+ CHECK(DeclaredType->isAnyPointerType());
+ return getNullabilityKind(DeclaredType, Ctx) == NullabilityKind::NonNull &&
+ isNullableOrUntracked(E, Env);
+}
+
+std::optional<CFGElement> diagnoseDereference(
+ const UnaryOperator* UnaryOp, const MatchFinder::MatchResult&,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State) {
+ if (isNullableOrUntracked(UnaryOp->getSubExpr(), State.Env)) {
+ return std::optional<CFGElement>(CFGStmt(UnaryOp));
+ }
+ return std::nullopt;
+}
+
+std::optional<CFGElement> diagnoseArrow(
+ const MemberExpr* MemberExpr, const MatchFinder::MatchResult& Result,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State) {
+ if (isNullableOrUntracked(MemberExpr->getBase(), State.Env)) {
+ return std::optional<CFGElement>(CFGStmt(MemberExpr));
+ }
+ return std::nullopt;
+}
+
+bool isIncompatibleArgumentList(ArrayRef<QualType> ParamTypes,
+ ArrayRef<const Expr*> Args,
+ const Environment& Env, ASTContext& Ctx) {
+ CHECK_EQ(ParamTypes.size(), Args.size());
+ for (unsigned int I = 0; I < Args.size(); ++I) {
+ auto ParamType = ParamTypes[I].getNonReferenceType();
+ if (!ParamType->isAnyPointerType()) {
+ continue;
+ }
+ if (isIncompatibleAssignment(ParamType, Args[I], Env, Ctx)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+NullabilityKind parseNullabilityKind(StringRef EnumName) {
+ return llvm::StringSwitch<NullabilityKind>(EnumName)
+ .Case("NK_nonnull", NullabilityKind::NonNull)
+ .Case("NK_nullable", NullabilityKind::Nullable)
+ .Case("NK_unspecified", NullabilityKind::Unspecified)
+ .Default(NullabilityKind::Unspecified);
+}
+
+/// Evaluates the `__assert_nullability` call by comparing the expected
+/// nullability to the nullability computed by the dataflow analysis.
+///
+/// If the function being diagnosed is called `__assert_nullability`, we assume
+/// it is a call of the shape __assert_nullability<a, b, c, ...>(p), where `p`
+/// is an expression that contains pointers and a, b, c ... represent each of
+/// the NullabilityKinds in `p`'s expected nullability. An expression's
+/// nullability can be expressed as a vector of NullabilityKinds, where each
+/// vector element corresponds to one of the pointers contained in the
+/// expression.
+///
+/// For example:
+/// \code
+/// enum NullabilityKind {
+/// NK_nonnull,
+/// NK_nullable,
+/// NK_unspecified,
+/// };
+///
+/// template<NullabilityKind ...NK, typename T>
+/// void __assert_nullability(T&);
+///
+/// template<typename T0, typename T1>
+/// struct Struct2Arg {
+/// T0 arg0;
+/// T1 arg1;
+/// };
+///
+/// void target(Struct2Arg<int *, int * _Nullable> p) {
+/// __assert_nullability<NK_unspecified, NK_nullable>(p);
+/// }
+/// \endcode
+bool diagnoseAssertNullabilityCall(
+ const CallExpr* CE,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State,
+ ASTContext& Ctx) {
+ auto* DRE = cast<DeclRefExpr>(CE->getCallee()->IgnoreImpCasts());
+
+ // Extract the expected nullability from the template parameter pack.
+ std::vector<NullabilityKind> Expected;
+ for (auto P : DRE->template_arguments()) {
+ if (P.getArgument().getKind() == TemplateArgument::Expression) {
+ if (auto* EnumDRE = dyn_cast<DeclRefExpr>(P.getSourceExpression())) {
+ Expected.push_back(parseNullabilityKind(EnumDRE->getDecl()->getName()));
+ }
+ }
+ }
+
+ // Compare the nullability computed by nullability analysis with the
+ // expected one.
+ const Expr* GivenExpr = CE->getArg(0);
+ std::optional<ArrayRef<NullabilityKind>> MaybeComputed =
+ State.Lattice.getExprNullability(GivenExpr);
+ if (!MaybeComputed.has_value()) {
+ llvm::dbgs()
+ << "Could not evaluate __assert_nullability. Could not find the "
+ "nullability of the argument expression: ";
+ CE->dump();
+ return false;
+ }
+ if (MaybeComputed->vec() == Expected) return true;
+ // The computed and expected nullabilities differ. Print both to aid
+ // debugging.
+ llvm::dbgs() << "__assert_nullability failed at location: ";
+ CE->getExprLoc().print(llvm::dbgs(), Ctx.getSourceManager());
+ llvm::dbgs() << "\nExpression:\n";
+ GivenExpr->dump();
+ llvm::dbgs() << "Expected nullability: ";
+ llvm::dbgs() << nullabilityToString(Expected) << "\n";
+ llvm::dbgs() << "Computed nullability: ";
+ llvm::dbgs() << nullabilityToString(*MaybeComputed) << "\n";
+ return false;
+}
+
+std::optional<CFGElement> diagnoseCallExpr(
+ const CallExpr* CE, const MatchFinder::MatchResult& Result,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State) {
+ // Emit a warning for a nullable callee. We don't do this for member functions
+ // because in this case the callee can't be null. If we're calling a
+ // pointer-to-member-function, the callee is a `.*` or `->*` `BinaryOperator`,
+ // which itself can never be null. A nullable pointer-to-member-function will
+ // manifest as a nullable RHS of this `BinaryOperator` and should be diagnosed
+ // there.
+ if (!isa<CXXMemberCallExpr>(CE) &&
+ isNullableOrUntracked(CE->getCallee(), State.Env)) {
+ return std::optional<CFGElement>(CFGStmt(CE->getCallee()));
+ }
+
+ if (auto* FD = CE->getDirectCallee()) {
+ if (FD->getDeclName().isIdentifier() &&
+ FD->getName() == "__assert_nullability" &&
+ !diagnoseAssertNullabilityCall(CE, State, *Result.Context)) {
+ // TODO: Handle __assert_nullability failures differently from regular
+ // diagnostic ([[unsafe]]) failures.
+ return std::optional<CFGElement>(CFGStmt(CE));
+ }
+ }
+
+ auto* Callee = CE->getCalleeDecl();
+ // TODO(mboehme): Retrieve the nullability directly from the callee using
+ // `getNullabilityForChild(CE->getCallee())`, as what we have here now
+ // doesn't work for callees that don't have a decl.
+ if (!Callee) return std::nullopt;
+
+ auto* CalleeType = Callee->getFunctionType();
+ if (!CalleeType) return std::nullopt;
+
+ // TODO(mboehme): We're only looking at the nullability spelled on the
+ // `FunctionProtoType`, but there could be extra information in the callee.
+ // An example (due to sammccall@):
+ //
+ // template <typename T> struct Sink {
+ // static void eat(T) { ... }
+ // }
+ // void target(Sink<Nonnull<int*>> &S) {
+ // S<Nonnull<int*>>::eat(nullptr); // no warning
+ // // callee is instantiated Sink<int*>::eat(int*)
+ // // however nullability vector of DRE S::eat should be [Nonnull]
+ // // (not sure if it is today)
+ // }
+ auto* CalleeFPT = CalleeType->getAs<FunctionProtoType>();
+ if (!CalleeFPT) return std::nullopt;
+
+ auto ParamTypes = CalleeFPT->getParamTypes();
+ ArrayRef<const Expr*> Args(CE->getArgs(), CE->getNumArgs());
+ // The first argument of an member operator call expression is the implicit
+ // object argument, which does not appear in the list of parameter types.
+ // Note that operator calls always have a direct callee.
+ if (isa<CXXOperatorCallExpr>(CE) &&
+ isa<CXXMethodDecl>(CE->getDirectCallee())) {
+ Args = Args.drop_front();
+ }
+ if (CalleeFPT->isVariadic()) {
+ CHECK_GE(Args.size(), ParamTypes.size());
+ Args = Args.take_front(ParamTypes.size());
+ }
+
+ return isIncompatibleArgumentList(ParamTypes, Args, State.Env,
+ *Result.Context)
+ ? std::optional<CFGElement>(CFGStmt(CE))
+ : std::nullopt;
+}
+
+std::optional<CFGElement> diagnoseConstructExpr(
+ const CXXConstructExpr* CE, const MatchFinder::MatchResult& Result,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State) {
+ auto ConstructorParamTypes = CE->getConstructor()
+ ->getType()
+ ->getAs<FunctionProtoType>()
+ ->getParamTypes();
+ ArrayRef<const Expr*> ConstructorArgs(CE->getArgs(), CE->getNumArgs());
+ return isIncompatibleArgumentList(ConstructorParamTypes, ConstructorArgs,
+ State.Env, *Result.Context)
+ ? std::optional<CFGElement>(CFGStmt(CE))
+ : std::nullopt;
+}
+
+std::optional<CFGElement> diagnoseReturn(
+ const ReturnStmt* RS, const MatchFinder::MatchResult& Result,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State) {
+ auto ReturnType = cast<FunctionDecl>(State.Env.getDeclCtx())->getReturnType();
+
+ // TODO: Handle non-pointer return types.
+ if (!ReturnType->isPointerType()) {
+ return std::nullopt;
+ }
+
+ auto* ReturnExpr = RS->getRetValue();
+ CHECK(ReturnExpr->getType()->isPointerType());
+
+ return isIncompatibleAssignment(ReturnType, ReturnExpr, State.Env,
+ *Result.Context)
+ ? std::optional<CFGElement>(CFGStmt(RS))
+ : std::nullopt;
+}
+
+std::optional<CFGElement> diagnoseMemberInitializer(
+ const CXXCtorInitializer* CI, const MatchFinder::MatchResult& Result,
+ const TransferStateForDiagnostics<PointerNullabilityLattice>& State) {
+ CHECK(CI->isAnyMemberInitializer());
+ auto MemberType = CI->getAnyMember()->getType();
+ if (!MemberType->isAnyPointerType()) {
+ return std::nullopt;
+ }
+ auto MemberInitExpr = CI->getInit();
+ return isIncompatibleAssignment(MemberType, MemberInitExpr, State.Env,
+ *Result.Context)
+ ? std::optional<CFGElement>(CFGInitializer(CI))
+ : std::nullopt;
+}
+
+auto buildDiagnoser() {
+ return CFGMatchSwitchBuilder<const dataflow::TransferStateForDiagnostics<
+ PointerNullabilityLattice>,
+ std::optional<CFGElement>>()
+ // (*)
+ .CaseOfCFGStmt<UnaryOperator>(isPointerDereference(), diagnoseDereference)
+ // (->)
+ .CaseOfCFGStmt<MemberExpr>(isPointerArrow(), diagnoseArrow)
+ // Check compatibility of parameter assignments
+ .CaseOfCFGStmt<CallExpr>(isCallExpr(), diagnoseCallExpr)
+ .CaseOfCFGStmt<ReturnStmt>(isPointerReturn(), diagnoseReturn)
+ .CaseOfCFGStmt<CXXConstructExpr>(isConstructExpr(), diagnoseConstructExpr)
+ .CaseOfCFGInit<CXXCtorInitializer>(isCtorMemberInitializer(),
+ diagnoseMemberInitializer)
+ .Build();
+}
+
+} // namespace
+
+PointerNullabilityDiagnoser::PointerNullabilityDiagnoser()
+ : Diagnoser(buildDiagnoser()) {}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/pointer_nullability_diagnosis.h b/nullability/pointer_nullability_diagnosis.h
new file mode 100644
index 0000000..57cf6a1
--- /dev/null
+++ b/nullability/pointer_nullability_diagnosis.h
@@ -0,0 +1,53 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_NULLABILITY_POINTER_NULLABILITY_DIAGNOSIS_H_
+#define CRUBIT_NULLABILITY_POINTER_NULLABILITY_DIAGNOSIS_H_
+
+#include <optional>
+
+#include "nullability/pointer_nullability_lattice.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Stmt.h"
+#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
+#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+/// Checks that nullable pointers are used safely, using nullability information
+/// that is collected by `PointerNullabilityAnalysis`.
+///
+/// Examples of null safety violations include dereferencing nullable pointers
+/// without null checks, and assignments between pointers of incompatible
+/// nullability.
+class PointerNullabilityDiagnoser {
+ public:
+ PointerNullabilityDiagnoser();
+
+ /// Returns the pointer to the statement if null safety is violated, otherwise
+ /// the optional is empty.
+ ///
+ /// TODO(b/233582219): Extend diagnosis to return more information, e.g. the
+ /// type of violation.
+ std::optional<CFGElement> diagnose(
+ const CFGElement* Elt, ASTContext& Ctx,
+ const dataflow::TransferStateForDiagnostics<PointerNullabilityLattice>&
+ State) {
+ return Diagnoser(*Elt, Ctx, State);
+ }
+
+ private:
+ dataflow::CFGMatchSwitch<
+ const dataflow::TransferStateForDiagnostics<PointerNullabilityLattice>,
+ std::optional<CFGElement>>
+ Diagnoser;
+};
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // CRUBIT_NULLABILITY_POINTER_NULLABILITY_DIAGNOSIS_H_
diff --git a/nullability/pointer_nullability_lattice.h b/nullability/pointer_nullability_lattice.h
new file mode 100644
index 0000000..66222c5
--- /dev/null
+++ b/nullability/pointer_nullability_lattice.h
@@ -0,0 +1,75 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef THIRD_PARTY_CRUBIT_NULLABILITY_POINTER_NULLABILITY_LATTICE_H_
+#define THIRD_PARTY_CRUBIT_NULLABILITY_POINTER_NULLABILITY_LATTICE_H_
+
+#include <optional>
+#include <ostream>
+
+#include "absl/container/flat_hash_map.h"
+#include "absl/log/check.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
+#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
+#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+class PointerNullabilityLattice {
+ private:
+ // Owned by the PointerNullabilityAnalysis object, shared by all lattice
+ // elements within one analysis run.
+ absl::flat_hash_map<const Expr *, std::vector<NullabilityKind>>
+ &ExprToNullability;
+
+ public:
+ PointerNullabilityLattice(
+ absl::flat_hash_map<const Expr *, std::vector<NullabilityKind>>
+ &ExprToNullability)
+ : ExprToNullability(ExprToNullability) {}
+
+ std::optional<ArrayRef<NullabilityKind>> getExprNullability(
+ const Expr *E) const {
+ auto I = ExprToNullability.find(&dataflow::ignoreCFGOmittedNodes(*E));
+ return I == ExprToNullability.end()
+ ? std::nullopt
+ : std::optional<ArrayRef<NullabilityKind>>(I->second);
+ }
+
+ // If the `ExprToNullability` map already contains an entry for `E`, does
+ // nothing. Otherwise, inserts a new entry with key `E` and value computed by
+ // the provided GetNullability.
+ // Returns the (cached or computed) nullability.
+ ArrayRef<NullabilityKind> insertExprNullabilityIfAbsent(
+ const Expr *E,
+ const std::function<std::vector<NullabilityKind>()> &GetNullability) {
+ E = &dataflow::ignoreCFGOmittedNodes(*E);
+ if (auto It = ExprToNullability.find(E); It != ExprToNullability.end())
+ return It->second;
+ // Deliberately perform a separate lookup after calling GetNullability.
+ // It may invalidate iterators, e.g. inserting missing vectors for children.
+ auto [Iterator, Inserted] = ExprToNullability.insert({E, GetNullability()});
+ CHECK(Inserted) << "GetNullability inserted same " << E->getStmtClassName();
+ return Iterator->second;
+ }
+
+ bool operator==(const PointerNullabilityLattice &Other) const { return true; }
+
+ dataflow::LatticeJoinEffect join(const PointerNullabilityLattice &Other) {
+ return dataflow::LatticeJoinEffect::Unchanged;
+ }
+};
+
+inline std::ostream &operator<<(std::ostream &OS,
+ const PointerNullabilityLattice &) {
+ return OS << "noop";
+}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // THIRD_PARTY_CRUBIT_NULLABILITY_POINTER_NULLABILITY_LATTICE_H_
diff --git a/nullability/pointer_nullability_matchers.cc b/nullability/pointer_nullability_matchers.cc
new file mode 100644
index 0000000..e2416c5
--- /dev/null
+++ b/nullability/pointer_nullability_matchers.cc
@@ -0,0 +1,70 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/pointer_nullability_matchers.h"
+
+#include "clang/AST/OperationKinds.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+using ast_matchers::anyOf;
+using ast_matchers::binaryOperator;
+using ast_matchers::callExpr;
+using ast_matchers::cxxConstructExpr;
+using ast_matchers::cxxCtorInitializer;
+using ast_matchers::cxxThisExpr;
+using ast_matchers::declRefExpr;
+using ast_matchers::expr;
+using ast_matchers::hasAnyOperatorName;
+using ast_matchers::hasCastKind;
+using ast_matchers::hasOperands;
+using ast_matchers::hasOperatorName;
+using ast_matchers::hasReturnValue;
+using ast_matchers::hasType;
+using ast_matchers::hasUnaryOperand;
+using ast_matchers::implicitCastExpr;
+using ast_matchers::isAnyPointer;
+using ast_matchers::isArrow;
+using ast_matchers::isMemberInitializer;
+using ast_matchers::memberExpr;
+using ast_matchers::returnStmt;
+using ast_matchers::unaryOperator;
+using ast_matchers::internal::Matcher;
+
+Matcher<Stmt> isPointerExpr() { return expr(hasType(isAnyPointer())); }
+Matcher<Stmt> isNullPointerLiteral() {
+ return implicitCastExpr(anyOf(hasCastKind(CK_NullToPointer),
+ hasCastKind(CK_NullToMemberPointer)));
+}
+Matcher<Stmt> isAddrOf() { return unaryOperator(hasOperatorName("&")); }
+Matcher<Stmt> isPointerDereference() {
+ return unaryOperator(hasOperatorName("*"), hasUnaryOperand(isPointerExpr()));
+}
+Matcher<Stmt> isPointerCheckBinOp() {
+ return binaryOperator(hasAnyOperatorName("!=", "=="),
+ hasOperands(isPointerExpr(), isPointerExpr()));
+}
+Matcher<Stmt> isImplicitCastPointerToBool() {
+ return implicitCastExpr(hasCastKind(CK_PointerToBoolean));
+}
+Matcher<Stmt> isMemberOfPointerType() {
+ return memberExpr(hasType(isAnyPointer()));
+}
+Matcher<Stmt> isPointerArrow() { return memberExpr(isArrow()); }
+Matcher<Stmt> isCXXThisExpr() { return cxxThisExpr(); }
+Matcher<Stmt> isCallExpr() { return callExpr(); }
+Matcher<Stmt> isPointerReturn() {
+ return returnStmt(hasReturnValue(hasType(isAnyPointer())));
+}
+Matcher<Stmt> isConstructExpr() { return cxxConstructExpr(); }
+Matcher<CXXCtorInitializer> isCtorMemberInitializer() {
+ return cxxCtorInitializer(isMemberInitializer());
+}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/pointer_nullability_matchers.h b/nullability/pointer_nullability_matchers.h
new file mode 100644
index 0000000..353bbe8
--- /dev/null
+++ b/nullability/pointer_nullability_matchers.h
@@ -0,0 +1,32 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CRUBIT_NULLABILITY_POINTER_NULLABILITY_MATCHERS_H_
+#define CRUBIT_NULLABILITY_POINTER_NULLABILITY_MATCHERS_H_
+
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+ast_matchers::internal::Matcher<Stmt> isPointerExpr();
+ast_matchers::internal::Matcher<Stmt> isMemberOfPointerType();
+ast_matchers::internal::Matcher<Stmt> isPointerArrow();
+ast_matchers::internal::Matcher<Stmt> isCXXThisExpr();
+ast_matchers::internal::Matcher<Stmt> isNullPointerLiteral();
+ast_matchers::internal::Matcher<Stmt> isAddrOf();
+ast_matchers::internal::Matcher<Stmt> isPointerDereference();
+ast_matchers::internal::Matcher<Stmt> isPointerCheckBinOp();
+ast_matchers::internal::Matcher<Stmt> isImplicitCastPointerToBool();
+ast_matchers::internal::Matcher<Stmt> isCallExpr();
+ast_matchers::internal::Matcher<Stmt> isPointerReturn();
+ast_matchers::internal::Matcher<Stmt> isConstructExpr();
+ast_matchers::internal::Matcher<CXXCtorInitializer> isCtorMemberInitializer();
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // CRUBIT_NULLABILITY_POINTER_NULLABILITY_MATCHERS_H_
diff --git a/nullability/pointer_nullability_test.cc b/nullability/pointer_nullability_test.cc
new file mode 100644
index 0000000..89a6bbc
--- /dev/null
+++ b/nullability/pointer_nullability_test.cc
@@ -0,0 +1,288 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/pointer_nullability.h"
+
+#include "clang/Testing/TestAST.h"
+#include "llvm/ADT/StringRef.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googlemock/include/gmock/gmock.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang::tidy::nullability {
+namespace {
+using testing::ElementsAre;
+
+class GetNullabilityAnnotationsFromTypeTest : public ::testing::Test {
+ protected:
+ // C++ declarations prepended before parsing type in nullVec().
+ std::string Preamble;
+
+ // Parses `Type` and returns getNullabilityAnnotationsFromType().
+ std::vector<NullabilityKind> nullVec(llvm::StringRef Type) {
+ clang::TestAST AST((Preamble + "\nusing Target = " + Type + ";").str());
+ auto Target = AST.context().getTranslationUnitDecl()->lookup(
+ &AST.context().Idents.get("Target"));
+ CHECK(Target.isSingleResult());
+ QualType TargetType =
+ AST.context().getTypedefType(Target.find_first<TypeAliasDecl>());
+ return getNullabilityAnnotationsFromType(TargetType);
+ }
+};
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, Pointers) {
+ EXPECT_THAT(nullVec("int"), ElementsAre());
+ EXPECT_THAT(nullVec("int *"), ElementsAre(NullabilityKind::Unspecified));
+ EXPECT_THAT(nullVec("int **"), ElementsAre(NullabilityKind::Unspecified,
+ NullabilityKind::Unspecified));
+ EXPECT_THAT(nullVec("int *_Nullable*_Nonnull"),
+ ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, Sugar) {
+ Preamble = "using X = int* _Nonnull;";
+
+ EXPECT_THAT(nullVec("X"), ElementsAre(NullabilityKind::NonNull));
+ EXPECT_THAT(nullVec("X*"), ElementsAre(NullabilityKind::Unspecified,
+ NullabilityKind::NonNull));
+
+ EXPECT_THAT(nullVec("X(*)"), ElementsAre(NullabilityKind::Unspecified,
+ NullabilityKind::NonNull));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, AliasTemplates) {
+ Preamble = R"cpp(
+ template <typename T>
+ using Nullable = T _Nullable;
+ template <typename T>
+ using Nonnull = T _Nonnull;
+ )cpp";
+ EXPECT_THAT(nullVec("Nullable<int*>"),
+ ElementsAre(NullabilityKind::Nullable));
+
+ EXPECT_THAT(
+ nullVec("Nullable<Nullable<int*>*>"),
+ ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable));
+
+ EXPECT_THAT(nullVec("Nullable<Nullable<Nonnull<int*>*>*>"),
+ ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable,
+ NullabilityKind::NonNull));
+
+ Preamble = R"cpp(
+ template <typename T, typename U>
+ struct Pair;
+ template <typename T>
+ using Two = Pair<T, T>;
+ )cpp";
+ EXPECT_THAT(
+ nullVec("Two<int* _Nullable>"),
+ ElementsAre(NullabilityKind::Nullable, NullabilityKind::Nullable));
+
+ Preamble = R"cpp(
+ template <typename T1>
+ using A = T1* _Nullable;
+ template <typename T2>
+ using B = A<T2>* _Nonnull;
+ )cpp";
+ EXPECT_THAT(nullVec("B<int>"),
+ ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
+
+ Preamble = R"cpp(
+ template <typename T, typename U, typename V>
+ struct Triple;
+ template <typename A, typename... Rest>
+ using TripleAlias = Triple<A _Nonnull, Rest...>;
+ )cpp";
+ EXPECT_THAT(nullVec("TripleAlias<int *, int *_Nullable, int*>"),
+ ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable,
+ NullabilityKind::Unspecified));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, DependentAlias) {
+ // Simple dependent type-aliases.
+ Preamble = R"cpp(
+ template <class T>
+ struct Nullable {
+ using type = T _Nullable;
+ };
+ )cpp";
+ // TODO: should be [Nullable, Nonnull]
+ EXPECT_THAT(
+ nullVec("Nullable<int* _Nonnull *>::type"),
+ ElementsAre(NullabilityKind::Nullable, NullabilityKind::Unspecified));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, NestedClassTemplate) {
+ // Simple struct inside template.
+ Preamble = R"cpp(
+ template <class T>
+ struct Outer {
+ struct Inner;
+ };
+ using OuterNullableInner = Outer<int* _Nonnull>::Inner;
+ )cpp";
+ // TODO: should be [NonNull]
+ EXPECT_THAT(nullVec("Outer<int* _Nonnull>::Inner"),
+ ElementsAre(NullabilityKind::Unspecified));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, ReferenceOuterTemplateParam) {
+ // Referencing type-params from indirectly-enclosing template.
+ Preamble = R"cpp(
+ template <class A, class B>
+ struct Pair;
+
+ template <class T>
+ struct Outer {
+ template <class U>
+ struct Inner {
+ using type = Pair<U, T>;
+ };
+ };
+ )cpp";
+ // TODO: should be [Nonnull, Nullable]
+ EXPECT_THAT(
+ nullVec("Outer<int *_Nullable>::Inner<int *_Nonnull>::type"),
+ ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, DependentlyNamedTemplate) {
+ // Instantiation of dependent-named template
+ Preamble = R"cpp(
+ struct Wrapper {
+ template <class T>
+ using Nullable = T _Nullable;
+ };
+
+ template <class U, class WrapT>
+ struct S {
+ using type = typename WrapT::template Nullable<U>* _Nonnull;
+ };
+ )cpp";
+ EXPECT_THAT(nullVec("S<int *, Wrapper>::type"),
+ ElementsAre(NullabilityKind::NonNull, NullabilityKind::Nullable));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, TemplateTemplateParams) {
+ // Template template params
+ Preamble = R"cpp(
+ template <class X>
+ struct Nullable {
+ using type = X _Nullable;
+ };
+ template <class X>
+ struct Nonnull {
+ using type = X _Nonnull;
+ };
+
+ template <template <class> class Nullability, class T>
+ struct Pointer {
+ using type = typename Nullability<T*>::type;
+ };
+ )cpp";
+ EXPECT_THAT(nullVec("Pointer<Nullable, int>::type"),
+ ElementsAre(NullabilityKind::Nullable));
+ // TODO: should be [Nullable, Nonnull]
+ EXPECT_THAT(
+ nullVec("Pointer<Nullable, Pointer<Nonnull, int>::type>::type"),
+ ElementsAre(NullabilityKind::Nullable, NullabilityKind::Unspecified));
+ // Same thing, but with alias templates.
+ Preamble = R"cpp(
+ template <class X>
+ using Nullable = X _Nullable;
+ template <class X>
+ using Nonnull = X _Nonnull;
+
+ template <template <class> class Nullability, class T>
+ struct Pointer {
+ using type = Nullability<T*>;
+ };
+ )cpp";
+ EXPECT_THAT(nullVec("Pointer<Nullable, int>::type"),
+ ElementsAre(NullabilityKind::Nullable));
+ // TODO: should be [Nullable, Nonnull]
+ EXPECT_THAT(
+ nullVec("Pointer<Nullable, Pointer<Nonnull, int>::type>::type"),
+ ElementsAre(NullabilityKind::Nullable, NullabilityKind::Unspecified));
+}
+
+TEST_F(GetNullabilityAnnotationsFromTypeTest, ClassTemplateParamPack) {
+ // Parameter packs
+ Preamble = R"cpp(
+ template <class... X>
+ struct TupleWrapper {
+ class Tuple;
+ };
+
+ template <class... X>
+ struct NullableTuple {
+ using type = TupleWrapper<X _Nullable...>::Tuple;
+ };
+ )cpp";
+ // TODO: should be [Unspecified, Nonnull]
+ EXPECT_THAT(
+ nullVec("TupleWrapper<int*, int* _Nonnull>::Tuple"),
+ ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
+ // TODO: should be [Nullable, Nullable]
+ EXPECT_THAT(
+ nullVec("NullableTuple<int*, int* _Nonnull>::type"),
+ ElementsAre(NullabilityKind::Unspecified, NullabilityKind::Unspecified));
+}
+
+class PrintWithNullabilityTest : public ::testing::Test {
+ protected:
+ // C++ declarations prepended before parsing type in nullVec().
+ std::string Preamble;
+
+ // Parses `Type`, augments it with Nulls, and prints the result.
+ std::string print(llvm::StringRef Type, ArrayRef<NullabilityKind> Nulls) {
+ clang::TestAST AST((Preamble + "\n using Target = " + Type + ";").str());
+ auto Target = AST.context().getTranslationUnitDecl()->lookup(
+ &AST.context().Idents.get("Target"));
+ CHECK(Target.isSingleResult());
+ QualType TargetType =
+ AST.context().getTypedefType(Target.find_first<TypeAliasDecl>());
+ return printWithNullability(TargetType, Nulls, AST.context());
+ }
+};
+
+TEST_F(PrintWithNullabilityTest, Pointers) {
+ EXPECT_EQ(print("int*", {NullabilityKind::Nullable}), "int * _Nullable");
+ EXPECT_EQ(
+ print("int***", {NullabilityKind::Nullable, NullabilityKind::NonNull,
+ NullabilityKind::Unspecified}),
+ "int ** _Nonnull * _Nullable");
+}
+
+TEST_F(PrintWithNullabilityTest, Sugar) {
+ Preamble = R"cpp(
+ template <class T>
+ using Ptr = T*;
+ using Int = int;
+ using IntPtr = Ptr<Int>;
+ )cpp";
+ EXPECT_EQ(print("IntPtr", {NullabilityKind::Nullable}), "int * _Nullable");
+}
+
+TEST_F(PrintWithNullabilityTest, Templates) {
+ Preamble = R"cpp(
+ template <class>
+ struct vector;
+ template <class, class>
+ struct pair;
+ )cpp";
+ EXPECT_EQ(print("vector<pair<int*, int*>*>",
+ {NullabilityKind::Nullable, NullabilityKind::NonNull,
+ NullabilityKind::Unspecified}),
+ "vector<pair<int * _Nonnull, int *> * _Nullable>");
+}
+
+TEST_F(PrintWithNullabilityTest, Functions) {
+ EXPECT_EQ(print("float*(*)(double*, double*)",
+ {NullabilityKind::Nullable, NullabilityKind::NonNull,
+ NullabilityKind::NonNull, NullabilityKind::Unspecified}),
+ "float * _Nonnull (* _Nullable)(double * _Nonnull, double *)");
+}
+
+} // namespace
+} // namespace clang::tidy::nullability
diff --git a/nullability/test/BUILD b/nullability/test/BUILD
new file mode 100644
index 0000000..cf99fe9
--- /dev/null
+++ b/nullability/test/BUILD
@@ -0,0 +1,219 @@
+# Integration tests for nullability verification.
+
+package(default_applicable_licenses = ["//:license"])
+
+cc_library(
+ name = "check_diagnostics",
+ testonly = 1,
+ srcs = ["check_diagnostics.cc"],
+ hdrs = ["check_diagnostics.h"],
+ deps = [
+ "//nullability:pointer_nullability_analysis",
+ "//nullability:pointer_nullability_diagnosis",
+ "@llvm-project//clang:analysis",
+ "@llvm-project//clang/unittests:dataflow_testing_support",
+ "@llvm-project//llvm:Support",
+ "@llvm-project//llvm:TestingSupport",
+ "@llvm-project//third-party/unittest:gtest",
+ ],
+)
+
+cc_test(
+ name = "assert_nullability_test",
+ srcs = ["assert_nullability_test.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "aliases",
+ srcs = ["aliases.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "basic",
+ srcs = ["basic.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "binary_ops",
+ srcs = ["binary_ops.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "casts",
+ srcs = ["casts.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "comparisons",
+ srcs = ["comparisons.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "constructors",
+ srcs = ["constructors.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "fields",
+ srcs = ["fields.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "function_calls",
+ srcs = ["function_calls.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "function_pointers",
+ srcs = ["function_pointers.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "initialization",
+ srcs = ["initialization.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "merge",
+ srcs = ["merge.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "operator_new",
+ srcs = ["operator_new.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "parens",
+ srcs = ["parens.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "pointer_arithmetic",
+ srcs = ["pointer_arithmetic.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "return_statements",
+ srcs = ["return_statements.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "templates",
+ srcs = ["templates.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "temporary_materialization",
+ srcs = ["temporary_materialization.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "this_pointer",
+ srcs = ["this_pointer.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
+
+cc_test(
+ name = "variance",
+ srcs = ["variance.cc"],
+ deps = [
+ ":check_diagnostics",
+ "@llvm-project//third-party/unittest:gtest",
+ "@llvm-project//third-party/unittest:gtest_main",
+ ],
+)
diff --git a/nullability/test/aliases.cc b/nullability/test/aliases.cc
new file mode 100644
index 0000000..852a482
--- /dev/null
+++ b/nullability/test/aliases.cc
@@ -0,0 +1,30 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for nullability information hidden behind aliases.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang::tidy::nullability {
+namespace {
+
+TEST(PointerNullabilityTest, Aliases) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T>
+ struct Factory {
+ T get();
+ };
+ using NeverNull = int* _Nonnull;
+ using MaybeNull = int* _Nullable;
+
+ void target(Factory<NeverNull> never, Factory<MaybeNull> maybe) {
+ *never.get();
+ *maybe.get(); // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace clang::tidy::nullability
\ No newline at end of file
diff --git a/nullability/test/assert_nullability_test.cc b/nullability/test/assert_nullability_test.cc
new file mode 100644
index 0000000..2bdc10f
--- /dev/null
+++ b/nullability/test/assert_nullability_test.cc
@@ -0,0 +1,211 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests that check that `__assert_nullability` works correctly.
+//
+// Note that this file should not contain all tests that use
+// `__assert_nullability`, but only tests to verify that `__assert_nullability`
+// itself works.
+//
+// TODO(mboehme): Because this test is doing something different than the
+// other tests, we would ideally want to place it in a different directory.
+// For the time being, the `_test` suffix at the end of the filename is
+// intended as a weak way of indicating this.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, AssertNullability) {
+ // Concrete struct.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct StructNonnullNullable {
+ int* _Nonnull nonnull;
+ int* _Nullable nullable;
+ };
+
+ void target(StructNonnullNullable p) {
+ __assert_nullability<>(p);
+ __assert_nullability<NK_nonnull>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull, NK_nullable>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull, NK_unspecified>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull, NK_nonnull>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable, NK_nullable>(p); // [[unsafe]]
+ __assert_nullability<NK_unspecified, NK_nullable>(p); // [[unsafe]]
+ }
+ )cc"));
+
+ // Struct with two template type parameters.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {};
+
+ void target(Struct2Arg<int *, int *_Nullable> p) {
+ __assert_nullability<NK_unspecified>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable>(p); // [[unsafe]]
+
+ __assert_nullability<NK_unspecified, NK_nonnull>(p); // [[unsafe]]
+ __assert_nullability<NK_unspecified, NK_nullable>(p);
+ __assert_nullability<NK_unspecified, NK_unspecified>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull, NK_nullable>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable, NK_nullable>(p); // [[unsafe]]
+
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_unspecified>(p);
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nullable>(p);
+ }
+ )cc"));
+
+ // Struct with one type and non-type template parameters.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, typename T2>
+ struct Struct3ArgWithInt {};
+
+ void target(Struct3ArgWithInt<2147483647, int* _Nullable, int* _Nonnull> p) {
+ __assert_nullability<>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable>(p); // [[unsafe]]
+
+ __assert_nullability<NK_unspecified, NK_nonnull>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull, NK_nonnull>(p); // [[unsafe]]
+ __assert_nullability<NK_nonnull, NK_nullable>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable, NK_nonnull>(p);
+ __assert_nullability<NK_nullable, NK_nullable>(p); // [[unsafe]]
+ __assert_nullability<NK_nullable, NK_unspecified>(p); // [[unsafe]]
+
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nonnull>(p);
+ }
+ )cc"));
+
+ // Nested template arguments.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {};
+
+ void target(
+ Struct2Arg<Struct2Arg<int *, int *_Nullable>,
+ Struct2Arg<Struct2Arg<int *_Nullable, int *_Nonnull>,
+ Struct2Arg<int *_Nullable, int *_Nullable>>>
+ p) {
+ __assert_nullability<>(p); // [[unsafe]]
+
+ __assert_nullability<NK_unspecified, NK_nullable, NK_nullable, NK_nonnull,
+ NK_nullable, NK_nullable>(p);
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nullable, NK_nonnull, NK_nullable,
+ NK_nullable, NK_nullable>(p);
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nullable, NK_nonnull, NK_nullable>(
+ p);
+ }
+ )cc"));
+
+ // Struct with two template parameters substituted with concrete structs.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct StructUnknownNullable {
+ int* unknown;
+ int* _Nullable nullable;
+ };
+
+ struct StructNullableNonnull {
+ int* _Nullable nullable;
+ int* _Nonnull nonnull;
+ };
+
+ template <typename T1, typename T2>
+ struct Struct2Arg {};
+
+ void target(Struct2Arg<StructUnknownNullable, StructNullableNonnull> p) {
+ __assert_nullability<>(p);
+
+ __assert_nullability<NK_unspecified, NK_nullable>(p); // [[unsafe]]
+
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nullable>(p);
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nullable, NK_nonnull>(p);
+ __assert_nullability // [[unsafe]]
+ <NK_nonnull, NK_nullable, NK_nullable, NK_nonnull>(p);
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nullable, NK_nonnull,
+ NK_unspecified>(p);
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 arg0;
+ T1 arg1;
+
+ T0 getT0();
+ T1 getT1();
+ };
+
+ void target(
+ Struct2Arg<Struct2Arg<int *, int *_Nullable>,
+ Struct2Arg<Struct2Arg<int *_Nullable, int *_Nonnull>,
+ Struct2Arg<int *_Nullable, int *_Nullable>>>
+ p) {
+ __assert_nullability<NK_unspecified, NK_nullable, NK_nullable, NK_nonnull,
+ NK_nullable, NK_nullable>(p);
+ __assert_nullability<NK_unspecified, NK_nullable>(p.arg0);
+ __assert_nullability<NK_unspecified>(p.arg0.arg0);
+ __assert_nullability<NK_nullable>(p.arg0.arg1);
+ __assert_nullability<NK_nullable, NK_nonnull, NK_nullable, NK_nullable>(
+ p.arg1);
+ __assert_nullability<NK_nullable, NK_nonnull>(p.arg1.arg0);
+ __assert_nullability<NK_nullable>(p.arg1.arg0.arg0);
+ __assert_nullability<NK_nonnull>(p.arg1.arg0.arg1);
+ __assert_nullability<NK_nullable, NK_nullable>(p.arg1.arg1);
+ __assert_nullability<NK_nullable>(p.arg1.arg1.arg0);
+ __assert_nullability<NK_nullable>(p.arg1.arg1.arg1);
+
+ __assert_nullability<>(p.arg0.arg0); // [[unsafe]]
+ __assert_nullability<NK_unspecified>(p.arg0); // [[unsafe]]
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_nonnull, NK_nullable, NK_nullable>(
+ p.arg1);
+
+ __assert_nullability<NK_unspecified, NK_nullable>(p.getT0());
+ __assert_nullability<NK_nonnull>(p.getT1().getT0().getT1());
+
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified, NK_nullable, NK_unspecified>(p.getT0());
+ __assert_nullability // [[unsafe]]
+ <NK_unspecified>(p.getT0());
+
+ __assert_nullability<NK_nonnull>(p.getT1().arg0.getT1());
+ __assert_nullability<NK_nonnull>(p.arg1.getT0().arg1);
+ __assert_nullability<NK_nonnull>(p.arg1.arg0.arg1);
+
+ __assert_nullability // [[unsafe]]
+ <>(p.getT1().getT0().getT1());
+ __assert_nullability // [[unsafe]]
+ <NK_nonnull, NK_nonnull>(p.arg1.getT0().arg1);
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable p, int* _Nonnull q, int* r) {
+ __assert_nullability<NK_nonnull, NK_nullable>(&p);
+ __assert_nullability<NK_nonnull, NK_nonnull>(&q);
+ __assert_nullability<NK_nonnull>(&*p); // [[unsafe]]
+ __assert_nullability<NK_nonnull>(&*q);
+ __assert_nullability<NK_nonnull>(&*r);
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/basic.cc b/nullability/test/basic.cc
new file mode 100644
index 0000000..9e8804e
--- /dev/null
+++ b/nullability/test/basic.cc
@@ -0,0 +1,241 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for basic functionality (simple dereferences without control flow).
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, NoPointerOperations) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() { 1 + 2; }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DerefNullPtr) {
+ // nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ int *x = nullptr;
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ // 0
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ int *x = 0;
+ *x; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DerefAddrOf) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ int i;
+ int *x = &i;
+ *x;
+ }
+ )cc"));
+
+ // transitive
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ int i;
+ int *x = &i;
+ int *y = x;
+ *y;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DerefPtrAnnotatedNonNullWithoutACheck) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) { *x; }
+ )cc"));
+
+ // transitive
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nonnull x) {
+ int *y = x;
+ *y;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DerefPtrAnnotatedNullableWithoutACheck) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ // transitive
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x) {
+ int *y = x;
+ *y; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DerefUnknownPtrWithoutACheck) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) { *x; }
+ )cc"));
+
+ // transitive
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ int *y = x;
+ *y;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DoubleDereference) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int** p) {
+ *p;
+ **p;
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int** _Nonnull p) {
+ *p;
+ **p;
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull* p) {
+ *p;
+ **p;
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull* _Nonnull p) {
+ *p;
+ **p;
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int** _Nullable p) {
+ *p; // [[unsafe]]
+ **p; // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable* p) {
+ *p;
+ **p; // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable* _Nullable p) {
+ *p; // [[unsafe]]
+ **p; // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable* _Nonnull p) {
+ *p;
+ **p; // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull* _Nullable p) {
+ *p; // [[unsafe]]
+ **p; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, ArrowOperatorOnNonNullPtr) {
+ // (->) member field
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *foo;
+ };
+ void target(Foo *_Nonnull foo) { foo->foo; }
+ )cc"));
+
+ // (->) member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *foo();
+ };
+ void target(Foo *_Nonnull foo) { foo->foo(); }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, ArrowOperatorOnNullablePtr) {
+ // (->) member field
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *foo;
+ };
+ void target(Foo *_Nullable foo) {
+ foo->foo; // [[unsafe]]
+ if (foo) {
+ foo->foo;
+ } else {
+ foo->foo; // [[unsafe]]
+ }
+ foo->foo; // [[unsafe]]
+ }
+ )cc"));
+
+ // (->) member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *foo();
+ };
+ void target(Foo *_Nullable foo) {
+ foo->foo(); // [[unsafe]]
+ if (foo) {
+ foo->foo();
+ } else {
+ foo->foo(); // [[unsafe]]
+ }
+ foo->foo(); // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, ArrowOperatorOnUnknownPtr) {
+ // (->) member field
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *foo;
+ };
+ void target(Foo *foo) { foo->foo; }
+ )cc"));
+
+ // (->) member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *foo();
+ };
+ void target(Foo *foo) { foo->foo(); }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/binary_ops.cc b/nullability/test/binary_ops.cc
new file mode 100644
index 0000000..ecb1443
--- /dev/null
+++ b/nullability/test/binary_ops.cc
@@ -0,0 +1,88 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for binary operators.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, BinaryExpressions) {
+ // x && y
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nullable y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ if (x && y) {
+ *x;
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ )cc"));
+
+ // x || y
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nullable y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ if (x || y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ )cc"));
+
+ // !x && !y
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nullable y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ if (!x && !y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ )cc"));
+
+ // !x || !y
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nullable y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ if (!x || !y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ } else {
+ *x;
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/casts.cc b/nullability/test/casts.cc
new file mode 100644
index 0000000..e719d3c
--- /dev/null
+++ b/nullability/test/casts.cc
@@ -0,0 +1,308 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for casts of types containing nullability annotations.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+// TODO(b/233582219): Implement diagnosis of unreachable program points
+TEST(PointerNullabilityTest, NonNullPtrImplicitCastToBool) {
+ // x
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) {
+ *x;
+ if (x) {
+ *x;
+ } else {
+ *x; // unreachable
+ }
+ *x;
+ }
+ )cc"));
+
+ // !x
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) {
+ *x;
+ if (!x) {
+ *x; // unreachable
+ } else {
+ *x;
+ }
+ *x;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NullablePtrImplicitCastToBool) {
+ // x
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ if (x) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ // !x
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ if (!x) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+}
+
+// TODO(b/233582219): Fix false negatives. Casting the pointer to boolean is
+// evidence of the author considering null a possibility, hence the unnannotated
+// pointer should be considered nullable and emit warnings where it fails or is
+// not null checked.
+TEST(PointerNullabilityTest, UnknownPtrImplicitCastToBool) {
+ // x
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ *x; // false-negative
+ if (x) {
+ *x;
+ } else {
+ *x; // false-negative
+ }
+ *x; // false-negative
+ }
+ )cc"));
+
+ // !x
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ *x; // false-negative
+ if (!x) {
+ *x; // false-negative
+ } else {
+ *x;
+ }
+ *x; // false-negative
+ }
+ )cc"));
+}
+
+// CK_Bitcast: Bitcasts preserve outer nullability
+TEST(PointerNullabilityTest, Bitcast) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct vector {};
+
+ void target() {
+ // Bitcasts preserve nullability.
+ __assert_nullability<NK_nullable>((void*)value<int* _Nullable>());
+ __assert_nullability<NK_nonnull>((void*)value<int* _Nonnull>());
+ __assert_nullability<NK_unspecified>((void*)value<int*>());
+ // Nullability of further outer pointer types is preserved in bitcasts.
+ __assert_nullability<NK_nullable, NK_nullable>(
+ (void**)value<int* _Nullable* _Nullable>());
+ __assert_nullability<NK_nonnull, NK_nonnull>(
+ (void**)value<int* _Nonnull* _Nonnull>());
+ __assert_nullability<NK_unspecified, NK_unspecified>((void**)value<int**>());
+ // But nullability of other inner types is dropped.
+ __assert_nullability<NK_nullable, NK_unspecified>(
+ (void**)value<vector<int* _Nullable>* _Nullable>());
+ __assert_nullability<NK_nonnull, NK_unspecified>(
+ (void**)value<vector<int* _Nonnull>* _Nonnull>());
+
+ __assert_nullability<NK_nonnull, NK_unspecified>(
+ (void**)value<int* _Nonnull>);
+ __assert_nullability<NK_nonnull>((void*)value<int* _Nonnull* _Nonnull>());
+ }
+ )cc"));
+}
+
+// CK_NoOp: No-op casts preserve deep nullability
+// TODO: fix false-positives from treating untracked values as unsafe.
+TEST(PointerNullabilityTest, NoOp) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct vector {};
+
+ void target() {
+ // No-op casts preserve deep nullability.
+ __assert_nullability // [[unsafe]] TODO: fix false positive
+ <NK_nullable, NK_nullable>(const_cast<vector<int>*>(
+ (vector<int>* const)value<vector<int* _Nullable>* _Nullable>()));
+ }
+ )cc"));
+}
+
+// Casts between types with inheritance - only simple cases handled.
+// TODO: fix false-positives from treating untracked values as unsafe.
+TEST(PointerNullabilityTest, Inheritance) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct base {
+ virtual void ensure_polymorphic();
+ };
+ template <class X>
+ struct derived : base<X> {};
+
+ void target() {
+ // CK_BaseToDerived: preserves outer nullability only.
+ // TODO: determine that derived's type param is actually nullable here.
+ __assert_nullability<NK_nullable, NK_unspecified>(
+ (derived<int *> *)value<base<int *_Nullable> *_Nullable>());
+ // CK_Dynamic: dynamic_cast returns a nullable pointer.
+ auto b = value<base<int *_Nonnull> *_Nonnull>();
+ __assert_nullability // [[unsafe]] TODO: fix false positive
+ <NK_nullable, NK_unspecified>(dynamic_cast<derived<int> *>(b));
+ // ... only if casting to a pointer!
+ auto c = value<base<int *>>();
+ __assert_nullability<NK_unspecified>(dynamic_cast<derived<int *> &>(c));
+ }
+ )cc"));
+}
+
+// User-defined conversions could do anything, use declared type.
+TEST(PointerNullabilityTest, UserDefinedConversions) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class X>
+ struct BuildFromPointer {
+ BuildFromPointer(int*);
+ };
+
+ void target() {
+ // User-defined conversions could do anything.
+ // CK_ConstructorConversion
+ __assert_nullability<NK_unspecified>(
+ (BuildFromPointer<double*>)value<int* _Nonnull>());
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CastToNonPointer) {
+ // Casting to non-pointer types destroyes nullability.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ using I = __INTPTR_TYPE__;
+
+ // TODO: fix false-positives from treating untracked values as unsafe.
+ void target() {
+ // Casting away pointerness destroys nullability.
+ // CK_PointerToIntegral
+ __assert_nullability<>((I)value<int* _Nonnull>());
+ // CK_PointerToBoolean
+ __assert_nullability<>((bool)value<int* _Nonnull>());
+ // Casting them back does not recover it.
+ // CK_IntegralToPointer
+ __assert_nullability // [[unsafe]] TODO: fix false positive
+ <>((int*)(I)value<int* _Nonnull>());
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, TrivialNullability) {
+ // Casts with trivial nullability
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ // Null is nullable!
+ __assert_nullability<NK_nullable>((int*)nullptr);
+
+ // Decayed objects are non-null.
+ int array[2];
+ __assert_nullability<NK_nonnull>((int*)array);
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CastNullToAlias) {
+ // This used to crash!
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ using P = int *;
+ P target() { return nullptr; }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CastExpression) {
+ // TODO: We currently do not warn on local variables
+ // whose annotations conflict with the initializer. Decide whether to do so,
+ // and then treat static casts in an equivalent manner.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable p) {
+ static_cast<int *_Nonnull>(p); // TODO: To warn, or not to warn, that is
+ // the question.
+ static_cast<int *>(p);
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, typename T2>
+ struct Struct3Arg {
+ T1 arg1;
+ T2 arg2;
+ };
+
+ void target(Struct3Arg<1, int *_Nullable, int *> &p) {
+ *static_cast<const Struct3Arg<1, int *, int *> &>(p).arg1; // [[unsafe]]
+ *static_cast<const Struct3Arg<1, int *, int *> &>(p).arg2;
+ *static_cast<int *>(p.arg1); // [[unsafe]]
+ *static_cast<int *>(p.arg2);
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Base {};
+ struct Derived : public Base {};
+
+ void target(Derived *_Nullable x, Derived *_Nonnull y) {
+ *static_cast<Base *>(x); // [[unsafe]]
+ *static_cast<Base *>(y); // [[unsafe]] TODO: Fix false positive.
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, typename T2>
+ struct Struct3Arg {
+ T1 arg1;
+ T2 arg2;
+ };
+
+ void target(Struct3Arg<1, int *_Nullable, int *> &p) {
+ *((const Struct3Arg<1, int *, int *> &)p).arg1; // [[unsafe]]
+ *((const Struct3Arg<1, int *, int *> &)p) // [[unsafe]]
+ .arg2; // TODO: Fix false positive.
+ *(int *)p.arg1; // [[unsafe]]
+ *(int *)p.arg2; // [[unsafe]] TODO: Fix false positive.
+ *(float *)p.arg1; // [[unsafe]]
+ *(char *)p.arg2; // [[unsafe]] TODO: Fix false positive.
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 arg0;
+ T1 arg1;
+ };
+
+ void target(Struct2Arg<const int *, const int *_Nullable> &p) {
+ *const_cast<int *>(p.arg0); // [[unsafe]] TODO: Fix false positive.
+ *const_cast<int *>(p.arg1); // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/check_diagnostics.cc b/nullability/test/check_diagnostics.cc
new file mode 100644
index 0000000..a66d444
--- /dev/null
+++ b/nullability/test/check_diagnostics.cc
@@ -0,0 +1,101 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "nullability/test/check_diagnostics.h"
+
+#include "nullability/pointer_nullability_analysis.h"
+#include "nullability/pointer_nullability_diagnosis.h"
+#include "clang/Analysis/CFG.h"
+#include "third_party/llvm/llvm-project/clang/unittests/Analysis/FlowSensitive/TestingSupport.h"
+#include "llvm/Testing/Support/Error.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+constexpr char kPreamble[] = R"cc(
+ enum NullabilityKind {
+ NK_nonnull,
+ NK_nullable,
+ NK_unspecified,
+ };
+
+ template <NullabilityKind... NK, typename T>
+ void __assert_nullability(const T&);
+
+ template <typename T>
+ T value();
+)cc";
+
+constexpr char kNewHeader[] = R"cc(
+ namespace std {
+ struct nothrow_t {
+ explicit nothrow_t() = default;
+ };
+ extern const nothrow_t nothrow;
+ using size_t = decltype(sizeof(int));
+ } // namespace std
+ void* operator new(std::size_t size, const std::nothrow_t&) noexcept;
+)cc";
+
+bool checkDiagnostics(llvm::StringRef SourceCode) {
+ std::vector<CFGElement> Diagnostics;
+ PointerNullabilityDiagnoser Diagnoser;
+ bool Failed = false;
+ EXPECT_THAT_ERROR(
+ dataflow::test::checkDataflow<PointerNullabilityAnalysis>(
+ dataflow::test::AnalysisInputs<PointerNullabilityAnalysis>(
+ SourceCode, ast_matchers::hasName("target"),
+ [](ASTContext &ASTCtx, dataflow::Environment &) {
+ return PointerNullabilityAnalysis(ASTCtx);
+ })
+ .withPostVisitCFG([&Diagnostics, &Diagnoser](
+ ASTContext &Ctx, const CFGElement &Elt,
+ const dataflow::TransferStateForDiagnostics<
+ PointerNullabilityLattice> &State) {
+ auto EltDiagnostics = Diagnoser.diagnose(&Elt, Ctx, State);
+ if (EltDiagnostics.has_value()) {
+ Diagnostics.push_back(EltDiagnostics.value());
+ }
+ })
+ .withASTBuildVirtualMappedFiles(
+ {{"preamble.h", kPreamble}, {"new", kNewHeader}})
+ .withASTBuildArgs({"-fsyntax-only", "-std=c++17",
+ "-Wno-unused-value", "-Wno-nonnull",
+ "-include", "preamble.h", "-I."}),
+ [&Diagnostics, &Failed](
+ const llvm::DenseMap<unsigned, std::string> &Annotations,
+ const dataflow::test::AnalysisOutputs &AnalysisData) {
+ // Note: use sorted sets for expected and actual lines to improve
+ // readability of the error output in case the test fails.
+ std::set<unsigned> ExpectedLines, ActualLines;
+ for (const auto &[Line, _] : Annotations) {
+ ExpectedLines.insert(Line);
+ }
+ auto &SrcMgr = AnalysisData.ASTCtx.getSourceManager();
+ for (auto Element : Diagnostics) {
+ if (std::optional<CFGStmt> stmt = Element.getAs<CFGStmt>()) {
+ ActualLines.insert(SrcMgr.getPresumedLineNumber(
+ stmt->getStmt()->getBeginLoc()));
+ } else if (std::optional<CFGInitializer> init =
+ Element.getAs<CFGInitializer>()) {
+ ActualLines.insert(SrcMgr.getPresumedLineNumber(
+ init->getInitializer()->getSourceLocation()));
+ } else {
+ ADD_FAILURE() << "this code should not be reached";
+ }
+ }
+ EXPECT_THAT(ActualLines, testing::ContainerEq(ExpectedLines));
+ if (ActualLines != ExpectedLines) {
+ Failed = true;
+ }
+ }),
+ llvm::Succeeded());
+ return !Failed;
+}
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/check_diagnostics.h b/nullability/test/check_diagnostics.h
new file mode 100644
index 0000000..9d05b76
--- /dev/null
+++ b/nullability/test/check_diagnostics.h
@@ -0,0 +1,25 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef THIRD_PARTY_CRUBIT_NULLABILITY_TEST_CHECK_DIAGNOSTICS_H_
+#define THIRD_PARTY_CRUBIT_NULLABILITY_TEST_CHECK_DIAGNOSTICS_H_
+
+#include "llvm/ADT/StringRef.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+
+/// Runs nullability verification on `SourceCode` and returns whether
+/// diagnostics are produced on those lines marked in the source code with
+/// `llvm::Annotations` style annotations (and no other lines).
+/// TODO(mboehme): So far, we only check the locations of the diagnostics; it
+/// would be desirable to check their actual content too.
+bool checkDiagnostics(llvm::StringRef SourceCode);
+
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
+
+#endif // THIRD_PARTY_CRUBIT_NULLABILITY_TEST_CHECK_DIAGNOSTICS_H_
diff --git a/nullability/test/comparisons.cc b/nullability/test/comparisons.cc
new file mode 100644
index 0000000..62d1163
--- /dev/null
+++ b/nullability/test/comparisons.cc
@@ -0,0 +1,502 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for comparisons of types containing nullability annotations.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, CompareNonNullPtrAndNonNullPtr) {
+ // nonnull == nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x, int* _Nonnull y) {
+ *x;
+ *y;
+ if (x == y) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+
+ // nonnull != nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x, int* _Nonnull y) {
+ *x;
+ *y;
+ if (x != y) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CompareNullablePtrAndNullablePtr) {
+ // nullable == nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nullable y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ if (x == y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ )cc"));
+
+ // nullable != nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nullable y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ if (x != y) {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ *y; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CompareUnknownPtrAndUnknownPtr) {
+ // unknown == unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x, int *y) {
+ *x;
+ *y;
+ if (x == y) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+
+ // unknown != unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x, int *y) {
+ *x;
+ *y;
+ if (x != y) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+}
+
+// TODO(b/233582219): Implement diagnosis of unreachable program points
+TEST(PointerNullabilityTest, CompareNonNullPtrAndNullPtr) {
+ // nonnull == nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) {
+ *x;
+ if (x == nullptr) {
+ *x; // unreachable
+ } else {
+ *x;
+ }
+ *x;
+ }
+ )cc"));
+
+ // nullptr == nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) {
+ *x;
+ if (nullptr == x) {
+ *x; // unreachable
+ } else {
+ *x;
+ }
+ *x;
+ }
+ )cc"));
+
+ // nonnull != nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) {
+ *x;
+ if (x != nullptr) {
+ *x;
+ } else {
+ *x; // unreachable
+ }
+ *x;
+ }
+ )cc"));
+
+ // nullptr != nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull x) {
+ *x;
+ if (nullptr != x) {
+ *x;
+ } else {
+ *x; // unreachable
+ }
+ *x;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CompareNullablePtrAndNullPtr) {
+ // nullable == nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ if (x == nullptr) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ // nullptr == nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ if (nullptr == x) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ // nullable != nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ if (x != nullptr) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ // nullptr != nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x) {
+ *x; // [[unsafe]]
+ if (nullptr != x) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CompareNullablePtrAndNonNullPtr) {
+ // nullable == nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nonnull y) {
+ *x; // [[unsafe]]
+ *y;
+ if (x == y) {
+ *x;
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+
+ // nonnull == nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nonnull y) {
+ *x; // [[unsafe]]
+ *y;
+ if (y == x) {
+ *x;
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+
+ // nullable != nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nonnull y) {
+ *x; // [[unsafe]]
+ *y;
+ if (x != y) {
+ *x; // [[unsafe]]
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+
+ // nonnull != nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nullable x, int* _Nonnull y) {
+ *x; // [[unsafe]]
+ *y;
+ if (y != x) {
+ *x; // [[unsafe]]
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CompareNullablePtrAndUnknownPtr) {
+ // nullable == unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x, int *y) {
+ *x; // [[unsafe]]
+ *y;
+ if (x == y) {
+ *x; // [[unsafe]]
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+
+ // unknown == nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x, int *y) {
+ *x; // [[unsafe]]
+ *y;
+ if (y == x) {
+ *x; // [[unsafe]]
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+
+ // nullable != unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x, int *y) {
+ *x; // [[unsafe]]
+ *y;
+ if (x != y) {
+ *x; // [[unsafe]]
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+
+ // unknown != nullable
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x, int *y) {
+ *x; // [[unsafe]]
+ *y;
+ if (y != x) {
+ *x; // [[unsafe]]
+ *y;
+ } else {
+ *x; // [[unsafe]]
+ *y;
+ }
+ *x; // [[unsafe]]
+ *y;
+ }
+ )cc"));
+}
+
+// TODO(b/233582219): Fix false negatives. The pointer is compared to nullptr,
+// hence the unnannotated pointer should be considered nullable and emit
+// warnings where it fails or is not null checked.
+TEST(PointerNullabilityTest, CompareUnknownPtrAndNullPtr) {
+ // unknown == nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ *x; // false-negative
+ if (x == nullptr) {
+ *x; // false-negative
+ } else {
+ *x;
+ }
+ *x; // false-negative
+ }
+ )cc"));
+
+ // nullptr == unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ *x; // false-negative
+ if (nullptr == x) {
+ *x; // false-negative
+ } else {
+ *x;
+ }
+ *x; // false-negative
+ }
+ )cc"));
+
+ // unknown != nullptr
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ *x; // false-negative
+ if (x != nullptr) {
+ *x;
+ } else {
+ *x; // false-negative
+ }
+ *x; // false-negative
+ }
+ )cc"));
+
+ // nullptr != unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x) {
+ *x; // false-negative
+ if (nullptr != x) {
+ *x;
+ } else {
+ *x; // false-negative
+ }
+ *x; // false-negative
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CompareUnknownPtrAndNonNullPtr) {
+ // unknown == nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x, int *_Nonnull y) {
+ *x;
+ *y;
+ if (x == y) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+
+ // nonnull == unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x, int *_Nonnull y) {
+ *x;
+ *y;
+ if (y == x) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+
+ // unknown != nonnull
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x, int *_Nonnull y) {
+ *x;
+ *y;
+ if (x != y) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+
+ // nonnull != unknown
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *x, int *_Nonnull y) {
+ *x;
+ *y;
+ if (y != x) {
+ *x;
+ *y;
+ } else {
+ *x;
+ *y;
+ }
+ *x;
+ *y;
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/constructors.cc b/nullability/test/constructors.cc
new file mode 100644
index 0000000..51a2cb0
--- /dev/null
+++ b/nullability/test/constructors.cc
@@ -0,0 +1,113 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for constructors.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, ConstructExpr) {
+ // Constructor call assigned to local variable.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct TakeNonnull {
+ explicit TakeNonnull(int *_Nonnull) {}
+ };
+ struct TakeNullable {
+ explicit TakeNullable(int *_Nullable) {}
+ };
+ struct TakeUnannotated {
+ explicit TakeUnannotated(int *) {}
+ };
+ int *_Nonnull makeNonnull();
+ int *_Nullable makeNullable();
+ int *makeUnannotated();
+ void target() {
+ auto NN1 = TakeNonnull(makeNonnull());
+ auto NN2 = TakeNonnull(makeNullable()); // [[unsafe]]
+ auto NN3 = TakeNonnull(makeUnannotated());
+
+ auto NB1 = TakeNullable(makeNonnull());
+ auto NB2 = TakeNullable(makeNullable());
+ auto NB3 = TakeNullable(makeUnannotated());
+
+ auto UN1 = TakeUnannotated(makeNonnull());
+ auto UN2 = TakeUnannotated(makeNullable());
+ auto UN3 = TakeUnannotated(makeUnannotated());
+ }
+ )cc"));
+
+ // Constructor call in a base initializer.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct TakeNonnull {
+ explicit TakeNonnull(int* _Nonnull);
+ };
+ struct target : TakeNonnull {
+ target(int* _Nullable ptr_nullable) // Forced line break.
+ : TakeNonnull(ptr_nullable) // [[unsafe]]
+ {}
+ };
+ )cc"));
+
+ // Call to a delegating constructor.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nullable makeNullable();
+ struct target {
+ target(int* _Nonnull);
+ target() // Forced line break.
+ : target(makeNullable()) // [[unsafe]]
+ {}
+ };
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, ConstructorMemberInitializer) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nullable makeNullable();
+ struct target {
+ int* _Nonnull ptr_nonnull;
+ int* _Nullable ptr_nullable;
+ int* ptr_unannotated;
+ target()
+ : ptr_nonnull(makeNullable()), // [[unsafe]]
+ ptr_nullable(makeNullable()),
+ ptr_unannotated(makeNullable()) {}
+ };
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull makeNonnull();
+ struct target {
+ int* _Nonnull ptr_nonnull;
+ int* _Nullable ptr_nullable;
+ int* ptr_unannotated;
+ target()
+ : ptr_nonnull(makeNonnull()),
+ ptr_nullable(makeNonnull()),
+ ptr_unannotated(makeNonnull()) {}
+ };
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *makeUnannotated();
+ struct target {
+ int *_Nonnull ptr_nonnull;
+ int *_Nullable ptr_nullable;
+ int *ptr_unannotated;
+ target()
+ : ptr_nonnull(makeUnannotated()),
+ ptr_nullable(makeUnannotated()),
+ ptr_unannotated(makeUnannotated()) {}
+ };
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/fields.cc b/nullability/test/fields.cc
new file mode 100644
index 0000000..e1f3f15
--- /dev/null
+++ b/nullability/test/fields.cc
@@ -0,0 +1,111 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for nullability annotations on fields.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, NonNullFieldsOfPointerType) {
+ // dereference field of pointer type
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo* _Nonnull ptr;
+ };
+ void target(Foo foo) { *foo.ptr; }
+ )cc"));
+
+ // dereference field of pointer type in member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo* _Nonnull ptr;
+ void target() { *ptr; }
+ };
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NullableFieldsOfPointerType) {
+ // dereference field of pointer type
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo* _Nullable ptr;
+ };
+ void target(Foo foo) {
+ *foo.ptr; // [[unsafe]]
+ if (foo.ptr) {
+ *foo.ptr;
+ } else {
+ *foo.ptr; // [[unsafe]]
+ }
+ *foo.ptr; // [[unsafe]]
+ }
+ )cc"));
+
+ // dereference field of pointer type in member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo* _Nullable ptr;
+ void target() {
+ *ptr; // [[unsafe]]
+ if (ptr) {
+ *ptr;
+ } else {
+ *ptr; // [[unsafe]]
+ }
+ *ptr; // [[unsafe]]
+ }
+ };
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, UnknownFieldsOfPointerType) {
+ // dereference field of pointer type
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *ptr;
+ };
+ void target(Foo foo) { *foo.ptr; }
+ )cc"));
+
+ // dereference field of pointer type in member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ Foo *ptr;
+ void target() { *ptr; }
+ };
+ )cc"));
+}
+
+// TODO: fix false positives due to unsupported PointerValues in the framework.
+TEST(PointerNullabilityTest, ChainedFieldDeref) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct S {
+ S* _Nonnull nonnull;
+ S* _Nullable nullable;
+ S* unknown;
+ };
+ void target(S& s) {
+ *(*s.nonnull).nonnull; // [[unsafe]] TODO: fix false positive
+ *(*s.nonnull).nullable; // [[unsafe]]
+ *(*s.nonnull).unknown; // [[unsafe]] TODO: fix false positive
+
+ s.nonnull->nonnull->nonnull; // [[unsafe]] TODO: fix false positive
+ s.nonnull->nonnull->nullable; // [[unsafe]] TODO: fix false positive
+ s.nonnull->nullable->nonnull; // [[unsafe]]
+ s.nonnull->unknown->nonnull; // [[unsafe]] TODO: fix false positive
+
+ *&s;
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/function_calls.cc b/nullability/test/function_calls.cc
new file mode 100644
index 0000000..12f1310
--- /dev/null
+++ b/nullability/test/function_calls.cc
@@ -0,0 +1,449 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for function calls.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, CallExprWithPointerReturnType) {
+ // free function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nonnull makeNonnull();
+ int *_Nullable makeNullable();
+ int *makeUnannotated();
+ void target() {
+ *makeNonnull();
+ *makeNullable(); // [[unsafe]]
+ *makeUnannotated();
+ }
+ )cc"));
+
+ // member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ int *_Nonnull makeNonnull();
+ int *_Nullable makeNullable();
+ int *makeUnannotated();
+ };
+ void target(Foo foo) {
+ *foo.makeNonnull();
+ *foo.makeNullable(); // [[unsafe]]
+ *foo.makeUnannotated();
+ }
+ )cc"));
+
+ // function pointer
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull (*makeNonnull)(),
+ int* _Nullable (*makeNullable)(), int* (*makeUnannotated)()) {
+ *makeNonnull();
+ *makeNullable(); // [[unsafe]]
+ *makeUnannotated();
+ }
+ )cc"));
+
+ // pointer to function pointer
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int* _Nonnull (**makeNonnull)(),
+ int* _Nullable (**makeNullable)(), int* (**makeUnannotated)()) {
+ *(*makeNonnull)();
+ *(*makeNullable)(); // [[unsafe]]
+ *(*makeUnannotated)();
+ }
+ )cc"));
+
+ // function returning a function pointer which returns a pointer
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ typedef int* _Nonnull (*MakeNonnullT)();
+ typedef int* _Nullable (*MakeNullableT)();
+ typedef int* (*MakeUnannotatedT)();
+ void target(MakeNonnullT (*makeNonnull)(), MakeNullableT (*makeNullable)(),
+ MakeUnannotatedT (*makeUnannotated)()) {
+ *(*makeNonnull)()();
+ *(*makeNullable)()(); // [[unsafe]]
+ *(*makeUnannotated)()();
+ }
+ )cc"));
+
+ // free function returns reference to pointer
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nonnull &makeNonnull();
+ int *_Nullable &makeNullable();
+ int *&makeUnannotated();
+ void target() {
+ *makeNonnull();
+ *makeNullable(); // [[unsafe]]
+ *makeUnannotated();
+ }
+ )cc"));
+
+ // function called in loop
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nullable makeNullable();
+ bool makeBool();
+ void target() {
+ bool first = true;
+ while (true) {
+ int *x = makeNullable();
+ if (first && x == nullptr) return;
+ first = false;
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallExprParamAssignment) {
+ // free function with single param
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void takeNonnull(int *_Nonnull);
+ void takeNullable(int *_Nullable);
+ void takeUnannotated(int *);
+ void target(int *_Nonnull ptr_nonnull, int *_Nullable ptr_nullable,
+ int *ptr_unannotated) {
+ takeNonnull(nullptr); // [[unsafe]]
+ takeNonnull(ptr_nonnull);
+ takeNonnull(ptr_nullable); // [[unsafe]]
+ takeNonnull(ptr_unannotated);
+
+ takeNullable(nullptr);
+ takeNullable(ptr_nonnull);
+ takeNullable(ptr_nullable);
+ takeNullable(ptr_unannotated);
+
+ takeUnannotated(nullptr);
+ takeUnannotated(ptr_nonnull);
+ takeUnannotated(ptr_nullable);
+ takeUnannotated(ptr_unannotated);
+ }
+ )cc"));
+
+ // free function with multiple params of mixed nullability
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void takeMixed(int *, int *_Nullable, int *_Nonnull);
+ void target() {
+ takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
+ }
+ )cc"));
+
+ // member function
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ void takeNonnull(int *_Nonnull);
+ void takeNullable(int *_Nullable);
+ void takeUnannotated(int *);
+ };
+ void target(Foo foo) {
+ foo.takeNonnull(nullptr); // [[unsafe]]
+ foo.takeNullable(nullptr);
+ foo.takeUnannotated(nullptr);
+ }
+ )cc"));
+
+ // function pointer
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(void (*takeNonnull)(int *_Nonnull),
+ void (*takeNullable)(int *_Nullable),
+ void (*takeUnannotated)(int *)) {
+ takeNonnull(nullptr); // [[unsafe]]
+ takeNullable(nullptr);
+ takeUnannotated(nullptr);
+ }
+ )cc"));
+
+ // pointer to function pointer
+ //
+ // TODO(b/233582219): Fix false negative. Implement support for retrieving
+ // parameter types from a pointer to function pointer.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(void (**takeNonnull)(int *_Nonnull),
+ void (**takeNullable)(int *_Nullable),
+ void (**takeUnannotated)(int *)) {
+ (*takeNonnull)(nullptr); // false-negative
+ (*takeNullable)(nullptr);
+ (*takeUnannotated)(nullptr);
+ }
+ )cc"));
+
+ // function returned from function
+ //
+ // TODO(b/233582219): Fix false negative. Implement support for retrieving
+ // parameter types for functions returned by another function.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ typedef void (*takeNonnullF)(int *_Nonnull);
+ typedef void (*takeNullableF)(int *_Nullable);
+ typedef void (*takeUnannotatedF)(int *);
+ void target(takeNonnullF (*takeNonnull)(), takeNullableF (*takeNullable)(),
+ takeUnannotatedF (*takeUnannotated)()) {
+ (*takeNonnull)()(nullptr); // false-negative
+ (*takeNullable)()(nullptr);
+ (*takeUnannotated)()(nullptr);
+ }
+ )cc"));
+
+ // passing a reference to a nonnull pointer
+ //
+ // TODO(b/233582219): Fix false negative. When the nonnull pointer is passed
+ // by reference into the callee which takes a nullable parameter, its value
+ // may be changed to null, making it unsafe to dereference when we return from
+ // the function call. Some possible approaches for handling this case:
+ // (1) Disallow passing a nonnull pointer as a nullable reference - and warn
+ // at the function call.
+ // (2) Assume in worst case the nonnull pointer becomes nullable after the
+ // call - and warn at the dereference.
+ // (3) Sacrifice soundness for reduction in noise, and skip the warning.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void takeNonnullRef(int *_Nonnull &);
+ void takeNullableRef(int *_Nullable &);
+ void takeUnannotatedRef(int *&);
+ void target(int *_Nonnull ptr_nonnull) {
+ takeNonnullRef(ptr_nonnull);
+ *ptr_nonnull;
+
+ // false-negative
+ takeNullableRef(ptr_nonnull);
+ *ptr_nonnull;
+
+ takeUnannotatedRef(ptr_nonnull);
+ *ptr_nonnull;
+ }
+ )cc"));
+
+ // passing a reference to a nullable pointer
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void takeNonnullRef(int *_Nonnull &);
+ void takeNullableRef(int *_Nullable &);
+ void takeUnannotatedRef(int *&);
+ void target(int *_Nullable ptr_nullable) {
+ takeNonnullRef(ptr_nullable); // [[unsafe]]
+ *ptr_nullable; // [[unsafe]]
+
+ takeNullableRef(ptr_nullable);
+ *ptr_nullable; // [[unsafe]]
+
+ takeUnannotatedRef(ptr_nullable);
+ *ptr_nullable; // [[unsafe]]
+ }
+ )cc"));
+
+ // passing a reference to an unannotated pointer
+ //
+ // TODO(b/233582219): Fix false negative. The unannotated pointer should be
+ // considered nullable if it has been used as a nullable pointer.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void takeNonnullRef(int *_Nonnull &);
+ void takeNullableRef(int *_Nullable &);
+ void takeUnannotatedRef(int *&);
+ void target(int *ptr_unannotated) {
+ takeNonnullRef(ptr_unannotated);
+ *ptr_unannotated;
+
+ takeNullableRef(ptr_unannotated);
+ *ptr_unannotated; // false-negative
+
+ takeUnannotatedRef(ptr_unannotated);
+ *ptr_unannotated;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CanOverwritePtrWithPtrCreatedFromRefReturnType) {
+ // Test that if we create a pointer from a function returning a reference, we
+ // can use that pointer to overwrite an existing nullable pointer and make it
+ // nonnull.
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int& get_int();
+
+ void target(int* _Nullable i) {
+ i = &get_int();
+ *i;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CanOverwritePtrWithPtrReturnedByFunction) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull get_int();
+
+ void target(int* _Nullable i) {
+ i = get_int();
+ *i;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallVariadicFunction) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void variadic(int* _Nonnull, ...);
+ void target() {
+ int i = 0;
+ variadic(&i, nullptr, &i);
+ variadic(nullptr, nullptr, &i); // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallMemberOperatorNoParams) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct MakeNonnull {
+ int *_Nonnull operator()();
+ };
+ struct MakeNullable {
+ int *_Nullable operator()();
+ };
+ struct MakeUnannotated {
+ int *operator()();
+ };
+ void target() {
+ MakeNonnull makeNonnull;
+ *makeNonnull();
+
+ MakeNullable makeNullable;
+ *makeNullable(); // [[unsafe]]
+
+ MakeUnannotated makeUnannotated;
+ *makeUnannotated();
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallMemberOperatorOneParam) {
+ // overloaded operator with single param
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ // map<int * _Nonnull, int>
+ struct MapWithNonnullKeys {
+ int &operator[](int *_Nonnull key);
+ };
+ // map<int * _Nullable, int>
+ struct MapWithNullableKeys {
+ int &operator[](int *_Nullable key);
+ };
+ // map<int *, int>
+ struct MapWithUnannotatedKeys {
+ int &operator[](int *key);
+ };
+ void target(int *_Nonnull ptr_nonnull, int *_Nullable ptr_nullable,
+ int *ptr_unannotated) {
+ MapWithNonnullKeys nonnull_keys;
+ nonnull_keys[nullptr] = 42; // [[unsafe]]
+ nonnull_keys[ptr_nonnull] = 42;
+ nonnull_keys[ptr_nullable] = 42; // [[unsafe]]
+ nonnull_keys[ptr_unannotated] = 42;
+
+ MapWithNullableKeys nullable_keys;
+ nullable_keys[nullptr] = 42;
+ nullable_keys[ptr_nonnull] = 42;
+ nullable_keys[ptr_nullable] = 42;
+ nullable_keys[ptr_unannotated] = 42;
+
+ MapWithUnannotatedKeys unannotated_keys;
+ unannotated_keys[nullptr] = 42;
+ unannotated_keys[ptr_nonnull] = 42;
+ unannotated_keys[ptr_nullable] = 42;
+ unannotated_keys[ptr_unannotated] = 42;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallMemberOperatorMultipleParams) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct TakeMixed {
+ void operator()(int *, int *_Nullable, int *_Nonnull);
+ };
+ void target() {
+ TakeMixed takeMixed;
+ takeMixed(nullptr, nullptr, nullptr); // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallFreeOperator) {
+ // No nullability involved. This is just a regression test to make sure we can
+ // process a call to a free overloaded operator.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct A {};
+ A operator+(A, A);
+ void target() {
+ A a;
+ a = a + a;
+ }
+ )cc"));
+}
+
+// Check that we distinguish between the nullability of the return type and
+// parameters.
+TEST(PointerNullabilityTest, DistinguishFunctionReturnTypeAndParams) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nullable callee(int *_Nonnull);
+
+ void target() {
+ int i = 0;
+ __assert_nullability<NK_nullable>(callee(&i));
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, DistinguishMethodReturnTypeAndParams) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct S {
+ int *_Nullable callee(int *_Nonnull);
+ };
+
+ void target(S s) {
+ int i = 0;
+ __assert_nullability<NK_nullable>(s.callee(&i));
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest,
+ ClassTemplate_DistinguishMethodReturnTypeAndParams) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct S {
+ T0 callee(T1);
+ };
+
+ void target(S<int *_Nullable, int *_Nonnull> s) {
+ int i = 0;
+ __assert_nullability<NK_nullable>(s.callee(&i));
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest,
+ CallFunctionTemplate_TemplateArgInReturnTypeHasNullTypeSourceInfo) {
+ // This test sets up a function call where we don't have a `TypeSourceInfo`
+ // for the argument to a template parameter used in the return type.
+ // This is a regression test for a crash that we observed on real-world code.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <class T>
+ struct A {
+ using Type = T;
+ };
+ template <int, class T>
+ typename A<T>::Type f(T);
+ void target() { f<0>(1); }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallFunctionTemplate_PartiallyDeduced) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int, class T>
+ T f(T);
+ void target() { f<0>(1); }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/function_pointers.cc b/nullability/test/function_pointers.cc
new file mode 100644
index 0000000..290ba15
--- /dev/null
+++ b/nullability/test/function_pointers.cc
@@ -0,0 +1,101 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for nullability of function pointers.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, FunctionToPointerDecayIsNonnull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ // Use `static_cast` to force function-to-pointer decay.
+ __assert_nullability<NK_nonnull>(static_cast<void (*)()>(target));
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, CallExplicitlyDereferencedDirectCallee) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void callee();
+ void target() { (*callee)(); }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, AnnotationsInReturnType) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nullable target() {
+ // Use `static_cast` to force function-to-pointer decay.
+ __assert_nullability<NK_nonnull, NK_nullable>(
+ static_cast<int* (*)()>(target));
+ return nullptr;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, AnnotationsInParameters) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable) {
+ // Use `static_cast` to force function-to-pointer decay.
+ __assert_nullability<NK_nonnull, NK_nullable>(
+ static_cast<void (*)(int *)>(target));
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NonnullCallback) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(void (*_Nonnull callback)()) {
+ // Both an explicit dereference and an implicit dereference done by a
+ // function call should be allowed.
+ (*callback)();
+ callback();
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NullableCallback) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(void (*_Nullable callback)()) {
+ // Both an explicit dereference and an implicit dereference done by a
+ // function call should be marked as unsafe.
+ (*callback)(); // [[unsafe]]
+ callback(); // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NonnullCallbackWithoutCalleeDecl) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ using NonnullCallbackType = void (*_Nonnull)();
+ NonnullCallbackType getCallback();
+ void target() {
+ __assert_nullability<NK_nonnull>(getCallback());
+ (*getCallback())();
+ getCallback()();
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NullableCallbackWithoutCalleeDecl) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ using NullableCallbackType = void (*_Nullable)();
+ NullableCallbackType getCallback();
+ void target(bool b) {
+ __assert_nullability<NK_nullable>(getCallback());
+ (*getCallback())(); // [[unsafe]]
+ getCallback()(); // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/initialization.cc b/nullability/test/initialization.cc
new file mode 100644
index 0000000..fd03f7e
--- /dev/null
+++ b/nullability/test/initialization.cc
@@ -0,0 +1,46 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests that check nullability is transferred correctly across initializers.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, TransitiveNullCheck) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x) {
+ int *y = x;
+ *x; // [[unsafe]]
+ if (y) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable x) {
+ int *y = x;
+ *y; // [[unsafe]]
+ if (x) {
+ *y;
+ } else {
+ *y; // [[unsafe]]
+ }
+ *y; // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/merge.cc b/nullability/test/merge.cc
new file mode 100644
index 0000000..e9e2787
--- /dev/null
+++ b/nullability/test/merge.cc
@@ -0,0 +1,317 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for merging different nullability types.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, MergeNullAndNonNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nonnull y, bool b) {
+ int *x = nullptr;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = y;
+ *x;
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNullAndNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable y, bool b) {
+ int *x = nullptr;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = y;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNullAndUnknown) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *y, bool b) {
+ int *x = nullptr;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = y;
+ *x;
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNonNullAndNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nonnull y, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = nullptr;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNonNullAndNonNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nonnull y, int *_Nonnull z, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = z;
+ *x;
+ }
+ *x;
+ if (b) {
+ *x;
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNonNullAndNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nonnull y, int *_Nullable z, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = z;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNonNullAndUnknown) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nonnull y, int *z, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = z;
+ *x;
+ }
+ *x;
+ if (b) {
+ *x;
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNullableAndNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable y, bool b) {
+ int *x = y;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = nullptr;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNullableAndNonNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable y, int *_Nonnull z, bool b) {
+ int *x = y;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = z;
+ *x;
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNullableAndNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable y, int *_Nullable z, bool b) {
+ int *x = y;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = z;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeNullableAndUnknown) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable y, int *z, bool b) {
+ int *x = y;
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ x = z;
+ *x;
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x;
+ } else {
+ *x; // [[unsafe]]
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeUnknownAndNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *y, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = nullptr;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeUnknownAndNonNull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *y, int *_Nonnull z, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = z;
+ *x;
+ }
+ *x;
+ if (b) {
+ *x;
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeUnknownAndNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *y, int *_Nullable z, bool b) {
+ int *x = y;
+ *x;
+ if (b) {
+ *x;
+ x = z;
+ *x; // [[unsafe]]
+ }
+ *x; // [[unsafe]]
+ if (b) {
+ *x; // [[unsafe]]
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MergeUnknownAndUnknown) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *y, int *z, bool b) {
+ int *x = y;
+ if (b) {
+ *x;
+ x = z;
+ *x;
+ }
+ *x;
+ if (b) {
+ *x;
+ } else {
+ *x;
+ }
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/operator_new.cc b/nullability/test/operator_new.cc
new file mode 100644
index 0000000..585efc3
--- /dev/null
+++ b/nullability/test/operator_new.cc
@@ -0,0 +1,74 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for operator new.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, ThrowingNew) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ int *p = new int;
+ *p;
+ delete p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, AssignFromNewMakesNullableNonnull) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target() {
+ int* _Nullable p = nullptr;
+ p = new int;
+ *p;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NoThrowNew) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+#include <new>
+ void target() {
+ int* p = new (std::nothrow) int;
+ *p; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, AssignFromNoThrowNewMakesNonnullNullable) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+#include <new>
+ void target() {
+ int i = 0;
+ int* _Nonnull p = &i;
+ p = new (std::nothrow) int;
+ *p; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NewPreservesNullabilityOnAllocatedType) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+#include <new>
+ void target() {
+ __assert_nullability<NK_nonnull, NK_nonnull>(new (int* _Nonnull));
+ __assert_nullability<NK_nonnull, NK_nullable>(new (int* _Nullable));
+ __assert_nullability<NK_nullable, NK_nonnull>(
+ new (std::nothrow)(int* _Nonnull));
+ __assert_nullability<NK_nullable, NK_nullable>(
+ new (std::nothrow)(int* _Nullable));
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/parens.cc b/nullability/test/parens.cc
new file mode 100644
index 0000000..926a148
--- /dev/null
+++ b/nullability/test/parens.cc
@@ -0,0 +1,53 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for parenthesized expressions.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, ParenthesizedExpressions) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+ T0 getT0();
+ };
+
+ void target(Struct1Arg<int *_Nullable> p) {
+ *(p).arg0; // [[unsafe]]
+ *((p)).arg0; // [[unsafe]]
+ *(p).getT0(); // [[unsafe]]
+ *(((p))).getT0(); // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, typename T2>
+ struct Struct3ArgWithInt {
+ T1 arg1;
+ T2 arg2;
+
+ T1 getT1();
+ T2 getT2();
+ };
+
+ void target(Struct3ArgWithInt<1, int *, int *_Nullable> p) {
+ *(((p)).arg1);
+ *(((p))).getT1();
+ (*((p)).arg2); // [[unsafe]]
+ *(((((p)))).getT2()); // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/pointer_arithmetic.cc b/nullability/test/pointer_arithmetic.cc
new file mode 100644
index 0000000..8af4adf
--- /dev/null
+++ b/nullability/test/pointer_arithmetic.cc
@@ -0,0 +1,43 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for pointer arithmetic.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+// TODO: fix false positives due to unsupported PointerValues in the framework.
+TEST(PointerNullabilityTest, PointerArithmetic) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void target(int *_Nullable p, int *_Nonnull q, int *r) {
+ *++p; // [[unsafe]]
+ *p++; // [[unsafe]]
+ *--p; // [[unsafe]]
+ *p--; // [[unsafe]]
+ *+p; // [[unsafe]]
+
+ *++q; // [[unsafe]] TODO: fix false positive
+ *q++; // [[unsafe]] TODO: fix false positive
+ *--q; // [[unsafe]] TODO: fix false positive
+ *q--; // [[unsafe]] TODO: fix false positive
+ *+q; // [[unsafe]] TODO: fix false positive
+
+ *++r; // [[unsafe]] TODO: fix false positive
+ *r++; // [[unsafe]] TODO: fix false positive
+ *--r; // [[unsafe]] TODO: fix false positive
+ *r--; // [[unsafe]] TODO: fix false positive
+ *+r; // [[unsafe]] TODO: fix false positive
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/return_statements.cc b/nullability/test/return_statements.cc
new file mode 100644
index 0000000..4672c1e
--- /dev/null
+++ b/nullability/test/return_statements.cc
@@ -0,0 +1,141 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for return statements.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, ReturnStatements) {
+ // nonnull return type
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull target() {
+ return nullptr; // [[unsafe]]
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull target(int* _Nonnull ptr_nonnull) {
+ return ptr_nonnull;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull target(int* _Nullable ptr_nullable) {
+ return ptr_nullable; // [[unsafe]]
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nonnull target(int *ptr_unannotated) {
+ return ptr_unannotated;
+ }
+ )cc"));
+
+ // nullable return type
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nullable target() { return nullptr; }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nullable target(int* _Nonnull ptr_nonnull) {
+ return ptr_nonnull;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nullable target(int* _Nullable ptr_nullable) {
+ return ptr_nullable;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nullable target(int *ptr_unannotated) {
+ return ptr_unannotated;
+ }
+ )cc"));
+
+ // unannotated return type
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* target() { return nullptr; }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* target(int* _Nonnull ptr_nonnull) {
+ return ptr_nonnull;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* target(int* _Nullable ptr_nullable) {
+ return ptr_nullable;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *target(int *ptr_unannotated) {
+ return ptr_unannotated;
+ }
+ )cc"));
+
+ // multiple return statements
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull target(bool b, int* _Nonnull ptr_nonnull) {
+ if (b) {
+ return nullptr; // [[unsafe]]
+ }
+ return ptr_nonnull;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull target(int* _Nullable ptr_nullable,
+ int* _Nonnull ptr_nonnull) {
+ if (ptr_nullable) {
+ return ptr_nullable;
+ }
+ return ptr_nonnull;
+ }
+ )cc"));
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int* _Nonnull target(int* _Nullable ptr_nullable_1,
+ int* _Nullable ptr_nullable_2) {
+ if (ptr_nullable_1) {
+ return ptr_nullable_2; // [[unsafe]]
+ }
+ return ptr_nullable_1; // [[unsafe]]
+ }
+ )cc"));
+
+ // return result of merging 2 pointer values
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nonnull target(bool b, int i) {
+ int *ptr;
+ if (b) {
+ ptr = &i;
+ } else {
+ ptr = nullptr;
+ }
+ return ptr; // [[unsafe]]
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, NonPointerReturnType) {
+ checkDiagnostics(R"cc(
+ struct S {
+ int* p;
+ int*& target() { return p; }
+ };
+ )cc");
+
+ checkDiagnostics(R"cc(
+ struct S {
+ int* _Nullable p;
+ int* _Nonnull& target() {
+ return p; // TODO: Fix false negative.
+ }
+ };
+ )cc");
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/templates.cc b/nullability/test/templates.cc
new file mode 100644
index 0000000..2de241a
--- /dev/null
+++ b/nullability/test/templates.cc
@@ -0,0 +1,1170 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for nullability annotations in template arguments.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+// TODO: Fix false negatives.
+TEST(PointerNullabilityTest, ClassTemplateInstantiation) {
+ // Class template specialization with one argument initialised as _Nullable.
+ // We test types that contain both nullability that is substituted into the
+ // template argument and nullability that is spelt inside the template. That
+ // is, we should be able to accurately store nullabilities from different
+ // sources in a single nullability vector.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+ T0 *unknownTPtr;
+ T0 *_Nullable nullableTPtr;
+ T0 *_Nonnull nonnullTPtr;
+
+ T0 getT();
+ T0 *getUnknownTPtr();
+ T0 *_Nullable getNullableTPtr();
+ T0 *_Nonnull getNonnullTPtr();
+ };
+ void target(Struct1Arg<int *_Nullable> p) {
+ *p.arg0; // [[unsafe]]
+ *p.unknownTPtr;
+ *p.nullableTPtr; // [[unsafe]]
+ *p.nonnullTPtr;
+ **p.unknownTPtr; // [[unsafe]]
+ **p.nullableTPtr; // [[unsafe]]
+ **p.nonnullTPtr; // [[unsafe]]
+
+ *p.getT(); // [[unsafe]]
+ *p.getUnknownTPtr();
+ *p.getNullableTPtr(); // [[unsafe]]
+ *p.getNonnullTPtr();
+ **p.getUnknownTPtr(); // [[unsafe]]
+ **p.getNullableTPtr(); // [[unsafe]]
+ **p.getNonnullTPtr(); // [[unsafe]]
+ }
+ )cc"));
+
+ // Class template specialization with one argument initialised as _Nonnull.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+ T0 *unknownTPtr;
+ T0 *_Nullable nullableTPtr;
+ T0 *_Nonnull nonnullTPtr;
+
+ T0 getT();
+ T0 *getUnknownTPtr();
+ T0 *_Nullable getNullableTPtr();
+ T0 *_Nonnull getNonnullTPtr();
+ };
+
+ void target(Struct1Arg<int *_Nonnull> p) {
+ *p.getT();
+ *p.getUnknownTPtr();
+ *p.getNullableTPtr(); // [[unsafe]]
+ *p.getNonnullTPtr();
+ **p.getUnknownTPtr();
+ **p.getNullableTPtr(); // [[unsafe]]
+ **p.getNonnullTPtr();
+
+ *p.arg0;
+ *p.unknownTPtr;
+ *p.nullableTPtr; // [[unsafe]]
+ *p.nonnullTPtr;
+ **p.unknownTPtr;
+ **p.nullableTPtr; // [[unsafe]]
+ **p.nonnullTPtr;
+ }
+ )cc"));
+
+ // Class template specialization with one argument initialised without
+ // nullability annotation.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+ T0 *unknownTPtr;
+ T0 *_Nullable nullableTPtr;
+ T0 *_Nonnull nonnullTPtr;
+ T0 getT();
+
+ T0 *getUnknownTPtr();
+ T0 *_Nullable getNullableTPtr();
+ T0 *_Nonnull getNonnullTPtr();
+ };
+
+ void target(Struct1Arg<int *> p) {
+ *p.getT();
+ *p.getUnknownTPtr();
+ *p.getNullableTPtr(); // [[unsafe]]
+ *p.getNonnullTPtr();
+ **p.getUnknownTPtr();
+ **p.getNullableTPtr(); // [[unasfe]]
+ **p.getNonnullTPtr();
+
+ *p.arg0;
+ *p.unknownTPtr;
+ *p.nullableTPtr; // [[unsafe]]
+ *p.nonnullTPtr;
+ **p.unknownTPtr;
+ **p.nullableTPtr; // [[unsafe]]
+ **p.nonnullTPtr;
+ }
+ )cc"));
+
+ // Class template specialization with two arguments, whose second argument is
+ // initialized as nullable.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 arg0;
+ T0 *unknownT0Ptr;
+ T0 *_Nullable nullableT0Ptr;
+ T0 *_Nonnull nonnullT0Ptr;
+
+ T1 arg1;
+ T1 *unknownT1Ptr;
+ T1 *_Nullable nullableT1Ptr;
+ T1 *_Nonnull nonnullT1Ptr;
+
+ T0 getT0();
+ T0 *getUnknownT0Ptr();
+ T0 *_Nullable getNullableT0Ptr();
+ T0 *_Nonnull getNonnullT0Ptr();
+
+ T1 getT1();
+ T1 *getUnknownT1Ptr();
+ T1 *_Nullable getNullableT1Ptr();
+ T1 *_Nonnull getNonnullT1Ptr();
+ };
+
+ void target(Struct2Arg<int *_Nonnull, double *_Nullable> p) {
+ *p.arg0;
+ *p.arg1; // [[unsafe]]
+
+ *p.unknownT0Ptr;
+ *p.nullableT0Ptr; // [[unsafe]]
+ *p.nonnullT0Ptr;
+
+ *p.unknownT1Ptr;
+ *p.nullableT1Ptr; // [[unsafe]]
+ *p.nonnullT1Ptr;
+
+ *p.getUnknownT0Ptr();
+ *p.getNullableT0Ptr(); // [[unsafe]]
+ *p.getNonnullT0Ptr();
+
+ *p.getUnknownT1Ptr();
+ *p.getNullableT1Ptr(); // [[unsafe]]
+ *p.getNonnullT1Ptr();
+ }
+ )cc"));
+
+ // Class template specialization with 5 arguments with interleaved
+ // nullable/nonnull/unknown.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1, typename T2, typename T3, typename T4>
+ struct Struct5Arg {
+ T0 arg0;
+ T1 arg1;
+ T2 arg2;
+ T3 arg3;
+ T4 arg4;
+
+ T0 getT0();
+ T1 getT1();
+ T2 getT2();
+ T3 getT3();
+ T4 getT4();
+ };
+ void target(Struct5Arg<int* _Nullable, double* _Nonnull, float*,
+ double* _Nullable, int* _Nonnull>
+ p) {
+ *p.arg0; // [[unsafe]]
+ *p.arg1;
+ *p.arg2;
+ *p.arg3; // [[unsafe]]
+ *p.arg4;
+
+ *p.getT0(); // [[unsafe]]
+ *p.getT1();
+ *p.getT2();
+ *p.getT3(); // [[unsafe]]
+ *p.getT4();
+ }
+ )cc"));
+
+ // Class template specialization with 5 arguments with interleaved
+ // nullable/nonnull/unknown/const.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1, typename T2, typename T3, typename T4>
+ struct Struct5Arg {
+ T0 arg0;
+ T1 arg1;
+ T2 arg2;
+ T3 arg3;
+ T4 arg4;
+
+ T0 getT0();
+ T1 getT1();
+ T2 getT2();
+ T3 getT3();
+ T4 getT4();
+ };
+ void target(Struct5Arg<int* const _Nullable, double const* const _Nonnull,
+ float*, double const* const _Nullable, int* _Nonnull>
+ p) {
+ *p.arg0; // [[unsafe]]
+ *p.arg1;
+ *p.arg2;
+ *p.arg3; // [[unsafe]]
+ *p.arg4;
+
+ *p.getT0(); // [[unsafe]]
+ *p.getT1();
+ *p.getT2();
+ *p.getT3(); // [[unsafe]]
+ *p.getT4();
+ }
+ )cc"));
+
+ // Class template specialization with interleaved int and type template
+ // parameters.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, int I2, typename T3, int I4, typename T5>
+ struct Struct6ArgWithInt {
+ T1 arg1;
+ T3 arg3;
+ T5 arg5;
+
+ T1 getT1();
+ T3 getT3();
+ T5 getT5();
+ };
+ void target(
+ Struct6ArgWithInt<0, int *_Nullable, 1, int *_Nullable, 2, int *> &x) {
+ *x.arg1; // [[unsafe]]
+ *x.arg3; // [[unsafe]]
+ *x.arg5;
+
+ *x.getT1(); // [[unsafe]]
+ *x.getT3(); // [[unsafe]]
+ *x.getT5();
+ }
+ )cc"));
+}
+
+// TODO: Fix false positives and false negatives.
+TEST(PointerNullabilityTest,
+ ClassTemplateInstantiationWithStructsAsParameters) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct3IntPtrs {
+ int* unknown;
+ int* _Nullable nullable;
+ int* _Nonnull nonnull;
+
+ int* getUnknown();
+ int* _Nullable getNullable();
+ int* _Nonnull getNonnull();
+ };
+
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+ T0 getT0();
+ };
+
+ void target(Struct1Arg<Struct3IntPtrs> p) {
+ *p.arg0.unknown;
+ *p.arg0.nullable; // [[unsafe]]
+ *p.arg0.nonnull;
+
+ *p.arg0.getUnknown();
+ *p.arg0.getNullable(); // [[unsafe]]
+ *p.arg0.getNonnull();
+
+ *p.getT0().unknown; // [[unsafe]] TODO: fix false positive.
+ *p.getT0().nullable; // [[unsafe]]
+ *p.getT0().nonnull; // [[unsafe]] TODO: fix false positive.
+
+ *p.getT0().getUnknown();
+ *p.getT0().getNullable(); // [[unsafe]]
+ *p.getT0().getNonnull();
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct1UnknownArg {
+ char* unknownChar;
+
+ char* getUnknownChar();
+ };
+
+ struct Struct1NullableArg {
+ char* _Nullable nullableChar;
+
+ char* _Nullable getNullableChar();
+ };
+
+ struct Struct1NonnullArg {
+ char* _Nonnull nonnullChar;
+
+ char* _Nonnull getNonnullChar();
+ };
+
+ struct StructLotsOfArgs {
+ int num;
+ long long* unknownLongLong;
+ double* _Nullable nullableDouble;
+ float* _Nonnull nonnullFloat;
+ short* unknownShort;
+ unsigned int* _Nullable nullableUInt;
+ bool* _Nullable nullableBool;
+
+ long long* getUnknownLongLong();
+ double* _Nullable getNullableDouble();
+ float* _Nonnull getNonnullFloat();
+ short* getUnknownShort();
+ unsigned int* _Nullable getNullableUInt();
+ bool* _Nullable getNullableBool();
+ };
+
+ template <typename T0, typename T1, typename T2, typename T3>
+ struct Struct4Arg {
+ T0 arg0;
+ T1 arg1;
+ T2 arg2;
+ T3 arg3;
+
+ T0 getT0();
+ T1 getT1();
+ T2 getT2();
+ T3 getT3();
+ };
+
+ void target(Struct4Arg<Struct1UnknownArg, Struct1NullableArg,
+ Struct1NonnullArg, StructLotsOfArgs>
+ p) {
+ *p.arg0.unknownChar;
+ *p.arg1.nullableChar; // [[unsafe]]
+ *p.arg2.nonnullChar;
+ *p.arg3.unknownLongLong;
+ *p.arg3.nullableDouble; // [[unsafe]]
+ *p.arg3.nonnullFloat;
+ *p.arg3.unknownShort;
+ *p.arg3.nullableUInt; // [[unsafe]]
+ *p.arg3.nullableBool; // [[unsafe]]
+
+ *p.arg0.getUnknownChar();
+ *p.arg1.getNullableChar(); // [[unsafe]]
+ *p.arg2.getNonnullChar();
+ *p.arg3.getUnknownLongLong();
+ *p.arg3.getNullableDouble(); // [[unsafe]]
+ *p.arg3.getNonnullFloat();
+ *p.arg3.getUnknownShort();
+ *p.arg3.getNullableUInt(); // [[unsafe]]
+ *p.arg3.getNullableBool(); // [[unsafe]]
+
+ *p.getT0().unknownChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT1().nullableChar; // [[unsafe]]
+ *p.getT2().nonnullChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT3().unknownLongLong; // [[unsafe]] TODO: fix false positive.
+ *p.getT3().nullableDouble; // [[unsafe]]
+ *p.getT3().nonnullFloat; // [[unsafe]] TODO: fix false positive.
+ *p.getT3().unknownShort; // [[unsafe]] TODO: fix false positive.
+ *p.getT3().nullableUInt; // [[unsafe]]
+ *p.getT3().nullableBool; // [[unsafe]]
+
+ *p.getT0().getUnknownChar();
+ *p.getT1().getNullableChar(); // [[unsafe]]
+ *p.getT2().getNonnullChar();
+ *p.getT3().getUnknownLongLong();
+ *p.getT3().getNullableDouble(); // [[unsafe]]
+ *p.getT3().getNonnullFloat();
+ *p.getT3().getUnknownShort();
+ *p.getT3().getNullableUInt(); // [[unsafe]]
+ *p.getT3().getNullableBool(); // [[unsafe]]
+ }
+ )cc"));
+
+ // With const arguments and int template parameter.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct1UnknownArg {
+ char* const constUnknownChar;
+ char const* unknownConstChar;
+ char const* const constUnknownConstChar;
+
+ char* const getConstUnknownChar();
+ char const* getUnknownConstChar();
+ char const* const getConstUnknownConstChar();
+ };
+
+ struct Struct1NullableArg {
+ char* const _Nullable constNullableChar;
+ char const* _Nullable nullableConstChar;
+ char const* const _Nullable constNullableConstChar;
+
+ char* const _Nullable getConstNullableChar();
+ char const* _Nullable getNullableConstChar();
+ char* const* _Nullable getConstNullableConstChar();
+ };
+
+ struct Struct1NonnullArg {
+ char* const _Nonnull constNonnullChar;
+ char const* _Nonnull nonnullConstChar;
+ char const* const _Nonnull constNonnullConstChar;
+
+ char* const _Nonnull getConstNonnullChar();
+ char const* _Nonnull getNonnullConstChar();
+ char const* const _Nonnull getConstNonnullConstChar();
+ };
+
+ template <int I0, typename T1, typename T2, typename T3>
+ struct Struct4Arg {
+ T1 arg1;
+ T2 arg2;
+ T3 arg3;
+
+ T1 getT1();
+ T2 getT2();
+ T3 getT3();
+ };
+
+ void target(
+ Struct4Arg<4, Struct1UnknownArg, Struct1NullableArg, Struct1NonnullArg>
+ p) {
+ *p.arg1.constUnknownChar;
+ *p.arg1.unknownConstChar;
+ *p.arg1.constUnknownConstChar;
+ *p.arg2.constNullableChar; // [[unsafe]]
+ *p.arg2.nullableConstChar; // [[unsafe]]
+ *p.arg2.constNullableConstChar; // [[unsafe]]
+ *p.arg3.constNonnullChar;
+ *p.arg3.nonnullConstChar;
+ *p.arg3.constNonnullConstChar;
+
+ *p.arg1.getConstUnknownChar();
+ *p.arg1.getUnknownConstChar();
+ *p.arg1.getConstUnknownConstChar();
+ *p.arg2.getConstNullableChar(); // [[unsafe]]
+ *p.arg2.getNullableConstChar(); // [[unsafe]]
+ *p.arg2.getConstNullableConstChar(); // [[unsafe]]
+ *p.arg3.getConstNonnullChar();
+ *p.arg3.getNonnullConstChar();
+ *p.arg3.getConstNonnullConstChar();
+
+ *p.getT1().constUnknownChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT1().unknownConstChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT1().constUnknownConstChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT2().constNullableChar; // [[unsafe]]
+ *p.getT2().nullableConstChar; // [[unsafe]]
+ *p.getT2().constNullableConstChar; // [[unsafe]]
+ *p.getT3().constNonnullChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT3().nonnullConstChar; // [[unsafe]] TODO: fix false positive.
+ *p.getT3().constNonnullConstChar; // [[unsafe]] TODO: fix false positive.
+
+ *p.getT1().getConstUnknownChar();
+ *p.getT1().getUnknownConstChar();
+ *p.getT1().getConstUnknownConstChar();
+ *p.getT2().getConstNullableChar(); // [[unsafe]]
+ *p.getT2().getNullableConstChar(); // [[unsafe]]
+ *p.getT2().getConstNullableConstChar(); // [[unsafe]]
+ *p.getT3().getConstNonnullChar();
+ *p.getT3().getNonnullConstChar();
+ *p.getT3().getConstNonnullConstChar();
+ }
+ )cc"));
+}
+
+// TODO: Fix false negatives.
+TEST(PointerNullabilityTest, MemberFunctionTemplateOfConcreteStruct) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct S {
+ template <typename T0>
+ T0 getT0();
+ };
+
+ void target(S p) {
+ *p.getT0<int *>();
+ *p.getT0<int *_Nonnull>();
+ *p.getT0<int *_Nullable>(); // TODO: fix false negative.
+
+ *p.getT0<int const *>();
+ *p.getT0<int *const>();
+ *p.getT0<int const *const>();
+ *p.getT0<int const *_Nonnull>();
+ *p.getT0<int *const _Nonnull>();
+ *p.getT0<int const *const _Nonnull>();
+ *p.getT0<int const *_Nullable>(); // TODO: fix false negative.
+ *p.getT0<int *const _Nullable>(); // TODO: fix false negative.
+ *p.getT0<int const *const _Nullable>(); // TODO: fix false negative.
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct S {
+ template <int I0, typename T1, int I2>
+ T1 getT1();
+ };
+
+ void target(S p) {
+ *p.getT1<0, int *, 1>();
+ *p.getT1<2147483647, int *_Nonnull, -2147483647>();
+ *p.getT1<4, int *_Nullable, 4>(); // TODO: fix false negative.
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, MemberFunctionTemplateOfTemplateStruct) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0>
+ struct S {
+ template <typename TN1>
+ TN1 getTN1();
+ };
+
+ void target(S<int> p) {
+ *p.getTN1<int *>();
+ *p.getTN1<int *_Nonnull>();
+ *p.getTN1<int *_Nullable>(); // TODO: fix false negative.
+
+ *p.getTN1<int const *>();
+ *p.getTN1<int *const>();
+ *p.getTN1<int const *const>();
+ *p.getTN1<int const *_Nonnull>();
+ *p.getTN1<int *const _Nonnull>();
+ *p.getTN1<int const *const _Nonnull>();
+ *p.getTN1<int const *_Nullable>(); // TODO: fix false negative.
+ *p.getTN1<int *const _Nullable>(); // TODO: fix false negative.
+ *p.getTN1<int const *const _Nullable>(); // TODO: fix false negative.
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0>
+ struct S {
+ template <int IN1, typename TN2, int IN3>
+ TN2 getTN2();
+ };
+
+ void target(S<int> p) {
+ *p.getTN2<0, int *, 1>();
+ *p.getTN2<2147483647, int *_Nonnull, -2147483647>();
+ *p.getTN2<4, int *_Nullable, 4>(); // TODO: fix false negative.
+ }
+ )cc"));
+}
+
+// TODO: Fix false positives.
+TEST(PointerNullabilityTest,
+ ClassTemplateInstantiationWithTemplateStructsAsParameters) {
+ // Class template with another class template as parameter
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 arg0;
+ T1 arg1;
+ };
+
+ template <typename TN0, typename TN1>
+ struct Struct2ArgNested {
+ Struct2Arg<TN1, Struct2Arg<TN0, TN1>>* arg0;
+ Struct2Arg<TN1, Struct2Arg<TN0, TN1>>* _Nullable arg1;
+ };
+
+ void target(Struct2ArgNested<int* _Nonnull, double* _Nullable> p) {
+ *p.arg0;
+ *p.arg1; // [[unsafe]]
+
+ *p.arg0->arg0;
+ *p.arg0->arg1.arg0;
+ *p.arg0->arg1.arg1;
+ }
+ )cc"));
+
+ // Class template with itself as parameter
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 arg0;
+ T1 arg1;
+ };
+
+ void target(Struct2Arg<Struct2Arg<int*, int* _Nullable>, int* _Nonnull> p) {
+ *p.arg0.arg0;
+ *p.arg0.arg1; // [[unsafe]]
+ *p.arg1;
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1, typename T2, typename T3, typename T4>
+ struct Struct5Arg {
+ T0 arg0;
+ T1 arg1;
+ T2 arg2;
+ T3 arg3;
+ T4 arg4;
+ };
+
+ void
+ target(Struct5Arg<
+ Struct5Arg<
+ Struct5Arg<Struct5Arg<int* _Nullable, int* _Nonnull,
+ float* _Nullable, int*, double* _Nullable>,
+ int, int, int, int* _Nullable>,
+ int, int* _Nullable, int, int>,
+ int, int* _Nullable, int* _Nonnull, int>
+ p) {
+ *p.arg0.arg0.arg0.arg0; // [[unsafe]]
+ *p.arg0.arg0.arg0.arg1; // [[unsafe]] TODO: fix false positive.
+ *p.arg0.arg0.arg0.arg2; // [[unsafe]]
+ *p.arg0.arg0.arg0.arg3; // [[unsafe]] TODO: fix false positive.
+ *p.arg0.arg0.arg0.arg4; // [[unsafe]]
+ *p.arg0.arg0.arg4; // [[unsafe]]
+ *p.arg0.arg2; // [[unsafe]]
+ *p.arg2; // [[unsafe]]
+ *p.arg3;
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, typename T2, typename T3, int I4,
+ typename T5, typename T6>
+ struct Struct7ArgWithInt {
+ T1 arg1;
+ T2 arg2;
+ T3 arg3;
+ T5 arg5;
+ T6 arg6;
+ };
+
+ void target(Struct7ArgWithInt<
+ 0,
+ Struct7ArgWithInt<
+ 2147483647,
+ Struct7ArgWithInt<
+ 0,
+ Struct7ArgWithInt<-2147483647, int* _Nullable,
+ int* _Nonnull, float* _Nullable, 0,
+ int*, double* _Nullable>,
+ int, int, 1, int, int* _Nullable>,
+ int, int* _Nullable, 2147483647, int, int>,
+ int, int* _Nullable, 2, int* _Nonnull, int>
+ p) {
+ *p.arg1.arg1.arg1.arg1; // [[unsafe]]
+ *p.arg1.arg1.arg1.arg2; // [[unsafe]] TODO: fix false positive.
+ *p.arg1.arg1.arg1.arg3; // [[unsafe]]
+ *p.arg1.arg1.arg1.arg5; // [[unsafe]] TODO: fix false positive.
+ *p.arg1.arg1.arg1.arg6; // [[unsafe]]
+ *p.arg1.arg1.arg6; // [[unsafe]]
+ *p.arg1.arg3; // [[unsafe]]
+ *p.arg3; // [[unsafe]]
+ *p.arg5;
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest,
+ ClassTemplateInstantiationWithPointersToStructsAsParameters) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct3IntPtrs {
+ int* unknown;
+ int* _Nullable nullable;
+ int* _Nonnull nonnull;
+
+ int* getUnknown();
+ int* _Nullable getNullable();
+ int* _Nonnull getNonnull();
+ };
+
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+
+ T0 getT0();
+ };
+
+ void target(Struct1Arg<Struct3IntPtrs*> p) {
+ *p.arg0->unknown;
+ *p.arg0->nullable; // [[unsafe]]
+ *p.arg0->nonnull;
+
+ *p.arg0->getUnknown();
+ *p.arg0->getNullable(); // [[unsafe]]
+ *p.arg0->getNonnull();
+
+ *p.getT0()->unknown;
+ *p.getT0()->nullable; // [[unsafe]]
+ *p.getT0()->nonnull;
+
+ *p.getT0()->getUnknown();
+ *p.getT0()->getNullable(); // [[unsafe]]
+ *p.getT0()->getNonnull();
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct3IntPtrs {
+ int* unknown;
+ int* _Nullable nullable;
+ int* _Nonnull nonnull;
+
+ int* getUnknown();
+ int* _Nullable getNullable();
+ int* _Nonnull getNonnull();
+ };
+
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+
+ T0 getT0();
+ };
+
+ void target(Struct1Arg<Struct3IntPtrs* _Nullable> p) {
+ *p.arg0->unknown; // [[unsafe]]
+ *p.arg0->nullable; // [[unsafe]]
+ *p.arg0->nonnull; // [[unsafe]]
+
+ *p.arg0->getUnknown(); // [[unsafe]]
+ *p.arg0->getNullable(); // [[unsafe]]
+ *p.arg0->getNonnull(); // [[unsafe]]
+
+ *p.getT0()->unknown; // [[unsafe]]
+ *p.getT0()->nullable; // [[unsafe]]
+ *p.getT0()->nonnull; // [[unsafe]]
+
+ *p.getT0()->getUnknown(); // [[unsafe]]
+ *p.getT0()->getNullable(); // [[unsafe]]
+ *p.getT0()->getNonnull(); // [[unsafe]]
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct3IntPtrs {
+ int* unknown;
+ int* _Nullable nullable;
+ int* _Nonnull nonnull;
+
+ int* getUnknown();
+ int* _Nullable getNullable();
+ int* _Nonnull getNonnull();
+ };
+
+ template <typename T0>
+ struct Struct1Arg {
+ T0 arg0;
+
+ T0 getT0();
+ };
+
+ void target(Struct1Arg<Struct3IntPtrs* _Nonnull> p) {
+ *p.arg0->unknown;
+ *p.arg0->nullable; // [[unsafe]]
+ *p.arg0->nonnull;
+
+ *p.arg0->getUnknown();
+ *p.arg0->getNullable(); // [[unsafe]]
+ *p.arg0->getNonnull();
+
+ *p.getT0()->unknown;
+ *p.getT0()->nullable; // [[unsafe]]
+ *p.getT0()->nonnull;
+
+ *p.getT0()->getUnknown();
+ *p.getT0()->getNullable(); // [[unsafe]]
+ *p.getT0()->getNonnull();
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Struct3IntPtrs {
+ int* unknown;
+ int* _Nullable nullable;
+ int* _Nonnull nonnull;
+
+ int* getUnknown();
+ int* _Nullable getNullable();
+ int* _Nonnull getNonnull();
+ };
+
+ template <int I0, typename T1>
+ struct Struct2Arg {
+ T1 arg1;
+
+ T1 getT1();
+ };
+
+ void target(Struct2Arg<0, Struct3IntPtrs*> p) {
+ *p.arg1->unknown;
+ *p.arg1->nullable; // [[unsafe]]
+ *p.arg1->nonnull;
+
+ *p.arg1->getUnknown();
+ *p.arg1->getNullable(); // [[unsafe]]
+ *p.arg1->getNonnull();
+
+ *p.getT1()->unknown;
+ *p.getT1()->nullable; // [[unsafe]]
+ *p.getT1()->nonnull;
+ *p.getT1()->getUnknown();
+ *p.getT1()->getNullable(); // [[unsafe]]
+ *p.getT1()->getNonnull();
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest,
+ ClassTemplateInstantiationWithPointersToTemplateStructsAsParameters) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 arg0;
+ T1 arg1;
+
+ T0 getT0();
+ T1 getT1();
+ };
+
+ void target(Struct2Arg<Struct2Arg<int *, int *_Nullable> *_Nullable,
+ Struct2Arg<int, int *> *_Nonnull>
+ p) {
+ *p.arg0; // [[unsafe]]
+ *p.arg0->arg0; // [[unsafe]]
+ *p.arg0->arg1; // [[unsafe]]
+ *p.arg1;
+ *p.arg1->arg1;
+
+ *p.arg0->getT0(); // [[unsafe]]
+ *p.arg0->getT1(); // [[unsafe]]
+ *p.arg1->getT1();
+
+ *p.getT0(); // [[unsafe]]
+ *p.getT0()->arg0; // [[unsafe]]
+ *p.getT0()->arg1; // [[unsafe]]
+ *p.getT1();
+ *p.getT1()->arg1;
+
+ *p.getT0()->getT0(); // [[unsafe]]
+ *p.getT0()->getT1(); // [[unsafe]]
+ *p.getT1()->getT1();
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct StructNonnullUnknown {
+ T0 nonnull;
+ T1 unknown;
+
+ T0 getNonnull();
+ T1 getUnknown();
+ };
+
+ template <typename T0, typename T1>
+ struct StructNonnullNullable {
+ T0 nonnull;
+ T1 nullable;
+
+ T0 getNonnull();
+ T1 getNullable();
+ };
+
+ template <typename T0, typename T1>
+ struct StructNullableNonnull {
+ T0 nullable;
+ T1 nonnull;
+
+ T0 getNullable();
+ T1 getNonnull();
+ };
+
+ template <typename T0, typename T1>
+ struct StructNullableNullable {
+ T0 nullable0;
+ T1 nullable1;
+
+ T0 getNullable0();
+ T1 getNullable1();
+ };
+
+ template <typename T0, typename T1>
+ struct StructNullableUnknown {
+ T0 nullable;
+ T1 unknown;
+
+ T0 getNullable();
+ T1 getUnknown();
+ };
+
+ template <typename T0, typename T1>
+ struct StructUnknownNullable {
+ T0 unknown;
+ T1 nullable;
+
+ T0 getUnknown();
+ T1 getNullable();
+ };
+
+ void
+ target(StructNonnullUnknown<
+ StructNonnullNullable<
+ StructNullableNullable<int* _Nullable, int* _Nullable>* _Nonnull,
+ StructUnknownNullable<int*,
+ int* _Nullable>* _Nullable>* _Nonnull,
+ StructUnknownNullable<
+ StructUnknownNullable<int*, int* _Nullable>*,
+ StructNullableNonnull<int* _Nullable,
+ int* _Nonnull>* _Nullable>*>
+ p) {
+ *p.nonnull;
+ *p.nonnull->nonnull;
+ *p.nonnull->nonnull->nullable0; // TODO: fix false negative.
+ *p.nonnull->nonnull->nullable1; // TODO: fix false negative.
+ *p.nonnull->nullable; // TODO: fix false negative.
+ *p.nonnull->nullable->unknown; // TODO: fix false negative.
+ *p.nonnull->nullable->nullable; // TODO: fix false negative.
+ *p.unknown->unknown;
+ *p.unknown->unknown->unknown;
+ *p.unknown->unknown->nullable; // TODO: fix false negative.
+ *p.unknown;
+ *p.unknown->nullable; // TODO: fix false negative.
+ *p.unknown->nullable->nullable; // TODO: fix false negative.
+ *p.unknown->nullable->nonnull; // TODO: fix false negative.
+
+ *p.nonnull->getNonnull();
+ *p.nonnull->getNonnull()->nullable0; // TODO: fix false negative.
+ *p.nonnull->getNonnull()->nullable1; // TODO: fix false negative.
+ *p.nonnull->getNullable();
+ *p.nonnull->getNullable()->unknown; // TODO: fix false negative.
+ *p.nonnull->getNullable()->nullable; // TODO: fix false negative.
+ *p.unknown->getUnknown();
+ *p.unknown->getUnknown()->unknown;
+ *p.unknown->getUnknown()->nullable; // TODO: fix false negative.
+ *p.unknown->getNullable(); // TODO: fix false negative.
+ *p.unknown->getNullable()->nullable; // TODO: fix false negative.
+ *p.unknown->getNullable()->nonnull; // TODO: fix false negative.
+
+ *p.nonnull->getNonnull()->getNullable0(); // TODO: fix false negative.
+ *p.nonnull->getNonnull()->getNullable1(); // TODO: fix false negative.
+ *p.nonnull->getNullable()->getUnknown(); // TODO: fix false negative.
+ *p.nonnull->getNullable()->getNullable(); // TODO: fix false negative.
+ *p.unknown->getUnknown()->getUnknown();
+ *p.unknown->getUnknown()->getNullable(); // TODO: fix false negative.
+ *p.unknown->getNullable()->getNullable(); // TODO: fix false negative.
+ *p.unknown->getNullable()->getNonnull(); // TODO: fix false negative.
+
+ *p.nonnull->nonnull->getNullable0(); // TODO: fix false negative.
+ *p.nonnull->nonnull->getNullable1(); // TODO: fix false negative.
+ *p.nonnull->nullable->getUnknown(); // TODO: fix false negative.
+ *p.nonnull->nullable->getNullable(); // TODO: fix false negative.
+ *p.unknown->unknown->getUnknown();
+ *p.unknown->unknown->getNullable(); // TODO: fix false negative.
+ *p.unknown->nullable->getNullable(); // TODO: fix false negative.
+ *p.unknown->nullable->getNonnull(); // TODO: fix false negative.
+
+ *p.getNonnull();
+ *p.getNonnull()->nonnull;
+ *p.getNonnull()->nonnull->nullable0; // TODO: fix false negative.
+ *p.getNonnull()->nonnull->nullable1; // TODO: fix false negative.
+ *p.getNonnull()->nullable; // TODO: fix false negative.
+ *p.getNonnull()->nullable->unknown; // TODO: fix false negative.
+ *p.getNonnull()->nullable->nullable; // TODO: fix false negative.
+ *p.getUnknown()->unknown;
+ *p.getUnknown()->unknown->unknown;
+ *p.getUnknown()->unknown->nullable; // TODO: fix false negative.
+ *p.getUnknown();
+ *p.getUnknown()->nullable; // TODO: fix false negative.
+ *p.getUnknown()->nullable->nullable; // TODO: fix false negative.
+ *p.getUnknown()->nullable->nonnull; // TODO: fix false negative.
+
+ *p.getNonnull()->getNonnull();
+ *p.getNonnull()->getNonnull()->nullable0; // TODO: fix false negative.
+ *p.getNonnull()->getNonnull()->nullable1; // TODO: fix false negative.
+ *p.getNonnull()->getNullable(); // TODO: fix false negative.
+ *p.getNonnull()->getNullable()->unknown; // TODO: fix false negative.
+ *p.getNonnull()->getNullable()->nullable; // TODO: fix false negative.
+ *p.getUnknown()->getUnknown();
+ *p.getUnknown()->getUnknown()->unknown;
+ *p.getUnknown()->getUnknown()->nullable; // TODO: fix false negative.
+ *p.getUnknown()->getNullable(); // TODO: fix false negative.
+ *p.getUnknown()->getNullable()->nullable; // TODO: fix false negative.
+ *p.getUnknown()->getNullable()->nonnull; // TODO: fix false negative.
+
+ *p.getNonnull()->nonnull->getNullable0(); // TODO: fix false negative.
+ *p.getNonnull()->nonnull->getNullable1(); // TODO: fix false negative.
+ *p.getNonnull()->nullable->getUnknown(); // TODO: fix false negative.
+ *p.getNonnull()->nullable->getNullable(); // TODO: fix false negative.
+ *p.getUnknown()->unknown->getUnknown();
+ *p.getUnknown()->unknown->getNullable(); // TODO: fix false negative.
+ *p.getUnknown()->nullable->getNullable(); // TODO: fix false negative.
+ *p.getUnknown()->nullable->getNonnull(); // TODO: fix false negative.
+
+ *p.getNonnull()->getNonnull()->getNullable0(); // TODO: fix false
+ // negative.
+ *p.getNonnull()->getNonnull()->getNullable1(); // TODO: fix false
+ // negative.
+ *p.getNonnull()->getNullable()->getUnknown(); // TODO: fix false
+ // negative.
+ *p.getNonnull()->getNullable()->getNullable(); // TODO: fix false
+ // negative.
+ *p.getUnknown()->getUnknown()->getUnknown();
+ *p.getUnknown()->getUnknown()->getNullable(); // TODO: fix false
+ // negative.
+ *p.getUnknown()->getNullable()->getNullable(); // TODO: fix false
+ // negative.
+ *p.getUnknown()->getNullable()->getNonnull(); // TODO: fix false
+ // negative.
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, FunctionTemplates) {
+ // Call expression that returns the first of two type parameters.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ T0 returnFirst();
+
+ void target() {
+ *returnFirst<int *_Nonnull, int *_Nullable>();
+ *returnFirst<int *, int *_Nullable>();
+ *returnFirst<int *_Nullable, int *_Nonnull>(); // [[unsafe]]
+ }
+ )cc"));
+
+ // Call expression that returns the second of two type parameters.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ T1 returnSecond();
+
+ void target() {
+ *returnSecond<int *_Nullable, int *_Nonnull>();
+ *returnSecond<int *_Nullable, int *>();
+ *returnSecond<int *, int *_Nullable>(); // [[unsafe]]
+ }
+ )cc"));
+
+ // Call expression that has an int parameter and two type parameters,
+ // returning the first type parameter.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <int I0, typename T1, typename T2>
+ T1 fn3ArgWithInt();
+
+ void target() {
+ *fn3ArgWithInt<1, int *_Nullable, int *>(); // [[unsafe]]
+ *fn3ArgWithInt<1, int *, int *_Nullable>();
+ }
+ )cc"));
+
+ // Call expression with template parameter substituted with a concrete struct.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct StructUnknownNullable {
+ int *var0;
+ int *_Nullable var1;
+
+ int *getVar0();
+ int *_Nullable getVar1();
+ };
+
+ template <typename T0, typename T1>
+ T1 returnSecond();
+
+ void target() {
+ *returnSecond<StructUnknownNullable, int *_Nullable>(); // [[unsafe]]
+ *returnSecond<int *_Nonnull, StructUnknownNullable *>();
+ // TODO: The following line is a false positive. We correctly compute the
+ // nullability of the expression, as confirmed by the call to
+ // `assert_nullability`. However, the dataflow framework currently does
+ // not model pointer values for this expression, which results in a (in
+ // this case incorrect) nullptr value.
+ *returnSecond<int *_Nonnull, StructUnknownNullable>() // [[unsafe]]
+ .var0; // TODO: Fix false positive.
+ __assert_nullability<NK_unspecified>(
+ returnSecond<int *_Nonnull, StructUnknownNullable>().var0);
+ *returnSecond<int *_Nonnull, StructUnknownNullable>().var1; // [[unsafe]]
+ *returnSecond<int *_Nonnull, StructUnknownNullable>().getVar0();
+ *returnSecond<int *_Nonnull, StructUnknownNullable>() // [[unsafe]]
+ .getVar1();
+ }
+ )cc"));
+}
+
+TEST(PointerNullabilityTest, ParenTypeInTemplate) {
+ checkDiagnostics(R"cc(
+ template <typename T>
+ struct S {
+ T(a);
+ T(*(b));
+
+ T (*f)();
+ T(((*g)))();
+ };
+
+ void targetNullable(S<int* _Nullable> s) {
+ *s.a; // [[unsafe]]
+ **s.b; // [[unsafe]]
+ *s.f;
+ *s.g;
+ // TODO: Handle function pointers. The analysis currently crashes.
+ // *s.f();
+ // *s.g();
+ }
+
+ void targetNonnull(S<int* _Nonnull> s) {
+ *s.a;
+ **s.b;
+ *s.f;
+ *s.g;
+ // TODO: Handle function pointers. The analysis currently crashes.
+ // *s.f();
+ // *s.g();
+ }
+ )cc");
+
+ checkDiagnostics(R"cc(
+ template <typename T>
+ struct S {
+ T arg;
+ };
+
+ void targetNullable(S<int* _Nullable>(a), S<int* _Nullable>(*(b)),
+ S<int(*_Nullable)> c, S<int*(*(*_Nullable))> d,
+ S<int* _Nullable (*)()> e) {
+ *a.arg; // [[unsafe]]
+ *b->arg; // [[unsafe]]
+ *c.arg; // [[unsafe]]
+ ***d.arg; // [[unsafe]]
+ *e.arg; // [[unsafe]]
+
+ // TODO: Handle function pointers. The analysis currently crashes.
+ // *e.arg();
+ }
+
+ void targetNonnull(S<int* _Nonnull>(a), S<int* _Nonnull>(*(b)),
+ S<int(*_Nonnull)> c, S<int*(*(*_Nonnull))> d,
+ S<int* _Nonnull (*)()> e) {
+ *a.arg;
+ *b->arg;
+ *c.arg;
+ ***d.arg;
+ *e.arg;
+
+ // TODO: Handle function pointers. The analysis currently crashes.
+ // *e.arg();
+ }
+ )cc");
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/temporary_materialization.cc b/nullability/test/temporary_materialization.cc
new file mode 100644
index 0000000..4851c88
--- /dev/null
+++ b/nullability/test/temporary_materialization.cc
@@ -0,0 +1,80 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for temporary materialization.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, NonFlowSensitiveMaterializeTemporaryExpr) {
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ int *_Nonnull makeNonnull();
+ int *_Nullable makeNullable();
+ int *makeUnannotated();
+
+ template <typename T>
+ T identity(const T &);
+
+ void target() {
+ {
+ *identity<int *_Nonnull>(makeNonnull());
+ int *const &p = makeNonnull();
+ *p;
+ }
+ {
+ *identity<int *_Nullable>(makeNullable()); // [[unsafe]]
+ int *const &p = makeNullable();
+ *p; // [[unsafe]]
+ }
+ {
+ *identity<int *>(makeUnannotated());
+ int *const &p = makeUnannotated();
+ *p;
+ }
+ }
+ )cc"));
+
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ template <typename T0, typename T1>
+ struct Struct2Arg {
+ T0 getT0();
+ T1 getT1();
+ };
+
+ template <typename T>
+ T make();
+
+ template <typename T>
+ T identity(const T &);
+
+ void target(Struct2Arg<int *, int *_Nullable> &p) {
+ *identity<Struct2Arg<int *, int *_Nullable>>(p).getT0();
+ *identity<Struct2Arg<int *, int *_Nullable>>(
+ make<Struct2Arg<int *, int *_Nullable>>())
+ .getT0();
+ *identity<Struct2Arg<int *, int *_Nullable>>(
+ Struct2Arg<int *, int *_Nullable>(p))
+ .getT0();
+ *identity<int *>(p.getT0());
+ *identity<Struct2Arg<int *, int *_Nullable>>(p).getT1(); // [[unsafe]]
+ *identity<Struct2Arg<int *, int *_Nullable>>( // [[unsafe]]
+ make<Struct2Arg<int *, int *_Nullable>>())
+ .getT1();
+ *identity<Struct2Arg<int *, int *_Nullable>>( // [[unsafe]]
+ Struct2Arg<int *, int *_Nullable>(p))
+ .getT1();
+ *identity<int *_Nullable>(p.getT1()); // [[unsafe]]
+ }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/this_pointer.cc b/nullability/test/this_pointer.cc
new file mode 100644
index 0000000..cc4ed74
--- /dev/null
+++ b/nullability/test/this_pointer.cc
@@ -0,0 +1,39 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for the treatment of the `this` pointer (which is always nonnull).
+#include <optional>
+#include <set>
+#include <string>
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, ThisPointer) {
+ // (->) implicit `this`
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ void foo();
+ void target() { foo(); }
+ };
+ )cc"));
+
+ // (->) explicit `this`
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ struct Foo {
+ void foo();
+ void target() { this->foo(); }
+ };
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/test/variance.cc b/nullability/test/variance.cc
new file mode 100644
index 0000000..2bb41da
--- /dev/null
+++ b/nullability/test/variance.cc
@@ -0,0 +1,29 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Tests for correct treatment of type variance.
+
+#include "nullability/test/check_diagnostics.h"
+#include "third_party/llvm/llvm-project/third-party/unittest/googletest/include/gtest/gtest.h"
+
+namespace clang {
+namespace tidy {
+namespace nullability {
+namespace {
+
+TEST(PointerNullabilityTest, NonConstPointerIsInvariant) {
+ // TODO(b/275458593): This test demonstrates a bug in the checker. The call
+ // `target(pp)` should be flagged as an error because non-const pointers are
+ // invariant over their pointee type and we should therefore not allow `int *
+ // _Nonnull * _Nonnull` to be converted to `int * _Nullable * _Nonnull`.
+ EXPECT_TRUE(checkDiagnostics(R"cc(
+ void callee(int* _Nullable* _Nonnull pp);
+ void target(int* _Nonnull* _Nonnull pp) { target(pp); }
+ )cc"));
+}
+
+} // namespace
+} // namespace nullability
+} // namespace tidy
+} // namespace clang
diff --git a/nullability/type_nullability.cc b/nullability/type_nullability.cc
new file mode 100644
index 0000000..307239d
--- /dev/null
+++ b/nullability/type_nullability.cc
@@ -0,0 +1,106 @@
+// Part of the Crubit project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "absl/log/check.h"
+#include "nullability/pointer_nullability.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Type.h"
+#include "clang/AST/TypeVisitor.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Basic/Specifiers.h"
+
+namespace clang::tidy::nullability {
+namespace {
+
+// Visitor to rebuild a QualType with explicit nullability.
+// Extra AttributedType nodes are added wrapping interior PointerTypes, and
+// other sugar is added as needed to allow this (e.g. TypeSpecializationType).
+//
+// We only have to handle types that have nontrivial nullability vectors, i.e.
+// those handled by NullabilityWalker.
+// Additionally, we only operate on canonical types (otherwise the sugar we're
+// adding could conflict with existing sugar).
+//
+// This needs to stay in sync with the other algorithms that manipulate
+// nullability data structures for particular types: the non-flow-sensitive
+// transfer and NullabilityWalker.
+struct Visitor : public TypeVisitor<Visitor, QualType> {
+ Visitor(ArrayRef<NullabilityKind> Nullability, ASTContext& Ctx)
+ : Nullability(Nullability), Ctx(Ctx) {}
+
+ bool done() const { return Nullability.empty(); }
+
+ using Base = TypeVisitor<Visitor, QualType>;
+ using Base::Visit;
+ QualType Visit(QualType T) {
+ if (T.isNull()) return T;
+ return Ctx.getQualifiedType(Visit(T.getTypePtr()), T.getLocalQualifiers());
+ }
+ TemplateArgument Visit(TemplateArgument TA) {
+ if (TA.getKind() == TemplateArgument::Type)
+ return TemplateArgument(Visit(TA.getAsType()));
+ return TA;
+ }
+
+ // Default behavior for unhandled types: do not transform.
+ QualType VisitType(const Type* T) { return QualType(T, 0); }
+
+ QualType VisitPointerType(const PointerType* PT) {
+ CHECK(!Nullability.empty())
+ << "Nullability vector too short at " << QualType(PT, 0).getAsString();
+ NullabilityKind NK = Nullability.front();
+ Nullability = Nullability.drop_front();
+
+ QualType Rebuilt = Ctx.getPointerType(Visit(PT->getPointeeType()));
+ if (NK == NullabilityKind::Unspecified) return Rebuilt;
+ return Ctx.getAttributedType(AttributedType::getNullabilityAttrKind(NK),
+ Rebuilt, Rebuilt);
+ }
+
+ QualType VisitRecordType(const RecordType* RT) {
+ if (const auto* CTSD =
+ dyn_cast<ClassTemplateSpecializationDecl>(RT->getDecl())) {
+ std::vector<TemplateArgument> TransformedArgs;
+ for (const auto& Arg : CTSD->getTemplateArgs().asArray())
+ TransformedArgs.push_back(Visit(Arg));
+ return Ctx.getTemplateSpecializationType(
+ TemplateName(CTSD->getSpecializedTemplate()), TransformedArgs,
+ QualType(RT, 0));
+ }
+ return QualType(RT, 0);
+ }
+
+ QualType VisitFunctionProtoType(const FunctionProtoType* T) {
+ QualType Ret = Visit(T->getReturnType());
+ std::vector<QualType> Params;
+ for (const auto& Param : T->getParamTypes()) Params.push_back(Visit(Param));
+ return Ctx.getFunctionType(Ret, Params, T->getExtProtoInfo());
+ }
+
+ private:
+ ArrayRef<NullabilityKind> Nullability;
+ ASTContext& Ctx;
+};
+
+} // namespace
+
+QualType rebuildWithNullability(QualType T,
+ ArrayRef<NullabilityKind> Nullability,
+ ASTContext& Ctx) {
+ Visitor V(Nullability, Ctx);
+ QualType Result = V.Visit(T.getCanonicalType());
+ CHECK(V.done()) << "Nullability vector[" << Nullability.size()
+ << "] too long for " << T.getAsString();
+ return Result;
+}
+
+std::string printWithNullability(QualType T,
+ ArrayRef<NullabilityKind> Nullability,
+ ASTContext& Ctx) {
+ return rebuildWithNullability(T, Nullability, Ctx)
+ .getAsString(Ctx.getPrintingPolicy());
+}
+
+} // namespace clang::tidy::nullability